@rebasepro/cms 0.23.0 → 0.24.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-DLHCka00.js → CollectionEditorDialog-p5-I-Apu.js} +81 -42
- package/dist/CollectionEditorDialog-p5-I-Apu.js.map +1 -0
- package/dist/{PropertyEditView-_pu5hzZn.js → PropertyEditView-DDB_X-00.js} +4 -4
- package/dist/PropertyEditView-DDB_X-00.js.map +1 -0
- package/dist/{RouterCollectionsStudioView-ChPNl_uU.js → RouterCollectionsStudioView-SGBV4CMg.js} +4 -4
- package/dist/{RouterCollectionsStudioView-ChPNl_uU.js.map → RouterCollectionsStudioView-SGBV4CMg.js.map} +1 -1
- package/dist/{SchemaChangeDialog-BeP0A4MJ.js → SchemaChangeDialog-CcFSXvkK.js} +33 -12
- package/dist/SchemaChangeDialog-CcFSXvkK.js.map +1 -0
- package/dist/collection_editor/_cms_internals.d.ts +2 -1
- package/dist/collection_editor/liveSchemaClient.d.ts +38 -18
- package/dist/collection_editor/serializable_types.d.ts +2 -0
- package/dist/collection_editor/types/config_controller.d.ts +11 -0
- package/dist/collection_editor/useLiveSchemaEditing.d.ts +5 -5
- package/dist/collection_editor/useLocalCollectionsConfigController.d.ts +7 -0
- package/dist/collection_editor_ui.js +5 -5
- package/dist/components/ClearFilterSortButton.d.ts +7 -1
- package/dist/components/CollectionViewBinding/CollectionViewActions.d.ts +6 -1
- package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +5 -2
- package/dist/components/CollectionViewBinding/FiltersDialog.d.ts +5 -0
- package/dist/components/EditViewBinding.d.ts +7 -1
- package/dist/components/EntityIdentityBar.d.ts +29 -4
- package/dist/components/NavigationLoadingErrorView.d.ts +16 -0
- package/dist/components/common/ActionMenuLabel.d.ts +15 -0
- package/dist/components/common/default_entity_actions.d.ts +37 -0
- package/dist/data_import/components/ImportConversionProblems.d.ts +11 -0
- package/dist/data_import/components/index.d.ts +1 -0
- package/dist/data_import/utils/data.d.ts +81 -4
- package/dist/data_import/utils/import_columns.d.ts +48 -0
- package/dist/editor/editor.d.ts +7 -1
- package/dist/editor/extensions/Image.d.ts +22 -1
- package/dist/editor/hooks/useProseMirror.d.ts +4 -1
- package/dist/editor.js +69 -7
- package/dist/editor.js.map +1 -1
- package/dist/{export-4bJWdlDK.js → export-DCHuPD1a.js} +4 -4
- package/dist/export-DCHuPD1a.js.map +1 -0
- package/dist/form/form_utils.d.ts +24 -0
- package/dist/form/resolve_form_properties.d.ts +26 -0
- package/dist/{history-BElrZ5Rw.js → history-CBd6SBRs.js} +3 -2
- package/dist/{history-BElrZ5Rw.js.map → history-CBd6SBRs.js.map} +1 -1
- package/dist/hooks/useRecordActions.d.ts +28 -6
- package/dist/hooks/useReferencePaths.d.ts +21 -0
- package/dist/hooks/useStorageReferenceResolver.d.ts +23 -0
- package/dist/{import-TCgkTVlS.js → import-DjQoRrIU.js} +34 -52
- package/dist/import-DjQoRrIU.js.map +1 -0
- package/dist/index.js +189 -115
- package/dist/index.js.map +1 -1
- package/dist/preview/components/StorageMarkdown.d.ts +10 -0
- package/dist/preview/util.d.ts +4 -0
- package/dist/util/caller_scopes.d.ts +15 -0
- package/dist/util/entity_actions.d.ts +37 -1
- package/dist/util/fit_inline_actions.d.ts +20 -0
- package/dist/{util-DybQBkug.js → util-BRvKFMLa.js} +4546 -3163
- package/dist/util-BRvKFMLa.js.map +1 -0
- package/dist/{util-CC0ozHoD.js → util-D7ebttKT.js} +6 -2
- package/dist/{util-CC0ozHoD.js.map → util-D7ebttKT.js.map} +1 -1
- package/package.json +9 -9
- package/dist/CollectionEditorDialog-DLHCka00.js.map +0 -1
- package/dist/PropertyEditView-_pu5hzZn.js.map +0 -1
- package/dist/SchemaChangeDialog-BeP0A4MJ.js.map +0 -1
- package/dist/export-4bJWdlDK.js.map +0 -1
- package/dist/import-TCgkTVlS.js.map +0 -1
- package/dist/util-DybQBkug.js.map +0 -1
package/dist/{RouterCollectionsStudioView-ChPNl_uU.js → RouterCollectionsStudioView-SGBV4CMg.js}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
2
|
-
import {
|
|
3
|
-
import { t as CollectionEditor } from "./CollectionEditorDialog-
|
|
4
|
-
import { s as AIModifiedPathsProvider } from "./PropertyEditView-
|
|
2
|
+
import { Bn as useUrlController } from "./util-BRvKFMLa.js";
|
|
3
|
+
import { t as CollectionEditor } from "./CollectionEditorDialog-p5-I-Apu.js";
|
|
4
|
+
import { s as AIModifiedPathsProvider } from "./PropertyEditView-DDB_X-00.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";
|
|
@@ -209,4 +209,4 @@ function RouterCollectionsStudioView(props) {
|
|
|
209
209
|
//#endregion
|
|
210
210
|
export { CollectionStudioView as i, RouterCollectionsStudioView_exports as n, CollectionsStudioView as r, RouterCollectionsStudioView as t };
|
|
211
211
|
|
|
212
|
-
//# sourceMappingURL=RouterCollectionsStudioView-
|
|
212
|
+
//# sourceMappingURL=RouterCollectionsStudioView-SGBV4CMg.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RouterCollectionsStudioView-ChPNl_uU.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 { useEffect, 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/cms-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 collection on screen gains or loses unsaved edits.\n *\n * For whatever decides which collection is on screen: this view is\n * remounted when that changes, and its edits go with it.\n */\n onDirtyChange?: (dirty: boolean) => 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, onDirtyChange, ...props }: CollectionStudioViewProps) {\n\n // Form state from the editor\n const [formDirty, setFormDirty] = useState<boolean>(false);\n\n useEffect(() => {\n onDirtyChange?.(formDirty);\n }, [formDirty, onDirtyChange]);\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-sheet\">\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, UnsavedChangesDialog } 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/cms-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 // ── Unsaved edits ───────────────────────────────────────────────────\n // The editor is keyed by the active id, so selecting another collection\n // remounts it and drops whatever was typed into the one on screen.\n // Controlled, the selection is the caller's navigation and the caller\n // guards it — `RouterCollectionsStudioView` is a route change, which the\n // editor's own navigation blocker asks about. Uncontrolled, it is state\n // this component owns, and nothing else would ask.\n const [editorDirty, setEditorDirty] = useState(false);\n const [pendingSelection, setPendingSelection] = useState<{ id: string | undefined } | null>(null);\n\n const selectCollection = (id: string | undefined) => {\n if (!isControlled && editorDirty && id !== activeCollectionId) {\n setPendingSelection({ id });\n return;\n }\n setActiveCollectionId(id);\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-sheet 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-sheet border-r\", defaultBorderMixin)}>\n <div className={cls(\"flex items-center justify-between px-3 py-2 border-b 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 aria-label={\"Add collection\"}\n disabled={configController.readOnly}\n onClick={() => selectCollection(\"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={() => selectCollection(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-surface-hover 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 onDirtyChange={setEditorDirty}\n // Not through the guard: the editor has already\n // asked about its own edits before cancelling.\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 <UnsavedChangesDialog\n open={pendingSelection !== null}\n body={\"There are unsaved changes in this collection\"}\n handleOk={() => {\n const next = pendingSelection;\n setPendingSelection(null);\n setEditorDirty(false);\n if (next) setActiveCollectionId(next.id);\n }}\n handleCancel={() => setPendingSelection(null)}\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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"}
|
|
1
|
+
{"version":3,"file":"RouterCollectionsStudioView-SGBV4CMg.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 { useEffect, 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/cms-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 collection on screen gains or loses unsaved edits.\n *\n * For whatever decides which collection is on screen: this view is\n * remounted when that changes, and its edits go with it.\n */\n onDirtyChange?: (dirty: boolean) => 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, onDirtyChange, ...props }: CollectionStudioViewProps) {\n\n // Form state from the editor\n const [formDirty, setFormDirty] = useState<boolean>(false);\n\n useEffect(() => {\n onDirtyChange?.(formDirty);\n }, [formDirty, onDirtyChange]);\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-sheet\">\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, UnsavedChangesDialog } 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/cms-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 // ── Unsaved edits ───────────────────────────────────────────────────\n // The editor is keyed by the active id, so selecting another collection\n // remounts it and drops whatever was typed into the one on screen.\n // Controlled, the selection is the caller's navigation and the caller\n // guards it — `RouterCollectionsStudioView` is a route change, which the\n // editor's own navigation blocker asks about. Uncontrolled, it is state\n // this component owns, and nothing else would ask.\n const [editorDirty, setEditorDirty] = useState(false);\n const [pendingSelection, setPendingSelection] = useState<{ id: string | undefined } | null>(null);\n\n const selectCollection = (id: string | undefined) => {\n if (!isControlled && editorDirty && id !== activeCollectionId) {\n setPendingSelection({ id });\n return;\n }\n setActiveCollectionId(id);\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-sheet 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-sheet border-r\", defaultBorderMixin)}>\n <div className={cls(\"flex items-center justify-between px-3 py-2 border-b 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 aria-label={\"Add collection\"}\n disabled={configController.readOnly}\n onClick={() => selectCollection(\"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={() => selectCollection(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-surface-hover 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 onDirtyChange={setEditorDirty}\n // Not through the guard: the editor has already\n // asked about its own edits before cancelling.\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 <UnsavedChangesDialog\n open={pendingSelection !== null}\n body={\"There are unsaved changes in this collection\"}\n handleOk={() => {\n const next = pendingSelection;\n setPendingSelection(null);\n setEditorDirty(false);\n if (next) setActiveCollectionId(next.id);\n }}\n handleCancel={() => setPendingSelection(null)}\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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"}
|
|
@@ -161,25 +161,37 @@ function WithheldConstraints({ constraints }) {
|
|
|
161
161
|
})]
|
|
162
162
|
});
|
|
163
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* How far a failed apply got, in a heading.
|
|
166
|
+
*
|
|
167
|
+
* The statements run one at a time with no transaction around them, so only
|
|
168
|
+
* "none ran" means the database is as it was.
|
|
169
|
+
*/
|
|
170
|
+
function partialApplyHeading(result) {
|
|
171
|
+
const ran = result.appliedStatements;
|
|
172
|
+
if (ran === 0) return "The database was not changed";
|
|
173
|
+
if (ran === void 0) return "The database may have been changed in part";
|
|
174
|
+
return `${ran} of ${result.statements.length} statements ran, then one failed`;
|
|
175
|
+
}
|
|
164
176
|
/** The receipt. Shown in place of the plan once the change has been applied. */
|
|
165
177
|
function Applied({ result }) {
|
|
166
178
|
return /* @__PURE__ */ jsxs("div", {
|
|
167
179
|
className: "flex flex-col gap-4",
|
|
168
180
|
children: [
|
|
169
181
|
/* @__PURE__ */ jsx(Alert, {
|
|
170
|
-
color: result.applied ? "success" : "warning",
|
|
182
|
+
color: result.applied || result.sourceOnly ? "success" : "warning",
|
|
171
183
|
children: /* @__PURE__ */ jsx(Typography, {
|
|
172
184
|
variant: "body2",
|
|
173
185
|
children: result.summary
|
|
174
186
|
})
|
|
175
187
|
}),
|
|
176
|
-
!result.applied && result.applyError && /* @__PURE__ */ jsxs(Alert, {
|
|
188
|
+
!result.applied && !result.sourceOnly && result.applyError && /* @__PURE__ */ jsxs(Alert, {
|
|
177
189
|
color: "warning",
|
|
178
190
|
children: [
|
|
179
191
|
/* @__PURE__ */ jsx(Typography, {
|
|
180
192
|
variant: "body2",
|
|
181
193
|
className: "font-medium mb-1",
|
|
182
|
-
children:
|
|
194
|
+
children: partialApplyHeading(result)
|
|
183
195
|
}),
|
|
184
196
|
/* @__PURE__ */ jsx(Typography, {
|
|
185
197
|
variant: "body2",
|
|
@@ -191,7 +203,7 @@ function Applied({ result }) {
|
|
|
191
203
|
children: [
|
|
192
204
|
"The commit is on ",
|
|
193
205
|
/* @__PURE__ */ jsx("code", { children: result.committed.branch }),
|
|
194
|
-
" either way, so
|
|
206
|
+
" either way, so whatever did not run will be applied on the next boot."
|
|
195
207
|
]
|
|
196
208
|
})
|
|
197
209
|
]
|
|
@@ -221,7 +233,7 @@ function SchemaChangeDialog({ open, collectionId, plan, planError, result, apply
|
|
|
221
233
|
const commitOnly = Boolean(plan?.applicable) && statements === 0;
|
|
222
234
|
const copy = verdict === "safe" && commitOnly ? {
|
|
223
235
|
title: "Ready to commit",
|
|
224
|
-
body: "This changes what your project declares. Nothing runs against the database — there is no schema change to make.",
|
|
236
|
+
body: plan && plan.changes.length > 0 ? "Nothing runs against the database now. Each change below says what it does and when it takes effect." : "This changes what your project declares. Nothing runs against the database — there is no schema change to make.",
|
|
225
237
|
color: "info"
|
|
226
238
|
} : verdict ? VERDICT_COPY[verdict] : void 0;
|
|
227
239
|
return /* @__PURE__ */ jsxs(Dialog, {
|
|
@@ -233,7 +245,7 @@ function SchemaChangeDialog({ open, collectionId, plan, planError, result, apply
|
|
|
233
245
|
children: [
|
|
234
246
|
/* @__PURE__ */ jsxs(DialogTitle, {
|
|
235
247
|
className: "flex items-center gap-2",
|
|
236
|
-
children: [result ? "Schema change applied" : "Review schema change", /* @__PURE__ */ jsx("code", {
|
|
248
|
+
children: [result ? result.sourceOnly ? "Schema change committed" : "Schema change applied" : "Review schema change", /* @__PURE__ */ jsx("code", {
|
|
237
249
|
className: "text-sm font-normal text-text-secondary dark:text-text-secondary-dark",
|
|
238
250
|
children: collectionId
|
|
239
251
|
})]
|
|
@@ -283,12 +295,21 @@ function SchemaChangeDialog({ open, collectionId, plan, planError, result, apply
|
|
|
283
295
|
plan.changes.length === 0 && /* @__PURE__ */ jsx(Typography, {
|
|
284
296
|
variant: "body2",
|
|
285
297
|
color: "secondary",
|
|
286
|
-
children: "Nothing here changes the shape of your data.
|
|
298
|
+
children: "Nothing here changes the shape of your data. Only the collection source changes, and it is committed."
|
|
287
299
|
}),
|
|
288
|
-
!plan.applicable && onSourceOnly && /* @__PURE__ */
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
300
|
+
!plan.applicable && onSourceOnly && /* @__PURE__ */ jsxs(Alert, {
|
|
301
|
+
color: "warning",
|
|
302
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
303
|
+
variant: "body2",
|
|
304
|
+
className: "font-medium mb-1",
|
|
305
|
+
children: "Edit source only: commit the collection, leave the database"
|
|
306
|
+
}), /* @__PURE__ */ jsx("ul", {
|
|
307
|
+
className: "space-y-1",
|
|
308
|
+
children: plan.changes.filter((change) => change.verdict !== "safe" && change.sourceOnly).map((change, index) => /* @__PURE__ */ jsx("li", {
|
|
309
|
+
className: "text-sm",
|
|
310
|
+
children: change.sourceOnly
|
|
311
|
+
}, `${change.property ?? change.collection}.${index}`))
|
|
312
|
+
})]
|
|
292
313
|
}),
|
|
293
314
|
plan.changes.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
294
315
|
className: "flex flex-col gap-3",
|
|
@@ -393,4 +414,4 @@ function SchemaChangeDialog({ open, collectionId, plan, planError, result, apply
|
|
|
393
414
|
//#endregion
|
|
394
415
|
export { SchemaChangeDialog_exports as n, SchemaChangeDialog as t };
|
|
395
416
|
|
|
396
|
-
//# sourceMappingURL=SchemaChangeDialog-
|
|
417
|
+
//# sourceMappingURL=SchemaChangeDialog-CcFSXvkK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SchemaChangeDialog-CcFSXvkK.js","names":[],"sources":["../src/collection_editor/ui/collection_editor/SchemaChangeDialog.tsx"],"sourcesContent":["/**\n * What a schema change would do, before it does it.\n *\n * The dialog exists because a schema change has three possible verdicts and two\n * of them are refusals. Without a preview, the only way to discover that a\n * change is refused is to press a button on a live database and read what comes\n * back — which is a fine way to learn and a poor way to work.\n *\n * ## What it shows, in the order somebody reads it\n *\n * The verdict first, in a sentence, because it decides whether the rest is\n * worth reading. Then the changes, each with what it does and — when it is\n * refused — what to do instead. Then the things that are easy to skip past and\n * expensive to have skipped: constraints the change asks for that the database\n * will not end up enforcing. The SQL and the file list come last, collapsed,\n * for the reader who wants them; they are the evidence, not the summary.\n *\n * ## Why the button says \"Commit and apply\"\n *\n * Because that is the order, and the order is the feature. The commit lands\n * first, so a failed apply leaves the repository ahead of the database — the\n * ordinary state of every project between an edit and a deploy, which the next\n * boot reconciles. A button labelled \"Save\" would be describing a different,\n * worse thing.\n */\nimport React, { useState } from \"react\";\nimport {\n Alert,\n Button,\n Chip,\n CircularProgress,\n cls,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Typography\n} from \"@rebasepro/ui\";\n\nimport type { LiveSchemaPlan, LiveSchemaResult, WithheldConstraint } from \"../../liveSchemaClient\";\n\nexport interface SchemaChangeDialogProps {\n open: boolean;\n /** The collection being changed, for the title. */\n collectionId: string;\n /** `undefined` while the plan is still being fetched. */\n plan?: LiveSchemaPlan;\n /** Set when planning itself failed — a network error, a refusal, a 500. */\n planError?: string;\n /** Set once the change has been applied, which turns this into a receipt. */\n result?: LiveSchemaResult;\n applying: boolean;\n /** Set when the apply threw rather than returning a result. */\n applyError?: string;\n onConfirm: () => void;\n /**\n * Ask again, when planning failed.\n *\n * A dropped connection should cost a click, not the whole wizard. The form\n * is already filled in, and making somebody redo it for a transient failure\n * teaches them to distrust the preview rather than the network.\n */\n onRetry?: () => void;\n /**\n * Write the collection source and leave the database alone.\n *\n * Offered only when the change is refused, and it is what keeps the editor\n * usable while it is. Removing a property is the ordinary case: the ensure\n * path has no `DROP COLUMN`, so a removal can never be applied — and\n * refusing the whole save over it would mean a dev could no longer delete a\n * field from a collection they are still designing.\n *\n * This is exactly what the editor did before live editing existed, so it is\n * not a new hazard; the difference is that it now says what it leaves\n * behind instead of reporting success.\n */\n onSourceOnly?: () => void;\n onClose: () => void;\n /**\n * Why this caller may preview but not apply, when that is the case.\n *\n * A person signed in with an API key, most often. Shown next to a disabled\n * button so the refusal arrives while they are deciding rather than after\n * they have decided.\n */\n applyRefusedBecause?: string;\n /**\n * Where a confirmation would land — `owner/repo` and a branch.\n *\n * On screen before the button, not in the receipt after it. A commit is\n * going somewhere, and which branch it is going to is the one fact somebody\n * cannot recover from being wrong about.\n */\n target?: { repository?: string; branch?: string };\n}\n\ntype Verdict = LiveSchemaPlan[\"verdict\"];\n\n/** What the verdict means, in the words the reader needs rather than the enum's. */\n/**\n * What the verdict means, in the words the reader needs rather than the enum's.\n *\n * `safe` is written twice because the two cases are genuinely different, and\n * saying \"can be made against the running database\" when nothing will be run is\n * the kind of small lie that teaches people to stop reading. A document\n * database has no table to alter, and a change that needs no DDL on a\n * relational one is in the same position: the commit *is* the change.\n */\nconst VERDICT_COPY: Record<Verdict, { title: string; body: string; color: \"info\" | \"warning\" | \"error\" }> = {\n safe: {\n title: \"Ready to apply\",\n body: \"Every change here can be made against the running database.\",\n color: \"info\"\n },\n diverges: {\n title: \"This would leave the database out of step\",\n body: \"The change can be written, but the database will not end up matching it. \" +\n \"The details below say which part, and what would make it applicable.\",\n color: \"warning\"\n },\n \"needs-migration\": {\n title: \"This needs a migration\",\n body: \"Some of this cannot be done to a running database at all — it drops or rewrites \" +\n \"something, and that belongs in a migration somebody has read.\",\n color: \"error\"\n }\n};\n\nconst VERDICT_CHIP: Record<Verdict, { label: string; scheme: \"greenLighter\" | \"orangeLighter\" | \"redLighter\" }> = {\n safe: { label: \"safe\", scheme: \"greenLighter\" },\n diverges: { label: \"diverges\", scheme: \"orangeLighter\" },\n \"needs-migration\": { label: \"needs migration\", scheme: \"redLighter\" }\n};\n\nconst monospace = \"font-mono text-xs leading-relaxed\";\nconst codeBlock = cls(\n monospace,\n \"bg-surface-raised rounded-md p-3 overflow-x-auto whitespace-pre\"\n);\n\n/** A disclosure that starts closed. The evidence, for the reader who wants it. */\nfunction Details({ summary, count, children }: {\n summary: string;\n count: number;\n children: React.ReactNode;\n}) {\n const [open, setOpen] = useState(false);\n if (count === 0) return null;\n return (\n <div>\n <button\n type=\"button\"\n onClick={() => setOpen(o => !o)}\n className=\"flex items-center gap-2 text-sm text-text-secondary dark:text-text-secondary-dark hover:underline\"\n >\n <span className={cls(\"transition-transform\", open && \"rotate-90\")}>›</span>\n {summary} ({count})\n </button>\n {open && <div className=\"mt-2\">{children}</div>}\n </div>\n );\n}\n\n/**\n * What still has to happen after this lands.\n *\n * Above the evidence and below the changes, because it is not a refusal and not\n * a detail: the change will apply, and something outside this dialog still has\n * to be done or a later environment will differ from this one.\n */\nfunction FollowUp({ items }: { items: string[] }) {\n if (items.length === 0) return null;\n return (\n <Alert color=\"info\">\n <Typography variant=\"body2\" className=\"font-medium mb-1\">\n {items.length === 1 ? \"One thing to do afterwards\" : \"To do afterwards\"}\n </Typography>\n <ul className=\"space-y-2\">\n {items.map(item => (\n <li key={item} className=\"text-sm\">{item}</li>\n ))}\n </ul>\n </Alert>\n );\n}\n\n/**\n * The destination, short enough to read at a glance.\n *\n * A remote repository already reports itself as `owner/repo`, which is exactly\n * what somebody recognises. A local one reports an absolute path, and the\n * leading directories are the part nobody needs: what matters is which\n * checkout, and its name says that.\n */\nfunction repositoryLabel(repository: string): string {\n if (!repository.startsWith(\"/\")) return repository;\n return repository.split(\"/\").filter(Boolean).pop() ?? repository;\n}\n\nfunction WithheldConstraints({ constraints }: { constraints: WithheldConstraint[] }) {\n if (constraints.length === 0) return null;\n return (\n <Alert color=\"warning\">\n <Typography variant=\"body2\" className=\"font-medium mb-1\">\n {constraints.length === 1\n ? \"One constraint will not be enforced\"\n : `${constraints.length} constraints will not be enforced`}\n </Typography>\n <ul className=\"space-y-2\">\n {constraints.map(constraint => (\n <li key={constraint.target} className=\"text-sm\">\n <code className=\"bg-surface-raised px-1 rounded\">\n {constraint.target}\n </code>\n <div className=\"mt-1\">{constraint.reason}</div>\n <div className=\"mt-1 text-text-secondary dark:text-text-secondary-dark\">\n {constraint.remedy}\n </div>\n </li>\n ))}\n </ul>\n </Alert>\n );\n}\n\n/**\n * How far a failed apply got, in a heading.\n *\n * The statements run one at a time with no transaction around them, so only\n * \"none ran\" means the database is as it was.\n */\nfunction partialApplyHeading(result: LiveSchemaResult): string {\n const ran = result.appliedStatements;\n if (ran === 0) return \"The database was not changed\";\n if (ran === undefined) return \"The database may have been changed in part\";\n return `${ran} of ${result.statements.length} statements ran, then one failed`;\n}\n\n/** The receipt. Shown in place of the plan once the change has been applied. */\nfunction Applied({ result }: { result: LiveSchemaResult }) {\n return (\n <div className=\"flex flex-col gap-4\">\n <Alert color={result.applied || result.sourceOnly ? \"success\" : \"warning\"}>\n <Typography variant=\"body2\">{result.summary}</Typography>\n </Alert>\n\n {!result.applied && !result.sourceOnly && result.applyError && (\n // Deliberately not an error state. The commit landed, which is\n // the durable half; the database is now behind the repository,\n // which is where every project sits between an edit and a\n // deploy, and boot reconciles it.\n <Alert color=\"warning\">\n <Typography variant=\"body2\" className=\"font-medium mb-1\">\n {partialApplyHeading(result)}\n </Typography>\n <Typography variant=\"body2\">{result.applyError}</Typography>\n <Typography variant=\"body2\" className=\"mt-2\">\n The commit is on <code>{result.committed.branch}</code> either way, so\n whatever did not run will be applied on the next boot.\n </Typography>\n </Alert>\n )}\n\n <WithheldConstraints constraints={result.withheldConstraints ?? []}/>\n <FollowUp items={result.followUp ?? []}/>\n\n <div>\n <Typography variant=\"label\" color=\"secondary\">Committed</Typography>\n <div className={cls(codeBlock, \"mt-1\")}>\n {result.committed.sha.slice(0, 9)} on {result.committed.branch}\n {\"\\n\"}\n {result.committed.files.map(file => ` ${file}`).join(\"\\n\")}\n </div>\n </div>\n </div>\n );\n}\n\nexport function SchemaChangeDialog({\n open,\n collectionId,\n plan,\n planError,\n result,\n applying,\n applyError,\n onConfirm,\n onRetry,\n onSourceOnly,\n onClose,\n applyRefusedBecause,\n target\n}: SchemaChangeDialogProps) {\n\n const verdict = plan?.verdict;\n const statements = plan?.statements.length ?? 0;\n\n // \"Commit\" only when we positively know there is nothing to apply: a plan\n // that is going ahead and has no statements. A *refused* plan also has none\n // — because it will not run — and labelling that button \"Commit\" would be\n // describing the change it is refusing to make. Unknown reads as the fuller\n // action, which is the safer way to be wrong about a label.\n const commitOnly = Boolean(plan?.applicable) && statements === 0;\n\n // Described from the plan rather than from an assumption about the engine.\n // A document database runs nothing; so does a relational change that only\n // moves source. Both are a commit, and calling either one \"apply\" would be\n // describing something that does not happen.\n // A plan with changes and no statements still changes something — a\n // security rule takes effect when the server next starts — so \"there is\n // no schema change to make\" is said only when there is none.\n const copy = verdict === \"safe\" && commitOnly\n ? {\n title: \"Ready to commit\",\n body: plan && plan.changes.length > 0\n ? \"Nothing runs against the database now. Each change below says what it does and \" +\n \"when it takes effect.\"\n : \"This changes what your project declares. Nothing runs against the database — \" +\n \"there is no schema change to make.\",\n color: \"info\" as const\n }\n : verdict\n ? VERDICT_COPY[verdict]\n : undefined;\n\n return (\n <Dialog open={open} onOpenChange={value => { if (!value) onClose(); }} maxWidth=\"3xl\">\n <DialogTitle className=\"flex items-center gap-2\">\n {result ? (result.sourceOnly ? \"Schema change committed\" : \"Schema change applied\") : \"Review schema change\"}\n <code className=\"text-sm font-normal text-text-secondary dark:text-text-secondary-dark\">\n {collectionId}\n </code>\n </DialogTitle>\n\n <DialogContent className=\"flex flex-col gap-4\">\n {planError && (\n <Alert color=\"error\">\n <Typography variant=\"body2\" className=\"font-medium mb-1\">\n The change could not be planned\n </Typography>\n <Typography variant=\"body2\">{planError}</Typography>\n </Alert>\n )}\n\n {!plan && !planError && !result && (\n <div className=\"flex items-center gap-3 py-6\">\n <CircularProgress size=\"small\"/>\n <Typography variant=\"body2\" color=\"secondary\">\n Working out what this change would do…\n </Typography>\n </div>\n )}\n\n {result && <Applied result={result}/>}\n\n {plan && !result && copy && (\n <>\n <Alert color={copy.color}>\n <Typography variant=\"body2\" className=\"font-medium mb-1\">\n {copy.title}\n </Typography>\n <Typography variant=\"body2\">{copy.body}</Typography>\n </Alert>\n\n {applyError && (\n <Alert color=\"error\">\n <Typography variant=\"body2\">{applyError}</Typography>\n </Alert>\n )}\n\n {plan.changes.length === 0 && (\n <Typography variant=\"body2\" color=\"secondary\">\n Nothing here changes the shape of your data. Only the collection\n source changes, and it is committed.\n </Typography>\n )}\n\n {!plan.applicable && onSourceOnly && (\n // Said per change: what a removal leaves behind is\n // not what a type change or a new required field\n // does, and the one that breaks every insert has to\n // be on screen before the button is pressed.\n <Alert color=\"warning\">\n <Typography variant=\"body2\" className=\"font-medium mb-1\">\n Edit source only: commit the collection, leave the database\n </Typography>\n <ul className=\"space-y-1\">\n {plan.changes.filter(change => change.verdict !== \"safe\" && change.sourceOnly).map((change, index) => (\n <li key={`${change.property ?? change.collection}.${index}`} className=\"text-sm\">\n {change.sourceOnly}\n </li>\n ))}\n </ul>\n </Alert>\n )}\n\n {plan.changes.length > 0 && (\n <ul className=\"flex flex-col gap-3\">\n {plan.changes.map((change, index) => (\n <li\n key={`${change.collection}.${change.property ?? \"\"}.${index}`}\n className=\"flex gap-3\"\n >\n <Chip\n size=\"smallest\"\n colorScheme={VERDICT_CHIP[change.verdict].scheme}\n className=\"shrink-0 mt-0.5\"\n >\n {VERDICT_CHIP[change.verdict].label}\n </Chip>\n <div className=\"min-w-0\">\n <Typography variant=\"body2\">{change.detail}</Typography>\n {change.remedy && (\n <Typography\n variant=\"body2\"\n color=\"secondary\"\n className=\"mt-1\"\n >\n {change.remedy}\n </Typography>\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n\n <WithheldConstraints constraints={plan.withheldConstraints ?? []}/>\n <FollowUp items={plan.followUp ?? []}/>\n\n {applyRefusedBecause && (\n <Alert color=\"info\">\n <Typography variant=\"body2\" className=\"font-medium mb-1\">\n You can preview this change, but not apply it\n </Typography>\n <Typography variant=\"body2\">{applyRefusedBecause}</Typography>\n </Alert>\n )}\n\n <Details summary=\"Statements that will run\" count={plan.statements.length}>\n <div className={codeBlock}>{plan.statements.join(\"\\n\")}</div>\n </Details>\n\n <Details summary=\"Files that will be committed\" count={plan.files.length}>\n <div className={codeBlock}>{plan.files.join(\"\\n\")}</div>\n </Details>\n\n <Details summary=\"Commit message\" count={plan.message ? 1 : 0}>\n <div className={codeBlock}>{plan.message}</div>\n </Details>\n </>\n )}\n </DialogContent>\n\n <DialogActions>\n {!result && target?.repository && (\n <span className={cls(\n monospace,\n \"mr-auto text-text-secondary dark:text-text-secondary-dark truncate\"\n )}>\n → {repositoryLabel(target.repository)}{target.branch ? ` · ${target.branch}` : \"\"}\n </span>\n )}\n <Button variant=\"text\" onClick={onClose} disabled={applying}>\n {result ? \"Close\" : \"Cancel\"}\n </Button>\n {!result && planError && onRetry && (\n <Button variant=\"outlined\" onClick={onRetry}>\n Try again\n </Button>\n )}\n {!result && plan && !plan.applicable && onSourceOnly && (\n <Button variant=\"outlined\" onClick={onSourceOnly} disabled={applying}>\n Edit source only\n </Button>\n )}\n {!result && (\n <Button\n variant=\"filled\"\n color=\"primary\"\n onClick={onConfirm}\n // `applicable` is the server's word, not a\n // re-derivation from the verdicts on screen:\n // `applySchemaChange` re-checks it before it writes\n // anything, so a button that disagreed would only\n // produce the same refusal one click later. The same\n // goes for the permission — `/apply` checks it too, and\n // this only saves somebody the round trip.\n disabled={!plan?.applicable || applying || Boolean(applyRefusedBecause)}\n >\n {applying\n ? (commitOnly ? \"Committing…\" : \"Applying…\")\n : (commitOnly ? \"Commit\" : \"Commit and apply\")}\n </Button>\n )}\n </DialogActions>\n </Dialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -20,7 +20,8 @@ export { ImportFileUpload } from "../data_import/components/ImportFileUpload.js"
|
|
|
20
20
|
export { ImportSaveInProgress } from "../data_import/components/ImportSaveInProgress.js";
|
|
21
21
|
export { useImportConfig } from "../data_import/hooks/useImportConfig.js";
|
|
22
22
|
export { getInferenceType } from "../data_import/utils/get_import_inference_type.js";
|
|
23
|
-
export { convertDataToEntity } from "../data_import/utils/data.js";
|
|
23
|
+
export { convertDataToEntity, convertImportData } from "../data_import/utils/data.js";
|
|
24
|
+
export { ImportConversionProblems } from "../data_import/components/ImportConversionProblems.js";
|
|
24
25
|
export { guessIdColumn } from "../data_import/utils/id_column.js";
|
|
25
26
|
export type { ImportConfig } from "../data_import/types/index.js";
|
|
26
27
|
export { DataNewPropertiesMapping } from "../data_import/components/DataNewPropertiesMapping.js";
|
|
@@ -7,18 +7,17 @@
|
|
|
7
7
|
* try-and-see would be asking somebody to discover that their change is refused
|
|
8
8
|
* by pressing a button on a live database.
|
|
9
9
|
*
|
|
10
|
-
* ##
|
|
10
|
+
* ## A change to an existing collection is a patch
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* it gets here. Accepting a patch and merging it in this module would put that
|
|
20
|
-
* reconstruction one layer away from the collections it needs to read.
|
|
12
|
+
* What the person changed — the difference between the collection the editor
|
|
13
|
+
* loaded and the one it is saving, from `diffCollections` in
|
|
14
|
+
* `@rebasepro/types` — not the collection. The server applies it to the
|
|
15
|
+
* collection as it is to plan the change, and writes only those keys into the
|
|
16
|
+
* file. Posting the whole collection had the server write back the panel's
|
|
17
|
+
* JSON, which had lost every handler, shared property and imported value.
|
|
18
|
+
* A new collection is still sent whole.
|
|
21
19
|
*/
|
|
20
|
+
import type { CollectionPatch } from "@rebasepro/types";
|
|
22
21
|
/** A constraint the change asks for that the statements will not carry. */
|
|
23
22
|
export interface WithheldConstraint {
|
|
24
23
|
target: string;
|
|
@@ -37,6 +36,12 @@ export interface LiveSchemaPlan {
|
|
|
37
36
|
property?: string;
|
|
38
37
|
detail: string;
|
|
39
38
|
remedy?: string;
|
|
39
|
+
/**
|
|
40
|
+
* What "Edit source only" leaves behind for this change. Absent on a
|
|
41
|
+
* change that may not be written to the source alone.
|
|
42
|
+
*/
|
|
43
|
+
sourceOnly?: string;
|
|
44
|
+
kept?: string;
|
|
40
45
|
}>;
|
|
41
46
|
statements: string[];
|
|
42
47
|
files: string[];
|
|
@@ -54,14 +59,23 @@ export interface LiveSchemaPlan {
|
|
|
54
59
|
*
|
|
55
60
|
* Empty almost always. The case it exists for: a project that replays
|
|
56
61
|
* versioned migrations, where applying here changes *this* database and
|
|
57
|
-
* commits
|
|
62
|
+
* commits the collection, but writes no migration — so the next
|
|
58
63
|
* environment built from migrations would not have the change.
|
|
59
64
|
*/
|
|
60
65
|
followUp?: string[];
|
|
61
66
|
}
|
|
62
67
|
export interface LiveSchemaResult {
|
|
63
68
|
applied: boolean;
|
|
69
|
+
/** Committed as "Edit source only": nothing was meant to run. */
|
|
70
|
+
sourceOnly?: boolean;
|
|
64
71
|
applyError?: string;
|
|
72
|
+
/**
|
|
73
|
+
* How many statements ran before the one that failed. The statements run
|
|
74
|
+
* one at a time with no transaction around them, so a failure part-way
|
|
75
|
+
* leaves the earlier ones applied. `undefined` when the server could not
|
|
76
|
+
* say.
|
|
77
|
+
*/
|
|
78
|
+
appliedStatements?: number;
|
|
65
79
|
committed: {
|
|
66
80
|
sha: string;
|
|
67
81
|
branch: string;
|
|
@@ -129,11 +143,12 @@ export interface LiveSchemaClientOptions {
|
|
|
129
143
|
}
|
|
130
144
|
export interface ProposedCollectionChange {
|
|
131
145
|
collectionId: string;
|
|
132
|
-
/**
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
collection
|
|
146
|
+
/** A new collection, whole. */
|
|
147
|
+
collection?: Record<string, unknown>;
|
|
148
|
+
/** What changed about an existing one. See the module comment. */
|
|
149
|
+
patch?: CollectionPatch;
|
|
150
|
+
/** Delete the collection's source — its file and its `index.ts` entry. The table stays. */
|
|
151
|
+
remove?: true;
|
|
137
152
|
}
|
|
138
153
|
export declare function createLiveSchemaClient(options: LiveSchemaClientOptions): {
|
|
139
154
|
/**
|
|
@@ -148,8 +163,13 @@ export declare function createLiveSchemaClient(options: LiveSchemaClientOptions)
|
|
|
148
163
|
status: () => Promise<LiveSchemaStatus>;
|
|
149
164
|
/** What the change would do. No side effects. */
|
|
150
165
|
plan: (change: ProposedCollectionChange) => Promise<LiveSchemaPlan>;
|
|
151
|
-
/**
|
|
152
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Commit the change, then apply it — or, with `sourceOnly`, commit it
|
|
168
|
+
* and run nothing ("Edit source only").
|
|
169
|
+
*/
|
|
170
|
+
apply: (change: ProposedCollectionChange & {
|
|
171
|
+
sourceOnly?: boolean;
|
|
172
|
+
}) => Promise<LiveSchemaResult>;
|
|
153
173
|
};
|
|
154
174
|
/** The client's shape, for the hook and the tests that fake it. */
|
|
155
175
|
export type LiveSchemaClient = ReturnType<typeof createLiveSchemaClient>;
|
|
@@ -21,6 +21,8 @@ export interface SerializableAdminBaseOptions {
|
|
|
21
21
|
hideFromCollection?: boolean;
|
|
22
22
|
readOnly?: boolean;
|
|
23
23
|
disabled?: boolean | SerializablePropertyDisabledConfig;
|
|
24
|
+
/** The server fills this on create, so the form does not require it then. */
|
|
25
|
+
filledByServer?: boolean;
|
|
24
26
|
span?: 1 | 2 | 3 | 4;
|
|
25
27
|
/** Opaque props handed to a custom `Field` / `Preview`. Round-tripped as-is. */
|
|
26
28
|
customProps?: unknown;
|
|
@@ -67,6 +67,17 @@ export type UpdateCollectionParams<M extends Record<string, unknown> = Record<st
|
|
|
67
67
|
export type SaveCollectionParams<M extends Record<string, unknown> = Record<string, unknown>> = {
|
|
68
68
|
id: string;
|
|
69
69
|
collectionData: AdminCollection<M>;
|
|
70
|
+
/**
|
|
71
|
+
* The collection as the editor loaded it, before the person changed
|
|
72
|
+
* anything.
|
|
73
|
+
*
|
|
74
|
+
* A controller that writes source saves only the difference between this
|
|
75
|
+
* and `collectionData` — what the person changed. The whole collection is
|
|
76
|
+
* JSON by the time it is sent, and writing it back whole deleted every
|
|
77
|
+
* handler, shared property and imported value JSON could not carry.
|
|
78
|
+
* Absent, the controller's own copy of the collection stands in for it.
|
|
79
|
+
*/
|
|
80
|
+
baseline?: AdminCollection<M>;
|
|
70
81
|
previousId?: string;
|
|
71
82
|
parentCollectionSlugs?: string[];
|
|
72
83
|
parentEntityIds?: string[];
|
|
@@ -30,12 +30,12 @@ export interface UseLiveSchemaEditingOptions {
|
|
|
30
30
|
*/
|
|
31
31
|
authKey?: string | null;
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
33
|
+
* Whether to ask the backend at all. `false` when the caller cannot be told
|
|
34
|
+
* anything (`/status` needs `schema:read`) or the editor is read-only: the
|
|
35
|
+
* answer is then "not available" without a request that could only be
|
|
36
|
+
* refused. Defaults to `true`.
|
|
37
37
|
*/
|
|
38
|
-
|
|
38
|
+
enabled?: boolean;
|
|
39
39
|
/** Swappable for the tests, which have no server. */
|
|
40
40
|
client?: LiveSchemaClient;
|
|
41
41
|
}
|
|
@@ -11,4 +11,11 @@ export declare function useLocalCollectionsConfigController(clientOrUrl: any, ba
|
|
|
11
11
|
* anonymous probe does not survive a sign-in.
|
|
12
12
|
*/
|
|
13
13
|
authKey?: string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the signed-in user holds `schema:read`, which both
|
|
16
|
+
* status routes require. `false` asks neither and leaves the editor
|
|
17
|
+
* read-only, because a 403 is the only answer such a user can get.
|
|
18
|
+
* Absent means ask.
|
|
19
|
+
*/
|
|
20
|
+
mayReadSchema?: boolean;
|
|
14
21
|
}): CollectionsConfigController;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as CollectionEditorDialog } from "./CollectionEditorDialog-
|
|
3
|
-
import { n as PropertyForm, r as PropertyFormDialog } from "./PropertyEditView-
|
|
4
|
-
import { i as CollectionStudioView, r as CollectionsStudioView, t as RouterCollectionsStudioView } from "./RouterCollectionsStudioView-
|
|
5
|
-
import { t as SchemaChangeDialog } from "./SchemaChangeDialog-
|
|
1
|
+
import { Bn as useUrlController } from "./util-BRvKFMLa.js";
|
|
2
|
+
import { n as CollectionEditorDialog } from "./CollectionEditorDialog-p5-I-Apu.js";
|
|
3
|
+
import { n as PropertyForm, r as PropertyFormDialog } from "./PropertyEditView-DDB_X-00.js";
|
|
4
|
+
import { i as CollectionStudioView, r as CollectionsStudioView, t as RouterCollectionsStudioView } from "./RouterCollectionsStudioView-SGBV4CMg.js";
|
|
5
|
+
import { t as SchemaChangeDialog } from "./SchemaChangeDialog-CcFSXvkK.js";
|
|
6
6
|
import "react";
|
|
7
7
|
import { useSnackbarController } from "@rebasepro/app";
|
|
8
8
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import type { FilterValues } from "@rebasepro/types";
|
|
1
2
|
import { EntityTableController } from "@rebasepro/cms-types";
|
|
2
|
-
export declare function ClearFilterSortButton({ tableController, enabled, compact }: {
|
|
3
|
+
export declare function ClearFilterSortButton({ tableController, enabled, fixedFilter, compact }: {
|
|
3
4
|
enabled: boolean;
|
|
4
5
|
tableController: EntityTableController;
|
|
6
|
+
/**
|
|
7
|
+
* The collection's fixed filter. Its fields are not the user's to clear,
|
|
8
|
+
* so they do not count as a filter that is set.
|
|
9
|
+
*/
|
|
10
|
+
fixedFilter?: FilterValues<string>;
|
|
5
11
|
/**
|
|
6
12
|
* Toolbar showing icon-only controls (split view, or a layout too narrow
|
|
7
13
|
* for the filters button's label). Only decides the icon size, which
|
|
@@ -14,6 +14,11 @@ export type CollectionViewActionsProps<M extends Record<string, unknown>> = {
|
|
|
14
14
|
*/
|
|
15
15
|
onAddExistingClick?: () => void;
|
|
16
16
|
onMultipleDeleteClick: () => void;
|
|
17
|
+
/**
|
|
18
|
+
* The rows are shared with other records through a junction, so removing
|
|
19
|
+
* them unlinks them from this record and deletes nothing. The bin says so.
|
|
20
|
+
*/
|
|
21
|
+
removeUnlinks?: boolean;
|
|
17
22
|
selectionController: SelectionController<M>;
|
|
18
23
|
tableController: EntityTableController<M>;
|
|
19
24
|
collectionEntitiesCount?: number;
|
|
@@ -21,4 +26,4 @@ export type CollectionViewActionsProps<M extends Record<string, unknown>> = {
|
|
|
21
26
|
children?: React.ReactNode;
|
|
22
27
|
openNewDocument: (defaultValues?: Record<string, unknown>) => void;
|
|
23
28
|
};
|
|
24
|
-
export declare function CollectionViewActions<M extends Record<string, unknown>>({ collection, relativePath, parentCollectionSlugs, parentEntityIds, onNewClick, onAddExistingClick, onMultipleDeleteClick, selectionEnabled, path, selectionController, tableController, collectionEntitiesCount, compact, children, openNewDocument }: CollectionViewActionsProps<M>): React.JSX.Element;
|
|
29
|
+
export declare function CollectionViewActions<M extends Record<string, unknown>>({ collection, relativePath, parentCollectionSlugs, parentEntityIds, onNewClick, onAddExistingClick, onMultipleDeleteClick, removeUnlinks, selectionEnabled, path, selectionController, tableController, collectionEntitiesCount, compact, children, openNewDocument }: CollectionViewActionsProps<M>): React.JSX.Element;
|
|
@@ -7,8 +7,11 @@ import React from "react";
|
|
|
7
7
|
*/
|
|
8
8
|
export type CollectionViewBindingProps<M extends Record<string, unknown>> = {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* The path the collection is addressed by: its slug, or for a
|
|
11
|
+
* subcollection `parentSlug/{id}/slug`. Defaults to the collection's slug.
|
|
12
|
+
* Not the `path` a Firestore or MongoDB collection declares for its store:
|
|
13
|
+
* the data layer translates to that one, and another collection's slug may
|
|
14
|
+
* be spelled the same.
|
|
12
15
|
*/
|
|
13
16
|
path?: string;
|
|
14
17
|
/**
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { Property } from "@rebasepro/types";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { FilterValues } from "@rebasepro/types";
|
|
4
|
+
/**
|
|
5
|
+
* The properties the dialog lists, each with a filter field: every property
|
|
6
|
+
* that can be filtered, bar the collection's fixed-filter fields.
|
|
7
|
+
*/
|
|
8
|
+
export declare function getFilterableProperties(properties: Record<string, Property>, fixedFilter?: FilterValues<string>, engine?: string): [string, Property][];
|
|
4
9
|
export interface FiltersDialogProps {
|
|
5
10
|
open: boolean;
|
|
6
11
|
onOpenChange: (open: boolean) => void;
|
|
@@ -82,11 +82,17 @@ export interface EditViewBindingProps<M extends Record<string, unknown> = Record
|
|
|
82
82
|
* a record is opened.
|
|
83
83
|
*/
|
|
84
84
|
export declare function EditViewBinding<M extends Record<string, unknown>>({ entityId, ...props }: EditViewBindingProps<M>): React.JSX.Element;
|
|
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> & {
|
|
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, deletedElsewhere }: EditViewBindingProps<M> & {
|
|
86
86
|
entity?: Entity<M>;
|
|
87
87
|
initialDirtyValues?: Partial<M>;
|
|
88
88
|
dataLoading: boolean;
|
|
89
89
|
status: EntityStatus;
|
|
90
90
|
setStatus: (status: EntityStatus) => void;
|
|
91
91
|
canEdit?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* The record was deleted by someone else while this form had it open. The
|
|
94
|
+
* form keeps its values; saving them over the record is no longer
|
|
95
|
+
* possible, saving them as a new one is.
|
|
96
|
+
*/
|
|
97
|
+
deletedElsewhere?: boolean;
|
|
92
98
|
}): React.JSX.Element;
|