@rebasepro/admin 0.6.1 → 0.8.0
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/{CollectionEditorDialog-HNXGxXdO.js → CollectionEditorDialog-BO_aQ-Z4.js} +236 -155
- package/dist/CollectionEditorDialog-BO_aQ-Z4.js.map +1 -0
- package/dist/{PropertyEditView-C2wp8kQ9.js → PropertyEditView-DKzOpMjR.js} +190 -50
- package/dist/PropertyEditView-DKzOpMjR.js.map +1 -0
- package/dist/{CollectionsStudioView-B1mN33GB.js → RouterCollectionsStudioView-BQTIExjA.js} +61 -39
- package/dist/RouterCollectionsStudioView-BQTIExjA.js.map +1 -0
- package/dist/collection_editor/_cms_internals.d.ts +1 -1
- package/dist/collection_editor/api/generateCollectionApi.d.ts +3 -2
- package/dist/collection_editor/extensibility_types.d.ts +135 -0
- package/dist/collection_editor/index.d.ts +6 -0
- package/dist/collection_editor/serializable_types.d.ts +370 -0
- package/dist/collection_editor/serializable_utils.d.ts +50 -0
- package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +3 -3
- package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +12 -1
- package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +13 -1
- package/dist/collection_editor/ui/collection_editor/CollectionsStudioView.d.ts +24 -2
- package/dist/collection_editor/ui/collection_editor/DisplaySettingsForm.d.ts +2 -1
- package/dist/collection_editor/ui/collection_editor/GeneralSettingsForm.d.ts +4 -1
- package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +12 -1
- package/dist/collection_editor/ui/collection_editor/RouterCollectionStudioView.d.ts +15 -0
- package/dist/collection_editor/ui/collection_editor/RouterCollectionsStudioView.d.ts +14 -0
- package/dist/collection_editor/useJsonCollectionsConfigController.d.ts +37 -0
- package/dist/collection_editor/useSafeSnackbarController.d.ts +2 -0
- package/dist/collection_editor_ui.d.ts +4 -0
- package/dist/collection_editor_ui.js +33 -4
- package/dist/collection_editor_ui.js.map +1 -0
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
- package/dist/components/EntityCollectionTable/index.d.ts +1 -1
- package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +3 -3
- package/dist/components/EntityCollectionView/hooks/useKanbanDragAndDrop.d.ts +3 -3
- package/dist/components/EntityEditView.d.ts +3 -2
- package/dist/components/RebaseCMS.d.ts +1 -1
- package/dist/components/SelectableTable/SelectionStore.d.ts +12 -21
- package/dist/components/field_configs.d.ts +1 -1
- package/dist/editor/editor.d.ts +0 -6
- package/dist/editor.js +1 -3
- package/dist/editor.js.map +1 -1
- package/dist/form/EntityForm.d.ts +10 -9
- package/dist/form/EntityFormBinding.d.ts +27 -0
- package/dist/form/components/StorageUploadProgress.d.ts +6 -1
- package/dist/form/form_utils.d.ts +8 -0
- package/dist/form/index.d.ts +5 -2
- package/dist/{history-BO6VT85a.js → history-B3cqBzEV.js} +2 -2
- package/dist/{history-BO6VT85a.js.map → history-B3cqBzEV.js.map} +1 -1
- package/dist/hooks/navigation/contexts/CollectionRegistryContext.d.ts +1 -1
- package/dist/hooks/navigation/useAsyncResolver.d.ts +26 -0
- package/dist/hooks/navigation/useBuildCollectionRegistryController.d.ts +2 -0
- package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
- package/dist/hooks/navigation/utils.d.ts +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -2
- package/dist/preview/components/StorageThumbnail.d.ts +3 -1
- package/dist/routes/CustomViewRoute.d.ts +2 -1
- package/dist/{src-BYniefVu.js → src-cxV1ODRG.js} +1731 -1710
- package/dist/src-cxV1ODRG.js.map +1 -0
- package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
- package/dist/types/components/EntityFormProps.d.ts +59 -0
- package/dist/types/components/PropertyPreviewProps.d.ts +2 -1
- package/dist/types/fields.d.ts +10 -7
- package/dist/util/dataControllerAdapter.d.ts +30 -0
- package/dist/util/navigation_utils.d.ts +1 -1
- package/dist/util/propertyConfigMapper.d.ts +17 -0
- package/package.json +8 -8
- package/src/collection_editor/_cms_internals.ts +1 -1
- package/src/collection_editor/api/generateCollectionApi.ts +3 -2
- package/src/collection_editor/extensibility_types.ts +168 -0
- package/src/collection_editor/index.ts +46 -0
- package/src/collection_editor/pgColumnToProperty.ts +2 -2
- package/src/collection_editor/serializable_types.ts +474 -0
- package/src/collection_editor/serializable_utils.ts +576 -0
- package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +6 -6
- package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +170 -89
- package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +95 -78
- package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +23 -20
- package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +79 -27
- package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +9 -7
- package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +22 -5
- package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +3 -3
- package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +154 -18
- package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
- package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -0
- package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +44 -0
- package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +41 -0
- package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +4 -3
- package/src/collection_editor/ui/collection_editor/properties/StoragePropertyField.tsx +35 -1
- package/src/collection_editor/ui/collection_editor/templates/blog_template.ts +1 -1
- package/src/collection_editor/ui/collection_editor/utils/supported_fields.tsx +0 -1
- package/src/collection_editor/ui/collection_editor/utils/update_property_for_widget.ts +7 -29
- package/src/collection_editor/useJsonCollectionsConfigController.tsx +286 -0
- package/src/collection_editor/useSafeSnackbarController.ts +9 -0
- package/src/collection_editor_ui.ts +6 -0
- package/src/components/DefaultDrawer.tsx +14 -4
- package/src/components/DrawerNavigationGroup.tsx +2 -2
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +11 -9
- package/src/components/EntityCollectionTable/fields/TableMultipleRelationField.tsx +2 -1
- package/src/components/EntityCollectionTable/fields/TableRelationField.tsx +2 -1
- package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +23 -20
- package/src/components/EntityCollectionTable/index.tsx +1 -1
- package/src/components/EntityCollectionTable/table_bindings.tsx +24 -44
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +13 -12
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +40 -190
- package/src/components/EntityCollectionView/EntityCollectionListView.tsx +66 -184
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +8 -25
- package/src/components/EntityCollectionView/hooks/useCollectionInlineEditor.ts +1 -1
- package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +6 -6
- package/src/components/EntityCollectionView/useBoardDataController.tsx +12 -40
- package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +3 -3
- package/src/components/EntityDetailView.tsx +2 -2
- package/src/components/EntityEditView.tsx +7 -6
- package/src/components/EntityEditViewFormActions.tsx +35 -29
- package/src/components/HomePage/ContentHomePage.tsx +14 -5
- package/src/components/RebaseCMS.tsx +3 -3
- package/src/components/RebaseNavigation.tsx +74 -9
- package/src/components/RelationSelector.tsx +2 -2
- package/src/components/SelectableTable/SelectableTable.tsx +13 -5
- package/src/components/SelectableTable/SelectionStore.ts +18 -48
- package/src/components/SideDialogs.tsx +0 -7
- package/src/components/common/default_entity_actions.tsx +22 -11
- package/src/components/field_configs.tsx +0 -14
- package/src/contexts/BreacrumbsContext.tsx +15 -6
- package/src/editor/editor.tsx +4 -7
- package/src/editor/selectors/ai-selector.tsx +0 -1
- package/src/form/EntityForm.tsx +191 -428
- package/src/form/EntityFormActions.tsx +35 -49
- package/src/form/EntityFormBinding.tsx +323 -0
- package/src/form/components/FormLayout.tsx +1 -1
- package/src/form/components/StorageUploadProgress.tsx +9 -2
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +12 -1
- package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +4 -4
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
- package/src/form/field_bindings/RelationFieldBinding.tsx +5 -7
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +19 -15
- package/src/form/form_utils.ts +163 -0
- package/src/form/index.tsx +14 -6
- package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -2
- package/src/hooks/navigation/contexts/NavigationStateContext.tsx +2 -2
- package/src/hooks/navigation/contexts/UrlContext.tsx +1 -2
- package/src/hooks/navigation/useAsyncResolver.ts +100 -0
- package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -8
- package/src/hooks/navigation/useBuildNavigationStateController.tsx +12 -8
- package/src/hooks/navigation/useNavigationResolution.ts +33 -0
- package/src/hooks/navigation/useResolvedCollections.ts +29 -71
- package/src/hooks/navigation/useResolvedViews.tsx +40 -70
- package/src/hooks/navigation/useTopLevelNavigation.ts +20 -16
- package/src/hooks/navigation/utils.ts +7 -4
- package/src/index.ts +6 -1
- package/src/preview/PropertyPreview.tsx +1 -16
- package/src/preview/components/StorageThumbnail.tsx +24 -7
- package/src/routes/CustomViewRoute.tsx +14 -6
- package/src/types/components/EntityFormActionsProps.tsx +1 -1
- package/src/types/components/EntityFormProps.tsx +68 -0
- package/src/types/components/PropertyPreviewProps.tsx +1 -1
- package/src/types/fields.tsx +9 -8
- package/src/util/dataControllerAdapter.ts +93 -0
- package/src/util/navigation_utils.ts +1 -1
- package/src/util/previews.ts +20 -0
- package/src/util/propertyConfigMapper.ts +134 -0
- package/src/util/property_utils.tsx +1 -2
- package/dist/CollectionEditorDialog-HNXGxXdO.js.map +0 -1
- package/dist/CollectionsStudioView-B1mN33GB.js.map +0 -1
- package/dist/PropertyEditView-C2wp8kQ9.js.map +0 -1
- package/dist/components/EntityCollectionTable/fields/VirtualTableDateField.d.ts +0 -12
- package/dist/components/EntityCollectionTable/fields/VirtualTableInput.d.ts +0 -10
- package/dist/components/EntityCollectionTable/fields/VirtualTableNumberInput.d.ts +0 -9
- package/dist/components/EntityCollectionTable/fields/VirtualTableSwitch.d.ts +0 -8
- package/dist/components/EntityCollectionView/Board.d.ts +0 -3
- package/dist/components/EntityCollectionView/BoardColumn.d.ts +0 -42
- package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +0 -9
- package/dist/components/EntityCollectionView/BoardSortableList.d.ts +0 -14
- package/dist/components/EntityCollectionView/board_types.d.ts +0 -105
- package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +0 -11
- package/dist/hooks/navigation/useNavigationRegistry.d.ts +0 -12
- package/dist/hooks/navigation/useNavigationURLs.d.ts +0 -11
- package/dist/src-BYniefVu.js.map +0 -1
- package/src/components/EntityCollectionTable/fields/VirtualTableDateField.tsx +0 -39
- package/src/components/EntityCollectionTable/fields/VirtualTableInput.tsx +0 -90
- package/src/components/EntityCollectionTable/fields/VirtualTableNumberInput.tsx +0 -83
- package/src/components/EntityCollectionTable/fields/VirtualTableSwitch.tsx +0 -32
- package/src/components/EntityCollectionView/Board.tsx +0 -475
- package/src/components/EntityCollectionView/BoardColumn.tsx +0 -165
- package/src/components/EntityCollectionView/BoardColumnTitle.tsx +0 -46
- package/src/components/EntityCollectionView/BoardSortableList.tsx +0 -177
- package/src/components/EntityCollectionView/board_types.ts +0 -113
- package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +0 -141
- package/src/hooks/navigation/useNavigationRegistry.ts +0 -164
- package/src/hooks/navigation/useNavigationURLs.ts +0 -56
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EntityCollection, RebasePlugin } from "@rebasepro/types";
|
|
2
|
-
import {
|
|
2
|
+
import { useRef, useMemo } from "react";
|
|
3
3
|
|
|
4
4
|
import { AuthController, CollectionRegistryController, RebaseData, User } from "@rebasepro/types";
|
|
5
5
|
import type { EntityCollectionsBuilder } from "@rebasepro/types";
|
|
@@ -8,6 +8,7 @@ import { CollectionRegistry } from "@rebasepro/common";
|
|
|
8
8
|
|
|
9
9
|
import { resolveCollections } from "./useNavigationResolution";
|
|
10
10
|
import { areCollectionListsEqual } from "./utils";
|
|
11
|
+
import { useAsyncResolver } from "./useAsyncResolver";
|
|
11
12
|
|
|
12
13
|
export type UseResolvedCollectionsProps<EC extends EntityCollection, USER extends User> = {
|
|
13
14
|
authController: AuthController<USER>;
|
|
@@ -50,16 +51,11 @@ export function useResolvedCollections<EC extends EntityCollection, USER extends
|
|
|
50
51
|
collectionRegistryController
|
|
51
52
|
} = props;
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
59
|
-
|
|
60
|
-
const refresh = useCallback(() => {
|
|
61
|
-
setRefreshTrigger(prev => prev + 1);
|
|
62
|
-
}, []);
|
|
54
|
+
// Stable identity string for the user — avoids re-triggering when the
|
|
55
|
+
// authController object reference changes but uid/roles are the same.
|
|
56
|
+
const userIdentity = authController.user
|
|
57
|
+
? `${authController.user.uid}:${(authController.user.roles ?? []).sort().join(',')}`
|
|
58
|
+
: null;
|
|
63
59
|
|
|
64
60
|
// Use refs for values that may be new objects each render but shouldn't
|
|
65
61
|
// re-trigger the effect. The effect reads them at execution time.
|
|
@@ -70,74 +66,36 @@ export function useResolvedCollections<EC extends EntityCollection, USER extends
|
|
|
70
66
|
const pluginsRef = useRef(plugins);
|
|
71
67
|
pluginsRef.current = plugins;
|
|
72
68
|
|
|
69
|
+
const { data: collections, loading, error, refresh } = useAsyncResolver<EntityCollection[]>({
|
|
70
|
+
resolver: async () => {
|
|
71
|
+
const resolved = await resolveCollections(
|
|
72
|
+
collectionsProp,
|
|
73
|
+
authControllerRef.current,
|
|
74
|
+
dataRef.current,
|
|
75
|
+
pluginsRef.current
|
|
76
|
+
);
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
const resolvedCollectionsRef = useRef<EntityCollection[]>([]);
|
|
76
|
-
|
|
77
|
-
const initialLoading = authController.initialLoading;
|
|
78
|
-
const user = authController.user;
|
|
79
|
-
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
if (disabled || initialLoading) return;
|
|
82
|
-
|
|
83
|
-
let cancelled = false;
|
|
84
|
-
|
|
85
|
-
(async () => {
|
|
86
|
-
try {
|
|
87
|
-
const resolved = await resolveCollections(
|
|
88
|
-
collectionsProp,
|
|
89
|
-
authControllerRef.current,
|
|
90
|
-
dataRef.current,
|
|
91
|
-
pluginsRef.current
|
|
92
|
-
);
|
|
78
|
+
const deduped = [...resolved];
|
|
93
79
|
|
|
94
|
-
|
|
80
|
+
// Register with the CollectionRegistry; returns true if changed
|
|
81
|
+
const changed = collectionRegistryController.collectionRegistryRef.current.registerMultiple(deduped);
|
|
95
82
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// Register with the CollectionRegistry; returns true if changed
|
|
99
|
-
const changed = collectionRegistryController.collectionRegistryRef.current.registerMultiple(deduped);
|
|
100
|
-
|
|
101
|
-
if (changed) {
|
|
102
|
-
console.debug("Collections have changed", deduped);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Only update state if collections actually changed
|
|
106
|
-
if (!areCollectionListsEqual(resolvedCollectionsRef.current, deduped)) {
|
|
107
|
-
resolvedCollectionsRef.current = deduped;
|
|
108
|
-
setResolvedCollections(deduped);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
setError(undefined);
|
|
112
|
-
} catch (e) {
|
|
113
|
-
if (!cancelled) {
|
|
114
|
-
console.error(e);
|
|
115
|
-
setError(e as Error);
|
|
116
|
-
}
|
|
117
|
-
} finally {
|
|
118
|
-
if (!cancelled) {
|
|
119
|
-
setLoading(false);
|
|
120
|
-
}
|
|
83
|
+
if (changed) {
|
|
84
|
+
console.debug("Collections have changed", deduped);
|
|
121
85
|
}
|
|
122
|
-
})();
|
|
123
86
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
collectionsProp,
|
|
129
|
-
disabled,
|
|
130
|
-
|
|
131
|
-
refreshTrigger,
|
|
132
|
-
initialLoading,
|
|
133
|
-
user
|
|
134
|
-
]);
|
|
87
|
+
return deduped;
|
|
88
|
+
},
|
|
89
|
+
initialValue: [],
|
|
90
|
+
isEqual: areCollectionListsEqual,
|
|
91
|
+
deps: [collectionsProp, userIdentity, disabled],
|
|
92
|
+
disabled: disabled || authController.initialLoading,
|
|
93
|
+
});
|
|
135
94
|
|
|
136
95
|
return useMemo(() => ({
|
|
137
|
-
collections
|
|
96
|
+
collections,
|
|
138
97
|
loading,
|
|
139
98
|
error,
|
|
140
99
|
refresh
|
|
141
|
-
}), [
|
|
100
|
+
}), [collections, loading, error, refresh]);
|
|
142
101
|
}
|
|
143
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import type { AppView, AppViewsBuilder, EffectiveRoleController, RebasePlugin } from "@rebasepro/types";
|
|
3
|
-
import {
|
|
3
|
+
import { useMemo, useRef } from "react";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Compare two view arrays by their slug identity.
|
|
@@ -20,6 +20,7 @@ function viewSlugsEqual(a: AppView[] | undefined, b: AppView[] | undefined): boo
|
|
|
20
20
|
import type { AuthController, RebaseData, User } from "@rebasepro/types";
|
|
21
21
|
|
|
22
22
|
import { resolveAppViews } from "./useNavigationResolution";
|
|
23
|
+
import { useAsyncResolver } from "./useAsyncResolver";
|
|
23
24
|
|
|
24
25
|
export type UseResolvedViewsProps<USER extends User> = {
|
|
25
26
|
authController: AuthController<USER>;
|
|
@@ -39,6 +40,23 @@ export type UseResolvedViewsResult = {
|
|
|
39
40
|
refresh: () => void;
|
|
40
41
|
};
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Combined result type for the async resolver — holds both views and
|
|
45
|
+
* adminViews so they can be resolved together in a single async call.
|
|
46
|
+
*/
|
|
47
|
+
type ResolvedViewsData = {
|
|
48
|
+
views: AppView[] | undefined;
|
|
49
|
+
adminViews: AppView[] | undefined;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Equality check for the combined views data.
|
|
54
|
+
* Compares both views and adminViews by slug identity.
|
|
55
|
+
*/
|
|
56
|
+
function areResolvedViewsEqual(a: ResolvedViewsData, b: ResolvedViewsData): boolean {
|
|
57
|
+
return viewSlugsEqual(a.views, b.views) && viewSlugsEqual(a.adminViews, b.adminViews);
|
|
58
|
+
}
|
|
59
|
+
|
|
42
60
|
/**
|
|
43
61
|
* Hook that resolves view and admin view props (which may be async builders or arrays)
|
|
44
62
|
* into concrete AppView[].
|
|
@@ -60,21 +78,11 @@ export function useResolvedViews<USER extends User>(
|
|
|
60
78
|
effectiveRoleController
|
|
61
79
|
} = props;
|
|
62
80
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// Track the trigger count to allow force-refresh
|
|
69
|
-
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
70
|
-
|
|
71
|
-
const refresh = useCallback(() => {
|
|
72
|
-
setRefreshTrigger(prev => prev + 1);
|
|
73
|
-
}, []);
|
|
74
|
-
|
|
75
|
-
// Refs for change-detection (avoids state updates when views haven't changed)
|
|
76
|
-
const viewsRef = useRef<AppView[] | undefined>(undefined);
|
|
77
|
-
const adminViewsRef = useRef<AppView[] | undefined>(undefined);
|
|
81
|
+
// Stable identity string for the user — avoids re-triggering when the
|
|
82
|
+
// authController object reference changes but uid/roles are the same.
|
|
83
|
+
const userIdentity = authController.user
|
|
84
|
+
? `${authController.user.uid}:${(authController.user.roles ?? []).sort().join(',')}`
|
|
85
|
+
: null;
|
|
78
86
|
|
|
79
87
|
// Use refs for values that may be new objects each render but shouldn't
|
|
80
88
|
// re-trigger the effect. The effect reads them at execution time.
|
|
@@ -104,64 +112,26 @@ export function useResolvedViews<USER extends User>(
|
|
|
104
112
|
resolvedAuthControllerRef.current = resolvedAuthController;
|
|
105
113
|
|
|
106
114
|
const initialLoading = resolvedAuthController.initialLoading;
|
|
107
|
-
const user = resolvedAuthController.user;
|
|
108
|
-
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
if (initialLoading) return;
|
|
111
|
-
|
|
112
|
-
let cancelled = false;
|
|
113
|
-
|
|
114
|
-
(async () => {
|
|
115
|
-
try {
|
|
116
|
-
const [newViews, newAdminViews] = await Promise.all([
|
|
117
|
-
resolveAppViews(viewsProp, resolvedAuthControllerRef.current, dataRef.current, pluginsRef.current),
|
|
118
|
-
resolveAppViews(adminViewsProp, resolvedAuthControllerRef.current, dataRef.current)
|
|
119
|
-
]);
|
|
120
|
-
|
|
121
|
-
// Compare views by slug identity rather than deepEqual.
|
|
122
|
-
// Views contain React elements (JSX) whose internal properties
|
|
123
|
-
// change on every render, making deepEqual unreliable.
|
|
124
|
-
if (!viewSlugsEqual(viewsRef.current, newViews)) {
|
|
125
|
-
viewsRef.current = newViews;
|
|
126
|
-
setResolvedViews(newViews);
|
|
127
|
-
}
|
|
128
115
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
setLoading(false);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
})();
|
|
146
|
-
|
|
147
|
-
return () => {
|
|
148
|
-
cancelled = true;
|
|
149
|
-
};
|
|
150
|
-
}, [
|
|
151
|
-
viewsProp,
|
|
152
|
-
adminViewsProp,
|
|
153
|
-
refreshTrigger,
|
|
154
|
-
adminMode,
|
|
155
|
-
initialLoading,
|
|
156
|
-
user
|
|
157
|
-
]);
|
|
116
|
+
const { data: resolvedData, loading, error, refresh } = useAsyncResolver<ResolvedViewsData>({
|
|
117
|
+
resolver: async () => {
|
|
118
|
+
const [newViews, newAdminViews] = await Promise.all([
|
|
119
|
+
resolveAppViews(viewsProp, resolvedAuthControllerRef.current, dataRef.current, pluginsRef.current),
|
|
120
|
+
resolveAppViews(adminViewsProp, resolvedAuthControllerRef.current, dataRef.current)
|
|
121
|
+
]);
|
|
122
|
+
return { views: newViews, adminViews: newAdminViews };
|
|
123
|
+
},
|
|
124
|
+
initialValue: { views: undefined, adminViews: undefined },
|
|
125
|
+
isEqual: areResolvedViewsEqual,
|
|
126
|
+
deps: [viewsProp, adminViewsProp, adminMode, userIdentity],
|
|
127
|
+
disabled: initialLoading,
|
|
128
|
+
});
|
|
158
129
|
|
|
159
130
|
return useMemo(() => ({
|
|
160
|
-
views:
|
|
161
|
-
adminViews:
|
|
131
|
+
views: resolvedData.views,
|
|
132
|
+
adminViews: resolvedData.adminViews,
|
|
162
133
|
loading,
|
|
163
134
|
error,
|
|
164
135
|
refresh
|
|
165
|
-
}), [
|
|
136
|
+
}), [resolvedData, loading, error, refresh]);
|
|
166
137
|
}
|
|
167
|
-
|
|
@@ -16,7 +16,7 @@ export type UseTopLevelNavigationProps = {
|
|
|
16
16
|
viewsOrder?: string[];
|
|
17
17
|
urlController: UrlController;
|
|
18
18
|
adminMode?: "content" | "studio" | "settings";
|
|
19
|
-
collectionRegistryController: CollectionRegistryController
|
|
19
|
+
collectionRegistryController: CollectionRegistryController & { collectionRegistryRef: React.MutableRefObject<CollectionRegistry> };
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
export type UseTopLevelNavigationResult = {
|
|
@@ -178,26 +178,30 @@ export function useTopLevelNavigation(
|
|
|
178
178
|
];
|
|
179
179
|
|
|
180
180
|
const groupOrderValue = (groupName?: string): number => {
|
|
181
|
-
if (groupName === NAVIGATION_ADMIN_GROUP_NAME) return 1;
|
|
181
|
+
if (groupName === NAVIGATION_ADMIN_GROUP_NAME || groupName === "Settings" || groupName === "Admin") return 1;
|
|
182
182
|
return 0;
|
|
183
183
|
};
|
|
184
184
|
|
|
185
|
+
const usedViewsOrder = viewsOrder ?? navigationEntriesOrder;
|
|
185
186
|
navigationEntries = navigationEntries.sort((a, b) => {
|
|
186
|
-
|
|
187
|
-
|
|
187
|
+
// Primary: group priority (admin/settings last)
|
|
188
|
+
const groupDiff = groupOrderValue(a.group) - groupOrderValue(b.group);
|
|
189
|
+
if (groupDiff !== 0) return groupDiff;
|
|
188
190
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
navigationEntries = navigationEntries.sort((a, b) => {
|
|
191
|
+
// Secondary: explicit view order (if provided)
|
|
192
|
+
if (usedViewsOrder) {
|
|
192
193
|
const getSortPath = (navEntry: NavigationEntry) => typeof navEntry.slug === "string" ? navEntry.slug : navEntry.slug[0];
|
|
193
194
|
const aIndex = usedViewsOrder.indexOf(getSortPath(a));
|
|
194
195
|
const bIndex = usedViewsOrder.indexOf(getSortPath(b));
|
|
195
|
-
if (aIndex
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
196
|
+
if (aIndex !== -1 || bIndex !== -1) {
|
|
197
|
+
if (aIndex === -1) return 1;
|
|
198
|
+
if (bIndex === -1) return -1;
|
|
199
|
+
return aIndex - bIndex;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return 0;
|
|
204
|
+
});
|
|
201
205
|
|
|
202
206
|
const collectedGroupsFromEntries = navigationEntries
|
|
203
207
|
.map(e => e.group)
|
|
@@ -213,9 +217,9 @@ export function useTopLevelNavigation(
|
|
|
213
217
|
];
|
|
214
218
|
|
|
215
219
|
const uniqueGroupsArray = [...new Set(allDefinedGroups)];
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
const uniqueGroups = [...
|
|
220
|
+
const lastGroups = uniqueGroupsArray.filter(g => g === NAVIGATION_ADMIN_GROUP_NAME || g === "Settings" || g === "Admin");
|
|
221
|
+
const otherGroups = uniqueGroupsArray.filter(g => g !== NAVIGATION_ADMIN_GROUP_NAME && g !== "Settings" && g !== "Admin");
|
|
222
|
+
const uniqueGroups = [...otherGroups, ...lastGroups] as string[];
|
|
219
223
|
|
|
220
224
|
const computedTopLevelNav = {
|
|
221
225
|
allowDragAndDrop: plugins?.some((plugin: RebasePlugin) => plugin.hooks?.allowDragAndDrop) ?? false,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { AppView, EntityCollection, RebasePlugin, NavigationGroupMapping } from "@rebasepro/types";
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import { getSubcollections } from "@rebasepro/common";
|
|
4
4
|
import { deepEqual as equal } from "fast-equals";
|
|
5
5
|
|
|
6
6
|
export const NAVIGATION_DEFAULT_GROUP_NAME = "Views";
|
|
7
7
|
export const NAVIGATION_ADMIN_GROUP_NAME = "Admin";
|
|
8
8
|
|
|
9
|
-
export function getGroup(collectionOrView: EntityCollection
|
|
9
|
+
export function getGroup(collectionOrView: EntityCollection | AppView) {
|
|
10
10
|
const trimmed = collectionOrView.group?.trim();
|
|
11
11
|
if (!trimmed || trimmed === "") {
|
|
12
12
|
return NAVIGATION_DEFAULT_GROUP_NAME;
|
|
@@ -28,8 +28,11 @@ export function computeNavigationGroups({
|
|
|
28
28
|
|
|
29
29
|
// Deep clone the input groups upfront to avoid mutating the caller's data
|
|
30
30
|
let result = navigationGroupMappings
|
|
31
|
-
? navigationGroupMappings.map(g => ({
|
|
32
|
-
|
|
31
|
+
? navigationGroupMappings.map(g => ({
|
|
32
|
+
name: g.name,
|
|
33
|
+
entries: [...g.entries],
|
|
34
|
+
...(g.collapsedByDefault !== undefined && { collapsedByDefault: g.collapsedByDefault })
|
|
35
|
+
}))
|
|
33
36
|
: navigationGroupMappings;
|
|
34
37
|
|
|
35
38
|
// Merge plugin navigation entries
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export * from "./types";
|
|
3
3
|
// Editor types only — the full ProseMirror editor (RichTextEditor) is a heavy import (~300 KB)
|
|
4
4
|
// and is available as a separate entry point: @rebasepro/admin/editor
|
|
5
|
-
export type { RichTextEditorProps, RichTextEditorTextSize,
|
|
5
|
+
export type { RichTextEditorProps, RichTextEditorTextSize, JSONContent, EditorAIController } from "./editor";
|
|
6
6
|
export * from "./form";
|
|
7
7
|
export * from "./preview";
|
|
8
8
|
export * from "./routes";
|
|
@@ -92,6 +92,11 @@ export {
|
|
|
92
92
|
getEntityTitlePropertyKey
|
|
93
93
|
} from "./util";
|
|
94
94
|
|
|
95
|
+
// Headless collection view adapters
|
|
96
|
+
// Map entity types to the data-agnostic CollectionView from @rebasepro/ui
|
|
97
|
+
export { mapPropertyToConfig, mapPropertiesToConfigs } from "./util/propertyConfigMapper";
|
|
98
|
+
export { useCollectionDataController, createStaticDataController } from "./util/dataControllerAdapter";
|
|
99
|
+
|
|
95
100
|
// Data import/export — merged from former standalone packages
|
|
96
101
|
export * from "./data_import";
|
|
97
102
|
export * from "./data_export";
|
|
@@ -82,6 +82,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
82
82
|
content = <StorageThumbnail
|
|
83
83
|
interactive={interactive}
|
|
84
84
|
storeUrl={property.storage?.storeUrl ?? false}
|
|
85
|
+
storageSourceKey={stringProperty.storage.storageSource}
|
|
85
86
|
size={props.size}
|
|
86
87
|
fill={fill}
|
|
87
88
|
storagePathOrDownloadUrl={filePath}/>;
|
|
@@ -107,22 +108,6 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
107
108
|
propertyKey={propertyKey}
|
|
108
109
|
size={props.size}
|
|
109
110
|
/>;
|
|
110
|
-
} else if (stringProperty.reference) {
|
|
111
|
-
if (typeof stringProperty.reference.path === "string") {
|
|
112
|
-
content = <ReferencePreview
|
|
113
|
-
disabled={!stringProperty.reference.path}
|
|
114
|
-
previewProperties={stringProperty.reference?.ui?.previewProperties}
|
|
115
|
-
includeId={stringProperty.reference.includeId}
|
|
116
|
-
includeEntityLink={stringProperty.reference.includeEntityLink}
|
|
117
|
-
size={props.size}
|
|
118
|
-
reference={new EntityReference({ id: value,
|
|
119
|
-
path: stringProperty.reference.path })}
|
|
120
|
-
textOnly={props.textOnly}
|
|
121
|
-
/>;
|
|
122
|
-
} else {
|
|
123
|
-
content = <EmptyValue/>;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
111
|
} else {
|
|
127
112
|
content = <StringPropertyPreview {...props}
|
|
128
113
|
property={stringProperty}
|
|
@@ -2,7 +2,8 @@ import React, { useEffect } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { renderSkeletonImageThumbnail } from "../property_previews/SkeletonPropertyComponent";
|
|
4
4
|
import { UrlComponentPreview } from "./UrlComponentPreview";
|
|
5
|
-
import { ErrorView, useStorageSource } from "@rebasepro/core";
|
|
5
|
+
import { ErrorView, useStorageSource, useStorageSources } from "@rebasepro/core";
|
|
6
|
+
import { resolveStorageSource } from "@rebasepro/common";
|
|
6
7
|
import { DownloadConfig, FileType } from "@rebasepro/types";
|
|
7
8
|
import type { PreviewSize } from "../../types/components/PropertyPreviewProps";
|
|
8
9
|
import { Skeleton } from "@rebasepro/ui";
|
|
@@ -12,6 +13,8 @@ type StorageThumbnailProps = {
|
|
|
12
13
|
size: PreviewSize;
|
|
13
14
|
interactive?: boolean;
|
|
14
15
|
fill?: boolean;
|
|
16
|
+
/** Key of the storage source backing this property (`StorageConfig.storageSource`). */
|
|
17
|
+
storageSourceKey?: string;
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
/**
|
|
@@ -24,7 +27,8 @@ function areEqual(prevProps: StorageThumbnailProps, nextProps: StorageThumbnailP
|
|
|
24
27
|
prevProps.storagePathOrDownloadUrl === nextProps.storagePathOrDownloadUrl &&
|
|
25
28
|
prevProps.storeUrl === nextProps.storeUrl &&
|
|
26
29
|
prevProps.interactive === nextProps.interactive &&
|
|
27
|
-
prevProps.fill === nextProps.fill
|
|
30
|
+
prevProps.fill === nextProps.fill &&
|
|
31
|
+
prevProps.storageSourceKey === nextProps.storageSourceKey;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
const URL_CACHE: Record<string, DownloadConfig> = {};
|
|
@@ -34,13 +38,26 @@ export function StorageThumbnailInternal({
|
|
|
34
38
|
interactive,
|
|
35
39
|
storagePathOrDownloadUrl,
|
|
36
40
|
size,
|
|
37
|
-
fill
|
|
41
|
+
fill,
|
|
42
|
+
storageSourceKey
|
|
38
43
|
}: StorageThumbnailProps) {
|
|
39
44
|
|
|
40
45
|
const [error, setError] = React.useState<Error | undefined>(undefined);
|
|
41
|
-
const
|
|
46
|
+
const defaultStorage = useStorageSource();
|
|
47
|
+
const storageSources = useStorageSources();
|
|
48
|
+
// Resolve the per-property backend so previews of `storeUrl: false`
|
|
49
|
+
// properties pointing at a named source hit the right backend.
|
|
50
|
+
const storage = resolveStorageSource({
|
|
51
|
+
sourceKey: storageSourceKey,
|
|
52
|
+
sources: storageSources.sources,
|
|
53
|
+
defaultSource: defaultStorage
|
|
54
|
+
});
|
|
42
55
|
|
|
43
|
-
|
|
56
|
+
// Cache key is namespaced by source so the same path on two backends
|
|
57
|
+
// does not collide.
|
|
58
|
+
const cacheKey = `${storageSourceKey ?? ""}::${storagePathOrDownloadUrl}`;
|
|
59
|
+
|
|
60
|
+
const [downloadConfig, setDownloadConfig] = React.useState<DownloadConfig>(URL_CACHE[cacheKey]);
|
|
44
61
|
|
|
45
62
|
useEffect(() => {
|
|
46
63
|
if (!storagePathOrDownloadUrl)
|
|
@@ -50,13 +67,13 @@ export function StorageThumbnailInternal({
|
|
|
50
67
|
.then(function (downloadConfig) {
|
|
51
68
|
if (!unmounted) {
|
|
52
69
|
setDownloadConfig(downloadConfig);
|
|
53
|
-
URL_CACHE[
|
|
70
|
+
URL_CACHE[cacheKey] = downloadConfig;
|
|
54
71
|
}
|
|
55
72
|
}).catch(setError);
|
|
56
73
|
return () => {
|
|
57
74
|
unmounted = true;
|
|
58
75
|
};
|
|
59
|
-
}, [storagePathOrDownloadUrl]);
|
|
76
|
+
}, [storagePathOrDownloadUrl, cacheKey]);
|
|
60
77
|
|
|
61
78
|
if (!storagePathOrDownloadUrl) return null;
|
|
62
79
|
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
1
2
|
import type { AppView } from "@rebasepro/types";
|
|
2
|
-
;
|
|
3
|
-
import { useEffect } from "react";
|
|
4
|
-
import { useBreadcrumbsController } from "../index";
|
|
5
|
-
import { useUrlController } from "../index";
|
|
3
|
+
import { useBreadcrumbsController, useUrlController } from "../index";
|
|
6
4
|
|
|
7
5
|
export function CustomViewRoute({ view }: {
|
|
8
6
|
view: AppView
|
|
@@ -11,8 +9,14 @@ export function CustomViewRoute({ view }: {
|
|
|
11
9
|
const breadcrumbs = useBreadcrumbsController();
|
|
12
10
|
const urlController = useUrlController();
|
|
13
11
|
|
|
12
|
+
// Use a ref to avoid breadcrumbs identity in the dep array —
|
|
13
|
+
// breadcrumbs.set() creates a new context value each call, which would
|
|
14
|
+
// re-trigger this effect and cause an infinite render loop.
|
|
15
|
+
const breadcrumbsRef = useRef(breadcrumbs);
|
|
16
|
+
breadcrumbsRef.current = breadcrumbs;
|
|
17
|
+
|
|
14
18
|
useEffect(() => {
|
|
15
|
-
|
|
19
|
+
breadcrumbsRef.current.set({
|
|
16
20
|
breadcrumbs: [{
|
|
17
21
|
title: view.name,
|
|
18
22
|
url: urlController.buildAppUrlPath(view.slug)
|
|
@@ -20,5 +24,9 @@ export function CustomViewRoute({ view }: {
|
|
|
20
24
|
});
|
|
21
25
|
}, [view.slug, urlController]);
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
if (typeof view.view === 'function') {
|
|
28
|
+
const ViewComponent = view.view;
|
|
29
|
+
return <ViewComponent />;
|
|
30
|
+
}
|
|
31
|
+
return <>{view.view}</>;
|
|
24
32
|
}
|
|
@@ -13,7 +13,7 @@ export interface EntityFormActionsProps {
|
|
|
13
13
|
disabled: boolean;
|
|
14
14
|
status: "new" | "existing" | "copy";
|
|
15
15
|
pluginActions: React.ReactNode[];
|
|
16
|
-
openEntityMode
|
|
16
|
+
openEntityMode?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
17
17
|
showDefaultActions?: boolean;
|
|
18
18
|
navigateBack: () => void;
|
|
19
19
|
formContext: FormContext
|
|
@@ -4,6 +4,11 @@ import { FormContext } from "../fields";
|
|
|
4
4
|
import { FormexController } from "./formex";
|
|
5
5
|
import { EntityFormActionsProps } from "./EntityFormActionsProps";
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Props for the headless EntityForm component.
|
|
9
|
+
* This form can be used without CMS context — all backend concerns
|
|
10
|
+
* (save, caching, analytics, plugin slots) are provided via callbacks.
|
|
11
|
+
*/
|
|
7
12
|
export type EntityFormProps<M extends Record<string, unknown>> = {
|
|
8
13
|
path: string;
|
|
9
14
|
fullIdPath?: string;
|
|
@@ -44,6 +49,69 @@ export type EntityFormProps<M extends Record<string, unknown>> = {
|
|
|
44
49
|
Builder?: React.ComponentType<EntityCustomViewParams<M>>;
|
|
45
50
|
|
|
46
51
|
children?: React.ReactNode;
|
|
52
|
+
|
|
53
|
+
// --- Headless callbacks (replace internal CMS hooks) ---
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Called on form submit with the values to save.
|
|
57
|
+
* The EntityFormBinding wrapper provides this automatically.
|
|
58
|
+
* When using EntityForm standalone, provide your own save logic.
|
|
59
|
+
*/
|
|
60
|
+
onSubmit?: (values: M, formex: FormexController<M>) => Promise<Entity<M> | void> | void;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Called when form values change (deferred).
|
|
64
|
+
* The EntityFormBinding wrapper uses this for entity caching.
|
|
65
|
+
*/
|
|
66
|
+
onValuesChangeDeferred?: (values: M, controller: FormexController<M>) => void;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Called when the form is reset.
|
|
70
|
+
*/
|
|
71
|
+
onReset?: () => void;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validate that a field value is unique.
|
|
75
|
+
* The EntityFormBinding wrapper provides this via the data layer.
|
|
76
|
+
*/
|
|
77
|
+
uniqueFieldValidator?: (params: { name: string; value: unknown }) => Promise<boolean>;
|
|
78
|
+
|
|
79
|
+
// --- Slots (replace useSlot) ---
|
|
80
|
+
|
|
81
|
+
/** Content rendered before the form fields */
|
|
82
|
+
beforeFields?: React.ReactNode;
|
|
83
|
+
/** Content rendered after the form fields */
|
|
84
|
+
afterFields?: React.ReactNode;
|
|
85
|
+
/** Plugin-provided action elements */
|
|
86
|
+
pluginActions?: React.ReactNode[];
|
|
87
|
+
|
|
88
|
+
// --- Local changes (managed externally in binding) ---
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Initial values for the form, after applying local changes.
|
|
92
|
+
* If not provided, computed from entity + collection defaults.
|
|
93
|
+
*/
|
|
94
|
+
computedInitialValues?: Partial<M>;
|
|
95
|
+
/**
|
|
96
|
+
* Whether there are unsaved local changes in the cache.
|
|
97
|
+
*/
|
|
98
|
+
hasLocalChanges?: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* The local changes data for the manual-apply menu.
|
|
101
|
+
*/
|
|
102
|
+
localChangesData?: Partial<M>;
|
|
103
|
+
/**
|
|
104
|
+
* Whether manual-apply local changes mode is enabled.
|
|
105
|
+
*/
|
|
106
|
+
manualApplyLocalChanges?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Cache key for the local changes menu.
|
|
109
|
+
*/
|
|
110
|
+
localChangesCacheKey?: string;
|
|
111
|
+
/**
|
|
112
|
+
* Callback when local changes are cleared.
|
|
113
|
+
*/
|
|
114
|
+
onClearLocalChanges?: () => void;
|
|
47
115
|
};
|
|
48
116
|
|
|
49
117
|
export type OnUpdateParams = {
|