@rebasepro/admin 0.13.0 → 0.13.1-canary.g249daa1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CollectionEditorDialog-CjXkWZ64.js → CollectionEditorDialog-DgfCYDmF.js} +3 -3
- package/dist/{CollectionEditorDialog-CjXkWZ64.js.map → CollectionEditorDialog-DgfCYDmF.js.map} +1 -1
- package/dist/{PropertyEditView-W8I3YV89.js → PropertyEditView-CCrruunW.js} +2 -2
- package/dist/{PropertyEditView-W8I3YV89.js.map → PropertyEditView-CCrruunW.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-BsLTNoqX.js → RouterCollectionsStudioView-C_EwDSi1.js} +9 -13
- package/dist/RouterCollectionsStudioView-C_EwDSi1.js.map +1 -0
- package/dist/collection_editor/serializable_types.d.ts +1 -0
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/AdditionalFieldValue.d.ts +25 -0
- package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +16 -0
- package/dist/components/CollectionViewBinding/EntityCardBinding.d.ts +3 -1
- package/dist/components/CollectionViewBinding/SearchExplanation.d.ts +48 -0
- package/dist/components/CollectionViewBinding/SearchHighlight.d.ts +92 -0
- package/dist/components/CollectionViewBinding/SlotValue.d.ts +63 -0
- package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +17 -4
- package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +12 -0
- package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +55 -5
- package/dist/components/DetailViewBinding.d.ts +15 -0
- package/dist/components/DrawerNavigationGroup.d.ts +9 -6
- package/dist/components/DrawerNavigationItem.d.ts +16 -6
- package/dist/components/EditViewBinding.d.ts +30 -1
- package/dist/components/EntityDisplayHeader.d.ts +53 -0
- package/dist/components/EntityIdentityBar.d.ts +43 -1
- package/dist/components/EntityViewBinding.d.ts +60 -6
- package/dist/components/RebaseLayout.d.ts +1 -1
- package/dist/components/RebaseShell.d.ts +1 -1
- package/dist/components/RelationSelector.d.ts +8 -1
- package/dist/components/UserSelector.d.ts +8 -1
- package/dist/components/app/Scaffold.d.ts +9 -3
- package/dist/components/index.d.ts +2 -0
- package/dist/{export-J79rMoNj.js → export-CV-qQfxK.js} +2 -2
- package/dist/{export-J79rMoNj.js.map → export-CV-qQfxK.js.map} +1 -1
- package/dist/form/components/FieldBlock.d.ts +58 -4
- package/dist/form/components/FormRail.d.ts +13 -0
- package/dist/form/components/FormSections.d.ts +15 -1
- package/dist/form/components/useRailVisible.d.ts +5 -4
- package/dist/form/form_utils.d.ts +38 -1
- package/dist/{history-D7L_JlLO.js → history-By7QT3V0.js} +4 -4
- package/dist/{history-D7L_JlLO.js.map → history-By7QT3V0.js.map} +1 -1
- package/dist/hooks/useEntityDisplay.d.ts +102 -0
- package/dist/hooks/useEntityDisplayTitle.d.ts +18 -9
- package/dist/{import-BvYl--U4.js → import-CYbvYa_x.js} +3 -3
- package/dist/{import-BvYl--U4.js.map → import-CYbvYa_x.js.map} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +152 -57
- package/dist/index.js.map +1 -1
- package/dist/preview/compact_text.d.ts +23 -0
- package/dist/preview/components/BooleanPreview.d.ts +3 -1
- package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayOneOfPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayPropertyPreview.d.ts +1 -1
- package/dist/preview/property_previews/MapPropertyPreview.d.ts +1 -1
- package/dist/preview/property_previews/StringPropertyPreview.d.ts +1 -1
- package/dist/routes/RebaseRoute.d.ts +1 -1
- package/dist/types/components/PropertyPreviewProps.d.ts +19 -0
- package/dist/util/number_format.d.ts +14 -0
- package/dist/util/previews.d.ts +9 -2
- package/dist/util/view_constants.d.ts +24 -0
- package/dist/util/view_mode.d.ts +18 -0
- package/dist/{util-GiwPgxnL.js → util-B8hurkxt.js} +3569 -1692
- package/dist/util-B8hurkxt.js.map +1 -0
- package/package.json +9 -9
- package/src/collection_editor/serializable_types.ts +1 -0
- package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +9 -11
- package/src/components/AdditionalFieldValue.tsx +68 -0
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +7 -8
- package/src/components/CollectionTableBinding/PropertyTableCell.tsx +2 -2
- package/src/components/CollectionTableBinding/column_utils.tsx +15 -6
- package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +2 -2
- package/src/components/CollectionViewBinding/BoardCardBinding.tsx +26 -27
- package/src/components/CollectionViewBinding/CollectionCardViewBinding.tsx +2 -1
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +822 -354
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +1 -1
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +51 -12
- package/src/components/CollectionViewBinding/EntityCardBinding.tsx +51 -39
- package/src/components/CollectionViewBinding/SearchExplanation.tsx +106 -0
- package/src/components/CollectionViewBinding/SearchHighlight.tsx +285 -0
- package/src/components/CollectionViewBinding/SlotValue.tsx +156 -0
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +23 -7
- package/src/components/CollectionViewBinding/SplitListShowButton.tsx +28 -0
- package/src/components/CollectionViewBinding/SplitListView.tsx +47 -7
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +12 -3
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +183 -64
- package/src/components/CollectionViewBinding/utils.ts +7 -4
- package/src/components/DefaultDrawer.tsx +19 -23
- package/src/components/DetailViewBinding.tsx +109 -62
- package/src/components/DrawerNavigationGroup.tsx +16 -39
- package/src/components/DrawerNavigationItem.tsx +44 -16
- package/src/components/EditViewBinding.tsx +233 -50
- package/src/components/EntityDisplayHeader.tsx +170 -0
- package/src/components/EntityIdentityBar.tsx +163 -23
- package/src/components/EntityInspector.tsx +24 -6
- package/src/components/EntityPreviewBinding.tsx +58 -18
- package/src/components/EntityViewBinding.tsx +344 -41
- package/src/components/InlineEntityPreview.tsx +10 -10
- package/src/components/RebaseLayout.tsx +2 -2
- package/src/components/RebaseShell.tsx +2 -2
- package/src/components/ReferenceTable/SelectionTableBinding.tsx +4 -2
- package/src/components/RelationSelector.tsx +23 -9
- package/src/components/SideDialogs.tsx +15 -3
- package/src/components/SidePanelBinding.tsx +122 -66
- package/src/components/UserSelector.tsx +20 -8
- package/src/components/app/Scaffold.tsx +112 -10
- package/src/components/history/LastEditedByIndicator.tsx +7 -12
- package/src/components/index.ts +3 -0
- package/src/data_import/utils/data.ts +9 -1
- package/src/form/EntityForm.tsx +74 -53
- package/src/form/EntityFormBinding.tsx +24 -11
- package/src/form/PropertyFieldBinding.tsx +2 -2
- package/src/form/components/FieldBlock.tsx +80 -10
- package/src/form/components/FormRail.tsx +19 -2
- package/src/form/components/FormSections.tsx +32 -5
- package/src/form/components/StorageUploadProgress.tsx +9 -3
- package/src/form/components/useRailVisible.tsx +5 -4
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -2
- package/src/form/field_bindings/BinaryFieldBinding.tsx +10 -5
- package/src/form/field_bindings/BlockFieldBinding.tsx +3 -3
- package/src/form/field_bindings/GeopointFieldBinding.tsx +23 -16
- package/src/form/field_bindings/MapFieldBinding.tsx +2 -2
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +13 -7
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +7 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +21 -2
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
- package/src/form/field_bindings/RelationFieldBinding.tsx +5 -2
- package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +2 -2
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +4 -1
- package/src/form/field_bindings/VectorFieldBinding.tsx +6 -4
- package/src/form/form_utils.ts +78 -0
- package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +29 -5
- package/src/hooks/useEntityDisplay.tsx +262 -0
- package/src/hooks/useEntityDisplayTitle.tsx +27 -38
- package/src/index.ts +9 -1
- package/src/preview/PropertyPreview.tsx +25 -5
- package/src/preview/compact_text.ts +68 -0
- package/src/preview/components/BooleanPreview.tsx +5 -2
- package/src/preview/components/StorageThumbnail.tsx +5 -2
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +6 -3
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +26 -1
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +10 -1
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +10 -1
- package/src/preview/property_previews/MapPropertyPreview.tsx +52 -1
- package/src/preview/property_previews/NumberPropertyPreview.tsx +9 -1
- package/src/preview/property_previews/StringPropertyPreview.tsx +8 -5
- package/src/routes/RebaseRoute.tsx +62 -4
- package/src/types/components/PropertyPreviewProps.tsx +21 -0
- package/src/util/navigation_utils.ts +4 -2
- package/src/util/number_format.ts +46 -0
- package/src/util/previews.ts +8 -55
- package/src/util/property_utils.tsx +6 -1
- package/src/util/view_constants.ts +27 -0
- package/src/util/view_mode.ts +36 -0
- package/dist/RouterCollectionsStudioView-BsLTNoqX.js.map +0 -1
- package/dist/util-GiwPgxnL.js.map +0 -1
|
@@ -128,27 +128,24 @@ export function GeopointFieldBinding({
|
|
|
128
128
|
: undefined;
|
|
129
129
|
|
|
130
130
|
return (
|
|
131
|
-
<div className="flex flex-col gap-2 w-full
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
<div className="flex flex-col gap-2 w-full">
|
|
132
|
+
{/* Label only, and only when there is one: a row that survives
|
|
133
|
+
`hideLabel` still spends its `mb-1` and the column's `gap-2`, so
|
|
134
|
+
the pair of inputs sat below the control next to them even
|
|
135
|
+
though the two labels were level. */}
|
|
136
|
+
{!hideLabel && <div className="flex items-center justify-between mb-1">
|
|
137
|
+
<LabelWithIcon
|
|
134
138
|
icon={getIconForProperty(property, "small")}
|
|
135
139
|
required={property.validation?.required}
|
|
136
140
|
title={property.name ?? propertyKey}
|
|
137
|
-
/>
|
|
138
|
-
|
|
139
|
-
<IconButton
|
|
140
|
-
size="small"
|
|
141
|
-
onClick={handleClear}
|
|
142
|
-
className="text-text-secondary hover:text-red-500"
|
|
143
|
-
aria-label="Clear location"
|
|
144
|
-
>
|
|
145
|
-
<Trash2Icon size={14}/>
|
|
146
|
-
</IconButton>
|
|
147
|
-
)}
|
|
148
|
-
</div>
|
|
141
|
+
/>
|
|
142
|
+
</div>}
|
|
149
143
|
|
|
150
144
|
<PropertyIdCopyTooltip propertyKey={propertyKey}>
|
|
151
|
-
|
|
145
|
+
{/* Clear sits beside the inputs rather than above them, so
|
|
146
|
+
entering a location does not grow the field by a row and
|
|
147
|
+
knock it out of line with its neighbour. */}
|
|
148
|
+
<div className="flex gap-4 w-full items-center">
|
|
152
149
|
<TextField
|
|
153
150
|
className="flex-1"
|
|
154
151
|
size={size}
|
|
@@ -178,6 +175,16 @@ export function GeopointFieldBinding({
|
|
|
178
175
|
placeholder="e.g. 2.1686"
|
|
179
176
|
inputClassName="font-mono"
|
|
180
177
|
/>
|
|
178
|
+
{!disabled && (latText || lngText) && (
|
|
179
|
+
<IconButton
|
|
180
|
+
size="small"
|
|
181
|
+
onClick={handleClear}
|
|
182
|
+
className="shrink-0 text-text-secondary hover:text-red-500"
|
|
183
|
+
aria-label="Clear location"
|
|
184
|
+
>
|
|
185
|
+
<Trash2Icon size={14}/>
|
|
186
|
+
</IconButton>
|
|
187
|
+
)}
|
|
181
188
|
</div>
|
|
182
189
|
</PropertyIdCopyTooltip>
|
|
183
190
|
|
|
@@ -6,7 +6,7 @@ import { useTranslation } from "@rebasepro/app";
|
|
|
6
6
|
|
|
7
7
|
import { ErrorBoundary } from "@rebasepro/ui";
|
|
8
8
|
import { getIconForProperty } from "../../util/property_utils";
|
|
9
|
-
import { isHidden, isReadOnly } from "@rebasepro/app";
|
|
9
|
+
import { isDisabled, isHidden, isReadOnly } from "@rebasepro/app";
|
|
10
10
|
import { FieldHelperText } from "../components/FieldHelperText";
|
|
11
11
|
import { LabelWithIconAndTooltip } from "../components/LabelWithIconAndTooltip";
|
|
12
12
|
import { PropertyFieldBinding } from "../PropertyFieldBinding";
|
|
@@ -50,7 +50,7 @@ export function MapFieldBinding({
|
|
|
50
50
|
{Object.entries(mapProperties)
|
|
51
51
|
.filter(([_, property]) => !isHidden(property))
|
|
52
52
|
.map(([entryKey, childProperty], index) => {
|
|
53
|
-
const thisDisabled = isReadOnly(childProperty) ||
|
|
53
|
+
const thisDisabled = isReadOnly(childProperty) || isDisabled(childProperty);
|
|
54
54
|
const fieldBindingProps: PropertyFieldBindingProps<Record<string, unknown>> = {
|
|
55
55
|
propertyKey: `${propertyKey}.${entryKey}`,
|
|
56
56
|
disabled: disabled || thisDisabled,
|
|
@@ -216,17 +216,26 @@ export function MarkdownEditorFieldBinding({
|
|
|
216
216
|
|
|
217
217
|
return (
|
|
218
218
|
<>
|
|
219
|
-
<div className="flex items-center w-full">
|
|
220
|
-
|
|
219
|
+
{!hideLabel && <div className="flex items-center w-full">
|
|
220
|
+
<LabelWithIconAndTooltip
|
|
221
221
|
propertyKey={propertyKey}
|
|
222
222
|
icon={getIconForProperty(property, "small")}
|
|
223
223
|
required={property.validation?.required}
|
|
224
224
|
title={property.name ?? propertyKey}
|
|
225
|
-
className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
|
|
226
|
-
|
|
225
|
+
className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>
|
|
226
|
+
</div>}
|
|
227
|
+
{/* `relative` so Clear can float inside the box. It used to sit in
|
|
228
|
+
the label row, which meant a clearable field with its label
|
|
229
|
+
lifted out by the form still drew a row of chrome above the box
|
|
230
|
+
and started lower than the field beside it.
|
|
231
|
+
Bottom-right, not top: the editor puts its own mode toggle at
|
|
232
|
+
`top-2 right-2` and the two would sit on each other. */}
|
|
233
|
+
<div
|
|
234
|
+
className={cls("relative rounded-md", fieldBackgroundMixin, disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin)}>
|
|
227
235
|
{property.admin?.clearable && !disabled && (
|
|
228
236
|
<IconButton
|
|
229
237
|
size="small"
|
|
238
|
+
className="absolute bottom-1 right-1 z-10"
|
|
230
239
|
onClick={(e) => {
|
|
231
240
|
e.stopPropagation();
|
|
232
241
|
e.preventDefault();
|
|
@@ -236,9 +245,6 @@ export function MarkdownEditorFieldBinding({
|
|
|
236
245
|
<XIcon/>
|
|
237
246
|
</IconButton>
|
|
238
247
|
)}
|
|
239
|
-
</div>
|
|
240
|
-
<div
|
|
241
|
-
className={cls("rounded-md", fieldBackgroundMixin, disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin)}>
|
|
242
248
|
{editor}
|
|
243
249
|
</div>
|
|
244
250
|
<FieldHelperText includeDescription={includeDescription}
|
|
@@ -87,7 +87,13 @@ export function MultiSelectFieldBinding({
|
|
|
87
87
|
return (
|
|
88
88
|
<>
|
|
89
89
|
<MultiSelect
|
|
90
|
-
className
|
|
90
|
+
// No top margin: `className` lands on the box itself, not on
|
|
91
|
+
// the label above it, so `mt-2` was 8px of gap between the
|
|
92
|
+
// form's label and this control and none between the label and
|
|
93
|
+
// the text field beside it. A binding renders a control, not a
|
|
94
|
+
// labelled control — the top of what it renders is the top of
|
|
95
|
+
// the box, or it will not line up with its neighbour.
|
|
96
|
+
className={"w-full"}
|
|
91
97
|
size={size}
|
|
92
98
|
value={validValue ? value.map((v) => v?.toString()) : []}
|
|
93
99
|
disabled={disabled}
|
|
@@ -45,7 +45,7 @@ export function ReadOnlyFieldBinding({
|
|
|
45
45
|
icon={getIconForProperty(property, "small")}
|
|
46
46
|
required={property.validation?.required}
|
|
47
47
|
title={property.name ?? propertyKey}
|
|
48
|
-
className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3
|
|
48
|
+
className={"h-8 text-text-secondary dark:text-text-secondary-dark ml-3"}/>
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
<div
|
|
@@ -53,13 +53,32 @@ export function ReadOnlyFieldBinding({
|
|
|
53
53
|
"w-full overflow-x-scroll no-scrollbar flex items-center",
|
|
54
54
|
skipCardWrapper
|
|
55
55
|
? ""
|
|
56
|
-
|
|
56
|
+
// `min-h-12` is `TextField`'s own `large` height, which is
|
|
57
|
+
// what every editable field in the form is. At 40px a
|
|
58
|
+
// read-only field sat 8px short of the input beside it,
|
|
59
|
+
// so a row holding one of each had its two boxes'
|
|
60
|
+
// baselines and bottom edges disagree — the field looked
|
|
61
|
+
// misaligned rather than uneditable.
|
|
62
|
+
// `px-3` is what `TextField` gives its input, so a
|
|
63
|
+
// read-only value starts on the same vertical line as
|
|
64
|
+
// the text beside it. It was `px-4 md:px-6`, which in a
|
|
65
|
+
// one-column span spent a sixth of the box on empty
|
|
66
|
+
// margin and pushed short values towards the middle.
|
|
67
|
+
: "rounded-lg border border-surface-200 dark:border-surface-700 px-3 min-h-12 opacity-80"
|
|
57
68
|
)}>
|
|
58
69
|
|
|
59
70
|
<ErrorBoundary>
|
|
71
|
+
{/* The field is labelled either just above by this
|
|
72
|
+
component, or by the `FieldBlock` that asked for
|
|
73
|
+
`hideLabel` — never by the preview. `BooleanPreview`
|
|
74
|
+
prints the property name beside its checkbox, so a
|
|
75
|
+
read-only boolean read as "Procesado por LLM" over a
|
|
76
|
+
checkbox saying "Procesado por LLM", wrapped over two
|
|
77
|
+
lines in a one-column span. */}
|
|
60
78
|
<PropertyPreview propertyKey={propertyKey}
|
|
61
79
|
value={value}
|
|
62
80
|
property={property}
|
|
81
|
+
hideLabel={hideLabel || !minimalistView}
|
|
63
82
|
size={"medium"}/>
|
|
64
83
|
</ErrorBoundary>
|
|
65
84
|
|
|
@@ -121,7 +121,7 @@ function ReferenceFieldBindingInternal({
|
|
|
121
121
|
/>}
|
|
122
122
|
|
|
123
123
|
{!refValue && <div className="justify-center text-left">
|
|
124
|
-
<EntityPreviewContainer className={cls("px-6 h-
|
|
124
|
+
<EntityPreviewContainer className={cls("px-6 h-12 text-sm font-medium flex items-center gap-6",
|
|
125
125
|
disabled || isSubmitting
|
|
126
126
|
? "text-surface-accent-500"
|
|
127
127
|
: "cursor-pointer text-surface-accent-700 dark:text-surface-accent-300 hover:bg-surface-accent-50 dark:hover:bg-surface-800 group-hover:bg-surface-accent-50 dark:group-hover:bg-surface-800")}
|
|
@@ -72,7 +72,10 @@ function RelationSelectorBinding({
|
|
|
72
72
|
const singleValue = normalizedSingle ?? null;
|
|
73
73
|
const multipleValue = (manyRelation && Array.isArray(value)) ? value : [];
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
// Passed straight through. Mapping the form's `large` onto the selector's
|
|
76
|
+
// `medium` produced a 56px box next to 48px text fields — the labels lined
|
|
77
|
+
// up and the boxes did not.
|
|
78
|
+
const selectorSize = size;
|
|
76
79
|
|
|
77
80
|
return (
|
|
78
81
|
<div className="">
|
|
@@ -176,7 +179,7 @@ function SingleRelationFieldBinding({
|
|
|
176
179
|
/>}
|
|
177
180
|
|
|
178
181
|
{!value && <div className="justify-center text-left">
|
|
179
|
-
<EntityPreviewContainer className={cls("px-6 h-
|
|
182
|
+
<EntityPreviewContainer className={cls("px-6 h-12 text-sm font-medium flex items-center gap-6",
|
|
180
183
|
disabled || isSubmitting
|
|
181
184
|
? "text-surface-accent-500"
|
|
182
185
|
: "cursor-pointer text-surface-accent-700 dark:text-surface-accent-300 hover:bg-surface-accent-50 dark:hover:bg-surface-800 group-hover:bg-surface-accent-50 dark:group-hover:bg-surface-800")}
|
|
@@ -11,7 +11,7 @@ import { getIconForProperty } from "../../util/property_utils";
|
|
|
11
11
|
import { PropertyFieldBinding } from "../PropertyFieldBinding";
|
|
12
12
|
import { ExpandablePanel, Typography } from "@rebasepro/ui";
|
|
13
13
|
import { useClearRestoreValue } from "../useClearRestoreValue";
|
|
14
|
-
import { useAuthController, useTranslation } from "@rebasepro/app";
|
|
14
|
+
import { isDisabled, useAuthController, useTranslation } from "@rebasepro/app";
|
|
15
15
|
import { mergeDeep } from "@rebasepro/utils";
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -96,14 +96,14 @@ export function RepeatFieldBinding({
|
|
|
96
96
|
</ErrorBoundary>;
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
-
const canAddElements = !property
|
|
99
|
+
const canAddElements = !isDisabled(property) && !isSubmitting && !disabled && (property.admin?.canAddElements || property.admin?.canAddElements === undefined);
|
|
100
100
|
const sortable = property.admin?.sortable === undefined ? true : property.admin.sortable;
|
|
101
101
|
const arrayContainer = <ArrayContainer droppableId={propertyKey}
|
|
102
102
|
addLabel={property.name ? t("add_to_field", { fieldName: property.name }) : t("add_entry")}
|
|
103
103
|
value={(value as unknown[]) ?? []}
|
|
104
104
|
buildEntry={buildEntry}
|
|
105
105
|
onInternalIdAdded={setLastAddedId}
|
|
106
|
-
disabled={isSubmitting ||
|
|
106
|
+
disabled={isSubmitting || isDisabled(property)}
|
|
107
107
|
canAddElements={canAddElements}
|
|
108
108
|
sortable={sortable}
|
|
109
109
|
newDefaultEntry={getDefaultValueFor(property.of)}
|
|
@@ -8,7 +8,7 @@ import { useDropzone } from "react-dropzone";
|
|
|
8
8
|
import { FieldHelperText } from "../components/FieldHelperText";
|
|
9
9
|
import { LabelWithIconAndTooltip } from "../components/LabelWithIconAndTooltip";
|
|
10
10
|
|
|
11
|
-
import { isReadOnly } from "@rebasepro/app";
|
|
11
|
+
import { isDisabled, isReadOnly } from "@rebasepro/app";
|
|
12
12
|
import { getIconForProperty } from "../../util/property_utils";
|
|
13
13
|
import { useAuthController, useSnackbarController, useStorageSource, useTranslation } from "@rebasepro/app";
|
|
14
14
|
import { StorageSourceContext } from "@rebasepro/app";
|
|
@@ -74,7 +74,7 @@ export function StorageUploadFieldBinding({
|
|
|
74
74
|
const authController = useAuthController();
|
|
75
75
|
|
|
76
76
|
const storageSource = useStorageSource();
|
|
77
|
-
const disabled = isReadOnly(property) ||
|
|
77
|
+
const disabled = isReadOnly(property) || isDisabled(property) || isSubmitting || context.disabled;
|
|
78
78
|
|
|
79
79
|
const {
|
|
80
80
|
internalValue,
|
|
@@ -34,7 +34,10 @@ export function UserSelectFieldBinding({
|
|
|
34
34
|
size = "large"
|
|
35
35
|
}: UserSelectProps) {
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
// Passed straight through. Mapping the form's `large` onto the selector's
|
|
38
|
+
// `medium` produced a 56px box next to 48px text fields — the labels lined
|
|
39
|
+
// up and the boxes did not.
|
|
40
|
+
const selectorSize = size;
|
|
38
41
|
|
|
39
42
|
return (
|
|
40
43
|
<>
|
|
@@ -98,11 +98,13 @@ export function VectorFieldBinding({
|
|
|
98
98
|
const isPopulated = arrayValue.length > 0;
|
|
99
99
|
|
|
100
100
|
return (
|
|
101
|
-
<div className="flex flex-col gap-2 w-full
|
|
102
|
-
{/*
|
|
103
|
-
|
|
101
|
+
<div className="flex flex-col gap-2 w-full">
|
|
102
|
+
{/* Only when there is a label to put in it — an empty row still
|
|
103
|
+
spends its `mb-1` and the column's `gap-2`, and the control ends
|
|
104
|
+
up below the one beside it under a label at the same height. */}
|
|
105
|
+
{label && <div className="flex items-center justify-between mb-1">
|
|
104
106
|
{label}
|
|
105
|
-
</div>
|
|
107
|
+
</div>}
|
|
106
108
|
|
|
107
109
|
<PropertyIdCopyTooltip propertyKey={propertyKey}>
|
|
108
110
|
<div className="w-full">
|
package/src/form/form_utils.ts
CHANGED
|
@@ -114,6 +114,84 @@ export function getChanges<T extends object>(source: Partial<T>, comparison: Par
|
|
|
114
114
|
return changes;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/**
|
|
118
|
+
* What the local-changes backup still has to offer, given what the form is
|
|
119
|
+
* already showing — the answer the "unsaved local changes" banner is asking for.
|
|
120
|
+
* `undefined` means there is nothing left to apply, and no banner.
|
|
121
|
+
*
|
|
122
|
+
* Measured against what the form *opens showing*, not against the stored
|
|
123
|
+
* record. The banner exists to offer a draft the form is not already displaying,
|
|
124
|
+
* and an edit carried across a change of layout is in both the backup and the
|
|
125
|
+
* form: measuring against the stored values alone offered to restore the values
|
|
126
|
+
* already on screen, over an edit the user had never left.
|
|
127
|
+
*/
|
|
128
|
+
export function getUnappliedLocalChanges<M extends Record<string, unknown>>(
|
|
129
|
+
backupValues: Partial<M>,
|
|
130
|
+
openingValues: Partial<M>
|
|
131
|
+
): Partial<M> | undefined {
|
|
132
|
+
const changes = getChanges(backupValues, openingValues);
|
|
133
|
+
const cleaned = removeEmptyContainers(changes);
|
|
134
|
+
if (!cleaned || typeof cleaned !== "object" || Object.keys(cleaned).length === 0) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
return cleaned as Partial<M>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* What travels when the same record changes layout — the split's "hide list",
|
|
142
|
+
* full screen's "show list", the side panel's "open full screen". One mounted
|
|
143
|
+
* form is replaced by another showing the same record, so the edit in progress
|
|
144
|
+
* has to be handed over; left to the local-changes backup alone, the new form
|
|
145
|
+
* met it as a draft from a closed tab and raised the "unsaved local changes"
|
|
146
|
+
* banner over changes the user had made a second earlier and never left.
|
|
147
|
+
*
|
|
148
|
+
* Returns `undefined` when there is nothing to carry, which is its own answer: a
|
|
149
|
+
* record nobody edited must open in the next layout exactly as clean as it was.
|
|
150
|
+
*
|
|
151
|
+
* Only what was edited here travels, keyed by `touched` — the same subset the
|
|
152
|
+
* backup stores. Carrying the whole record instead marks every field touched in
|
|
153
|
+
* the receiving form, which lights up every empty required field as an error and
|
|
154
|
+
* writes the entire record back out as a "local change".
|
|
155
|
+
*/
|
|
156
|
+
export function getEditHandoffValues<M extends Record<string, unknown>>({
|
|
157
|
+
status,
|
|
158
|
+
dirty,
|
|
159
|
+
values,
|
|
160
|
+
touched,
|
|
161
|
+
storedValues
|
|
162
|
+
}: {
|
|
163
|
+
status: EntityStatus;
|
|
164
|
+
/** Whether `values` differ from what is stored. */
|
|
165
|
+
dirty: boolean;
|
|
166
|
+
values: Partial<M>;
|
|
167
|
+
touched: Record<string, boolean>;
|
|
168
|
+
/** The stored record, for the fallback below. Absent for a new one. */
|
|
169
|
+
storedValues?: Partial<M>;
|
|
170
|
+
}): Partial<M> | undefined {
|
|
171
|
+
|
|
172
|
+
if (!values) return undefined;
|
|
173
|
+
|
|
174
|
+
// A record with nothing stored behind it carries whole: there is no
|
|
175
|
+
// baseline to fall back to, so anything not carried is simply lost.
|
|
176
|
+
if (status === "new" || status === "copy") {
|
|
177
|
+
return Object.keys(values).length > 0 ? values : undefined;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!dirty) return undefined;
|
|
181
|
+
|
|
182
|
+
const touchedValues = removeEmptyContainers(extractTouchedValues(values, touched ?? {})) as Partial<M> | undefined;
|
|
183
|
+
if (touchedValues && Object.keys(touchedValues).length > 0) {
|
|
184
|
+
return touchedValues;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Dirty with nothing touched — a value set programmatically, by a plugin or
|
|
188
|
+
// a custom field that writes through `setFieldValue` without the blur that
|
|
189
|
+
// marks it. Diffing against the stored record still finds it, and dropping
|
|
190
|
+
// it here would hand over a form that opens clean over an edit.
|
|
191
|
+
const changes = getChanges(values, storedValues ?? {});
|
|
192
|
+
return Object.keys(changes).length > 0 ? changes : undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
117
195
|
export function getInitialEntityValues<M extends Record<string, unknown>>(
|
|
118
196
|
authController: AuthController,
|
|
119
197
|
collection: AdminCollection,
|
|
@@ -41,11 +41,35 @@ export function useBuildCollectionRegistryController(props: {
|
|
|
41
41
|
if (!collectionPath) return undefined;
|
|
42
42
|
|
|
43
43
|
let collection: AdminCollection | undefined;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
|
|
45
|
+
// An exact slug match first, because a slug is allowed to contain
|
|
46
|
+
// slashes and several drivers need it to: a Firestore collection
|
|
47
|
+
// partitioned by locale is declared as `content/de-DE/podcasts`, and
|
|
48
|
+
// that string is its *name*, not a path to walk.
|
|
49
|
+
//
|
|
50
|
+
// Without this, such a slug went straight to `resolvePathToCollections`,
|
|
51
|
+
// which reads any `a/b/c` as collection/entity/subcollection, looked for
|
|
52
|
+
// a root collection called `content`, found none and threw — and the
|
|
53
|
+
// catch below turned that into `undefined`. `RebaseRoute` renders
|
|
54
|
+
// `null` for an unresolved collection, so the whole view came out blank
|
|
55
|
+
// with nothing in the console above `debug`. Thirty-five collections in
|
|
56
|
+
// one app were unreachable that way.
|
|
57
|
+
// `collectionPath`, not `cleanedPath`: an even segment count means the
|
|
58
|
+
// path ends at a record, and the id has already been trimmed off above.
|
|
59
|
+
// Matching the raw path instead would only ever hit for a collection
|
|
60
|
+
// route, leaving `content/de-DE/podcasts/abc123` back on the walker.
|
|
61
|
+
collection = registry.get(collectionPath) as AdminCollection | undefined;
|
|
62
|
+
|
|
63
|
+
if (!collection) {
|
|
64
|
+
try {
|
|
65
|
+
collection = registry.resolvePathToCollections(collectionPath).finalCollection;
|
|
66
|
+
} catch (e) {
|
|
67
|
+
// Warn, not debug. An unresolved collection ends as a route with
|
|
68
|
+
// nothing in it, and at `debug` there was no way to tell that
|
|
69
|
+
// from a collection that legitimately has no rows.
|
|
70
|
+
console.warn(`[rebase] Could not resolve a collection for "${collectionPath}"`, e);
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
49
73
|
}
|
|
50
74
|
|
|
51
75
|
if (!collection) {
|