@rebasepro/admin 0.13.0 → 0.13.1-canary.g1822133
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-CjXkWZ64.js → CollectionEditorDialog-CSheud_E.js} +19 -9
- package/dist/CollectionEditorDialog-CSheud_E.js.map +1 -0
- package/dist/{PropertyEditView-W8I3YV89.js → PropertyEditView-CWZlC1gq.js} +5 -5
- package/dist/PropertyEditView-CWZlC1gq.js.map +1 -0
- package/dist/{RouterCollectionsStudioView-BsLTNoqX.js → RouterCollectionsStudioView-CwTpdzWF.js} +9 -13
- package/dist/RouterCollectionsStudioView-CwTpdzWF.js.map +1 -0
- package/dist/collection_editor/serializable_types.d.ts +1 -0
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/AdditionalFieldValue.d.ts +25 -0
- package/dist/components/ClearFilterSortButton.d.ts +7 -1
- package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +16 -0
- package/dist/components/CollectionViewBinding/EntityCardBinding.d.ts +3 -1
- package/dist/components/CollectionViewBinding/SearchExplanation.d.ts +48 -0
- package/dist/components/CollectionViewBinding/SearchHighlight.d.ts +101 -0
- package/dist/components/CollectionViewBinding/SlotValue.d.ts +63 -0
- package/dist/components/CollectionViewBinding/SortButton.d.ts +7 -1
- package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +17 -4
- package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +12 -0
- package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +55 -5
- package/dist/components/DetailViewBinding.d.ts +15 -0
- package/dist/components/DrawerNavigationGroup.d.ts +9 -6
- package/dist/components/DrawerNavigationItem.d.ts +16 -6
- package/dist/components/EditViewBinding.d.ts +30 -1
- package/dist/components/EntityDisplayHeader.d.ts +53 -0
- package/dist/components/EntityIdentityBar.d.ts +43 -1
- package/dist/components/EntityViewBinding.d.ts +60 -6
- package/dist/components/RebaseLayout.d.ts +1 -1
- package/dist/components/RebaseShell.d.ts +1 -1
- package/dist/components/RelationSelector.d.ts +21 -1
- package/dist/components/UserSelector.d.ts +8 -1
- package/dist/components/app/Scaffold.d.ts +9 -3
- package/dist/components/index.d.ts +2 -0
- package/dist/data_export/export/export.d.ts +7 -0
- package/dist/data_export/export/fetch_export_data.d.ts +48 -0
- package/dist/data_export/export/index.d.ts +1 -0
- package/dist/data_import/utils/csv.d.ts +30 -0
- package/dist/data_import/utils/index.d.ts +2 -0
- package/dist/data_import/utils/save_entities.d.ts +38 -0
- package/dist/data_import/utils/transforms.d.ts +9 -0
- package/dist/editor.js +3 -0
- package/dist/editor.js.map +1 -1
- package/dist/{export-J79rMoNj.js → export-L53WektO.js} +175 -39
- package/dist/export-L53WektO.js.map +1 -0
- package/dist/form/components/FieldBlock.d.ts +58 -4
- package/dist/form/components/FormRail.d.ts +13 -0
- package/dist/form/components/FormSections.d.ts +15 -1
- package/dist/form/components/useRailVisible.d.ts +5 -4
- package/dist/form/form_utils.d.ts +38 -1
- package/dist/{history-D7L_JlLO.js → history-D5SKygpJ.js} +4 -4
- package/dist/{history-D7L_JlLO.js.map → history-D5SKygpJ.js.map} +1 -1
- package/dist/hooks/useEntityDisplay.d.ts +102 -0
- package/dist/hooks/useEntityDisplayTitle.d.ts +18 -9
- package/dist/{import-BvYl--U4.js → import-C_4edkoD.js} +6 -5
- package/dist/import-C_4edkoD.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +237 -123
- package/dist/index.js.map +1 -1
- package/dist/preview/compact_text.d.ts +23 -0
- package/dist/preview/components/BooleanPreview.d.ts +3 -1
- package/dist/preview/components/date_fns_locales.d.ts +9 -0
- package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayOneOfPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayPropertyPreview.d.ts +1 -1
- package/dist/preview/property_previews/MapPropertyPreview.d.ts +1 -1
- package/dist/preview/property_previews/StringPropertyPreview.d.ts +1 -1
- package/dist/routes/RebaseRoute.d.ts +1 -1
- package/dist/types/components/PropertyPreviewProps.d.ts +19 -0
- package/dist/util/number_format.d.ts +14 -0
- package/dist/util/previews.d.ts +9 -2
- package/dist/util/view_constants.d.ts +24 -0
- package/dist/util/view_mode.d.ts +18 -0
- package/dist/{util-GiwPgxnL.js → util-C-D5yD2n.js} +4161 -1809
- package/dist/util-C-D5yD2n.js.map +1 -0
- package/package.json +10 -9
- package/src/collection_editor/serializable_types.ts +1 -0
- package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +8 -3
- package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +5 -2
- package/src/collection_editor/ui/collection_editor/CollectionJsonImportDialog.tsx +1 -0
- package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +9 -8
- package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +9 -11
- package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +5 -2
- package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +5 -2
- package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +1 -0
- package/src/collection_editor/useJsonCollectionsConfigController.tsx +92 -84
- package/src/collection_editor/useLocalCollectionsConfigController.tsx +10 -3
- package/src/components/AdditionalFieldValue.tsx +68 -0
- package/src/components/ClearFilterSortButton.tsx +16 -7
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +7 -8
- package/src/components/CollectionTableBinding/PropertyTableCell.tsx +2 -2
- package/src/components/CollectionTableBinding/column_utils.tsx +15 -6
- package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +2 -2
- package/src/components/CollectionTableBinding/table_bindings.tsx +7 -1
- package/src/components/CollectionViewBinding/BoardCardBinding.tsx +26 -27
- package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +13 -7
- package/src/components/CollectionViewBinding/CollectionCardViewBinding.tsx +2 -1
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +822 -354
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +1 -1
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +52 -12
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +9 -0
- package/src/components/CollectionViewBinding/EntityCardBinding.tsx +51 -39
- package/src/components/CollectionViewBinding/SearchExplanation.tsx +106 -0
- package/src/components/CollectionViewBinding/SearchHighlight.tsx +311 -0
- package/src/components/CollectionViewBinding/SlotValue.tsx +156 -0
- package/src/components/CollectionViewBinding/SortButton.tsx +16 -4
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +23 -7
- package/src/components/CollectionViewBinding/SplitListShowButton.tsx +28 -0
- package/src/components/CollectionViewBinding/SplitListView.tsx +47 -7
- package/src/components/CollectionViewBinding/ViewModeToggle.tsx +10 -5
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +23 -6
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +183 -64
- package/src/components/CollectionViewBinding/utils.ts +7 -4
- package/src/components/DefaultDrawer.tsx +26 -23
- package/src/components/DetailViewBinding.tsx +109 -62
- package/src/components/DrawerNavigationGroup.tsx +16 -39
- package/src/components/DrawerNavigationItem.tsx +56 -16
- package/src/components/EditViewBinding.tsx +233 -50
- package/src/components/EntityDisplayHeader.tsx +170 -0
- package/src/components/EntityIdentityBar.tsx +163 -23
- package/src/components/EntityInspector.tsx +24 -6
- package/src/components/EntityPreviewBinding.tsx +58 -18
- package/src/components/EntityViewBinding.tsx +344 -41
- package/src/components/InlineEntityPreview.tsx +10 -10
- package/src/components/RebaseLayout.tsx +2 -2
- package/src/components/RebaseShell.tsx +2 -2
- package/src/components/ReferenceTable/SelectionTableBinding.tsx +4 -2
- package/src/components/RelationSelector.tsx +76 -15
- package/src/components/SearchIconsView.tsx +14 -33
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -0
- package/src/components/SideDialogs.tsx +15 -3
- package/src/components/SidePanelBinding.tsx +122 -66
- package/src/components/UserSelector.tsx +67 -13
- package/src/components/app/Scaffold.tsx +112 -10
- package/src/components/history/LastEditedByIndicator.tsx +7 -12
- package/src/components/index.ts +3 -0
- package/src/data_export/export/ExportCollectionAction.tsx +101 -48
- package/src/data_export/export/export.ts +63 -16
- package/src/data_export/export/fetch_export_data.ts +90 -0
- package/src/data_export/export/index.ts +1 -0
- package/src/data_import/components/DataNewPropertiesMapping.tsx +20 -3
- package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +1 -0
- package/src/data_import/components/ImportSaveInProgress.tsx +35 -36
- package/src/data_import/import/ImportCollectionAction.tsx +13 -2
- package/src/data_import/utils/csv.ts +153 -0
- package/src/data_import/utils/data.ts +16 -2
- package/src/data_import/utils/file_to_json.ts +77 -6
- package/src/data_import/utils/index.ts +2 -0
- package/src/data_import/utils/save_entities.ts +138 -0
- package/src/data_import/utils/transforms.ts +23 -0
- package/src/editor/components/image-bubble.tsx +2 -0
- package/src/editor/selectors/link-selector.tsx +1 -0
- package/src/form/EntityForm.tsx +74 -53
- package/src/form/EntityFormBinding.tsx +24 -11
- package/src/form/PropertyFieldBinding.tsx +2 -2
- package/src/form/components/FieldBlock.tsx +80 -10
- package/src/form/components/FormRail.tsx +19 -2
- package/src/form/components/FormSections.tsx +32 -5
- package/src/form/components/StorageUploadProgress.tsx +9 -3
- package/src/form/components/useRailVisible.tsx +5 -4
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -2
- package/src/form/field_bindings/BinaryFieldBinding.tsx +11 -5
- package/src/form/field_bindings/BlockFieldBinding.tsx +3 -3
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +4 -0
- package/src/form/field_bindings/GeopointFieldBinding.tsx +23 -16
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +3 -0
- package/src/form/field_bindings/MapFieldBinding.tsx +2 -2
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +13 -7
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +7 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +21 -2
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
- package/src/form/field_bindings/RelationFieldBinding.tsx +5 -2
- package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +2 -2
- package/src/form/field_bindings/TextFieldBinding.tsx +18 -0
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +4 -1
- package/src/form/field_bindings/VectorFieldBinding.tsx +7 -4
- package/src/form/form_utils.ts +78 -0
- package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +29 -5
- package/src/hooks/useEntityDisplay.tsx +262 -0
- package/src/hooks/useEntityDisplayTitle.tsx +27 -38
- package/src/index.ts +9 -1
- package/src/preview/PropertyPreview.tsx +25 -5
- package/src/preview/compact_text.ts +68 -0
- package/src/preview/components/BooleanPreview.tsx +5 -2
- package/src/preview/components/DatePreview.tsx +15 -2
- package/src/preview/components/ReferencePreview.tsx +5 -8
- package/src/preview/components/RelationPreview.tsx +8 -7
- package/src/preview/components/StorageThumbnail.tsx +5 -2
- package/src/preview/components/date_fns_locales.ts +125 -0
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +6 -3
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +26 -1
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +10 -1
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +10 -1
- package/src/preview/property_previews/MapPropertyPreview.tsx +52 -1
- package/src/preview/property_previews/NumberPropertyPreview.tsx +9 -1
- package/src/preview/property_previews/StringPropertyPreview.tsx +8 -5
- package/src/routes/RebaseRoute.tsx +62 -4
- package/src/types/components/PropertyPreviewProps.tsx +21 -0
- package/src/util/navigation_utils.ts +4 -2
- package/src/util/number_format.ts +46 -0
- package/src/util/previews.ts +8 -55
- package/src/util/property_utils.tsx +6 -1
- package/src/util/view_constants.ts +27 -0
- package/src/util/view_mode.ts +36 -0
- package/dist/CollectionEditorDialog-CjXkWZ64.js.map +0 -1
- package/dist/PropertyEditView-W8I3YV89.js.map +0 -1
- package/dist/RouterCollectionsStudioView-BsLTNoqX.js.map +0 -1
- package/dist/export-J79rMoNj.js.map +0 -1
- package/dist/import-BvYl--U4.js.map +0 -1
- package/dist/util-GiwPgxnL.js.map +0 -1
|
@@ -3,30 +3,39 @@ import React from "react";
|
|
|
3
3
|
import { NavLink } from "react-router";
|
|
4
4
|
import { cls, Tooltip } from "@rebasepro/ui";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
tooltipsOpen,
|
|
12
|
-
url,
|
|
13
|
-
onClick,
|
|
14
|
-
indented = false
|
|
15
|
-
}: {
|
|
6
|
+
/**
|
|
7
|
+
* Props of a drawer navigation row, named so an app overriding
|
|
8
|
+
* `Shell.DrawerNavigationItem` can type its wrapper against them.
|
|
9
|
+
*/
|
|
10
|
+
export type DrawerNavigationItemProps = {
|
|
16
11
|
icon: React.ReactElement,
|
|
17
12
|
name: string,
|
|
18
|
-
|
|
13
|
+
/** @deprecated No longer read: the tooltip is uncontrolled. */
|
|
14
|
+
tooltipsOpen?: boolean,
|
|
19
15
|
drawerOpen: boolean,
|
|
16
|
+
/** @deprecated No longer read: the tooltip is uncontrolled. */
|
|
20
17
|
adminMenuOpen?: boolean,
|
|
21
18
|
url: string,
|
|
22
19
|
onClick?: () => void,
|
|
23
20
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
21
|
+
* Drop the row's icon and indent the label into the space it occupied, so the
|
|
22
|
+
* row reads as a child of the group header above it.
|
|
23
|
+
*
|
|
24
|
+
* Nothing in the framework sets this: the stock drawer always passes `false`,
|
|
25
|
+
* and an app that wants the categorised look opts into it by overriding
|
|
26
|
+
* `Shell.DrawerNavigationItem` and deciding for itself which rows get it.
|
|
27
27
|
*/
|
|
28
28
|
indented?: boolean,
|
|
29
|
-
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function DrawerNavigationItem({
|
|
32
|
+
name,
|
|
33
|
+
icon,
|
|
34
|
+
drawerOpen,
|
|
35
|
+
url,
|
|
36
|
+
onClick,
|
|
37
|
+
indented = false
|
|
38
|
+
}: DrawerNavigationItemProps) {
|
|
30
39
|
|
|
31
40
|
// Indented rows give up the icon rather than keeping it *and* indenting: a
|
|
32
41
|
// per-row icon repeated down a group is what flattens the hierarchy, since
|
|
@@ -40,9 +49,30 @@ export function DrawerNavigationItem({
|
|
|
40
49
|
{icon}
|
|
41
50
|
</div>;
|
|
42
51
|
|
|
52
|
+
const [wantsTooltip, setWantsTooltip] = React.useState(false);
|
|
53
|
+
|
|
54
|
+
// Radix will not report a close it never noticed: while the tooltip is masked
|
|
55
|
+
// the pointer can leave the row without `onOpenChange(false)` ever firing. So
|
|
56
|
+
// the stale `true` is dropped on the way *into* the masked state — otherwise
|
|
57
|
+
// it surfaces the moment the mask lifts, as a tooltip hanging over a rail the
|
|
58
|
+
// pointer left long ago.
|
|
59
|
+
if (drawerOpen && wantsTooltip) setWantsTooltip(false);
|
|
60
|
+
|
|
43
61
|
const listItem = <div>
|
|
44
62
|
<NavLink
|
|
45
63
|
onClick={onClick}
|
|
64
|
+
// The label below is `hidden` (display:none) while the rail is
|
|
65
|
+
// collapsed, which takes it out of the accessibility tree along with
|
|
66
|
+
// the row's only text — a collapsed rail was a column of links with no
|
|
67
|
+
// accessible name at all. The tooltip does not cover this: Radix wires
|
|
68
|
+
// a trigger to its content with `aria-describedby`, which is a
|
|
69
|
+
// description rather than a name, and only while the tooltip is open —
|
|
70
|
+
// and this one is masked shut whenever the drawer is open.
|
|
71
|
+
//
|
|
72
|
+
// Named unconditionally rather than only when collapsed: expanded, this
|
|
73
|
+
// is the same string the row already displays, so the accessible name
|
|
74
|
+
// stays stable across the transition instead of appearing with it.
|
|
75
|
+
aria-label={name}
|
|
46
76
|
style={{
|
|
47
77
|
width: "100%",
|
|
48
78
|
transition: drawerOpen ? "width 150ms ease-in" : undefined
|
|
@@ -70,8 +100,18 @@ export function DrawerNavigationItem({
|
|
|
70
100
|
</NavLink>
|
|
71
101
|
</div>;
|
|
72
102
|
|
|
103
|
+
// Per row, and always controlled. The old shared flag could only say "every
|
|
104
|
+
// tooltip" or "none", and the state it needed was unreachable, so no entry
|
|
105
|
+
// tooltip ever opened — leaving a bare rail of unlabelled icons. Radix decides
|
|
106
|
+
// when this one wants to be open, from the pointer and from focus; the drawer
|
|
107
|
+
// only masks it once the label says the same thing.
|
|
108
|
+
//
|
|
109
|
+
// Masked rather than withheld: dropping `open`, or the title, would move the
|
|
110
|
+
// tooltip between controlled and uncontrolled as the drawer opens, which
|
|
111
|
+
// strands the old state — a tooltip left up over a pointer nowhere near it.
|
|
73
112
|
return <Tooltip
|
|
74
|
-
open={drawerOpen
|
|
113
|
+
open={!drawerOpen && wantsTooltip}
|
|
114
|
+
onOpenChange={setWantsTooltip}
|
|
75
115
|
side="right"
|
|
76
116
|
title={name}>
|
|
77
117
|
{listItem}
|
|
@@ -38,13 +38,15 @@ import {
|
|
|
38
38
|
useSlot,
|
|
39
39
|
getIcon
|
|
40
40
|
} from "@rebasepro/app";
|
|
41
|
-
import { getEntityFromMemoryCache } from "@rebasepro/app";
|
|
41
|
+
import { getEntityFromMemoryCache, removeEntityFromMemoryCache, saveEntityToMemoryCache } from "@rebasepro/app";
|
|
42
42
|
import { EntityFormBinding } from "../form";
|
|
43
|
+
import { getEditHandoffValues } from "../form/form_utils";
|
|
43
44
|
import type { EntityFormBindingProps } from "../form";
|
|
44
45
|
import type { OnUpdateParams } from "../types/components/EntityFormProps";
|
|
45
46
|
import { EditFormActions } from "./EditFormActions";
|
|
46
47
|
import { EntityIdentityBar } from "./EntityIdentityBar";
|
|
47
48
|
import { SplitListCloseButton } from "./CollectionViewBinding/SplitListCloseButton";
|
|
49
|
+
import { SplitListShowButton } from "./CollectionViewBinding/SplitListShowButton";
|
|
48
50
|
import { useRecordActions } from "../hooks/useRecordActions";
|
|
49
51
|
import { useSideDialogContext } from "./SideDialogs";
|
|
50
52
|
import { useAdminContext } from "../hooks/useAdminContext";
|
|
@@ -53,10 +55,17 @@ import { useEntityDisplayTitle } from "../hooks/useEntityDisplayTitle";
|
|
|
53
55
|
import { createFormexStub, getEntityFromCache } from "@rebasepro/app";
|
|
54
56
|
import { usePermissions } from "@rebasepro/app";
|
|
55
57
|
import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
|
|
56
|
-
import {
|
|
58
|
+
import { withListState } from "../util/view_mode";
|
|
57
59
|
import { useNavigate } from "react-router";
|
|
58
60
|
|
|
59
|
-
import {
|
|
61
|
+
import {
|
|
62
|
+
MAIN_TAB_VALUE,
|
|
63
|
+
JSON_TAB_VALUE,
|
|
64
|
+
HISTORY_TAB_VALUE,
|
|
65
|
+
ENTITY_VIEW_TAB_BAR_CLASS,
|
|
66
|
+
ENTITY_VIEW_TAB_CLASS,
|
|
67
|
+
ENTITY_VIEW_TAB_LABEL_CLASS
|
|
68
|
+
} from "../util/view_constants";
|
|
60
69
|
export { MAIN_TAB_VALUE, JSON_TAB_VALUE, HISTORY_TAB_VALUE };
|
|
61
70
|
|
|
62
71
|
export type BarActionsParams = {
|
|
@@ -64,6 +73,19 @@ export type BarActionsParams = {
|
|
|
64
73
|
status: EntityStatus,
|
|
65
74
|
path: string,
|
|
66
75
|
entityId?: string | number;
|
|
76
|
+
/** Whether `values` differ from what is stored. */
|
|
77
|
+
dirty: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Hand the edit in progress to the layout this action is about to open, so
|
|
80
|
+
* the record arrives showing — and still holding — what the user typed here.
|
|
81
|
+
* Call it from any action that changes layout without leaving the record;
|
|
82
|
+
* it decides on its own whether there is anything worth carrying, so a
|
|
83
|
+
* record nobody edited still arrives clean.
|
|
84
|
+
*
|
|
85
|
+
* Only supplied where there is a form to carry: the detail view passes no
|
|
86
|
+
* such thing.
|
|
87
|
+
*/
|
|
88
|
+
carryEdit?: () => void;
|
|
67
89
|
};
|
|
68
90
|
|
|
69
91
|
export type OnTabChangeParams<M extends Record<string, unknown>> = {
|
|
@@ -89,6 +111,16 @@ export interface EditViewBindingProps<M extends Record<string, unknown> = Record
|
|
|
89
111
|
onSaved?: (params: OnUpdateParams) => void;
|
|
90
112
|
onTabChange?: (props: OnTabChangeParams<M>) => void;
|
|
91
113
|
navigateBack?: () => void;
|
|
114
|
+
/**
|
|
115
|
+
* Dismiss this record — the ✕ at the identity bar's trailing edge, and what
|
|
116
|
+
* "save and close" reaches once the save lands.
|
|
117
|
+
*
|
|
118
|
+
* Set by the layouts that own the record's place on screen but are not side
|
|
119
|
+
* dialogs: the split, where dismissing is navigating back to the collection.
|
|
120
|
+
* The side panel and the dialog close through {@link useSideDialogContext}
|
|
121
|
+
* instead, which is what runs their unsaved-changes prompt.
|
|
122
|
+
*/
|
|
123
|
+
onCloseRequest?: () => void;
|
|
92
124
|
layout?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
93
125
|
barActions?: (params: BarActionsParams) => React.ReactNode;
|
|
94
126
|
/**
|
|
@@ -96,6 +128,12 @@ export interface EditViewBindingProps<M extends Record<string, unknown> = Record
|
|
|
96
128
|
* of the breadcrumb — the side panel's close button.
|
|
97
129
|
*/
|
|
98
130
|
barActionsStart?: React.ReactNode;
|
|
131
|
+
/**
|
|
132
|
+
* Full screen only: bring the list back beside this record. Set when the
|
|
133
|
+
* record was opened out of a split collection, so folding the list away is
|
|
134
|
+
* reversible; it takes the place of the back arrow when present.
|
|
135
|
+
*/
|
|
136
|
+
onShowList?: () => void;
|
|
99
137
|
formProps?: Partial<EntityFormBindingProps<M>>,
|
|
100
138
|
/**
|
|
101
139
|
* Pre-populate the form with these values when creating a new entity.
|
|
@@ -127,9 +165,39 @@ export function EditViewBinding<M extends Record<string, unknown>>({
|
|
|
127
165
|
useCache: false
|
|
128
166
|
});
|
|
129
167
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
168
|
+
// The edit handed over by another layout of this same record — see
|
|
169
|
+
// {@link carryEdit} below for the sending half.
|
|
170
|
+
//
|
|
171
|
+
// Read once per record and consumed: this is a handoff, and an edit left in
|
|
172
|
+
// the channel after it has been picked up is one that reopens the record
|
|
173
|
+
// dirty on a later, unrelated visit, carrying values the user walked away
|
|
174
|
+
// from. Held in a ref rather than re-read each render, because the read has
|
|
175
|
+
// to survive the clear — {@link EntityFormBinding} measures the
|
|
176
|
+
// local-changes banner against what the form opens showing, and a prop that
|
|
177
|
+
// reverted to `undefined` once the cache was cleared would raise the banner
|
|
178
|
+
// over the very edit that was just carried in.
|
|
179
|
+
//
|
|
180
|
+
// A copy is keyed with the new records, not under the record it was copied
|
|
181
|
+
// from: it is going to be stored as its own row, and both the handoff and
|
|
182
|
+
// the local-changes backup have always keyed it that way from the writing
|
|
183
|
+
// side. Reading it under `path/id` meant a copy in progress was written to
|
|
184
|
+
// one key and looked for under another.
|
|
185
|
+
const carriesAsNew = props.copy || entityId === undefined;
|
|
186
|
+
const handoffKey = carriesAsNew ? props.path + "#new" : props.path + "/" + entityId;
|
|
187
|
+
const handoff = useRef<{ key: string, values: object | undefined } | null>(null);
|
|
188
|
+
if (handoff.current?.key !== handoffKey) {
|
|
189
|
+
handoff.current = {
|
|
190
|
+
key: handoffKey,
|
|
191
|
+
values: getEntityFromMemoryCache(handoffKey)
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
removeEntityFromMemoryCache(handoffKey);
|
|
196
|
+
}, [handoffKey]);
|
|
197
|
+
|
|
198
|
+
const initialDirtyValues = carriesAsNew
|
|
199
|
+
? (props.defaultValues ?? handoff.current.values)
|
|
200
|
+
: handoff.current.values;
|
|
133
201
|
|
|
134
202
|
const { canEdit: canEditHook } = usePermissions();
|
|
135
203
|
|
|
@@ -186,12 +254,14 @@ export function EditViewBindingInner<M extends Record<string, unknown>>({
|
|
|
186
254
|
onSaved,
|
|
187
255
|
onTabChange,
|
|
188
256
|
navigateBack,
|
|
257
|
+
onCloseRequest,
|
|
189
258
|
entity,
|
|
190
259
|
initialDirtyValues,
|
|
191
260
|
dataLoading,
|
|
192
261
|
layout = "side_panel",
|
|
193
262
|
barActions,
|
|
194
263
|
barActionsStart,
|
|
264
|
+
onShowList,
|
|
195
265
|
status,
|
|
196
266
|
setStatus,
|
|
197
267
|
formProps,
|
|
@@ -271,7 +341,9 @@ parentEntityIds,
|
|
|
271
341
|
}
|
|
272
342
|
}, [selectedTabProp, defaultSelectedView]);
|
|
273
343
|
|
|
274
|
-
|
|
344
|
+
// See DetailViewBinding: tabs are governed by `hideFromEntityViews`, the
|
|
345
|
+
// drawer by `hideFromNavigation`. They are separate roles.
|
|
346
|
+
const childViews = getAdminEntityChildViews(collection).filter(v => !v.collection.hideFromEntityViews);
|
|
275
347
|
const subcollections = childViews.map(v => v.collection);
|
|
276
348
|
const subcollectionsCount = subcollections?.length ?? 0;
|
|
277
349
|
const customViews = collection.entityViews ?? [];
|
|
@@ -405,8 +477,8 @@ parentEntityIds,
|
|
|
405
477
|
|
|
406
478
|
const displayTitle = useEntityDisplayTitle({
|
|
407
479
|
collection,
|
|
480
|
+
entity: usedEntity,
|
|
408
481
|
values: (formContext?.values ?? usedEntity?.values) as Record<string, unknown> | undefined,
|
|
409
|
-
entityId,
|
|
410
482
|
status
|
|
411
483
|
});
|
|
412
484
|
|
|
@@ -420,6 +492,35 @@ parentEntityIds,
|
|
|
420
492
|
|| (status === "existing" && !formex?.dirty)
|
|
421
493
|
);
|
|
422
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Hand this record's edit in progress to the layout about to replace this
|
|
497
|
+
* one — the split's "hide list", full screen's "show list", the side panel's
|
|
498
|
+
* "open full screen". None of them is a navigation *away* from the record:
|
|
499
|
+
* the same record is about to be shown by a different form, and what the
|
|
500
|
+
* user has typed belongs to the record, not to the pane it was typed in.
|
|
501
|
+
*
|
|
502
|
+
* Every control that changes layout has to call this, and none of them is
|
|
503
|
+
* blocked by the unsaved-changes prompt — the prompt asks about leaving, and
|
|
504
|
+
* these do not leave. What used to happen instead is that the edit reached
|
|
505
|
+
* the next layout only as its local-changes backup, which is the channel for
|
|
506
|
+
* a draft left behind by a closed tab: the record reopened *clean*, with a
|
|
507
|
+
* banner offering to apply changes the user had never walked away from.
|
|
508
|
+
*/
|
|
509
|
+
const carryEdit = useCallback(() => {
|
|
510
|
+
const carried = formex
|
|
511
|
+
? getEditHandoffValues<M>({
|
|
512
|
+
status,
|
|
513
|
+
dirty: formex.dirty,
|
|
514
|
+
values: formex.values,
|
|
515
|
+
touched: formex.touched,
|
|
516
|
+
storedValues: usedEntity?.values
|
|
517
|
+
})
|
|
518
|
+
: undefined;
|
|
519
|
+
if (!carried) return;
|
|
520
|
+
const key = (status === "new" || status === "copy") ? path + "#new" : path + "/" + entityId;
|
|
521
|
+
saveEntityToMemoryCache(key, carried);
|
|
522
|
+
}, [formex, status, path, entityId, usedEntity]);
|
|
523
|
+
|
|
423
524
|
const [inspectorTab, setInspectorTab] = useState<InspectorTab | null>(null);
|
|
424
525
|
// A save adds a revision, and the inspector can be open while it happens:
|
|
425
526
|
// Save lives in the identity bar, above the panel. Counting saves gives the
|
|
@@ -429,7 +530,26 @@ parentEntityIds,
|
|
|
429
530
|
/* ---- record actions, in the bar's overflow menu ---------------------- */
|
|
430
531
|
|
|
431
532
|
const sideDialogContext = useSideDialogContext();
|
|
432
|
-
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Whether the save in flight was asked to dismiss the record afterwards.
|
|
536
|
+
*
|
|
537
|
+
* A ref, and for the reason {@link SideDialogs} keeps its own as one: the
|
|
538
|
+
* flag is raised and the form submitted in a single click handler, and the
|
|
539
|
+
* `onSaved` that has to act on it was captured before React could re-render.
|
|
540
|
+
* As state it read one save late — the first "save and close" saved and
|
|
541
|
+
* stayed open, and the *next* save closed the record.
|
|
542
|
+
*
|
|
543
|
+
* The side panel and the dialog use the side-dialog controller's flag,
|
|
544
|
+
* which their own close path consumes; this one covers the layouts that
|
|
545
|
+
* close by calling {@link onCloseRequest} instead.
|
|
546
|
+
*/
|
|
547
|
+
const pendingCloseRef = useRef(false);
|
|
548
|
+
|
|
549
|
+
// The split can close after a save the same way the overlays do — through
|
|
550
|
+
// the callback its layout supplies. Without one there is nothing to close.
|
|
551
|
+
const canCloseAfterSave = layout === "side_panel" || layout === "dialog"
|
|
552
|
+
|| (layout === "split" && Boolean(onCloseRequest));
|
|
433
553
|
|
|
434
554
|
const recordActions = useRecordActions({
|
|
435
555
|
collection,
|
|
@@ -524,35 +644,40 @@ parentEntityIds,
|
|
|
524
644
|
const FormViewBuilder = formViewConfig?.Builder ? resolveComponentRef<EntityCustomViewParams>(formViewConfig.Builder as ComponentRef<EntityCustomViewParams>) : null;
|
|
525
645
|
const formViewIncludeActions = formViewConfig?.includeActions !== false;
|
|
526
646
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
647
|
+
// Without edit permission the record is rendered read-only — the same
|
|
648
|
+
// sections, spans and rail as the form, so losing the permission changes the
|
|
649
|
+
// controls and nothing else. The `h4` that used to head this repeated the
|
|
650
|
+
// collection name the identity bar above it already carries.
|
|
651
|
+
const entityReadOnlyView = !canEdit && entity ? (
|
|
652
|
+
FormViewBuilder && readOnlyFormContext
|
|
653
|
+
? <div className={cls(
|
|
654
|
+
"@container/form flex-1 flex flex-row w-full overflow-y-auto justify-center",
|
|
655
|
+
(canEdit || !mainViewVisible || selectedSecondaryForm) ? "hidden" : ""
|
|
656
|
+
)}>
|
|
657
|
+
{/* Same widths as the default form (see EntityForm). */}
|
|
658
|
+
<div className={"w-full max-w-3xl @7xl/form:max-w-4xl 2xl:max-w-4xl flex flex-col pt-6 pb-16 px-5 sm:px-8"}>
|
|
659
|
+
<ErrorBoundary>
|
|
660
|
+
<Suspense fallback={<CircularProgressCenter />}>
|
|
661
|
+
<FormViewBuilder
|
|
662
|
+
collection={collection}
|
|
663
|
+
parentCollectionSlugs={parentCollectionSlugs} parentEntityIds={parentEntityIds}
|
|
664
|
+
entity={usedEntity}
|
|
665
|
+
modifiedValues={usedEntity?.values}
|
|
666
|
+
formContext={readOnlyFormContext as FormContext<Record<string, unknown>>}
|
|
667
|
+
/>
|
|
668
|
+
</Suspense>
|
|
669
|
+
</ErrorBoundary>
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
: <EntityViewBinding
|
|
673
|
+
className={cls((canEdit || !mainViewVisible || selectedSecondaryForm) ? "hidden" : "")}
|
|
674
|
+
entity={entity}
|
|
675
|
+
path={path}
|
|
676
|
+
collection={collection}
|
|
677
|
+
asPage
|
|
678
|
+
openEntityMode={layout}
|
|
679
|
+
formContext={readOnlyFormContext}/>
|
|
680
|
+
) : null;
|
|
556
681
|
|
|
557
682
|
const entityView = FormViewBuilder ? (
|
|
558
683
|
// formView.Builder replaces the default form
|
|
@@ -609,8 +734,32 @@ parentEntityIds,
|
|
|
609
734
|
...params,
|
|
610
735
|
selectedTab: MAIN_TAB_VALUE === activeTab ? undefined : activeTab
|
|
611
736
|
};
|
|
737
|
+
// Two caller-supplied callbacks for one save, and either may
|
|
738
|
+
// navigate — the layouts' `onSaved` all do. Where both do, the
|
|
739
|
+
// second one's destination is what survives (docs/bug-classes.md
|
|
740
|
+
// #28), so `formProps.onSaved` is deliberately last: it belongs
|
|
741
|
+
// to whoever embedded this view, which outranks the layout.
|
|
612
742
|
onSaved?.(res);
|
|
613
743
|
formProps?.onSaved?.(res);
|
|
744
|
+
|
|
745
|
+
// After `onSaved`, never instead of it: the layout's own handler
|
|
746
|
+
// is what lowers its dirty flag, and closing is a navigation the
|
|
747
|
+
// unsaved-changes blocker would otherwise stop on values that
|
|
748
|
+
// are already stored.
|
|
749
|
+
//
|
|
750
|
+
// And after this handler returns, not inside it. The layouts
|
|
751
|
+
// that close this way close by navigating, and they have just
|
|
752
|
+
// navigated themselves — the split leaves `…/edit` for the
|
|
753
|
+
// record's own URL. Deferring keeps the close unambiguously
|
|
754
|
+
// last, which is what decides it: of two navigations raised for
|
|
755
|
+
// one action the final one survives, measured in
|
|
756
|
+
// `router_two_navigations_one_handler.test.tsx`. It also lets
|
|
757
|
+
// the state those handlers just set settle before the blocker
|
|
758
|
+
// reads it. See docs/bug-classes.md #28.
|
|
759
|
+
if (pendingCloseRef.current) {
|
|
760
|
+
pendingCloseRef.current = false;
|
|
761
|
+
setTimeout(() => onCloseRequest?.(), 0);
|
|
762
|
+
}
|
|
614
763
|
}}
|
|
615
764
|
Builder={resolveComponentRef(selectedSecondaryForm?.Builder as ComponentRef<EntityCustomViewParams<M>> | undefined) as React.ComponentType<EntityCustomViewParams<M>> | undefined}
|
|
616
765
|
/>
|
|
@@ -620,7 +769,7 @@ parentEntityIds,
|
|
|
620
769
|
const icon = getIcon(subcollection.icon, undefined, undefined, "smallest");
|
|
621
770
|
return (
|
|
622
771
|
<Tab
|
|
623
|
-
className=
|
|
772
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, ENTITY_VIEW_TAB_LABEL_CLASS)}
|
|
624
773
|
value={subcollection.slug}
|
|
625
774
|
key={`entity_detail_collection_tab_${subcollection.name}`}>
|
|
626
775
|
<span className="flex items-center gap-1.5">
|
|
@@ -636,7 +785,7 @@ parentEntityIds,
|
|
|
636
785
|
const icon = getIcon(view.icon, undefined, undefined, "smallest");
|
|
637
786
|
return (
|
|
638
787
|
<Tab
|
|
639
|
-
className={!view.tabComponent
|
|
788
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, !view.tabComponent && ENTITY_VIEW_TAB_LABEL_CLASS)}
|
|
640
789
|
value={view.key}
|
|
641
790
|
key={`entity_detail_collection_tab_${view.name}`}>
|
|
642
791
|
{view.tabComponent ?? (
|
|
@@ -653,7 +802,7 @@ parentEntityIds,
|
|
|
653
802
|
const icon = getIcon(view.icon, undefined, undefined, "smallest");
|
|
654
803
|
return (
|
|
655
804
|
<Tab
|
|
656
|
-
className={!view.tabComponent
|
|
805
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, !view.tabComponent && ENTITY_VIEW_TAB_LABEL_CLASS)}
|
|
657
806
|
value={view.key}
|
|
658
807
|
key={`entity_detail_collection_tab_${view.name}`}>
|
|
659
808
|
{view.tabComponent ?? (
|
|
@@ -673,6 +822,7 @@ parentEntityIds,
|
|
|
673
822
|
<IconButton
|
|
674
823
|
size="small"
|
|
675
824
|
onClick={() => {
|
|
825
|
+
carryEdit();
|
|
676
826
|
const editSuffix = collection.defaultEntityAction === "view" ? "/edit" : "";
|
|
677
827
|
const entityUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}${editSuffix}`);
|
|
678
828
|
navigate(`${entityUrl}#full`);
|
|
@@ -687,6 +837,9 @@ parentEntityIds,
|
|
|
687
837
|
|
|
688
838
|
<EntityIdentityBar
|
|
689
839
|
collection={collection as AdminCollection}
|
|
840
|
+
collectionUrl={layout === "full_screen" || layout === "split"
|
|
841
|
+
? withListState(urlController.buildUrlCollectionPath(path))
|
|
842
|
+
: undefined}
|
|
690
843
|
title={displayTitle}
|
|
691
844
|
entityId={status === "existing" ? entityId : undefined}
|
|
692
845
|
status={status}
|
|
@@ -696,29 +849,55 @@ parentEntityIds,
|
|
|
696
849
|
saveDisabled={!canEdit || saveDisabled}
|
|
697
850
|
// Full screen replaces the collection entirely, leaving browser Back
|
|
698
851
|
// as the only route to it. The detail view has carried this arrow
|
|
699
|
-
// for that reason; the edit view is reached the same way.
|
|
700
|
-
|
|
701
|
-
|
|
852
|
+
// for that reason; the edit view is reached the same way. Where the
|
|
853
|
+
// list can be unfolded back beside the record, that chevron reaches
|
|
854
|
+
// the same collection and the arrow beside it is noise.
|
|
855
|
+
onBack={layout === "full_screen" && !onShowList
|
|
856
|
+
? () => navigate(withListState(urlController.buildUrlCollectionPath(path)))
|
|
702
857
|
: undefined}
|
|
703
858
|
onSave={canEdit && formContext ? () => {
|
|
704
859
|
sideDialogContext.setPendingClose?.(false);
|
|
860
|
+
pendingCloseRef.current = false;
|
|
705
861
|
formContext.submit();
|
|
706
862
|
} : undefined}
|
|
707
863
|
onSaveAndClose={canEdit && formContext && canCloseAfterSave ? () => {
|
|
864
|
+
// Lowered again once the submit settles. A submit the form
|
|
865
|
+
// rejects never reaches `onSaved`, so nothing would consume the
|
|
866
|
+
// flag and the *next* save — a keyboard ⌘S, say — would close
|
|
867
|
+
// the panel out from under an edit nobody asked to finish. A
|
|
868
|
+
// save that succeeds has already closed by the time this runs.
|
|
869
|
+
if (layout === "split") {
|
|
870
|
+
pendingCloseRef.current = true;
|
|
871
|
+
Promise.resolve(formContext.submit())
|
|
872
|
+
.finally(() => { pendingCloseRef.current = false; });
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
708
875
|
sideDialogContext.setPendingClose?.(true);
|
|
709
|
-
formContext.submit()
|
|
876
|
+
Promise.resolve(formContext.submit())
|
|
877
|
+
.finally(() => sideDialogContext.setPendingClose?.(false));
|
|
710
878
|
} : undefined}
|
|
879
|
+
// Welded to Save in the split, its own button in the overlays.
|
|
880
|
+
saveAndClosePlacement={layout === "split" ? "menu" : "button"}
|
|
881
|
+
onClose={onCloseRequest}
|
|
711
882
|
onDiscard={canEdit && formContext ? () => formContext.formex.resetForm() : undefined}
|
|
712
883
|
onInspect={includeJsonView ? () => setInspectorTab("json") : undefined}
|
|
713
884
|
onViewHistory={includeHistoryView ? () => setInspectorTab("history") : undefined}
|
|
885
|
+
externalLink={usedEntity
|
|
886
|
+
? customizationController?.entityLinkBuilder?.({ entity: usedEntity })
|
|
887
|
+
: undefined}
|
|
714
888
|
recordActions={recordActionItems}
|
|
715
889
|
pluginActions={formPluginActions}
|
|
716
890
|
leading={<>
|
|
717
891
|
{barActionsStart}
|
|
718
892
|
{/* Split view: closing the list is opening this record
|
|
719
893
|
full screen, and the control for it belongs at this
|
|
720
|
-
bar's leading edge.
|
|
721
|
-
|
|
894
|
+
bar's leading edge. Full screen carries its mirror.
|
|
895
|
+
Both change layout, so both hand the edit over. */}
|
|
896
|
+
{layout === "split" && <SplitListCloseButton onBeforeHide={carryEdit}/>}
|
|
897
|
+
{layout === "full_screen" && onShowList && <SplitListShowButton onClick={() => {
|
|
898
|
+
carryEdit();
|
|
899
|
+
onShowList();
|
|
900
|
+
}}/>}
|
|
722
901
|
</>}
|
|
723
902
|
trailing={<>
|
|
724
903
|
{pluginActionsTop}
|
|
@@ -727,7 +906,9 @@ parentEntityIds,
|
|
|
727
906
|
path,
|
|
728
907
|
entityId,
|
|
729
908
|
values: formContext?.values ?? usedEntity?.values ?? {},
|
|
730
|
-
status
|
|
909
|
+
status,
|
|
910
|
+
dirty: Boolean(formContext?.formex?.dirty),
|
|
911
|
+
carryEdit
|
|
731
912
|
})}
|
|
732
913
|
</>}
|
|
733
914
|
/>
|
|
@@ -737,10 +918,11 @@ parentEntityIds,
|
|
|
737
918
|
disappears entirely when there is nowhere else to go. */}
|
|
738
919
|
{hasAdditionalViews && <div className={cls(
|
|
739
920
|
"h-10 shrink-0 flex items-stretch border-b px-2 min-w-0",
|
|
921
|
+
ENTITY_VIEW_TAB_BAR_CLASS,
|
|
740
922
|
defaultBorderMixin
|
|
741
923
|
)}>
|
|
742
924
|
<Tabs
|
|
743
|
-
className={"!w-full"}
|
|
925
|
+
className={"!w-full items-stretch"}
|
|
744
926
|
innerClassName={"h-full"}
|
|
745
927
|
variant={"boxy"}
|
|
746
928
|
value={activeTab}
|
|
@@ -748,7 +930,8 @@ parentEntityIds,
|
|
|
748
930
|
onSideTabClick(value);
|
|
749
931
|
}}>
|
|
750
932
|
|
|
751
|
-
<Tab value={MAIN_TAB_VALUE}
|
|
933
|
+
<Tab value={MAIN_TAB_VALUE}
|
|
934
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, ENTITY_VIEW_TAB_LABEL_CLASS)}>
|
|
752
935
|
<span className="flex items-center gap-1.5">
|
|
753
936
|
{getIcon(collection.icon, undefined, undefined, "smallest")}
|
|
754
937
|
{collection.singularName ?? collection.name}
|