@firecms/core 3.0.0-alpha.63 → 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.
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +2 -0
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +2 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.es.js +4238 -4194
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +5 -5
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collections.d.ts +1 -1
- package/dist/types/datasource.d.ts +1 -1
- package/dist/types/dialogs_controller.d.ts +2 -0
- package/dist/types/entity_actions.d.ts +4 -2
- package/dist/types/plugins.d.ts +10 -0
- package/dist/util/index.d.ts +0 -1
- package/dist/util/navigation_utils.d.ts +0 -1
- package/dist/util/strings.d.ts +1 -0
- package/package.json +3 -3
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +5 -3
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +4 -0
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -5
- package/src/components/EntityCollectionTable/useEntityCollectionTableController.tsx +98 -28
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +38 -2
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +0 -1
- package/src/components/ErrorView.tsx +1 -1
- package/src/components/HomePage/DefaultHomePage.tsx +1 -0
- package/src/contexts/DialogsProvider.tsx +3 -1
- package/src/core/Scaffold.tsx +5 -8
- package/src/index.ts +0 -2
- package/src/internal/useBuildDataSource.ts +2 -1
- package/src/types/collections.ts +1 -1
- package/src/types/datasource.ts +2 -2
- package/src/types/dialogs_controller.tsx +3 -1
- package/src/types/entity_actions.tsx +5 -2
- package/src/types/plugins.tsx +13 -0
- package/src/util/index.ts +0 -1
- package/src/util/join_collections.ts +3 -1
- package/src/util/navigation_utils.ts +0 -15
- package/src/util/strings.ts +11 -0
- package/dist/util/useDebounceValue.d.ts +0 -1
- 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 `
|
|
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> =
|
|
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
|
|
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
|
/**
|
package/dist/types/plugins.d.ts
CHANGED
|
@@ -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
|
package/dist/util/index.d.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";
|
|
@@ -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;
|
package/dist/util/strings.d.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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": "
|
|
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
|
-
|
|
89
|
-
|
|
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 {
|
|
3
|
+
import { useDataSource, useFireCMSContext, useNavigationController } from "../../hooks";
|
|
4
4
|
import { useDataOrder } from "../../hooks/data/useDataOrder";
|
|
5
|
-
import {
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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 }: {
|
|
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}
|
|
@@ -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
|
};
|
package/src/core/Scaffold.tsx
CHANGED
|
@@ -83,7 +83,6 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
|
|
|
83
83
|
|
|
84
84
|
const largeLayout = useLargeLayout();
|
|
85
85
|
|
|
86
|
-
const navigation = useNavigationController();
|
|
87
86
|
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
|
88
87
|
const [onHover, setOnHover] = React.useState(false);
|
|
89
88
|
|
|
@@ -124,13 +123,11 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
|
|
|
124
123
|
hovered={onHover}
|
|
125
124
|
setDrawerOpen={setDrawerOpen}>
|
|
126
125
|
{includeDrawer && (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
closeDrawer={handleDrawerClose}
|
|
133
|
-
{...drawerProps}/>)}
|
|
126
|
+
<Drawer
|
|
127
|
+
hovered={onHover}
|
|
128
|
+
drawerOpen={computedDrawerOpen}
|
|
129
|
+
closeDrawer={handleDrawerClose}
|
|
130
|
+
{...drawerProps}/> )}
|
|
134
131
|
</StyledDrawer>
|
|
135
132
|
|
|
136
133
|
<main
|
package/src/index.ts
CHANGED
package/src/types/collections.ts
CHANGED
|
@@ -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 `
|
|
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;
|
package/src/types/datasource.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
package/src/types/plugins.tsx
CHANGED
|
@@ -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:
|
|
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
|
}
|