@rebasepro/admin 0.9.1-canary.fd3754b → 0.10.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-DKGlYDPc.js → CollectionEditorDialog-BOlKGGg7.js} +149 -35
- package/dist/{CollectionEditorDialog-DKGlYDPc.js.map → CollectionEditorDialog-BOlKGGg7.js.map} +1 -1
- package/dist/{PropertyEditView-h0N-AMCY.js → PropertyEditView-i51tFOfM.js} +3 -3
- package/dist/PropertyEditView-i51tFOfM.js.map +1 -0
- package/dist/{RouterCollectionsStudioView-DdEStpFk.js → RouterCollectionsStudioView-Dh4W53PU.js} +4 -4
- package/dist/{RouterCollectionsStudioView-DdEStpFk.js.map → RouterCollectionsStudioView-Dh4W53PU.js.map} +1 -1
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/field_configs.d.ts +17 -0
- package/dist/contexts/BreacrumbsContext.d.ts +1 -1
- package/dist/editor.js.map +1 -1
- package/dist/export-BM6HPujv.js +518 -0
- package/dist/export-BM6HPujv.js.map +1 -0
- package/dist/{history-C0FdqGNz.js → history-CtIPJxvQ.js} +5 -5
- package/dist/history-CtIPJxvQ.js.map +1 -0
- package/dist/hooks/useSelectionDialog.d.ts +1 -1
- package/dist/import-DzQnT03v.js +328 -0
- package/dist/import-DzQnT03v.js.map +1 -0
- package/dist/index.js +3299 -1
- package/dist/index.js.map +1 -0
- package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
- package/dist/preview/util.d.ts +1 -0
- package/dist/util/view_mode.d.ts +54 -0
- package/dist/{src-DLAFyeZI.js → util-XFwQ9FEc.js} +17584 -21521
- package/dist/util-XFwQ9FEc.js.map +1 -0
- package/package.json +12 -17
- package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +139 -13
- package/src/collection_editor/ui/collection_editor/properties/CommonPropertyFields.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +1 -1
- package/src/components/CollectionTableBinding/CollectionRowActions.tsx +1 -1
- package/src/components/CollectionTableBinding/fields/TableReferenceField.tsx +1 -1
- package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +1 -1
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +1 -1
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +76 -126
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +4 -8
- package/src/components/CollectionViewBinding/FiltersDialog.tsx +18 -0
- package/src/components/CollectionViewBinding/SplitListView.tsx +114 -63
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +9 -3
- package/src/components/DetailViewBinding.tsx +4 -2
- package/src/components/EditFormActions.tsx +2 -2
- package/src/components/EditViewBinding.tsx +3 -2
- package/src/components/EntityPreviewBinding.tsx +2 -1
- package/src/components/HomePage/ContentHomePage.tsx +2 -1
- package/src/components/HomePage/FavouritesView.tsx +1 -1
- package/src/components/RebaseNavigation.tsx +16 -1
- package/src/components/RebaseRouteDefs.tsx +1 -1
- package/src/components/ReferenceTable/SelectionTableBinding.tsx +2 -1
- package/src/components/ReferenceWidget.tsx +1 -1
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +1 -1
- package/src/components/SidePanelBinding.tsx +46 -8
- package/src/components/SidePanelProvider.tsx +14 -11
- package/src/components/field_configs.tsx +42 -25
- package/src/components/history/EntityHistoryView.tsx +3 -3
- package/src/contexts/BreacrumbsContext.tsx +1 -1
- package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +2 -4
- package/src/data_import/import/ImportCollectionAction.tsx +6 -6
- package/src/editor/selectors/color-selector.tsx +1 -1
- package/src/editor/selectors/node-selector.tsx +1 -1
- package/src/editor/selectors/text-buttons.tsx +1 -1
- package/src/form/EntityForm.tsx +4 -1
- package/src/form/EntityFormBinding.tsx +3 -1
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -1
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +3 -2
- package/src/form/field_bindings/BlockFieldBinding.tsx +2 -1
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +2 -1
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +2 -1
- package/src/form/field_bindings/MapFieldBinding.tsx +2 -1
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +2 -1
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -1
- package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +2 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -1
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +3 -2
- package/src/form/field_bindings/RelationFieldBinding.tsx +2 -1
- package/src/form/field_bindings/RepeatFieldBinding.tsx +2 -1
- package/src/form/field_bindings/SelectFieldBinding.tsx +2 -1
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +2 -1
- package/src/form/field_bindings/SwitchFieldBinding.tsx +2 -1
- package/src/form/field_bindings/TextFieldBinding.tsx +2 -1
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +2 -1
- package/src/form/field_bindings/VectorFieldBinding.tsx +2 -1
- package/src/hooks/useBuildSidePanel.tsx +8 -0
- package/src/hooks/useResolvedNavigationFrom.tsx +4 -2
- package/src/hooks/useSelectionDialog.tsx +3 -3
- package/src/preview/components/ImagePreview.tsx +1 -1
- package/src/preview/components/ReferencePreview.tsx +1 -1
- package/src/preview/components/RelationPreview.tsx +2 -2
- package/src/preview/components/UrlComponentPreview.tsx +2 -9
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +1 -1
- package/src/preview/property_previews/NumberPropertyPreview.tsx +1 -1
- package/src/preview/util.ts +9 -0
- package/src/routes/CustomViewRoute.tsx +2 -1
- package/src/routes/RebaseRoute.tsx +20 -25
- package/src/util/navigation_utils.ts +2 -5
- package/src/util/view_mode.ts +102 -0
- package/dist/PropertyEditView-h0N-AMCY.js.map +0 -1
- package/dist/history-C0FdqGNz.js.map +0 -1
- package/dist/src-DLAFyeZI.js.map +0 -1
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getNavigationEntriesFromPath,
|
|
8
8
|
NavigationViewInternal,
|
|
9
9
|
removeInitialAndTrailingSlashes,
|
|
10
|
+
removeTrailingSlash,
|
|
10
11
|
resolveDefaultSelectedView
|
|
11
12
|
} from "@rebasepro/common";
|
|
12
13
|
import { resolvedSelectedEntityView } from "../util/resolutions";
|
|
@@ -280,6 +281,13 @@ export function buildSidePanelsFromUrl(path: string, collections: CollectionConf
|
|
|
280
281
|
|
|
281
282
|
}
|
|
282
283
|
|
|
284
|
+
// `edit` is not a registered view, so the navigation parser skips it. Pick it
|
|
285
|
+
// up from the raw path so that reloading an edit URL reopens the edit form
|
|
286
|
+
// rather than silently dropping the user back on the detail view.
|
|
287
|
+
if (sidePanel && !sidePanel.selectedTab && removeTrailingSlash(path).endsWith("/edit")) {
|
|
288
|
+
sidePanel.selectedTab = "edit";
|
|
289
|
+
}
|
|
290
|
+
|
|
283
291
|
// When the URL doesn't contain a tab segment but the collection has a
|
|
284
292
|
// defaultSelectedView, resolve it so the panel opens with the correct
|
|
285
293
|
// width and the URL is updated on the next replace() cycle.
|
|
@@ -86,11 +86,13 @@ export function resolveNavigationFrom<M extends Record<string, unknown>, USER ex
|
|
|
86
86
|
throw Error(`No collection defined in the navigation for the entity with path ${entry.slug}`);
|
|
87
87
|
}
|
|
88
88
|
// `context.data` is the flat SDK layer; re-wrap the row into the
|
|
89
|
-
// Entity view-model the navigation entry expects.
|
|
89
|
+
// Entity view-model the navigation entry expects. The address is
|
|
90
|
+
// the one we just fetched by — reading it back off the row would
|
|
91
|
+
// find nothing, since a row is only its columns.
|
|
90
92
|
return data.collection(entry.slug).findById(entry.entityId)
|
|
91
93
|
.then((row) => {
|
|
92
94
|
if (!row) return undefined;
|
|
93
|
-
const entity = { id:
|
|
95
|
+
const entity = { id: entry.entityId, path: entry.slug, values: row };
|
|
94
96
|
return { ...entry,
|
|
95
97
|
entity };
|
|
96
98
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { SelectionProps, SelectionTableBinding } from "../components";
|
|
1
|
+
import { SelectionProps, SelectionTableBinding } from "../components/ReferenceTable/SelectionTableBinding";
|
|
2
2
|
import type { CollectionConfig } from "@rebasepro/types";
|
|
3
3
|
import { useCallback, useMemo } from "react";
|
|
4
|
-
import { useSideDialogsController } from "
|
|
5
|
-
import { useCollectionRegistryController } from "
|
|
4
|
+
import { useSideDialogsController } from "./useSideDialogsController";
|
|
5
|
+
import { useCollectionRegistryController } from "./navigation/contexts/CollectionRegistryContext";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* This hook is used to open a side dialog that allows the selection
|
|
@@ -3,7 +3,7 @@ import React, { CSSProperties, useMemo, useState, useEffect } from "react";impor
|
|
|
3
3
|
import { PreviewSize } from "../../types/components/PropertyPreviewProps";
|
|
4
4
|
import { getThumbnailMeasure } from "../util";
|
|
5
5
|
import { useTranslation } from "@rebasepro/app";
|
|
6
|
-
import { sanitizeUrl } from "
|
|
6
|
+
import { sanitizeUrl } from "../util";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @group Preview components
|
|
@@ -8,7 +8,7 @@ import { Skeleton } from "@rebasepro/ui";
|
|
|
8
8
|
import { ErrorBoundary } from "@rebasepro/ui";
|
|
9
9
|
import { ErrorView } from "@rebasepro/app";
|
|
10
10
|
import { EntityPreviewBinding, EntityPreviewContainer } from "../../components/EntityPreviewBinding";
|
|
11
|
-
import { useCollectionRegistryController } from "../../
|
|
11
|
+
import { useCollectionRegistryController } from "../../hooks/navigation/contexts/CollectionRegistryContext";
|
|
12
12
|
import { getEntityTitlePropertyKey } from "../../util/previews";
|
|
13
13
|
import { getValueInPath } from "@rebasepro/utils";
|
|
14
14
|
|
|
@@ -5,8 +5,8 @@ import { Entity, EntityRelation } from "@rebasepro/types";
|
|
|
5
5
|
import type { PreviewSize } from "../../types/components/PropertyPreviewProps";
|
|
6
6
|
import { useCustomizationController, useFetch, ErrorView, useComponentOverride, CollectionScopeProvider } from "@rebasepro/app";
|
|
7
7
|
import { Skeleton } from "@rebasepro/ui";
|
|
8
|
-
import { EntityPreviewBinding, EntityPreviewContainer } from "../../components";
|
|
9
|
-
import { useCollectionRegistryController } from "../../
|
|
8
|
+
import { EntityPreviewBinding, EntityPreviewContainer } from "../../components/EntityPreviewBinding";
|
|
9
|
+
import { useCollectionRegistryController } from "../../hooks/navigation/contexts/CollectionRegistryContext";
|
|
10
10
|
import { getEntityTitlePropertyKey } from "../../util/previews";
|
|
11
11
|
import { getValueInPath } from "@rebasepro/utils";
|
|
12
12
|
|
|
@@ -5,16 +5,9 @@ import { PreviewType } from "@rebasepro/types";
|
|
|
5
5
|
import type { PreviewSize } from "../../types/components/PropertyPreviewProps";
|
|
6
6
|
import { cls, ExternalLinkIcon, FileTextIcon, Tooltip, Typography } from "@rebasepro/ui";
|
|
7
7
|
import { EmptyValue } from "./EmptyValue";
|
|
8
|
-
import { getThumbnailMeasure } from "../util";
|
|
8
|
+
import { getThumbnailMeasure, sanitizeUrl } from "../util";
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
if (!url) return "about:blank";
|
|
12
|
-
const trimmed = url.trim();
|
|
13
|
-
if (/^(?:javascript|data|vbscript):/i.test(trimmed)) {
|
|
14
|
-
return "about:blank";
|
|
15
|
-
}
|
|
16
|
-
return trimmed;
|
|
17
|
-
}
|
|
10
|
+
export { sanitizeUrl };
|
|
18
11
|
|
|
19
12
|
/**
|
|
20
13
|
* @group Preview components
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ArrayProperty, StringProperty } from "@rebasepro/types";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import type { PropertyPreviewProps } from "../../types/components/PropertyPreviewProps";
|
|
4
|
-
import { StringPropertyPreview } from "
|
|
4
|
+
import { StringPropertyPreview } from "./StringPropertyPreview";
|
|
5
5
|
import { ErrorBoundary } from "@rebasepro/ui";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NumberProperty } from "@rebasepro/types";
|
|
2
2
|
import React from "react";
|
|
3
3
|
|
|
4
|
-
import { EnumValuesChip } from "
|
|
4
|
+
import { EnumValuesChip } from "../components/EnumValuesChip";
|
|
5
5
|
import type { PropertyPreviewProps } from "../../types/components/PropertyPreviewProps";
|
|
6
6
|
import { enumToObjectEntries } from "@rebasepro/common";
|
|
7
7
|
|
package/src/preview/util.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { CollectionSize } from "@rebasepro/types";
|
|
2
2
|
import type { PreviewSize } from "../types/components/PropertyPreviewProps";
|
|
3
|
+
export function sanitizeUrl(url: string | undefined): string {
|
|
4
|
+
if (!url) return "about:blank";
|
|
5
|
+
const trimmed = url.trim();
|
|
6
|
+
if (/^(?:javascript|data|vbscript):/i.test(trimmed)) {
|
|
7
|
+
return "about:blank";
|
|
8
|
+
}
|
|
9
|
+
return trimmed;
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
export const SMALL_THUMBNAIL = 40;
|
|
4
13
|
export const MEDIUM_THUMBNAIL = 100;
|
|
5
14
|
export const LARGE_THUMBNAIL = 200;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useEffect, useRef } from "react";
|
|
2
2
|
import type { AppView } from "@rebasepro/types";
|
|
3
|
-
import { useBreadcrumbsController
|
|
3
|
+
import { useBreadcrumbsController } from "../hooks/useBreadcrumbsController";
|
|
4
|
+
import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
|
|
4
5
|
|
|
5
6
|
export function CustomViewRoute({ view }: {
|
|
6
7
|
view: AppView
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import type { CollectionConfig, ViewMode } from "@rebasepro/types";
|
|
2
|
-
import {
|
|
2
|
+
import { useLocation } from "react-router-dom";
|
|
3
3
|
import { EditViewBinding } from "../components/EditViewBinding";
|
|
4
4
|
import { DetailViewBinding } from "../components/DetailViewBinding";
|
|
5
|
-
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
6
6
|
import { useNavigate } from "react-router-dom";
|
|
7
|
-
import { CollectionViewBinding } from "../components";
|
|
8
|
-
import { NotFoundPage, useUserConfigurationPersistence, useComponentOverride } from "@rebasepro/app";
|
|
7
|
+
import { CollectionViewBinding } from "../components/CollectionViewBinding/CollectionViewBinding";
|
|
8
|
+
import { NotFoundPage, useUserConfigurationPersistence, useComponentOverride, useNavigationBlocker } from "@rebasepro/app";
|
|
9
|
+
import { resolveOpenEntityMode, resolveViewMode } from "../util/view_mode";
|
|
9
10
|
import { UnsavedChangesDialog } from "@rebasepro/app";
|
|
10
11
|
import { CircularProgressCenter } from "@rebasepro/ui";
|
|
11
12
|
import { getNavigationEntriesFromPath, NavigationViewCollectionInternal, NavigationViewEntityCustomInternal, NavigationViewInternal } from "@rebasepro/common";
|
|
12
13
|
import { toArray } from "@rebasepro/utils";
|
|
13
|
-
import { useCollectionRegistryController
|
|
14
|
-
import {
|
|
14
|
+
import { useCollectionRegistryController } from "../hooks/navigation/contexts/CollectionRegistryContext";
|
|
15
|
+
import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
|
|
16
|
+
import { useBreadcrumbsController } from "../hooks/useBreadcrumbsController";
|
|
15
17
|
|
|
16
18
|
export function RebaseRoute() {
|
|
17
19
|
|
|
@@ -145,23 +147,16 @@ export function RebaseRoute() {
|
|
|
145
147
|
if (!collection)
|
|
146
148
|
collection = collectionRegistry.getCollection(firstCollectionEntry.slug);
|
|
147
149
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const savedView = userConfigPersistence?.getCollectionConfig(collection.slug)?.defaultViewMode;
|
|
159
|
-
if (savedView) currentViewMode = savedView as ViewMode;
|
|
160
|
-
}
|
|
161
|
-
if (currentViewMode === "kanban") effectiveOpenMode = "side_panel";
|
|
162
|
-
else if (currentViewMode === "table" || currentViewMode === "cards") effectiveOpenMode = "full_screen";
|
|
163
|
-
else effectiveOpenMode = "split";
|
|
164
|
-
}
|
|
150
|
+
const effectiveOpenMode = collection
|
|
151
|
+
? resolveOpenEntityMode({
|
|
152
|
+
collection,
|
|
153
|
+
viewMode: resolveViewMode({
|
|
154
|
+
collection,
|
|
155
|
+
search: location.search,
|
|
156
|
+
savedViewMode: userConfigPersistence?.getCollectionConfig(collection.slug)?.defaultViewMode as ViewMode | undefined
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
: undefined;
|
|
165
160
|
if (collection && effectiveOpenMode === "split") {
|
|
166
161
|
// Extract subcollection tab from the 3rd entry if present
|
|
167
162
|
let selectedTab: string | undefined;
|
|
@@ -292,7 +287,7 @@ function EntityFullScreenRoute({
|
|
|
292
287
|
|
|
293
288
|
const entityPath = basePath + `/${entityId}`;
|
|
294
289
|
|
|
295
|
-
const blocker =
|
|
290
|
+
const blocker = useNavigationBlocker(useCallback(({
|
|
296
291
|
currentLocation,
|
|
297
292
|
nextLocation
|
|
298
293
|
}) => {
|
|
@@ -315,7 +310,7 @@ function EntityFullScreenRoute({
|
|
|
315
310
|
return false;
|
|
316
311
|
|
|
317
312
|
return blocked.current;
|
|
318
|
-
});
|
|
313
|
+
}, [entityPath, basePath]));
|
|
319
314
|
|
|
320
315
|
const lastCollectionEntry = [...navigationEntries].reverse().find((entry) => entry.type === "collection");
|
|
321
316
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CollectionConfig, UrlController, SidePanelController, NavigateOptions } from "@rebasepro/types";
|
|
2
|
+
import { withViewMode } from "./view_mode";
|
|
2
3
|
|
|
3
4
|
// Canonical path utilities — single source of truth in @rebasepro/common
|
|
4
5
|
export {
|
|
@@ -71,11 +72,7 @@ export function navigateToEntity({
|
|
|
71
72
|
if (entityId && selectedTab) {
|
|
72
73
|
to += `/${selectedTab}`;
|
|
73
74
|
}
|
|
74
|
-
|
|
75
|
-
const currentViewParam = new URLSearchParams(window.location.search).get("__view");
|
|
76
|
-
if (currentViewParam) {
|
|
77
|
-
to += `${to.includes("?") ? "&" : "?"}__view=${currentViewParam}`;
|
|
78
|
-
}
|
|
75
|
+
to = withViewMode(to);
|
|
79
76
|
if (!entityId) {
|
|
80
77
|
to += "#new";
|
|
81
78
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { CollectionConfig, ViewMode } from "@rebasepro/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Query param that carries the active collection view mode across navigations.
|
|
5
|
+
*/
|
|
6
|
+
export const VIEW_MODE_PARAM = "__view";
|
|
7
|
+
|
|
8
|
+
export const VIEW_MODES: ViewMode[] = ["list", "table", "cards", "kanban"];
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_VIEW_MODE: ViewMode = "list";
|
|
11
|
+
|
|
12
|
+
export type OpenEntityMode = "side_panel" | "full_screen" | "split" | "dialog";
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_OPEN_ENTITY_MODE: OpenEntityMode = "split";
|
|
15
|
+
|
|
16
|
+
export function isViewMode(value: string | null | undefined): value is ViewMode {
|
|
17
|
+
return Boolean(value) && VIEW_MODES.includes(value as ViewMode);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Read the active view mode from a query string, if it holds a valid one.
|
|
22
|
+
*
|
|
23
|
+
* @param search a query string (e.g. `location.search`). Falls back to the
|
|
24
|
+
* current browser location when omitted.
|
|
25
|
+
*/
|
|
26
|
+
export function getViewModeFromSearch(search?: string): ViewMode | null {
|
|
27
|
+
const source = search ?? (typeof window !== "undefined" ? window.location.search : "");
|
|
28
|
+
const value = new URLSearchParams(source).get(VIEW_MODE_PARAM);
|
|
29
|
+
return isViewMode(value) ? value : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Append the active view mode to a URL so the target route keeps rendering the
|
|
34
|
+
* view the user was last looking at.
|
|
35
|
+
*
|
|
36
|
+
* Every entity/collection navigation must go through this — a missed call site
|
|
37
|
+
* silently resets the user's view mode mid-session.
|
|
38
|
+
*
|
|
39
|
+
* @param url the URL to decorate. May already contain a query string and/or hash.
|
|
40
|
+
* @param search query string to read the view mode from. Defaults to the
|
|
41
|
+
* current browser location.
|
|
42
|
+
*/
|
|
43
|
+
export function withViewMode(url: string, search?: string): string {
|
|
44
|
+
const viewMode = getViewModeFromSearch(search);
|
|
45
|
+
if (!viewMode) return url;
|
|
46
|
+
|
|
47
|
+
// Preserve any hash — the param belongs to the query, which precedes it.
|
|
48
|
+
const hashIndex = url.indexOf("#");
|
|
49
|
+
const hash = hashIndex >= 0 ? url.substring(hashIndex) : "";
|
|
50
|
+
const base = hashIndex >= 0 ? url.substring(0, hashIndex) : url;
|
|
51
|
+
|
|
52
|
+
if (new URLSearchParams(base.split("?")[1] ?? "").has(VIEW_MODE_PARAM)) {
|
|
53
|
+
return url;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const separator = base.includes("?") ? "&" : "?";
|
|
57
|
+
return `${base}${separator}${VIEW_MODE_PARAM}=${viewMode}${hash}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the view mode for a collection, honouring the documented priority:
|
|
62
|
+
* URL param > saved user config > collection default.
|
|
63
|
+
*/
|
|
64
|
+
export function resolveViewMode({
|
|
65
|
+
collection,
|
|
66
|
+
search,
|
|
67
|
+
savedViewMode
|
|
68
|
+
}: {
|
|
69
|
+
collection?: Pick<CollectionConfig<any>, "defaultViewMode">;
|
|
70
|
+
search?: string;
|
|
71
|
+
savedViewMode?: ViewMode | null;
|
|
72
|
+
}): ViewMode {
|
|
73
|
+
const fromUrl = getViewModeFromSearch(search);
|
|
74
|
+
if (fromUrl) return fromUrl;
|
|
75
|
+
if (isViewMode(savedViewMode)) return savedViewMode;
|
|
76
|
+
return collection?.defaultViewMode ?? DEFAULT_VIEW_MODE;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resolve how clicking an entity should present it.
|
|
81
|
+
*
|
|
82
|
+
* An explicit `collection.openEntityMode` always wins. Otherwise the mode is
|
|
83
|
+
* derived from the active view mode, so the presentation suits the surface the
|
|
84
|
+
* user is looking at: a board keeps its columns behind an overlay, a wide table
|
|
85
|
+
* hands the entity the full width, and list/card views use master-detail.
|
|
86
|
+
*
|
|
87
|
+
* This is the single source of truth — row clicks, find-by-id and the router
|
|
88
|
+
* must all resolve through it, otherwise the same entity opens differently
|
|
89
|
+
* depending on how it was reached.
|
|
90
|
+
*/
|
|
91
|
+
export function resolveOpenEntityMode({
|
|
92
|
+
collection,
|
|
93
|
+
viewMode
|
|
94
|
+
}: {
|
|
95
|
+
collection?: Pick<CollectionConfig<any>, "openEntityMode">;
|
|
96
|
+
viewMode?: ViewMode;
|
|
97
|
+
}): OpenEntityMode {
|
|
98
|
+
if (collection?.openEntityMode) return collection.openEntityMode;
|
|
99
|
+
if (viewMode === "kanban") return "side_panel";
|
|
100
|
+
if (viewMode === "table" || viewMode === "cards") return "full_screen";
|
|
101
|
+
return DEFAULT_OPEN_ENTITY_MODE;
|
|
102
|
+
}
|