@firecms/core 3.0.0-canary.167 → 3.0.0-canary.169

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 (96) hide show
  1. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +2 -1
  2. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
  3. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +15 -1
  4. package/dist/components/EntityCollectionTable/index.d.ts +1 -1
  5. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
  6. package/dist/components/SelectableTable/SelectableTable.d.ts +12 -2
  7. package/dist/components/UnsavedChangesDialog.d.ts +8 -0
  8. package/dist/components/VirtualTable/VirtualTableProps.d.ts +13 -0
  9. package/dist/components/common/index.d.ts +1 -1
  10. package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +10 -2
  11. package/dist/components/common/useDebouncedCallback.d.ts +1 -0
  12. package/dist/components/common/useScrollRestoration.d.ts +14 -0
  13. package/dist/contexts/BreacrumbsContext.d.ts +8 -0
  14. package/dist/core/EntityEditView.d.ts +25 -7
  15. package/dist/core/FireCMSRouter.d.ts +4 -0
  16. package/dist/core/NavigationRoutes.d.ts +0 -1
  17. package/dist/core/index.d.ts +2 -1
  18. package/dist/hooks/data/useEntityFetch.d.ts +2 -1
  19. package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
  20. package/dist/hooks/useBuildNavigationController.d.ts +0 -1
  21. package/dist/index.es.js +2344 -1305
  22. package/dist/index.es.js.map +1 -1
  23. package/dist/index.umd.js +2344 -1306
  24. package/dist/index.umd.js.map +1 -1
  25. package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
  26. package/dist/routes/CustomCMSRoute.d.ts +4 -0
  27. package/dist/routes/FireCMSRoute.d.ts +1 -0
  28. package/dist/routes/HomePageRoute.d.ts +3 -0
  29. package/dist/types/collections.d.ts +18 -6
  30. package/dist/types/datasource.d.ts +2 -2
  31. package/dist/types/dialogs_controller.d.ts +2 -6
  32. package/dist/types/entities.d.ts +1 -1
  33. package/dist/types/entity_actions.d.ts +1 -6
  34. package/dist/types/navigation.d.ts +28 -12
  35. package/dist/types/plugins.d.ts +1 -0
  36. package/dist/types/side_entity_controller.d.ts +1 -1
  37. package/dist/util/entity_cache.d.ts +23 -0
  38. package/dist/util/navigation_from_path.d.ts +5 -1
  39. package/dist/util/navigation_utils.d.ts +12 -1
  40. package/package.json +5 -5
  41. package/src/components/CircularProgressCenter.tsx +1 -1
  42. package/src/components/DeleteEntityDialog.tsx +1 -1
  43. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +40 -19
  44. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +10 -4
  45. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +18 -1
  46. package/src/components/EntityCollectionTable/index.tsx +1 -1
  47. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -3
  48. package/src/components/EntityCollectionView/EntityCollectionView.tsx +79 -38
  49. package/src/components/NotFoundPage.tsx +2 -2
  50. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -5
  51. package/src/components/SelectableTable/SelectableTable.tsx +18 -3
  52. package/src/components/UnsavedChangesDialog.tsx +42 -0
  53. package/src/components/VirtualTable/VirtualTable.tsx +19 -2
  54. package/src/components/VirtualTable/VirtualTableProps.tsx +15 -0
  55. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +1 -1
  56. package/src/components/common/default_entity_actions.tsx +24 -10
  57. package/src/components/common/index.ts +1 -1
  58. package/src/components/common/{useDataSourceEntityCollectionTableController.tsx → useDataSourceTableController.tsx} +122 -13
  59. package/src/components/common/useDebouncedCallback.tsx +20 -0
  60. package/src/components/common/useScrollRestoration.tsx +68 -0
  61. package/src/contexts/BreacrumbsContext.tsx +38 -0
  62. package/src/contexts/DialogsProvider.tsx +0 -1
  63. package/src/core/DefaultAppBar.tsx +28 -4
  64. package/src/core/EntityEditView.tsx +457 -296
  65. package/src/core/EntitySidePanel.tsx +72 -6
  66. package/src/core/FireCMS.tsx +7 -4
  67. package/src/core/FireCMSRouter.tsx +17 -0
  68. package/src/core/NavigationRoutes.tsx +15 -28
  69. package/src/core/SideDialogs.tsx +10 -5
  70. package/src/core/index.tsx +4 -2
  71. package/src/hooks/data/save.ts +1 -1
  72. package/src/hooks/data/useCollectionFetch.tsx +1 -1
  73. package/src/hooks/data/useEntityFetch.tsx +5 -1
  74. package/src/hooks/useBreadcrumbsController.tsx +31 -0
  75. package/src/hooks/useBuildNavigationController.tsx +59 -21
  76. package/src/hooks/useLargeLayout.tsx +0 -35
  77. package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
  78. package/src/internal/useBuildSideDialogsController.tsx +4 -2
  79. package/src/internal/useBuildSideEntityController.tsx +157 -77
  80. package/src/internal/useUnsavedChangesDialog.tsx +126 -92
  81. package/src/routes/CustomCMSRoute.tsx +21 -0
  82. package/src/routes/FireCMSRoute.tsx +230 -0
  83. package/src/routes/HomePageRoute.tsx +17 -0
  84. package/src/types/collections.ts +19 -6
  85. package/src/types/datasource.ts +2 -1
  86. package/src/types/dialogs_controller.tsx +2 -6
  87. package/src/types/entities.ts +1 -1
  88. package/src/types/entity_actions.tsx +1 -7
  89. package/src/types/navigation.ts +35 -16
  90. package/src/types/plugins.tsx +1 -0
  91. package/src/types/side_entity_controller.tsx +1 -1
  92. package/src/util/entity_cache.ts +196 -0
  93. package/src/util/navigation_from_path.ts +10 -2
  94. package/src/util/navigation_utils.ts +53 -1
  95. package/src/util/parent_references_from_path.ts +3 -3
  96. /package/src/util/{common.tsx → common.ts} +0 -0
@@ -19,7 +19,7 @@ import {
19
19
  import {
20
20
  EntityCollectionRowActions,
21
21
  EntityCollectionTable,
22
- useDataSourceEntityCollectionTableController
22
+ useDataSourceTableController
23
23
  } from "../EntityCollectionTable";
24
24
 
25
25
  import {
@@ -28,6 +28,8 @@ import {
28
28
  canEditEntity,
29
29
  getPropertyInPath,
30
30
  mergeDeep,
31
+ mergeEntityActions,
32
+ navigateToEntity,
31
33
  resolveCollection,
32
34
  resolveProperty
33
35
  } from "../../util";
@@ -62,6 +64,9 @@ import { setIn } from "@firecms/formex";
62
64
  import { getSubcollectionColumnId } from "../EntityCollectionTable/internal/common";
63
65
  import {
64
66
  COLLECTION_GROUP_PARENT_ID,
67
+ copyEntityAction,
68
+ deleteEntityAction,
69
+ editEntityAction,
65
70
  OnCellValueChange,
66
71
  OnColumnResizeParams,
67
72
  UniqueFieldValidator,
@@ -70,13 +75,14 @@ import {
70
75
  } from "../common";
71
76
  import { PopupFormField } from "../EntityCollectionTable/internal/popup_field/PopupFormField";
72
77
  import { GetPropertyForProps } from "../EntityCollectionTable/EntityCollectionTableProps";
73
- import { copyEntityAction, deleteEntityAction, editEntityAction } from "../common/default_entity_actions";
74
78
  import { DeleteEntityDialog } from "../DeleteEntityDialog";
75
79
  import { useAnalyticsController } from "../../hooks/useAnalyticsController";
76
80
  import { useSelectionController } from "./useSelectionController";
77
81
  import { EntityCollectionViewStartActions } from "./EntityCollectionViewStartActions";
78
82
  import { addRecentId, getRecentIds } from "./utils";
79
- import { mergeEntityActions } from "../../util/entity_actions";
83
+ import { useScrollRestoration } from "../common/useScrollRestoration";
84
+
85
+ const DEFAULT_ENTITY_OPEN_MODE: "side_panel" | "full_screen" = "side_panel";
80
86
 
81
87
  /**
82
88
  * @group Components
@@ -98,6 +104,11 @@ export type EntityCollectionViewProps<M extends Record<string, any>> = {
98
104
 
99
105
  className?: string;
100
106
 
107
+ /**
108
+ * If true, this view will store its filter and sorting status in the url params
109
+ */
110
+ updateUrl?: boolean;
111
+
101
112
  } & EntityCollection<M>;
102
113
 
103
114
  /**
@@ -130,6 +141,7 @@ export const EntityCollectionView = React.memo(
130
141
  parentCollectionIds,
131
142
  isSubCollection,
132
143
  className,
144
+ updateUrl,
133
145
  ...collectionProp
134
146
  }: EntityCollectionViewProps<M>
135
147
  ) {
@@ -146,11 +158,15 @@ export const EntityCollectionView = React.memo(
146
158
 
147
159
  const containerRef = React.useRef<HTMLDivElement>(null);
148
160
 
161
+ const scrollRestoration = useScrollRestoration();
162
+
149
163
  const collection = useMemo(() => {
150
164
  const userOverride = userConfigPersistence?.getCollectionConfig<M>(fullPath);
151
165
  return (userOverride ? mergeDeep(collectionProp, userOverride) : collectionProp) as EntityCollection<M>;
152
166
  }, [collectionProp, fullPath, userConfigPersistence?.getCollectionConfig]);
153
167
 
168
+ const openEntityMode = collection?.openEntityMode ?? DEFAULT_ENTITY_OPEN_MODE;
169
+
154
170
  const collectionRef = React.useRef(collection);
155
171
  useEffect(() => {
156
172
  collectionRef.current = collection;
@@ -193,14 +209,12 @@ export const EntityCollectionView = React.memo(
193
209
  setSelectedEntities
194
210
  } = usedSelectionController;
195
211
 
196
- // useEffect(() => {
197
- // setDeleteEntityClicked(undefined);
198
- // }, [selectedEntities]);
199
-
200
- const tableController = useDataSourceEntityCollectionTableController<M>({
212
+ const tableController = useDataSourceTableController<M>({
201
213
  fullPath,
202
214
  collection,
203
- lastDeleteTimestamp
215
+ lastDeleteTimestamp,
216
+ scrollRestoration,
217
+ updateUrl
204
218
  });
205
219
 
206
220
  const tableKey = React.useRef<string>(Math.random().toString(36));
@@ -217,13 +231,21 @@ export const EntityCollectionView = React.memo(
217
231
  path: clickedEntity.path,
218
232
  entityId: clickedEntity.id
219
233
  });
220
- return sideEntityController.open({
221
- entityId: clickedEntity.id,
222
- path: clickedEntity.path,
234
+
235
+ if (collection) {
236
+ addRecentId(collection.id, clickedEntity.id);
237
+ }
238
+
239
+ const path = collection?.collectionGroup ? clickedEntity.path : (fullPath ?? clickedEntity.path);
240
+ navigateToEntity({
241
+ navigation,
242
+ path,
243
+ sideEntityController,
244
+ openEntityMode,
223
245
  collection,
224
- updateUrl: true,
225
- onClose: unselectNavigatedEntity,
246
+ entityId: clickedEntity.id
226
247
  });
248
+
227
249
  }, [unselectNavigatedEntity, sideEntityController]);
228
250
 
229
251
  const onNewClick = useCallback(() => {
@@ -232,12 +254,15 @@ export const EntityCollectionView = React.memo(
232
254
  analyticsController.onAnalyticsEvent?.("new_entity_click", {
233
255
  path: fullPath
234
256
  });
235
- sideEntityController.open({
236
- path: fullPath,
257
+ navigateToEntity({
258
+ openEntityMode,
237
259
  collection,
238
- updateUrl: true,
239
- onClose: unselectNavigatedEntity,
240
- });
260
+ entityId: undefined,
261
+ path: fullPath,
262
+ sideEntityController,
263
+ navigation,
264
+ onClose: unselectNavigatedEntity
265
+ })
241
266
  }, [fullPath, sideEntityController]);
242
267
 
243
268
  const onMultipleDeleteClick = () => {
@@ -349,7 +374,7 @@ export const EntityCollectionView = React.memo(
349
374
 
350
375
  };
351
376
 
352
- const resolvedFullPath = navigation.resolveAliasesFrom(fullPath);
377
+ const resolvedFullPath = navigation.resolveIdsFrom(fullPath);
353
378
  const resolvedCollection = useMemo(() => resolveCollection<M>({
354
379
  collection,
355
380
  path: fullPath,
@@ -393,13 +418,15 @@ export const EntityCollectionView = React.memo(
393
418
  startIcon={<KeyboardTabIcon size={"small"}/>}
394
419
  onClick={(event: any) => {
395
420
  event.stopPropagation();
396
- sideEntityController.open({
397
- path: fullPath,
398
- entityId: entity.id,
399
- selectedSubPath: subcollection.id ?? subcollection.path,
421
+ navigateToEntity({
422
+ openEntityMode,
400
423
  collection,
401
- updateUrl: true,
402
- });
424
+ entityId: entity.id,
425
+ selectedTab: subcollection.id ?? subcollection.path,
426
+ path: fullPath,
427
+ navigation,
428
+ sideEntityController
429
+ })
403
430
  }}>
404
431
  {subcollection.name}
405
432
  </Button>
@@ -505,6 +532,7 @@ export const EntityCollectionView = React.memo(
505
532
  hideId={collection?.hideIdFromCollection}
506
533
  onCollectionChange={updateLastDeleteTimestamp}
507
534
  selectionController={usedSelectionController}
535
+ openEntityMode={openEntityMode}
508
536
  />
509
537
  );
510
538
 
@@ -617,6 +645,8 @@ export const EntityCollectionView = React.memo(
617
645
  properties={resolvedCollection.properties}
618
646
  getPropertyFor={getPropertyFor}
619
647
  onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
648
+ onScroll={tableController.onScroll}
649
+ initialScroll={tableController.initialScroll}
620
650
  textSearchLoading={textSearchLoading}
621
651
  textSearchEnabled={textSearchEnabled}
622
652
  actionsStart={<EntityCollectionViewStartActions
@@ -662,6 +692,7 @@ export const EntityCollectionView = React.memo(
662
692
  additionalIDHeaderWidget={<EntityIdHeaderWidget
663
693
  path={fullPath}
664
694
  collection={collection}/>}
695
+ openEntityMode={openEntityMode}
665
696
  />
666
697
 
667
698
  {popupCell && <PopupFormField
@@ -692,7 +723,7 @@ export const EntityCollectionView = React.memo(
692
723
  </div>
693
724
  );
694
725
  }, (a, b) => {
695
- return equal(a.fullPath, b.fullPath) &&
726
+ return equal(a.path, b.path) &&
696
727
  equal(a.parentCollectionIds, b.parentCollectionIds) &&
697
728
  equal(a.isSubCollection, b.isSubCollection) &&
698
729
  equal(a.className, b.className) &&
@@ -709,6 +740,7 @@ export const EntityCollectionView = React.memo(
709
740
  equal(a.textSearchEnabled, b.textSearchEnabled) &&
710
741
  equal(a.additionalFields, b.additionalFields) &&
711
742
  equal(a.sideDialogWidth, b.sideDialogWidth) &&
743
+ equal(a.openEntityMode, b.openEntityMode) &&
712
744
  equal(a.forceFilter, b.forceFilter);
713
745
  }) as React.FunctionComponent<EntityCollectionViewProps<any>>
714
746
 
@@ -733,7 +765,7 @@ function EntitiesCount({
733
765
 
734
766
  const sortByProperty = sortBy ? sortBy[0] : undefined;
735
767
  const currentSort = sortBy ? sortBy[1] : undefined;
736
- const resolvedPath = useMemo(() => navigation.resolveAliasesFrom(fullPath), [fullPath, navigation.resolveAliasesFrom]);
768
+ const resolvedPath = useMemo(() => navigation.resolveIdsFrom(fullPath), [fullPath, navigation.resolveIdsFrom]);
737
769
 
738
770
  useEffect(() => {
739
771
  if (dataSource.countEntities)
@@ -780,11 +812,15 @@ function EntityIdHeaderWidget({
780
812
  collection: EntityCollection,
781
813
  path: string
782
814
  }) {
815
+
816
+ const navigation = useNavigationController();
783
817
  const [openPopup, setOpenPopup] = React.useState(false);
784
818
  const [searchString, setSearchString] = React.useState("");
785
819
  const [recentIds, setRecentIds] = React.useState<string[]>(getRecentIds(collection.id));
786
820
  const sideEntityController = useSideEntityController();
787
821
 
822
+ const openEntityMode = collection?.openEntityMode ?? DEFAULT_ENTITY_OPEN_MODE;
823
+
788
824
  return (
789
825
  <Tooltip title={!openPopup ? "Find by ID" : undefined} asChild={false}>
790
826
  <Popover
@@ -805,13 +841,16 @@ function EntityIdHeaderWidget({
805
841
  e.preventDefault();
806
842
  if (!searchString) return;
807
843
  setOpenPopup(false);
808
- setRecentIds(addRecentId(collection.id, searchString.trim()));
809
- return sideEntityController.open({
810
- entityId: searchString.trim(),
811
- path,
844
+ const entityId = searchString.trim();
845
+ setRecentIds(addRecentId(collection.id, entityId));
846
+ navigateToEntity({
847
+ openEntityMode,
812
848
  collection,
813
- updateUrl: true
814
- });
849
+ entityId,
850
+ path,
851
+ sideEntityController,
852
+ navigation
853
+ })
815
854
  }}
816
855
  className={"w-96 max-w-full"}>
817
856
 
@@ -838,12 +877,14 @@ function EntityIdHeaderWidget({
838
877
  hover={true}
839
878
  onClick={() => {
840
879
  setOpenPopup(false);
841
- sideEntityController.open({
880
+ navigateToEntity({
881
+ openEntityMode,
882
+ collection,
842
883
  entityId: id,
843
884
  path,
844
- collection,
845
- updateUrl: true
846
- });
885
+ sideEntityController,
886
+ navigation
887
+ })
847
888
  }}
848
889
  includeEntityLink={false}
849
890
  size={"small"}/>
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Link as ReactLink } from "react-router-dom";
2
+ import { Link } from "react-router-dom";
3
3
  import { Button, Typography } from "@firecms/ui";
4
4
 
5
5
  export function NotFoundPage() {
@@ -17,7 +17,7 @@ export function NotFoundPage() {
17
17
  </Typography>
18
18
  <Button
19
19
  variant={"text"}
20
- component={ReactLink}
20
+ component={Link}
21
21
  to={"/"}>Back to home</Button>
22
22
  </div>
23
23
  </div>
@@ -4,7 +4,7 @@ import { CollectionSize, Entity, EntityCollection, FilterValues } from "../../ty
4
4
  import {
5
5
  EntityCollectionRowActions,
6
6
  EntityCollectionTable,
7
- useDataSourceEntityCollectionTableController
7
+ useDataSourceTableController
8
8
  } from "../EntityCollectionTable";
9
9
  import {
10
10
  useAuthController,
@@ -107,7 +107,7 @@ export function ReferenceSelectionTable<M extends Record<string, any>>(
107
107
  const analyticsController = useAnalyticsController();
108
108
  const customizationController = useCustomizationController();
109
109
 
110
- const fullPath = navigation.resolveAliasesFrom(pathInput);
110
+ const fullPath = navigation.resolveIdsFrom(pathInput);
111
111
 
112
112
  const dataSource = useDataSource(collection);
113
113
 
@@ -233,7 +233,9 @@ export function ReferenceSelectionTable<M extends Record<string, any>>(
233
233
  selectionEnabled={multiselect}
234
234
  hideId={collection?.hideIdFromCollection}
235
235
  fullPath={fullPath}
236
- selectionController={selectionController}/>;
236
+ selectionController={selectionController}
237
+ openEntityMode={"side_panel"}
238
+ />;
237
239
 
238
240
  };
239
241
 
@@ -256,7 +258,7 @@ export function ReferenceSelectionTable<M extends Record<string, any>>(
256
258
 
257
259
  const displayedColumnIds = useColumnIds(resolvedCollection, false);
258
260
 
259
- const tableController = useDataSourceEntityCollectionTableController<M>({
261
+ const tableController = useDataSourceTableController<M>({
260
262
  fullPath,
261
263
  collection,
262
264
  entitiesDisplayedFirst,
@@ -289,12 +291,14 @@ export function ReferenceSelectionTable<M extends Record<string, any>>(
289
291
  tableController={tableController}
290
292
  enablePopupIcon={false}
291
293
  tableRowActionsBuilder={tableRowActionsBuilder}
294
+ openEntityMode={"side_panel"}
292
295
  title={<Typography variant={"subtitle2"} className={"flex flex-row gap-2"}>
293
296
  <IconForView
294
297
  size={"small"}
295
298
  collectionOrView={collection}
296
299
  className={"text-surface-300 dark:text-surface-600"}/>
297
- {collection.singularName ? `Select ${collection.singularName}` : `Select from ${collection.name}`}
300
+ {collection.singularName ? `Select ${collection.singularName}` : `Select
301
+ from ${collection.name}`}
298
302
  </Typography>}
299
303
  defaultSize={collection.defaultSize}
300
304
  properties={resolvedCollection.properties}
@@ -1,5 +1,4 @@
1
1
  import React, { useCallback, useEffect, useRef } from "react";
2
- import equal from "react-fast-compare";
3
2
  import {
4
3
  CollectionSize,
5
4
  Entity,
@@ -60,7 +59,7 @@ export type SelectableTableProps<M extends Record<string, any>> = {
60
59
 
61
60
  /**
62
61
  * Controller holding the logic for the table
63
- * {@link useDataSourceEntityCollectionTableController}
62
+ * {@link useDataSourceTableController}
64
63
  * {@link EntityTableController}
65
64
  */
66
65
  tableController: EntityTableController<M>;
@@ -77,6 +76,18 @@ export type SelectableTableProps<M extends Record<string, any>> = {
77
76
 
78
77
  size?: CollectionSize;
79
78
 
79
+ initialScroll?: number;
80
+
81
+ /**
82
+ * Callback when the table is scrolled
83
+ * @param props
84
+ */
85
+ onScroll?: (props: {
86
+ scrollDirection: "forward" | "backward",
87
+ scrollOffset: number,
88
+ scrollUpdateWasRequested: boolean
89
+ }) => void;
90
+
80
91
  emptyComponent?: React.ReactNode;
81
92
 
82
93
  endAdornment?: React.ReactNode;
@@ -101,7 +112,7 @@ export type SelectableTableProps<M extends Record<string, any>> = {
101
112
  *
102
113
  * The data displayed in the table is managed by a {@link EntityTableController}.
103
114
  * You can build the default, bound to a path in the datasource, by using the hook
104
- * {@link useDataSourceEntityCollectionTableController}
115
+ * {@link useDataSourceTableController}
105
116
  *
106
117
  * @see EntityCollectionTableProps
107
118
  * @see EntityCollectionView
@@ -136,6 +147,8 @@ export const SelectableTable = React.memo<SelectableTableProps<any>>(
136
147
  setPopupCell
137
148
  },
138
149
  filterable = true,
150
+ onScroll,
151
+ initialScroll,
139
152
  emptyComponent,
140
153
  columns,
141
154
  forceFilter,
@@ -229,6 +242,8 @@ export const SelectableTable = React.memo<SelectableTableProps<any>>(
229
242
  sortBy={sortBy}
230
243
  onSortByUpdate={setSortBy as ((sortBy?: [string, "asc" | "desc"]) => void)}
231
244
  hoverRow={hoverRow}
245
+ initialScroll={initialScroll}
246
+ onScroll={onScroll}
232
247
  checkFilterCombination={checkFilterCombination}
233
248
  createFilterField={filterable ? createFilterField : undefined}
234
249
  rowClassName={useCallback((entity: Entity<M>) => {
@@ -0,0 +1,42 @@
1
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography } from "@firecms/ui";
2
+
3
+ export interface UnsavedChangesDialogProps {
4
+ open: boolean;
5
+ body?: React.ReactNode;
6
+ title?: string;
7
+ handleOk: () => void;
8
+ handleCancel: () => void;
9
+ }
10
+
11
+ export function UnsavedChangesDialog({
12
+ open,
13
+ handleOk,
14
+ handleCancel,
15
+ body,
16
+ title
17
+ }: UnsavedChangesDialogProps) {
18
+
19
+ return (
20
+ <Dialog
21
+ onEscapeKeyDown={() => {
22
+ handleCancel();
23
+ }}
24
+ open={open}
25
+ >
26
+ <DialogTitle variant={"h6"}>{title}</DialogTitle>
27
+ <DialogContent>
28
+
29
+ {body}
30
+
31
+ <Typography>
32
+ Are you sure you want to leave this page?
33
+ </Typography>
34
+
35
+ </DialogContent>
36
+ <DialogActions>
37
+ <Button variant="text" onClick={handleCancel} autoFocus> Cancel </Button>
38
+ <Button onClick={handleOk}> Ok </Button>
39
+ </DialogActions>
40
+ </Dialog>
41
+ );
42
+ }
@@ -20,6 +20,7 @@ import { VirtualTableHeaderRow } from "./VirtualTableHeaderRow";
20
20
  import { VirtualTableRow } from "./VirtualTableRow";
21
21
  import { VirtualTableCell } from "./VirtualTableCell";
22
22
  import { AssignmentIcon, CenteredView, cls, Typography } from "@firecms/ui";
23
+ import { useDebounceCallback } from "../common/useDebouncedCallback";
23
24
 
24
25
  const VirtualListContext = createContext<VirtualTableContextProps<any>>({} as any);
25
26
  VirtualListContext.displayName = "VirtualListContext";
@@ -103,6 +104,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
103
104
  error,
104
105
  emptyComponent,
105
106
  onSortByUpdate,
107
+ onScroll: onScrollProp,
106
108
  loading,
107
109
  cellRenderer,
108
110
  hoverRow,
@@ -112,6 +114,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
112
114
  className,
113
115
  endAdornment,
114
116
  AddColumnComponent,
117
+ initialScroll = 0,
115
118
  debug
116
119
  }: VirtualTableProps<T>) {
117
120
 
@@ -123,6 +126,15 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
123
126
  const tableRef = useRef<HTMLDivElement>(null);
124
127
  const endReachCallbackThreshold = useRef<number>(0);
125
128
 
129
+ const debouncedScroll = useDebounceCallback(onScrollProp, 200);
130
+
131
+ // Set initial scroll position
132
+ useEffect(() => {
133
+ if (tableRef.current && initialScroll) {
134
+ tableRef.current.scrollTo(0, initialScroll);
135
+ }
136
+ }, [tableRef, initialScroll]);
137
+
126
138
  useEffect(() => {
127
139
  setColumns(columnsProp);
128
140
  }, [columnsProp]);
@@ -158,7 +170,6 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
158
170
  console.log("scrollToTop");
159
171
  endReachCallbackThreshold.current = 0;
160
172
  if (tableRef.current) {
161
- // scrollRef.current = [scrollRef.current[0], 0];
162
173
  tableRef.current.scrollTo(tableRef.current?.scrollLeft, 0);
163
174
  }
164
175
  }, []);
@@ -222,6 +233,13 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
222
233
  scrollOffset,
223
234
  scrollUpdateWasRequested
224
235
  });
236
+ if (onScrollProp) {
237
+ debouncedScroll({
238
+ scrollDirection,
239
+ scrollOffset,
240
+ scrollUpdateWasRequested
241
+ })
242
+ }
225
243
  if (!scrollUpdateWasRequested && (scrollOffset >= maxScroll - endOffset))
226
244
  onEndReachedInternal(scrollOffset);
227
245
  }, [maxScroll, onEndReachedInternal]);
@@ -294,7 +312,6 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
294
312
  if (debug)
295
313
  console.log("VirtualTable render", virtualListController);
296
314
 
297
- // useTraceUpdate(virtualListController);
298
315
  return (
299
316
  <div
300
317
  ref={measureRef}
@@ -82,6 +82,16 @@ export interface VirtualTableProps<T extends Record<string, any>> {
82
82
  */
83
83
  onFilterUpdate?: (filter?: VirtualTableFilterValues<any> | undefined) => void;
84
84
 
85
+ /**
86
+ * Callback when the table is scrolled
87
+ * @param props
88
+ */
89
+ onScroll?: (props: {
90
+ scrollDirection: "forward" | "backward",
91
+ scrollOffset: number,
92
+ scrollUpdateWasRequested: boolean
93
+ }) => void;
94
+
85
95
  /**
86
96
  * Default sort applied to this collection
87
97
  */
@@ -147,6 +157,11 @@ export interface VirtualTableProps<T extends Record<string, any>> {
147
157
  */
148
158
  AddColumnComponent?: React.ComponentType;
149
159
 
160
+ /**
161
+ * Initial scroll position
162
+ */
163
+ initialScroll?: number;
164
+
150
165
  /**
151
166
  * Debug mode
152
167
  */
@@ -66,7 +66,7 @@ export function VirtualTableInput(props: {
66
66
  width: "100%",
67
67
  color: "unset",
68
68
  fontWeight: "unset",
69
- lineHeight: "unset",
69
+ lineHeight: 1.7142857,
70
70
  fontSize: "unset",
71
71
  fontFamily: "unset",
72
72
  background: "unset",
@@ -1,7 +1,8 @@
1
- import { ArchiveIcon, DeleteIcon, EditIcon, FileCopyIcon, OpenInNewIcon } from "@firecms/ui";
1
+ 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
6
 
6
7
  export const editEntityAction: EntityAction = {
7
8
  icon: <EditIcon/>,
@@ -15,23 +16,31 @@ export const editEntityAction: EntityAction = {
15
16
  context,
16
17
  highlightEntity,
17
18
  unhighlightEntity,
19
+ openEntityMode
18
20
  }): Promise<void> {
21
+
19
22
  highlightEntity?.(entity);
23
+
20
24
  context.analyticsController?.onAnalyticsEvent?.("entity_click", {
21
25
  path: entity.path,
22
26
  entityId: entity.id
23
27
  });
28
+
24
29
  if (collection) {
25
30
  addRecentId(collection.id, entity.id);
26
31
  }
27
- const path = collection?.collectionGroup ? entity.path : (fullPath ?? entity.path);
28
- context.sideEntityController.open({
32
+
33
+ const path = collection?.collectionGroup ? entity.path : (fullPath ?? entity.path)
34
+ navigateToEntity({
35
+ openEntityMode,
36
+ collection,
29
37
  entityId: entity.id,
30
38
  path,
31
- collection,
32
- updateUrl: true,
39
+ sideEntityController: context.sideEntityController,
33
40
  onClose: () => unhighlightEntity?.(entity),
41
+ navigation: context.navigation
34
42
  });
43
+
35
44
  return Promise.resolve(undefined);
36
45
  }
37
46
  }
@@ -44,27 +53,32 @@ export const copyEntityAction: EntityAction = {
44
53
  entity,
45
54
  collection,
46
55
  context,
56
+ fullPath,
47
57
  highlightEntity,
48
58
  unhighlightEntity,
59
+ openEntityMode
49
60
  }): Promise<void> {
50
61
  highlightEntity?.(entity);
51
62
  context.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
52
63
  path: entity.path,
53
64
  entityId: entity.id
54
65
  });
55
- context.sideEntityController.open({
66
+ const path = collection?.collectionGroup ? entity.path : (fullPath ?? entity.path)
67
+ navigateToEntity({
68
+ openEntityMode,
69
+ collection,
56
70
  entityId: entity.id,
57
- path: entity.path,
71
+ path,
58
72
  copy: true,
59
- collection,
60
- updateUrl: true,
73
+ sideEntityController: context.sideEntityController,
61
74
  onClose: () => unhighlightEntity?.(entity),
75
+ navigation: context.navigation
62
76
  });
77
+
63
78
  return Promise.resolve(undefined);
64
79
  }
65
80
  }
66
81
 
67
-
68
82
  export const deleteEntityAction: EntityAction = {
69
83
  icon: <DeleteIcon/>,
70
84
  name: "Delete",
@@ -1,6 +1,6 @@
1
1
  export * from "./types";
2
2
  export * from "./useDebouncedData";
3
3
  export * from "./useColumnsIds";
4
- export * from "./useDataSourceEntityCollectionTableController";
4
+ export * from "./useDataSourceTableController";
5
5
  export * from "./useTableSearchHelper";
6
6
  export * from "./default_entity_actions";