@firecms/core 3.0.0-beta.12 → 3.0.0-beta.13

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 (84) hide show
  1. package/dist/app/Scaffold.d.ts +4 -0
  2. package/dist/components/ArrayContainer.d.ts +12 -5
  3. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
  4. package/dist/components/EntityJsonPreview.d.ts +3 -0
  5. package/dist/core/DefaultAppBar.d.ts +8 -2
  6. package/dist/core/EntityEditView.d.ts +8 -1
  7. package/dist/core/FireCMS.d.ts +2 -2
  8. package/dist/form/EntityForm.d.ts +5 -1
  9. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  10. package/dist/hooks/data/save.d.ts +1 -1
  11. package/dist/index.es.js +8630 -8578
  12. package/dist/index.es.js.map +1 -1
  13. package/dist/index.umd.js +8663 -8612
  14. package/dist/index.umd.js.map +1 -1
  15. package/dist/internal/useBuildDataSource.d.ts +3 -2
  16. package/dist/internal/useBuildSideEntityController.d.ts +3 -3
  17. package/dist/types/collections.d.ts +6 -0
  18. package/dist/types/fields.d.ts +4 -1
  19. package/dist/types/firecms.d.ts +1 -1
  20. package/dist/types/navigation.d.ts +4 -0
  21. package/dist/types/plugins.d.ts +1 -0
  22. package/dist/types/properties.d.ts +22 -1
  23. package/dist/util/createFormexStub.d.ts +2 -0
  24. package/dist/util/navigation_utils.d.ts +2 -1
  25. package/dist/util/objects.d.ts +1 -1
  26. package/dist/util/property_utils.d.ts +2 -2
  27. package/dist/util/references.d.ts +2 -2
  28. package/dist/util/resolutions.d.ts +11 -6
  29. package/package.json +6 -5
  30. package/src/app/Scaffold.tsx +13 -3
  31. package/src/components/ArrayContainer.tsx +40 -23
  32. package/src/components/DeleteEntityDialog.tsx +4 -2
  33. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +8 -6
  34. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -2
  35. package/src/components/EntityCollectionView/EntityCollectionView.tsx +22 -6
  36. package/src/components/EntityJsonPreview.tsx +66 -0
  37. package/src/components/EntityPreview.tsx +10 -3
  38. package/src/components/EntityView.tsx +4 -1
  39. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -1
  40. package/src/components/VirtualTable/VirtualTable.tsx +29 -1
  41. package/src/components/common/default_entity_actions.tsx +15 -4
  42. package/src/core/DefaultAppBar.tsx +17 -5
  43. package/src/core/EntityEditView.tsx +111 -35
  44. package/src/core/FireCMS.tsx +13 -12
  45. package/src/form/EntityForm.tsx +31 -23
  46. package/src/form/PropertyFieldBinding.tsx +8 -5
  47. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +4 -1
  48. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -0
  49. package/src/form/field_bindings/BlockFieldBinding.tsx +1 -1
  50. package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
  51. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +6 -2
  52. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -2
  53. package/src/form/field_bindings/RepeatFieldBinding.tsx +8 -2
  54. package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
  55. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +5 -2
  56. package/src/hooks/data/save.ts +24 -29
  57. package/src/hooks/useBuildNavigationController.tsx +12 -1
  58. package/src/hooks/useFireCMSContext.tsx +0 -30
  59. package/src/internal/useBuildDataSource.ts +9 -5
  60. package/src/internal/useBuildSideEntityController.tsx +26 -20
  61. package/src/preview/PropertyPreview.tsx +4 -2
  62. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +4 -3
  63. package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
  64. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +4 -2
  65. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
  66. package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -2
  67. package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -2
  68. package/src/routes/FireCMSRoute.tsx +15 -4
  69. package/src/types/collections.ts +7 -0
  70. package/src/types/fields.tsx +5 -1
  71. package/src/types/firecms.tsx +1 -1
  72. package/src/types/navigation.ts +5 -0
  73. package/src/types/plugins.tsx +1 -0
  74. package/src/types/properties.ts +27 -1
  75. package/src/types/side_entity_controller.tsx +5 -0
  76. package/src/util/createFormexStub.tsx +62 -0
  77. package/src/util/join_collections.ts +3 -1
  78. package/src/util/navigation_from_path.ts +5 -1
  79. package/src/util/navigation_utils.ts +64 -15
  80. package/src/util/objects.ts +5 -1
  81. package/src/util/property_utils.tsx +7 -3
  82. package/src/util/references.ts +8 -6
  83. package/src/util/resolutions.ts +17 -9
  84. package/src/util/useStorageUploadController.tsx +21 -2
@@ -93,6 +93,10 @@ export type EntityCollectionViewProps<M extends Record<string, any>> = {
93
93
  * It defaults to the collection path if not provided.
94
94
  */
95
95
  fullPath?: string;
96
+ /**
97
+ * Full path using navigation ids.
98
+ */
99
+ fullIdPath?: string;
96
100
  /**
97
101
  * If this is a subcollection, specify the parent collection ids.
98
102
  */
@@ -138,6 +142,7 @@ export type EntityCollectionViewProps<M extends Record<string, any>> = {
138
142
  export const EntityCollectionView = React.memo(
139
143
  function EntityCollectionView<M extends Record<string, any>>({
140
144
  fullPath: fullPathProp,
145
+ fullIdPath,
141
146
  parentCollectionIds,
142
147
  isSubCollection,
143
148
  className,
@@ -146,10 +151,11 @@ export const EntityCollectionView = React.memo(
146
151
  }: EntityCollectionViewProps<M>
147
152
  ) {
148
153
 
154
+
149
155
  const context = useFireCMSContext();
156
+ const navigation = useNavigationController();
150
157
  const fullPath = fullPathProp ?? collectionProp.path;
151
158
  const dataSource = useDataSource(collectionProp);
152
- const navigation = useNavigationController();
153
159
  const sideEntityController = useSideEntityController();
154
160
  const authController = useAuthController();
155
161
  const userConfigPersistence = useUserConfigurationPersistence();
@@ -240,6 +246,7 @@ export const EntityCollectionView = React.memo(
240
246
  navigateToEntity({
241
247
  navigation,
242
248
  path,
249
+ fullIdPath,
243
250
  sideEntityController,
244
251
  openEntityMode,
245
252
  collection,
@@ -259,6 +266,7 @@ export const EntityCollectionView = React.memo(
259
266
  collection,
260
267
  entityId: undefined,
261
268
  path: fullPath,
269
+ fullIdPath,
262
270
  sideEntityController,
263
271
  navigation,
264
272
  onClose: unselectNavigatedEntity
@@ -378,7 +386,8 @@ export const EntityCollectionView = React.memo(
378
386
  const resolvedCollection = useMemo(() => resolveCollection<M>({
379
387
  collection,
380
388
  path: fullPath,
381
- propertyConfigs: customizationController.propertyConfigs
389
+ propertyConfigs: customizationController.propertyConfigs,
390
+ authController,
382
391
  }), [collection, fullPath]);
383
392
 
384
393
  const getPropertyFor = useCallback(({
@@ -399,7 +408,8 @@ export const EntityCollectionView = React.memo(
399
408
  path: entity.path,
400
409
  values: entity.values,
401
410
  entityId: entity.id,
402
- propertyConfigs: customizationController.propertyConfigs
411
+ propertyConfigs: customizationController.propertyConfigs,
412
+ authController
403
413
  });
404
414
  }, [collection.properties, customizationController.propertyConfigs, resolvedCollection.properties]);
405
415
 
@@ -424,6 +434,7 @@ export const EntityCollectionView = React.memo(
424
434
  entityId: entity.id,
425
435
  selectedTab: subcollection.id ?? subcollection.path,
426
436
  path: fullPath,
437
+ fullIdPath,
427
438
  navigation,
428
439
  sideEntityController
429
440
  })
@@ -527,7 +538,7 @@ export const EntityCollectionView = React.memo(
527
538
  highlightEntity={setHighlightedEntity}
528
539
  unhighlightEntity={unselectNavigatedEntity}
529
540
  collection={collection}
530
- fullPath={fullPath}
541
+ fullPath={fullIdPath ?? fullPath}
531
542
  actions={actions}
532
543
  hideId={collection?.hideIdFromCollection}
533
544
  onCollectionChange={updateLastDeleteTimestamp}
@@ -691,6 +702,7 @@ export const EntityCollectionView = React.memo(
691
702
  getIdColumnWidth={getIdColumnWidth}
692
703
  additionalIDHeaderWidget={<EntityIdHeaderWidget
693
704
  path={fullPath}
705
+ fullIdPath={fullIdPath ?? fullPath}
694
706
  collection={collection}/>}
695
707
  openEntityMode={openEntityMode}
696
708
  />
@@ -807,10 +819,12 @@ function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityC
807
819
 
808
820
  function EntityIdHeaderWidget({
809
821
  collection,
810
- path
822
+ path,
823
+ fullIdPath
811
824
  }: {
812
825
  collection: EntityCollection,
813
- path: string
826
+ path: string,
827
+ fullIdPath: string
814
828
  }) {
815
829
 
816
830
  const navigation = useNavigationController();
@@ -848,6 +862,7 @@ function EntityIdHeaderWidget({
848
862
  collection,
849
863
  entityId,
850
864
  path,
865
+ fullIdPath,
851
866
  sideEntityController,
852
867
  navigation
853
868
  })
@@ -882,6 +897,7 @@ function EntityIdHeaderWidget({
882
897
  collection,
883
898
  entityId: id,
884
899
  path,
900
+ fullIdPath,
885
901
  sideEntityController,
886
902
  navigation
887
903
  })
@@ -0,0 +1,66 @@
1
+ import React, { useRef } from "react";
2
+ import { Highlight, themes } from "prism-react-renderer";
3
+ import { useModeController } from "../hooks";
4
+
5
+ export function EntityJsonPreview({ values }: { values: object }) {
6
+ const code = JSON.stringify(values, null, "\t");
7
+ const { mode } = useModeController();
8
+ const preRef = useRef<HTMLPreElement>(null);
9
+
10
+ // Global keydown handler
11
+ // const handleGlobalKeyDown = useCallback((e: KeyboardEvent) => {
12
+ // // Check for Control (Windows/Linux) or Command (macOS) + "a":
13
+ // if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "a") {
14
+ // // If our code view is mounted, perform selection
15
+ // if (preRef.current) {
16
+ // e.preventDefault();
17
+ // e.stopPropagation();
18
+ //
19
+ // const selection = window.getSelection();
20
+ // const range = document.createRange();
21
+ // range.selectNodeContents(preRef.current);
22
+ // if (selection) {
23
+ // selection.removeAllRanges();
24
+ // selection.addRange(range);
25
+ // }
26
+ // }
27
+ // }
28
+ // }, []);
29
+
30
+ // // Attach the global keydown listener when component mounts,
31
+ // // and remove it when it unmounts.
32
+ // useEffect(() => {
33
+ // document.addEventListener("keydown", handleGlobalKeyDown);
34
+ // return () => {
35
+ // document.removeEventListener("keydown", handleGlobalKeyDown);
36
+ // };
37
+ // }, [handleGlobalKeyDown]);
38
+
39
+ return (
40
+ <Highlight
41
+ theme={mode === "dark" ? themes.vsDark : themes.github}
42
+ code={code}
43
+ language="json"
44
+ >
45
+ {({ style, tokens, getLineProps, getTokenProps }) => (
46
+ <pre
47
+ // Bind the ref to our pre element so we can select its contents.
48
+ ref={preRef}
49
+ style={{
50
+ ...style,
51
+ backgroundColor: "inherit"
52
+ }}
53
+ className="max-w-6xl mx-auto p-8 rounded text-sm"
54
+ >
55
+ {tokens.map((line, i) => (
56
+ <div key={i} {...getLineProps({ line })} className="text-wrap">
57
+ {line.map((token, key) => (
58
+ <span key={key} {...getTokenProps({ token })} className="word-break" />
59
+ ))}
60
+ </div>
61
+ ))}
62
+ </pre>
63
+ )}
64
+ </Highlight>
65
+ );
66
+ }
@@ -13,7 +13,12 @@ import {
13
13
  } from "../util";
14
14
  import { cls, defaultBorderMixin, IconButton, KeyboardTabIcon, Skeleton, Tooltip, Typography } from "@firecms/ui";
15
15
  import { PreviewSize, PropertyPreview, SkeletonPropertyComponent } from "../preview";
16
- import { useCustomizationController, useNavigationController, useSideEntityController } from "../hooks";
16
+ import {
17
+ useAuthController,
18
+ useCustomizationController,
19
+ useNavigationController,
20
+ useSideEntityController
21
+ } from "../hooks";
17
22
  import { useAnalyticsController } from "../hooks/useAnalyticsController";
18
23
 
19
24
  export type EntityPreviewProps = {
@@ -46,6 +51,7 @@ export function EntityPreview({
46
51
  entity
47
52
  }: EntityPreviewProps) {
48
53
 
54
+ const authController = useAuthController();
49
55
  const analyticsController = useAnalyticsController();
50
56
  const sideEntityController = useSideEntityController();
51
57
  const customizationController = useCustomizationController();
@@ -62,10 +68,11 @@ export function EntityPreview({
62
68
  collection,
63
69
  path: entity.path,
64
70
  values: entity.values,
65
- propertyConfigs: customizationController.propertyConfigs
71
+ propertyConfigs: customizationController.propertyConfigs,
72
+ authController
66
73
  }), [collection]);
67
74
 
68
- const listProperties = useMemo(() => getEntityPreviewKeys(resolvedCollection, customizationController.propertyConfigs, previewProperties, size === "medium" || size === "large" ? 3 : 1),
75
+ const listProperties = useMemo(() => getEntityPreviewKeys(authController, resolvedCollection, customizationController.propertyConfigs, previewProperties, size === "medium" || size === "large" ? 3 : 1),
69
76
  [previewProperties, resolvedCollection, size]);
70
77
 
71
78
  const titleProperty = getEntityTitlePropertyKey(resolvedCollection, customizationController.propertyConfigs);
@@ -5,6 +5,7 @@ import { resolveCollection } from "../util";
5
5
  import { cls, defaultBorderMixin, IconButton, OpenInNewIcon } from "@firecms/ui";
6
6
  import { CustomizationController } from "../types/customization_controller";
7
7
  import { useCustomizationController } from "../hooks/useCustomizationController";
8
+ import { useAuthController } from "../hooks";
8
9
 
9
10
  /**
10
11
  * @group Components
@@ -24,13 +25,15 @@ export function EntityView<M extends Record<string, any>>(
24
25
  className
25
26
  }: EntityViewProps<M>) {
26
27
 
28
+ const authController = useAuthController();
27
29
  const customizationController: CustomizationController = useCustomizationController();
28
30
  const resolvedCollection: ResolvedEntityCollection<M> = useMemo(() => resolveCollection<M>({
29
31
  collection,
30
32
  path,
31
33
  entityId: entity.id,
32
34
  values: entity.values,
33
- propertyConfigs: customizationController.propertyConfigs
35
+ propertyConfigs: customizationController.propertyConfigs,
36
+ authController
34
37
  }), [collection, path, entity, customizationController.propertyConfigs]);
35
38
 
36
39
  const properties: ResolvedProperties = resolvedCollection.properties;
@@ -101,6 +101,7 @@ export function ReferenceSelectionTable<M extends Record<string, any>>(
101
101
  maxSelection,
102
102
  }: ReferenceSelectionInnerProps<M>) {
103
103
 
104
+ const authController = useAuthController();
104
105
  const sideDialogContext = useSideDialogContext();
105
106
  const sideEntityController = useSideEntityController();
106
107
  const navigation = useNavigationController();
@@ -253,7 +254,8 @@ export function ReferenceSelectionTable<M extends Record<string, any>>(
253
254
  collection: collection,
254
255
  path: fullPath,
255
256
  values: {},
256
- propertyConfigs: customizationController.propertyConfigs
257
+ propertyConfigs: customizationController.propertyConfigs,
258
+ authController
257
259
  }), [collection, customizationController.propertyConfigs, fullPath]);
258
260
 
259
261
  const displayedColumnIds = useColumnIds(resolvedCollection, false);
@@ -139,7 +139,35 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
139
139
  setColumns(columnsProp);
140
140
  }, [columnsProp]);
141
141
 
142
- const [measureRef, bounds] = useMeasure();
142
+ const [_, setForceUpdate] = useState(false);
143
+ useEffect(() => {
144
+ // Create a ResizeObserver to detect size changes more aggressively
145
+ if (tableRef.current) {
146
+ const resizeObserver = new ResizeObserver(() => {
147
+ // Force a re-render when size changes
148
+ setForceUpdate(prev => !prev);
149
+ });
150
+
151
+ resizeObserver.observe(tableRef.current);
152
+
153
+ return () => {
154
+ if (tableRef.current) {
155
+ resizeObserver.unobserve(tableRef.current);
156
+ }
157
+ resizeObserver.disconnect();
158
+ };
159
+ }
160
+ return () => {
161
+ }
162
+ }, [tableRef]);
163
+
164
+ const [measureRef, bounds] = useMeasure({
165
+ debounce: 50,
166
+ polyfill: ResizeObserver,
167
+ scroll: true,
168
+ // This is important for handling zooming in react-flow
169
+ offsetSize: true
170
+ });
143
171
 
144
172
  const onColumnResizeInternal = useCallback((params: OnVirtualTableColumnResizeParams) => {
145
173
  if (debug)
@@ -2,7 +2,7 @@ import { DeleteIcon, EditIcon, FileCopyIcon } from "@firecms/ui";
2
2
  import { EntityAction } from "../../types";
3
3
  import { DeleteEntityDialog } from "../DeleteEntityDialog";
4
4
  import { addRecentId } from "../EntityCollectionView/utils";
5
- import { navigateToEntity } from "../../util";
5
+ import { navigateToEntity, resolveDefaultSelectedView } from "../../util";
6
6
 
7
7
  export const editEntityAction: EntityAction = {
8
8
  icon: <EditIcon/>,
@@ -30,15 +30,24 @@ export const editEntityAction: EntityAction = {
30
30
  addRecentId(collection.id, entity.id);
31
31
  }
32
32
 
33
- const path = collection?.collectionGroup ? entity.path : (fullPath ?? entity.path)
33
+ const path = collection?.collectionGroup ? collection.id : (fullPath ?? collection?.id ?? entity.path);
34
+ const defaultSelectedView = resolveDefaultSelectedView(
35
+ collection ? collection.defaultSelectedView : undefined,
36
+ {
37
+ status: "existing",
38
+ entityId: entity.id,
39
+ }
40
+ );
34
41
  navigateToEntity({
35
42
  openEntityMode,
36
43
  collection,
37
44
  entityId: entity.id,
38
45
  path,
46
+ fullIdPath: path,
39
47
  sideEntityController: context.sideEntityController,
40
48
  onClose: () => unhighlightEntity?.(entity),
41
- navigation: context.navigation
49
+ navigation: context.navigation,
50
+ selectedTab: defaultSelectedView
42
51
  });
43
52
 
44
53
  return Promise.resolve(undefined);
@@ -63,12 +72,14 @@ export const copyEntityAction: EntityAction = {
63
72
  path: entity.path,
64
73
  entityId: entity.id
65
74
  });
66
- const path = collection?.collectionGroup ? entity.path : (fullPath ?? entity.path)
75
+
76
+ const path = collection?.collectionGroup ? collection.id : (fullPath ?? collection?.id ?? entity.path);
67
77
  navigateToEntity({
68
78
  openEntityMode,
69
79
  collection,
70
80
  entityId: entity.id,
71
81
  path,
82
+ fullIdPath: path,
72
83
  copy: true,
73
84
  sideEntityController: context.sideEntityController,
74
85
  onClose: () => unhighlightEntity?.(entity),
@@ -22,12 +22,19 @@ import { useBreadcrumbsController } from "../hooks/useBreadcrumbsController";
22
22
 
23
23
  export type DefaultAppBarProps<ADDITIONAL_PROPS = object> = {
24
24
 
25
+ /**
26
+ * The content of the app bar, usually a title or logo. This includes a link to the home page.
27
+ */
25
28
  title?: React.ReactNode;
29
+
26
30
  /**
27
- * A component that gets rendered on the upper side of the main toolbar
31
+ * A component that gets rendered on the upper side to the end of the main toolbar
28
32
  */
29
33
  endAdornment?: React.ReactNode;
30
34
 
35
+ /**
36
+ * A component that gets rendered on the upper side to the start of the main toolbar
37
+ */
31
38
  startAdornment?: React.ReactNode;
32
39
 
33
40
  dropDownActions?: React.ReactNode;
@@ -57,14 +64,18 @@ export const DefaultAppBar = function DefaultAppBar({
57
64
  includeModeToggle = true,
58
65
  className,
59
66
  style,
60
- user: userProp
67
+ user: userProp,
68
+ logo: logoProp,
61
69
  }: DefaultAppBarProps) {
62
70
 
63
71
  const {
64
72
  hasDrawer,
65
73
  drawerOpen,
66
- logo
74
+ logo: appLogo
67
75
  } = useApp();
76
+
77
+ const logo = logoProp ?? appLogo;
78
+
68
79
  const navigation = useNavigationController();
69
80
 
70
81
  const breadcrumbs = useBreadcrumbsController();
@@ -134,7 +145,7 @@ export const DefaultAppBar = function DefaultAppBar({
134
145
  </Link>
135
146
  </div>}
136
147
 
137
- {breadcrumbs.breadcrumbs && <div className="mr-8 hidden lg:block">
148
+ {(breadcrumbs.breadcrumbs ?? []).length > 0 && <div className="mr-8 hidden lg:block">
138
149
  <div className={"flex flex-row gap-2"}>
139
150
  {breadcrumbs.breadcrumbs.map((breadcrumb, index) => {
140
151
  return <React.Fragment key={breadcrumb.url + "_" + index}>
@@ -176,7 +187,8 @@ export const DefaultAppBar = function DefaultAppBar({
176
187
  </IconButton>}>
177
188
  <MenuItem onClick={() => setMode("dark")}><DarkModeIcon size={"smallest"}/> Dark</MenuItem>
178
189
  <MenuItem onClick={() => setMode("light")}><LightModeIcon size={"smallest"}/> Light </MenuItem>
179
- <MenuItem onClick={() => setMode("system")}> <BrightnessMediumIcon size={"smallest"}/>System</MenuItem>
190
+ <MenuItem onClick={() => setMode("system")}> <BrightnessMediumIcon
191
+ size={"smallest"}/>System</MenuItem>
180
192
  </Menu>}
181
193
 
182
194
  <Menu trigger={avatarComponent}>