@rebasepro/admin 0.6.1 → 0.7.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-iGgS4rLX.js} +233 -152
- package/dist/CollectionEditorDialog-iGgS4rLX.js.map +1 -0
- package/dist/{PropertyEditView-C2wp8kQ9.js → PropertyEditView-C9yhLyja.js} +129 -15
- package/dist/PropertyEditView-C9yhLyja.js.map +1 -0
- package/dist/{CollectionsStudioView-B1mN33GB.js → RouterCollectionsStudioView-Dqx27GD8.js} +61 -39
- package/dist/RouterCollectionsStudioView-Dqx27GD8.js.map +1 -0
- 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 +374 -0
- package/dist/collection_editor/serializable_utils.d.ts +50 -0
- package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +2 -1
- 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/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/form/EntityForm.d.ts +10 -9
- package/dist/form/EntityFormBinding.d.ts +27 -0
- package/dist/form/form_utils.d.ts +8 -0
- package/dist/form/index.d.ts +5 -1
- package/dist/{history-BO6VT85a.js → history-ChHgyyvM.js} +2 -2
- package/dist/{history-BO6VT85a.js.map → history-ChHgyyvM.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 +2 -0
- package/dist/index.js +2 -2
- package/dist/routes/CustomViewRoute.d.ts +2 -1
- package/dist/{src-BYniefVu.js → src-DOX2nOyh.js} +1586 -1283
- package/dist/src-DOX2nOyh.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/fields.d.ts +6 -1
- 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 +18 -18
- package/src/collection_editor/extensibility_types.ts +168 -0
- package/src/collection_editor/index.ts +46 -0
- package/src/collection_editor/serializable_types.ts +487 -0
- package/src/collection_editor/serializable_utils.ts +588 -0
- package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +4 -4
- package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +134 -70
- 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 +155 -10
- 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/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 +13 -3
- package/src/components/DrawerNavigationGroup.tsx +2 -2
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +12 -11
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +40 -190
- package/src/components/EntityCollectionView/EntityCollectionListView.tsx +54 -183
- package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +6 -6
- package/src/components/EntityEditView.tsx +5 -4
- package/src/components/EntityEditViewFormActions.tsx +32 -26
- package/src/components/HomePage/ContentHomePage.tsx +13 -4
- package/src/components/RebaseCMS.tsx +3 -2
- package/src/components/RebaseNavigation.tsx +71 -7
- package/src/components/SelectableTable/SelectableTable.tsx +2 -2
- package/src/components/SideDialogs.tsx +0 -7
- package/src/components/common/default_entity_actions.tsx +9 -9
- package/src/contexts/BreacrumbsContext.tsx +15 -6
- package/src/editor/selectors/ai-selector.tsx +0 -1
- package/src/form/EntityForm.tsx +191 -428
- package/src/form/EntityFormActions.tsx +33 -47
- package/src/form/EntityFormBinding.tsx +323 -0
- package/src/form/components/FormLayout.tsx +1 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
- package/src/form/form_utils.ts +163 -0
- package/src/form/index.tsx +14 -5
- 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 +5 -0
- 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/fields.tsx +7 -1
- package/src/util/dataControllerAdapter.ts +93 -0
- package/src/util/navigation_utils.ts +1 -1
- package/src/util/propertyConfigMapper.ts +134 -0
- package/LICENSE +0 -21
- 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/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/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/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/hooks/navigation/useNavigationRegistry.ts +0 -164
- package/src/hooks/navigation/useNavigationURLs.ts +0 -56
|
@@ -123,7 +123,7 @@ type ActionsViewProps<M extends Record<string, unknown>> = {
|
|
|
123
123
|
status: "new" | "existing" | "copy",
|
|
124
124
|
sideDialogContext: SideDialogController,
|
|
125
125
|
pluginActions?: React.ReactNode[],
|
|
126
|
-
openEntityMode
|
|
126
|
+
openEntityMode?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
127
127
|
navigateBack: () => void;
|
|
128
128
|
formContext: FormContext,
|
|
129
129
|
formex: FormexController<Record<string, unknown>>;
|
|
@@ -164,6 +164,7 @@ function buildBottomActions<M extends Record<string, unknown>>({
|
|
|
164
164
|
{formActions.length > 0 && <div className="grow flex overflow-auto no-scrollbar">
|
|
165
165
|
{formActions.map((action, index) => {
|
|
166
166
|
|
|
167
|
+
|
|
167
168
|
const props = {
|
|
168
169
|
view: "form",
|
|
169
170
|
entity,
|
|
@@ -208,17 +209,19 @@ function buildBottomActions<M extends Record<string, unknown>>({
|
|
|
208
209
|
{t("back_to_detail") ?? "Back to details"}
|
|
209
210
|
</Button>
|
|
210
211
|
)}
|
|
211
|
-
<
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
212
|
+
<Tooltip title={hasErrors ? (t("fix_errors_before_saving") ?? "Fix highlighted errors before saving") : undefined}>
|
|
213
|
+
<Button variant={canClose ? "text" : "filled"}
|
|
214
|
+
color="primary"
|
|
215
|
+
type="submit"
|
|
216
|
+
disabled={disabled || formex.isSubmitting}
|
|
217
|
+
onClick={() => {
|
|
218
|
+
sideDialogContext.setPendingClose(false);
|
|
219
|
+
}}>
|
|
220
|
+
{status === "existing" && t("save")}
|
|
221
|
+
{status === "copy" && t("create_copy")}
|
|
222
|
+
{status === "new" && t("create")}
|
|
223
|
+
</Button>
|
|
224
|
+
</Tooltip>
|
|
222
225
|
{canClose && <LoadingButton variant="filled"
|
|
223
226
|
color="primary"
|
|
224
227
|
type="submit"
|
|
@@ -256,19 +259,22 @@ function buildSideActions<M extends Record<string, unknown>>({
|
|
|
256
259
|
const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
257
260
|
return <div
|
|
258
261
|
className={cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin, className)}>
|
|
259
|
-
<
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
262
|
+
<Tooltip title={hasErrors ? (t("fix_errors_before_saving") ?? "Fix highlighted errors before saving") : undefined}>
|
|
263
|
+
<LoadingButton fullWidth={true}
|
|
264
|
+
variant="filled"
|
|
265
|
+
color="primary"
|
|
266
|
+
type="submit"
|
|
267
|
+
loading={formex.isSubmitting}
|
|
268
|
+
startIcon={hasErrors ? <AlertCircleIcon/> : undefined}
|
|
269
|
+
disabled={disabled || formex.isSubmitting}
|
|
270
|
+
onClick={() => {
|
|
271
|
+
sideDialogContext.setPendingClose?.(false);
|
|
272
|
+
}}>
|
|
273
|
+
{status === "existing" && t("save")}
|
|
274
|
+
{status === "copy" && t("create_copy")}
|
|
275
|
+
{status === "new" && t("create")}
|
|
276
|
+
</LoadingButton>
|
|
277
|
+
</Tooltip>
|
|
272
278
|
|
|
273
279
|
<Button fullWidth={true} variant="text" disabled={disabled || formex.isSubmitting} type="reset">
|
|
274
280
|
{status === "existing" ? t("discard") : t("clear")}
|
|
@@ -293,7 +299,7 @@ function buildSideActions<M extends Record<string, unknown>>({
|
|
|
293
299
|
openEntityMode,
|
|
294
300
|
navigateBack,
|
|
295
301
|
formContext
|
|
296
|
-
} satisfies EntityActionClickProps<
|
|
302
|
+
} satisfies EntityActionClickProps<Record<string, unknown>>;
|
|
297
303
|
const isEnabled = !action.isEnabled || action.isEnabled(props);
|
|
298
304
|
return (
|
|
299
305
|
<EntityActionButton key={action.key ?? action.name ?? index} action={action} enabled={isEnabled} props={props}/>
|
|
@@ -63,14 +63,23 @@ export function ContentHomePage({
|
|
|
63
63
|
onNavigationEntriesUpdate = () => {}
|
|
64
64
|
} = navigationStateController.topLevelNavigation || {};
|
|
65
65
|
|
|
66
|
-
//
|
|
67
|
-
//
|
|
66
|
+
// Build a set of Studio-only dev view slugs so we can distinguish them from
|
|
67
|
+
// CMS custom views added via <RebaseCMS views={...}> or plugins.
|
|
68
|
+
const studioViewSlugs = useMemo(() => {
|
|
69
|
+
const slugs = new Set<string>();
|
|
70
|
+
(registry.studioConfig?.devViews ?? []).forEach(v => slugs.add(v.slug));
|
|
71
|
+
if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
|
|
72
|
+
return slugs;
|
|
73
|
+
}, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
|
|
74
|
+
|
|
75
|
+
// Studio mode shows Studio dev views + admin entries (Users/Roles).
|
|
76
|
+
// Content mode shows collections, CMS custom views, and admin entries — but not Studio dev views.
|
|
68
77
|
const rawNavigationEntries = useMemo(() => {
|
|
69
78
|
if (adminModeController.mode === "studio") {
|
|
70
79
|
return unFilteredNavigationEntries.filter(e => e.type === "view" || e.type === "admin");
|
|
71
80
|
}
|
|
72
|
-
return unFilteredNavigationEntries.filter(e => e.type !== "view");
|
|
73
|
-
}, [unFilteredNavigationEntries, adminModeController.mode]);
|
|
81
|
+
return unFilteredNavigationEntries.filter(e => e.type !== "view" || !studioViewSlugs.has(e.slug));
|
|
82
|
+
}, [unFilteredNavigationEntries, adminModeController.mode, studioViewSlugs]);
|
|
74
83
|
|
|
75
84
|
const groupOrderFromNavController = useMemo(() => {
|
|
76
85
|
const entryGroups = new Set(rawNavigationEntries.map(e => e.group).filter(Boolean));
|
|
@@ -10,11 +10,12 @@ import type { RebaseCMSConfig } from "@rebasepro/types";
|
|
|
10
10
|
* is auto-wired as a native feature (slots, provider, Studio view) without
|
|
11
11
|
* needing any external plugin.
|
|
12
12
|
*/
|
|
13
|
-
export function RebaseCMS({ collections, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings }: RebaseCMSConfig) {
|
|
13
|
+
export function RebaseCMS({ collections, views, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings }: RebaseCMSConfig) {
|
|
14
14
|
const dispatch = useRebaseRegistryDispatch();
|
|
15
15
|
|
|
16
16
|
useLayoutEffect(() => {
|
|
17
17
|
dispatch.registerCMS({ collections,
|
|
18
|
+
views,
|
|
18
19
|
homePage,
|
|
19
20
|
entityViews,
|
|
20
21
|
entityActions,
|
|
@@ -22,7 +23,7 @@ plugins,
|
|
|
22
23
|
collectionEditor,
|
|
23
24
|
navigationGroupMappings });
|
|
24
25
|
return () => dispatch.unregisterCMS();
|
|
25
|
-
}, [dispatch, collections, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings]);
|
|
26
|
+
}, [dispatch, collections, views, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings]);
|
|
26
27
|
|
|
27
28
|
return null;
|
|
28
29
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo, lazy, Suspense } from "react";
|
|
1
|
+
import React, { useMemo, useRef, useEffect, lazy, Suspense } from "react";
|
|
2
2
|
import {
|
|
3
3
|
useRebaseRegistry,
|
|
4
4
|
useRebaseContext,
|
|
@@ -7,10 +7,14 @@ import {
|
|
|
7
7
|
useAdminModeController,
|
|
8
8
|
useBuildLocalConfigurationPersistence,
|
|
9
9
|
useRebaseClient,
|
|
10
|
+
useData,
|
|
11
|
+
useDataSources,
|
|
10
12
|
StudioBridgeRegistryProvider,
|
|
11
13
|
useBridgeRegistration,
|
|
12
|
-
CustomizationControllerContext
|
|
14
|
+
CustomizationControllerContext,
|
|
15
|
+
RebaseDataContext
|
|
13
16
|
} from "@rebasepro/core";
|
|
17
|
+
import { buildRoutedRebaseData, resolveDataSource } from "@rebasepro/common";
|
|
14
18
|
import { CircularProgressCenter } from "@rebasepro/ui";
|
|
15
19
|
import type { AppView, CollectionEditorOptions, EntityCustomView, EntityAction, EntityCollection, RebasePlugin } from "@rebasepro/types";
|
|
16
20
|
import type { CollectionRegistryController } from "@rebasepro/types";
|
|
@@ -35,8 +39,8 @@ import { ConfigControllerProvider } from "../collection_editor/ConfigControllerP
|
|
|
35
39
|
|
|
36
40
|
// Lazy-load the schema view — only fetched when studio schema tool is active
|
|
37
41
|
const CollectionsStudioView = lazy(() =>
|
|
38
|
-
import("../collection_editor/ui/collection_editor/
|
|
39
|
-
.then(m => ({ default: m.
|
|
42
|
+
import("../collection_editor/ui/collection_editor/RouterCollectionsStudioView")
|
|
43
|
+
.then(m => ({ default: m.RouterCollectionsStudioView }))
|
|
40
44
|
);
|
|
41
45
|
|
|
42
46
|
export interface RebaseNavigationProps {
|
|
@@ -86,7 +90,24 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
|
|
|
86
90
|
// ── Build the navigation controllers ──────────────────────────────
|
|
87
91
|
const collectionsBuilder = useMemo(() => Array.isArray(cmsCollections) ? () => [...cmsCollections] : cmsCollections, [cmsCollections]);
|
|
88
92
|
|
|
89
|
-
const
|
|
93
|
+
const dataSources = useDataSources();
|
|
94
|
+
const collectionRegistryController = useBuildCollectionRegistryController({ userConfigPersistence, dataSources: dataSources.registry });
|
|
95
|
+
|
|
96
|
+
// ── Multi-data-source routing ─────────────────────────────────────
|
|
97
|
+
// Combine the default data source (server transport) with the registered
|
|
98
|
+
// direct/custom sources from <Rebase> and route each collection by its
|
|
99
|
+
// resolved data-source key, looked up by path against the collection
|
|
100
|
+
// registry. A stable resolver ref keeps the routed data instance
|
|
101
|
+
// referentially stable across registry rebuilds, so data-effect
|
|
102
|
+
// dependencies don't thrash.
|
|
103
|
+
const defaultData = useData();
|
|
104
|
+
const getCollectionRef = useRef(collectionRegistryController.getCollection);
|
|
105
|
+
getCollectionRef.current = collectionRegistryController.getCollection;
|
|
106
|
+
const routedData = useMemo(() => buildRoutedRebaseData({
|
|
107
|
+
defaultData,
|
|
108
|
+
sources: dataSources.sources,
|
|
109
|
+
resolveKey: (slugOrPath) => resolveDataSource(getCollectionRef.current(slugOrPath), dataSources.registry).key
|
|
110
|
+
}), [defaultData, dataSources]);
|
|
90
111
|
|
|
91
112
|
const urlController = useBuildUrlController({
|
|
92
113
|
basePath: "/",
|
|
@@ -102,6 +123,26 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
|
|
|
102
123
|
[cmsCollections]
|
|
103
124
|
);
|
|
104
125
|
|
|
126
|
+
// ── Dev-only data-source sanity check ─────────────────────────────
|
|
127
|
+
// Warn about data sources declared with a direct/custom transport that
|
|
128
|
+
// have no client-side driver — these silently fall back to the default
|
|
129
|
+
// (server) source, a common source of misrouting. Unambiguous: it never
|
|
130
|
+
// false-positives on server-mediated sources (which intentionally have
|
|
131
|
+
// no client driver).
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (process.env.NODE_ENV === "production") return;
|
|
134
|
+
const { registry, sources } = dataSources;
|
|
135
|
+
const missing = Object.values(registry)
|
|
136
|
+
.filter((d) => (d.transport === "direct" || d.transport === "custom") && !sources[d.key])
|
|
137
|
+
.map((d) => d.key);
|
|
138
|
+
if (missing.length > 0) {
|
|
139
|
+
console.warn(
|
|
140
|
+
`[Rebase] These data source(s) declare a direct/custom transport but have no client-side driver and will fall back to the default data source: ${missing.map(k => `"${k}"`).join(", ")}. ` +
|
|
141
|
+
`Provide a \`driver\` for them in \`dataSources\` on <Rebase>.`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}, [dataSources]);
|
|
145
|
+
|
|
105
146
|
const internalConfigController = useLocalCollectionsConfigController(
|
|
106
147
|
rebaseClient,
|
|
107
148
|
resolvedCollections,
|
|
@@ -120,6 +161,7 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
|
|
|
120
161
|
return {
|
|
121
162
|
slug: "schema",
|
|
122
163
|
name: "Edit collections",
|
|
164
|
+
group: "Database",
|
|
123
165
|
icon: "LayoutList",
|
|
124
166
|
nestedRoutes: true,
|
|
125
167
|
view: (
|
|
@@ -136,13 +178,30 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
|
|
|
136
178
|
return base;
|
|
137
179
|
}, [registry.studioConfig?.devViews, schemaView]);
|
|
138
180
|
|
|
181
|
+
// Merge CMS-registered views with Studio dev views.
|
|
182
|
+
// Order: CMS views (developer's primary content) → Studio dev views (tooling).
|
|
183
|
+
// Plugin views are merged later inside resolveAppViews.
|
|
184
|
+
const cmsViews = registry.cmsConfig?.views;
|
|
185
|
+
const mergedViews = useMemo(() => {
|
|
186
|
+
if (!cmsViews) return devViews;
|
|
187
|
+
if (Array.isArray(cmsViews) && cmsViews.length === 0) return devViews;
|
|
188
|
+
// When cmsViews is a builder function, wrap it to append devViews after resolution
|
|
189
|
+
if (typeof cmsViews === "function") {
|
|
190
|
+
return async (params: Parameters<typeof cmsViews>[0]) => {
|
|
191
|
+
const resolved = await cmsViews(params) ?? [];
|
|
192
|
+
return [...resolved, ...devViews];
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return [...cmsViews, ...devViews];
|
|
196
|
+
}, [cmsViews, devViews]);
|
|
197
|
+
|
|
139
198
|
const navigationStateController = useBuildNavigationStateController({
|
|
140
199
|
plugins: registry.cmsConfig?.plugins ?? EMPTY_PLUGINS,
|
|
141
200
|
collections: collectionsBuilder,
|
|
142
|
-
views:
|
|
201
|
+
views: mergedViews,
|
|
143
202
|
navigationGroupMappings: registry.cmsConfig?.navigationGroupMappings,
|
|
144
203
|
authController: context.authController!,
|
|
145
|
-
data:
|
|
204
|
+
data: routedData,
|
|
146
205
|
collectionRegistryController,
|
|
147
206
|
urlController,
|
|
148
207
|
adminMode: adminModeController?.mode
|
|
@@ -175,7 +234,11 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
|
|
|
175
234
|
}, [parentCustomizationController, registry.cmsConfig?.entityViews, registry.cmsConfig?.entityActions]);
|
|
176
235
|
|
|
177
236
|
// ── Inner content with all context providers ──────────────────────
|
|
237
|
+
// Re-provide RebaseDataContext with the routed data so that every CMS
|
|
238
|
+
// consumer (list/entity views, references, board, import/export, and
|
|
239
|
+
// `context.data`) is routed to the correct driver by collection path.
|
|
178
240
|
const navigationContent = (
|
|
241
|
+
<RebaseDataContext.Provider value={routedData}>
|
|
179
242
|
<CustomizationControllerContext.Provider value={enrichedCustomizationController}>
|
|
180
243
|
<StudioBridgeRegistryProvider>
|
|
181
244
|
<CollectionRegistryContext.Provider value={collectionRegistryController}>
|
|
@@ -194,6 +257,7 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
|
|
|
194
257
|
</CollectionRegistryContext.Provider>
|
|
195
258
|
</StudioBridgeRegistryProvider>
|
|
196
259
|
</CustomizationControllerContext.Provider>
|
|
260
|
+
</RebaseDataContext.Provider>
|
|
197
261
|
);
|
|
198
262
|
|
|
199
263
|
// ── Wrap with ConfigControllerProvider when collection editor is enabled ──
|
|
@@ -2,7 +2,7 @@ import { createSelectionStore } from "./SelectionStore";
|
|
|
2
2
|
import type { Property } from "@rebasepro/types";
|
|
3
3
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
4
4
|
import { CollectionSize, Entity, EntityRelation, EntityTableController, FilterValues, SelectedCellProps } from "@rebasepro/types";
|
|
5
|
-
import { CellRendererParams,
|
|
5
|
+
import { CellRendererParams, TableView, VirtualTableColumn, VirtualTableFilterValues, OnRowClickParams, VirtualTableWhereFilterOp } from "@rebasepro/ui";
|
|
6
6
|
import { enumToObjectEntries } from "@rebasepro/common";
|
|
7
7
|
import { DEFAULT_PAGE_SIZE, EntityCollectionTableController, OnCellValueChange, OnColumnResizeParams } from "@rebasepro/core";
|
|
8
8
|
import { FilterFormFieldProps } from "@rebasepro/ui";
|
|
@@ -244,7 +244,7 @@ export const SelectableTable = function SelectableTable<M extends Record<string,
|
|
|
244
244
|
<div className="h-full w-full flex flex-col bg-white dark:bg-surface-900"
|
|
245
245
|
ref={ref}>
|
|
246
246
|
|
|
247
|
-
|
|
247
|
+
<TableView
|
|
248
248
|
data={data as unknown as Record<string, unknown>[]}
|
|
249
249
|
columns={columns}
|
|
250
250
|
cellRenderer={(props) => cellRenderer(props as unknown as CellRendererParams<Entity<M>>)}
|
|
@@ -179,13 +179,6 @@ function SideDialogView({
|
|
|
179
179
|
style={{ zIndex: 45 + panelIndex * 10 }}
|
|
180
180
|
onOpenChange={(open) => {
|
|
181
181
|
if (!open) {
|
|
182
|
-
// Check if any suggestion menu is visible in DOM
|
|
183
|
-
const suggestionMenu = document.querySelector("[data-suggestion-menu=\"true\"]");
|
|
184
|
-
if (suggestionMenu && window.getComputedStyle(suggestionMenu).visibility !== "hidden") {
|
|
185
|
-
// Don't close the sheet if a suggestion menu is visible
|
|
186
|
-
// Let Tiptap handle closing the menu first
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
182
|
onCloseRequest();
|
|
190
183
|
}
|
|
191
184
|
}}
|
|
@@ -36,7 +36,7 @@ export const editEntityAction: EntityAction = {
|
|
|
36
36
|
|
|
37
37
|
highlightEntity?.(entity);
|
|
38
38
|
|
|
39
|
-
context
|
|
39
|
+
context?.analyticsController?.onAnalyticsEvent?.("entity_click", {
|
|
40
40
|
path: entity.path,
|
|
41
41
|
entityId: entity.id
|
|
42
42
|
});
|
|
@@ -62,7 +62,7 @@ export const editEntityAction: EntityAction = {
|
|
|
62
62
|
path: newFullIdPath,
|
|
63
63
|
sideEntityController,
|
|
64
64
|
onClose: () => unhighlightEntity?.(entity),
|
|
65
|
-
navigation: context
|
|
65
|
+
navigation: context?.urlController!,
|
|
66
66
|
selectedTab: defaultSelectedView
|
|
67
67
|
});
|
|
68
68
|
|
|
@@ -92,7 +92,7 @@ export const copyEntityAction: EntityAction = {
|
|
|
92
92
|
throw new Error("INTERNAL: copyEntityAction: sideEntityController is undefined");
|
|
93
93
|
}
|
|
94
94
|
highlightEntity?.(entity);
|
|
95
|
-
context
|
|
95
|
+
context?.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
|
|
96
96
|
path: entity.path,
|
|
97
97
|
entityId: entity.id
|
|
98
98
|
});
|
|
@@ -106,7 +106,7 @@ export const copyEntityAction: EntityAction = {
|
|
|
106
106
|
copy: true,
|
|
107
107
|
sideEntityController,
|
|
108
108
|
onClose: () => unhighlightEntity?.(entity),
|
|
109
|
-
navigation: context
|
|
109
|
+
navigation: context?.urlController!
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
return Promise.resolve(undefined);
|
|
@@ -131,7 +131,7 @@ export const deleteEntityAction: EntityAction = {
|
|
|
131
131
|
if (!entity) {
|
|
132
132
|
throw new Error("INTERNAL: deleteEntityAction: Entity is undefined");
|
|
133
133
|
}
|
|
134
|
-
if (!context
|
|
134
|
+
if (!context?.dialogsController) {
|
|
135
135
|
throw new Error("INTERNAL: deleteEntityAction: context.dialogsController is undefined");
|
|
136
136
|
}
|
|
137
137
|
const { closeDialog } = context.dialogsController.open({
|
|
@@ -146,14 +146,14 @@ export const deleteEntityAction: EntityAction = {
|
|
|
146
146
|
callbacks={collection.callbacks}
|
|
147
147
|
open={open}
|
|
148
148
|
onEntityDelete={() => {
|
|
149
|
-
context
|
|
149
|
+
context?.analyticsController?.onAnalyticsEvent?.("single_entity_deleted", {
|
|
150
150
|
path
|
|
151
151
|
});
|
|
152
152
|
selectionController?.setSelectedEntities(selectionController.selectedEntities.filter(e => e.id !== entity.id));
|
|
153
153
|
onCollectionChange?.();
|
|
154
154
|
// In full-screen mode, navigateBack would go to the deleted entity's
|
|
155
155
|
// detail URL, which no longer exists. Navigate to the parent collection instead.
|
|
156
|
-
if (openEntityMode === "full_screen" && context
|
|
156
|
+
if (openEntityMode === "full_screen" && context?.urlController) {
|
|
157
157
|
const collectionUrl = context.urlController.buildUrlCollectionPath(path);
|
|
158
158
|
context.urlController.navigate(collectionUrl, { replace: true });
|
|
159
159
|
} else {
|
|
@@ -258,7 +258,7 @@ export const resetPasswordAction: EntityAction = {
|
|
|
258
258
|
if (!entity) {
|
|
259
259
|
throw new Error("INTERNAL: resetPasswordAction: Entity is undefined");
|
|
260
260
|
}
|
|
261
|
-
if (!context
|
|
261
|
+
if (!context?.dialogsController) {
|
|
262
262
|
throw new Error("INTERNAL: resetPasswordAction: context.dialogsController is undefined");
|
|
263
263
|
}
|
|
264
264
|
|
|
@@ -272,7 +272,7 @@ export const resetPasswordAction: EntityAction = {
|
|
|
272
272
|
roles: entity.values?.roles as string[] || []
|
|
273
273
|
};
|
|
274
274
|
|
|
275
|
-
const { closeDialog } = context
|
|
275
|
+
const { closeDialog } = context!.dialogsController.open({
|
|
276
276
|
key: "reset_password_dialog_" + entity.id,
|
|
277
277
|
Component: ({ open }) => (
|
|
278
278
|
<ResetPasswordActionDialog
|
|
@@ -22,14 +22,23 @@ export const BreadcrumbsProvider: React.FC<BreadcrumbsProviderProps> = ({ childr
|
|
|
22
22
|
const set = useCallback((props: {
|
|
23
23
|
breadcrumbs: BreadcrumbEntry[];
|
|
24
24
|
}) => {
|
|
25
|
-
setBreadcrumbs(prev =>
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
setBreadcrumbs(prev => {
|
|
26
|
+
const next = props.breadcrumbs.map(newEntry => {
|
|
27
|
+
const prevEntry = newEntry.id ? prev.find(p => p.id === newEntry.id) : undefined;
|
|
28
|
+
if (prevEntry && newEntry.count === null && typeof prevEntry.count === "number") {
|
|
29
|
+
return { ...newEntry,
|
|
29
30
|
count: prevEntry.count };
|
|
31
|
+
}
|
|
32
|
+
return newEntry;
|
|
33
|
+
});
|
|
34
|
+
// Bail out if nothing changed — return same reference to skip re-render
|
|
35
|
+
if (prev.length === next.length && prev.every((p, i) =>
|
|
36
|
+
p.title === next[i].title && p.url === next[i].url && p.id === next[i].id && p.count === next[i].count
|
|
37
|
+
)) {
|
|
38
|
+
return prev;
|
|
30
39
|
}
|
|
31
|
-
return
|
|
32
|
-
})
|
|
40
|
+
return next;
|
|
41
|
+
});
|
|
33
42
|
}, []);
|
|
34
43
|
|
|
35
44
|
const updateCount = useCallback((id: string, count: number | null | undefined) => {
|