@firecms/core 3.0.0-canary.168 → 3.0.0-canary.169
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +2 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -2
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +15 -1
- package/dist/components/EntityCollectionTable/index.d.ts +1 -1
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
- package/dist/components/SelectableTable/SelectableTable.d.ts +12 -2
- package/dist/components/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +13 -0
- package/dist/components/common/index.d.ts +1 -1
- package/dist/components/common/{useDataSourceEntityCollectionTableController.d.ts → useDataSourceTableController.d.ts} +10 -2
- package/dist/components/common/useDebouncedCallback.d.ts +1 -0
- package/dist/components/common/useScrollRestoration.d.ts +14 -0
- package/dist/contexts/BreacrumbsContext.d.ts +8 -0
- package/dist/core/EntityEditView.d.ts +25 -7
- package/dist/core/FireCMSRouter.d.ts +4 -0
- package/dist/core/NavigationRoutes.d.ts +0 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/hooks/data/useEntityFetch.d.ts +2 -1
- package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
- package/dist/hooks/useBuildNavigationController.d.ts +0 -1
- package/dist/index.es.js +2344 -1305
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2344 -1306
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useUnsavedChangesDialog.d.ts +7 -9
- package/dist/routes/CustomCMSRoute.d.ts +4 -0
- package/dist/routes/FireCMSRoute.d.ts +1 -0
- package/dist/routes/HomePageRoute.d.ts +3 -0
- package/dist/types/collections.d.ts +18 -6
- package/dist/types/datasource.d.ts +2 -2
- package/dist/types/dialogs_controller.d.ts +3 -7
- package/dist/types/entities.d.ts +1 -1
- package/dist/types/entity_actions.d.ts +1 -6
- package/dist/types/navigation.d.ts +28 -12
- package/dist/types/plugins.d.ts +1 -0
- package/dist/types/side_entity_controller.d.ts +1 -1
- package/dist/util/entity_cache.d.ts +23 -0
- package/dist/util/navigation_from_path.d.ts +5 -1
- package/dist/util/navigation_utils.d.ts +12 -1
- package/package.json +5 -5
- package/src/components/CircularProgressCenter.tsx +1 -1
- package/src/components/DeleteEntityDialog.tsx +1 -1
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +40 -19
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +10 -4
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +18 -1
- package/src/components/EntityCollectionTable/index.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +3 -3
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +79 -38
- package/src/components/NotFoundPage.tsx +2 -2
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +9 -5
- package/src/components/SelectableTable/SelectableTable.tsx +18 -3
- package/src/components/UnsavedChangesDialog.tsx +42 -0
- package/src/components/VirtualTable/VirtualTable.tsx +19 -2
- package/src/components/VirtualTable/VirtualTableProps.tsx +15 -0
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +1 -1
- package/src/components/common/default_entity_actions.tsx +24 -10
- package/src/components/common/index.ts +1 -1
- package/src/components/common/{useDataSourceEntityCollectionTableController.tsx → useDataSourceTableController.tsx} +122 -13
- package/src/components/common/useDebouncedCallback.tsx +20 -0
- package/src/components/common/useScrollRestoration.tsx +68 -0
- package/src/contexts/BreacrumbsContext.tsx +38 -0
- package/src/contexts/DialogsProvider.tsx +2 -3
- package/src/core/DefaultAppBar.tsx +28 -4
- package/src/core/EntityEditView.tsx +457 -296
- package/src/core/EntitySidePanel.tsx +72 -6
- package/src/core/FireCMS.tsx +7 -4
- package/src/core/FireCMSRouter.tsx +17 -0
- package/src/core/NavigationRoutes.tsx +15 -28
- package/src/core/SideDialogs.tsx +10 -5
- package/src/core/index.tsx +4 -2
- package/src/hooks/data/save.ts +1 -1
- package/src/hooks/data/useCollectionFetch.tsx +1 -1
- package/src/hooks/data/useEntityFetch.tsx +5 -1
- package/src/hooks/useBreadcrumbsController.tsx +31 -0
- package/src/hooks/useBuildNavigationController.tsx +59 -21
- package/src/hooks/useLargeLayout.tsx +0 -35
- package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
- package/src/internal/useBuildSideDialogsController.tsx +4 -2
- package/src/internal/useBuildSideEntityController.tsx +157 -77
- package/src/internal/useUnsavedChangesDialog.tsx +126 -92
- package/src/routes/CustomCMSRoute.tsx +21 -0
- package/src/routes/FireCMSRoute.tsx +230 -0
- package/src/routes/HomePageRoute.tsx +17 -0
- package/src/types/collections.ts +19 -6
- package/src/types/datasource.ts +2 -1
- package/src/types/dialogs_controller.tsx +3 -7
- package/src/types/entities.ts +1 -1
- package/src/types/entity_actions.tsx +1 -7
- package/src/types/navigation.ts +35 -16
- package/src/types/plugins.tsx +1 -0
- package/src/types/side_entity_controller.tsx +1 -1
- package/src/util/entity_cache.ts +196 -0
- package/src/util/navigation_from_path.ts +10 -2
- package/src/util/navigation_utils.ts +53 -1
- package/src/util/parent_references_from_path.ts +3 -3
- /package/src/util/{common.tsx → common.ts} +0 -0
|
@@ -9,14 +9,15 @@ import {
|
|
|
9
9
|
FilterValues,
|
|
10
10
|
FireCMSContext,
|
|
11
11
|
SelectedCellProps,
|
|
12
|
-
User
|
|
12
|
+
User,
|
|
13
|
+
WhereFilterOp
|
|
13
14
|
} from "../../types";
|
|
14
15
|
import { useDebouncedData } from "./useDebouncedData";
|
|
15
|
-
import
|
|
16
|
+
import { ScrollRestorationController } from "./useScrollRestoration";
|
|
16
17
|
|
|
17
18
|
const DEFAULT_PAGE_SIZE = 50;
|
|
18
19
|
|
|
19
|
-
export type
|
|
20
|
+
export type DataSourceTableControllerProps<M extends Record<string, any> = any> = {
|
|
20
21
|
/**
|
|
21
22
|
* Full path where the data of this table is located
|
|
22
23
|
*/
|
|
@@ -37,6 +38,14 @@ export type DataSourceEntityCollectionTableControllerProps<M extends Record<stri
|
|
|
37
38
|
* Force filter to be applied to the table.
|
|
38
39
|
*/
|
|
39
40
|
forceFilter?: FilterValues<string>;
|
|
41
|
+
|
|
42
|
+
scrollRestoration?: ScrollRestorationController;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* When set to true the filters and sort will be updated in the URL
|
|
46
|
+
*/
|
|
47
|
+
updateUrl?: boolean;
|
|
48
|
+
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
/**
|
|
@@ -48,18 +57,22 @@ export type DataSourceEntityCollectionTableControllerProps<M extends Record<stri
|
|
|
48
57
|
*
|
|
49
58
|
* @param fullPath
|
|
50
59
|
* @param collection
|
|
60
|
+
* @param scrollRestoration
|
|
51
61
|
* @param entitiesDisplayedFirst
|
|
52
62
|
* @param lastDeleteTimestamp
|
|
53
63
|
* @param forceFilterFromProps
|
|
64
|
+
* @param updateUrl
|
|
54
65
|
*/
|
|
55
|
-
export function
|
|
66
|
+
export function useDataSourceTableController<M extends Record<string, any> = any, USER extends User = User>(
|
|
56
67
|
{
|
|
57
68
|
fullPath,
|
|
58
69
|
collection,
|
|
70
|
+
scrollRestoration,
|
|
59
71
|
entitiesDisplayedFirst,
|
|
60
72
|
lastDeleteTimestamp,
|
|
61
73
|
forceFilter: forceFilterFromProps,
|
|
62
|
-
|
|
74
|
+
updateUrl
|
|
75
|
+
}: DataSourceTableControllerProps<M>)
|
|
63
76
|
: EntityTableController<M> {
|
|
64
77
|
|
|
65
78
|
const {
|
|
@@ -71,14 +84,13 @@ export function useDataSourceEntityCollectionTableController<M extends Record<st
|
|
|
71
84
|
const [popupCell, setPopupCell] = React.useState<SelectedCellProps<M> | undefined>(undefined);
|
|
72
85
|
const navigation = useNavigationController();
|
|
73
86
|
const dataSource = useDataSource(collection);
|
|
74
|
-
const resolvedPath = useMemo(() => navigation.
|
|
87
|
+
const resolvedPath = useMemo(() => navigation.resolveIdsFrom(fullPath), [fullPath, navigation.resolveIdsFrom]);
|
|
75
88
|
|
|
76
89
|
const forceFilter = forceFilterFromProps ?? forceFilterFromCollection;
|
|
77
90
|
const paginationEnabled = collection.pagination === undefined || Boolean(collection.pagination);
|
|
78
91
|
const pageSize = typeof collection.pagination === "number" ? collection.pagination : DEFAULT_PAGE_SIZE;
|
|
79
92
|
|
|
80
93
|
const [searchString, setSearchString] = React.useState<string | undefined>();
|
|
81
|
-
const [itemCount, setItemCount] = React.useState<number | undefined>(paginationEnabled ? pageSize : undefined);
|
|
82
94
|
|
|
83
95
|
const checkFilterCombination = useCallback((filterValues: FilterValues<any>,
|
|
84
96
|
sortBy?: [string, "asc" | "desc"]) => {
|
|
@@ -92,6 +104,21 @@ export function useDataSourceEntityCollectionTableController<M extends Record<st
|
|
|
92
104
|
})
|
|
93
105
|
}, []);
|
|
94
106
|
|
|
107
|
+
const onScroll = ({
|
|
108
|
+
scrollOffset
|
|
109
|
+
}: {
|
|
110
|
+
scrollOffset: number
|
|
111
|
+
}) => {
|
|
112
|
+
if (scrollRestoration) {
|
|
113
|
+
scrollRestoration.updateCollectionScroll({
|
|
114
|
+
fullPath: resolvedPath,
|
|
115
|
+
scrollOffset,
|
|
116
|
+
data: rawData,
|
|
117
|
+
filters: filterValues
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
95
122
|
const initialSortInternal = useMemo(() => {
|
|
96
123
|
if (initialSort && forceFilter && !checkFilterCombination(forceFilter, initialSort)) {
|
|
97
124
|
console.warn("Initial sort is not compatible with the force filter. Ignoring initial sort");
|
|
@@ -100,21 +127,38 @@ export function useDataSourceEntityCollectionTableController<M extends Record<st
|
|
|
100
127
|
return initialSort;
|
|
101
128
|
}, [initialSort, forceFilter]);
|
|
102
129
|
|
|
130
|
+
const {
|
|
131
|
+
filterValues: initialFilteUrl,
|
|
132
|
+
sortBy: initialSortUrl,
|
|
133
|
+
} = parseFilterAndSort(window.location.search);
|
|
134
|
+
|
|
135
|
+
const [filterValues, setFilterValues] = React.useState<FilterValues<Extract<keyof M, string>> | undefined>(forceFilter ?? initialFilteUrl ?? initialFilter ?? undefined);
|
|
136
|
+
const [sortBy, setSortBy] = React.useState<[Extract<keyof M, string>, "asc" | "desc"] | undefined>(initialSortUrl ?? initialSortInternal);
|
|
137
|
+
|
|
138
|
+
useUpdateUrl(filterValues, sortBy, searchString, updateUrl);
|
|
139
|
+
|
|
140
|
+
const collectionScroll = scrollRestoration?.getCollectionScroll(fullPath, filterValues);
|
|
141
|
+
const initialItemCount = collectionScroll?.data.length ?? pageSize;
|
|
142
|
+
|
|
103
143
|
useEffect(() => {
|
|
104
|
-
if (
|
|
105
|
-
|
|
144
|
+
if (scrollRestoration) {
|
|
145
|
+
scrollRestoration.updateCollectionScroll({
|
|
146
|
+
fullPath: resolvedPath,
|
|
147
|
+
scrollOffset: collectionScroll?.scrollOffset ?? 0,
|
|
148
|
+
data: rawData,
|
|
149
|
+
filters: filterValues
|
|
150
|
+
});
|
|
106
151
|
}
|
|
107
|
-
}, [
|
|
152
|
+
}, []);
|
|
108
153
|
|
|
109
|
-
const [
|
|
110
|
-
const [sortBy, setSortBy] = React.useState<[Extract<keyof M, string>, "asc" | "desc"] | undefined>(initialSortInternal);
|
|
154
|
+
const [itemCount, setItemCount] = React.useState<number | undefined>(paginationEnabled ? initialItemCount : undefined);
|
|
111
155
|
|
|
112
156
|
const sortByProperty = sortBy ? sortBy[0] : undefined;
|
|
113
157
|
const currentSort = sortBy ? sortBy[1] : undefined;
|
|
114
158
|
|
|
115
159
|
const context: FireCMSContext<USER> = useFireCMSContext();
|
|
116
160
|
|
|
117
|
-
const [rawData, setRawData] = useState<Entity<M>[]>([]);
|
|
161
|
+
const [rawData, setRawData] = useState<Entity<M>[]>(collectionScroll?.data ?? []);
|
|
118
162
|
|
|
119
163
|
const [dataLoading, setDataLoading] = useState<boolean>(false);
|
|
120
164
|
const [dataLoadingError, setDataLoadingError] = useState<Error | undefined>();
|
|
@@ -227,6 +271,8 @@ export function useDataSourceEntityCollectionTableController<M extends Record<st
|
|
|
227
271
|
clearFilter,
|
|
228
272
|
itemCount,
|
|
229
273
|
setItemCount,
|
|
274
|
+
initialScroll: collectionScroll?.scrollOffset,
|
|
275
|
+
onScroll,
|
|
230
276
|
paginationEnabled,
|
|
231
277
|
pageSize,
|
|
232
278
|
checkFilterCombination,
|
|
@@ -234,3 +280,66 @@ export function useDataSourceEntityCollectionTableController<M extends Record<st
|
|
|
234
280
|
setPopupCell
|
|
235
281
|
}
|
|
236
282
|
}
|
|
283
|
+
|
|
284
|
+
function useUpdateUrl<M extends Record<string, any> = any>(
|
|
285
|
+
filterValues: FilterValues<Extract<keyof M, string>> | undefined,
|
|
286
|
+
sortBy: [Extract<keyof M, string>, "asc" | "desc"] | undefined,
|
|
287
|
+
searchString: string | undefined,
|
|
288
|
+
updateUrl: boolean | undefined
|
|
289
|
+
) {
|
|
290
|
+
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
if (updateUrl) {
|
|
293
|
+
const newUrl = encodeFilterAndSort(filterValues, sortBy);
|
|
294
|
+
const search = searchString ? `&search=${encodeURIComponent(searchString)}` : "";
|
|
295
|
+
const state = `${newUrl}${search}`;
|
|
296
|
+
const hash = window.location.hash;
|
|
297
|
+
if (state === "")
|
|
298
|
+
window.history.replaceState({}, "", `${window.location.pathname}${hash}`);
|
|
299
|
+
else
|
|
300
|
+
window.history.replaceState({}, "", `?${state}${hash}`);
|
|
301
|
+
}
|
|
302
|
+
}, [filterValues, sortBy, searchString, updateUrl]);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function encodeFilterAndSort(filterValues?: FilterValues<string> | undefined, sortBy?: [string, "asc" | "desc"] | undefined) {
|
|
306
|
+
const entries: Record<string, string> = {};
|
|
307
|
+
if (sortBy) {
|
|
308
|
+
entries["__sort"] = encodeURIComponent(sortBy[0]);
|
|
309
|
+
entries["__sort_order"] = encodeURIComponent(sortBy[1]);
|
|
310
|
+
}
|
|
311
|
+
if (filterValues) {
|
|
312
|
+
Object.entries(filterValues).forEach(([key, value]) => {
|
|
313
|
+
if (value) {
|
|
314
|
+
entries[encodeURIComponent(`${key}_op`)] = encodeURIComponent(value[0]);
|
|
315
|
+
entries[encodeURIComponent(`${key}_value`)] = encodeURIComponent(value[1]);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (!Object.keys(entries).length) {
|
|
320
|
+
return "";
|
|
321
|
+
}
|
|
322
|
+
return Object.entries(entries).map(([key, value]) => `${key}=${value}`).join("&");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function parseFilterAndSort<M>(search: string): {
|
|
326
|
+
filterValues: FilterValues<string> | undefined,
|
|
327
|
+
sortBy?: [Extract<keyof M, string>, "asc" | "desc"]
|
|
328
|
+
} {
|
|
329
|
+
const entries = new URLSearchParams(search);
|
|
330
|
+
const filterValues: FilterValues<string> = {};
|
|
331
|
+
let sortBy: [string, "asc" | "desc"] | undefined = undefined;
|
|
332
|
+
entries.forEach((value, key) => {
|
|
333
|
+
if (key === "__sort") {
|
|
334
|
+
sortBy = [value, entries.get("__sort_order") as "asc" | "desc"];
|
|
335
|
+
} else if (key.endsWith("_op")) {
|
|
336
|
+
const filterValue = entries.get(`${key.replace("_op", "_value")}`);
|
|
337
|
+
filterValues[key.replace("_op", "")] = [value as WhereFilterOp, filterValue as string];
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
filterValues: Object.keys(filterValues).length ? filterValues : undefined,
|
|
343
|
+
sortBy
|
|
344
|
+
}
|
|
345
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useCallback, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
export function useDebounceCallback<T extends (...args: any[]) => any>(
|
|
4
|
+
callback?: T,
|
|
5
|
+
delay?: number
|
|
6
|
+
): T {
|
|
7
|
+
const timeoutRef = useRef<number | null>(null);
|
|
8
|
+
|
|
9
|
+
const debouncedCallback = useCallback((...args: Parameters<T>) => {
|
|
10
|
+
if (timeoutRef.current !== null) {
|
|
11
|
+
clearTimeout(timeoutRef.current);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
timeoutRef.current = window.setTimeout(() => {
|
|
15
|
+
callback?.(...args);
|
|
16
|
+
}, delay ?? 200);
|
|
17
|
+
}, [callback, delay]);
|
|
18
|
+
|
|
19
|
+
return debouncedCallback as T;
|
|
20
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Entity, FilterValues } from "../../types";
|
|
2
|
+
|
|
3
|
+
const collectionScrollCache = new Map<string, { scrollOffset: number, data: Entity<any>[] }>();
|
|
4
|
+
|
|
5
|
+
export type ScrollRestorationController = {
|
|
6
|
+
|
|
7
|
+
getCollectionScroll: (fullPath: string,
|
|
8
|
+
filters?: FilterValues<any>) => {
|
|
9
|
+
scrollOffset: number,
|
|
10
|
+
data: Entity<any>[]
|
|
11
|
+
} | undefined;
|
|
12
|
+
|
|
13
|
+
updateCollectionScroll: (props: {
|
|
14
|
+
fullPath: string,
|
|
15
|
+
scrollOffset: number,
|
|
16
|
+
filters?: FilterValues<any>;
|
|
17
|
+
data: Entity<any>[]
|
|
18
|
+
}) => void;
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useScrollRestoration(): ScrollRestorationController {
|
|
23
|
+
|
|
24
|
+
const updateCollectionScroll = ({
|
|
25
|
+
fullPath,
|
|
26
|
+
filters,
|
|
27
|
+
scrollOffset,
|
|
28
|
+
data
|
|
29
|
+
}: {
|
|
30
|
+
fullPath: string;
|
|
31
|
+
filters?: FilterValues<any>;
|
|
32
|
+
sort?: [string, "asc" | "desc"];
|
|
33
|
+
scrollOffset: number;
|
|
34
|
+
data: Entity<any>[]
|
|
35
|
+
}) => {
|
|
36
|
+
collectionScrollCache.set(
|
|
37
|
+
createCacheKey(fullPath, filters),
|
|
38
|
+
{
|
|
39
|
+
scrollOffset,
|
|
40
|
+
data
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const getCollectionScroll = (fullPath: string,
|
|
45
|
+
filters?: FilterValues<any>) => {
|
|
46
|
+
return collectionScrollCache.get(createCacheKey(fullPath, filters));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
getCollectionScroll,
|
|
51
|
+
updateCollectionScroll
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function createCacheKey(fullPath: string, filters?: FilterValues<any>) {
|
|
56
|
+
|
|
57
|
+
if (!filters) {
|
|
58
|
+
return fullPath;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// codify the filters into a url friendly string
|
|
62
|
+
const filtersString = filters ? Object.keys(filters).map(key => {
|
|
63
|
+
const value = JSON.stringify(filters[key]);
|
|
64
|
+
return `${key}=${value}`;
|
|
65
|
+
}).join("&") : "";
|
|
66
|
+
|
|
67
|
+
return `${fullPath}?${filtersString}`;
|
|
68
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { BreadcrumbEntry, BreadcrumbsController } from "../hooks/useBreadcrumbsController";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_BREADCRUMBS_CONTROLLER = {
|
|
5
|
+
breadcrumbs: [],
|
|
6
|
+
set: (props: {
|
|
7
|
+
breadcrumbs: BreadcrumbEntry[];
|
|
8
|
+
}) => {
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const BreadcrumbContext = React.createContext<BreadcrumbsController>(DEFAULT_BREADCRUMBS_CONTROLLER);
|
|
13
|
+
|
|
14
|
+
interface BreadcrumbsProviderProps {
|
|
15
|
+
children: React.ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const BreadcrumbsProvider: React.FC<BreadcrumbsProviderProps> = ({ children }) => {
|
|
19
|
+
|
|
20
|
+
const [breadcrumbs, setBreadcrumbs] = useState<BreadcrumbEntry[]>([]);
|
|
21
|
+
|
|
22
|
+
const set = (props: {
|
|
23
|
+
breadcrumbs: BreadcrumbEntry[];
|
|
24
|
+
}) => {
|
|
25
|
+
setBreadcrumbs(props.breadcrumbs);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<BreadcrumbContext.Provider
|
|
30
|
+
value={{
|
|
31
|
+
breadcrumbs,
|
|
32
|
+
set
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</BreadcrumbContext.Provider>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
@@ -8,7 +8,7 @@ export const DialogsProvider: React.FC<PropsWithChildren<{}>> = ({ children }) =
|
|
|
8
8
|
const [dialogEntries, setDialogEntries] = useState<DialogControllerEntryProps[]>([]);
|
|
9
9
|
const dialogEntriesRef = useRef<DialogControllerEntryProps[]>(dialogEntries);
|
|
10
10
|
|
|
11
|
-
const updateDialogEntries = (newPanels: DialogControllerEntryProps
|
|
11
|
+
const updateDialogEntries = (newPanels: DialogControllerEntryProps[]) => {
|
|
12
12
|
dialogEntriesRef.current = newPanels;
|
|
13
13
|
setDialogEntries(newPanels);
|
|
14
14
|
};
|
|
@@ -23,7 +23,7 @@ export const DialogsProvider: React.FC<PropsWithChildren<{}>> = ({ children }) =
|
|
|
23
23
|
|
|
24
24
|
}, [dialogEntries]);
|
|
25
25
|
|
|
26
|
-
const open = useCallback(
|
|
26
|
+
const open = useCallback((dialogEntry: DialogControllerEntryProps) => {
|
|
27
27
|
|
|
28
28
|
const updatedPanels = [...dialogEntriesRef.current, dialogEntry];
|
|
29
29
|
updateDialogEntries(updatedPanels);
|
|
@@ -46,7 +46,6 @@ export const DialogsProvider: React.FC<PropsWithChildren<{}>> = ({ children }) =
|
|
|
46
46
|
key={`dialog_${i}`}
|
|
47
47
|
open={true}
|
|
48
48
|
closeDialog={close}
|
|
49
|
-
{...entry.props}
|
|
50
49
|
/>)}
|
|
51
50
|
</DialogsControllerContext.Provider>
|
|
52
51
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import { Link
|
|
3
|
+
import { Link, useNavigate } from "react-router-dom";
|
|
4
4
|
import { ErrorBoundary, FireCMSLogo } from "../components";
|
|
5
5
|
import {
|
|
6
6
|
Avatar,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { useAuthController, useLargeLayout, useModeController, useNavigationController } from "../hooks";
|
|
18
18
|
import { User } from "../types";
|
|
19
19
|
import { useApp } from "../app/useApp";
|
|
20
|
+
import { useBreadcrumbsController } from "../hooks/useBreadcrumbsController";
|
|
20
21
|
|
|
21
22
|
export type DefaultAppBarProps<ADDITIONAL_PROPS = object> = {
|
|
22
23
|
|
|
@@ -65,6 +66,8 @@ export const DefaultAppBar = function DefaultAppBar({
|
|
|
65
66
|
} = useApp();
|
|
66
67
|
const navigation = useNavigationController();
|
|
67
68
|
|
|
69
|
+
const breadcrumbs = useBreadcrumbsController();
|
|
70
|
+
|
|
68
71
|
const authController = useAuthController();
|
|
69
72
|
const {
|
|
70
73
|
mode,
|
|
@@ -108,8 +111,8 @@ export const DefaultAppBar = function DefaultAppBar({
|
|
|
108
111
|
className)}>
|
|
109
112
|
|
|
110
113
|
|
|
111
|
-
{navigation && <div className="mr-
|
|
112
|
-
<
|
|
114
|
+
{navigation && <div className="mr-2 hidden lg:block">
|
|
115
|
+
<Link
|
|
113
116
|
className="visited:text-inherit visited:dark:text-inherit block"
|
|
114
117
|
to={navigation?.basePath ?? "/"}
|
|
115
118
|
>
|
|
@@ -127,7 +130,28 @@ export const DefaultAppBar = function DefaultAppBar({
|
|
|
127
130
|
</Typography>
|
|
128
131
|
: title}
|
|
129
132
|
</div>
|
|
130
|
-
</
|
|
133
|
+
</Link>
|
|
134
|
+
</div>}
|
|
135
|
+
|
|
136
|
+
{breadcrumbs.breadcrumbs && <div className="mr-8 hidden lg:block">
|
|
137
|
+
<div className={"flex flex-row gap-2"}>
|
|
138
|
+
{breadcrumbs.breadcrumbs.map((breadcrumb, index) => {
|
|
139
|
+
return <React.Fragment key={breadcrumb.url + "_" + index}>
|
|
140
|
+
<Typography variant={"caption"} color={"secondary"}>
|
|
141
|
+
/
|
|
142
|
+
</Typography>
|
|
143
|
+
<Link
|
|
144
|
+
key={index}
|
|
145
|
+
className="visited:text-inherit visited:dark:text-inherit block"
|
|
146
|
+
to={breadcrumb.url}
|
|
147
|
+
>
|
|
148
|
+
<Typography variant={"caption"} color={"secondary"}>
|
|
149
|
+
{breadcrumb.title}
|
|
150
|
+
</Typography>
|
|
151
|
+
</Link>
|
|
152
|
+
</React.Fragment>;
|
|
153
|
+
})}
|
|
154
|
+
</div>
|
|
131
155
|
</div>}
|
|
132
156
|
|
|
133
157
|
{startAdornment}
|