@rebasepro/admin 0.11.1-canary.gfd39654 → 0.12.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/README.md +1 -1
- package/dist/{CollectionEditorDialog-o7F_zbGY.js → CollectionEditorDialog-Dpc75wIZ.js} +56 -260
- package/dist/CollectionEditorDialog-Dpc75wIZ.js.map +1 -0
- package/dist/{PropertyEditView-DgB2b22q.js → PropertyEditView-DH3XZC2x.js} +2 -2
- package/dist/{PropertyEditView-DgB2b22q.js.map → PropertyEditView-DH3XZC2x.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-DaLRgPkj.js → RouterCollectionsStudioView-BZLq4MGp.js} +4 -4
- package/dist/{RouterCollectionsStudioView-DaLRgPkj.js.map → RouterCollectionsStudioView-BZLq4MGp.js.map} +1 -1
- package/dist/collection_editor/CollectionEditorDialogsContext.d.ts +1 -1
- package/dist/collection_editor/_cms_internals.d.ts +1 -1
- package/dist/collection_editor/extensibility_types.d.ts +1 -1
- package/dist/collection_editor/serializable_types.d.ts +40 -12
- package/dist/collection_editor/serializable_utils.d.ts +1 -1
- package/dist/collection_editor/ui/collection_editor/CollectionRLSTab.d.ts +1 -11
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/CollectionEditorDialogs.d.ts +2 -2
- package/dist/components/CollectionTableBinding/column_utils.d.ts +7 -1
- package/dist/components/CollectionViewBinding/CollectionListViewBinding.d.ts +2 -2
- package/dist/components/DefaultDrawer.d.ts +3 -3
- package/dist/components/DrawerNavigationGroup.d.ts +8 -1
- package/dist/components/DrawerNavigationItem.d.ts +7 -1
- package/dist/components/EditViewBinding.d.ts +1 -1
- package/dist/components/RebaseAdmin.d.ts +1 -1
- package/dist/components/RebaseAuthGate.d.ts +1 -1
- package/dist/components/RebaseLayout.d.ts +5 -1
- package/dist/components/RebaseNavigation.d.ts +2 -2
- package/dist/components/RebaseRouteDefs.d.ts +1 -1
- package/dist/components/RebaseShell.d.ts +6 -1
- package/dist/components/RelationSelector.d.ts +14 -0
- package/dist/components/SelectableTable/filters/RelationFilterField.d.ts +27 -0
- package/dist/components/SelectableTable/filters/null_filter.d.ts +46 -0
- package/dist/components/SidePanelProvider.d.ts +2 -2
- package/dist/components/app/Scaffold.d.ts +11 -0
- package/dist/components/field_configs.d.ts +1 -1
- package/dist/{export-BvqhjdEb.js → export-BzIpUdSK.js} +3 -3
- package/dist/export-BzIpUdSK.js.map +1 -0
- package/dist/form/EntityForm.d.ts +2 -2
- package/dist/form/EntityFormBinding.d.ts +5 -5
- package/dist/{history-Dz20hH38.js → history-g9688IaK.js} +2 -2
- package/dist/{history-Dz20hH38.js.map → history-g9688IaK.js.map} +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/useAdminContext.d.ts +29 -0
- package/dist/hooks/useHistory.d.ts +7 -11
- package/dist/{import-DINSB-Vu.js → import-BjUG-cIY.js} +2 -2
- package/dist/{import-DINSB-Vu.js.map → import-BjUG-cIY.js.map} +1 -1
- package/dist/index.js +61 -38
- package/dist/index.js.map +1 -1
- package/dist/types/components/EntityFormProps.d.ts +1 -1
- package/dist/types/fields.d.ts +1 -1
- package/dist/{util-BhhfHY1M.js → util-B5fJm1FA.js} +296 -169
- package/dist/util-B5fJm1FA.js.map +1 -0
- package/package.json +9 -9
- package/src/collection_editor/CollectionEditorDialogsContext.tsx +1 -1
- package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
- package/src/collection_editor/_cms_internals.ts +1 -1
- package/src/collection_editor/extensibility_types.ts +1 -1
- package/src/collection_editor/index.ts +2 -2
- package/src/collection_editor/serializable_types.ts +44 -13
- package/src/collection_editor/serializable_utils.ts +68 -59
- package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +66 -41
- package/src/collection_editor/ui/collection_editor/EntityActionsEditTab.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/SubcollectionsEditTab.tsx +2 -2
- package/src/components/CollectionEditorDialogs.tsx +2 -2
- package/src/components/CollectionTableBinding/CollectionRowActions.tsx +5 -4
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +8 -2
- package/src/components/CollectionTableBinding/column_utils.tsx +19 -5
- package/src/components/CollectionTableBinding/table_bindings.tsx +10 -10
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +8 -7
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +2 -2
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +2 -2
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +2 -2
- package/src/components/DefaultDrawer.tsx +19 -8
- package/src/components/DeleteEntityDialog.tsx +2 -2
- package/src/components/DetailViewBinding.tsx +21 -1
- package/src/components/DrawerNavigationGroup.tsx +47 -4
- package/src/components/DrawerNavigationItem.tsx +19 -5
- package/src/components/EditFormActions.tsx +4 -3
- package/src/components/EditViewBinding.tsx +1 -1
- package/src/components/HomePage/ContentHomePage.tsx +4 -4
- package/src/components/RebaseAdmin.tsx +3 -3
- package/src/components/RebaseAuthGate.tsx +1 -1
- package/src/components/RebaseLayout.tsx +8 -2
- package/src/components/RebaseNavigation.tsx +9 -9
- package/src/components/RebaseRouteDefs.tsx +2 -2
- package/src/components/RebaseShell.tsx +10 -1
- package/src/components/RelationSelector.tsx +18 -3
- package/src/components/SelectableTable/filters/FilterFieldBinding.tsx +1 -1
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +36 -9
- package/src/components/SelectableTable/filters/RelationFilterField.tsx +93 -17
- package/src/components/SelectableTable/filters/null_filter.ts +63 -0
- package/src/components/SidePanelProvider.tsx +2 -2
- package/src/components/app/Scaffold.tsx +16 -1
- package/src/data_export/export/ExportCollectionAction.tsx +2 -2
- package/src/form/EntityForm.tsx +2 -2
- package/src/form/EntityFormActions.tsx +2 -1
- package/src/form/EntityFormBinding.tsx +7 -7
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +3 -3
- package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +16 -5
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +3 -3
- package/src/form/field_bindings/RelationFieldBinding.tsx +17 -6
- package/src/form/field_bindings/RepeatFieldBinding.tsx +2 -2
- package/src/hooks/index.ts +1 -1
- package/src/hooks/navigation/useNavigationResolution.ts +10 -4
- package/src/hooks/{useCMSContext.tsx → useAdminContext.tsx} +10 -10
- package/src/hooks/useHistory.ts +7 -11
- package/src/hooks/useResolvedNavigationFrom.tsx +2 -2
- package/src/index.ts +4 -4
- package/src/preview/PropertyPreview.tsx +6 -6
- package/src/preview/components/RelationPreview.tsx +1 -1
- package/src/preview/property_previews/ArrayOfMapsPreview.tsx +1 -1
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +2 -2
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +2 -2
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +2 -2
- package/src/types/components/EntityFormProps.tsx +2 -2
- package/src/types/fields.tsx +1 -1
- package/dist/CollectionEditorDialog-o7F_zbGY.js.map +0 -1
- package/dist/collection_editor/pgColumnToProperty.d.ts +0 -7
- package/dist/editor/extensions/Image/index.d.ts +0 -6
- package/dist/export-BvqhjdEb.js.map +0 -1
- package/dist/hooks/useCMSContext.d.ts +0 -29
- package/dist/util-BhhfHY1M.js.map +0 -1
- package/src/collection_editor/pgColumnToProperty.ts +0 -291
- package/src/editor/extensions/Image/index.ts +0 -133
|
@@ -18,6 +18,18 @@ export interface ScaffoldProps {
|
|
|
18
18
|
*/
|
|
19
19
|
autoOpenDrawer?: boolean;
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Start with the drawer expanded rather than collapsed to icons.
|
|
23
|
+
*
|
|
24
|
+
* Distinct from {@link autoOpenDrawer}, which is a hover behaviour: this one
|
|
25
|
+
* only seeds the initial state, so the user's own toggle still wins for the
|
|
26
|
+
* rest of the session.
|
|
27
|
+
*
|
|
28
|
+
* Ignored on small layouts, where an expanded drawer covers the content it is
|
|
29
|
+
* meant to navigate.
|
|
30
|
+
*/
|
|
31
|
+
defaultDrawerOpen?: boolean;
|
|
32
|
+
|
|
21
33
|
/**
|
|
22
34
|
* Logo to be displayed in the top bar and drawer.
|
|
23
35
|
* Note that this has no effect if you are using a custom AppBar or Drawer.
|
|
@@ -51,6 +63,7 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
|
|
|
51
63
|
const {
|
|
52
64
|
children,
|
|
53
65
|
autoOpenDrawer,
|
|
66
|
+
defaultDrawerOpen = false,
|
|
54
67
|
logo,
|
|
55
68
|
className,
|
|
56
69
|
style,
|
|
@@ -70,7 +83,9 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
|
|
|
70
83
|
const includeDrawer = drawerChildren.length > 0;
|
|
71
84
|
const largeLayout = useLargeLayout();
|
|
72
85
|
|
|
73
|
-
|
|
86
|
+
// Seeded once, deliberately: a `useEffect` syncing this to the prop would
|
|
87
|
+
// re-expand the drawer under a user who had just collapsed it.
|
|
88
|
+
const [drawerOpen, setDrawerOpen] = React.useState(defaultDrawerOpen && largeLayout);
|
|
74
89
|
const [onHover, setOnHover] = React.useState(false);
|
|
75
90
|
|
|
76
91
|
const setOnHoverTrue = useCallback(() => setOnHover(true), []);
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
useData,
|
|
8
8
|
useTranslation
|
|
9
9
|
} from "@rebasepro/app";
|
|
10
|
-
import {
|
|
10
|
+
import { useAdminContext } from "../../hooks";
|
|
11
11
|
import { Entity, User } from "@rebasepro/types";
|
|
12
12
|
import { CollectionActionsProps, ExportConfig, RebaseContext, AdminCollection } from "@rebasepro/admin-types";
|
|
13
13
|
import { getDefaultValuesFor } from "@rebasepro/common";
|
|
@@ -56,7 +56,7 @@ export function ExportCollectionAction<M extends Record<string, unknown>, USER e
|
|
|
56
56
|
const [exportType, setExportType] = React.useState<"csv" | "json">("csv");
|
|
57
57
|
const [dateExportType, setDateExportType] = React.useState<"timestamp" | "string">("string");
|
|
58
58
|
|
|
59
|
-
const context =
|
|
59
|
+
const context = useAdminContext<USER>();
|
|
60
60
|
const dataClient = useData();
|
|
61
61
|
|
|
62
62
|
const canExport = !exportAllowed || exportAllowed({
|
package/src/form/EntityForm.tsx
CHANGED
|
@@ -39,9 +39,9 @@ import {
|
|
|
39
39
|
/**
|
|
40
40
|
* Headless entity form component.
|
|
41
41
|
*
|
|
42
|
-
* Renders a form for a entity collection without any
|
|
42
|
+
* Renders a form for a entity collection without any admin or backend dependencies.
|
|
43
43
|
* All backend concerns (save, caching, analytics, plugin slots) are provided via
|
|
44
|
-
* callback props. For
|
|
44
|
+
* callback props. For admin-connected usage, use {@link EntityFormBinding} instead.
|
|
45
45
|
*
|
|
46
46
|
* @group Components
|
|
47
47
|
*/
|
|
@@ -5,6 +5,7 @@ import type { EntityAction, AdminCollection } from "@rebasepro/admin-types";
|
|
|
5
5
|
import React from "react";
|
|
6
6
|
import { Entity } from "@rebasepro/types";
|
|
7
7
|
import type { EntityFormActionsProps } from "../types/components/EntityFormActionsProps";
|
|
8
|
+
import { getIcon } from "@rebasepro/app";
|
|
8
9
|
import {
|
|
9
10
|
AlertCircleIcon,
|
|
10
11
|
Button,
|
|
@@ -136,7 +137,7 @@ function buildBottomActions<M extends Record<string, unknown>>({
|
|
|
136
137
|
formContext
|
|
137
138
|
});
|
|
138
139
|
}}>
|
|
139
|
-
{action.icon}
|
|
140
|
+
{getIcon(action.icon, undefined, undefined, "smallest")}
|
|
140
141
|
</IconButton>
|
|
141
142
|
))}
|
|
142
143
|
</div>}
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
|
|
31
31
|
import { useCollectionRegistryController } from "../hooks/navigation/contexts/CollectionRegistryContext";
|
|
32
32
|
import { useSidePanel } from "../hooks/useSidePanel";
|
|
33
|
-
import {
|
|
33
|
+
import { useAdminContext } from "../hooks/useAdminContext";
|
|
34
34
|
import { EntityForm } from "./EntityForm";
|
|
35
35
|
import {
|
|
36
36
|
extractTouchedValues,
|
|
@@ -41,9 +41,9 @@ import {
|
|
|
41
41
|
import { mergeDeep } from "@rebasepro/utils";
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* Props for the
|
|
44
|
+
* Props for the admin-connected EntityFormBinding wrapper.
|
|
45
45
|
* Extends EntityFormProps — you can pass all the same props as EntityForm,
|
|
46
|
-
* but the binding provides
|
|
46
|
+
* but the binding provides admin context (save, caching, analytics, plugin slots)
|
|
47
47
|
* automatically.
|
|
48
48
|
*/
|
|
49
49
|
export type EntityFormBindingProps<M extends Record<string, unknown>> = Omit<EntityFormProps<M>,
|
|
@@ -63,7 +63,7 @@ export type EntityFormBindingProps<M extends Record<string, unknown>> = Omit<Ent
|
|
|
63
63
|
>;
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
*
|
|
66
|
+
* admin-connected wrapper around EntityForm.
|
|
67
67
|
*
|
|
68
68
|
* Provides all backend concerns that the headless EntityForm delegates via callbacks:
|
|
69
69
|
* - Save via the data layer (`saveEntityWithCallbacks`)
|
|
@@ -74,8 +74,8 @@ export type EntityFormBindingProps<M extends Record<string, unknown>> = Omit<Ent
|
|
|
74
74
|
* - Analytics events
|
|
75
75
|
* - Navigation via side entity controller
|
|
76
76
|
*
|
|
77
|
-
* Use this component inside the
|
|
78
|
-
* For headless usage outside the
|
|
77
|
+
* Use this component inside the admin (`<RebaseShell>`).
|
|
78
|
+
* For headless usage outside the admin, use `EntityForm` directly.
|
|
79
79
|
*
|
|
80
80
|
* @group Components
|
|
81
81
|
*/
|
|
@@ -102,7 +102,7 @@ export function EntityFormBinding<M extends Record<string, unknown>>({
|
|
|
102
102
|
const dataClient = useData();
|
|
103
103
|
const snackbarController = useSnackbarController();
|
|
104
104
|
const customizationController = useCustomizationController();
|
|
105
|
-
const context =
|
|
105
|
+
const context = useAdminContext();
|
|
106
106
|
const analyticsController = useAnalyticsController();
|
|
107
107
|
|
|
108
108
|
const [status, setStatus] = useState<EntityStatus>(initialStatus);
|
|
@@ -77,7 +77,7 @@ export function ArrayOfReferencesFieldBinding({
|
|
|
77
77
|
collection,
|
|
78
78
|
onMultipleEntitiesSelected,
|
|
79
79
|
selectedEntityIds,
|
|
80
|
-
fixedFilter: ofProperty.fixedFilter
|
|
80
|
+
fixedFilter: ofProperty.admin?.fixedFilter
|
|
81
81
|
}
|
|
82
82
|
);
|
|
83
83
|
|
|
@@ -104,8 +104,8 @@ export function ArrayOfReferencesFieldBinding({
|
|
|
104
104
|
onClick={onEntryClick}
|
|
105
105
|
hover={!disabled}
|
|
106
106
|
reference={entryValue}
|
|
107
|
-
includeId={ofProperty.includeId}
|
|
108
|
-
includeEntityLink={ofProperty.includeEntityLink}
|
|
107
|
+
includeId={ofProperty.admin?.includeId}
|
|
108
|
+
includeEntityLink={ofProperty.admin?.includeEntityLink}
|
|
109
109
|
/>
|
|
110
110
|
);
|
|
111
111
|
}, [ofProperty.path, ofProperty.admin?.previewProperties, value]);
|
|
@@ -43,8 +43,19 @@ export function MultipleRelationFieldBinding({
|
|
|
43
43
|
|
|
44
44
|
const parentCollection = context.collection;
|
|
45
45
|
const capabilities = parentCollection ? getDataSourceCapabilities(parentCollection.engine) : undefined;
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
// Only two things have to hold here: there is a collection, and its engine
|
|
47
|
+
// has relations at all. Whether *this* property's relation can be resolved
|
|
48
|
+
// is `resolveRelationProperty`'s question, and it answers it across all
|
|
49
|
+
// three forms — a pre-stamped `resolvedRelation`, an inline `relation`, or
|
|
50
|
+
// a lookup by name in the collection's `relations` array — with an error
|
|
51
|
+
// naming the property and the collection when it cannot.
|
|
52
|
+
//
|
|
53
|
+
// This used to demand the `relations` array as well, which the inline form
|
|
54
|
+
// does not produce. That is the documented way to declare a relation, so
|
|
55
|
+
// every collection using it threw here and rendered the error boundary
|
|
56
|
+
// instead of the field.
|
|
57
|
+
if (!parentCollection || !capabilities?.supportsRelations) {
|
|
58
|
+
throw Error("MultipleRelationFieldBinding expected a collection whose engine supports relations");
|
|
48
59
|
}
|
|
49
60
|
const resolvedProperty = resolveRelationProperty(property, parentCollection, propertyKey)
|
|
50
61
|
const relation = resolvedProperty;
|
|
@@ -66,7 +77,7 @@ export function MultipleRelationFieldBinding({
|
|
|
66
77
|
collection,
|
|
67
78
|
onMultipleEntitiesSelected,
|
|
68
79
|
selectedEntityIds,
|
|
69
|
-
fixedFilter: property.fixedFilter
|
|
80
|
+
fixedFilter: property.admin?.fixedFilter
|
|
70
81
|
}
|
|
71
82
|
);
|
|
72
83
|
|
|
@@ -92,8 +103,8 @@ export function MultipleRelationFieldBinding({
|
|
|
92
103
|
onClick={onEntryClick}
|
|
93
104
|
hover={!disabled}
|
|
94
105
|
relation={entryValue}
|
|
95
|
-
includeId={property.includeId}
|
|
96
|
-
includeEntityLink={property.includeEntityLink}
|
|
106
|
+
includeId={property.admin?.includeId}
|
|
107
|
+
includeEntityLink={property.admin?.includeEntityLink}
|
|
97
108
|
/>
|
|
98
109
|
);
|
|
99
110
|
}, [relation, property.admin?.previewProperties, value]);
|
|
@@ -85,7 +85,7 @@ function ReferenceFieldBindingInternal({
|
|
|
85
85
|
collection,
|
|
86
86
|
onSingleEntitySelected,
|
|
87
87
|
selectedEntityIds: validValue && refValue ? [refValue.id] : undefined,
|
|
88
|
-
fixedFilter: property.fixedFilter
|
|
88
|
+
fixedFilter: property.admin?.fixedFilter
|
|
89
89
|
}
|
|
90
90
|
);
|
|
91
91
|
|
|
@@ -115,8 +115,8 @@ function ReferenceFieldBindingInternal({
|
|
|
115
115
|
size={size}
|
|
116
116
|
onClick={disabled || isSubmitting ? undefined : onEntryClick}
|
|
117
117
|
reference={refValue}
|
|
118
|
-
includeEntityLink={property.includeEntityLink}
|
|
119
|
-
includeId={property.includeId}
|
|
118
|
+
includeEntityLink={property.admin?.includeEntityLink}
|
|
119
|
+
includeId={property.admin?.includeId}
|
|
120
120
|
/>}
|
|
121
121
|
|
|
122
122
|
{!refValue && <div className="justify-center text-left">
|
|
@@ -22,8 +22,19 @@ export function RelationFieldBinding(props: FieldProps<RelationProperty>) {
|
|
|
22
22
|
}
|
|
23
23
|
const collection = context.collection;
|
|
24
24
|
const capabilities = collection ? getDataSourceCapabilities(collection.engine) : undefined;
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// Only two things have to hold here: there is a collection, and its engine
|
|
26
|
+
// has relations at all. Whether *this* property's relation can be resolved
|
|
27
|
+
// is `resolveRelationProperty`'s question, and it answers it across all
|
|
28
|
+
// three forms — a pre-stamped `resolvedRelation`, an inline `relation`, or
|
|
29
|
+
// a lookup by name in the collection's `relations` array — with an error
|
|
30
|
+
// naming the property and the collection when it cannot.
|
|
31
|
+
//
|
|
32
|
+
// This used to demand the `relations` array as well, which the inline form
|
|
33
|
+
// does not produce. That is the documented way to declare a relation, so
|
|
34
|
+
// every collection using it threw here and rendered the error boundary
|
|
35
|
+
// instead of the field.
|
|
36
|
+
if (!collection || !capabilities?.supportsRelations) {
|
|
37
|
+
throw Error("RelationFieldBinding expected a collection whose engine supports relations");
|
|
27
38
|
}
|
|
28
39
|
const resolvedProperty = resolveRelationProperty(property, collection, propertyKey);
|
|
29
40
|
const relation = resolvedProperty;
|
|
@@ -82,7 +93,7 @@ function RelationSelectorBinding({
|
|
|
82
93
|
}
|
|
83
94
|
}}
|
|
84
95
|
disabled={disabled || isSubmitting}
|
|
85
|
-
fixedFilter={property.fixedFilter}
|
|
96
|
+
fixedFilter={property.admin?.fixedFilter}
|
|
86
97
|
size={selectorSize}
|
|
87
98
|
/>
|
|
88
99
|
|
|
@@ -127,7 +138,7 @@ function SingleRelationFieldBinding({
|
|
|
127
138
|
collection,
|
|
128
139
|
onSingleEntitySelected,
|
|
129
140
|
selectedEntityIds: validValue && normalizedValue ? [normalizedValue.id] : undefined,
|
|
130
|
-
fixedFilter: property.fixedFilter
|
|
141
|
+
fixedFilter: property.admin?.fixedFilter
|
|
131
142
|
});
|
|
132
143
|
|
|
133
144
|
const onEntryClick = (e: React.SyntheticEvent) => {
|
|
@@ -158,8 +169,8 @@ function SingleRelationFieldBinding({
|
|
|
158
169
|
size={size}
|
|
159
170
|
onClick={disabled || isSubmitting ? undefined : onEntryClick}
|
|
160
171
|
relation={usedRelation}
|
|
161
|
-
includeEntityLink={property.includeEntityLink}
|
|
162
|
-
includeId={property.includeId}
|
|
172
|
+
includeEntityLink={property.admin?.includeEntityLink}
|
|
173
|
+
includeId={property.admin?.includeId}
|
|
163
174
|
/>}
|
|
164
175
|
|
|
165
176
|
{!value && <div className="justify-center text-left">
|
|
@@ -92,8 +92,8 @@ export function RepeatFieldBinding({
|
|
|
92
92
|
</ErrorBoundary>;
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
-
const canAddElements = !property.admin?.disabled && !isSubmitting && !disabled && (property.canAddElements || property.canAddElements === undefined);
|
|
96
|
-
const sortable = property.sortable === undefined ? true : property.sortable;
|
|
95
|
+
const canAddElements = !property.admin?.disabled && !isSubmitting && !disabled && (property.admin?.canAddElements || property.admin?.canAddElements === undefined);
|
|
96
|
+
const sortable = property.admin?.sortable === undefined ? true : property.admin.sortable;
|
|
97
97
|
const arrayContainer = <ArrayContainer droppableId={propertyKey}
|
|
98
98
|
addLabel={property.name ? t("add_to_field", { fieldName: property.name }) : t("add_entry")}
|
|
99
99
|
value={(value as unknown[]) ?? []}
|
package/src/hooks/index.ts
CHANGED
|
@@ -13,4 +13,4 @@ export * from "./navigation/useResolvedViews";
|
|
|
13
13
|
export * from "./navigation/useTopLevelNavigation";
|
|
14
14
|
export * from "./useSideDialogsController";
|
|
15
15
|
export * from "./useBreadcrumbsController";
|
|
16
|
-
export * from "./
|
|
16
|
+
export * from "./useAdminContext";
|
|
@@ -91,8 +91,14 @@ function injectAuthCollectionConfig(
|
|
|
91
91
|
if (actionToInject) {
|
|
92
92
|
const injectedAction = actionToInject;
|
|
93
93
|
const existing = result.entityActions ?? [];
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
// An entry is either the action or the key of an app-level one, and a
|
|
95
|
+
// collection that names `"reset_password"` already has this action — so
|
|
96
|
+
// the string form has to count here too, or the reset action is injected
|
|
97
|
+
// a second time and the user gets two identical buttons.
|
|
98
|
+
const alreadyHas = existing.some((a) =>
|
|
99
|
+
typeof a === "string"
|
|
100
|
+
? a === injectedAction.key
|
|
101
|
+
: a.key != null && a.key === injectedAction.key
|
|
96
102
|
);
|
|
97
103
|
if (!alreadyHas) {
|
|
98
104
|
result = {
|
|
@@ -238,13 +244,13 @@ export async function resolveAppViews(
|
|
|
238
244
|
if (count > 1) {
|
|
239
245
|
console.warn(
|
|
240
246
|
`[Rebase] Duplicate view slug "${slug}" detected (${count} views). ` +
|
|
241
|
-
`Last-write-wins. Ensure unique slugs across
|
|
247
|
+
`Last-write-wins. Ensure unique slugs across admin views and plugins.`
|
|
242
248
|
);
|
|
243
249
|
}
|
|
244
250
|
});
|
|
245
251
|
}
|
|
246
252
|
|
|
247
|
-
// Filter by roles — applies to
|
|
253
|
+
// Filter by roles — applies to admin, plugin, and builder-returned views
|
|
248
254
|
resolvedViews = filterViewsByRole(resolvedViews, authController);
|
|
249
255
|
|
|
250
256
|
return resolvedViews;
|
|
@@ -25,13 +25,13 @@ import {
|
|
|
25
25
|
} from "./navigation/contexts";
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* The
|
|
29
|
-
* controllers that are only available inside the
|
|
28
|
+
* The admin context extends the core RebaseContext with admin-specific
|
|
29
|
+
* controllers that are only available inside the admin routing tree.
|
|
30
30
|
*
|
|
31
|
-
* Use {@link
|
|
31
|
+
* Use {@link useAdminContext} to obtain an instance.
|
|
32
32
|
* @group Hooks and utilities
|
|
33
33
|
*/
|
|
34
|
-
export type
|
|
34
|
+
export type AdminContext<
|
|
35
35
|
DB = Record<string, unknown>,
|
|
36
36
|
USER extends User = User,
|
|
37
37
|
AuthControllerType extends AuthController<USER> = AuthController<USER>
|
|
@@ -44,20 +44,20 @@ export type CMSContext<
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Hook that builds a fully-populated
|
|
48
|
-
* core {@link RebaseContext} with
|
|
47
|
+
* Hook that builds a fully-populated admin context by combining the
|
|
48
|
+
* core {@link RebaseContext} with admin-specific controllers.
|
|
49
49
|
*
|
|
50
|
-
* Use this instead of `useRebaseContext()` in
|
|
50
|
+
* Use this instead of `useRebaseContext()` in admin components that
|
|
51
51
|
* need to pass context to entity action callbacks, plugin slots,
|
|
52
|
-
* or any consumer that expects
|
|
52
|
+
* or any consumer that expects admin controllers on the context object.
|
|
53
53
|
*
|
|
54
54
|
* @group Hooks and utilities
|
|
55
55
|
*/
|
|
56
|
-
export function
|
|
56
|
+
export function useAdminContext<
|
|
57
57
|
DB = Record<string, unknown>,
|
|
58
58
|
USER extends User = User,
|
|
59
59
|
AuthControllerType extends AuthController<USER> = AuthController<USER>
|
|
60
|
-
>():
|
|
60
|
+
>(): AdminContext<DB, USER, AuthControllerType> {
|
|
61
61
|
const baseContext = useRebaseContext<USER, AuthControllerType>();
|
|
62
62
|
const sidePanelController = useSidePanel();
|
|
63
63
|
const sideDialogsController = useSideDialogsController();
|
package/src/hooks/useHistory.ts
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState, useMemo } from "react";
|
|
2
2
|
import { useApiConfig } from "@rebasepro/app";
|
|
3
|
+
import type { EntityHistoryEntry } from "@rebasepro/types";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
values: Record<string, unknown> | null;
|
|
11
|
-
previous_values: Record<string, unknown> | null;
|
|
12
|
-
updated_by: string | null;
|
|
13
|
-
updated_at: string;
|
|
14
|
-
}
|
|
5
|
+
/**
|
|
6
|
+
* What the history REST endpoint returns. The fourth declaration of this shape
|
|
7
|
+
* — the two drivers had one each and disagreed on `updated_at` — now an alias
|
|
8
|
+
* of the one in `@rebasepro/types`.
|
|
9
|
+
*/
|
|
10
|
+
export type HistoryEntryData = EntityHistoryEntry;
|
|
15
11
|
|
|
16
12
|
export interface UseEntityHistoryResult {
|
|
17
13
|
entries: HistoryEntryData[];
|
|
@@ -4,7 +4,7 @@ import { Entity, User } from "@rebasepro/types";
|
|
|
4
4
|
import { RebaseContext } from "@rebasepro/admin-types";
|
|
5
5
|
import { useEffect, useState, useMemo } from "react";
|
|
6
6
|
import { getNavigationEntriesFromPath } from "@rebasepro/app";
|
|
7
|
-
import {
|
|
7
|
+
import { useAdminContext } from "./useAdminContext";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @see resolveNavigationFrom
|
|
@@ -136,7 +136,7 @@ export function useResolvedNavigationFrom<M extends Record<string, unknown>>(
|
|
|
136
136
|
path
|
|
137
137
|
}: NavigationFromProps): NavigationFrom<M> {
|
|
138
138
|
|
|
139
|
-
const context =
|
|
139
|
+
const context = useAdminContext();
|
|
140
140
|
const { navigationStateController, collectionRegistryController } = context;
|
|
141
141
|
|
|
142
142
|
const [data, setData] = useState<ResolvedNavigationEntry<M>[] | undefined>();
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// admin-specific types take priority over base types when names conflict
|
|
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
|
|
@@ -77,7 +77,7 @@ export {
|
|
|
77
77
|
mergeEntityActions,
|
|
78
78
|
resolveEntityAction,
|
|
79
79
|
resolveEntityView,
|
|
80
|
-
// Property utilities (moved from @rebasepro/app — property-aware logic belongs in
|
|
80
|
+
// Property utilities (moved from @rebasepro/app — property-aware logic belongs in admin)
|
|
81
81
|
isReferenceProperty,
|
|
82
82
|
isRelationProperty,
|
|
83
83
|
getIconForWidget,
|
|
@@ -87,7 +87,7 @@ export {
|
|
|
87
87
|
getBracketNotation,
|
|
88
88
|
getPropertiesWithPropertiesOrder,
|
|
89
89
|
getDefaultPropertiesOrder,
|
|
90
|
-
// Preview utilities (moved from @rebasepro/app — property-aware logic belongs in
|
|
90
|
+
// Preview utilities (moved from @rebasepro/app — property-aware logic belongs in admin)
|
|
91
91
|
getEntityPreviewKeys,
|
|
92
92
|
getEntityTitlePropertyKey,
|
|
93
93
|
getEntityTitlePropertyKeyForEntity
|
|
@@ -102,7 +102,7 @@ export { useCollectionDataController, createStaticDataController } from "./util/
|
|
|
102
102
|
export * from "./data_import";
|
|
103
103
|
export * from "./data_export";
|
|
104
104
|
|
|
105
|
-
// Collection editor — moved from @rebasepro/studio (
|
|
105
|
+
// Collection editor — moved from @rebasepro/studio (admin-dependent visual schema editor)
|
|
106
106
|
export * from "./collection_editor";
|
|
107
107
|
|
|
108
108
|
|
|
@@ -195,8 +195,8 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
195
195
|
content = <ReferencePreview
|
|
196
196
|
disabled={!property.path}
|
|
197
197
|
previewProperties={property.admin?.previewProperties}
|
|
198
|
-
includeId={property.includeId}
|
|
199
|
-
includeEntityLink={property.includeEntityLink}
|
|
198
|
+
includeId={property.admin?.includeId}
|
|
199
|
+
includeEntityLink={property.admin?.includeEntityLink}
|
|
200
200
|
size={props.size}
|
|
201
201
|
reference={value as EntityReference}
|
|
202
202
|
textOnly={props.textOnly}
|
|
@@ -224,8 +224,8 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
224
224
|
<RelationPreview
|
|
225
225
|
disabled={!property.relation}
|
|
226
226
|
previewProperties={property.admin?.previewProperties}
|
|
227
|
-
includeId={property.includeId}
|
|
228
|
-
includeEntityLink={property.includeEntityLink}
|
|
227
|
+
includeId={property.admin?.includeId}
|
|
228
|
+
includeEntityLink={property.admin?.includeEntityLink}
|
|
229
229
|
size={"small"}
|
|
230
230
|
relation={entityRelation}
|
|
231
231
|
textOnly={props.textOnly}
|
|
@@ -242,8 +242,8 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
242
242
|
content = <RelationPreview
|
|
243
243
|
disabled={!property.relation}
|
|
244
244
|
previewProperties={property.admin?.previewProperties}
|
|
245
|
-
includeId={property.includeId}
|
|
246
|
-
includeEntityLink={property.includeEntityLink}
|
|
245
|
+
includeId={property.admin?.includeId}
|
|
246
|
+
includeEntityLink={property.admin?.includeEntityLink}
|
|
247
247
|
size={props.size}
|
|
248
248
|
relation={relationValue}
|
|
249
249
|
textOnly={props.textOnly}
|
|
@@ -158,7 +158,7 @@ function RelationPreviewExisting<M extends Record<string, unknown> = Record<stri
|
|
|
158
158
|
collection: AdminCollection<M>
|
|
159
159
|
}) {
|
|
160
160
|
|
|
161
|
-
//
|
|
161
|
+
// admin wire format embeds relation data as { id, path, values }
|
|
162
162
|
const passedEntity = relation.data as Entity<M> | undefined;
|
|
163
163
|
const ResolvedEntityPreview = useComponentOverride("EntityPreview", EntityPreviewBinding);
|
|
164
164
|
const customizationController = useCustomizationController();
|
|
@@ -28,7 +28,7 @@ export function ArrayOfMapsPreview({
|
|
|
28
28
|
throw Error(`You need to specify a 'properties' prop (or specify a custom field) in your map property '${propertyKey}'${mapProperty.name ? ` ("${mapProperty.name}")` : ""}`);
|
|
29
29
|
}
|
|
30
30
|
const values = value as Record<string, unknown>[];
|
|
31
|
-
const previewProperties: string[] | undefined = (mapProperty as MapProperty).previewProperties;
|
|
31
|
+
const previewProperties: string[] | undefined = (mapProperty as MapProperty).admin?.previewProperties;
|
|
32
32
|
|
|
33
33
|
if (!values) return null;
|
|
34
34
|
|
|
@@ -33,8 +33,8 @@ export function ArrayOfReferencesPreview({
|
|
|
33
33
|
previewProperties={ofProperty.admin?.previewProperties}
|
|
34
34
|
size={childSize}
|
|
35
35
|
reference={reference}
|
|
36
|
-
includeId={ofProperty.includeId}
|
|
37
|
-
includeEntityLink={ofProperty.includeEntityLink}
|
|
36
|
+
includeId={ofProperty.admin?.includeId}
|
|
37
|
+
includeEntityLink={ofProperty.admin?.includeEntityLink}
|
|
38
38
|
/>
|
|
39
39
|
</div>;
|
|
40
40
|
}
|
|
@@ -39,8 +39,8 @@ export function ArrayOfRelationsPreview({
|
|
|
39
39
|
previewProperties={ofProperty.admin?.previewProperties}
|
|
40
40
|
size={"small"}
|
|
41
41
|
relation={entityRelation}
|
|
42
|
-
includeId={ofProperty.includeId}
|
|
43
|
-
includeEntityLink={ofProperty.includeEntityLink}
|
|
42
|
+
includeId={ofProperty.admin?.includeId}
|
|
43
|
+
includeEntityLink={ofProperty.admin?.includeEntityLink}
|
|
44
44
|
/>
|
|
45
45
|
</div>
|
|
46
46
|
);
|
|
@@ -41,7 +41,7 @@ export function SkeletonPropertyComponent({
|
|
|
41
41
|
content = <>{arrayProperty.of.map((p, i) => renderGenericArrayCell(p, i))} </>;
|
|
42
42
|
} else {
|
|
43
43
|
if (arrayProperty.of.type === "map" && arrayProperty.of.properties) {
|
|
44
|
-
content = renderArrayOfMaps(arrayProperty.of.properties, size, arrayProperty.of.previewProperties);
|
|
44
|
+
content = renderArrayOfMaps(arrayProperty.of.properties, size, arrayProperty.of.admin?.previewProperties);
|
|
45
45
|
} else if (arrayProperty.of.type === "string") {
|
|
46
46
|
if (arrayProperty.of.enum) {
|
|
47
47
|
content = renderArrayEnumTableCell();
|
|
@@ -79,7 +79,7 @@ function renderMap<T extends Record<string, any>>(property: MapProperty, size: P
|
|
|
79
79
|
if (size === "large") {
|
|
80
80
|
mapPropertyKeys = Object.keys(property.properties);
|
|
81
81
|
} else {
|
|
82
|
-
mapPropertyKeys = (property.previewProperties || Object.keys(property.properties)) as string[];
|
|
82
|
+
mapPropertyKeys = (property.admin?.previewProperties || Object.keys(property.properties)) as string[];
|
|
83
83
|
if (size === "medium")
|
|
84
84
|
mapPropertyKeys = mapPropertyKeys.slice(0, 3);
|
|
85
85
|
else if (size === "small")
|
|
@@ -7,7 +7,7 @@ import { EntityFormActionsProps } from "./EntityFormActionsProps";
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Props for the headless EntityForm component.
|
|
10
|
-
* This form can be used without
|
|
10
|
+
* This form can be used without admin context — all backend concerns
|
|
11
11
|
* (save, caching, analytics, plugin slots) are provided via callbacks.
|
|
12
12
|
*/
|
|
13
13
|
export type EntityFormProps<M extends Record<string, unknown>> = {
|
|
@@ -51,7 +51,7 @@ export type EntityFormProps<M extends Record<string, unknown>> = {
|
|
|
51
51
|
|
|
52
52
|
children?: React.ReactNode;
|
|
53
53
|
|
|
54
|
-
// --- Headless callbacks (replace internal
|
|
54
|
+
// --- Headless callbacks (replace internal admin hooks) ---
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Called on form submit with the values to save.
|
package/src/types/fields.tsx
CHANGED
|
@@ -243,7 +243,7 @@ export interface PropertyFieldBindingProps<M extends Record<string, unknown> = R
|
|
|
243
243
|
propertyKey: string;
|
|
244
244
|
|
|
245
245
|
/**
|
|
246
|
-
* The
|
|
246
|
+
* The admin property you are binding this field to
|
|
247
247
|
*/
|
|
248
248
|
property: Property;
|
|
249
249
|
|