@firecms/collection_editor 3.0.0-canary.20 → 3.0.0-canary.201

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 (74) hide show
  1. package/LICENSE +114 -21
  2. package/dist/ConfigControllerProvider.d.ts +2 -2
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.es.js +10061 -4770
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +10751 -3
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/types/collection_editor_controller.d.ts +4 -2
  9. package/dist/types/collection_inference.d.ts +1 -1
  10. package/dist/types/config_permissions.d.ts +2 -2
  11. package/dist/types/persisted_collection.d.ts +1 -1
  12. package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
  13. package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
  14. package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
  15. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +3 -1
  16. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +3 -2
  17. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +1 -1
  18. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
  19. package/dist/ui/collection_editor/LayoutModeSwitch.d.ts +5 -0
  20. package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
  21. package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
  22. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
  23. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
  24. package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
  25. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
  26. package/dist/useCollectionEditorPlugin.d.ts +8 -11
  27. package/dist/utils/collections.d.ts +6 -0
  28. package/package.json +24 -35
  29. package/src/ConfigControllerProvider.tsx +67 -64
  30. package/src/index.ts +1 -0
  31. package/src/types/collection_editor_controller.tsx +7 -4
  32. package/src/types/collection_inference.ts +1 -1
  33. package/src/types/config_permissions.ts +1 -1
  34. package/src/types/persisted_collection.ts +2 -3
  35. package/src/ui/CollectionViewHeaderAction.tsx +10 -5
  36. package/src/ui/EditorCollectionAction.tsx +10 -63
  37. package/src/ui/EditorCollectionActionStart.tsx +88 -0
  38. package/src/ui/HomePageEditorCollectionAction.tsx +19 -13
  39. package/src/ui/NewCollectionButton.tsx +1 -1
  40. package/src/ui/NewCollectionCard.tsx +3 -3
  41. package/src/ui/PropertyAddColumnComponent.tsx +11 -6
  42. package/src/ui/collection_editor/CollectionDetailsForm.tsx +88 -11
  43. package/src/ui/collection_editor/CollectionEditorDialog.tsx +101 -34
  44. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +8 -7
  45. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +39 -36
  46. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +6 -5
  47. package/src/ui/collection_editor/EnumForm.tsx +10 -6
  48. package/src/ui/collection_editor/GetCodeDialog.tsx +56 -26
  49. package/src/ui/collection_editor/LayoutModeSwitch.tsx +54 -0
  50. package/src/ui/collection_editor/PropertyEditView.tsx +257 -79
  51. package/src/ui/collection_editor/PropertyFieldPreview.tsx +7 -10
  52. package/src/ui/collection_editor/PropertyTree.tsx +9 -7
  53. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
  54. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +3 -5
  55. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +26 -9
  56. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +42 -9
  57. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
  58. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +54 -47
  59. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -1
  60. package/src/ui/collection_editor/properties/MapPropertyField.tsx +7 -6
  61. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
  62. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +2 -0
  63. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
  64. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +34 -19
  65. package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
  66. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +1 -0
  67. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +2 -2
  68. package/src/ui/collection_editor/templates/pages_template.ts +1 -6
  69. package/src/useCollectionEditorPlugin.tsx +33 -32
  70. package/src/utils/collections.ts +36 -0
  71. package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
  72. package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
  73. package/src/ui/RootCollectionSuggestions.tsx +0 -63
  74. package/src/ui/collection_editor/PropertySelectItem.tsx +0 -32
@@ -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);
@@ -226,17 +228,13 @@ export function CollectionPropertiesEditorForm({
226
228
  namespace
227
229
  }: OnPropertyChangedParams) => {
228
230
 
231
+ console.log("!!!!!! onPropertyChanged", property)
232
+
229
233
  const fullId = id ? getFullId(id, namespace) : undefined;
230
234
  const propertyPath = fullId ? idToPropertiesPath(fullId) : undefined;
231
235
 
232
236
  // If the id has changed we need to a little cleanup
233
237
  if (previousId && previousId !== id) {
234
- console.debug("onPropertyChanged, id change", {
235
- id,
236
- property,
237
- previousId,
238
- namespace
239
- })
240
238
 
241
239
  const previousFullId = getFullId(previousId, namespace);
242
240
  const previousPropertyPath = idToPropertiesPath(previousFullId);
@@ -273,7 +271,7 @@ export function CollectionPropertiesEditorForm({
273
271
 
274
272
  };
275
273
 
276
- const onPropertyErrorInternal = useCallback((id: string, namespace?: string, error?: Record<string, any>) => {
274
+ const onPropertyErrorInternal = (id: string, namespace?: string, error?: Record<string, any>) => {
277
275
  const propertyPath = id ? getFullId(id, namespace) : undefined;
278
276
  console.debug("onPropertyErrorInternal", {
279
277
  id,
@@ -286,7 +284,7 @@ export function CollectionPropertiesEditorForm({
286
284
  onPropertyError(id, namespace, hasError ? error : undefined);
287
285
  setFieldError(idToPropertiesPath(propertyPath), hasError ? "Property error" : undefined);
288
286
  }
289
- }, [])
287
+ }
290
288
 
291
289
  const closePropertyDialog = () => {
292
290
  setSelectedPropertyIndex(undefined);
@@ -301,9 +299,9 @@ export function CollectionPropertiesEditorForm({
301
299
  ? values.propertiesOrder
302
300
  : Object.keys(values.properties)) as string[];
303
301
 
304
- const owner = useMemo(() => values.ownerId ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
302
+ const owner = useMemo(() => values.ownerId && getUser ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
305
303
 
306
- const onPropertyClick = useCallback((propertyKey: string, namespace?: string) => {
304
+ const onPropertyClick = (propertyKey: string, namespace?: string) => {
307
305
  console.debug("CollectionEditor: onPropertyClick", {
308
306
  propertyKey,
309
307
  namespace
@@ -311,11 +309,11 @@ export function CollectionPropertiesEditorForm({
311
309
  setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
312
310
  setSelectedPropertyKey(propertyKey);
313
311
  setSelectedPropertyNamespace(namespace);
314
- }, [usedPropertiesOrder]);
312
+ };
315
313
 
316
314
  const body = (
317
- <div className={"grid grid-cols-12 gap-2 h-full bg-gray-50 dark:bg-gray-900"}>
318
- <div className={cn(
315
+ <div className={"grid grid-cols-12 gap-2 h-full bg-surface-50 dark:bg-surface-900"}>
316
+ <div className={cls(
319
317
  "p-4 md:p-8 pb-20 md:pb-20",
320
318
  "col-span-12 lg:col-span-5 h-full overflow-auto",
321
319
  !asDialog && "border-r " + defaultBorderMixin
@@ -349,7 +347,8 @@ export function CollectionPropertiesEditorForm({
349
347
  </div>}
350
348
 
351
349
  <div className="ml-1 mt-2 flex flex-row gap-2">
352
- <Tooltip title={"Get the code for this collection"}>
350
+ <Tooltip title={"Get the code for this collection"}
351
+ asChild={true}>
353
352
  <IconButton
354
353
  variant={"filled"}
355
354
  disabled={inferringProperties}
@@ -357,15 +356,17 @@ export function CollectionPropertiesEditorForm({
357
356
  <CodeIcon/>
358
357
  </IconButton>
359
358
  </Tooltip>
360
- {inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}>
359
+ {inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
360
+ asChild={true}>
361
361
  <IconButton
362
362
  variant={"filled"}
363
363
  disabled={inferringProperties}
364
364
  onClick={inferPropertiesFromData}>
365
- {inferringProperties ? <CircularProgress size={"small"}/> : <AutoAwesomeIcon/>}
365
+ {inferringProperties ? <CircularProgress size={"small"}/> : <AutorenewIcon/>}
366
366
  </IconButton>
367
367
  </Tooltip>}
368
- <Tooltip title={"Add new property"}>
368
+ <Tooltip title={"Add new property"}
369
+ asChild={true}>
369
370
  <Button
370
371
  variant={"outlined"}
371
372
  onClick={() => setNewPropertyDialogOpen(true)}>
@@ -469,6 +470,7 @@ export function CollectionPropertiesEditorForm({
469
470
  getData={getData}
470
471
  propertyConfigs={propertyConfigs}
471
472
  collectionEditable={collectionEditable}
473
+ onCancel={closePropertyDialog}
472
474
  onOkClicked={asDialog
473
475
  ? closePropertyDialog
474
476
  : undefined
@@ -496,11 +498,12 @@ export function CollectionPropertiesEditorForm({
496
498
  collectionEditable={collectionEditable}
497
499
  existingPropertyKeys={values.propertiesOrder as string[]}/>
498
500
 
499
- <GetCodeDialog
500
- collection={values}
501
- open={codeDialogOpen}
502
- onOpenChange={setCodeDialogOpen}/>
503
-
501
+ <ErrorBoundary>
502
+ <GetCodeDialog
503
+ collection={values}
504
+ open={codeDialogOpen}
505
+ onOpenChange={setCodeDialogOpen}/>
506
+ </ErrorBoundary>
504
507
  </>
505
508
  );
506
509
  }
@@ -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>
@@ -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
+ }