@rebasepro/admin 0.13.1-canary.gef9608c → 0.14.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-7b-kSLno.js → CollectionEditorDialog-CSheud_E.js} +19 -9
- package/dist/CollectionEditorDialog-CSheud_E.js.map +1 -0
- package/dist/{PropertyEditView-C-31uvpG.js → PropertyEditView-CWZlC1gq.js} +5 -5
- package/dist/PropertyEditView-CWZlC1gq.js.map +1 -0
- package/dist/{RouterCollectionsStudioView-BJylABq3.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/ClearFilterSortButton.d.ts +7 -1
- 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 +26 -0
- package/dist/components/CollectionViewBinding/SortButton.d.ts +7 -1
- package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +17 -6
- package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +1 -1
- package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +26 -1
- package/dist/components/DetailViewBinding.d.ts +7 -0
- package/dist/components/DrawerNavigationGroup.d.ts +4 -2
- package/dist/components/DrawerNavigationItem.d.ts +13 -5
- package/dist/components/EditViewBinding.d.ts +22 -7
- package/dist/components/EntityIdentityBar.d.ts +24 -1
- package/dist/components/RelationSelector.d.ts +13 -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-C-XG3aRh.js → export-L53WektO.js} +175 -39
- package/dist/export-L53WektO.js.map +1 -0
- package/dist/form/form_utils.d.ts +38 -1
- package/dist/{history-DfMuvPfr.js → history-D5SKygpJ.js} +2 -2
- package/dist/{history-DfMuvPfr.js.map → history-D5SKygpJ.js.map} +1 -1
- package/dist/{import-B0GyllB0.js → import-C_4edkoD.js} +5 -4
- package/dist/import-C_4edkoD.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +96 -80
- package/dist/index.js.map +1 -1
- package/dist/preview/components/date_fns_locales.d.ts +9 -0
- package/dist/util/view_constants.d.ts +24 -0
- package/dist/util/view_mode.d.ts +18 -0
- package/dist/{util-DHNXodBo.js → util-C-D5yD2n.js} +2173 -673
- 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/ClearFilterSortButton.tsx +16 -7
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +2 -2
- 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 +18 -1
- package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +13 -7
- package/src/components/CollectionViewBinding/CollectionCardViewBinding.tsx +2 -1
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +816 -335
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +1 -1
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +26 -6
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +9 -0
- package/src/components/CollectionViewBinding/EntityCardBinding.tsx +45 -10
- package/src/components/CollectionViewBinding/SearchExplanation.tsx +106 -0
- package/src/components/CollectionViewBinding/SearchHighlight.tsx +311 -0
- package/src/components/CollectionViewBinding/SlotValue.tsx +96 -0
- package/src/components/CollectionViewBinding/SortButton.tsx +16 -4
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +23 -9
- package/src/components/CollectionViewBinding/SplitListShowButton.tsx +3 -3
- package/src/components/CollectionViewBinding/SplitListView.tsx +36 -9
- package/src/components/CollectionViewBinding/ViewModeToggle.tsx +10 -5
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +11 -3
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +76 -24
- package/src/components/CollectionViewBinding/utils.ts +7 -4
- package/src/components/DefaultDrawer.tsx +7 -0
- package/src/components/DetailViewBinding.tsx +33 -9
- package/src/components/DrawerNavigationGroup.tsx +12 -32
- package/src/components/DrawerNavigationItem.tsx +33 -12
- package/src/components/EditViewBinding.tsx +175 -28
- package/src/components/EntityIdentityBar.tsx +109 -17
- package/src/components/EntityInspector.tsx +24 -6
- package/src/components/EntityViewBinding.tsx +4 -2
- package/src/components/ReferenceTable/SelectionTableBinding.tsx +4 -2
- package/src/components/RelationSelector.tsx +61 -11
- package/src/components/SearchIconsView.tsx +14 -33
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -0
- package/src/components/SidePanelBinding.tsx +36 -25
- package/src/components/UserSelector.tsx +52 -9
- package/src/components/history/LastEditedByIndicator.tsx +7 -12
- 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 +12 -3
- package/src/form/EntityFormBinding.tsx +8 -12
- package/src/form/PropertyFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -2
- package/src/form/field_bindings/BinaryFieldBinding.tsx +1 -0
- package/src/form/field_bindings/BlockFieldBinding.tsx +3 -3
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +4 -0
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +3 -0
- package/src/form/field_bindings/MapFieldBinding.tsx +2 -2
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +15 -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/VectorFieldBinding.tsx +1 -0
- package/src/form/form_utils.ts +78 -0
- package/src/index.ts +9 -1
- package/src/preview/PropertyPreview.tsx +7 -3
- 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/date_fns_locales.ts +125 -0
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +6 -3
- package/src/util/navigation_utils.ts +4 -2
- 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-7b-kSLno.js.map +0 -1
- package/dist/PropertyEditView-C-31uvpG.js.map +0 -1
- package/dist/RouterCollectionsStudioView-BJylABq3.js.map +0 -1
- package/dist/export-C-XG3aRh.js.map +0 -1
- package/dist/import-B0GyllB0.js.map +0 -1
- package/dist/util-DHNXodBo.js.map +0 -1
package/dist/{RouterCollectionsStudioView-BJylABq3.js → RouterCollectionsStudioView-CwTpdzWF.js}
RENAMED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import {
|
|
3
|
-
import { s as AIModifiedPathsProvider } from "./PropertyEditView-
|
|
4
|
-
import { t as CollectionEditor } from "./CollectionEditorDialog-
|
|
2
|
+
import { ln as useUrlController } from "./util-C-D5yD2n.js";
|
|
3
|
+
import { s as AIModifiedPathsProvider } from "./PropertyEditView-CWZlC1gq.js";
|
|
4
|
+
import { t as CollectionEditor } from "./CollectionEditorDialog-CSheud_E.js";
|
|
5
5
|
import { useEffect, useState } from "react";
|
|
6
6
|
import { Button, IconButton, PlusIcon, ResizablePanels, Tooltip, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
7
7
|
import { IconForView, UnsavedChangesDialog, useUnsavedChangesDialog } from "@rebasepro/app";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { readStoredString, writeStoredString } from "@rebasepro/utils";
|
|
9
10
|
import { useLocation, useNavigate } from "react-router";
|
|
10
11
|
//#region src/collection_editor/ui/collection_editor/CollectionStudioView.tsx
|
|
11
12
|
function CollectionStudioView({ collectionId, onSave, onCancel, ...props }) {
|
|
@@ -74,17 +75,12 @@ function CollectionsStudioView({ configController, collections: collectionsProp,
|
|
|
74
75
|
};
|
|
75
76
|
const collections = collectionsProp ?? configController.collections ?? [];
|
|
76
77
|
const [sidebarSize, setSidebarSize] = useState(() => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
} catch (e) {
|
|
81
|
-
return 25;
|
|
82
|
-
}
|
|
78
|
+
const saved = readStoredString("rebase_collections_editor_sidebar_size");
|
|
79
|
+
const parsed = saved === null ? NaN : parseFloat(saved);
|
|
80
|
+
return Number.isFinite(parsed) && parsed > 0 && parsed < 100 ? parsed : 25;
|
|
83
81
|
});
|
|
84
82
|
useEffect(() => {
|
|
85
|
-
|
|
86
|
-
localStorage.setItem("rebase_collections_editor_sidebar_size", sidebarSize.toString());
|
|
87
|
-
} catch (e) {}
|
|
83
|
+
writeStoredString("rebase_collections_editor_sidebar_size", sidebarSize.toString());
|
|
88
84
|
}, [sidebarSize]);
|
|
89
85
|
return /* @__PURE__ */ jsx("div", {
|
|
90
86
|
className: "flex h-full w-full bg-surface-50 dark:bg-surface-800 overflow-hidden text-text-primary dark:text-text-primary-dark",
|
|
@@ -189,4 +185,4 @@ function RouterCollectionsStudioView(props) {
|
|
|
189
185
|
//#endregion
|
|
190
186
|
export { CollectionStudioView as i, RouterCollectionsStudioView_exports as n, CollectionsStudioView as r, RouterCollectionsStudioView as t };
|
|
191
187
|
|
|
192
|
-
//# sourceMappingURL=RouterCollectionsStudioView-
|
|
188
|
+
//# sourceMappingURL=RouterCollectionsStudioView-CwTpdzWF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RouterCollectionsStudioView-CwTpdzWF.js","names":[],"sources":["../src/collection_editor/ui/collection_editor/CollectionStudioView.tsx","../src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx","../src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx"],"sourcesContent":["\nimport { useUnsavedChangesDialog, UnsavedChangesDialog } from \"@rebasepro/app\";\nimport * as React from \"react\";\nimport { useState } from \"react\";\nimport { Button, PlusIcon, Typography } from \"@rebasepro/ui\";\nimport { CollectionEditorDialogProps } from \"./CollectionEditorDialog\";\nimport { AIModifiedPathsProvider } from \"./AIModifiedPathsContext\";\nimport { CollectionEditor } from \"./CollectionEditorDialog\";\nimport type { AdminCollection } from \"@rebasepro/admin-types\";\n\nexport type CollectionStudioViewProps = Omit<CollectionEditorDialogProps, \"open\" | \"isNewCollection\" | \"editedCollectionId\" | \"handleClose\" | \"handleCancel\"> & {\n collectionId?: string | \"new\";\n\n /**\n * Called after a successful save.\n * Receives the saved collection (or undefined if dismissed without saving).\n * When not provided, save completes silently.\n */\n onSave?: (collection?: AdminCollection) => void;\n\n /**\n * Called when the user cancels editing.\n * When not provided, cancel is a no-op.\n */\n onCancel?: () => void;\n};\n\nexport function CollectionStudioView({ collectionId, onSave, onCancel, ...props }: CollectionStudioViewProps) {\n\n // Form state from the editor\n const [formDirty, setFormDirty] = useState<boolean>(false);\n const [cancelRequested, setCancelRequested] = useState<boolean>(false);\n\n const { dialogProps, triggerDialog } = useUnsavedChangesDialog(\n formDirty,\n () => setFormDirty(false)\n );\n\n const activeCollectionId = collectionId;\n\n const handleCancelClick = () => {\n if (!formDirty) {\n onCancel?.();\n } else {\n setCancelRequested(true);\n triggerDialog();\n }\n };\n\n return (\n <div className=\"flex-grow flex flex-col h-full w-full bg-surface-50 dark:bg-surface-800\">\n <AIModifiedPathsProvider>\n {activeCollectionId ? (\n <CollectionEditor\n key={activeCollectionId}\n {...props}\n fullScreen={true}\n open={true}\n isNewCollection={activeCollectionId === \"new\"}\n editedCollectionId={activeCollectionId !== \"new\" ? activeCollectionId : undefined}\n handleCancel={handleCancelClick}\n handleClose={(savedCollection) => {\n setFormDirty(false);\n if (savedCollection) {\n setTimeout(() => {\n onSave?.(savedCollection);\n }, 0);\n }\n }}\n setFormDirty={setFormDirty}\n />\n ) : (\n <div className=\"flex-grow flex flex-col items-center justify-center h-full gap-4\">\n <Typography variant=\"label\">\n Select a collection or create a new one\n </Typography>\n <Button\n disabled={props.configController?.readOnly}\n onClick={() => onSave?.()}\n >\n <PlusIcon/>\n Add new collection\n </Button>\n </div>\n )}\n\n <UnsavedChangesDialog\n {...dialogProps}\n handleOk={() => {\n dialogProps.handleOk();\n if (cancelRequested) {\n onCancel?.();\n setCancelRequested(false);\n }\n }}\n handleCancel={() => {\n dialogProps.handleCancel();\n setCancelRequested(false);\n }}\n />\n </AIModifiedPathsProvider>\n </div>\n );\n}\n","\nimport { IconForView } from \"@rebasepro/app\";\nimport React, { useState, useEffect } from \"react\";\nimport {\n Button,\n cls,\n defaultBorderMixin,\n IconButton,\n iconSize,\n PlusIcon,\n ResizablePanels,\n Tooltip,\n Typography\n} from \"@rebasepro/ui\";\n\nimport { CollectionsConfigController } from \"../../types/config_controller\";\nimport { CollectionStudioView } from \"./CollectionStudioView\";\nimport type { CollectionEditorExtensionProps } from \"../../extensibility_types\";\nimport type { AdminCollection } from \"@rebasepro/admin-types\";\nimport { readStoredString, writeStoredString } from \"@rebasepro/utils\";\n\nexport interface CollectionsStudioViewProps extends CollectionEditorExtensionProps {\n configController: CollectionsConfigController;\n\n /**\n * Collections to show in the sidebar.\n * When provided, overrides the collections from `configController`.\n * Use this to control exactly which collections the editor displays.\n */\n collections?: AdminCollection[];\n\n /**\n * Controlled active collection ID.\n * When provided together with `onActiveCollectionChange`,\n * navigation is fully external — no internal state is used.\n */\n activeCollectionId?: string;\n\n /**\n * Called when the user clicks a collection, \"new\", or navigates.\n * When provided, the component is fully controlled (no internal state).\n * When not provided, the component manages selection via internal state.\n *\n * Pass `\"new\"` to create a new collection. Pass `undefined` to deselect.\n */\n onActiveCollectionChange?: (collectionId: string | undefined) => void;\n}\n\nexport function CollectionsStudioView({\n configController,\n collections: collectionsProp,\n activeCollectionId: controlledActiveId,\n onActiveCollectionChange,\n propertyTypePresets,\n hiddenPropertyTypes,\n renderExtraPropertyFields,\n renderExtraCollectionFields,\n visibleTabs,\n standalone,\n}: CollectionsStudioViewProps) {\n\n // ── Navigation state ────────────────────────────────────────────────\n // If onActiveCollectionChange is provided, the component is controlled.\n // Otherwise, use internal state.\n const [internalActiveId, setInternalActiveId] = useState<string | undefined>(undefined);\n const isControlled = onActiveCollectionChange !== undefined;\n const activeCollectionId = isControlled ? controlledActiveId : internalActiveId;\n\n const setActiveCollectionId = (id: string | undefined) => {\n if (isControlled) {\n onActiveCollectionChange?.(id);\n } else {\n setInternalActiveId(id);\n }\n };\n\n // ── Collections list ────────────────────────────────────────────────\n const collections = collectionsProp ?? configController.collections ?? [];\n\n // ── Sidebar sizing ──────────────────────────────────────────────────\n // Checked, not just parsed: `parseFloat` answers NaN for anything it cannot\n // read, and a NaN pane size lays the sidebar out to nothing — with the bad\n // value still in storage on reload. `SplitListView.getSavedPanelSize` is the\n // same read, done this way.\n const [sidebarSize, setSidebarSize] = useState(() => {\n const saved = readStoredString(\"rebase_collections_editor_sidebar_size\");\n const parsed = saved === null ? NaN : parseFloat(saved);\n return Number.isFinite(parsed) && parsed > 0 && parsed < 100 ? parsed : 25;\n });\n\n useEffect(() => {\n writeStoredString(\"rebase_collections_editor_sidebar_size\", sidebarSize.toString());\n }, [sidebarSize]);\n\n return (\n <div className=\"flex h-full w-full bg-surface-50 dark:bg-surface-800 overflow-hidden text-text-primary dark:text-text-primary-dark\">\n <ResizablePanels\n orientation=\"horizontal\"\n panelSizePercent={sidebarSize}\n onPanelSizeChange={setSidebarSize}\n minPanelSizePx={220}\n firstPanel={\n <div className={cls(\"flex flex-col h-full w-full bg-surface-50 dark:bg-surface-800 border-r\", defaultBorderMixin)}>\n <div className={cls(\"flex items-center justify-between px-3 py-2 border-b bg-surface-50 dark:bg-surface-900 min-h-[48px]\", defaultBorderMixin)}>\n <Typography variant=\"caption\" className=\"font-semibold text-[11px] uppercase tracking-wider text-surface-400 dark:text-surface-400\">\n Collections\n </Typography>\n <Tooltip title={configController.readOnly ? configController.readOnlyReason || \"Read only\" : \"Add collection\"}>\n <div>\n <IconButton\n size=\"small\"\n disabled={configController.readOnly}\n onClick={() => setActiveCollectionId(\"new\")}\n className={activeCollectionId === \"new\" ? \"text-primary dark:text-primary-dark\" : \"text-text-secondary dark:text-text-secondary-dark\"}\n >\n <PlusIcon size={iconSize.smallest}/>\n </IconButton>\n </div>\n </Tooltip>\n </div>\n\n <div className=\"flex-grow overflow-y-auto w-full no-scrollbar p-2 space-y-0.5\">\n {collections.length === 0 && (\n <div className=\"p-4 text-center\">\n <Typography variant=\"caption\" className=\"text-text-disabled dark:text-text-disabled-dark italic\">\n No collections yet.\n </Typography>\n </div>\n )}\n {collections.map((collection) => {\n const collectionKey = collection.slug;\n const isSelected = activeCollectionId === collectionKey;\n return (\n <div\n key={collectionKey}\n onClick={() => setActiveCollectionId(collectionKey)}\n className={cls(\n \"flex items-center gap-2.5 px-3 h-[30px] cursor-pointer rounded-lg text-[13px] font-medium transition-colors\",\n isSelected\n ? \"bg-primary/8 text-primary dark:bg-primary/10 dark:text-primary-light font-semibold\"\n : \"hover:bg-primary/5 dark:hover:bg-primary/5 text-surface-700 dark:text-surface-300 hover:text-surface-900 dark:hover:text-white\"\n )}\n >\n <IconForView collectionOrView={collection} size={\"smallest\"} className={cls(\n isSelected\n ? \"text-primary dark:text-primary-light\"\n : \"text-surface-500 dark:text-text-secondary-dark\"\n )}/>\n <span className=\"truncate flex-1\">\n {collection.name || collection.slug}\n </span>\n </div>\n );\n })}\n </div>\n </div>\n }\n secondPanel={\n <div className=\"flex-grow flex flex-col min-w-0 h-full w-full\">\n {/* We use key to force unmount when switching collections, preventing stale state */}\n {activeCollectionId ? (\n <CollectionStudioView\n key={activeCollectionId}\n configController={configController}\n collectionId={activeCollectionId}\n onSave={(savedCollection) => {\n // After creating a new collection, switch to it\n if (activeCollectionId === \"new\" && savedCollection?.slug) {\n setActiveCollectionId(savedCollection.slug);\n }\n }}\n onCancel={() => setActiveCollectionId(undefined)}\n propertyTypePresets={propertyTypePresets}\n hiddenPropertyTypes={hiddenPropertyTypes}\n renderExtraPropertyFields={renderExtraPropertyFields}\n renderExtraCollectionFields={renderExtraCollectionFields}\n visibleTabs={visibleTabs}\n standalone={standalone}\n />\n ) : (\n <div className=\"flex flex-col items-center justify-center h-full gap-4\">\n <Typography variant=\"label\">\n Select a collection or create a new one to start editing\n </Typography>\n <Button\n disabled={configController.readOnly}\n onClick={() => setActiveCollectionId(\"new\")}\n >\n <PlusIcon/>\n Add new collection\n </Button>\n </div>\n )}\n </div>\n }\n />\n </div>\n );\n}\n","/**\n * Router-aware wrapper around CollectionsStudioView.\n *\n * This component provides the react-router-based navigation behavior\n * that Rebase Studio expects: the active collection is derived from the\n * URL, and clicking a collection navigates via react-router.\n *\n * External consumers should use `CollectionsStudioView` directly\n * (which has no react-router dependency).\n */\nimport React from \"react\";\nimport { useLocation, useNavigate } from \"react-router\";\nimport { useUrlController } from \"../../_cms_internals\";\nimport { CollectionsStudioView, CollectionsStudioViewProps } from \"./CollectionsStudioView\";\n\nexport type RouterCollectionsStudioViewProps = Omit<CollectionsStudioViewProps, \"activeCollectionId\" | \"onActiveCollectionChange\">;\n\nexport function RouterCollectionsStudioView(props: RouterCollectionsStudioViewProps) {\n const navigate = useNavigate();\n const urlController = useUrlController();\n const location = useLocation();\n\n // Determine the active collection from the URL segment after \"schema/\"\n const basePath = urlController.buildAppUrlPath(\"schema\");\n const relativePath = location.pathname.replace(basePath, \"\").replace(/^\\//, \"\");\n const activeCollectionId = relativePath.split(\"/\")[0] || undefined;\n\n return (\n <CollectionsStudioView\n {...props}\n activeCollectionId={activeCollectionId}\n onActiveCollectionChange={(id) => {\n if (id) {\n navigate(urlController.buildAppUrlPath(`schema/${id}`));\n } else {\n navigate(urlController.buildAppUrlPath(\"schema\"));\n }\n }}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -116,6 +116,7 @@ export interface SerializableAdminRelationOptions extends SerializableAdminBaseO
|
|
|
116
116
|
fixedFilter?: FilterValues<string>;
|
|
117
117
|
includeId?: boolean;
|
|
118
118
|
includeEntityLink?: boolean;
|
|
119
|
+
renderInForm?: boolean;
|
|
119
120
|
}
|
|
120
121
|
/** JSON-serializable version of `AdminArrayOptions`. */
|
|
121
122
|
export interface SerializableAdminArrayOptions extends SerializableAdminBaseOptions {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as PropertyForm, r as PropertyFormDialog } from "./PropertyEditView-
|
|
3
|
-
import { n as CollectionEditorDialog } from "./CollectionEditorDialog-
|
|
4
|
-
import { i as CollectionStudioView, r as CollectionsStudioView, t as RouterCollectionsStudioView } from "./RouterCollectionsStudioView-
|
|
1
|
+
import { ln as useUrlController } from "./util-C-D5yD2n.js";
|
|
2
|
+
import { n as PropertyForm, r as PropertyFormDialog } from "./PropertyEditView-CWZlC1gq.js";
|
|
3
|
+
import { n as CollectionEditorDialog } from "./CollectionEditorDialog-CSheud_E.js";
|
|
4
|
+
import { i as CollectionStudioView, r as CollectionsStudioView, t as RouterCollectionsStudioView } from "./RouterCollectionsStudioView-CwTpdzWF.js";
|
|
5
5
|
import "react";
|
|
6
6
|
import { useSnackbarController } from "@rebasepro/app";
|
|
7
7
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { EntityTableController } from "@rebasepro/admin-types";
|
|
2
|
-
export declare function ClearFilterSortButton({ tableController, enabled }: {
|
|
2
|
+
export declare function ClearFilterSortButton({ tableController, enabled, compact }: {
|
|
3
3
|
enabled: boolean;
|
|
4
4
|
tableController: EntityTableController;
|
|
5
|
+
/**
|
|
6
|
+
* Toolbar showing icon-only controls (split view, or a layout too narrow
|
|
7
|
+
* for the filters button's label). Only decides the icon size, which
|
|
8
|
+
* follows that button — see `CollectionViewStartActions`.
|
|
9
|
+
*/
|
|
10
|
+
compact?: boolean;
|
|
5
11
|
}): import("react").JSX.Element | null;
|
|
@@ -2,6 +2,8 @@ import React from "react";
|
|
|
2
2
|
import { Entity } from "@rebasepro/types";
|
|
3
3
|
import { CollectionSize, AdminCollection } from "@rebasepro/admin-types";
|
|
4
4
|
export type EntityCardBindingProps<M extends Record<string, unknown> = Record<string, unknown>> = {
|
|
5
|
+
/** The active search, so a card can mark the hit and say where it was. */
|
|
6
|
+
searchString?: string;
|
|
5
7
|
entity: Entity<M>;
|
|
6
8
|
collection: AdminCollection<M>;
|
|
7
9
|
onClick?: (entity: Entity<M>) => void;
|
|
@@ -18,4 +20,4 @@ export type EntityCardBindingProps<M extends Record<string, unknown> = Record<st
|
|
|
18
20
|
* Card component for displaying a entity in a grid view.
|
|
19
21
|
* Shows thumbnail, title, and preview properties via the shared slot system.
|
|
20
22
|
*/
|
|
21
|
-
export declare function EntityCardBinding<M extends Record<string, unknown> = Record<string, unknown>>({ entity, collection, onClick, selected, highlighted, onSelectionChange, selectionEnabled, size }: EntityCardBindingProps<M>): React.JSX.Element;
|
|
23
|
+
export declare function EntityCardBinding<M extends Record<string, unknown> = Record<string, unknown>>({ entity, collection, onClick, selected, highlighted, onSelectionChange, selectionEnabled, size, searchString }: EntityCardBindingProps<M>): React.JSX.Element;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { SearchMatch } from "@rebasepro/types";
|
|
3
|
+
/**
|
|
4
|
+
* The one place a view asks "why is this row here?" and gets something to draw.
|
|
5
|
+
*
|
|
6
|
+
* List, cards and board all render the same title-and-subtitle slots over the
|
|
7
|
+
* same machinery. An explanation that shows up in one of them and not the others
|
|
8
|
+
* reads as randomness rather than as a feature, so the decision and the markup
|
|
9
|
+
* live here and each view calls them.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Terms to mark inside values the row already shows, plus the single match
|
|
13
|
+
* worth putting where the subtitle would go — or nothing, when the row is not
|
|
14
|
+
* the product of a search or the hit is already visible.
|
|
15
|
+
*
|
|
16
|
+
* Prefers the server's `_matches`, which knows the stemming the index used, and
|
|
17
|
+
* falls back to reading the row directly. That fallback is not belt-and-braces:
|
|
18
|
+
* rows reach the panel over REST, the realtime socket and the offline cache,
|
|
19
|
+
* and only one of those carries query metadata.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useSearchExplanation(entity: {
|
|
22
|
+
values?: Record<string, unknown>;
|
|
23
|
+
searchMatches?: SearchMatch[];
|
|
24
|
+
}, properties: Record<string, unknown> | undefined, shownKeys: (string | undefined)[], searchString: string | undefined): {
|
|
25
|
+
terms: string[];
|
|
26
|
+
offSlot: SearchMatch | undefined;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Which field the hit was in, and the text around it.
|
|
30
|
+
*
|
|
31
|
+
* Sits where the subtitle would have gone. Deliberately quiet — a small amber
|
|
32
|
+
* caption and one line — because it is context for a result, not the result. It
|
|
33
|
+
* has to survive a narrow column, so it truncates rather than wraps.
|
|
34
|
+
*/
|
|
35
|
+
export declare function MatchExplanation({ match, label }: {
|
|
36
|
+
match: SearchMatch;
|
|
37
|
+
label: string;
|
|
38
|
+
}): React.JSX.Element;
|
|
39
|
+
/**
|
|
40
|
+
* What to call a matched field on screen.
|
|
41
|
+
*
|
|
42
|
+
* The path is what the config and the server speak — `questionnaire.
|
|
43
|
+
* certifications` — and is unreadable in a list. Resolve it to the property's
|
|
44
|
+
* declared `name`, walking into `map` properties for a dotted path, and fall
|
|
45
|
+
* back to the last segment humanised, so an unnamed field still reads as words
|
|
46
|
+
* rather than as a path.
|
|
47
|
+
*/
|
|
48
|
+
export declare function fieldLabel(properties: Record<string, unknown> | undefined, path: string): string;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { SearchMatch } from "@rebasepro/types";
|
|
3
|
+
/**
|
|
4
|
+
* Pull the first mark near the front of the snippet.
|
|
5
|
+
*
|
|
6
|
+
* The snippet renders on one truncated line whose width is not knowable here —
|
|
7
|
+
* a list column is wide, the same list beside an open record is not. Whatever
|
|
8
|
+
* precedes the mark competes with it for that width, and when the mark loses
|
|
9
|
+
* the reader gets a fragment of their record with nothing highlighted in it,
|
|
10
|
+
* which reads as the feature being broken rather than as a narrow column.
|
|
11
|
+
*
|
|
12
|
+
* `ts_headline` centres its fragment on the match, so roughly half the window
|
|
13
|
+
* is lead-in; that is the right shape for a paragraph and the wrong one for a
|
|
14
|
+
* line. Trimming here rather than asking Postgres for a smaller window keeps
|
|
15
|
+
* the trailing context, which is what actually explains the hit — and applies
|
|
16
|
+
* the same shape to the snippets built in the browser, so a row looks the same
|
|
17
|
+
* whichever path produced it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function trimSnippetLead(text: string, lead?: number): string;
|
|
20
|
+
export declare function Snippet({ text, className }: {
|
|
21
|
+
text: string;
|
|
22
|
+
className?: string;
|
|
23
|
+
}): React.JSX.Element;
|
|
24
|
+
/**
|
|
25
|
+
* Mark every occurrence of the searched terms inside a plain value.
|
|
26
|
+
*
|
|
27
|
+
* Used for values the list already renders, where the server's snippet would be
|
|
28
|
+
* the wrong thing to show: the snippet is truncated to a fragment and folded to
|
|
29
|
+
* the indexed form, while the cell should keep the record's real text. So the
|
|
30
|
+
* terms are re-matched here, on the display value.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately dumber than the server's matching, and it has to be: this cannot
|
|
33
|
+
* stem or unaccent without reimplementing the text search configuration in the
|
|
34
|
+
* browser and getting it subtly different. It marks what it can find as a
|
|
35
|
+
* prefix, case- and accent-insensitively, and marks nothing when it cannot —
|
|
36
|
+
* a highlight that is merely absent reads as "no hit in this field", which is
|
|
37
|
+
* the honest answer for a stem the browser cannot reproduce.
|
|
38
|
+
*/
|
|
39
|
+
export declare function Highlighted({ text, terms, className }: {
|
|
40
|
+
text: string;
|
|
41
|
+
terms: string[];
|
|
42
|
+
className?: string;
|
|
43
|
+
}): React.JSX.Element;
|
|
44
|
+
interface Segment {
|
|
45
|
+
text: string;
|
|
46
|
+
hit: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Split `text` into hit / non-hit runs.
|
|
50
|
+
*
|
|
51
|
+
* Matches on the *folded* form but slices the original, so the marked text
|
|
52
|
+
* keeps its accents and case — `Gestión`, not `gestion`. Folding is
|
|
53
|
+
* length-preserving for the diacritics this handles, which is what makes the
|
|
54
|
+
* indices transferable; a fold that changed length would misalign the slices.
|
|
55
|
+
*/
|
|
56
|
+
export declare function splitOnTerms(text: string, terms: string[]): Segment[];
|
|
57
|
+
/**
|
|
58
|
+
* The terms a searcher typed, as things to look for in a rendered value.
|
|
59
|
+
*
|
|
60
|
+
* Quoted phrases stay whole; `-excluded` terms are dropped, since marking a
|
|
61
|
+
* word the user asked *not* to see would be actively wrong. `websearch_to_tsquery`
|
|
62
|
+
* reads the same syntax on the server.
|
|
63
|
+
*/
|
|
64
|
+
export declare function searchTerms(searchString: string | undefined): string[];
|
|
65
|
+
/**
|
|
66
|
+
* The best match to show when the term is nowhere the list already displays.
|
|
67
|
+
*
|
|
68
|
+
* `shownKeys` are the property paths the row is already rendering, so a match
|
|
69
|
+
* there needs no explaining — {@link Highlighted} marks it in place and the
|
|
70
|
+
* subtitle keeps its own job. Anything else is invisible, and one of those is
|
|
71
|
+
* what the subtitle slot gets replaced with.
|
|
72
|
+
*
|
|
73
|
+
* The first such match wins, and the server returns them in the order the
|
|
74
|
+
* collection declared its `search` fields — so the field an author put first is
|
|
75
|
+
* the one a reader sees, rather than whichever the database aggregated first.
|
|
76
|
+
*/
|
|
77
|
+
export declare function offSlotMatch(matches: SearchMatch[] | undefined, shownKeys: (string | undefined)[]): SearchMatch | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Where a search term appears in a row, computed from the row itself.
|
|
80
|
+
*
|
|
81
|
+
* The server can say this too — `_matches`, from `ts_headline` — and its answer
|
|
82
|
+
* is better, because it knows the stemming the index used. This exists because
|
|
83
|
+
* the admin cannot rely on getting it: rows reach the panel over three
|
|
84
|
+
* different transports (REST, the realtime socket, the offline cache) and only
|
|
85
|
+
* one of them carries query metadata. A feature that explains a result on some
|
|
86
|
+
* loads and not others is worse than one that always gives a slightly simpler
|
|
87
|
+
* answer.
|
|
88
|
+
*
|
|
89
|
+
* So: use the server's when it is there, fall back to this. Both produce the
|
|
90
|
+
* same `{ field, snippet }` shape, and this one marks with the same `<mark>`.
|
|
91
|
+
*/
|
|
92
|
+
export declare function localRowMatch(values: Record<string, unknown> | undefined, properties: Record<string, unknown> | undefined, terms: string[], shownKeys: (string | undefined)[]): SearchMatch | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* The text around the first hit, with every hit in view marked.
|
|
95
|
+
*
|
|
96
|
+
* Trimmed to a readable window rather than the whole field: a match in a long
|
|
97
|
+
* body should show the sentence it is in, not the first line of the document,
|
|
98
|
+
* and certainly not the document.
|
|
99
|
+
*/
|
|
100
|
+
export declare function snippetAround(text: string, terms: string[]): string | undefined;
|
|
101
|
+
export {};
|
|
@@ -35,3 +35,29 @@ export interface SlotValueProps {
|
|
|
35
35
|
* seven versions.
|
|
36
36
|
*/
|
|
37
37
|
export declare function SlotValue({ slot, size, textOnly, fill }: SlotValueProps): React.JSX.Element | null;
|
|
38
|
+
/**
|
|
39
|
+
* The `tags` slot, as chips beside the status.
|
|
40
|
+
*
|
|
41
|
+
* Honours both arms the same way the rest of the file does. A declared **path**
|
|
42
|
+
* goes through {@link SlotValue}, so an array of enum values keeps its own
|
|
43
|
+
* colours — that is the stated advantage of naming a property. A **resolver**
|
|
44
|
+
* returns bare strings with no property to render them as, so they become plain
|
|
45
|
+
* chips; a single string is accepted as one tag, which the role's type documents.
|
|
46
|
+
*
|
|
47
|
+
* Lives here rather than in the list, the card and the board card because that is
|
|
48
|
+
* three copies of the same decision, and this file exists to hold exactly one.
|
|
49
|
+
*/
|
|
50
|
+
export declare function TagChips({ slot, max }: {
|
|
51
|
+
slot: PreviewSlot;
|
|
52
|
+
max?: number;
|
|
53
|
+
}): React.JSX.Element | null;
|
|
54
|
+
/**
|
|
55
|
+
* Whether a computed image string can be fetched as-is, or has to be resolved
|
|
56
|
+
* through storage first.
|
|
57
|
+
*
|
|
58
|
+
* Exported because it is the whole decision: rendering either branch needs a
|
|
59
|
+
* provider (a storage source, a DOM), and the thing worth pinning is which
|
|
60
|
+
* branch a given string takes. `//host/x` is protocol-relative and equally
|
|
61
|
+
* fetchable; `data:` and `blob:` are already the bytes.
|
|
62
|
+
*/
|
|
63
|
+
export declare function isFetchableImageUrl(value: string): boolean;
|
|
@@ -8,6 +8,12 @@ export type SortButtonProps<M extends Record<string, unknown>> = {
|
|
|
8
8
|
* the options offered.
|
|
9
9
|
*/
|
|
10
10
|
properties: Properties;
|
|
11
|
+
/**
|
|
12
|
+
* Toolbar showing icon-only controls (split view, or a layout too narrow
|
|
13
|
+
* for the filters button's label). Only decides the icon size, which
|
|
14
|
+
* follows that button — see `CollectionViewStartActions`.
|
|
15
|
+
*/
|
|
16
|
+
compact?: boolean;
|
|
11
17
|
};
|
|
12
18
|
/**
|
|
13
19
|
* Toolbar control that orders the collection by one property.
|
|
@@ -21,4 +27,4 @@ export type SortButtonProps<M extends Record<string, unknown>> = {
|
|
|
21
27
|
* is one click rather than a trip through a direction selector; clicking a
|
|
22
28
|
* different one starts it ascending. The popover stays open through both.
|
|
23
29
|
*/
|
|
24
|
-
export declare function SortButton<M extends Record<string, unknown>>({ tableController, properties }: SortButtonProps<M>): React.JSX.Element | null;
|
|
30
|
+
export declare function SortButton<M extends Record<string, unknown>>({ tableController, properties, compact }: SortButtonProps<M>): React.JSX.Element | null;
|
|
@@ -3,10 +3,21 @@ import React from "react";
|
|
|
3
3
|
* Closes the list of a split view, leaving the open record on its own.
|
|
4
4
|
*
|
|
5
5
|
* It leads the record's own app bar, where the breadcrumb starts — this is the
|
|
6
|
-
* entity pane's old "Open full screen" button, moved to that edge
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* entity pane's old "Open full screen" button, moved to that edge: closing the
|
|
7
|
+
* list and opening the record full screen are the same thing, so there is one
|
|
8
|
+
* control and one route (`#full`) rather than two. Full screen carries the
|
|
9
|
+
* mirrored icon to bring the list back.
|
|
10
|
+
*
|
|
11
|
+
* A pane glyph rather than the double chevron it wore first. Now that the bar
|
|
12
|
+
* ends in a ✕ that dismisses the record, an arrow pointing off the left edge of
|
|
13
|
+
* the record's own bar was the other thing on it that read as "close me" — and
|
|
14
|
+
* it is the one control here that does not act on the record at all.
|
|
11
15
|
*/
|
|
12
|
-
export declare function SplitListCloseButton(
|
|
16
|
+
export declare function SplitListCloseButton({ onBeforeHide }?: {
|
|
17
|
+
/**
|
|
18
|
+
* Run before the layout changes, while this form is still mounted: the edit
|
|
19
|
+
* view uses it to hand its edit in progress to the full-screen form that is
|
|
20
|
+
* about to show the same record.
|
|
21
|
+
*/
|
|
22
|
+
onBeforeHide?: () => void;
|
|
23
|
+
}): React.JSX.Element | null;
|
|
@@ -3,7 +3,7 @@ import React from "react";
|
|
|
3
3
|
* Brings the list back beside a record that was opened full screen.
|
|
4
4
|
*
|
|
5
5
|
* The mirror of {@link SplitListCloseButton}: the same edge of the same bar,
|
|
6
|
-
* the same
|
|
6
|
+
* the same pane glyph opening rather than closing, and the same single route —
|
|
7
7
|
* showing the list is dropping the `#full` hash. It replaces the back arrow
|
|
8
8
|
* where it applies, because the list it unfolds already holds the record.
|
|
9
9
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Property, RelationProperty } from "@rebasepro/types";
|
|
2
2
|
import type { Entity } from "@rebasepro/types";
|
|
3
|
-
import type { PropertyConfig, AdminCollection } from "@rebasepro/admin-types";
|
|
3
|
+
import type { PropertyConfig, AdminCollection, EntityDisplayRole } from "@rebasepro/admin-types";
|
|
4
4
|
import type { AuthController } from "@rebasepro/admin-types";
|
|
5
5
|
import { ChipColorScheme } from "@rebasepro/ui";
|
|
6
6
|
/**
|
|
@@ -17,6 +17,16 @@ export interface PreviewSlot {
|
|
|
17
17
|
property?: Property;
|
|
18
18
|
propertyKey?: string;
|
|
19
19
|
value: unknown;
|
|
20
|
+
/**
|
|
21
|
+
* Which display role this slot fills.
|
|
22
|
+
*
|
|
23
|
+
* Load-bearing for the computed case. Without a property there is nothing to
|
|
24
|
+
* infer from, and "render it as text" is right for a title and wrong for an
|
|
25
|
+
* image: a resolver that returned a URL had that URL printed inside the card's
|
|
26
|
+
* image frame. The role is the one thing that distinguishes them, so it
|
|
27
|
+
* travels with the slot rather than being re-derived at each call site.
|
|
28
|
+
*/
|
|
29
|
+
role?: EntityDisplayRole;
|
|
20
30
|
}
|
|
21
31
|
/**
|
|
22
32
|
* Date slot extends the base with a pre-formatted string.
|
|
@@ -72,6 +82,19 @@ export interface EntityPreviewSlots {
|
|
|
72
82
|
status: PreviewSlot | undefined;
|
|
73
83
|
/** META — date / timestamp */
|
|
74
84
|
date: DatePreviewSlot | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* TAGS — free chips beside the status.
|
|
87
|
+
*
|
|
88
|
+
* Declared only. Every other role has an inference ladder behind it, and
|
|
89
|
+
* this one deliberately does not: there is no "first enum" or "looks like a
|
|
90
|
+
* timestamp" that means *labels*, so guessing would scatter arbitrary chips
|
|
91
|
+
* across every row of every panel that never asked for them.
|
|
92
|
+
*
|
|
93
|
+
* The role existed on `EntityDisplay` and in `useEntityDisplay` before this
|
|
94
|
+
* field did, which is why a declared `display.tags` resolved correctly and
|
|
95
|
+
* then had nowhere to go.
|
|
96
|
+
*/
|
|
97
|
+
tags: PreviewSlot | undefined;
|
|
75
98
|
/** Entity ID (always available) */
|
|
76
99
|
entityId: string | number;
|
|
77
100
|
}
|
|
@@ -88,6 +111,8 @@ export interface CollectionSlotKeys {
|
|
|
88
111
|
relationKeys: string[];
|
|
89
112
|
statusKey: string | undefined;
|
|
90
113
|
dateKey: string | undefined;
|
|
114
|
+
/** Declared only — see {@link EntityPreviewSlots.tags}. */
|
|
115
|
+
tagsKey: string | undefined;
|
|
91
116
|
}
|
|
92
117
|
/**
|
|
93
118
|
* Resolve which property key fills each slot for a given collection.
|
|
@@ -24,6 +24,13 @@ export interface DetailViewBindingProps<M extends Record<string, unknown> = Reco
|
|
|
24
24
|
parentEntityIds: string[];
|
|
25
25
|
onTabChange?: (props: OnTabChangeParams<M>) => void;
|
|
26
26
|
onEditClick?: () => void;
|
|
27
|
+
/**
|
|
28
|
+
* Dismiss this record — the ✕ at the identity bar's trailing edge.
|
|
29
|
+
*
|
|
30
|
+
* Set by the layouts that own the record's place on screen but are not side
|
|
31
|
+
* dialogs: the split, where dismissing is navigating back to the collection.
|
|
32
|
+
*/
|
|
33
|
+
onCloseRequest?: () => void;
|
|
27
34
|
layout?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
28
35
|
barActions?: (params: BarActionsParams) => React.ReactNode;
|
|
29
36
|
/**
|
|
@@ -45,8 +45,10 @@ export interface DrawerNavigationGroupProps {
|
|
|
45
45
|
/**
|
|
46
46
|
* Lucide icon name for the group header, from `NavigationGroupMapping.icon`.
|
|
47
47
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
48
|
+
* It decorates the header and nothing else: the entries below are untouched and
|
|
49
|
+
* keep their own icons. An app that wants the categorised look — rows giving up
|
|
50
|
+
* their icons to indent under the group — builds it in its own drawer, by
|
|
51
|
+
* overriding `Shell.DrawerNavigationItem` and passing `indented`.
|
|
50
52
|
*/
|
|
51
53
|
icon?: string;
|
|
52
54
|
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Props of a drawer navigation row, named so an app overriding
|
|
4
|
+
* `Shell.DrawerNavigationItem` can type its wrapper against them.
|
|
5
|
+
*/
|
|
6
|
+
export type DrawerNavigationItemProps = {
|
|
3
7
|
icon: React.ReactElement;
|
|
4
8
|
name: string;
|
|
5
9
|
/** @deprecated No longer read: the tooltip is uncontrolled. */
|
|
@@ -10,9 +14,13 @@ export declare function DrawerNavigationItem({ name, icon, drawerOpen, url, onCl
|
|
|
10
14
|
url: string;
|
|
11
15
|
onClick?: () => void;
|
|
12
16
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
17
|
+
* Drop the row's icon and indent the label into the space it occupied, so the
|
|
18
|
+
* row reads as a child of the group header above it.
|
|
19
|
+
*
|
|
20
|
+
* Nothing in the framework sets this: the stock drawer always passes `false`,
|
|
21
|
+
* and an app that wants the categorised look opts into it by overriding
|
|
22
|
+
* `Shell.DrawerNavigationItem` and deciding for itself which rows get it.
|
|
16
23
|
*/
|
|
17
24
|
indented?: boolean;
|
|
18
|
-
}
|
|
25
|
+
};
|
|
26
|
+
export declare function DrawerNavigationItem({ name, icon, drawerOpen, url, onClick, indented }: DrawerNavigationItemProps): React.JSX.Element;
|
|
@@ -10,14 +10,19 @@ export type BarActionsParams = {
|
|
|
10
10
|
status: EntityStatus;
|
|
11
11
|
path: string;
|
|
12
12
|
entityId?: string | number;
|
|
13
|
+
/** Whether `values` differ from what is stored. */
|
|
14
|
+
dirty: boolean;
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Hand the edit in progress to the layout this action is about to open, so
|
|
17
|
+
* the record arrives showing — and still holding — what the user typed here.
|
|
18
|
+
* Call it from any action that changes layout without leaving the record;
|
|
19
|
+
* it decides on its own whether there is anything worth carrying, so a
|
|
20
|
+
* record nobody edited still arrives clean.
|
|
21
|
+
*
|
|
22
|
+
* Only supplied where there is a form to carry: the detail view passes no
|
|
23
|
+
* such thing.
|
|
19
24
|
*/
|
|
20
|
-
|
|
25
|
+
carryEdit?: () => void;
|
|
21
26
|
};
|
|
22
27
|
export type OnTabChangeParams<M extends Record<string, unknown>> = {
|
|
23
28
|
path: string;
|
|
@@ -41,6 +46,16 @@ export interface EditViewBindingProps<M extends Record<string, unknown> = Record
|
|
|
41
46
|
onSaved?: (params: OnUpdateParams) => void;
|
|
42
47
|
onTabChange?: (props: OnTabChangeParams<M>) => void;
|
|
43
48
|
navigateBack?: () => void;
|
|
49
|
+
/**
|
|
50
|
+
* Dismiss this record — the ✕ at the identity bar's trailing edge, and what
|
|
51
|
+
* "save and close" reaches once the save lands.
|
|
52
|
+
*
|
|
53
|
+
* Set by the layouts that own the record's place on screen but are not side
|
|
54
|
+
* dialogs: the split, where dismissing is navigating back to the collection.
|
|
55
|
+
* The side panel and the dialog close through {@link useSideDialogContext}
|
|
56
|
+
* instead, which is what runs their unsaved-changes prompt.
|
|
57
|
+
*/
|
|
58
|
+
onCloseRequest?: () => void;
|
|
44
59
|
layout?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
45
60
|
barActions?: (params: BarActionsParams) => React.ReactNode;
|
|
46
61
|
/**
|
|
@@ -67,7 +82,7 @@ export interface EditViewBindingProps<M extends Record<string, unknown> = Record
|
|
|
67
82
|
* a record is opened.
|
|
68
83
|
*/
|
|
69
84
|
export declare function EditViewBinding<M extends Record<string, unknown>>({ entityId, ...props }: EditViewBindingProps<M>): React.JSX.Element;
|
|
70
|
-
export declare function EditViewBindingInner<M extends Record<string, unknown>>({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onValuesModified, onSaved, onTabChange, navigateBack, entity, initialDirtyValues, dataLoading, layout, barActions, barActionsStart, onShowList, status, setStatus, formProps, canEdit }: EditViewBindingProps<M> & {
|
|
85
|
+
export declare function EditViewBindingInner<M extends Record<string, unknown>>({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onValuesModified, onSaved, onTabChange, navigateBack, onCloseRequest, entity, initialDirtyValues, dataLoading, layout, barActions, barActionsStart, onShowList, status, setStatus, formProps, canEdit }: EditViewBindingProps<M> & {
|
|
71
86
|
entity?: Entity<M>;
|
|
72
87
|
initialDirtyValues?: Partial<M>;
|
|
73
88
|
dataLoading: boolean;
|
|
@@ -29,6 +29,29 @@ export interface EntityIdentityBarProps {
|
|
|
29
29
|
* action is "done with this record" rather than "keep editing".
|
|
30
30
|
*/
|
|
31
31
|
onSaveAndClose?: () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Where {@link onSaveAndClose} is offered.
|
|
34
|
+
*
|
|
35
|
+
* `"button"` — its own filled button, and Save demotes to text. For the
|
|
36
|
+
* overlays, where you opened the record to do one thing to it and closing is
|
|
37
|
+
* the dominant intent.
|
|
38
|
+
*
|
|
39
|
+
* `"menu"` — a `▾` welded to the Save button. For the split, where the list
|
|
40
|
+
* is right there and `j`/`k` walk from record to record: there, saving and
|
|
41
|
+
* *staying* is the common case, so it keeps the filled button and dismissing
|
|
42
|
+
* is one step further in. The gesture still exists, so muscle memory carried
|
|
43
|
+
* over from the side panel finds it.
|
|
44
|
+
*/
|
|
45
|
+
saveAndClosePlacement?: "button" | "menu";
|
|
46
|
+
/**
|
|
47
|
+
* Dismiss this record, from the ✕ at the bar's trailing edge.
|
|
48
|
+
*
|
|
49
|
+
* Fenced off from Save by a separator: it is chrome, not the last item in
|
|
50
|
+
* the action row. The panel that supplies it decides what dismissing means —
|
|
51
|
+
* in the split it is a navigation, which the layout's unsaved-changes
|
|
52
|
+
* blocker already guards, so the ✕ cannot drop an edit without asking.
|
|
53
|
+
*/
|
|
54
|
+
onClose?: () => void;
|
|
32
55
|
onBack?: () => void;
|
|
33
56
|
onInspect?: () => void;
|
|
34
57
|
/** Opens the inspector straight on its history tab. */
|
|
@@ -69,4 +92,4 @@ export interface EntityIdentityBarProps {
|
|
|
69
92
|
* editing. They live here instead, and the left half of a bar that was
|
|
70
93
|
* previously empty now does the work.
|
|
71
94
|
*/
|
|
72
|
-
export declare function EntityIdentityBar({ collection, collectionUrl, title, entityId, status, dirty, saving, onSave, onDiscard, saveDisabled, hasErrors, onSaveAndClose, onBack, onInspect, onViewHistory, externalLink, recordActions, pluginActions, trailing, leading }: EntityIdentityBarProps): React.JSX.Element;
|
|
95
|
+
export declare function EntityIdentityBar({ collection, collectionUrl, title, entityId, status, dirty, saving, onSave, onDiscard, saveDisabled, hasErrors, onSaveAndClose, saveAndClosePlacement, onClose, onBack, onInspect, onViewHistory, externalLink, recordActions, pluginActions, trailing, leading }: EntityIdentityBarProps): React.JSX.Element;
|
|
@@ -47,7 +47,20 @@ export interface RelationSelectorProps {
|
|
|
47
47
|
pageSize?: number;
|
|
48
48
|
emptyPlaceholder?: string;
|
|
49
49
|
searchPlaceholder?: string;
|
|
50
|
+
/** Shown in the open list when a search matched nothing. */
|
|
50
51
|
noResultsText?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Shown on the closed trigger when nothing is selected. Says only that the
|
|
54
|
+
* field is empty — see the note by `resolvedPlaceholder` for why it cannot
|
|
55
|
+
* say whether there is anything to pick.
|
|
56
|
+
*/
|
|
57
|
+
emptyText?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Shown in the open list when the target collection turned out to hold no
|
|
60
|
+
* rows at all, as opposed to a search simply not matching. Defaults to a
|
|
61
|
+
* sentence naming the target collection.
|
|
62
|
+
*/
|
|
63
|
+
emptyCollectionText?: string;
|
|
51
64
|
loadingText?: string;
|
|
52
65
|
}
|
|
53
66
|
export declare const RelationSelector: React.ForwardRefExoticComponent<RelationSelectorProps & React.RefAttributes<HTMLButtonElement>>;
|
|
@@ -17,6 +17,13 @@ export interface DownloadEntitiesExportParams<M extends Record<string, unknown>>
|
|
|
17
17
|
export declare function downloadEntitiesExport<M extends Record<string, unknown>>({ data, additionalData, properties, propertiesOrder, name, flattenArrays, additionalHeaders, exportType, dateExportType }: DownloadEntitiesExportParams<M>): void;
|
|
18
18
|
export declare function getEntityCSVExportableData(data: Entity<Record<string, unknown>>[], additionalData: Record<string, unknown>[] | undefined, properties: Properties, headers: Header[], dateExportType: "timestamp" | "string"): unknown[][];
|
|
19
19
|
export declare function getEntityJsonExportableData(data: Entity<Record<string, unknown>>[], additionalData: Record<string, unknown>[] | undefined, properties: Properties, dateExportType: "timestamp" | "string"): Record<string, unknown>[];
|
|
20
|
+
/**
|
|
21
|
+
* Neutralise a cell that a spreadsheet would otherwise evaluate, by prefixing a
|
|
22
|
+
* single quote — the standard OWASP mitigation. The apostrophe is consumed by
|
|
23
|
+
* the spreadsheet as "treat the rest as text", so the value reads back intact.
|
|
24
|
+
*/
|
|
25
|
+
export declare function escapeCsvFormula(value: string): string;
|
|
26
|
+
export declare function entryToCSVRow(entry: unknown[]): string;
|
|
20
27
|
export declare function downloadBlob(content: BlobPart[], filename: string, contentType: string): void;
|
|
21
28
|
export declare function downloadDataAsCsv(data: object[], name: string): void;
|
|
22
29
|
export {};
|