@rebasepro/admin 0.12.0 → 0.12.1-canary.g009ed95
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/README.md +1 -1
- package/dist/{CollectionEditorDialog-Dpc75wIZ.js → CollectionEditorDialog-B-iVE9y3.js} +75 -62
- package/dist/{CollectionEditorDialog-Dpc75wIZ.js.map → CollectionEditorDialog-B-iVE9y3.js.map} +1 -1
- package/dist/{PropertyEditView-DH3XZC2x.js → PropertyEditView-DoLEH_bQ.js} +36 -16
- package/dist/{PropertyEditView-DH3XZC2x.js.map → PropertyEditView-DoLEH_bQ.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-BZLq4MGp.js → RouterCollectionsStudioView-C3669Ezm.js} +5 -5
- package/dist/{RouterCollectionsStudioView-BZLq4MGp.js.map → RouterCollectionsStudioView-C3669Ezm.js.map} +1 -1
- package/dist/collection_editor/serializable_types.d.ts +4 -1
- package/dist/collection_editor/ui/collection_editor/CollectionRelationsTab.d.ts +1 -0
- package/dist/collection_editor/useLocalCollectionsConfigController.d.ts +8 -0
- package/dist/collection_editor_ui.js +5 -5
- package/dist/collection_editor_ui.js.map +1 -1
- package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +10 -0
- package/dist/components/CollectionViewBinding/SplitViewContext.d.ts +23 -0
- package/dist/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.d.ts +26 -0
- package/dist/components/DetailViewBinding.d.ts +5 -0
- package/dist/components/EditFormActions.d.ts +12 -1
- package/dist/components/EditViewBinding.d.ts +6 -1
- package/dist/components/EntityIdentityBar.d.ts +53 -0
- package/dist/components/EntityInspector.d.ts +29 -0
- package/dist/components/EntityPreviewNesting.d.ts +25 -0
- package/dist/components/EntityViewBinding.d.ts +9 -0
- package/dist/components/InlineEntityPreview.d.ts +47 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/editor.js +18 -10
- package/dist/editor.js.map +1 -1
- package/dist/{export-BzIpUdSK.js → export-B8DIn-u8.js} +2 -2
- package/dist/{export-BzIpUdSK.js.map → export-B8DIn-u8.js.map} +1 -1
- package/dist/form/EntityForm.d.ts +1 -1
- package/dist/form/EntityFormActions.d.ts +11 -1
- package/dist/form/PropertyFieldBinding.d.ts +1 -1
- package/dist/form/components/FieldBlock.d.ts +30 -0
- package/dist/form/components/FormRail.d.ts +18 -0
- package/dist/form/components/FormSections.d.ts +16 -0
- package/dist/form/components/index.d.ts +3 -2
- package/dist/form/components/useRailVisible.d.ts +24 -0
- package/dist/form/field_bindings/BinaryFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/DateTimeFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/GeopointFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MultiSelectFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/SelectFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/SwitchFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/TextFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/VectorFieldBinding.d.ts +1 -1
- package/dist/{history-g9688IaK.js → history-ClwDEtqN.js} +32 -31
- package/dist/history-ClwDEtqN.js.map +1 -0
- package/dist/hooks/navigation/utils.d.ts +16 -0
- package/dist/hooks/useBuildSidePanel.d.ts +14 -0
- package/dist/hooks/useEntityDisplayTitle.d.ts +18 -0
- package/dist/hooks/useRecordActions.d.ts +17 -0
- package/dist/{import-BjUG-cIY.js → import-Cwzf4RW5.js} +2 -2
- package/dist/{import-BjUG-cIY.js.map → import-Cwzf4RW5.js.map} +1 -1
- package/dist/index.js +106 -35
- package/dist/index.js.map +1 -1
- package/dist/markdown-CklalUUk.js.map +1 -1
- package/dist/preview/components/InlineEntityListPreview.d.ts +15 -0
- package/dist/preview/index.d.ts +1 -0
- package/dist/preview/property_previews/ArrayOfReferencesPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayOfRelationsPreview.d.ts +1 -1
- package/dist/types/components/EntityFormActionsProps.d.ts +0 -1
- package/dist/types/components/EntityFormProps.d.ts +0 -5
- package/dist/types/fields.d.ts +24 -0
- package/dist/{util-B5fJm1FA.js → util-DSh3HRzK.js} +1974 -1130
- package/dist/util-DSh3HRzK.js.map +1 -0
- package/package.json +32 -34
- package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
- package/src/collection_editor/serializable_types.ts +4 -3
- package/src/collection_editor/serializable_utils.ts +51 -17
- package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +65 -22
- package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +38 -30
- package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +3 -1
- package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/RelationPropertyField.tsx +16 -0
- package/src/collection_editor/useLocalCollectionsConfigController.tsx +127 -15
- package/src/collection_editor/validateCollectionJson.ts +3 -3
- package/src/components/AdminModeSyncer.tsx +1 -1
- package/src/components/CollectionTableBinding/PropertyTableCell.tsx +10 -2
- package/src/components/CollectionTableBinding/column_utils.tsx +26 -2
- package/src/components/CollectionTableBinding/table_bindings.tsx +2 -1
- package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +23 -16
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +7 -1
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +8 -2
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +22 -11
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +33 -0
- package/src/components/CollectionViewBinding/SplitListView.tsx +18 -3
- package/src/components/CollectionViewBinding/SplitViewContext.tsx +34 -0
- package/src/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.ts +48 -9
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +80 -25
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +18 -6
- package/src/components/DefaultAppBar.tsx +1 -1
- package/src/components/DefaultDrawer.tsx +1 -1
- package/src/components/DetailViewBinding.tsx +100 -143
- package/src/components/DrawerNavigationItem.tsx +1 -1
- package/src/components/EditFormActions.tsx +54 -129
- package/src/components/EditViewBinding.tsx +182 -93
- package/src/components/EntityIdentityBar.tsx +288 -0
- package/src/components/EntityInspector.tsx +132 -0
- package/src/components/EntityPreviewBinding.tsx +6 -2
- package/src/components/EntityPreviewNesting.tsx +50 -0
- package/src/components/EntityViewBinding.tsx +35 -32
- package/src/components/HomePage/FavouritesView.tsx +1 -1
- package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
- package/src/components/HomePage/SmallNavigationCard.tsx +1 -1
- package/src/components/InlineEntityPreview.tsx +149 -0
- package/src/components/RebaseLayout.tsx +1 -1
- package/src/components/RebaseNavigation.tsx +45 -5
- package/src/components/RebaseRouteDefs.tsx +1 -1
- package/src/components/SelectableTable/SelectableTable.tsx +2 -2
- package/src/components/SidePanelBinding.tsx +19 -13
- package/src/components/UserSelector.tsx +12 -6
- package/src/components/common/default_entity_actions.tsx +4 -3
- package/src/components/history/EntityHistoryEntry.tsx +28 -7
- package/src/components/history/EntityHistoryView.tsx +20 -20
- package/src/components/history/LastEditedByIndicator.tsx +4 -3
- package/src/components/index.ts +2 -0
- package/src/data_import/utils/get_import_inference_type.ts +6 -0
- package/src/form/EntityForm.tsx +267 -179
- package/src/form/EntityFormActions.tsx +50 -99
- package/src/form/EntityFormBinding.tsx +4 -2
- package/src/form/PropertyFieldBinding.tsx +4 -0
- package/src/form/components/FieldBlock.tsx +119 -0
- package/src/form/components/FormRail.tsx +125 -0
- package/src/form/components/FormSections.tsx +118 -0
- package/src/form/components/index.tsx +3 -2
- package/src/form/components/useRailVisible.tsx +46 -0
- package/src/form/field_bindings/BinaryFieldBinding.tsx +3 -2
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +3 -2
- package/src/form/field_bindings/GeopointFieldBinding.tsx +3 -2
- package/src/form/field_bindings/MapFieldBinding.tsx +1 -2
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +3 -2
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -1
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +2 -1
- package/src/form/field_bindings/RelationFieldBinding.tsx +6 -4
- package/src/form/field_bindings/RepeatFieldBinding.tsx +4 -1
- package/src/form/field_bindings/SelectFieldBinding.tsx +3 -2
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +31 -8
- package/src/form/field_bindings/SwitchFieldBinding.tsx +7 -3
- package/src/form/field_bindings/TextFieldBinding.tsx +18 -16
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +3 -2
- package/src/form/field_bindings/VectorFieldBinding.tsx +2 -1
- package/src/hooks/navigation/useBuildUrlController.tsx +1 -1
- package/src/hooks/navigation/useTopLevelNavigation.ts +18 -7
- package/src/hooks/navigation/utils.ts +20 -0
- package/src/hooks/useBuildSideDialogsController.tsx +2 -2
- package/src/hooks/useBuildSidePanel.tsx +22 -61
- package/src/hooks/useEntityDisplayTitle.tsx +60 -0
- package/src/hooks/useHistory.ts +6 -5
- package/src/hooks/useRecordActions.tsx +69 -0
- package/src/hooks/useResolvedUsers.ts +10 -5
- package/src/preview/PropertyPreview.tsx +48 -22
- package/src/preview/components/InlineEntityListPreview.tsx +42 -0
- package/src/preview/components/ReferencePreview.tsx +34 -14
- package/src/preview/components/RelationPreview.tsx +34 -14
- package/src/preview/index.ts +1 -0
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +24 -1
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +25 -1
- package/src/routes/RebaseRoute.tsx +2 -2
- package/src/types/components/EntityFormActionsProps.tsx +0 -1
- package/src/types/components/EntityFormProps.tsx +0 -6
- package/src/types/fields.tsx +27 -0
- package/dist/form/components/FormEntry.d.ts +0 -6
- package/dist/form/components/FormLayout.d.ts +0 -5
- package/dist/history-g9688IaK.js.map +0 -1
- package/dist/util-B5fJm1FA.js.map +0 -1
- package/src/form/components/FormEntry.tsx +0 -22
- package/src/form/components/FormLayout.tsx +0 -16
|
@@ -24,11 +24,21 @@ import { getIcon } from "@rebasepro/app";
|
|
|
24
24
|
import { useAdminContext } from "../hooks/useAdminContext";
|
|
25
25
|
import { useChildViewSource } from "../hooks/useChildViewSource";
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* The entity form's footer.
|
|
29
|
+
*
|
|
30
|
+
* It used to render in two shapes: this footer, and a `w-80 2xl:w-96` sticky
|
|
31
|
+
* side rail carrying the same Save and Discard above several hundred pixels of
|
|
32
|
+
* nothing. The rail is gone, and Save/Discard have moved to the identity bar
|
|
33
|
+
* where they stay visible while the form scrolls — so with
|
|
34
|
+
* `showDefaultActions={false}` this renders only what the bar cannot host:
|
|
35
|
+
* record actions (copy, delete, custom), plugin actions, the saving error, and
|
|
36
|
+
* the side-panel-only "save and close".
|
|
37
|
+
*/
|
|
27
38
|
export function EditFormActions({
|
|
28
39
|
collection,
|
|
29
40
|
path,
|
|
30
41
|
entity,
|
|
31
|
-
layout,
|
|
32
42
|
savingError,
|
|
33
43
|
formex,
|
|
34
44
|
disabled,
|
|
@@ -69,10 +79,11 @@ export function EditFormActions({
|
|
|
69
79
|
return actions;
|
|
70
80
|
}, [canCreate, canDelete, collection, path, customizationController.entityActions?.length, entity, collection.disableDefaultActions, isLinkedChildView]);
|
|
71
81
|
|
|
72
|
-
|
|
82
|
+
// Record actions stay in the footer whether or not the bar carries Save;
|
|
83
|
+
// they are things you do *to* the record, not ways to leave the form.
|
|
84
|
+
const formActions = entityActions.filter(a => a.includeInForm === undefined || a.includeInForm);
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
const bottomActions = buildBottomActions({
|
|
86
|
+
return buildBottomActions({
|
|
76
87
|
savingError,
|
|
77
88
|
entity,
|
|
78
89
|
formActions,
|
|
@@ -88,36 +99,8 @@ export function EditFormActions({
|
|
|
88
99
|
formContext,
|
|
89
100
|
formex,
|
|
90
101
|
t,
|
|
91
|
-
|
|
102
|
+
showDefaultActions
|
|
92
103
|
});
|
|
93
|
-
|
|
94
|
-
const sideActions = buildSideActions({
|
|
95
|
-
savingError,
|
|
96
|
-
entity,
|
|
97
|
-
formActions,
|
|
98
|
-
collection,
|
|
99
|
-
context,
|
|
100
|
-
sidePanelController,
|
|
101
|
-
sideDialogContext,
|
|
102
|
-
disabled,
|
|
103
|
-
status,
|
|
104
|
-
pluginActions,
|
|
105
|
-
openEntityMode,
|
|
106
|
-
navigateBack,
|
|
107
|
-
formContext,
|
|
108
|
-
formex,
|
|
109
|
-
t,
|
|
110
|
-
className: layout === "responsive" ? "hidden @6xl:flex" : undefined
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
if (layout === "responsive") {
|
|
114
|
-
return <>
|
|
115
|
-
{bottomActions}
|
|
116
|
-
{sideActions}
|
|
117
|
-
</>;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return layout === "bottom" ? bottomActions : sideActions;
|
|
121
104
|
}
|
|
122
105
|
|
|
123
106
|
type ActionsViewProps<M extends Record<string, unknown>> = {
|
|
@@ -136,6 +119,7 @@ type ActionsViewProps<M extends Record<string, unknown>> = {
|
|
|
136
119
|
formContext: FormContext,
|
|
137
120
|
formex: FormexController<Record<string, unknown>>;
|
|
138
121
|
t: (key: string, vars?: Record<string, string>) => string;
|
|
122
|
+
showDefaultActions: boolean;
|
|
139
123
|
className?: string;
|
|
140
124
|
};
|
|
141
125
|
|
|
@@ -155,14 +139,28 @@ function buildBottomActions<M extends Record<string, unknown>>({
|
|
|
155
139
|
formContext,
|
|
156
140
|
formex,
|
|
157
141
|
t,
|
|
142
|
+
showDefaultActions,
|
|
158
143
|
className
|
|
159
144
|
}: ActionsViewProps<M>) {
|
|
160
145
|
|
|
161
146
|
const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
147
|
+
// "Save and close" only means something where there is something to close.
|
|
162
148
|
const canClose = openEntityMode === "side_panel" || openEntityMode === "dialog";
|
|
149
|
+
const showBackToDetail = collection.defaultEntityAction === "view" && status === "existing";
|
|
150
|
+
|
|
151
|
+
// When the container carries the actions — the entity view's identity bar
|
|
152
|
+
// does — this renders nothing at all. A full-width bar holding a copy icon
|
|
153
|
+
// and a delete icon is the same waste as the side rail it replaced: the
|
|
154
|
+
// record actions moved into the bar's overflow menu, and Save, Discard and
|
|
155
|
+
// "save and close" are buttons up there too.
|
|
156
|
+
const hasContent = showDefaultActions || Boolean(savingError);
|
|
157
|
+
|
|
158
|
+
if (!hasContent) return null;
|
|
159
|
+
|
|
163
160
|
return <DialogActions
|
|
164
161
|
className={className}
|
|
165
|
-
|
|
162
|
+
// In normal flow at the foot of the form column, not overlaying it.
|
|
163
|
+
position={"sticky"}>
|
|
166
164
|
{savingError &&
|
|
167
165
|
<div className="text-right">
|
|
168
166
|
<Typography color={"error"}>{savingError.message}</Typography>
|
|
@@ -202,13 +200,8 @@ function buildBottomActions<M extends Record<string, unknown>>({
|
|
|
202
200
|
<ErrorTooltip title={"This form has errors"}>
|
|
203
201
|
<AlertCircleIcon className="ml-4 text-red-500" size={iconSize.smallest}/>
|
|
204
202
|
</ErrorTooltip> : null}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
disabled={disabled || formex.isSubmitting}
|
|
208
|
-
type="reset">
|
|
209
|
-
{status === "existing" ? t("discard") : t("clear")}
|
|
210
|
-
</Button>
|
|
211
|
-
{collection.defaultEntityAction === "view" && status === "existing" && (
|
|
203
|
+
|
|
204
|
+
{showBackToDetail && (
|
|
212
205
|
<Button variant="text"
|
|
213
206
|
color="primary"
|
|
214
207
|
disabled={disabled}
|
|
@@ -216,19 +209,29 @@ function buildBottomActions<M extends Record<string, unknown>>({
|
|
|
216
209
|
{t("back_to_detail") ?? "Back to details"}
|
|
217
210
|
</Button>
|
|
218
211
|
)}
|
|
219
|
-
|
|
220
|
-
|
|
212
|
+
|
|
213
|
+
{showDefaultActions && <>
|
|
214
|
+
<Button variant="text"
|
|
221
215
|
color="primary"
|
|
222
|
-
type="submit"
|
|
223
216
|
disabled={disabled || formex.isSubmitting}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}}>
|
|
227
|
-
{status === "existing" && t("save")}
|
|
228
|
-
{status === "copy" && t("create_copy")}
|
|
229
|
-
{status === "new" && t("create")}
|
|
217
|
+
type="reset">
|
|
218
|
+
{status === "existing" ? t("discard") : t("clear")}
|
|
230
219
|
</Button>
|
|
231
|
-
|
|
220
|
+
<Tooltip title={hasErrors ? (t("fix_errors_before_saving") ?? "Fix highlighted errors before saving") : undefined}>
|
|
221
|
+
<Button variant={canClose ? "text" : "filled"}
|
|
222
|
+
color="primary"
|
|
223
|
+
type="submit"
|
|
224
|
+
disabled={disabled || formex.isSubmitting}
|
|
225
|
+
onClick={() => {
|
|
226
|
+
sideDialogContext.setPendingClose(false);
|
|
227
|
+
}}>
|
|
228
|
+
{status === "existing" && t("save")}
|
|
229
|
+
{status === "copy" && t("create_copy")}
|
|
230
|
+
{status === "new" && t("create")}
|
|
231
|
+
</Button>
|
|
232
|
+
</Tooltip>
|
|
233
|
+
</>}
|
|
234
|
+
|
|
232
235
|
{canClose && <LoadingButton variant="filled"
|
|
233
236
|
color="primary"
|
|
234
237
|
type="submit"
|
|
@@ -244,84 +247,6 @@ function buildBottomActions<M extends Record<string, unknown>>({
|
|
|
244
247
|
</DialogActions>;
|
|
245
248
|
}
|
|
246
249
|
|
|
247
|
-
function buildSideActions<M extends Record<string, unknown>>({
|
|
248
|
-
savingError,
|
|
249
|
-
entity,
|
|
250
|
-
formActions,
|
|
251
|
-
collection,
|
|
252
|
-
context,
|
|
253
|
-
sidePanelController,
|
|
254
|
-
disabled,
|
|
255
|
-
status,
|
|
256
|
-
sideDialogContext,
|
|
257
|
-
pluginActions,
|
|
258
|
-
openEntityMode,
|
|
259
|
-
navigateBack,
|
|
260
|
-
formContext,
|
|
261
|
-
formex,
|
|
262
|
-
t,
|
|
263
|
-
className
|
|
264
|
-
}: ActionsViewProps<M>) {
|
|
265
|
-
|
|
266
|
-
const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
267
|
-
return <div
|
|
268
|
-
className={cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin, className)}>
|
|
269
|
-
<Tooltip title={hasErrors ? (t("fix_errors_before_saving") ?? "Fix highlighted errors before saving") : undefined}>
|
|
270
|
-
<LoadingButton fullWidth={true}
|
|
271
|
-
variant="filled"
|
|
272
|
-
color="primary"
|
|
273
|
-
type="submit"
|
|
274
|
-
loading={formex.isSubmitting}
|
|
275
|
-
startIcon={hasErrors ? <AlertCircleIcon/> : undefined}
|
|
276
|
-
disabled={disabled || formex.isSubmitting}
|
|
277
|
-
onClick={() => {
|
|
278
|
-
sideDialogContext.setPendingClose?.(false);
|
|
279
|
-
}}>
|
|
280
|
-
{status === "existing" && t("save")}
|
|
281
|
-
{status === "copy" && t("create_copy")}
|
|
282
|
-
{status === "new" && t("create")}
|
|
283
|
-
</LoadingButton>
|
|
284
|
-
</Tooltip>
|
|
285
|
-
|
|
286
|
-
<Button fullWidth={true} variant="text" disabled={disabled || formex.isSubmitting} type="reset">
|
|
287
|
-
{status === "existing" ? t("discard") : t("clear")}
|
|
288
|
-
</Button>
|
|
289
|
-
{collection.defaultEntityAction === "view" && status === "existing" && (
|
|
290
|
-
<Button fullWidth={true} variant="text" disabled={disabled} onClick={navigateBack}>
|
|
291
|
-
{t("back_to_detail") ?? "Back to details"}
|
|
292
|
-
</Button>
|
|
293
|
-
)}
|
|
294
|
-
|
|
295
|
-
{pluginActions}
|
|
296
|
-
|
|
297
|
-
{formActions.length > 0 && <div className="flex flex-row flex-wrap mt-2">
|
|
298
|
-
{formActions.map((action, index) => {
|
|
299
|
-
const props = {
|
|
300
|
-
view: "form",
|
|
301
|
-
entity,
|
|
302
|
-
path: getCollectionDataPath(collection),
|
|
303
|
-
collection: collection,
|
|
304
|
-
context,
|
|
305
|
-
sidePanelController,
|
|
306
|
-
openEntityMode,
|
|
307
|
-
navigateBack,
|
|
308
|
-
formContext
|
|
309
|
-
} satisfies EntityActionClickProps<Record<string, unknown>>;
|
|
310
|
-
const isEnabled = !action.isEnabled || action.isEnabled(props);
|
|
311
|
-
return (
|
|
312
|
-
<EntityActionButton key={action.key ?? action.name ?? index} action={action} enabled={isEnabled} props={props}/>
|
|
313
|
-
);
|
|
314
|
-
})}
|
|
315
|
-
</div>}
|
|
316
|
-
|
|
317
|
-
{savingError &&
|
|
318
|
-
<div className="text-right">
|
|
319
|
-
<Typography color={"error"}>{savingError.message}</Typography>
|
|
320
|
-
</div>
|
|
321
|
-
}
|
|
322
|
-
</div>;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
250
|
function EntityActionButton({
|
|
326
251
|
action,
|
|
327
252
|
enabled,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
HistoryIcon,
|
|
19
19
|
IconButton,
|
|
20
20
|
Maximize2Icon,
|
|
21
|
+
MenuItem,
|
|
21
22
|
Skeleton,
|
|
22
23
|
Tab,
|
|
23
24
|
Tabs,
|
|
@@ -42,13 +43,18 @@ import { EntityFormBinding } from "../form";
|
|
|
42
43
|
import type { EntityFormBindingProps } from "../form";
|
|
43
44
|
import type { OnUpdateParams } from "../types/components/EntityFormProps";
|
|
44
45
|
import { EditFormActions } from "./EditFormActions";
|
|
45
|
-
import {
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
import { EntityIdentityBar } from "./EntityIdentityBar";
|
|
47
|
+
import { SplitListCloseButton } from "./CollectionViewBinding/SplitListCloseButton";
|
|
48
|
+
import { useRecordActions } from "../hooks/useRecordActions";
|
|
49
|
+
import { useSideDialogContext } from "./SideDialogs";
|
|
50
|
+
import { useAdminContext } from "../hooks/useAdminContext";
|
|
51
|
+
import { EntityInspector, InspectorTab } from "./EntityInspector";
|
|
52
|
+
import { useEntityDisplayTitle } from "../hooks/useEntityDisplayTitle";
|
|
48
53
|
import { createFormexStub, getEntityFromCache } from "@rebasepro/app";
|
|
49
54
|
import { usePermissions } from "@rebasepro/app";
|
|
50
55
|
import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
|
|
51
|
-
import {
|
|
56
|
+
import { withViewMode } from "../util/view_mode";
|
|
57
|
+
import { useNavigate } from "react-router";
|
|
52
58
|
|
|
53
59
|
import { MAIN_TAB_VALUE, JSON_TAB_VALUE, HISTORY_TAB_VALUE } from "../util/view_constants";
|
|
54
60
|
export { MAIN_TAB_VALUE, JSON_TAB_VALUE, HISTORY_TAB_VALUE };
|
|
@@ -85,6 +91,11 @@ export interface EditViewBindingProps<M extends Record<string, unknown> = Record
|
|
|
85
91
|
navigateBack?: () => void;
|
|
86
92
|
layout?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
87
93
|
barActions?: (params: BarActionsParams) => React.ReactNode;
|
|
94
|
+
/**
|
|
95
|
+
* Controls the owning panel puts at the identity bar's leading edge, ahead
|
|
96
|
+
* of the breadcrumb — the side panel's close button.
|
|
97
|
+
*/
|
|
98
|
+
barActionsStart?: React.ReactNode;
|
|
88
99
|
formProps?: Partial<EntityFormBindingProps<M>>,
|
|
89
100
|
/**
|
|
90
101
|
* Pre-populate the form with these values when creating a new entity.
|
|
@@ -180,6 +191,7 @@ export function EditViewBindingInner<M extends Record<string, unknown>>({
|
|
|
180
191
|
dataLoading,
|
|
181
192
|
layout = "side_panel",
|
|
182
193
|
barActions,
|
|
194
|
+
barActionsStart,
|
|
183
195
|
status,
|
|
184
196
|
setStatus,
|
|
185
197
|
formProps,
|
|
@@ -198,6 +210,7 @@ export function EditViewBindingInner<M extends Record<string, unknown>>({
|
|
|
198
210
|
const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
|
|
199
211
|
|
|
200
212
|
const context = useRebaseContext();
|
|
213
|
+
const adminContext = useAdminContext();
|
|
201
214
|
const urlController = useUrlController();
|
|
202
215
|
const navigate = useNavigate();
|
|
203
216
|
|
|
@@ -265,22 +278,30 @@ parentEntityIds,
|
|
|
265
278
|
const customViewsCount = customViews?.length ?? 0;
|
|
266
279
|
const includeJsonView = collection.includeJsonView === undefined ? true : collection.includeJsonView;
|
|
267
280
|
const includeHistoryView = Boolean(collection.history);
|
|
268
|
-
|
|
281
|
+
// The inspector holds the developer tools; the tab strip holds destinations.
|
|
282
|
+
// A strip with only the record in it has nothing to choose between, so it
|
|
283
|
+
// does not render at all.
|
|
284
|
+
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
|
|
269
285
|
|
|
270
286
|
const {
|
|
271
287
|
resolvedEntityViews
|
|
272
288
|
} = resolvedSelectedEntityView(customViews, customizationController, undefined, canEdit);
|
|
273
289
|
|
|
290
|
+
// JSON and history are no longer tab values, but a bookmarked or in-flight
|
|
291
|
+
// URL may still name one. They resolve to the record, and open the
|
|
292
|
+
// inspector on that pane, so an old link keeps working.
|
|
293
|
+
const legacyInspectorTab = selectedTab === JSON_TAB_VALUE
|
|
294
|
+
? "json"
|
|
295
|
+
: selectedTab === HISTORY_TAB_VALUE ? "history" : undefined;
|
|
296
|
+
|
|
274
297
|
const validTabValues = useMemo(() => {
|
|
275
298
|
const set = new Set<string>([
|
|
276
299
|
MAIN_TAB_VALUE,
|
|
277
|
-
...(includeJsonView ? [JSON_TAB_VALUE] : []),
|
|
278
|
-
...(includeHistoryView ? [HISTORY_TAB_VALUE] : []),
|
|
279
300
|
...resolvedEntityViews.map(v => v.key),
|
|
280
301
|
...subcollections.map(s => s.slug)
|
|
281
302
|
]);
|
|
282
303
|
return set;
|
|
283
|
-
}, [
|
|
304
|
+
}, [resolvedEntityViews, subcollections]);
|
|
284
305
|
|
|
285
306
|
const activeTab = validTabValues.has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
|
|
286
307
|
|
|
@@ -289,7 +310,6 @@ parentEntityIds,
|
|
|
289
310
|
selectedSecondaryForm
|
|
290
311
|
} = resolvedSelectedEntityView(customViews, customizationController, activeTab, canEdit);
|
|
291
312
|
|
|
292
|
-
const actionsAtTheBottom = layout === "side_panel" || layout === "dialog" || selectedEntityView?.includeActions === "bottom";
|
|
293
313
|
|
|
294
314
|
const mainViewVisible = activeTab === MAIN_TAB_VALUE || Boolean(selectedSecondaryForm);
|
|
295
315
|
|
|
@@ -381,35 +401,72 @@ parentEntityIds,
|
|
|
381
401
|
|
|
382
402
|
const globalLoading = (dataLoading && !usedEntity) || (canEdit === undefined && (status === "existing" || status === "copy"));
|
|
383
403
|
|
|
384
|
-
|
|
385
|
-
const jsonTabMounted = mountedTabsRef.current.has(JSON_TAB_VALUE);
|
|
386
|
-
const jsonView = (activeTab === JSON_TAB_VALUE || jsonTabMounted) ? <div
|
|
387
|
-
className={cls("relative flex-1 h-full overflow-auto w-full",
|
|
388
|
-
{ "hidden": activeTab !== JSON_TAB_VALUE })}
|
|
389
|
-
key={"json_view"}
|
|
390
|
-
role="tabpanel">
|
|
391
|
-
<ErrorBoundary>
|
|
392
|
-
<JsonPreviewBinding
|
|
393
|
-
values={formContext?.values ?? entity?.values ?? {}} />
|
|
394
|
-
</ErrorBoundary>
|
|
395
|
-
</div> : null;
|
|
404
|
+
/* ---- identity bar ---------------------------------------------------- */
|
|
396
405
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
406
|
+
const displayTitle = useEntityDisplayTitle({
|
|
407
|
+
collection,
|
|
408
|
+
values: (formContext?.values ?? usedEntity?.values) as Record<string, unknown> | undefined,
|
|
409
|
+
entityId,
|
|
410
|
+
status
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const formex = formContext?.formex;
|
|
414
|
+
const hasFormErrors = Boolean(formex && Object.keys(formex.errors).length > 0 && formex.submitCount > 0);
|
|
415
|
+
// Same rule the footer used: an untouched existing record has nothing to save.
|
|
416
|
+
const saveDisabled = Boolean(
|
|
417
|
+
!formContext
|
|
418
|
+
|| formContext.disabled
|
|
419
|
+
|| formex?.isSubmitting
|
|
420
|
+
|| (status === "existing" && !formex?.dirty)
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
const [inspectorTab, setInspectorTab] = useState<InspectorTab | null>(null);
|
|
424
|
+
|
|
425
|
+
/* ---- record actions, in the bar's overflow menu ---------------------- */
|
|
426
|
+
|
|
427
|
+
const sideDialogContext = useSideDialogContext();
|
|
428
|
+
const canCloseAfterSave = layout === "side_panel" || layout === "dialog";
|
|
429
|
+
|
|
430
|
+
const recordActions = useRecordActions({
|
|
431
|
+
collection,
|
|
432
|
+
path,
|
|
433
|
+
entity: usedEntity
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const recordActionItems = usedEntity && recordActions.length
|
|
437
|
+
? recordActions.map((action, index) => {
|
|
438
|
+
const clickProps = {
|
|
439
|
+
view: "form" as const,
|
|
440
|
+
entity: usedEntity,
|
|
441
|
+
path,
|
|
442
|
+
collection,
|
|
443
|
+
context,
|
|
444
|
+
sidePanelController: adminContext.sidePanelController,
|
|
445
|
+
openEntityMode: layout,
|
|
446
|
+
navigateBack: navigateBack ?? (() => undefined),
|
|
447
|
+
formContext
|
|
448
|
+
};
|
|
449
|
+
const enabled = !action.isEnabled || action.isEnabled(clickProps as never);
|
|
450
|
+
return (
|
|
451
|
+
<MenuItem key={action.key ?? action.name ?? index}
|
|
452
|
+
disabled={!enabled}
|
|
453
|
+
onClick={() => action.onClick(clickProps as never)}>
|
|
454
|
+
{getIcon(action.icon, undefined, undefined, "smallest")}
|
|
455
|
+
{action.name}
|
|
456
|
+
</MenuItem>
|
|
457
|
+
);
|
|
458
|
+
})
|
|
459
|
+
: null;
|
|
460
|
+
|
|
461
|
+
// Plugin form actions (Autofill and friends) render inline in the bar; the
|
|
462
|
+
// form no longer has a footer to put them in.
|
|
463
|
+
const formPluginActions = useSlot("form.actions", formActionTopProps);
|
|
464
|
+
|
|
465
|
+
// A URL still naming the old `json`/`history` tab opens the inspector on
|
|
466
|
+
// that pane instead of 404-ing into the record with nothing shown.
|
|
467
|
+
useEffect(() => {
|
|
468
|
+
if (legacyInspectorTab) setInspectorTab(legacyInspectorTab);
|
|
469
|
+
}, [legacyInspectorTab]);
|
|
413
470
|
|
|
414
471
|
const subCollectionsViews = childViews && childViews.map(({ collection: subcollection }) => {
|
|
415
472
|
const subcollectionId = subcollection.slug;
|
|
@@ -520,7 +577,10 @@ parentEntityIds,
|
|
|
520
577
|
entity={entity}
|
|
521
578
|
initialDirtyValues={initialDirtyValues}
|
|
522
579
|
openEntityMode={layout}
|
|
523
|
-
|
|
580
|
+
// Save and Discard live in the identity bar now, where they stay
|
|
581
|
+
// visible while the form scrolls. The form's own footer would be a
|
|
582
|
+
// second copy of them.
|
|
583
|
+
showDefaultActions={false}
|
|
524
584
|
initialStatus={status}
|
|
525
585
|
className={cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className)}
|
|
526
586
|
EntityFormActionsComponent={ResolvedFormActions as React.FC<typeof ResolvedFormActions extends React.ComponentType<infer P> ? P : never>}
|
|
@@ -601,9 +661,9 @@ parentEntityIds,
|
|
|
601
661
|
);
|
|
602
662
|
});
|
|
603
663
|
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
664
|
+
const fullScreenButton = !barActions && // Not in split: the list panel beside it carries the close control,
|
|
665
|
+
// and an expand button there competes with it.
|
|
666
|
+
(layout === "side_panel" || layout === "dialog") && entityId ? (
|
|
607
667
|
<Tooltip title={"Open full screen"}>
|
|
608
668
|
<IconButton
|
|
609
669
|
size="small"
|
|
@@ -620,59 +680,82 @@ parentEntityIds,
|
|
|
620
680
|
|
|
621
681
|
let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-800">
|
|
622
682
|
|
|
623
|
-
|
|
624
|
-
|
|
683
|
+
<EntityIdentityBar
|
|
684
|
+
collection={collection as AdminCollection}
|
|
685
|
+
title={displayTitle}
|
|
686
|
+
entityId={status === "existing" ? entityId : undefined}
|
|
687
|
+
status={status}
|
|
688
|
+
dirty={Boolean(formContext?.formex?.dirty)}
|
|
689
|
+
saving={Boolean(formContext?.isSaving)}
|
|
690
|
+
hasErrors={hasFormErrors}
|
|
691
|
+
saveDisabled={!canEdit || saveDisabled}
|
|
692
|
+
// Full screen replaces the collection entirely, leaving browser Back
|
|
693
|
+
// as the only route to it. The detail view has carried this arrow
|
|
694
|
+
// for that reason; the edit view is reached the same way.
|
|
695
|
+
onBack={layout === "full_screen"
|
|
696
|
+
? () => navigate(withViewMode(urlController.buildUrlCollectionPath(path)))
|
|
697
|
+
: undefined}
|
|
698
|
+
onSave={canEdit && formContext ? () => {
|
|
699
|
+
sideDialogContext.setPendingClose?.(false);
|
|
700
|
+
formContext.submit();
|
|
701
|
+
} : undefined}
|
|
702
|
+
onSaveAndClose={canEdit && formContext && canCloseAfterSave ? () => {
|
|
703
|
+
sideDialogContext.setPendingClose?.(true);
|
|
704
|
+
formContext.submit();
|
|
705
|
+
} : undefined}
|
|
706
|
+
onDiscard={canEdit && formContext ? () => formContext.formex.resetForm() : undefined}
|
|
707
|
+
onInspect={includeJsonView ? () => setInspectorTab("json") : undefined}
|
|
708
|
+
onViewHistory={includeHistoryView ? () => setInspectorTab("history") : undefined}
|
|
709
|
+
recordActions={recordActionItems}
|
|
710
|
+
pluginActions={formPluginActions}
|
|
711
|
+
leading={<>
|
|
712
|
+
{barActionsStart}
|
|
713
|
+
{/* Split view: closing the list is opening this record
|
|
714
|
+
full screen, and the control for it belongs at this
|
|
715
|
+
bar's leading edge. */}
|
|
716
|
+
{layout === "split" && <SplitListCloseButton/>}
|
|
717
|
+
</>}
|
|
718
|
+
trailing={<>
|
|
719
|
+
{pluginActionsTop}
|
|
720
|
+
{fullScreenButton}
|
|
721
|
+
{barActions?.({
|
|
722
|
+
path,
|
|
723
|
+
entityId,
|
|
724
|
+
values: formContext?.values ?? usedEntity?.values ?? {},
|
|
725
|
+
status
|
|
726
|
+
})}
|
|
727
|
+
</>}
|
|
728
|
+
/>
|
|
625
729
|
|
|
626
|
-
|
|
730
|
+
{/* Destinations only: the record is the page, so its tab leads rather
|
|
731
|
+
than sitting third behind two unlabelled developer tools. The row
|
|
732
|
+
disappears entirely when there is nowhere else to go. */}
|
|
733
|
+
{hasAdditionalViews && <div className={cls(
|
|
734
|
+
"h-10 shrink-0 flex items-stretch border-b px-2 min-w-0",
|
|
735
|
+
defaultBorderMixin
|
|
736
|
+
)}>
|
|
737
|
+
<Tabs
|
|
738
|
+
className={"!w-full"}
|
|
739
|
+
innerClassName={"h-full"}
|
|
740
|
+
variant={"boxy"}
|
|
741
|
+
value={activeTab}
|
|
742
|
+
onValueChange={(value) => {
|
|
743
|
+
onSideTabClick(value);
|
|
744
|
+
}}>
|
|
627
745
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
{pluginActionsTop}
|
|
636
|
-
|
|
637
|
-
{hasAdditionalViews && <div className={"flex-1 flex justify-end min-w-0 shrink-0"}>
|
|
638
|
-
<Tabs
|
|
639
|
-
className={"!w-fit max-w-full"}
|
|
640
|
-
value={activeTab}
|
|
641
|
-
onValueChange={(value) => {
|
|
642
|
-
onSideTabClick(value);
|
|
643
|
-
}}>
|
|
644
|
-
|
|
645
|
-
{includeJsonView && <Tab
|
|
646
|
-
disabled={!hasAdditionalViews}
|
|
647
|
-
value={JSON_TAB_VALUE}
|
|
648
|
-
className={"text-sm"}>
|
|
649
|
-
<CodeIcon size={iconSize.smallest} />
|
|
650
|
-
</Tab>}
|
|
651
|
-
|
|
652
|
-
{includeHistoryView && <Tab
|
|
653
|
-
disabled={!hasAdditionalViews}
|
|
654
|
-
value={HISTORY_TAB_VALUE}
|
|
655
|
-
className={"text-sm"}>
|
|
656
|
-
<HistoryIcon size={iconSize.smallest} />
|
|
657
|
-
</Tab>}
|
|
658
|
-
|
|
659
|
-
<Tab
|
|
660
|
-
disabled={!hasAdditionalViews}
|
|
661
|
-
value={MAIN_TAB_VALUE}
|
|
662
|
-
className={"text-sm min-w-[90px]"}>
|
|
663
|
-
<span className="flex items-center gap-1.5">
|
|
664
|
-
{getIcon(collection.icon, undefined, undefined, "smallest")}
|
|
665
|
-
{collection.singularName ?? collection.name}
|
|
666
|
-
</span>
|
|
667
|
-
</Tab>
|
|
746
|
+
<Tab value={MAIN_TAB_VALUE}>
|
|
747
|
+
<span className="flex items-center gap-1.5">
|
|
748
|
+
{getIcon(collection.icon, undefined, undefined, "smallest")}
|
|
749
|
+
{collection.singularName ?? collection.name}
|
|
750
|
+
</span>
|
|
751
|
+
</Tab>
|
|
668
752
|
|
|
669
|
-
|
|
753
|
+
{customViewTabsStart}
|
|
670
754
|
|
|
671
|
-
|
|
755
|
+
{customViewTabsEnd}
|
|
672
756
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
</div>}
|
|
757
|
+
{subcollectionTabs}
|
|
758
|
+
</Tabs>
|
|
676
759
|
</div>}
|
|
677
760
|
|
|
678
761
|
{globalLoading
|
|
@@ -682,14 +765,20 @@ parentEntityIds,
|
|
|
682
765
|
{entityView}
|
|
683
766
|
</>}
|
|
684
767
|
|
|
685
|
-
{jsonView}
|
|
686
|
-
|
|
687
|
-
{historyView}
|
|
688
|
-
|
|
689
768
|
{customViewsView}
|
|
690
769
|
|
|
691
770
|
{subCollectionsViews}
|
|
692
771
|
|
|
772
|
+
<EntityInspector
|
|
773
|
+
tab={inspectorTab}
|
|
774
|
+
onTabChange={setInspectorTab}
|
|
775
|
+
onClose={() => setInspectorTab(null)}
|
|
776
|
+
collection={collection as AdminCollection}
|
|
777
|
+
entity={usedEntity as Entity<Record<string, unknown>> | undefined}
|
|
778
|
+
formContext={formContext as FormContext<Record<string, unknown>> | undefined}
|
|
779
|
+
values={formContext?.values ?? usedEntity?.values}
|
|
780
|
+
includeHistory={includeHistoryView}/>
|
|
781
|
+
|
|
693
782
|
</div>;
|
|
694
783
|
|
|
695
784
|
if (plugins && plugins.length > 0) {
|