@firecms/core 3.0.0-alpha.64 → 3.0.0-alpha.65

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 (36) hide show
  1. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +2 -0
  2. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +2 -0
  3. package/dist/index.es.js +4252 -4207
  4. package/dist/index.es.js.map +1 -1
  5. package/dist/index.umd.js +5 -5
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/types/collections.d.ts +1 -1
  8. package/dist/types/datasource.d.ts +1 -1
  9. package/dist/types/dialogs_controller.d.ts +2 -0
  10. package/dist/types/entity_actions.d.ts +4 -2
  11. package/dist/types/plugins.d.ts +10 -0
  12. package/dist/util/index.d.ts +0 -1
  13. package/dist/util/navigation_utils.d.ts +0 -1
  14. package/dist/util/strings.d.ts +1 -0
  15. package/package.json +3 -3
  16. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +5 -3
  17. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +4 -0
  18. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -5
  19. package/src/components/EntityCollectionTable/useEntityCollectionTableController.tsx +98 -28
  20. package/src/components/EntityCollectionView/EntityCollectionView.tsx +38 -2
  21. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +0 -1
  22. package/src/components/ErrorView.tsx +1 -1
  23. package/src/components/HomePage/DefaultHomePage.tsx +1 -0
  24. package/src/contexts/DialogsProvider.tsx +3 -1
  25. package/src/internal/useBuildDataSource.ts +2 -1
  26. package/src/types/collections.ts +1 -1
  27. package/src/types/datasource.ts +2 -2
  28. package/src/types/dialogs_controller.tsx +3 -1
  29. package/src/types/entity_actions.tsx +5 -2
  30. package/src/types/plugins.tsx +13 -0
  31. package/src/util/index.ts +0 -1
  32. package/src/util/join_collections.ts +3 -1
  33. package/src/util/navigation_utils.ts +0 -15
  34. package/src/util/strings.ts +11 -0
  35. package/dist/util/useDebounceValue.d.ts +0 -1
  36. package/src/util/useDebounceValue.ts +0 -18
@@ -67,7 +67,7 @@ export interface EntityCollection<M extends Record<string, any> = any, UserType
67
67
  properties: PropertiesOrBuilders<M>;
68
68
  /**
69
69
  * Can this collection be edited by the end user.
70
- * Defaults to `false`.
70
+ * Defaults to `true`.
71
71
  * Keep in mind that you can also set this prop to individual properties.
72
72
  */
73
73
  editable?: boolean;
@@ -156,7 +156,7 @@ export type SaveEntityDelegateProps<M extends Record<string, any> = any> = Omit<
156
156
  export type FetchCollectionDelegateProps<M extends Record<string, any> = any> = Omit<FetchCollectionProps<M>, "collection"> & {
157
157
  isCollectionGroup?: boolean;
158
158
  };
159
- export type ListenCollectionDelegateProps<M extends Record<string, any> = any> = Omit<ListenCollectionProps<M>, "collection"> & {
159
+ export type ListenCollectionDelegateProps<M extends Record<string, any> = any> = ListenCollectionProps<M> & {
160
160
  isCollectionGroup?: boolean;
161
161
  };
162
162
  export interface DataSourceDelegate {
@@ -1,3 +1,4 @@
1
+ import React from "react";
1
2
  /**
2
3
  * Controller to open the side dialog
3
4
  * @group Hooks and utilities
@@ -26,5 +27,6 @@ export interface DialogControllerEntryProps {
26
27
  */
27
28
  Component: React.ComponentType<{
28
29
  open: boolean;
30
+ closeDialog: () => void;
29
31
  }>;
30
32
  }
@@ -7,9 +7,11 @@ import { SideEntityController } from "./side_entity_controller";
7
7
  export type EntityAction<M extends object = any, UserType extends User = User> = {
8
8
  name: string;
9
9
  icon?: React.ReactElement;
10
- onClick: (props: EntityActionClickProps<M, UserType>) => Promise<void>;
10
+ onClick: (props: EntityActionClickProps<M, UserType>) => Promise<void> | void;
11
11
  /**
12
- * Show this action in the menu, defaults to true
12
+ * Show this action collapsed in the menu of the collection view.
13
+ * Defaults to true
14
+ * If false, the action will be shown in the menu
13
15
  */
14
16
  collapsed?: boolean;
15
17
  /**
@@ -97,6 +97,16 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollec
97
97
  };
98
98
  };
99
99
  collectionView?: {
100
+ showTextSearchBar?: (props: {
101
+ context: FireCMSContext;
102
+ path: string;
103
+ collection: EC;
104
+ }) => boolean;
105
+ onTextSearchClick?: (props: {
106
+ context: FireCMSContext;
107
+ path: string;
108
+ collection: EC;
109
+ }) => Promise<boolean>;
100
110
  /**
101
111
  * Use this method to inject widgets to the entity collections header
102
112
  * @param props
@@ -8,7 +8,6 @@ export * from "./paths";
8
8
  export * from "./regexp";
9
9
  export * from "./navigation_utils";
10
10
  export * from "./useDebounce";
11
- export * from "./useDebounceValue";
12
11
  export * from "./property_utils";
13
12
  export * from "./resolutions";
14
13
  export * from "./permissions";
@@ -1,5 +1,4 @@
1
1
  import { EntityCollection } from "../types";
2
- export declare function unslugify(slug: string): string;
3
2
  export declare function removeInitialAndTrailingSlashes(s: string): string;
4
3
  export declare function removeInitialSlash(s: string): string;
5
4
  export declare function removeTrailingSlash(s: string): string;
@@ -3,3 +3,4 @@ export declare const toSnakeCase: (str: string) => string;
3
3
  export declare function randomString(strLength?: number): string;
4
4
  export declare function randomColor(): string;
5
5
  export declare function slugify(text?: string, separator?: string, lowercase?: boolean): string;
6
+ export declare function unslugify(slug: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
- "version": "3.0.0-alpha.64",
3
+ "version": "3.0.0-alpha.65",
4
4
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
5
5
  "funding": {
6
6
  "url": "https://github.com/sponsors/firecmsco"
@@ -45,7 +45,7 @@
45
45
  "generateIcons": "ts-node --esm src/icons/generateIcons.ts"
46
46
  },
47
47
  "dependencies": {
48
- "@firecms/ui": "^3.0.0-alpha.64",
48
+ "@firecms/ui": "^3.0.0-alpha.65",
49
49
  "@fontsource/ibm-plex-mono": "^5.0.8",
50
50
  "@fontsource/roboto": "^5.0.8",
51
51
  "@hello-pangea/dnd": "^16.5.0",
@@ -116,7 +116,7 @@
116
116
  "dist",
117
117
  "src"
118
118
  ],
119
- "gitHead": "da6a7aca05fc7b09614f9d67409881e0cefd27f2",
119
+ "gitHead": "36de2bb0fb9e1baa20262d3c520830389e688f91",
120
120
  "publishConfig": {
121
121
  "access": "public"
122
122
  }
@@ -5,7 +5,6 @@ import {
5
5
  AdditionalFieldDelegate,
6
6
  CollectionSize,
7
7
  Entity,
8
- EntityCollection,
9
8
  FilterValues,
10
9
  FireCMSContext,
11
10
  ResolvedProperty,
@@ -111,7 +110,9 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
111
110
  AdditionalHeaderWidget,
112
111
  additionalIDHeaderWidget,
113
112
  emptyComponent,
114
- getIdColumnWidth
113
+ getIdColumnWidth,
114
+ onTextSearchClick,
115
+ textSearchLoading
115
116
  }: EntityCollectionTableProps<M>) {
116
117
 
117
118
  const largeLayout = useLargeLayout();
@@ -394,6 +395,8 @@ export const EntityCollectionTable = React.memo<EntityCollectionTableProps<any>>
394
395
  forceFilter={disabledFilterChange}
395
396
  filterIsSet={filterIsSet}
396
397
  onTextSearch={textSearchEnabled ? onTextSearch : undefined}
398
+ textSearchLoading={textSearchLoading}
399
+ onTextSearchClick={textSearchEnabled ? onTextSearchClick : undefined}
397
400
  clearFilter={clearFilter}
398
401
  size={size}
399
402
  onSizeChanged={updateSize}
@@ -515,4 +518,3 @@ function filterableProperty(property: ResolvedProperty, partOfArray = false): bo
515
518
  }
516
519
  return ["string", "number", "boolean", "date", "reference", "array"].includes(property.dataType);
517
520
  }
518
-
@@ -132,6 +132,10 @@ export type EntityCollectionTableProps<M extends Record<string, any>,
132
132
  emptyComponent?: React.ReactNode;
133
133
 
134
134
  getIdColumnWidth?: () => number;
135
+
136
+ onTextSearchClick?: () => void;
137
+
138
+ textSearchLoading?: boolean;
135
139
  }
136
140
 
137
141
  export type GetPropertyForProps<M extends Record<string, any>> = {
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useEffect } from "react";
2
2
 
3
3
  import {
4
4
  Button,
@@ -21,15 +21,27 @@ interface CollectionTableToolbarProps {
21
21
  actionsStart?: React.ReactNode;
22
22
  actions?: React.ReactNode;
23
23
  title?: React.ReactNode,
24
+ onTextSearchClick?: () => void;
24
25
  onTextSearch?: (searchString?: string) => void;
25
26
  onSizeChanged: (size: CollectionSize) => void;
26
27
  clearFilter?: () => void;
28
+ textSearchLoading?: boolean;
27
29
  }
28
30
 
29
31
  export function CollectionTableToolbar(props: CollectionTableToolbarProps) {
30
32
 
33
+ const searchInputRef = React.useRef<HTMLInputElement>(null);
31
34
  const largeLayout = useLargeLayout();
32
35
 
36
+ const searchLoading = React.useRef<boolean>(false);
37
+
38
+ useEffect(() => {
39
+ if (searchInputRef.current && searchLoading.current && !props.textSearchLoading) {
40
+ searchInputRef.current.focus();
41
+ }
42
+ searchLoading.current = props.textSearchLoading ?? false;
43
+ }, [props.textSearchLoading]);
44
+
33
45
  const clearFilterButton = !props.forceFilter && props.filterIsSet && props.clearFilter &&
34
46
  <Button
35
47
  variant={"outlined"}
@@ -82,12 +94,15 @@ export function CollectionTableToolbar(props: CollectionTableToolbarProps) {
82
94
  <CircularProgress size={"small"}/>}
83
95
  </div>}
84
96
 
85
- {props.onTextSearch &&
97
+ {(props.onTextSearch || props.onTextSearchClick) &&
86
98
  <SearchBar
87
99
  key={"search-bar"}
88
- onTextSearch={props.onTextSearch}
89
- expandable={true}/>
90
- }
100
+ inputRef={searchInputRef}
101
+ loading={props.textSearchLoading}
102
+ disabled={Boolean(props.onTextSearchClick)}
103
+ onClick={props.onTextSearchClick}
104
+ onTextSearch={props.onTextSearchClick ? undefined : props.onTextSearch}
105
+ expandable={true}/>}
91
106
 
92
107
  {props.actions}
93
108
 
@@ -1,8 +1,16 @@
1
- import React, { useCallback, useMemo } from "react";
1
+ import React, { useCallback, useEffect, useMemo, useState } from "react";
2
2
 
3
- import { useCollectionFetch, useDataSource, useNavigationController } from "../../hooks";
3
+ import { useDataSource, useFireCMSContext, useNavigationController } from "../../hooks";
4
4
  import { useDataOrder } from "../../hooks/data/useDataOrder";
5
- import { Entity, EntityCollection, FilterValues, SelectedCellProps, TableController, User } from "../../types";
5
+ import {
6
+ Entity,
7
+ EntityCollection,
8
+ FilterValues,
9
+ FireCMSContext,
10
+ SelectedCellProps,
11
+ TableController,
12
+ User
13
+ } from "../../types";
6
14
  import { useDebouncedData } from "./useDebouncedData";
7
15
 
8
16
  const DEFAULT_PAGE_SIZE = 50;
@@ -44,7 +52,6 @@ export function useEntityCollectionTableController<M extends Record<string, any>
44
52
  } = collection;
45
53
 
46
54
  const [popupCell, setPopupCell] = React.useState<SelectedCellProps<M> | undefined>(undefined);
47
-
48
55
  const navigation = useNavigationController();
49
56
  const dataSource = useDataSource();
50
57
  const resolvedPath = useMemo(() => navigation.resolveAliasesFrom(fullPath), [fullPath, navigation.resolveAliasesFrom]);
@@ -56,6 +63,28 @@ export function useEntityCollectionTableController<M extends Record<string, any>
56
63
  const [searchString, setSearchString] = React.useState<string | undefined>();
57
64
  const [itemCount, setItemCount] = React.useState<number | undefined>(paginationEnabled ? pageSize : undefined);
58
65
 
66
+ const initialSortInternal = useMemo(() => {
67
+ if (initialSort && forceFilter && !checkFilterCombination(forceFilter, initialSort)) {
68
+ console.warn("Initial sort is not compatible with the force filter. Ignoring initial sort");
69
+ return undefined;
70
+ }
71
+ return initialSort;
72
+ }, [initialSort, forceFilter]);
73
+
74
+ const [filterValues, setFilterValues] = React.useState<FilterValues<Extract<keyof M, string>> | undefined>(forceFilter ?? initialFilter ?? undefined);
75
+ const [sortBy, setSortBy] = React.useState<[Extract<keyof M, string>, "asc" | "desc"] | undefined>(initialSortInternal);
76
+
77
+ const sortByProperty = sortBy ? sortBy[0] : undefined;
78
+ const currentSort = sortBy ? sortBy[1] : undefined;
79
+
80
+ const context: FireCMSContext<UserType> = useFireCMSContext();
81
+
82
+ const [rawData, setRawData] = useState<Entity<M>[]>([]);
83
+
84
+ const [dataLoading, setDataLoading] = useState<boolean>(false);
85
+ const [dataLoadingError, setDataLoadingError] = useState<Error | undefined>();
86
+ const [noMoreToLoad, setNoMoreToLoad] = useState<boolean>(false);
87
+
59
88
  const checkFilterCombination = useCallback((filterValues: FilterValues<any>,
60
89
  sortBy?: [string, "asc" | "desc"]) => {
61
90
  if (!dataSource.isFilterCombinationValid)
@@ -68,17 +97,6 @@ export function useEntityCollectionTableController<M extends Record<string, any>
68
97
  })
69
98
  }, []);
70
99
 
71
- const initialSortInternal = useMemo(() => {
72
- if (initialSort && forceFilter && !checkFilterCombination(forceFilter, initialSort)) {
73
- console.warn("Initial sort is not compatible with the force filter. Ignoring initial sort");
74
- return undefined;
75
- }
76
- return initialSort;
77
- }, [initialSort, forceFilter]);
78
-
79
- const [filterValues, setFilterValues] = React.useState<FilterValues<Extract<keyof M, string>> | undefined>(forceFilter ?? initialFilter ?? undefined);
80
- const [sortBy, setSortBy] = React.useState<[Extract<keyof M, string>, "asc" | "desc"] | undefined>(initialSortInternal);
81
-
82
100
  const clearFilter = useCallback(() => setFilterValues(forceFilter ?? undefined), [forceFilter]);
83
101
 
84
102
  const updateFilterValues = useCallback((updatedFilter: FilterValues<Extract<keyof M, string>> | undefined) => {
@@ -93,19 +111,71 @@ export function useEntityCollectionTableController<M extends Record<string, any>
93
111
  }
94
112
  }, [forceFilter]);
95
113
 
96
- const {
97
- data: rawData,
98
- dataLoading,
99
- noMoreToLoad,
100
- dataLoadingError
101
- } = useCollectionFetch<M, UserType>({
102
- path: fullPath,
103
- collection,
104
- filterValues,
105
- sortBy,
106
- searchString,
107
- itemCount
108
- });
114
+ useEffect(() => {
115
+
116
+ setDataLoading(true);
117
+
118
+ const onEntitiesUpdate = async (entities: Entity<M>[]) => {
119
+ if (collection.callbacks?.onFetch) {
120
+ try {
121
+ entities = await Promise.all(
122
+ entities.map((entity) =>
123
+ collection.callbacks!.onFetch!({
124
+ collection,
125
+ path: resolvedPath,
126
+ entity,
127
+ context
128
+ })));
129
+ } catch (e: any) {
130
+ console.error(e);
131
+ }
132
+ }
133
+ setDataLoading(false);
134
+ setDataLoadingError(undefined);
135
+ setRawData(entities.map(e => ({
136
+ ...e,
137
+ // values: sanitizeData(e.values, resolvedCollection.properties)
138
+ })));
139
+ setNoMoreToLoad(!itemCount || entities.length < itemCount);
140
+ };
141
+
142
+ const onError = (error: Error) => {
143
+ console.error("ERROR", error);
144
+ setDataLoading(false);
145
+ setRawData([]);
146
+ setDataLoadingError(error);
147
+ };
148
+
149
+ if (dataSource.listenCollection) {
150
+ return dataSource.listenCollection<M>({
151
+ path: resolvedPath,
152
+ collection,
153
+ onUpdate: onEntitiesUpdate,
154
+ onError,
155
+ searchString,
156
+ filter: filterValues,
157
+ limit: itemCount,
158
+ startAfter: undefined,
159
+ orderBy: sortByProperty,
160
+ order: currentSort
161
+ });
162
+ } else {
163
+ dataSource.fetchCollection<M>({
164
+ path: resolvedPath,
165
+ collection,
166
+ searchString,
167
+ filter: filterValues,
168
+ limit: itemCount,
169
+ startAfter: undefined,
170
+ orderBy: sortByProperty,
171
+ order: currentSort
172
+ })
173
+ .then(onEntitiesUpdate)
174
+ .catch(onError);
175
+ return () => {
176
+ };
177
+ }
178
+ }, [resolvedPath, itemCount, currentSort, sortByProperty, filterValues, searchString]);
109
179
 
110
180
  const orderedData = useDataOrder({
111
181
  data: rawData,
@@ -137,6 +137,7 @@ export const EntityCollectionView = React.memo(
137
137
  const [selectedNavigationEntity, setSelectedNavigationEntity] = useState<Entity<M> | undefined>(undefined);
138
138
  const [deleteEntityClicked, setDeleteEntityClicked] = React.useState<Entity<M> | Entity<M>[] | undefined>(undefined);
139
139
 
140
+ const [textSearchLoading, setTextSearchLoading] = useState<boolean>(false);
140
141
  const [lastDeleteTimestamp, setLastDeleteTimestamp] = React.useState<number>(0);
141
142
 
142
143
  // number of entities in the collection
@@ -299,7 +300,7 @@ export const EntityCollectionView = React.memo(
299
300
  propertyKey,
300
301
  onValueUpdated,
301
302
  setError,
302
- entity
303
+ entity,
303
304
  }) => {
304
305
 
305
306
  const updatedValues = setIn({ ...entity.values }, propertyKey, value);
@@ -426,7 +427,10 @@ export const EntityCollectionView = React.memo(
426
427
 
427
428
  const largeLayout = useLargeLayout();
428
429
 
429
- const getActionsForEntity = useCallback(({ entity, customEntityActions }: { entity?: Entity<M>, customEntityActions?: EntityAction[] }): EntityAction[] => {
430
+ const getActionsForEntity = useCallback(({ entity, customEntityActions }: {
431
+ entity?: Entity<M>,
432
+ customEntityActions?: EntityAction[]
433
+ }): EntityAction[] => {
430
434
  const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPathToCollectionSegments(fullPath), entity) : true;
431
435
  const actions: EntityAction[] = [editEntityAction];
432
436
  if (createEnabled)
@@ -555,6 +559,35 @@ export const EntityCollectionView = React.memo(
555
559
  }
556
560
  : undefined;
557
561
 
562
+ const [textSearchInitialised, setTextSearchInitialised] = useState<boolean>(false);
563
+ let onTextSearchClick: (() => void) | undefined;
564
+ let textSearchEnabled = Boolean(collection.textSearchEnabled);
565
+ if (context?.plugins) {
566
+ const addTextSearchClickListener = context.plugins?.find(p => Boolean(p.collectionView?.onTextSearchClick));
567
+
568
+ onTextSearchClick = addTextSearchClickListener
569
+ ? () => {
570
+ setTextSearchLoading(true);
571
+ Promise.all(context.plugins?.map(p => {
572
+ if (p.collectionView?.onTextSearchClick)
573
+ return p.collectionView.onTextSearchClick({ context, path: resolvedFullPath, collection });
574
+ return Promise.resolve(true);
575
+ }) as Promise<boolean>[])
576
+ .then((res) => {
577
+ if (res.every(Boolean)) setTextSearchInitialised(true);
578
+ })
579
+ .finally(() => setTextSearchLoading(false));
580
+ }
581
+ : undefined;
582
+
583
+ context.plugins?.forEach(p => {
584
+ if (!textSearchEnabled)
585
+ if (p.collectionView?.showTextSearchBar) {
586
+ textSearchEnabled = p.collectionView.showTextSearchBar({ context, path: resolvedFullPath, collection });
587
+ }
588
+ })
589
+ }
590
+
558
591
  return (
559
592
  <div className={cn("overflow-hidden h-full w-full", className)}
560
593
  ref={containerRef}>
@@ -575,6 +608,9 @@ export const EntityCollectionView = React.memo(
575
608
  defaultSize={collection.defaultSize}
576
609
  properties={resolvedCollection.properties}
577
610
  getPropertyFor={getPropertyFor}
611
+ onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
612
+ textSearchLoading={textSearchLoading}
613
+ textSearchEnabled={textSearchEnabled}
578
614
  actions={<EntityCollectionViewActions
579
615
  parentCollectionIds={parentCollectionIds ?? []}
580
616
  collection={collection}
@@ -48,7 +48,6 @@ export function EntityCollectionViewActions<M extends Record<string, any>>({
48
48
  id={`add_entity_${path}`}
49
49
  onClick={onNewClick}
50
50
  startIcon={<AddIcon/>}
51
- size="large"
52
51
  variant="filled"
53
52
  color="primary">
54
53
  Add {collection.singularName ?? collection.name}
@@ -26,7 +26,7 @@ export function ErrorView({
26
26
  tooltip
27
27
  }: ErrorViewProps): React.ReactElement {
28
28
  const component = error instanceof Error ? error.message : error;
29
- console.log("ErrorView", error)
29
+ console.error("ErrorView", error)
30
30
 
31
31
  const body = (
32
32
  <div
@@ -133,6 +133,7 @@ export function DefaultHomePage({
133
133
  <SearchBar onTextSearch={updateSearchResults}
134
134
  placeholder={"Search collections"}
135
135
  large={false}
136
+ innerClassName={"w-full"}
136
137
  className={"w-full flex-grow"}/>
137
138
  {additionalActions}
138
139
  </div>
@@ -44,7 +44,9 @@ export const DialogsProvider: React.FC<PropsWithChildren<{}>> = ({ children }) =
44
44
  {children}
45
45
  {dialogEntries.map((entry, i) => <entry.Component
46
46
  key={`dialog_${i}`}
47
- open={true}/>)}
47
+ open={true}
48
+ closeDialog={close}
49
+ />)}
48
50
  </DialogsControllerContext.Provider>
49
51
  );
50
52
  };
@@ -121,7 +121,8 @@ export function useBuildDataSource({
121
121
  order,
122
122
  onUpdate,
123
123
  onError,
124
- isCollectionGroup
124
+ isCollectionGroup,
125
+ collection
125
126
  });
126
127
  }, [delegate, navigationController.getCollection])
127
128
  : undefined,
@@ -78,7 +78,7 @@ export interface EntityCollection<M extends Record<string, any> = any, UserType
78
78
 
79
79
  /**
80
80
  * Can this collection be edited by the end user.
81
- * Defaults to `false`.
81
+ * Defaults to `true`.
82
82
  * Keep in mind that you can also set this prop to individual properties.
83
83
  */
84
84
  editable?: boolean;
@@ -234,9 +234,9 @@ export type FetchCollectionDelegateProps<M extends Record<string, any> = any> =
234
234
  };
235
235
 
236
236
  export type ListenCollectionDelegateProps<M extends Record<string, any> = any> =
237
- Omit<ListenCollectionProps<M>, "collection">
237
+ ListenCollectionProps<M>
238
238
  & {
239
- isCollectionGroup?: boolean
239
+ isCollectionGroup?: boolean;
240
240
  };
241
241
 
242
242
  export interface DataSourceDelegate {
@@ -1,3 +1,5 @@
1
+ import React from "react";
2
+
1
3
  /**
2
4
  * Controller to open the side dialog
3
5
  * @group Hooks and utilities
@@ -26,6 +28,6 @@ export interface DialogControllerEntryProps {
26
28
  /**
27
29
  * The component type that will be rendered
28
30
  */
29
- Component: React.ComponentType<{ open: boolean }>;
31
+ Component: React.ComponentType<{ open: boolean, closeDialog: () => void }>;
30
32
 
31
33
  }
@@ -8,9 +8,12 @@ import { SideEntityController } from "./side_entity_controller";
8
8
  export type EntityAction<M extends object = any, UserType extends User = User> = {
9
9
  name: string;
10
10
  icon?: React.ReactElement;
11
- onClick: (props: EntityActionClickProps<M, UserType>) => Promise<void>;
11
+ onClick: (props: EntityActionClickProps<M, UserType>) => Promise<void> | void;
12
+
12
13
  /**
13
- * Show this action in the menu, defaults to true
14
+ * Show this action collapsed in the menu of the collection view.
15
+ * Defaults to true
16
+ * If false, the action will be shown in the menu
14
17
  */
15
18
  collapsed?: boolean;
16
19
 
@@ -121,6 +121,19 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollec
121
121
  }
122
122
 
123
123
  collectionView?: {
124
+
125
+ showTextSearchBar?: (props: {
126
+ context: FireCMSContext,
127
+ path: string,
128
+ collection: EC
129
+ }) => boolean;
130
+
131
+ onTextSearchClick?: (props: {
132
+ context: FireCMSContext,
133
+ path: string,
134
+ collection: EC
135
+ }) => Promise<boolean>;
136
+
124
137
  /**
125
138
  * Use this method to inject widgets to the entity collections header
126
139
  * @param props
package/src/util/index.ts CHANGED
@@ -8,7 +8,6 @@ export * from "./paths";
8
8
  export * from "./regexp";
9
9
  export * from "./navigation_utils";
10
10
  export * from "./useDebounce";
11
- export * from "./useDebounceValue";
12
11
  export * from "./property_utils";
13
12
  export * from "./resolutions";
14
13
  export * from "./permissions";
@@ -118,9 +118,11 @@ export function mergeCollection(target: EntityCollection,
118
118
  return resultCollection
119
119
  }
120
120
 
121
- function mergePropertyOrBuilder(target: Property, source: PropertyOrBuilder): PropertyOrBuilder {
121
+ function mergePropertyOrBuilder(target: PropertyOrBuilder, source: PropertyOrBuilder): PropertyOrBuilder {
122
122
  if (isPropertyBuilder(source)) {
123
123
  return source;
124
+ } else if (isPropertyBuilder(target)) {
125
+ return target;
124
126
  } else {
125
127
  const mergedProperty = mergeDeep(target, source);
126
128
  const targetEditable = Boolean(target.editable);
@@ -1,20 +1,5 @@
1
1
  import { EntityCollection } from "../types";
2
2
 
3
- export function unslugify(slug: string): string {
4
- if (slug.includes("-") || slug.includes("_") || !slug.includes(" ")) {
5
- const result = slug.replace(/[-_]/g, " ");
6
- return result.replace(/\w\S*/g, function (txt) {
7
- return txt.charAt(0).toUpperCase() + txt.substr(1);
8
- });
9
- } else {
10
- return slug;
11
- // const unCamelCased = slug.replace(/([a-z])([A-Z])/g, "$1 $2");
12
- // return unCamelCased.replace(/\b\w/g, function (l) {
13
- // return l.toUpperCase()
14
- // });
15
- }
16
- }
17
-
18
3
  export function removeInitialAndTrailingSlashes(s: string): string {
19
4
  return removeInitialSlash(removeTrailingSlash(s));
20
5
  }
@@ -49,3 +49,14 @@ export function slugify(text?: string, separator = "_", lowercase = true) {
49
49
  ? text.toLowerCase() // Convert the string to lowercase letters
50
50
  : text;
51
51
  }
52
+
53
+ export function unslugify(slug: string): string {
54
+ if (slug.includes("-") || slug.includes("_") || !slug.includes(" ")) {
55
+ const result = slug.replace(/[-_]/g, " ");
56
+ return result.replace(/\w\S*/g, function (txt) {
57
+ return txt.charAt(0).toUpperCase() + txt.substr(1);
58
+ });
59
+ } else {
60
+ return slug;
61
+ }
62
+ }
@@ -1 +0,0 @@
1
- export declare function useDebounceValue<T>(value: T, delay?: number): T;
@@ -1,18 +0,0 @@
1
- import { useEffect, useState } from "react";
2
-
3
- export function useDebounceValue<T>(value: T, delay = 300): T {
4
- // State and setters for debounced value
5
- const [debouncedValue, setDebouncedValue] = useState(value);
6
- useEffect(
7
- () => {
8
- const handler = setTimeout(() => {
9
- setDebouncedValue(value);
10
- }, delay);
11
- return () => {
12
- clearTimeout(handler);
13
- };
14
- },
15
- [value, delay] // Only re-call effect if value or delay changes
16
- );
17
- return debouncedValue;
18
- }