@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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/admin",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.14.0",
|
|
5
5
|
"description": "Rebase CMS — content management views, forms, and routing",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -83,14 +83,14 @@
|
|
|
83
83
|
"react-dropzone": "^19.1.1",
|
|
84
84
|
"react-use-measure": "^2.1.7",
|
|
85
85
|
"zod": "^4.4.3",
|
|
86
|
-
"@rebasepro/admin-types": "0.
|
|
87
|
-
"@rebasepro/app": "0.
|
|
88
|
-
"@rebasepro/forms": "0.
|
|
89
|
-
"@rebasepro/
|
|
90
|
-
"@rebasepro/
|
|
91
|
-
"@rebasepro/
|
|
92
|
-
"@rebasepro/
|
|
93
|
-
"@rebasepro/
|
|
86
|
+
"@rebasepro/admin-types": "0.14.0",
|
|
87
|
+
"@rebasepro/app": "0.14.0",
|
|
88
|
+
"@rebasepro/forms": "0.14.0",
|
|
89
|
+
"@rebasepro/inference": "0.14.0",
|
|
90
|
+
"@rebasepro/types": "0.14.0",
|
|
91
|
+
"@rebasepro/utils": "0.14.0",
|
|
92
|
+
"@rebasepro/common": "0.14.0",
|
|
93
|
+
"@rebasepro/ui": "0.14.0"
|
|
94
94
|
},
|
|
95
95
|
"peerDependencies": {
|
|
96
96
|
"react": ">=19.2.7",
|
|
@@ -107,6 +107,7 @@
|
|
|
107
107
|
"@types/react-dom": "^19.2.3",
|
|
108
108
|
"@types/react-measure": "^2.0.12",
|
|
109
109
|
"@vitejs/plugin-react": "^6.0.4",
|
|
110
|
+
"fast-check": "^4.9.0",
|
|
110
111
|
"jest": "^30.4.2",
|
|
111
112
|
"react-router": "^8.3.0",
|
|
112
113
|
"ts-jest": "^29.4.12",
|
|
@@ -155,6 +155,7 @@ export interface SerializableAdminRelationOptions extends SerializableAdminBaseO
|
|
|
155
155
|
fixedFilter?: FilterValues<string>;
|
|
156
156
|
includeId?: boolean;
|
|
157
157
|
includeEntityLink?: boolean;
|
|
158
|
+
renderInForm?: boolean;
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
/** JSON-serializable version of `AdminArrayOptions`. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { AIIcon } from "@rebasepro/app";
|
|
2
|
+
import { AIIcon, useTranslation } from "@rebasepro/app";
|
|
3
3
|
import { useCollectionRegistryController } from "../../_cms_internals";
|
|
4
4
|
import React, { useState } from "react";
|
|
5
5
|
import { useSafeSnackbarController } from "../../useSafeSnackbarController";
|
|
@@ -70,6 +70,7 @@ export function AICollectionGeneratorPopover({
|
|
|
70
70
|
showLabel = true,
|
|
71
71
|
onAnalyticsEvent
|
|
72
72
|
}: AICollectionGeneratorPopoverProps) {
|
|
73
|
+
const { t } = useTranslation();
|
|
73
74
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
74
75
|
const [prompt, setPrompt] = useState("");
|
|
75
76
|
const [loading, setLoading] = useState(false);
|
|
@@ -204,8 +205,8 @@ export function AICollectionGeneratorPopover({
|
|
|
204
205
|
|
|
205
206
|
<Typography variant="caption" color="secondary">
|
|
206
207
|
{existingCollection
|
|
207
|
-
? "
|
|
208
|
-
: "
|
|
208
|
+
? t("describe_changes_to_make")
|
|
209
|
+
: t("describe_collection_to_create")
|
|
209
210
|
}
|
|
210
211
|
</Typography>
|
|
211
212
|
|
|
@@ -213,6 +214,10 @@ export function AICollectionGeneratorPopover({
|
|
|
213
214
|
size="small"
|
|
214
215
|
multiline
|
|
215
216
|
autoFocus
|
|
217
|
+
aria-label={existingCollection
|
|
218
|
+
? t("describe_changes_to_make")
|
|
219
|
+
: t("describe_collection_to_create")
|
|
220
|
+
}
|
|
216
221
|
className="w-full text-text-primary dark:text-text-primary-dark"
|
|
217
222
|
value={prompt}
|
|
218
223
|
onChange={(e) => setPrompt(e.target.value)}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { IconForView } from "@rebasepro/app";
|
|
3
3
|
import { FieldCaption } from "../../_cms_internals";
|
|
4
4
|
import React, { useMemo, useState } from "react";
|
|
5
|
-
import { useAuthController, useCustomizationController } from "@rebasepro/app";
|
|
5
|
+
import { useAuthController, useCustomizationController, useTranslation } from "@rebasepro/app";
|
|
6
6
|
import { getFieldConfig, PropertyConfigBadge, SearchIconsView } from "../../_cms_internals";
|
|
7
7
|
import { Property } from "@rebasepro/types";
|
|
8
8
|
import {
|
|
@@ -372,10 +372,13 @@ function DefaultDatabaseField({
|
|
|
372
372
|
onDatabaseIdUpdate
|
|
373
373
|
}: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
|
|
374
374
|
|
|
375
|
-
|
|
375
|
+
const { t } = useTranslation();
|
|
376
|
+
|
|
377
|
+
return <Tooltip title={t("database_id")}
|
|
376
378
|
side={"top"}
|
|
377
379
|
align={"start"}>
|
|
378
380
|
<TextField size={"small"}
|
|
381
|
+
aria-label={t("database_id")}
|
|
379
382
|
invisible={true}
|
|
380
383
|
inputClassName={"text-end"}
|
|
381
384
|
value={databaseId ?? ""}
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
} from "@rebasepro/ui";
|
|
41
41
|
import { useFormex } from "@rebasepro/forms";
|
|
42
42
|
import { useCollectionsConfigController } from "../../useCollectionsConfigController";
|
|
43
|
-
import { useRebaseContext } from "@rebasepro/app";
|
|
43
|
+
import { useRebaseContext, useTranslation } from "@rebasepro/app";
|
|
44
44
|
import type { AdminCollection } from "@rebasepro/admin-types";
|
|
45
45
|
import type { PostgresPolicy, SecurityOperation, SecurityRule } from "@rebasepro/types";
|
|
46
46
|
|
|
@@ -394,6 +394,7 @@ function InlinePolicyEditor({
|
|
|
394
394
|
onSave: (policyData: Partial<PostgresPolicy>) => void;
|
|
395
395
|
onCancel: () => void;
|
|
396
396
|
}) {
|
|
397
|
+
const { t } = useTranslation();
|
|
397
398
|
const [name, setName] = useState(policy?.policyname || "");
|
|
398
399
|
const [behavior, setBehavior] = useState<"PERMISSIVE" | "RESTRICTIVE">(policy?.permissive || "PERMISSIVE");
|
|
399
400
|
const [command, setCommand] = useState<PolicyCommand>((policy?.cmd as PolicyCommand) || "ALL");
|
|
@@ -441,8 +442,8 @@ function InlinePolicyEditor({
|
|
|
441
442
|
<Paper className={cls("p-4 md:p-6 flex flex-col gap-6 bg-white dark:bg-surface-800 border-none sm:border-solid rounded-none sm:rounded-xl", defaultBorderMixin)}>
|
|
442
443
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
443
444
|
<div className="flex flex-col gap-1.5">
|
|
444
|
-
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">
|
|
445
|
-
<TextField value={name} onChange={(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => setName(e.target.value)} placeholder="e.g. allow_read_all"/>
|
|
445
|
+
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">{t("studio_policy_name")}</Typography>
|
|
446
|
+
<TextField aria-label={t("studio_policy_name")} value={name} onChange={(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => setName(e.target.value)} placeholder="e.g. allow_read_all"/>
|
|
446
447
|
</div>
|
|
447
448
|
<div className="flex flex-col gap-1.5">
|
|
448
449
|
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">Behavior</Typography>
|
|
@@ -484,7 +485,7 @@ function InlinePolicyEditor({
|
|
|
484
485
|
</Typography>
|
|
485
486
|
)}
|
|
486
487
|
<div className="flex gap-2 items-center">
|
|
487
|
-
<TextField size="small" value={customRole}
|
|
488
|
+
<TextField size="small" aria-label="Add a custom role" value={customRole}
|
|
488
489
|
onChange={(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => setCustomRole(e.target.value)}
|
|
489
490
|
onKeyDown={(e: React.KeyboardEvent) => {
|
|
490
491
|
if (e.key === "Enter") {
|
|
@@ -501,14 +502,14 @@ function InlinePolicyEditor({
|
|
|
501
502
|
</div>
|
|
502
503
|
{command !== "INSERT" && (
|
|
503
504
|
<div className="flex flex-col gap-1.5">
|
|
504
|
-
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">
|
|
505
|
-
<TextField value={usingExpr} onChange={(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => setUsingExpr(e.target.value)} placeholder="e.g. auth.uid() = uid"/>
|
|
505
|
+
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">{t("studio_policy_using_expr")}</Typography>
|
|
506
|
+
<TextField aria-label={t("studio_policy_using_expr")} value={usingExpr} onChange={(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => setUsingExpr(e.target.value)} placeholder="e.g. auth.uid() = uid"/>
|
|
506
507
|
</div>
|
|
507
508
|
)}
|
|
508
509
|
{showCheck && (
|
|
509
510
|
<div className="flex flex-col gap-1.5">
|
|
510
|
-
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">
|
|
511
|
-
<TextField value={checkExpr} onChange={(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => setCheckExpr(e.target.value)} placeholder="e.g. auth.uid() = uid"/>
|
|
511
|
+
<Typography variant="caption" className="uppercase tracking-wider text-text-secondary">{t("studio_policy_check_expr")}</Typography>
|
|
512
|
+
<TextField aria-label={t("studio_policy_check_expr")} value={checkExpr} onChange={(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => setCheckExpr(e.target.value)} placeholder="e.g. auth.uid() = uid"/>
|
|
512
513
|
</div>
|
|
513
514
|
)}
|
|
514
515
|
</Paper>
|
|
@@ -17,6 +17,7 @@ import { CollectionsConfigController } from "../../types/config_controller";
|
|
|
17
17
|
import { CollectionStudioView } from "./CollectionStudioView";
|
|
18
18
|
import type { CollectionEditorExtensionProps } from "../../extensibility_types";
|
|
19
19
|
import type { AdminCollection } from "@rebasepro/admin-types";
|
|
20
|
+
import { readStoredString, writeStoredString } from "@rebasepro/utils";
|
|
20
21
|
|
|
21
22
|
export interface CollectionsStudioViewProps extends CollectionEditorExtensionProps {
|
|
22
23
|
configController: CollectionsConfigController;
|
|
@@ -77,21 +78,18 @@ export function CollectionsStudioView({
|
|
|
77
78
|
const collections = collectionsProp ?? configController.collections ?? [];
|
|
78
79
|
|
|
79
80
|
// ── Sidebar sizing ──────────────────────────────────────────────────
|
|
81
|
+
// Checked, not just parsed: `parseFloat` answers NaN for anything it cannot
|
|
82
|
+
// read, and a NaN pane size lays the sidebar out to nothing — with the bad
|
|
83
|
+
// value still in storage on reload. `SplitListView.getSavedPanelSize` is the
|
|
84
|
+
// same read, done this way.
|
|
80
85
|
const [sidebarSize, setSidebarSize] = useState(() => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
} catch (e) {
|
|
85
|
-
return 25;
|
|
86
|
-
}
|
|
86
|
+
const saved = readStoredString("rebase_collections_editor_sidebar_size");
|
|
87
|
+
const parsed = saved === null ? NaN : parseFloat(saved);
|
|
88
|
+
return Number.isFinite(parsed) && parsed > 0 && parsed < 100 ? parsed : 25;
|
|
87
89
|
});
|
|
88
90
|
|
|
89
91
|
useEffect(() => {
|
|
90
|
-
|
|
91
|
-
localStorage.setItem("rebase_collections_editor_sidebar_size", sidebarSize.toString());
|
|
92
|
-
} catch (e) {
|
|
93
|
-
// ignore local storage error
|
|
94
|
-
}
|
|
92
|
+
writeStoredString("rebase_collections_editor_sidebar_size", sidebarSize.toString());
|
|
95
93
|
}, [sidebarSize]);
|
|
96
94
|
|
|
97
95
|
return (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { IconForView } from "@rebasepro/app";
|
|
2
|
+
import { IconForView, useTranslation } from "@rebasepro/app";
|
|
3
3
|
import { FieldCaption } from "../../_cms_internals";
|
|
4
4
|
import React, { useState } from "react";
|
|
5
5
|
|
|
@@ -271,10 +271,13 @@ function DefaultDatabaseField({
|
|
|
271
271
|
onDatabaseIdUpdate
|
|
272
272
|
}: { databaseId?: string, disabled?: boolean, onDatabaseIdUpdate: (databaseId: string) => void }) {
|
|
273
273
|
|
|
274
|
-
|
|
274
|
+
const { t } = useTranslation();
|
|
275
|
+
|
|
276
|
+
return <Tooltip title={t("database_id")}
|
|
275
277
|
side={"top"}
|
|
276
278
|
align={"start"}>
|
|
277
279
|
<TextField size={"small"}
|
|
280
|
+
aria-label={t("database_id")}
|
|
278
281
|
invisible={true}
|
|
279
282
|
inputClassName={"text-end"}
|
|
280
283
|
disabled={disabled}
|
|
@@ -111,7 +111,7 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
|
|
|
111
111
|
position={"start"}
|
|
112
112
|
size={"medium"}
|
|
113
113
|
label={t("spread_children_as_columns")}
|
|
114
|
-
onValueChange={(v) => setFieldValue("
|
|
114
|
+
onValueChange={(v) => setFieldValue("admin.spreadChildren", v)}
|
|
115
115
|
value={values.admin?.spreadChildren ?? false}
|
|
116
116
|
/>
|
|
117
117
|
<FieldCaption>
|
|
@@ -7,8 +7,11 @@ export function AdvancedPropertyValidation({ disabled }: {
|
|
|
7
7
|
disabled: boolean
|
|
8
8
|
}) {
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// `ui` was renamed to `admin` in 0.11. Nothing reads `ui.*` — the toggles did
|
|
11
|
+
// nothing — and the boot validator treats the old name as a removed key, so a
|
|
12
|
+
// save through here left a project that would not start.
|
|
13
|
+
const hideFromCollection = "admin.hideFromCollection";
|
|
14
|
+
const readOnly = "admin.readOnly";
|
|
12
15
|
|
|
13
16
|
return (
|
|
14
17
|
|
|
@@ -64,6 +64,24 @@ export function useJsonCollectionsConfigController({
|
|
|
64
64
|
const storeRef = useRef(store);
|
|
65
65
|
storeRef.current = store;
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* The current collections, readable outside a state updater.
|
|
69
|
+
*
|
|
70
|
+
* Three mutators used to compute their new value inside
|
|
71
|
+
* `setCollections(prev => …)` and persist from in there, which cost them
|
|
72
|
+
* both halves of a save: the promise they returned resolved before the
|
|
73
|
+
* store had answered, so a failed write reached the caller as a success,
|
|
74
|
+
* and a `setState` updater must be pure — under `StrictMode` React invokes
|
|
75
|
+
* it twice, so every one of those saves wrote to the store twice in
|
|
76
|
+
* development. Reading `prev` from a ref lets them do what
|
|
77
|
+
* `updatePropertiesOrder` already did: compute, set, then await.
|
|
78
|
+
*/
|
|
79
|
+
const collectionsRef = useRef<AdminCollection[]>([]);
|
|
80
|
+
const applyCollections = useCallback((next: AdminCollection[]) => {
|
|
81
|
+
collectionsRef.current = next;
|
|
82
|
+
setCollections(next);
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
67
85
|
// ── Load on mount ─────────────────────────────────────────────────
|
|
68
86
|
useEffect(() => {
|
|
69
87
|
if (!autoLoad) return;
|
|
@@ -78,7 +96,7 @@ export function useJsonCollectionsConfigController({
|
|
|
78
96
|
if (cancelled) return;
|
|
79
97
|
// Deserialized as a set: a relation's target is a slug, and a slug only
|
|
80
98
|
// resolves against the other collections it arrived with.
|
|
81
|
-
|
|
99
|
+
applyCollections(fromSerializableCollectionConfigs(serialized));
|
|
82
100
|
setNavigationEntries(navEntries);
|
|
83
101
|
} catch (e) {
|
|
84
102
|
console.error("useJsonCollectionsConfigController: failed to load collections", e);
|
|
@@ -88,7 +106,10 @@ export function useJsonCollectionsConfigController({
|
|
|
88
106
|
})();
|
|
89
107
|
|
|
90
108
|
return () => { cancelled = true; };
|
|
91
|
-
|
|
109
|
+
// `applyCollections` is `useCallback(…, [])`, so naming it here cannot
|
|
110
|
+
// re-run the mount effect — it is listed because a stable identity is a
|
|
111
|
+
// fact about today's code, not a reason to leave a dependency out.
|
|
112
|
+
}, [autoLoad, applyCollections]);
|
|
92
113
|
|
|
93
114
|
// ── Helpers ────────────────────────────────────────────────────────
|
|
94
115
|
|
|
@@ -116,49 +137,44 @@ export function useJsonCollectionsConfigController({
|
|
|
116
137
|
) => {
|
|
117
138
|
const collection = collectionData as AdminCollection;
|
|
118
139
|
await persistCollection(collection);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
}, [persistCollection]);
|
|
140
|
+
const prev = collectionsRef.current;
|
|
141
|
+
const idx = prev.findIndex(c => c.slug === id);
|
|
142
|
+
if (idx >= 0) {
|
|
143
|
+
const next = [...prev];
|
|
144
|
+
next[idx] = collection;
|
|
145
|
+
applyCollections(next);
|
|
146
|
+
} else {
|
|
147
|
+
applyCollections([...prev, collection]);
|
|
148
|
+
}
|
|
149
|
+
}, [persistCollection, applyCollections]);
|
|
129
150
|
|
|
130
151
|
const updateCollection = useCallback(async <M extends Record<string, unknown>>(
|
|
131
152
|
{ id, collectionData, previousId }: UpdateCollectionParams<M>
|
|
132
153
|
) => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return next;
|
|
155
|
-
});
|
|
156
|
-
}, [persistCollection]);
|
|
154
|
+
const prev = collectionsRef.current;
|
|
155
|
+
const lookupId = previousId ?? id;
|
|
156
|
+
const idx = prev.findIndex(c => c.slug === lookupId);
|
|
157
|
+
if (idx < 0) return;
|
|
158
|
+
|
|
159
|
+
const merged = { ...prev[idx], ...collectionData } as AdminCollection;
|
|
160
|
+
const next = [...prev];
|
|
161
|
+
next[idx] = merged;
|
|
162
|
+
applyCollections(next);
|
|
163
|
+
|
|
164
|
+
await persistCollection(merged);
|
|
165
|
+
|
|
166
|
+
// If the slug changed, clean up the old entry in the store. After the
|
|
167
|
+
// save, not beside it: dropping the old key first and then failing to
|
|
168
|
+
// write the new one loses the collection outright.
|
|
169
|
+
if (previousId && previousId !== id) {
|
|
170
|
+
await storeRef.current.delete(previousId);
|
|
171
|
+
}
|
|
172
|
+
}, [persistCollection, applyCollections]);
|
|
157
173
|
|
|
158
174
|
const deleteCollection = useCallback(async ({ id }: DeleteCollectionParams) => {
|
|
159
175
|
await storeRef.current.delete(id);
|
|
160
|
-
|
|
161
|
-
}, []);
|
|
176
|
+
applyCollections(collectionsRef.current.filter(c => c.slug !== id));
|
|
177
|
+
}, [applyCollections]);
|
|
162
178
|
|
|
163
179
|
const saveProperty = useCallback(async ({
|
|
164
180
|
path,
|
|
@@ -166,56 +182,48 @@ export function useJsonCollectionsConfigController({
|
|
|
166
182
|
property,
|
|
167
183
|
newPropertiesOrder,
|
|
168
184
|
}: SavePropertyParams) => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const next = [...prev];
|
|
183
|
-
next[idx] = collection as AdminCollection;
|
|
185
|
+
const prev = collectionsRef.current;
|
|
186
|
+
const idx = prev.findIndex(c => c.slug === path);
|
|
187
|
+
if (idx < 0) return;
|
|
188
|
+
|
|
189
|
+
const collection = { ...prev[idx] };
|
|
190
|
+
collection.properties = {
|
|
191
|
+
...collection.properties,
|
|
192
|
+
[propertyKey]: property,
|
|
193
|
+
} as EngineProperties;
|
|
194
|
+
if (newPropertiesOrder) {
|
|
195
|
+
collection.propertiesOrder = newPropertiesOrder as typeof collection.propertiesOrder;
|
|
196
|
+
}
|
|
184
197
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
198
|
+
const next = [...prev];
|
|
199
|
+
next[idx] = collection as AdminCollection;
|
|
200
|
+
applyCollections(next);
|
|
188
201
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}, [persistCollection]);
|
|
202
|
+
await persistCollection(next[idx]);
|
|
203
|
+
}, [persistCollection, applyCollections]);
|
|
192
204
|
|
|
193
205
|
const deleteProperty = useCallback(async ({
|
|
194
206
|
path,
|
|
195
207
|
propertyKey,
|
|
196
208
|
newPropertiesOrder,
|
|
197
209
|
}: DeletePropertyParams) => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const next = [...prev];
|
|
210
|
-
next[idx] = collection as AdminCollection;
|
|
210
|
+
const prev = collectionsRef.current;
|
|
211
|
+
const idx = prev.findIndex(c => c.slug === path);
|
|
212
|
+
if (idx < 0) return;
|
|
213
|
+
|
|
214
|
+
const collection = { ...prev[idx] };
|
|
215
|
+
const { [propertyKey]: _removed, ...remaining } = collection.properties;
|
|
216
|
+
collection.properties = remaining;
|
|
217
|
+
if (newPropertiesOrder) {
|
|
218
|
+
collection.propertiesOrder = newPropertiesOrder as typeof collection.propertiesOrder;
|
|
219
|
+
}
|
|
211
220
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
221
|
+
const next = [...prev];
|
|
222
|
+
next[idx] = collection as AdminCollection;
|
|
223
|
+
applyCollections(next);
|
|
215
224
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}, [persistCollection]);
|
|
225
|
+
await persistCollection(next[idx]);
|
|
226
|
+
}, [persistCollection, applyCollections]);
|
|
219
227
|
|
|
220
228
|
const updatePropertiesOrder = useCallback(async ({
|
|
221
229
|
collection,
|
|
@@ -226,16 +234,16 @@ export function useJsonCollectionsConfigController({
|
|
|
226
234
|
propertiesOrder: newPropertiesOrder,
|
|
227
235
|
} as AdminCollection;
|
|
228
236
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
237
|
+
const prev = collectionsRef.current;
|
|
238
|
+
const idx = prev.findIndex(c => c.slug === collection.slug);
|
|
239
|
+
if (idx >= 0) {
|
|
232
240
|
const next = [...prev];
|
|
233
241
|
next[idx] = updated;
|
|
234
|
-
|
|
235
|
-
}
|
|
242
|
+
applyCollections(next);
|
|
243
|
+
}
|
|
236
244
|
|
|
237
245
|
await persistCollection(updated);
|
|
238
|
-
}, [persistCollection]);
|
|
246
|
+
}, [persistCollection, applyCollections]);
|
|
239
247
|
|
|
240
248
|
const updateKanbanColumnsOrder = useCallback(async ({
|
|
241
249
|
collection,
|
|
@@ -202,13 +202,18 @@ collectionData });
|
|
|
202
202
|
await request("/collection/delete", { collectionId: id });
|
|
203
203
|
},
|
|
204
204
|
|
|
205
|
+
// The follow-up write carries exactly one key, and `partial` is what says
|
|
206
|
+
// so. Read as a whole-collection save it deletes everything it does not
|
|
207
|
+
// mention — `securityRules` first, which then falls back to the
|
|
208
|
+
// directory default and widens who can read the collection.
|
|
205
209
|
saveProperty: async ({ path, propertyKey, property, newPropertiesOrder }: SavePropertyParams) => {
|
|
206
210
|
await request("/property/save", { collectionId: path,
|
|
207
211
|
propertyKey,
|
|
208
212
|
propertyConfig: property });
|
|
209
213
|
if (newPropertiesOrder) {
|
|
210
214
|
await request("/collection/save", { collectionId: path,
|
|
211
|
-
collectionData: { propertiesOrder: newPropertiesOrder }
|
|
215
|
+
collectionData: { propertiesOrder: newPropertiesOrder },
|
|
216
|
+
partial: true });
|
|
212
217
|
}
|
|
213
218
|
},
|
|
214
219
|
deleteProperty: async ({ path, propertyKey, newPropertiesOrder }: DeletePropertyParams) => {
|
|
@@ -216,14 +221,16 @@ collectionData: { propertiesOrder: newPropertiesOrder } });
|
|
|
216
221
|
propertyKey });
|
|
217
222
|
if (newPropertiesOrder) {
|
|
218
223
|
await request("/collection/save", { collectionId: path,
|
|
219
|
-
collectionData: { propertiesOrder: newPropertiesOrder }
|
|
224
|
+
collectionData: { propertiesOrder: newPropertiesOrder },
|
|
225
|
+
partial: true });
|
|
220
226
|
}
|
|
221
227
|
},
|
|
222
228
|
|
|
223
229
|
updatePropertiesOrder: async ({ collection, fullPath, newPropertiesOrder }: UpdatePropertiesOrderParams) => {
|
|
224
230
|
const collectionId = (collection as AdminCollection & { id?: string }).id || fullPath.split("/").pop();
|
|
225
231
|
await request("/collection/save", { collectionId,
|
|
226
|
-
collectionData: { propertiesOrder: newPropertiesOrder }
|
|
232
|
+
collectionData: { propertiesOrder: newPropertiesOrder },
|
|
233
|
+
partial: true });
|
|
227
234
|
},
|
|
228
235
|
updateKanbanColumnsOrder: async () => {
|
|
229
236
|
// Kanban order mapping logic can be added later if needed natively.
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FilterXIcon, iconSize, IconButton, Tooltip } from "@rebasepro/ui";
|
|
2
2
|
import { EntityTableController } from "@rebasepro/admin-types";
|
|
3
3
|
import { useTranslation } from "@rebasepro/app";
|
|
4
4
|
|
|
5
5
|
export function ClearFilterSortButton({
|
|
6
6
|
tableController,
|
|
7
|
-
enabled
|
|
7
|
+
enabled,
|
|
8
|
+
compact
|
|
8
9
|
}: {
|
|
9
10
|
enabled: boolean;
|
|
10
|
-
tableController: EntityTableController
|
|
11
|
+
tableController: EntityTableController;
|
|
12
|
+
/**
|
|
13
|
+
* Toolbar showing icon-only controls (split view, or a layout too narrow
|
|
14
|
+
* for the filters button's label). Only decides the icon size, which
|
|
15
|
+
* follows that button — see `CollectionViewStartActions`.
|
|
16
|
+
*/
|
|
17
|
+
compact?: boolean;
|
|
11
18
|
}) {
|
|
12
19
|
const { t } = useTranslation();
|
|
13
20
|
|
|
@@ -29,17 +36,19 @@ export function ClearFilterSortButton({
|
|
|
29
36
|
}
|
|
30
37
|
return (
|
|
31
38
|
<Tooltip title={label}>
|
|
32
|
-
|
|
39
|
+
{/* An `IconButton`, like every other icon-only control in this
|
|
40
|
+
toolbar. As a `Button` it was a rounded-md box with text
|
|
41
|
+
padding sitting between two circular icon buttons. */}
|
|
42
|
+
<IconButton
|
|
33
43
|
size={"small"}
|
|
34
|
-
variant={"text"}
|
|
35
44
|
aria-label={label}
|
|
36
45
|
onClick={() => {
|
|
37
46
|
tableController.clearFilter?.();
|
|
38
47
|
tableController.setSortBy?.(undefined);
|
|
39
48
|
}}
|
|
40
49
|
>
|
|
41
|
-
<FilterXIcon size={iconSize.small}/>
|
|
42
|
-
</
|
|
50
|
+
<FilterXIcon size={compact ? iconSize.smallest : iconSize.small}/>
|
|
51
|
+
</IconButton>
|
|
43
52
|
</Tooltip>
|
|
44
53
|
);
|
|
45
54
|
}
|
|
@@ -17,7 +17,7 @@ import { propertiesToColumns } from "./column_utils";
|
|
|
17
17
|
import { ErrorView } from "@rebasepro/app";
|
|
18
18
|
import { SelectableTable } from "../SelectableTable/SelectableTable";
|
|
19
19
|
import { cls } from "@rebasepro/ui";
|
|
20
|
-
import { getRowHeight } from "@rebasepro/app";
|
|
20
|
+
import { getRowHeight, isDisabled } from "@rebasepro/app";
|
|
21
21
|
import { getValueInPath } from "@rebasepro/utils";
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -147,7 +147,7 @@ export const CollectionTableBinding = function CollectionTableBinding<M extends
|
|
|
147
147
|
propertyKey,
|
|
148
148
|
entity
|
|
149
149
|
}) ?? columnCustom?.resolvedProperty;
|
|
150
|
-
if (!property
|
|
150
|
+
if (!property || !isDisabled(property)) {
|
|
151
151
|
disabled = false;
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -18,7 +18,7 @@ import { EntityTableCellActions } from "./internal/EntityTableCellActions";
|
|
|
18
18
|
import { useSelectableTableController } from "../SelectableTable/SelectableTableContext";
|
|
19
19
|
import { useClearRestoreValue } from "../../form/useClearRestoreValue";
|
|
20
20
|
import { getRowHeight } from "@rebasepro/app";
|
|
21
|
-
import { isReadOnly } from "@rebasepro/app";
|
|
21
|
+
import { isDisabled, isReadOnly } from "@rebasepro/app";
|
|
22
22
|
import { TableRelationField } from "./fields/TableRelationField";
|
|
23
23
|
import { TableRelationSelectorField } from "./fields/TableRelationSelectorField";
|
|
24
24
|
|
|
@@ -95,7 +95,7 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any>>(
|
|
|
95
95
|
const customPreview = Boolean(property.admin?.Preview);
|
|
96
96
|
const readOnlyProperty = isReadOnly(property);
|
|
97
97
|
const disabledTooltip: string | undefined = typeof property.admin?.disabled === "object" ? property.admin?.disabled.disabledMessage : undefined;
|
|
98
|
-
const disabled = readonly || disabledProp ||
|
|
98
|
+
const disabled = readonly || disabledProp || isDisabled(property);
|
|
99
99
|
|
|
100
100
|
const validation = useMemo(() => mapPropertyToZod({
|
|
101
101
|
property,
|