@rebasepro/admin 0.14.1 → 0.14.2-canary.g27a129e
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-BeS4lxGh.js → CollectionEditorDialog-Dxx0ZaDk.js} +11 -5
- package/dist/CollectionEditorDialog-Dxx0ZaDk.js.map +1 -0
- package/dist/{PropertyEditView-BFJ6H9-Q.js → PropertyEditView-D6xNTm0d.js} +2 -2
- package/dist/{PropertyEditView-BFJ6H9-Q.js.map → PropertyEditView-D6xNTm0d.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-sfsUpEJz.js → RouterCollectionsStudioView-C4vSn9Pu.js} +4 -4
- package/dist/{RouterCollectionsStudioView-sfsUpEJz.js.map → RouterCollectionsStudioView-C4vSn9Pu.js.map} +1 -1
- package/dist/collection_editor/serializable_types.d.ts +6 -0
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/CollectionViewBinding/ViewModeToggle.d.ts +11 -5
- package/dist/components/RebaseAdmin.d.ts +1 -1
- package/dist/{export-KEf57Jso.js → export-BZi1MzQ5.js} +2 -2
- package/dist/{export-KEf57Jso.js.map → export-BZi1MzQ5.js.map} +1 -1
- package/dist/{history-Ctd2cRn_.js → history-B5pkfldE.js} +2 -2
- package/dist/{history-Ctd2cRn_.js.map → history-B5pkfldE.js.map} +1 -1
- package/dist/{import-BujhycMq.js → import-CXpuYo-T.js} +2 -2
- package/dist/{import-BujhycMq.js.map → import-CXpuYo-T.js.map} +1 -1
- package/dist/index.js +19 -14
- package/dist/index.js.map +1 -1
- package/dist/util/resolutions.d.ts +10 -1
- package/dist/util/view_mode.d.ts +24 -6
- package/dist/{util-bL-fk6C6.js → util-BKtHPLe4.js} +141 -52
- package/dist/util-BKtHPLe4.js.map +1 -0
- package/package.json +9 -9
- package/src/collection_editor/serializable_types.ts +6 -0
- package/src/collection_editor/serializable_utils.ts +8 -0
- package/src/collection_editor/ui/collection_editor/ViewModeSwitch.tsx +16 -2
- package/src/components/CollectionEditorDialogs.tsx +4 -3
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +4 -4
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +99 -22
- package/src/components/CollectionViewBinding/ViewModeToggle.tsx +66 -32
- package/src/components/EntityInspector.tsx +3 -2
- package/src/components/HomePage/NavigationCardBinding.tsx +2 -2
- package/src/components/RebaseAdmin.tsx +3 -2
- package/src/components/RebaseNavigation.tsx +13 -6
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +3 -2
- package/src/util/resolutions.ts +30 -1
- package/src/util/view_mode.ts +49 -12
- package/dist/CollectionEditorDialog-BeS4lxGh.js.map +0 -1
- package/dist/util-bL-fk6C6.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useMemo, useRef, useEffect, useCallback, useContext,
|
|
1
|
+
import React, { useMemo, useRef, useEffect, useCallback, useContext, Suspense } from "react";
|
|
2
2
|
import {
|
|
3
3
|
useRebaseRegistry,
|
|
4
4
|
useRebaseContext,
|
|
@@ -16,9 +16,9 @@ import {
|
|
|
16
16
|
RebaseDataContext
|
|
17
17
|
} from "@rebasepro/app";
|
|
18
18
|
import { buildRoutedRebaseData, resolveDataSource } from "@rebasepro/common";
|
|
19
|
-
import { CircularProgressCenter } from "@rebasepro/ui";
|
|
19
|
+
import { CircularProgressCenter, lazyChunk } from "@rebasepro/ui";
|
|
20
20
|
|
|
21
|
-
import type { AppView, CollectionEditorOptions, EntityCustomView, EntityAction, RebasePlugin, AdminCollection } from "@rebasepro/admin-types";
|
|
21
|
+
import type { AppView, CollectionCustomView, CollectionEditorOptions, EntityCustomView, EntityAction, RebasePlugin, AdminCollection } from "@rebasepro/admin-types";
|
|
22
22
|
import type { CollectionRegistryController } from "@rebasepro/types";
|
|
23
23
|
import type { UrlController, NavigationStateController } from "@rebasepro/admin-types";
|
|
24
24
|
|
|
@@ -41,7 +41,7 @@ import { ConfigControllerProvider } from "../collection_editor/ConfigControllerP
|
|
|
41
41
|
import { useCollectionsConfigController } from "../collection_editor/useCollectionsConfigController";
|
|
42
42
|
|
|
43
43
|
// Lazy-load the schema view — only fetched when studio schema tool is active
|
|
44
|
-
const CollectionsStudioView =
|
|
44
|
+
const CollectionsStudioView = lazyChunk(() =>
|
|
45
45
|
import("../collection_editor/ui/collection_editor/RouterCollectionsStudioView")
|
|
46
46
|
.then(m => ({ default: m.RouterCollectionsStudioView }))
|
|
47
47
|
);
|
|
@@ -260,7 +260,10 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
|
|
|
260
260
|
const enrichedCustomizationController = useMemo(() => {
|
|
261
261
|
const cmsEntityViews = (registry.cmsConfig?.entityViews ?? []) as EntityCustomView[];
|
|
262
262
|
const cmsEntityActions = (registry.cmsConfig?.entityActions ?? []) as EntityAction[];
|
|
263
|
-
|
|
263
|
+
// Collection view modes ride the same channel, for the same reason:
|
|
264
|
+
// `admin.customViews: ["map"]` has to find the component somewhere.
|
|
265
|
+
const cmsCollectionViews = (registry.cmsConfig?.collectionViews ?? []) as CollectionCustomView[];
|
|
266
|
+
if (cmsEntityViews.length === 0 && cmsEntityActions.length === 0 && cmsCollectionViews.length === 0) {
|
|
264
267
|
return parentCustomizationController;
|
|
265
268
|
}
|
|
266
269
|
return {
|
|
@@ -272,9 +275,13 @@ export function RebaseNavigation({ children }: RebaseNavigationProps) {
|
|
|
272
275
|
entityActions: [
|
|
273
276
|
...(parentCustomizationController.entityActions ?? []),
|
|
274
277
|
...cmsEntityActions.filter(a => !(parentCustomizationController.entityActions ?? []).some(ea => ea.key === a.key))
|
|
278
|
+
],
|
|
279
|
+
collectionViews: [
|
|
280
|
+
...(parentCustomizationController.collectionViews ?? []),
|
|
281
|
+
...cmsCollectionViews.filter(v => !(parentCustomizationController.collectionViews ?? []).some(cv => cv.key === v.key))
|
|
275
282
|
]
|
|
276
283
|
};
|
|
277
|
-
}, [parentCustomizationController, registry.cmsConfig?.entityViews, registry.cmsConfig?.entityActions]);
|
|
284
|
+
}, [parentCustomizationController, registry.cmsConfig?.entityViews, registry.cmsConfig?.entityActions, registry.cmsConfig?.collectionViews]);
|
|
278
285
|
|
|
279
286
|
// ── Inner content with all context providers ──────────────────────
|
|
280
287
|
// Re-provide RebaseDataContext with the routed data so that every admin
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { FieldHelperText } from "../components/FieldHelperText";
|
|
3
3
|
import { LabelWithIconAndTooltip } from "../components/LabelWithIconAndTooltip";
|
|
4
4
|
import { useAuthController, useStorageSource } from "@rebasepro/app";
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
fieldBackgroundHoverMixin,
|
|
14
14
|
fieldBackgroundMixin,
|
|
15
15
|
IconButton,
|
|
16
|
+
lazyChunk,
|
|
16
17
|
Skeleton,
|
|
17
18
|
XIcon
|
|
18
19
|
} from "@rebasepro/ui";
|
|
@@ -22,7 +23,7 @@ import { randomString } from "@rebasepro/utils";
|
|
|
22
23
|
|
|
23
24
|
// Lazy-load ProseMirror editor + markdown parser/serializer (~300KB)
|
|
24
25
|
// Only fetched when a markdown field is actually rendered.
|
|
25
|
-
const RichTextEditor =
|
|
26
|
+
const RichTextEditor = lazyChunk(() => import("../../editor").then(m => ({ default: m.RichTextEditor })));
|
|
26
27
|
const loadMarkdownUtils = () => import("../../editor/markdown");
|
|
27
28
|
let _markdownUtils: Awaited<ReturnType<typeof loadMarkdownUtils>> | null = null;
|
|
28
29
|
const getMarkdownUtils = async () => {
|
package/src/util/resolutions.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { HistoryIcon } from "@rebasepro/ui";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import type { CustomizationController, EntityAction, EntityCustomView } from "@rebasepro/admin-types";
|
|
3
|
+
import type { CollectionCustomView, CustomizationController, EntityAction, EntityCustomView } from "@rebasepro/admin-types";
|
|
4
|
+
import { VIEW_MODES } from "./view_mode";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Built-in entity views that are resolved by token name.
|
|
@@ -29,6 +30,34 @@ export function resolveEntityView(
|
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a collection's `admin.customViews` into the views it can actually
|
|
35
|
+
* render, dropping every entry that names a key nothing registered.
|
|
36
|
+
*
|
|
37
|
+
* Dropping is deliberate: an unresolvable key must not become a switcher entry
|
|
38
|
+
* that renders a blank panel. It falls out of `enabledViews` too, so a user
|
|
39
|
+
* whose saved view mode was removed lands on a built-in.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveCollectionViews(
|
|
42
|
+
customViews: (string | CollectionCustomView<any>)[] | undefined,
|
|
43
|
+
contextCollectionViews?: CollectionCustomView<any>[]
|
|
44
|
+
): CollectionCustomView<any>[] {
|
|
45
|
+
if (!customViews) return [];
|
|
46
|
+
const resolved: CollectionCustomView<any>[] = [];
|
|
47
|
+
const seen = new Set<string>();
|
|
48
|
+
for (const entry of customViews) {
|
|
49
|
+
const view = typeof entry === "string"
|
|
50
|
+
? contextCollectionViews?.find((candidate) => candidate.key === entry)
|
|
51
|
+
: entry;
|
|
52
|
+
// A custom view keyed "table" would shadow a built-in in the switcher
|
|
53
|
+
// and be unreachable in the render chain, which checks built-ins first.
|
|
54
|
+
if (!view || VIEW_MODES.includes(view.key) || seen.has(view.key)) continue;
|
|
55
|
+
seen.add(view.key);
|
|
56
|
+
resolved.push(view);
|
|
57
|
+
}
|
|
58
|
+
return resolved;
|
|
59
|
+
}
|
|
60
|
+
|
|
32
61
|
export function resolveEntityAction<M extends Record<string, unknown>>(
|
|
33
62
|
entityAction: string | EntityAction<M>,
|
|
34
63
|
contextEntityActions?: EntityAction<M>[]
|
package/src/util/view_mode.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
|
|
2
|
-
import type { ViewMode, AdminCollection } from "@rebasepro/admin-types";
|
|
2
|
+
import type { ViewMode, AdminCollection, CollectionCustomView } from "@rebasepro/admin-types";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Query param that carries the active collection view mode across navigations.
|
|
6
6
|
*/
|
|
7
7
|
export const VIEW_MODE_PARAM = "__view";
|
|
8
8
|
|
|
9
|
+
/** The view modes every collection has, before custom views are added. */
|
|
9
10
|
export const VIEW_MODES: ViewMode[] = ["list", "table", "cards", "kanban"];
|
|
10
11
|
|
|
11
12
|
export const DEFAULT_VIEW_MODE: ViewMode = "list";
|
|
@@ -14,20 +15,34 @@ export type OpenEntityMode = "side_panel" | "full_screen" | "split" | "dialog";
|
|
|
14
15
|
|
|
15
16
|
export const DEFAULT_OPEN_ENTITY_MODE: OpenEntityMode = "split";
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Whether `value` names a view mode this collection can actually render.
|
|
20
|
+
*
|
|
21
|
+
* `customKeys` are the keys of the collection's resolved custom views. They
|
|
22
|
+
* have to be passed in rather than looked up: a custom view is registered per
|
|
23
|
+
* collection, so there is no global set to check against, and validating
|
|
24
|
+
* against the built-ins alone is what would send `?__view=map` to the table.
|
|
25
|
+
*
|
|
26
|
+
* Omitting them checks the built-ins only, which is the right answer for
|
|
27
|
+
* callers that have no collection in hand.
|
|
28
|
+
*/
|
|
29
|
+
export function isViewMode(value: string | null | undefined, customKeys?: readonly string[]): value is ViewMode {
|
|
30
|
+
if (!value) return false;
|
|
31
|
+
return VIEW_MODES.includes(value as ViewMode) || Boolean(customKeys?.includes(value));
|
|
19
32
|
}
|
|
20
33
|
|
|
21
34
|
/**
|
|
22
|
-
* Read the active view mode from a query string, if it holds
|
|
35
|
+
* Read the active view mode from a query string, if it holds one this
|
|
36
|
+
* collection can render.
|
|
23
37
|
*
|
|
24
38
|
* @param search a query string (e.g. `location.search`). Falls back to the
|
|
25
39
|
* current browser location when omitted.
|
|
40
|
+
* @param customKeys keys of the collection's custom views, if any.
|
|
26
41
|
*/
|
|
27
|
-
export function getViewModeFromSearch(search?: string): ViewMode | null {
|
|
42
|
+
export function getViewModeFromSearch(search?: string, customKeys?: readonly string[]): ViewMode | null {
|
|
28
43
|
const source = search ?? (typeof window !== "undefined" ? window.location.search : "");
|
|
29
44
|
const value = new URLSearchParams(source).get(VIEW_MODE_PARAM);
|
|
30
|
-
return isViewMode(value) ? value : null;
|
|
45
|
+
return isViewMode(value, customKeys) ? value : null;
|
|
31
46
|
}
|
|
32
47
|
|
|
33
48
|
/**
|
|
@@ -42,7 +57,14 @@ export function getViewModeFromSearch(search?: string): ViewMode | null {
|
|
|
42
57
|
* current browser location.
|
|
43
58
|
*/
|
|
44
59
|
export function withViewMode(url: string, search?: string): string {
|
|
45
|
-
|
|
60
|
+
// Carries whatever the param holds rather than validating it. These call
|
|
61
|
+
// sites are navigations — they have no collection in hand, so they cannot
|
|
62
|
+
// know whether `map` is one of *this* collection's custom views. Checking
|
|
63
|
+
// against the built-ins here would drop every custom view on the first
|
|
64
|
+
// record click. The binding validates on read, which is where the answer
|
|
65
|
+
// is knowable.
|
|
66
|
+
const source = search ?? (typeof window !== "undefined" ? window.location.search : "");
|
|
67
|
+
const viewMode = new URLSearchParams(source).get(VIEW_MODE_PARAM);
|
|
46
68
|
if (!viewMode) return url;
|
|
47
69
|
|
|
48
70
|
// Preserve any hash — the param belongs to the query, which precedes it.
|
|
@@ -101,16 +123,24 @@ export function withListState(url: string, search?: string): string {
|
|
|
101
123
|
export function resolveViewMode({
|
|
102
124
|
collection,
|
|
103
125
|
search,
|
|
104
|
-
savedViewMode
|
|
126
|
+
savedViewMode,
|
|
127
|
+
customKeys
|
|
105
128
|
}: {
|
|
106
129
|
collection?: Pick<AdminCollection<any>, "defaultViewMode">;
|
|
107
130
|
search?: string;
|
|
108
131
|
savedViewMode?: ViewMode | null;
|
|
132
|
+
/** Keys of the collection's resolved custom views. */
|
|
133
|
+
customKeys?: readonly string[];
|
|
109
134
|
}): ViewMode {
|
|
110
|
-
const fromUrl = getViewModeFromSearch(search);
|
|
135
|
+
const fromUrl = getViewModeFromSearch(search, customKeys);
|
|
111
136
|
if (fromUrl) return fromUrl;
|
|
112
|
-
if (isViewMode(savedViewMode)) return savedViewMode;
|
|
113
|
-
|
|
137
|
+
if (isViewMode(savedViewMode, customKeys)) return savedViewMode;
|
|
138
|
+
// A `defaultViewMode` naming a custom view that is no longer registered
|
|
139
|
+
// would leave the collection on a mode nothing renders, so it gets the
|
|
140
|
+
// same check the other two sources get.
|
|
141
|
+
const configured = collection?.defaultViewMode;
|
|
142
|
+
if (isViewMode(configured, customKeys)) return configured;
|
|
143
|
+
return DEFAULT_VIEW_MODE;
|
|
114
144
|
}
|
|
115
145
|
|
|
116
146
|
/**
|
|
@@ -127,12 +157,19 @@ export function resolveViewMode({
|
|
|
127
157
|
*/
|
|
128
158
|
export function resolveOpenEntityMode({
|
|
129
159
|
collection,
|
|
130
|
-
viewMode
|
|
160
|
+
viewMode,
|
|
161
|
+
customView
|
|
131
162
|
}: {
|
|
132
163
|
collection?: Pick<AdminCollection<any>, "openEntityMode">;
|
|
133
164
|
viewMode?: ViewMode;
|
|
165
|
+
/** The resolved custom view, when `viewMode` names one. */
|
|
166
|
+
customView?: Pick<CollectionCustomView, "openEntityMode">;
|
|
134
167
|
}): OpenEntityMode {
|
|
135
168
|
if (collection?.openEntityMode) return collection.openEntityMode;
|
|
169
|
+
// A custom view gets to say, because only it knows whether it owns its
|
|
170
|
+
// surface. Falls to the board's answer: a map or a calendar keeps its
|
|
171
|
+
// canvas and shows the record over it, where "split" would halve it.
|
|
172
|
+
if (customView) return customView.openEntityMode ?? "side_panel";
|
|
136
173
|
if (viewMode === "kanban") return "side_panel";
|
|
137
174
|
if (viewMode === "table" || viewMode === "cards") return "full_screen";
|
|
138
175
|
return DEFAULT_OPEN_ENTITY_MODE;
|