@firecms/core 3.0.0-alpha.28 → 3.0.0-alpha.29

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 (57) hide show
  1. package/dist/core/FireCMS.d.ts +2 -2
  2. package/dist/core/builders.d.ts +2 -2
  3. package/dist/core/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +4 -2
  4. package/dist/core/components/EntityCollectionTable/internal/EntityCollectionRowActions.d.ts +15 -11
  5. package/dist/core/components/EntityCollectionTable/internal/default_entity_actions.d.ts +5 -0
  6. package/dist/core/components/EntityCollectionTable/types.d.ts +1 -37
  7. package/dist/core/components/VirtualTable/VirtualTableProps.d.ts +1 -1
  8. package/dist/core/contexts/DialogsProvider.d.ts +4 -0
  9. package/dist/core/index.d.ts +1 -1
  10. package/dist/core/internal/useBuildNavigationContext.d.ts +3 -3
  11. package/dist/core/util/resolutions.d.ts +1 -1
  12. package/dist/hooks/useDialogsController.d.ts +11 -0
  13. package/dist/index.es.js +8400 -8230
  14. package/dist/index.es.js.map +1 -1
  15. package/dist/index.umd.js +67 -67
  16. package/dist/index.umd.js.map +1 -1
  17. package/dist/styles.d.ts +1 -1
  18. package/dist/types/collections.d.ts +36 -13
  19. package/dist/types/dialogs_controller.d.ts +30 -0
  20. package/dist/types/entity_actions.d.ts +33 -0
  21. package/dist/types/firecms.d.ts +5 -5
  22. package/dist/types/firecms_context.d.ts +7 -0
  23. package/dist/types/index.d.ts +1 -0
  24. package/dist/types/navigation.d.ts +3 -3
  25. package/dist/types/plugins.d.ts +11 -10
  26. package/package.json +3 -3
  27. package/src/components/Sheet.tsx +1 -0
  28. package/src/core/FireCMS.tsx +9 -6
  29. package/src/core/builders.ts +6 -6
  30. package/src/core/components/ArrayContainer.tsx +1 -1
  31. package/src/core/components/EntityCollectionTable/EntityCollectionTable.tsx +16 -9
  32. package/src/core/components/EntityCollectionTable/EntityCollectionTableProps.tsx +5 -2
  33. package/src/core/components/EntityCollectionTable/internal/EntityCollectionRowActions.tsx +96 -79
  34. package/src/core/components/EntityCollectionTable/internal/default_entity_actions.tsx +107 -0
  35. package/src/core/components/EntityCollectionTable/types.tsx +1 -56
  36. package/src/core/components/EntityCollectionView/EntityCollectionView.tsx +66 -42
  37. package/src/core/components/ReferenceSelectionInner.tsx +2 -2
  38. package/src/core/components/VirtualTable/VirtualTable.tsx +4 -6
  39. package/src/core/components/VirtualTable/VirtualTableProps.tsx +1 -1
  40. package/src/core/contexts/DialogsProvider.tsx +50 -0
  41. package/src/core/index.tsx +1 -1
  42. package/src/core/internal/useBuildNavigationContext.tsx +13 -13
  43. package/src/core/internal/useBuildSideEntityController.tsx +9 -1
  44. package/src/form/EntityForm.tsx +63 -6
  45. package/src/hooks/useDialogsController.tsx +14 -0
  46. package/src/hooks/useFireCMSContext.tsx +5 -11
  47. package/src/styles.ts +1 -1
  48. package/src/types/collections.ts +36 -17
  49. package/src/types/dialogs_controller.tsx +31 -0
  50. package/src/types/entity_actions.tsx +36 -0
  51. package/src/types/firecms.tsx +5 -5
  52. package/src/types/firecms_context.tsx +8 -0
  53. package/src/types/index.ts +1 -0
  54. package/src/types/navigation.ts +3 -3
  55. package/src/types/plugins.tsx +11 -10
  56. /package/dist/core/contexts/{SnackbarContext.d.ts → SnackbarProvider.d.ts} +0 -0
  57. /package/src/core/contexts/{SnackbarContext.tsx → SnackbarProvider.tsx} +0 -0
@@ -1,12 +1,11 @@
1
1
  import React, { MouseEvent, useCallback } from "react";
2
+ import equal from "react-fast-compare"
2
3
 
3
- import { CollectionSize, Entity } from "../../../../types";
4
- import { Checkbox, IconButton, Tooltip, Typography } from "../../../../components";
5
- import { useLargeLayout } from "../../../../hooks/useLargeLayout";
6
- import { DeleteIcon, FileCopyIcon, KeyboardTabIcon, MoreVertIcon } from "../../../../icons";
7
- import { Menu, MenuItem } from "../../../../components/Menu";
4
+ import { CollectionSize, Entity, EntityAction, EntityCollection, SelectionController } from "../../../../types";
5
+ import { Checkbox, cn, IconButton, Menu, MenuItem, Tooltip, Typography } from "../../../../components";
6
+ import { useFireCMSContext, useLargeLayout } from "../../../../hooks";
7
+ import { MoreVertIcon } from "../../../../icons";
8
8
  import { Skeleton } from "../../../../components/Skeleton";
9
- import { cn } from "../../../../components/util/cn";
10
9
 
11
10
  /**
12
11
  *
@@ -25,62 +24,57 @@ import { cn } from "../../../../components/util/cn";
25
24
  *
26
25
  * @category Collection components
27
26
  */
28
- export function EntityCollectionRowActions<M extends Record<string, any>>({
29
- entity,
30
- width,
31
- frozen,
32
- isSelected,
33
- selectionEnabled,
34
- size,
35
- toggleEntitySelection,
36
- onCopyClicked,
37
- onEditClicked,
38
- onDeleteClicked,
39
- hideId
40
- }:
41
- {
42
- entity: Entity<M>,
43
- width: number,
44
- frozen?: boolean,
45
- size: CollectionSize,
46
- isSelected: boolean,
47
- selectionEnabled?: boolean,
48
- toggleEntitySelection?: (selectedEntity: Entity<M>) => void
49
- onEditClicked?: (selectedEntity: Entity<M>) => void,
50
- onCopyClicked?: (selectedEntity: Entity<M>) => void,
51
- onDeleteClicked?: (selectedEntity: Entity<M>) => void,
52
- hideId?: boolean
53
- }) {
27
+ export const EntityCollectionRowActions = React.memo(function EntityCollectionRowActions({
28
+ entity,
29
+ collection,
30
+ fullPath,
31
+ width,
32
+ frozen,
33
+ isSelected,
34
+ selectionEnabled,
35
+ size,
36
+ highlightEntity,
37
+ onCollectionChange,
38
+ unhighlightEntity,
39
+ actions = [],
40
+ hideId,
41
+ selectionController
42
+ }:
43
+ {
44
+ entity: Entity<any>,
45
+ collection?: EntityCollection<any>,
46
+ fullPath?: string,
47
+ width: number,
48
+ frozen?: boolean,
49
+ size: CollectionSize,
50
+ isSelected: boolean,
51
+ selectionEnabled?: boolean,
52
+ actions?: EntityAction[],
53
+ hideId?: boolean,
54
+ onCollectionChange?: () => void,
55
+ selectionController?: SelectionController;
56
+ highlightEntity?: (entity: Entity<any>) => void;
57
+ unhighlightEntity?: (entity: Entity<any>) => void;
58
+ }) {
54
59
 
55
60
  const largeLayout = useLargeLayout();
56
61
 
57
- const editEnabled = Boolean(onEditClicked);
58
- const copyEnabled = Boolean(onCopyClicked);
59
- const deleteEnabled = Boolean(onDeleteClicked);
62
+ const context = useFireCMSContext();
60
63
 
61
64
  const onCheckedChange = useCallback((checked: boolean) => {
62
- if (toggleEntitySelection)
63
- toggleEntitySelection(entity);
64
- }, [entity, toggleEntitySelection]);
65
-
66
- const onDeleteClick = useCallback((event: MouseEvent) => {
67
- event.stopPropagation();
68
- if (onDeleteClicked)
69
- onDeleteClicked(entity);
70
- }, [entity, onDeleteClicked]);
71
-
72
- const onCopyClick = useCallback((event: MouseEvent) => {
73
- event.stopPropagation();
74
- if (onCopyClicked)
75
- onCopyClicked(entity);
76
- }, [entity, onCopyClicked]);
65
+ selectionController?.toggleEntitySelection(entity);
66
+ }, [entity, selectionController?.toggleEntitySelection]);
77
67
 
78
68
  const onClick = useCallback((event: MouseEvent) => {
79
69
  event.stopPropagation();
80
- if (toggleEntitySelection)
81
- toggleEntitySelection(entity);
82
- }, [entity, toggleEntitySelection]);
70
+ selectionController?.toggleEntitySelection(entity);
71
+ }, [entity, selectionController?.toggleEntitySelection]);
72
+
73
+ const hasActions = actions.length > 0;
74
+ const hasCollapsedActions = actions.some(a => a.collapsed || a.collapsed === undefined);
83
75
 
76
+ const collapsedActions = actions.filter(a => a.collapsed || a.collapsed === undefined);
77
+ const uncollapsedActions = actions.filter(a => a.collapsed === false);
84
78
  return (
85
79
  <div
86
80
  onClick={onClick}
@@ -95,20 +89,58 @@ export function EntityCollectionRowActions<M extends Record<string, any>>({
95
89
  contain: "strict"
96
90
  }}>
97
91
 
98
- {(editEnabled || deleteEnabled || selectionEnabled) &&
92
+ {(hasActions || selectionEnabled) &&
99
93
  <div className="w-34 flex justify-center">
100
- {editEnabled &&
101
- <Tooltip title={`Edit ${entity.id}`}>
94
+
95
+ {uncollapsedActions.map((action, index) => (
96
+ <Tooltip key={index} title={action.name}>
102
97
  <IconButton
103
98
  onClick={(event: MouseEvent) => {
104
99
  event.stopPropagation();
105
- if (onEditClicked)
106
- onEditClicked(entity);
100
+ action.onClick({
101
+ entity,
102
+ fullPath,
103
+ collection,
104
+ context,
105
+ selectionController,
106
+ highlightEntity,
107
+ unhighlightEntity,
108
+ onCollectionChange
109
+ });
107
110
  }}
108
111
  size={largeLayout ? "medium" : "small"}>
109
- <KeyboardTabIcon/>
112
+ {action.icon}
110
113
  </IconButton>
111
114
  </Tooltip>
115
+ ))}
116
+
117
+ {hasCollapsedActions &&
118
+ <Menu
119
+ trigger={<IconButton
120
+ size={largeLayout ? "medium" : "small"}>
121
+ <MoreVertIcon/>
122
+ </IconButton>}>
123
+ {collapsedActions.map((action, index) => (
124
+ <MenuItem
125
+ key={index}
126
+ onClick={(e) => {
127
+ e.stopPropagation();
128
+ action.onClick({
129
+ entity,
130
+ fullPath,
131
+ collection,
132
+ context,
133
+ selectionController,
134
+ highlightEntity,
135
+ unhighlightEntity,
136
+ onCollectionChange
137
+ });
138
+ }}>
139
+ {action.icon}
140
+ {action.name}
141
+ </MenuItem>
142
+ ))}
143
+ </Menu>
112
144
  }
113
145
 
114
146
  {selectionEnabled &&
@@ -120,24 +152,6 @@ export function EntityCollectionRowActions<M extends Record<string, any>>({
120
152
  />
121
153
  </Tooltip>}
122
154
 
123
- {(copyEnabled || deleteEnabled) &&
124
- <Menu
125
- trigger={<IconButton
126
- size={largeLayout ? "medium" : "small"}>
127
- <MoreVertIcon/>
128
- </IconButton>}>
129
- {deleteEnabled && <MenuItem onClick={onDeleteClick}>
130
- <DeleteIcon/>
131
- Delete
132
- </MenuItem>}
133
-
134
- {copyEnabled && <MenuItem onClick={onCopyClick}>
135
- <FileCopyIcon/>
136
- Copy
137
- </MenuItem>}
138
- </Menu>
139
- }
140
-
141
155
  </div>}
142
156
 
143
157
  {!hideId && size !== "xs" && (
@@ -145,7 +159,10 @@ export function EntityCollectionRowActions<M extends Record<string, any>>({
145
159
 
146
160
  {entity
147
161
  ? <Typography
148
- className={"font-mono"}
162
+ onClick={(event) => {
163
+ event.stopPropagation();
164
+ }}
165
+ className={"font-mono select-all"}
149
166
  variant={"caption"}
150
167
  color={"secondary"}> {entity.id} </Typography>
151
168
  : <Skeleton/>
@@ -156,4 +173,4 @@ export function EntityCollectionRowActions<M extends Record<string, any>>({
156
173
  </div>
157
174
  );
158
175
 
159
- }
176
+ }, equal);
@@ -0,0 +1,107 @@
1
+ import { EntityAction } from "../../../../types";
2
+ import { DeleteEntityDialog } from "./DeleteEntityDialog";
3
+ import { ArchiveIcon, DeleteIcon, FileCopyIcon, KeyboardTabIcon } from "../../../../icons";
4
+
5
+ export const editEntityAction: EntityAction = {
6
+ icon: <KeyboardTabIcon/>,
7
+ name: "Edit",
8
+ collapsed: false,
9
+ onClick({
10
+ entity,
11
+ collection,
12
+ context,
13
+ highlightEntity,
14
+ unhighlightEntity
15
+ }): Promise<void> {
16
+ highlightEntity?.(entity);
17
+ context.onAnalyticsEvent?.("entity_click", {
18
+ path: entity.path,
19
+ entityId: entity.id
20
+ });
21
+ context.sideEntityController.open({
22
+ entityId: entity.id,
23
+ path: entity.path,
24
+ collection,
25
+ updateUrl: true,
26
+ onClose: () => unhighlightEntity?.(entity)
27
+ });
28
+ return Promise.resolve(undefined);
29
+ }
30
+ }
31
+
32
+ export const copyEntityAction: EntityAction = {
33
+ icon: <FileCopyIcon/>,
34
+ name: "Copy",
35
+ onClick({
36
+ entity,
37
+ collection,
38
+ context,
39
+ highlightEntity,
40
+ unhighlightEntity
41
+ }): Promise<void> {
42
+ highlightEntity?.(entity);
43
+ context.onAnalyticsEvent?.("copy_entity_click", {
44
+ path: entity.path,
45
+ entityId: entity.id
46
+ });
47
+ context.sideEntityController.open({
48
+ entityId: entity.id,
49
+ path: entity.path,
50
+ copy: true,
51
+ collection,
52
+ updateUrl: true,
53
+ onClose: () => unhighlightEntity?.(entity)
54
+ });
55
+ return Promise.resolve(undefined);
56
+ }
57
+ }
58
+ export const archiveEntityAction: EntityAction = {
59
+ icon: <ArchiveIcon/>,
60
+ name: "Archive",
61
+ onClick({
62
+ entity,
63
+ collection,
64
+ context,
65
+ }): Promise<void> {
66
+ // Add your code here
67
+ return Promise.resolve(undefined);
68
+ }
69
+ }
70
+
71
+ export const deleteEntityAction: EntityAction = {
72
+ icon: <DeleteIcon/>,
73
+ name: "Delete",
74
+ onClick({
75
+ entity,
76
+ fullPath,
77
+ collection,
78
+ context,
79
+ selectionController,
80
+ onCollectionChange,
81
+ sideEntityController
82
+ }): Promise<void> {
83
+ const { closeDialog } = context.dialogsController.open({
84
+ key: "delete_entity_dialog_" + entity.id,
85
+ Component: ({ open }) => {
86
+ if (!collection || !fullPath)
87
+ throw new Error("deleteEntityAction: Collection is undefined");
88
+ return <DeleteEntityDialog
89
+ entityOrEntitiesToDelete={entity}
90
+ path={fullPath}
91
+ collection={collection}
92
+ callbacks={collection.callbacks}
93
+ open={open}
94
+ onEntityDelete={() => {
95
+ context.onAnalyticsEvent?.("single_entity_deleted", {
96
+ path: fullPath
97
+ });
98
+ selectionController?.setSelectedEntities(selectionController.selectedEntities.filter(e => e.id !== entity.id));
99
+ onCollectionChange?.();
100
+ sideEntityController?.close();
101
+ }}
102
+ onClose={closeDialog}/>;
103
+ }
104
+ })
105
+ return Promise.resolve(undefined);
106
+ }
107
+ }
@@ -1,59 +1,4 @@
1
- import {
2
- CollectionSize,
3
- DataSource,
4
- Entity,
5
- EntityCollection,
6
- FireCMSContext,
7
- ResolvedProperty, SelectedCellProps,
8
- User
9
- } from "../../../types";
10
-
11
- export type EntityCollectionTableProviderProps<M extends Record<string, any>, AdditionalKey extends string, UserType extends User> = {
12
-
13
- /**
14
- * Absolute collection path
15
- */
16
- path: string;
17
-
18
- /**
19
- * Use to resolve the collection properties for specific path, entity id or values
20
- */
21
- collection: EntityCollection<M>
22
-
23
- /**
24
- * List of entities that will be displayed on top, no matter the ordering.
25
- * This is used for reference fields selection
26
- */
27
- entitiesDisplayedFirst?: Entity<M>[];
28
-
29
- /**
30
- * Callback when anywhere on the table is clicked
31
- */
32
- onEntityClick?(entity: Entity<M>): void;
33
-
34
- /**
35
- * Callback when the selected size of the table is changed
36
- */
37
- onSizeChanged?(size: CollectionSize): void;
38
-
39
- /**
40
- * Can the table be edited inline
41
- */
42
- inlineEditing: ((entity: Entity<any>) => boolean) | boolean;
43
-
44
- /**
45
- * Builder for creating the buttons in each row
46
- * @param entity
47
- * @param size
48
- */
49
- tableRowActionsBuilder?: ({
50
- entity,
51
- size
52
- }: {
53
- entity: Entity<M>,
54
- size: CollectionSize
55
- }) => React.ReactNode;
56
- };
1
+ import { CollectionSize, Entity, FireCMSContext, ResolvedProperty, SelectedCellProps } from "../../../types";
57
2
 
58
3
  export type EntityCollectionTableController<M extends Record<string, any>> = {
59
4
 
@@ -6,6 +6,7 @@ import {
6
6
  AdditionalFieldDelegate,
7
7
  CollectionSize,
8
8
  Entity,
9
+ EntityAction,
9
10
  EntityCollection,
10
11
  FilterValues,
11
12
  PartialEntityCollection,
@@ -40,14 +41,14 @@ import {
40
41
  useAuthController,
41
42
  useDataSource,
42
43
  useFireCMSContext,
44
+ useLargeLayout,
43
45
  useNavigationContext,
44
46
  useSideEntityController
45
47
  } from "../../../hooks";
46
48
  import { useUserConfigurationPersistence } from "../../../hooks/useUserConfigurationPersistence";
47
49
  import { EntityCollectionViewActions } from "./EntityCollectionViewActions";
48
50
  import { useEntityCollectionTableController } from "../EntityCollectionTable/useEntityCollectionTableController";
49
- import { Button, cn, IconButton, TextField, Tooltip, Typography } from "../../../components";
50
- import { Popover } from "../../../components/Popover";
51
+ import { Button, cn, IconButton, Popover, TextField, Tooltip, Typography } from "../../../components";
51
52
  import { Skeleton } from "../../../components/Skeleton";
52
53
  import { setIn } from "formik";
53
54
  import { getSubcollectionColumnId } from "../EntityCollectionTable/internal/common";
@@ -55,6 +56,11 @@ import { KeyboardTabIcon, SearchIcon } from "../../../icons";
55
56
  import { useColumnIds } from "./useColumnsIds";
56
57
  import { PopupFormField } from "../EntityCollectionTable/internal/popup_field/PopupFormField";
57
58
  import { GetPropertyForProps } from "../EntityCollectionTable/EntityCollectionTableProps";
59
+ import {
60
+ copyEntityAction,
61
+ deleteEntityAction,
62
+ editEntityAction
63
+ } from "../EntityCollectionTable/internal/default_entity_actions";
58
64
 
59
65
  const COLLECTION_GROUP_PARENT_ID = "collectionGroupParent";
60
66
 
@@ -116,6 +122,7 @@ export const EntityCollectionView = React.memo(
116
122
  return (userOverride ? mergeDeep(collectionProp, userOverride) : collectionProp) as EntityCollection<M>;
117
123
  }, [collectionProp, fullPath, userConfigPersistence?.getCollectionConfig]);
118
124
 
125
+ const canCreateEntities = canCreateEntity(collection, authController, fullPathToCollectionSegments(fullPath), null);
119
126
  const [selectedNavigationEntity, setSelectedNavigationEntity] = useState<Entity<M> | undefined>(undefined);
120
127
  const [deleteEntityClicked, setDeleteEntityClicked] = React.useState<Entity<M> | Entity<M>[] | undefined>(undefined);
121
128
 
@@ -343,39 +350,8 @@ export const EntityCollectionView = React.memo(
343
350
 
344
351
  const displayedColumnIds = useColumnIds(resolvedCollection, true);
345
352
 
346
- const onCopyClicked = useCallback((clickedEntity: Entity<M>) => {
347
- setSelectedNavigationEntity(clickedEntity);
348
- context.onAnalyticsEvent?.("copy_entity_click", {
349
- path: clickedEntity.path,
350
- entityId: clickedEntity.id
351
- });
352
- sideEntityController.open({
353
- entityId: clickedEntity.id,
354
- path: clickedEntity.path,
355
- copy: true,
356
- collection,
357
- updateUrl: true,
358
- onClose: unselectNavigatedEntity
359
- });
360
- }, [sideEntityController, collection, fullPath, unselectNavigatedEntity]);
361
-
362
- const onEditClicked = useCallback((clickedEntity: Entity<M>) => {
363
- setSelectedNavigationEntity(clickedEntity);
364
- context.onAnalyticsEvent?.("entity_click", {
365
- path: clickedEntity.path,
366
- entityId: clickedEntity.id
367
- });
368
- sideEntityController.open({
369
- entityId: clickedEntity.id,
370
- path: clickedEntity.path,
371
- collection,
372
- updateUrl: true,
373
- onClose: unselectNavigatedEntity
374
- });
375
- }, [sideEntityController, collection, fullPath, unselectNavigatedEntity]);
376
-
377
353
  const additionalFields = useMemo(() => {
378
- const subcollectionColumns: AdditionalFieldDelegate<M, any, any>[] = collection.subcollections?.map((subcollection) => {
354
+ const subcollectionColumns: AdditionalFieldDelegate<M, any>[] = collection.subcollections?.map((subcollection) => {
379
355
  return {
380
356
  id: getSubcollectionColumnId(subcollection),
381
357
  name: subcollection.name,
@@ -401,7 +377,7 @@ export const EntityCollectionView = React.memo(
401
377
  };
402
378
  }) ?? [];
403
379
 
404
- const collectionGroupParentCollections: AdditionalFieldDelegate<M, any, any>[] = collection.collectionGroup
380
+ const collectionGroupParentCollections: AdditionalFieldDelegate<M, any>[] = collection.collectionGroup
405
381
  ? [{
406
382
  id: COLLECTION_GROUP_PARENT_ID,
407
383
  name: "Parent entities",
@@ -432,6 +408,32 @@ export const EntityCollectionView = React.memo(
432
408
  ];
433
409
  }, [collection, fullPath]);
434
410
 
411
+ const updateLastDeleteTimestamp = useCallback(() => {
412
+ setLastDeleteTimestamp(Date.now());
413
+ }, []);
414
+
415
+ const largeLayout = useLargeLayout();
416
+
417
+ const getActionsForEntity = useCallback(({ entity, customEntityActions }: { entity?: Entity<M>, customEntityActions?: EntityAction[] }): EntityAction[] => {
418
+ const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPathToCollectionSegments(fullPath), entity) : true;
419
+ const actions: EntityAction[] = [editEntityAction];
420
+ if (createEnabled)
421
+ actions.push(copyEntityAction);
422
+ if (deleteEnabled)
423
+ actions.push(deleteEntityAction);
424
+ if (customEntityActions)
425
+ actions.push(...customEntityActions);
426
+ return actions;
427
+ }, [authController, collection, createEnabled, fullPath]);
428
+
429
+ const getIdColumnWidth = useCallback(() => {
430
+ const entityActions = getActionsForEntity({});
431
+ const collapsedActions = entityActions.filter(a => a.collapsed !== false);
432
+ const uncollapsedActions = entityActions.filter(a => a.collapsed === false);
433
+ const actionsWidth = uncollapsedActions.length * (largeLayout ? 40 : 30);
434
+ return (largeLayout ? (80 + actionsWidth) : (70 + actionsWidth)) + (collapsedActions.length > 0 ? (largeLayout ? 40 : 30) : 0);
435
+ }, [largeLayout, getActionsForEntity]);
436
+
435
437
  const tableRowActionsBuilder = useCallback(({
436
438
  entity,
437
439
  size,
@@ -446,7 +448,7 @@ export const EntityCollectionView = React.memo(
446
448
 
447
449
  const isSelected = isEntitySelected(entity);
448
450
 
449
- const deleteEnabled = canDeleteEntity(collection, authController, fullPathToCollectionSegments(fullPath), entity);
451
+ const actions = getActionsForEntity({ entity, customEntityActions: collection.entityActions });
450
452
 
451
453
  return (
452
454
  <EntityCollectionRowActions
@@ -456,15 +458,18 @@ export const EntityCollectionView = React.memo(
456
458
  isSelected={isSelected}
457
459
  selectionEnabled={selectionEnabled}
458
460
  size={size}
459
- toggleEntitySelection={toggleEntitySelection}
460
- onEditClicked={onEditClicked}
461
- onCopyClicked={createEnabled ? onCopyClicked : undefined}
462
- onDeleteClicked={deleteEnabled ? onSingleDeleteClick : undefined}
461
+ highlightEntity={setSelectedNavigationEntity}
462
+ unhighlightEntity={unselectNavigatedEntity}
463
+ collection={collection}
464
+ fullPath={fullPath}
465
+ actions={actions}
463
466
  hideId={collection?.hideIdFromCollection}
467
+ onCollectionChange={updateLastDeleteTimestamp}
468
+ selectionController={usedSelectionController}
464
469
  />
465
470
  );
466
471
 
467
- }, [isEntitySelected, collection, authController, fullPath, selectionEnabled, toggleEntitySelection, onEditClicked, createEnabled, onCopyClicked]);
472
+ }, [isEntitySelected, collection, authController, fullPath, selectionEnabled, toggleEntitySelection, createEnabled]);
468
473
 
469
474
  const title = <Popover
470
475
  open={popOverOpen}
@@ -522,6 +527,7 @@ export const EntityCollectionView = React.memo(
522
527
  propertyKey={propertyKey}
523
528
  property={property}
524
529
  fullPath={fullPath}
530
+ collection={collection}
525
531
  parentPathSegments={parentPathSegments ?? []}/>;
526
532
  })}
527
533
  </>;
@@ -569,10 +575,25 @@ export const EntityCollectionView = React.memo(
569
575
  selectionEnabled={selectionEnabled}
570
576
  collectionEntitiesCount={docsCount}
571
577
  />}
578
+ emptyComponent={canCreateEntities
579
+ ? <div className="flex flex-col items-center justify-center">
580
+ <Typography variant={"subtitle2"}>So empty...</Typography>
581
+ <Button
582
+ color={"primary"}
583
+ variant={"outlined"}
584
+ onClick={onNewClick}
585
+ className="mt-4"
586
+ >
587
+ Create your first entity
588
+ </Button>
589
+ </div>
590
+ : undefined
591
+ }
572
592
  hoverRow={hoverRow}
573
593
  inlineEditing={checkInlineEditing()}
574
594
  AdditionalHeaderWidget={buildAdditionalHeaderWidget}
575
595
  AddColumnComponent={addColumnComponentInternal}
596
+ getIdColumnWidth={getIdColumnWidth}
576
597
  additionalIDHeaderWidget={<EntityIdHeaderWidget
577
598
  path={fullPath}
578
599
  collection={collection}/>}
@@ -690,7 +711,10 @@ function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityC
690
711
  return { properties: { [key]: { columnWidth: width } } } as PartialEntityCollection;
691
712
  }
692
713
 
693
- function EntityIdHeaderWidget({ collection, path }: {
714
+ function EntityIdHeaderWidget({
715
+ collection,
716
+ path
717
+ }: {
694
718
  collection: EntityCollection,
695
719
  path: string
696
720
  }) {
@@ -230,9 +230,9 @@ export function ReferenceSelectionInner<M extends Record<string, any>>(
230
230
  size={size}
231
231
  isSelected={isSelected}
232
232
  selectionEnabled={multiselect}
233
- toggleEntitySelection={toggleEntitySelection}
234
233
  hideId={collection?.hideIdFromCollection}
235
- />;
234
+ fullPath={fullPath}
235
+ selectionController={selectionController}/>;
236
236
 
237
237
  }, [multiselect, selectionController.selectedEntities, toggleEntitySelection, collection?.hideIdFromCollection]);
238
238
 
@@ -102,7 +102,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
102
102
  onFilterUpdate,
103
103
  sortBy,
104
104
  error,
105
- emptyMessage,
105
+ emptyComponent,
106
106
  onSortByUpdate,
107
107
  loading,
108
108
  cellRenderer,
@@ -249,13 +249,11 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
249
249
  if (loading)
250
250
  return <CircularProgressCenter/>;
251
251
  return <div
252
- className="flex overflow-auto items-center justify-center p-2 gap-2 h-full">
252
+ className="flex flex-col overflow-auto items-center justify-center p-2 gap-2 h-full">
253
253
  <AssignmentIcon/>
254
- <Typography>
255
- {emptyMessage}
256
- </Typography>
254
+ {emptyComponent}
257
255
  </div>;
258
- }, [emptyMessage, loading]);
256
+ }, [emptyComponent, loading]);
259
257
 
260
258
  const empty = !loading && (data?.length ?? 0) === 0;
261
259
  const customView = error ? buildErrorView() : (empty ? buildEmptyView() : undefined);
@@ -102,7 +102,7 @@ export interface VirtualTableProps<T extends Record<string, any>> {
102
102
  /**
103
103
  * Message displayed when there is no data
104
104
  */
105
- emptyMessage?: string;
105
+ emptyComponent?: React.ReactNode;
106
106
 
107
107
  /**
108
108
  * Is the table in a loading state