@firecms/core 3.0.0-beta.11 → 3.0.0-beta.12
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/ArrayContainer.d.ts +7 -3
- 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/PropertyIdCopyTooltip.d.ts +1 -1
- package/dist/components/SelectableTable/SelectableTable.d.ts +12 -2
- package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +1 -1
- package/dist/components/UnsavedChangesDialog.d.ts +8 -0
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +13 -0
- package/dist/components/common/default_entity_actions.d.ts +0 -2
- package/dist/components/common/index.d.ts +1 -1
- package/dist/components/common/useColumnsIds.d.ts +1 -0
- 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 +29 -21
- package/dist/core/EntityEditViewFormActions.d.ts +2 -0
- package/dist/core/FireCMSRouter.d.ts +4 -0
- package/dist/core/NavigationRoutes.d.ts +0 -1
- package/dist/core/SideDialogs.d.ts +4 -2
- package/dist/core/index.d.ts +2 -1
- package/dist/form/EntityForm.d.ts +45 -0
- package/dist/form/EntityFormActions.d.ts +17 -0
- package/dist/form/PropertyFieldBinding.d.ts +1 -1
- package/dist/form/components/FormEntry.d.ts +6 -0
- package/dist/form/components/FormLayout.d.ts +5 -0
- package/dist/form/components/index.d.ts +2 -0
- package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/BlockFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/RepeatFieldBinding.d.ts +1 -1
- package/dist/form/index.d.ts +16 -16
- package/dist/hooks/data/useEntityFetch.d.ts +2 -1
- package/dist/hooks/useBreadcrumbsController.d.ts +26 -0
- package/dist/hooks/useBuildNavigationController.d.ts +4 -1
- package/dist/hooks/useModeController.d.ts +1 -2
- package/dist/index.es.js +18213 -16819
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +18164 -16771
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useBuildSideEntityController.d.ts +2 -2
- 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 +30 -10
- package/dist/types/datasource.d.ts +2 -2
- package/dist/types/dialogs_controller.d.ts +7 -3
- package/dist/types/entities.d.ts +1 -1
- package/dist/types/entity_actions.d.ts +10 -0
- package/dist/types/fields.d.ts +9 -2
- package/dist/types/navigation.d.ts +29 -12
- package/dist/types/plugins.d.ts +1 -0
- package/dist/types/properties.d.ts +18 -3
- package/dist/types/side_entity_controller.d.ts +6 -1
- package/dist/util/builders.d.ts +1 -1
- package/dist/util/entity_actions.d.ts +2 -0
- package/dist/util/entity_cache.d.ts +23 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/navigation_from_path.d.ts +6 -1
- package/dist/util/navigation_utils.d.ts +12 -1
- package/dist/util/resolutions.d.ts +6 -1
- package/package.json +19 -21
- package/src/components/ArrayContainer.tsx +48 -29
- 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/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -11
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +83 -41
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +4 -2
- package/src/components/NotFoundPage.tsx +2 -2
- package/src/components/PropertyIdCopyTooltip.tsx +2 -3
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +11 -6
- package/src/components/SelectableTable/SelectableTable.tsx +18 -3
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +7 -7
- 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 +26 -37
- package/src/components/common/index.ts +1 -1
- package/src/components/common/useColumnsIds.tsx +1 -1
- package/src/components/common/useDataSourceTableController.tsx +420 -0
- 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 +3 -2
- package/src/contexts/ModeController.tsx +1 -3
- package/src/core/DefaultAppBar.tsx +44 -14
- package/src/core/DefaultDrawer.tsx +1 -1
- package/src/core/EntityEditView.tsx +278 -1000
- package/src/core/EntityEditViewFormActions.tsx +199 -0
- package/src/core/EntitySidePanel.tsx +85 -15
- package/src/core/FireCMS.tsx +7 -4
- package/src/core/FireCMSRouter.tsx +17 -0
- package/src/core/NavigationRoutes.tsx +23 -32
- package/src/core/SideDialogs.tsx +20 -11
- package/src/core/index.tsx +4 -2
- package/src/form/EntityForm.tsx +774 -0
- package/src/form/EntityFormActions.tsx +169 -0
- package/src/form/PropertyFieldBinding.tsx +13 -11
- package/src/form/components/FormEntry.tsx +22 -0
- package/src/form/components/FormLayout.tsx +16 -0
- package/src/form/components/LabelWithIcon.tsx +6 -3
- package/src/form/components/StorageUploadProgress.tsx +1 -1
- package/src/form/components/index.tsx +2 -0
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +4 -4
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -2
- package/src/form/field_bindings/BlockFieldBinding.tsx +4 -2
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -0
- package/src/form/field_bindings/MapFieldBinding.tsx +14 -16
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +9 -6
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +1 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
- package/src/form/field_bindings/RepeatFieldBinding.tsx +5 -2
- package/src/form/field_bindings/SelectFieldBinding.tsx +1 -1
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +6 -4
- package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -0
- package/src/form/field_bindings/TextFieldBinding.tsx +1 -0
- package/src/form/index.tsx +16 -32
- package/src/form/validation.ts +12 -6
- 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/useBuildModeController.tsx +15 -28
- package/src/hooks/useBuildNavigationController.tsx +70 -24
- package/src/hooks/useLargeLayout.tsx +0 -35
- package/src/hooks/useModeController.tsx +1 -2
- package/src/hooks/useResolvedNavigationFrom.tsx +4 -6
- package/src/internal/useBuildSideDialogsController.tsx +3 -2
- package/src/internal/useBuildSideEntityController.tsx +124 -83
- package/src/internal/useUnsavedChangesDialog.tsx +126 -92
- package/src/preview/PropertyPreview.tsx +8 -8
- package/src/preview/components/UrlComponentPreview.tsx +17 -18
- package/src/preview/property_previews/MapPropertyPreview.tsx +3 -2
- package/src/routes/CustomCMSRoute.tsx +21 -0
- package/src/routes/FireCMSRoute.tsx +235 -0
- package/src/routes/HomePageRoute.tsx +17 -0
- package/src/types/collections.ts +38 -13
- package/src/types/datasource.ts +2 -1
- package/src/types/dialogs_controller.tsx +7 -3
- package/src/types/entities.ts +1 -1
- package/src/types/entity_actions.tsx +12 -0
- package/src/types/fields.tsx +13 -2
- package/src/types/navigation.ts +36 -16
- package/src/types/plugins.tsx +1 -0
- package/src/types/properties.ts +21 -3
- package/src/types/side_dialogs_controller.tsx +2 -0
- package/src/types/side_entity_controller.tsx +6 -1
- package/src/util/builders.ts +5 -3
- package/src/util/entity_actions.ts +28 -0
- package/src/util/entity_cache.ts +204 -0
- package/src/util/index.ts +1 -0
- package/src/util/join_collections.ts +3 -0
- package/src/util/navigation_from_path.ts +13 -6
- package/src/util/navigation_utils.ts +53 -1
- package/src/util/objects.ts +4 -1
- package/src/util/parent_references_from_path.ts +3 -3
- package/src/util/resolutions.ts +26 -5
- package/src/util/useStorageUploadController.tsx +39 -21
- package/src/components/common/useDataSourceEntityCollectionTableController.tsx +0 -236
- /package/src/util/{common.tsx → common.ts} +0 -0
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import { useClearRestoreValue } from "../useClearRestoreValue";
|
|
29
29
|
|
|
30
30
|
const dropZoneClasses = "box-border relative pt-[2px] items-center border border-transparent min-h-[254px] outline-none rounded-md duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] focus:border-primary-solid";
|
|
31
|
-
const disabledClasses =
|
|
31
|
+
const disabledClasses = fieldBackgroundDisabledMixin;
|
|
32
32
|
const nonActiveDropClasses = fieldBackgroundHoverMixin
|
|
33
33
|
const activeDropClasses = "pt-0 border-2 border-solid"
|
|
34
34
|
const acceptDropClasses = "transition-colors duration-200 ease-[cubic-bezier(0,0,0.2,1)] border-2 border-solid border-green-500"
|
|
@@ -103,7 +103,7 @@ export function StorageUploadFieldBinding({
|
|
|
103
103
|
icon={getIconForProperty(property, "small")}
|
|
104
104
|
required={property.validation?.required}
|
|
105
105
|
title={property.name}
|
|
106
|
-
className={"
|
|
106
|
+
className={"h-8text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
|
|
107
107
|
|
|
108
108
|
<StorageUpload
|
|
109
109
|
value={internalValue}
|
|
@@ -206,6 +206,7 @@ function FileDropComponent({
|
|
|
206
206
|
className={cls(
|
|
207
207
|
fieldBackgroundMixin,
|
|
208
208
|
disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
|
|
209
|
+
disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : "",
|
|
209
210
|
dropZoneClasses,
|
|
210
211
|
multipleFilesSupported && internalValue.length ? "" : "flex",
|
|
211
212
|
{
|
|
@@ -248,7 +249,7 @@ function FileDropComponent({
|
|
|
248
249
|
metadata={metadata}
|
|
249
250
|
storagePath={storagePathBuilder(entry.file)}
|
|
250
251
|
onFileUploadComplete={onFileUploadComplete}
|
|
251
|
-
imageSize={size === "
|
|
252
|
+
imageSize={size === "large" ? 220 : 118}
|
|
252
253
|
simple={false}
|
|
253
254
|
/>
|
|
254
255
|
);
|
|
@@ -285,7 +286,8 @@ function FileDropComponent({
|
|
|
285
286
|
<div
|
|
286
287
|
className="flex-grow min-h-[38px] box-border m-2 text-center">
|
|
287
288
|
<Typography align={"center"}
|
|
288
|
-
variant={"label"}
|
|
289
|
+
variant={"label"}
|
|
290
|
+
className={disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : ""}>
|
|
289
291
|
{helpText}
|
|
290
292
|
</Typography>
|
|
291
293
|
</div>
|
|
@@ -43,6 +43,7 @@ export const SwitchFieldBinding = function SwitchFieldBinding({
|
|
|
43
43
|
value={value}
|
|
44
44
|
onValueChange={(v) => setValue(v)}
|
|
45
45
|
error={showError}
|
|
46
|
+
className={property.widthPercentage !== undefined ? "mt-8" : undefined}
|
|
46
47
|
label={<LabelWithIcon
|
|
47
48
|
icon={getIconForProperty(property, "small")}
|
|
48
49
|
required={property.validation?.required}
|
|
@@ -75,6 +75,7 @@ export function TextFieldBinding<T extends string | number>({
|
|
|
75
75
|
value={value}
|
|
76
76
|
onChange={onChange}
|
|
77
77
|
autoFocus={autoFocus}
|
|
78
|
+
className={property.widthPercentage !== undefined ? "mt-8" : undefined}
|
|
78
79
|
label={<LabelWithIcon
|
|
79
80
|
icon={getIconForProperty(property, "small")}
|
|
80
81
|
required={property.validation?.required}
|
package/src/form/index.tsx
CHANGED
|
@@ -1,36 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
import { MultiSelectFieldBinding } from "./field_bindings/MultiSelectFieldBinding";
|
|
3
|
-
import { ArrayOfReferencesFieldBinding } from "./field_bindings/ArrayOfReferencesFieldBinding";
|
|
4
|
-
import { StorageUploadFieldBinding } from "./field_bindings/StorageUploadFieldBinding";
|
|
5
|
-
import { TextFieldBinding } from "./field_bindings/TextFieldBinding";
|
|
6
|
-
import { SwitchFieldBinding } from "./field_bindings/SwitchFieldBinding";
|
|
7
|
-
import { DateTimeFieldBinding } from "./field_bindings/DateTimeFieldBinding";
|
|
8
|
-
import { ReferenceFieldBinding } from "./field_bindings/ReferenceFieldBinding";
|
|
9
|
-
import { MapFieldBinding } from "./field_bindings/MapFieldBinding";
|
|
10
|
-
import { KeyValueFieldBinding } from "./field_bindings/KeyValueFieldBinding";
|
|
11
|
-
import { RepeatFieldBinding } from "./field_bindings/RepeatFieldBinding";
|
|
12
|
-
import { BlockFieldBinding } from "./field_bindings/BlockFieldBinding";
|
|
13
|
-
import { ReadOnlyFieldBinding } from "./field_bindings/ReadOnlyFieldBinding";
|
|
14
|
-
import { MarkdownEditorFieldBinding } from "./field_bindings/MarkdownEditorFieldBinding";
|
|
15
|
-
import { ArrayCustomShapedFieldBinding } from "./field_bindings/ArrayCustomShapedFieldBinding";
|
|
1
|
+
export * from "./EntityForm";
|
|
16
2
|
|
|
17
|
-
export {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
TextFieldBinding
|
|
33
|
-
};
|
|
3
|
+
export { SelectFieldBinding } from "./field_bindings/SelectFieldBinding";
|
|
4
|
+
export { MultiSelectFieldBinding } from "./field_bindings/MultiSelectFieldBinding";
|
|
5
|
+
export { ArrayOfReferencesFieldBinding } from "./field_bindings/ArrayOfReferencesFieldBinding";
|
|
6
|
+
export { StorageUploadFieldBinding } from "./field_bindings/StorageUploadFieldBinding";
|
|
7
|
+
export { TextFieldBinding } from "./field_bindings/TextFieldBinding";
|
|
8
|
+
export { SwitchFieldBinding } from "./field_bindings/SwitchFieldBinding";
|
|
9
|
+
export { DateTimeFieldBinding } from "./field_bindings/DateTimeFieldBinding";
|
|
10
|
+
export { ReferenceFieldBinding } from "./field_bindings/ReferenceFieldBinding";
|
|
11
|
+
export { MapFieldBinding } from "./field_bindings/MapFieldBinding";
|
|
12
|
+
export { KeyValueFieldBinding } from "./field_bindings/KeyValueFieldBinding";
|
|
13
|
+
export { RepeatFieldBinding } from "./field_bindings/RepeatFieldBinding";
|
|
14
|
+
export { BlockFieldBinding } from "./field_bindings/BlockFieldBinding";
|
|
15
|
+
export { ReadOnlyFieldBinding } from "./field_bindings/ReadOnlyFieldBinding";
|
|
16
|
+
export { MarkdownEditorFieldBinding } from "./field_bindings/MarkdownEditorFieldBinding";
|
|
17
|
+
export { ArrayCustomShapedFieldBinding } from "./field_bindings/ArrayCustomShapedFieldBinding";
|
|
34
18
|
|
|
35
19
|
export * from "./components";
|
|
36
20
|
|
package/src/form/validation.ts
CHANGED
|
@@ -90,11 +90,11 @@ export function mapPropertyToYup<T extends CMSType>(propertyContext: PropertyCon
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
export function getYupMapObjectSchema({
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
property,
|
|
94
|
+
entityId,
|
|
95
|
+
customFieldValidator,
|
|
96
|
+
name
|
|
97
|
+
}: PropertyContext<Record<string, any>>): ObjectSchema<any> {
|
|
98
98
|
const objectSchema: any = {};
|
|
99
99
|
const validation = property.validation;
|
|
100
100
|
if (property.properties)
|
|
@@ -159,7 +159,13 @@ function getYupStringSchema({
|
|
|
159
159
|
if (validation.lowercase) collection = collection.lowercase();
|
|
160
160
|
if (validation.uppercase) collection = collection.uppercase();
|
|
161
161
|
if (property.email) collection = collection.email(`${property.name} must be an email`);
|
|
162
|
-
if (property.url)
|
|
162
|
+
if (property.url) {
|
|
163
|
+
if (!property.storage || property.storage?.storeUrl) {
|
|
164
|
+
collection = collection.url(`${property.name} must be a url`);
|
|
165
|
+
} else {
|
|
166
|
+
console.warn(`Property ${property.name} has a url validation but its storage configuration is not set to store urls`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
163
169
|
} else {
|
|
164
170
|
collection = collection.notRequired().nullable(true);
|
|
165
171
|
}
|
package/src/hooks/data/save.ts
CHANGED
|
@@ -75,7 +75,7 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, USE
|
|
|
75
75
|
|
|
76
76
|
const customizationController = context.customizationController;
|
|
77
77
|
|
|
78
|
-
const resolvedPath = context.navigation.
|
|
78
|
+
const resolvedPath = context.navigation.resolveIdsFrom(path);
|
|
79
79
|
|
|
80
80
|
const callbacks = collection.callbacks;
|
|
81
81
|
if (callbacks?.onPreSave) {
|
|
@@ -73,7 +73,7 @@ export function useCollectionFetch<M extends Record<string, any>, USER extends U
|
|
|
73
73
|
const dataSource = useDataSource(collection);
|
|
74
74
|
const navigationController = useNavigationController();
|
|
75
75
|
|
|
76
|
-
const path = navigationController.
|
|
76
|
+
const path = navigationController.resolveIdsFrom(inputPath);
|
|
77
77
|
|
|
78
78
|
const sortByProperty = sortBy ? sortBy[0] : undefined;
|
|
79
79
|
const currentSort = sortBy ? sortBy[1] : undefined;
|
|
@@ -10,6 +10,7 @@ import { useFireCMSContext } from "../useFireCMSContext";
|
|
|
10
10
|
export interface EntityFetchProps<M extends Record<string, any>, USER extends User = User> {
|
|
11
11
|
path: string;
|
|
12
12
|
entityId?: string;
|
|
13
|
+
databaseId?: string;
|
|
13
14
|
collection: EntityCollection<M, USER>;
|
|
14
15
|
useCache?: boolean;
|
|
15
16
|
}
|
|
@@ -40,13 +41,14 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
|
|
|
40
41
|
path: inputPath,
|
|
41
42
|
entityId,
|
|
42
43
|
collection,
|
|
44
|
+
databaseId,
|
|
43
45
|
useCache = false
|
|
44
46
|
}: EntityFetchProps<M, USER>): EntityFetchResult<M> {
|
|
45
47
|
|
|
46
48
|
const dataSource = useDataSource(collection);
|
|
47
49
|
const navigationController = useNavigationController();
|
|
48
50
|
|
|
49
|
-
const path = navigationController.
|
|
51
|
+
const path = navigationController.resolveIdsFrom(inputPath);
|
|
50
52
|
|
|
51
53
|
const context: FireCMSContext<USER> = useFireCMSContext();
|
|
52
54
|
|
|
@@ -96,6 +98,7 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
|
|
|
96
98
|
return dataSource.listenEntity<M>({
|
|
97
99
|
path,
|
|
98
100
|
entityId,
|
|
101
|
+
databaseId,
|
|
99
102
|
collection,
|
|
100
103
|
onUpdate: onEntityUpdate,
|
|
101
104
|
onError
|
|
@@ -104,6 +107,7 @@ export function useEntityFetch<M extends Record<string, any>, USER extends User>
|
|
|
104
107
|
dataSource.fetchEntity<M>({
|
|
105
108
|
path,
|
|
106
109
|
entityId,
|
|
110
|
+
databaseId,
|
|
107
111
|
collection
|
|
108
112
|
})
|
|
109
113
|
.then(onEntityUpdate)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { BreadcrumbContext } from "../contexts/BreacrumbsContext";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @group Hooks and utilities
|
|
6
|
+
*/
|
|
7
|
+
export interface BreadcrumbsController {
|
|
8
|
+
breadcrumbs: BreadcrumbEntry[];
|
|
9
|
+
set: (props: {
|
|
10
|
+
breadcrumbs: BreadcrumbEntry[];
|
|
11
|
+
}) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @group Hooks and utilities
|
|
16
|
+
*/
|
|
17
|
+
export interface BreadcrumbEntry {
|
|
18
|
+
title: string;
|
|
19
|
+
url: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Hook to retrieve the BreadcrumbsController.
|
|
24
|
+
*
|
|
25
|
+
* Consider that in order to use this hook you need to have a parent
|
|
26
|
+
* `FireCMS`
|
|
27
|
+
*
|
|
28
|
+
* @see BreadcrumbsController
|
|
29
|
+
* @group Hooks and utilities
|
|
30
|
+
*/
|
|
31
|
+
export const useBreadcrumbsController = (): BreadcrumbsController => useContext(BreadcrumbContext);
|
|
@@ -16,7 +16,7 @@ export function useBuildModeController(): ModeController {
|
|
|
16
16
|
}, []);
|
|
17
17
|
|
|
18
18
|
const prefersDarkModeStorage: boolean | null = localStorage.getItem("prefers-dark-mode") != null ? localStorage.getItem("prefers-dark-mode") === "true" : null;
|
|
19
|
-
const prefersDarkMode = prefersDarkModeStorage ?? prefersDarkModeQuery;
|
|
19
|
+
const prefersDarkMode = prefersDarkModeStorage ?? prefersDarkModeQuery();
|
|
20
20
|
const [mode, setMode] = useState<"light" | "dark">(prefersDarkMode ? "dark" : "light");
|
|
21
21
|
|
|
22
22
|
useEffect(() => {
|
|
@@ -24,43 +24,30 @@ export function useBuildModeController(): ModeController {
|
|
|
24
24
|
setDocumentMode(prefersDarkMode ? "dark" : "light");
|
|
25
25
|
}, [prefersDarkMode]);
|
|
26
26
|
|
|
27
|
-
// color-scheme: dark;
|
|
28
|
-
const setDarkMode = useCallback(() => {
|
|
29
|
-
setMode("dark");
|
|
30
|
-
setDocumentMode("dark");
|
|
31
|
-
}, []);
|
|
32
|
-
|
|
33
|
-
const setLightMode = useCallback(() => {
|
|
34
|
-
setMode("light");
|
|
35
|
-
setDocumentMode("light");
|
|
36
|
-
}, []);
|
|
37
|
-
|
|
38
27
|
const setDocumentMode = (mode: "light" | "dark") => {
|
|
39
28
|
document.body.style.setProperty("color-scheme", mode);
|
|
40
29
|
document.documentElement.dataset.theme = mode;
|
|
41
30
|
};
|
|
42
31
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
const prefersDarkModeQueryResult = prefersDarkModeQuery();
|
|
32
|
+
const setModeInternal = useCallback((mode: "light" | "dark" | "system") => {
|
|
46
33
|
if (mode === "light") {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
setDocumentMode("light");
|
|
35
|
+
localStorage.setItem("prefers-dark-mode", "false");
|
|
36
|
+
setMode("light");
|
|
37
|
+
} else if (mode === "dark") {
|
|
38
|
+
setDocumentMode("dark");
|
|
39
|
+
localStorage.setItem("prefers-dark-mode", "true");
|
|
40
|
+
setMode("dark");
|
|
52
41
|
} else {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
setLightMode();
|
|
42
|
+
const preferredMode = prefersDarkModeQuery() ? "dark" : "light";
|
|
43
|
+
setDocumentMode(preferredMode);
|
|
44
|
+
localStorage.removeItem("prefers-dark-mode");
|
|
45
|
+
setMode(preferredMode);
|
|
58
46
|
}
|
|
59
|
-
}, [
|
|
47
|
+
}, [prefersDarkModeQuery]);
|
|
60
48
|
|
|
61
49
|
return {
|
|
62
50
|
mode,
|
|
63
|
-
setMode
|
|
64
|
-
toggleMode
|
|
51
|
+
setMode: setModeInternal
|
|
65
52
|
};
|
|
66
53
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
EntityCollection,
|
|
10
10
|
EntityCollectionsBuilder,
|
|
11
11
|
EntityReference,
|
|
12
|
+
NavigationBlocker,
|
|
12
13
|
NavigationController,
|
|
13
14
|
PermissionsBuilder,
|
|
14
15
|
TopNavigationEntry,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
resolvePermissions
|
|
27
28
|
} from "../util";
|
|
28
29
|
import { getParentReferencesFromPath } from "../util/parent_references_from_path";
|
|
30
|
+
import { useBlocker, useNavigate } from "react-router-dom";
|
|
29
31
|
|
|
30
32
|
const DEFAULT_BASE_PATH = "/";
|
|
31
33
|
const DEFAULT_COLLECTION_PATH = "/c";
|
|
@@ -49,6 +51,11 @@ export type BuildNavigationContextProps<EC extends EntityCollection, USER extend
|
|
|
49
51
|
* @param collections
|
|
50
52
|
*/
|
|
51
53
|
injectCollections?: (collections: EntityCollection[]) => EntityCollection[];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* If true, the navigation logic will not be updated until this flag is false
|
|
57
|
+
*/
|
|
58
|
+
disabled?: boolean;
|
|
52
59
|
};
|
|
53
60
|
|
|
54
61
|
export function useBuildNavigationController<EC extends EntityCollection, USER extends User>(props: BuildNavigationContextProps<EC, USER>): NavigationController {
|
|
@@ -63,9 +70,12 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
63
70
|
viewsOrder,
|
|
64
71
|
userConfigPersistence,
|
|
65
72
|
dataSourceDelegate,
|
|
66
|
-
injectCollections
|
|
73
|
+
injectCollections,
|
|
74
|
+
disabled
|
|
67
75
|
} = props;
|
|
68
76
|
|
|
77
|
+
const navigate = useNavigate();
|
|
78
|
+
|
|
69
79
|
const collectionsRef = useRef<EntityCollection[] | undefined>();
|
|
70
80
|
const viewsRef = useRef<CMSView[] | undefined>();
|
|
71
81
|
const adminViewsRef = useRef<CMSView[] | undefined>();
|
|
@@ -185,7 +195,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
185
195
|
|
|
186
196
|
const refreshNavigation = useCallback(async () => {
|
|
187
197
|
|
|
188
|
-
if (authController.initialLoading)
|
|
198
|
+
if (disabled || authController.initialLoading)
|
|
189
199
|
return;
|
|
190
200
|
|
|
191
201
|
console.debug("Refreshing navigation");
|
|
@@ -202,7 +212,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
202
212
|
let shouldUpdateTopLevelNav = false;
|
|
203
213
|
if (!areCollectionListsEqual(collectionsRef.current ?? [], resolvedCollections)) {
|
|
204
214
|
collectionsRef.current = resolvedCollections;
|
|
205
|
-
console.
|
|
215
|
+
console.debug("Collections have changed", resolvedCollections);
|
|
206
216
|
shouldUpdateTopLevelNav = true;
|
|
207
217
|
}
|
|
208
218
|
if (collectionsRef.current === undefined) {
|
|
@@ -237,6 +247,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
237
247
|
collectionPermissions,
|
|
238
248
|
authController.user,
|
|
239
249
|
authController.initialLoading,
|
|
250
|
+
disabled,
|
|
240
251
|
viewsProp,
|
|
241
252
|
adminViewsProp,
|
|
242
253
|
computeTopNavigation,
|
|
@@ -332,16 +343,7 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
332
343
|
throw Error("Expected path starting with " + fullCollectionPath);
|
|
333
344
|
}, [fullCollectionPath]);
|
|
334
345
|
|
|
335
|
-
const
|
|
336
|
-
path
|
|
337
|
-
}: {
|
|
338
|
-
path: string
|
|
339
|
-
}): string => {
|
|
340
|
-
return `s/edit/${encodePath(path)}`;
|
|
341
|
-
},
|
|
342
|
-
[]);
|
|
343
|
-
|
|
344
|
-
const resolveAliasesFrom = useCallback((path: string): string => {
|
|
346
|
+
const resolveIdsFrom = useCallback((path: string): string => {
|
|
345
347
|
const collections = collectionsRef.current ?? [];
|
|
346
348
|
return resolveCollectionPathIds(path, collections);
|
|
347
349
|
}, []);
|
|
@@ -401,24 +403,16 @@ export function useBuildNavigationController<EC extends EntityCollection, USER e
|
|
|
401
403
|
isUrlCollectionPath,
|
|
402
404
|
urlPathToDataPath,
|
|
403
405
|
buildUrlCollectionPath,
|
|
404
|
-
|
|
405
|
-
buildCMSUrlPath,
|
|
406
|
-
resolveAliasesFrom,
|
|
406
|
+
resolveIdsFrom,
|
|
407
407
|
topLevelNavigation,
|
|
408
408
|
refreshNavigation,
|
|
409
409
|
getParentReferencesFromPath: getAllParentReferencesForPath,
|
|
410
410
|
getParentCollectionIds,
|
|
411
|
-
convertIdsToPaths
|
|
411
|
+
convertIdsToPaths,
|
|
412
|
+
navigate,
|
|
412
413
|
};
|
|
413
414
|
}
|
|
414
415
|
|
|
415
|
-
export function getSidePanelKey(path: string, entityId?: string) {
|
|
416
|
-
if (entityId)
|
|
417
|
-
return `${removeInitialAndTrailingSlashes(path)}/${removeInitialAndTrailingSlashes(entityId)}`;
|
|
418
|
-
else
|
|
419
|
-
return removeInitialAndTrailingSlashes(path);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
416
|
function encodePath(input: string) {
|
|
423
417
|
return encodeURIComponent(removeInitialAndTrailingSlashes(input))
|
|
424
418
|
.replaceAll("%2F", "/")
|
|
@@ -427,6 +421,7 @@ function encodePath(input: string) {
|
|
|
427
421
|
|
|
428
422
|
function filterOutNotAllowedCollections(resolvedCollections: EntityCollection[], authController: AuthController<User>): EntityCollection[] {
|
|
429
423
|
return resolvedCollections
|
|
424
|
+
.filter((c) => Boolean(c.path))
|
|
430
425
|
.filter((c) => {
|
|
431
426
|
if (!c.permissions) return true;
|
|
432
427
|
const resolvedPermissions = resolvePermissions(c, authController, c.path, null);
|
|
@@ -513,3 +508,54 @@ function areCollectionsEqual(a: EntityCollection, b: EntityCollection) {
|
|
|
513
508
|
}
|
|
514
509
|
return equal(removeFunctions(restA), removeFunctions(restB));
|
|
515
510
|
}
|
|
511
|
+
|
|
512
|
+
function useCustomBlocker(): NavigationBlocker {
|
|
513
|
+
const [blockListeners, setBlockListeners] = useState<Record<string, {
|
|
514
|
+
block: boolean,
|
|
515
|
+
basePath?: string
|
|
516
|
+
}>>({});
|
|
517
|
+
|
|
518
|
+
const shouldBlock = Object.values(blockListeners).some(b => b.block);
|
|
519
|
+
|
|
520
|
+
let blocker: any;
|
|
521
|
+
try {
|
|
522
|
+
blocker = useBlocker(({
|
|
523
|
+
nextLocation
|
|
524
|
+
}) => {
|
|
525
|
+
const allBasePaths = Object.values(blockListeners).map(b => b.basePath).filter(Boolean) as string[];
|
|
526
|
+
if (allBasePaths && allBasePaths.some(path => nextLocation.pathname.startsWith(path)))
|
|
527
|
+
return false;
|
|
528
|
+
return shouldBlock;
|
|
529
|
+
});
|
|
530
|
+
} catch (e) {
|
|
531
|
+
console.warn("Blocker not available, navigation will not be blocked");
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const updateBlockListener = (path: string, block: boolean, basePath?: string) => {
|
|
535
|
+
setBlockListeners(prev => ({
|
|
536
|
+
...prev,
|
|
537
|
+
[path]: {
|
|
538
|
+
block,
|
|
539
|
+
basePath
|
|
540
|
+
}
|
|
541
|
+
}));
|
|
542
|
+
return () => setBlockListeners(prev => {
|
|
543
|
+
const {
|
|
544
|
+
[path]: removed,
|
|
545
|
+
...rest
|
|
546
|
+
} = prev;
|
|
547
|
+
return rest;
|
|
548
|
+
})
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
const isBlocked = (path: string) => {
|
|
552
|
+
return (blockListeners[path]?.block ?? false) && blocker?.state === "blocked";
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return {
|
|
556
|
+
updateBlockListener,
|
|
557
|
+
isBlocked,
|
|
558
|
+
proceed: blocker?.proceed,
|
|
559
|
+
reset: blocker?.reset
|
|
560
|
+
}
|
|
561
|
+
}
|
|
@@ -63,38 +63,3 @@ function checkLargeLayout(breakpoint: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" |
|
|
|
63
63
|
return false;
|
|
64
64
|
return window.matchMedia(`(min-width: ${breakpoints[breakpoint] + 1}px)`).matches;
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
// import { useEffect, useState } from "react";
|
|
68
|
-
//
|
|
69
|
-
// const breakpoints = {
|
|
70
|
-
// xs: 0,
|
|
71
|
-
// sm: 640,
|
|
72
|
-
// md: 768,
|
|
73
|
-
// lg: 1024,
|
|
74
|
-
// xl: 1280,
|
|
75
|
-
// "2xl": 1536,
|
|
76
|
-
// "3xl": 1920
|
|
77
|
-
// }
|
|
78
|
-
// export const useLargeLayout = (breakpoint: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" = "lg"): boolean => {
|
|
79
|
-
// const [isLargeLayout, setIsLargeLayout] = useState<boolean>(false);
|
|
80
|
-
//
|
|
81
|
-
// useEffect(() => {
|
|
82
|
-
// const handleResize = () => {
|
|
83
|
-
// const matched = window.matchMedia(`(min-width: ${breakpoints[breakpoint] + 1}px)`).matches;
|
|
84
|
-
// setIsLargeLayout(matched);
|
|
85
|
-
// };
|
|
86
|
-
//
|
|
87
|
-
// // Set initial state
|
|
88
|
-
// handleResize();
|
|
89
|
-
//
|
|
90
|
-
// // Set up event listener for resize events
|
|
91
|
-
// window.addEventListener("resize", handleResize);
|
|
92
|
-
//
|
|
93
|
-
// // Clean up event listener when component unmounts
|
|
94
|
-
// return () => {
|
|
95
|
-
// window.removeEventListener("resize", handleResize);
|
|
96
|
-
// };
|
|
97
|
-
// }, []);
|
|
98
|
-
//
|
|
99
|
-
// return isLargeLayout;
|
|
100
|
-
// };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Entity, EntityCollection, EntityCustomView, FireCMSContext, User } from "../types";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { getNavigationEntriesFromPath } from "../util/navigation_from_path";
|
|
4
4
|
import { useFireCMSContext } from "./useFireCMSContext";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -67,10 +67,10 @@ export function resolveNavigationFrom<M extends Record<string, any>, USER extend
|
|
|
67
67
|
const navigation = context.navigation;
|
|
68
68
|
|
|
69
69
|
if (!navigation) {
|
|
70
|
-
throw Error("Calling
|
|
70
|
+
throw Error("Calling resolveNavigationFrom, but main navigation has not yet been initialised");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
const navigationEntries =
|
|
73
|
+
const navigationEntries = getNavigationEntriesFromPath({
|
|
74
74
|
path,
|
|
75
75
|
collections: navigation.collections ?? []
|
|
76
76
|
});
|
|
@@ -143,9 +143,7 @@ export function useResolvedNavigationFrom<M extends Record<string, any>, USER ex
|
|
|
143
143
|
setDataLoading(true);
|
|
144
144
|
setDataLoadingError(undefined);
|
|
145
145
|
resolveNavigationFrom<M, USER>({ path, context })
|
|
146
|
-
.then(
|
|
147
|
-
setData(res);
|
|
148
|
-
})
|
|
146
|
+
.then(setData)
|
|
149
147
|
.catch((e) => setDataLoadingError(e))
|
|
150
148
|
.finally(() => setDataLoading(false));
|
|
151
149
|
}
|
|
@@ -25,8 +25,9 @@ export function useBuildSideDialogsController(): SideDialogsController {
|
|
|
25
25
|
const newPanels = panelKeys
|
|
26
26
|
.map(key => routesStore.current[key])
|
|
27
27
|
.filter(p => Boolean(p)) as SideDialogPanelProps[];
|
|
28
|
-
if (!equal(sidePanelsRef.current.map(p => p.key), newPanels.map(p => p.key)))
|
|
28
|
+
if (!equal(sidePanelsRef.current.map(p => p.key), newPanels.map(p => p.key))) {
|
|
29
29
|
updateSidePanels(newPanels);
|
|
30
|
+
}
|
|
30
31
|
}, [location]);
|
|
31
32
|
|
|
32
33
|
const close = useCallback(() => {
|
|
@@ -39,7 +40,7 @@ export function useBuildSideDialogsController(): SideDialogsController {
|
|
|
39
40
|
updateSidePanels(updatedPanels);
|
|
40
41
|
|
|
41
42
|
if (routesCount.current > 0) {
|
|
42
|
-
if (lastSidePanel.urlPath)
|
|
43
|
+
if (lastSidePanel.urlPath) // if it has a url path, we need to navigate back, don't remove this code
|
|
43
44
|
navigate(-1);
|
|
44
45
|
routesCount.current--;
|
|
45
46
|
} else if (lastSidePanel.parentUrlPath) {
|