@firecms/core 3.0.0-canary.81 → 3.0.0-canary.82

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 (41) hide show
  1. package/dist/app/Drawer.d.ts +1 -1
  2. package/dist/app/Scaffold.d.ts +1 -1
  3. package/dist/components/CircularProgressCenter.d.ts +1 -1
  4. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +0 -1
  5. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +1 -1
  6. package/dist/components/ErrorView.d.ts +1 -1
  7. package/dist/components/HomePage/DefaultHomePage.d.ts +1 -1
  8. package/dist/components/HomePage/NavigationCardBinding.d.ts +1 -1
  9. package/dist/core/DefaultAppBar.d.ts +1 -1
  10. package/dist/core/DefaultDrawer.d.ts +1 -1
  11. package/dist/core/EntityEditView.d.ts +1 -1
  12. package/dist/core/FireCMS.d.ts +1 -1
  13. package/dist/core/NavigationRoutes.d.ts +1 -1
  14. package/dist/index.es.js +13585 -10269
  15. package/dist/index.es.js.map +1 -1
  16. package/dist/index.umd.js +19610 -7
  17. package/dist/index.umd.js.map +1 -1
  18. package/dist/types/collections.d.ts +1 -2
  19. package/dist/util/plurals.d.ts +0 -2
  20. package/package.json +4 -4
  21. package/src/app/Drawer.tsx +1 -1
  22. package/src/app/Scaffold.tsx +1 -1
  23. package/src/components/CircularProgressCenter.tsx +1 -1
  24. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +1 -8
  25. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +2 -2
  26. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +12 -4
  27. package/src/components/EntityCollectionView/EntityCollectionView.tsx +15 -16
  28. package/src/components/EntityCollectionView/useSelectionController.tsx +19 -7
  29. package/src/components/ErrorView.tsx +1 -1
  30. package/src/components/HomePage/DefaultHomePage.tsx +1 -1
  31. package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
  32. package/src/components/SelectableTable/SelectableTable.tsx +2 -3
  33. package/src/components/VirtualTable/VirtualTableCell.tsx +1 -1
  34. package/src/core/DefaultAppBar.tsx +2 -2
  35. package/src/core/DefaultDrawer.tsx +1 -1
  36. package/src/core/EntityEditView.tsx +7 -3
  37. package/src/core/FireCMS.tsx +1 -1
  38. package/src/core/NavigationRoutes.tsx +1 -1
  39. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +12 -4
  40. package/src/types/collections.ts +1 -2
  41. package/src/util/plurals.ts +0 -2
@@ -313,14 +313,13 @@ export interface CollectionActionsProps<M extends Record<string, any> = any, Use
313
313
  /**
314
314
  * Use this controller to retrieve the selected entities or modify them in
315
315
  * an {@link EntityCollection}
316
- * If you want to pass a `SelectionController` to
317
316
  * @group Models
318
317
  */
319
318
  export type SelectionController<M extends Record<string, any> = any> = {
320
319
  selectedEntities: Entity<M>[];
321
320
  setSelectedEntities: Dispatch<SetStateAction<Entity<M>[]>>;
322
321
  isEntitySelected: (entity: Entity<M>) => boolean;
323
- toggleEntitySelection: (entity: Entity<M>) => void;
322
+ toggleEntitySelection: (entity: Entity<M>, newSelectedState?: boolean) => void;
324
323
  };
325
324
  /**
326
325
  * Filter conditions in a `Query.where()` clause are specified using the
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Returns the plural of an English word.
3
3
  *
4
- * @export
5
4
  * @param {string} word
6
5
  * @param {number} [amount]
7
6
  * @returns {string}
@@ -10,7 +9,6 @@ export declare function plural(word: string, amount?: number): string;
10
9
  /**
11
10
  * Returns the singular of an English word.
12
11
  *
13
- * @export
14
12
  * @param {string} word
15
13
  * @param {number} [amount]
16
14
  * @returns {string}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
3
  "type": "module",
4
- "version": "3.0.0-canary.81",
4
+ "version": "3.0.0-canary.82",
5
5
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/firecmsco"
@@ -46,8 +46,8 @@
46
46
  "./package.json": "./package.json"
47
47
  },
48
48
  "dependencies": {
49
- "@firecms/formex": "^3.0.0-canary.81",
50
- "@firecms/ui": "^3.0.0-canary.81",
49
+ "@firecms/formex": "^3.0.0-canary.82",
50
+ "@firecms/ui": "^3.0.0-canary.82",
51
51
  "@fontsource/jetbrains-mono": "^5.0.20",
52
52
  "@hello-pangea/dnd": "^16.6.0",
53
53
  "@radix-ui/react-portal": "^1.1.1",
@@ -102,7 +102,7 @@
102
102
  "dist",
103
103
  "src"
104
104
  ],
105
- "gitHead": "1b8dbb7820c18ee05749d105d0984d5ba01585b1",
105
+ "gitHead": "5bd98b85c3f5c0c9a08860188e15e2687f1d0542",
106
106
  "publishConfig": {
107
107
  "access": "public"
108
108
  },
@@ -7,7 +7,7 @@ import { DefaultDrawer } from "../core";
7
7
  * If you want to customise the drawer, you can create your own component and pass it as a child.
8
8
  * For custom drawers, you can use the {@link useApp} to open and close the drawer.
9
9
  *
10
- * @constructor
10
+
11
11
  */
12
12
  export function Drawer({
13
13
  children,
@@ -36,7 +36,7 @@ export interface ScaffoldProps {
36
36
  * This component needs a parent {@link FireCMS}
37
37
  *
38
38
  * @param props
39
- * @constructor
39
+
40
40
  * @group Core
41
41
  */
42
42
  export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
@@ -5,7 +5,7 @@ import { CircularProgress, CircularProgressProps, Typography } from "@firecms/ui
5
5
  *
6
6
  * @param text
7
7
  * @param props
8
- * @constructor
8
+
9
9
  * @ignore
10
10
  */
11
11
  export function CircularProgressCenter({ text, ...props }: CircularProgressProps & {
@@ -14,7 +14,6 @@ import { useFireCMSContext, useLargeLayout } from "../../hooks";
14
14
  * @param size
15
15
  * @param toggleEntitySelection
16
16
  * @param hideId
17
- * @constructor
18
17
  *
19
18
  * @group Collection components
20
19
  */
@@ -56,12 +55,7 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
56
55
  const context = useFireCMSContext();
57
56
 
58
57
  const onCheckedChange = useCallback((checked: boolean) => {
59
- selectionController?.toggleEntitySelection(entity);
60
- }, [entity, selectionController?.toggleEntitySelection]);
61
-
62
- const onClick = useCallback((event: MouseEvent) => {
63
- event.stopPropagation();
64
- selectionController?.toggleEntitySelection(entity);
58
+ selectionController?.toggleEntitySelection(entity, checked);
65
59
  }, [entity, selectionController?.toggleEntitySelection]);
66
60
 
67
61
  const hasActions = actions.length > 0;
@@ -71,7 +65,6 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
71
65
  const uncollapsedActions = actions.filter(a => a.collapsed === false);
72
66
  return (
73
67
  <div
74
- onClick={onClick}
75
68
  className={cls(
76
69
  "h-full flex items-center justify-center flex-col bg-gray-50 dark:bg-gray-900 bg-opacity-90 dark:bg-opacity-90 z-10",
77
70
  frozen ? "sticky left-0" : ""
@@ -246,7 +246,7 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
246
246
  : collectionColumns) as VirtualTableColumn[]
247
247
  ];
248
248
 
249
- const cellRenderer = (props: CellRendererParams<any>) => {
249
+ const cellRenderer = useCallback((props: CellRendererParams<any>) => {
250
250
  const column = props.column;
251
251
  const columns = props.columns;
252
252
  const columnKey = column.key;
@@ -286,7 +286,7 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
286
286
  <ErrorView error={e}/>
287
287
  </EntityTableCell>;
288
288
  }
289
- }
289
+ }, [tableRowActionsBuilder, additionalCellRenderer, propertyCellRenderer, size]);
290
290
 
291
291
  return (
292
292
 
@@ -179,10 +179,18 @@ function StorageUpload({
179
179
  onDropRejected: (fileRejections, event) => {
180
180
  for (const fileRejection of fileRejections) {
181
181
  for (const error of fileRejection.errors) {
182
- snackbarContext.open({
183
- type: "error",
184
- message: `Error uploading file: File is larger than ${storage.maxSize} bytes`
185
- });
182
+ console.log("Error uploading file: ", error);
183
+ if (error.code === "file-too-large") {
184
+ snackbarContext.open({
185
+ type: "error",
186
+ message: `Error uploading file: File is larger than ${storage.maxSize} bytes`
187
+ });
188
+ } else if (error.code === "file-invalid-type") {
189
+ snackbarContext.open({
190
+ type: "error",
191
+ message: "Error uploading file: File type is not supported"
192
+ });
193
+ }
186
194
  }
187
195
  }
188
196
  }
@@ -117,7 +117,7 @@ export type EntityCollectionViewProps<M extends Record<string, any>> = {
117
117
  *
118
118
  * @param fullPath
119
119
  * @param collection
120
- * @constructor
120
+
121
121
  * @group Components
122
122
  */
123
123
  export const EntityCollectionView = React.memo(
@@ -153,7 +153,7 @@ export const EntityCollectionView = React.memo(
153
153
  }, [collection]);
154
154
 
155
155
  const canCreateEntities = canCreateEntity(collection, authController, fullPath, null);
156
- const [selectedNavigationEntity, setSelectedNavigationEntity] = useState<Entity<M> | undefined>(undefined);
156
+ const [highlightedEntity, setHighlightedEntity] = useState<Entity<M> | undefined>(undefined);
157
157
  const [deleteEntityClicked, setDeleteEntityClicked] = React.useState<Entity<M> | Entity<M>[] | undefined>(undefined);
158
158
 
159
159
  const [lastDeleteTimestamp, setLastDeleteTimestamp] = React.useState<number>(0);
@@ -162,12 +162,12 @@ export const EntityCollectionView = React.memo(
162
162
  const [docsCount, setDocsCount] = useState<number>(0);
163
163
 
164
164
  const unselectNavigatedEntity = useCallback(() => {
165
- const currentSelection = selectedNavigationEntity;
165
+ const currentSelection = highlightedEntity;
166
166
  setTimeout(() => {
167
- if (currentSelection === selectedNavigationEntity)
168
- setSelectedNavigationEntity(undefined);
167
+ if (currentSelection === highlightedEntity)
168
+ setHighlightedEntity(undefined);
169
169
  }, 2400);
170
- }, [selectedNavigationEntity]);
170
+ }, [highlightedEntity]);
171
171
 
172
172
  const checkInlineEditing = useCallback((entity?: Entity<any>): boolean => {
173
173
  const collection = collectionRef.current;
@@ -186,13 +186,12 @@ export const EntityCollectionView = React.memo(
186
186
  const usedSelectionController = collection.selectionController ?? selectionController;
187
187
  const {
188
188
  selectedEntities,
189
- isEntitySelected,
190
189
  setSelectedEntities
191
190
  } = usedSelectionController;
192
191
 
193
- useEffect(() => {
194
- setDeleteEntityClicked(undefined);
195
- }, [selectedEntities]);
192
+ // useEffect(() => {
193
+ // setDeleteEntityClicked(undefined);
194
+ // }, [selectedEntities]);
196
195
 
197
196
  const tableController = useDataSourceEntityCollectionTableController<M>({
198
197
  fullPath,
@@ -210,7 +209,7 @@ export const EntityCollectionView = React.memo(
210
209
  const onEntityClick = useCallback((clickedEntity: Entity<M>) => {
211
210
  console.log("Entity clicked", clickedEntity)
212
211
  const collection = collectionRef.current;
213
- setSelectedNavigationEntity(clickedEntity);
212
+ setHighlightedEntity(clickedEntity);
214
213
  analyticsController.onAnalyticsEvent?.("edit_entity_clicked", {
215
214
  path: clickedEntity.path,
216
215
  entityId: clickedEntity.id
@@ -468,7 +467,7 @@ export const EntityCollectionView = React.memo(
468
467
  return (largeLayout ? (80 + actionsWidth) : (70 + actionsWidth)) + (collapsedActions.length > 0 ? (largeLayout ? 40 : 30) : 0);
469
468
  };
470
469
 
471
- const tableRowActionsBuilder = ({
470
+ const tableRowActionsBuilder = useCallback(({
472
471
  entity,
473
472
  size,
474
473
  width,
@@ -480,7 +479,7 @@ export const EntityCollectionView = React.memo(
480
479
  frozen?: boolean
481
480
  }) => {
482
481
 
483
- const isSelected = isEntitySelected(entity);
482
+ const isSelected = usedSelectionController.selectedEntities.map(e => e.id).includes(entity.id);
484
483
 
485
484
  const actions = getActionsForEntity({
486
485
  entity,
@@ -495,7 +494,7 @@ export const EntityCollectionView = React.memo(
495
494
  isSelected={isSelected}
496
495
  selectionEnabled={selectionEnabled}
497
496
  size={size}
498
- highlightEntity={setSelectedNavigationEntity}
497
+ highlightEntity={setHighlightedEntity}
499
498
  unhighlightEntity={unselectNavigatedEntity}
500
499
  collection={collection}
501
500
  fullPath={fullPath}
@@ -506,7 +505,7 @@ export const EntityCollectionView = React.memo(
506
505
  />
507
506
  );
508
507
 
509
- };
508
+ }, [updateLastDeleteTimestamp, usedSelectionController]);
510
509
 
511
510
  const title = <Popover
512
511
  open={popOverOpen}
@@ -610,7 +609,7 @@ export const EntityCollectionView = React.memo(
610
609
  uniqueFieldValidator={uniqueFieldValidator}
611
610
  title={title}
612
611
  selectionController={usedSelectionController}
613
- highlightedEntities={selectedNavigationEntity ? [selectedNavigationEntity] : []}
612
+ highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
614
613
  defaultSize={collection.defaultSize}
615
614
  properties={resolvedCollection.properties}
616
615
  getPropertyFor={getPropertyFor}
@@ -7,19 +7,31 @@ export function useSelectionController<M extends Record<string, any> = any>(
7
7
 
8
8
  const [selectedEntities, setSelectedEntities] = useState<Entity<M>[]>([]);
9
9
 
10
- const toggleEntitySelection = useCallback((entity: Entity<M>) => {
10
+ const toggleEntitySelection = useCallback((entity: Entity<M>, newSelectedState?: boolean) => {
11
11
  let newValue;
12
- if (selectedEntities.map(e => e.id).includes(entity.id)) {
13
- onSelectionChange?.(entity, false);
14
- newValue = selectedEntities.filter((item: Entity<M>) => item.id !== entity.id);
12
+ if (newSelectedState === undefined) {
13
+ if (selectedEntities.map(e => e.id).includes(entity.id)) {
14
+ onSelectionChange?.(entity, false);
15
+ newValue = selectedEntities.filter((item: Entity<M>) => item.id !== entity.id);
16
+ } else {
17
+ onSelectionChange?.(entity, true);
18
+ newValue = [...selectedEntities, entity];
19
+ }
15
20
  } else {
16
- onSelectionChange?.(entity, true);
17
- newValue = [...selectedEntities, entity];
21
+ if (newSelectedState) {
22
+ onSelectionChange?.(entity, true);
23
+ newValue = [...selectedEntities, entity];
24
+ } else {
25
+ onSelectionChange?.(entity, false);
26
+ newValue = selectedEntities.filter((item: Entity<M>) => item.id !== entity.id);
27
+ }
18
28
  }
19
29
  setSelectedEntities(newValue);
20
30
  }, [selectedEntities]);
21
31
 
22
- const isEntitySelected = useCallback((entity: Entity<M>) => selectedEntities.map(e => e.id).includes(entity.id), [selectedEntities]);
32
+ const isEntitySelected = useCallback((entity: Entity<M>) => {
33
+ return selectedEntities.map(e => e.id).includes(entity.id);
34
+ }, [selectedEntities]);
23
35
 
24
36
  return {
25
37
  selectedEntities,
@@ -17,7 +17,7 @@ export interface ErrorViewProps {
17
17
  * @param title
18
18
  * @param error
19
19
  * @param tooltip
20
- * @constructor
20
+
21
21
  * @group Components
22
22
  */
23
23
  export function ErrorView({
@@ -23,7 +23,7 @@ search.addIndex("path");
23
23
  /**
24
24
  * Default entry view for the CMS. This component renders navigation cards
25
25
  * for each collection defined in the navigation.
26
- * @constructor
26
+
27
27
  * @group Components
28
28
  */
29
29
  export function DefaultHomePage({
@@ -19,7 +19,7 @@ import { SmallNavigationCard } from "./SmallNavigationCard";
19
19
  * @param name
20
20
  * @param description
21
21
  * @param onClick
22
- * @constructor
22
+
23
23
  */
24
24
  export function NavigationCardBinding({
25
25
  path,
@@ -128,8 +128,6 @@ export const SelectableTable = React.memo<SelectableTableProps<any>>(
128
128
  setFilterValues,
129
129
  sortBy,
130
130
  setSortBy,
131
- setSearchString,
132
- clearFilter,
133
131
  itemCount,
134
132
  setItemCount,
135
133
  pageSize = 50,
@@ -248,7 +246,8 @@ export const SelectableTable = React.memo<SelectableTableProps<any>>(
248
246
  );
249
247
 
250
248
  },
251
- equal
249
+ () => false,
250
+ // equal
252
251
  );
253
252
 
254
253
  function createFilterField({
@@ -16,7 +16,7 @@ type VirtualTableCellProps<T extends any> = {
16
16
  };
17
17
 
18
18
  export const VirtualTableCell = React.memo<VirtualTableCellProps<any>>(
19
- function VirtualTableCell<T extends any>(props: VirtualTableCellProps<T>) {
19
+ function VirtualTableCell<T>(props: VirtualTableCellProps<T>) {
20
20
  // @ts-ignore
21
21
  return props.rowData && props.cellRenderer(
22
22
  {
@@ -45,7 +45,7 @@ export type DefaultAppBarProps<ADDITIONAL_PROPS = object> = {
45
45
  * This component renders the main app bar of FireCMS.
46
46
  * You will likely not need to use this component directly.
47
47
  *
48
- * @constructor
48
+
49
49
  */
50
50
  export const DefaultAppBar = function DefaultAppBar({
51
51
  title,
@@ -95,7 +95,7 @@ export const DefaultAppBar = function DefaultAppBar({
95
95
  return (
96
96
  <div
97
97
  style={style}
98
- className={cls("pr-2 w-full h-16 transition-all ease-in duration-75 fixed",
98
+ className={cls("w-full h-16 transition-all ease-in duration-75 fixed",
99
99
  {
100
100
  "pl-[17rem]": drawerOpen && largeLayout,
101
101
  "pl-20": hasDrawer && !(drawerOpen && largeLayout),
@@ -144,7 +144,7 @@ export function DefaultDrawer({
144
144
  * It expands when the drawer is open.
145
145
  *
146
146
  * @param logo
147
- * @constructor
147
+
148
148
  */
149
149
  export function DrawerLogo({ logo }: {
150
150
  logo?: string;
@@ -95,7 +95,7 @@ export interface EntityEditViewProps<M extends Record<string, any>> {
95
95
  * side panel. Instead, you might want to use {@link EntityForm} or {@link EntityCollectionView}
96
96
  */
97
97
  export function EntityEditView<M extends Record<string, any>, UserType extends User>({
98
- entityId: entityIdProp,
98
+ entityId,
99
99
  ...props
100
100
  }: EntityEditViewProps<M>) {
101
101
  const {
@@ -105,7 +105,7 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
105
105
  dataLoadingError
106
106
  } = useEntityFetch<M, UserType>({
107
107
  path: props.path,
108
- entityId: entityIdProp,
108
+ entityId: entityId,
109
109
  collection: props.collection,
110
110
  useCache: false
111
111
  });
@@ -114,8 +114,12 @@ export function EntityEditView<M extends Record<string, any>, UserType extends U
114
114
  return <CircularProgressCenter/>
115
115
  }
116
116
 
117
+ if (entityId && !entity) {
118
+ console.error(`Entity with id ${entityId} not found in collection ${props.collection.path}`);
119
+ }
120
+
117
121
  return <EntityEditViewInner<M> {...props}
118
- entityId={entityIdProp}
122
+ entityId={entityId}
119
123
  entity={entity}
120
124
  dataLoading={dataLoading}/>;
121
125
  }
@@ -28,7 +28,7 @@ import { useProjectLog } from "../hooks/useProjectLog";
28
28
  *
29
29
  * You only need to use this component if you are building a custom app.
30
30
  *
31
- * @constructor
31
+
32
32
  * @group Core
33
33
  */
34
34
  export function FireCMS<UserType extends User, EC extends EntityCollection>(props: FireCMSProps<UserType, EC>) {
@@ -25,7 +25,7 @@ export type NavigationRoutesProps = {
25
25
  * or the home route) related to a {@link NavigationController}.
26
26
  * This component needs a parent {@link FireCMS}
27
27
  *
28
- * @constructor
28
+
29
29
  * @group Components
30
30
  */
31
31
  export const NavigationRoutes = React.memo<NavigationRoutesProps>(
@@ -170,10 +170,18 @@ function FileDropComponent({
170
170
  onDropRejected: (fileRejections, event) => {
171
171
  for (const fileRejection of fileRejections) {
172
172
  for (const error of fileRejection.errors) {
173
- snackbarContext.open({
174
- type: "error",
175
- message: `Error uploading file: File is larger than ${storage.maxSize} bytes`
176
- });
173
+ console.log("Error uploading file: ", error);
174
+ if (error.code === "file-too-large") {
175
+ snackbarContext.open({
176
+ type: "error",
177
+ message: `Error uploading file: File is larger than ${storage.maxSize} bytes`
178
+ });
179
+ } else if (error.code === "file-invalid-type") {
180
+ snackbarContext.open({
181
+ type: "error",
182
+ message: "Error uploading file: File type is not supported"
183
+ });
184
+ }
177
185
  }
178
186
  }
179
187
  }
@@ -362,14 +362,13 @@ export interface CollectionActionsProps<M extends Record<string, any> = any, Use
362
362
  /**
363
363
  * Use this controller to retrieve the selected entities or modify them in
364
364
  * an {@link EntityCollection}
365
- * If you want to pass a `SelectionController` to
366
365
  * @group Models
367
366
  */
368
367
  export type SelectionController<M extends Record<string, any> = any> = {
369
368
  selectedEntities: Entity<M>[];
370
369
  setSelectedEntities: Dispatch<SetStateAction<Entity<M>[]>>;
371
370
  isEntitySelected: (entity: Entity<M>) => boolean;
372
- toggleEntitySelection: (entity: Entity<M>) => void;
371
+ toggleEntitySelection: (entity: Entity<M>, newSelectedState?: boolean) => void;
373
372
  }
374
373
 
375
374
  /**
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Returns the plural of an English word.
3
3
  *
4
- * @export
5
4
  * @param {string} word
6
5
  * @param {number} [amount]
7
6
  * @returns {string}
@@ -92,7 +91,6 @@ export function plural(word: string, amount?: number): string {
92
91
  /**
93
92
  * Returns the singular of an English word.
94
93
  *
95
- * @export
96
94
  * @param {string} word
97
95
  * @param {number} [amount]
98
96
  * @returns {string}