@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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as React$1 from "react";
|
|
2
2
|
import React, { Suspense, createContext, createElement, forwardRef, lazy, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
3
3
|
import { deepEqual } from "fast-equals";
|
|
4
|
-
import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowLeftIcon, ArrowRightToLineIcon, Badge, BooleanSwitchWithLabel, Button, CHIP_COLORS, CalendarIcon, Card, CardView, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KanbanView, KeyRoundIcon, Label, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListView,
|
|
4
|
+
import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowLeftIcon, ArrowRightToLineIcon, Badge, BooleanSwitchWithLabel, Button, CHIP_COLORS, CHIP_SEED_KEYS, CalendarIcon, Card, CardView, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KanbanView, KeyRoundIcon, Label, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListView, LoadingButton, MailIcon, Markdown, Maximize2Icon, Menu, MenuItem, MinusIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PencilIcon, PlusIcon, Popover, PopoverPrimitive, Portal, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SearchBar, SearchIcon, Select, SelectItem, Separator, SettingsIcon, Sheet, Skeleton, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, Unlink2Icon, UploadIcon, UserIcon, VirtualTable, VirtualTableDateField, VirtualTableInput, VirtualTableInput as VirtualTableInput$1, VirtualTableNumberInput, VirtualTableSwitch, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled, getColorSchemeForSeed, iconSize, lucideIcons, paperMixin, useDebouncedCallback, useInjectStyles, useOutsideAlerter } from "@rebasepro/ui";
|
|
5
5
|
import { CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, canReadCollection, enumToObjectEntries, getArrayResolvedProperties, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityChildViews, getLabelOrConfigFrom, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, isPropertyBuilder, normalizeToEntityRelation, resolveArrayProperties, resolveEnumValues, resolveProperty, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource } from "@rebasepro/common";
|
|
6
|
-
import {
|
|
6
|
+
import { CONTAINER_FULL_WIDTH, CollectionScopeProvider, DEFAULT_PAGE_SIZE, ErrorTooltip, ErrorView, IconForView, PluginProviderStack, SIDE_PANEL_DEFAULT_WIDTH, StorageSourceContext, UnsavedChangesDialog, UserDisplay, addInitialSlash, apiBaseOf, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, flattenKeys, getAdminEntityChildViews, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getFormFieldKeys, getIcon, getLastSegment as getLastSegment$1, getLeadingRelationTitleKey, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getRelationIncludeParams, getRowHeight, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, iconsSearch, isFilterableRelation, isHidden, isReadOnly, looksLikeIdentifierValue, removeEntityFromCache, removeEntityFromMemoryCache, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, removeTrailingSlash as removeTrailingSlash$1, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveComponentRef, resolveDefaultSelectedView, resolveFilterOperators, resolveFormLayout, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAnalyticsController, useApiBase, useApiConfig, useAuthController, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataTableController, useFetch, useLargeLayout, useModeController, useNavigationBlocker, usePermissions, useRebaseClient, useRebaseContext, useRelationSelector, useResolvedComponent, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/app";
|
|
7
7
|
import { Field, Formex, getIn, setIn, useCreateFormex, useFormex } from "@rebasepro/forms";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
import { defaultDateFormat, getHashValue, getValueInPath, hydrateRegExp, isObject, mergeDeep, randomString, toArray } from "@rebasepro/utils";
|
|
@@ -14,7 +14,7 @@ import { useDropzone } from "react-dropzone";
|
|
|
14
14
|
import { EntityReference, EntityRelation, GeoPoint, Vector, getCollectionDataPath, getDataSourceCapabilities, isRelationalCollectionConfig } from "@rebasepro/types";
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
import useMeasure from "react-use-measure";
|
|
17
|
-
import { useLocation, useNavigate, useSearchParams } from "react-router
|
|
17
|
+
import { useLocation, useNavigate, useSearchParams } from "react-router";
|
|
18
18
|
import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
|
|
19
19
|
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
|
|
20
20
|
import { SortableContext, horizontalListSortingStrategy, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
@@ -24,40 +24,139 @@ import { inferTypeFromValue } from "@rebasepro/inference";
|
|
|
24
24
|
import { Highlight, themes } from "prism-react-renderer";
|
|
25
25
|
import { generateKeyBetween, generateNKeysBetween } from "fractional-indexing";
|
|
26
26
|
import { resolveAdminCollection } from "@rebasepro/admin-types";
|
|
27
|
-
//#region src/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
//#region src/util/property_utils.tsx
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a size value (string token or number) to a numeric pixel value
|
|
30
|
+
* suitable for passing to Lucide icons.
|
|
31
|
+
*/
|
|
32
|
+
function resolveSize(size) {
|
|
33
|
+
if (typeof size === "number") return size;
|
|
34
|
+
return iconSize[size];
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
function isReferenceProperty(property) {
|
|
37
|
+
if (!property) return null;
|
|
38
|
+
if (property.type === "reference") return true;
|
|
39
|
+
if (property.type === "array") if (Array.isArray(property.of)) return false;
|
|
40
|
+
else return property.of?.type === "reference";
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
function isRelationProperty(property) {
|
|
44
|
+
if (!property) return null;
|
|
45
|
+
if (property.type === "relation") return true;
|
|
46
|
+
if (property.type === "array") if (Array.isArray(property.of)) return false;
|
|
47
|
+
else return property.of?.type === "relation";
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
function getIconForWidget(widget, size) {
|
|
51
|
+
return /* @__PURE__ */ jsx(widget?.Icon ?? CircleIcon, { size: resolveSize(size) });
|
|
43
52
|
}
|
|
44
|
-
//#endregion
|
|
45
|
-
//#region src/form/components/LabelWithIcon.tsx
|
|
46
53
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
54
|
+
* Returns a default Lucide icon component based on property type.
|
|
55
|
+
* This provides a sensible fallback when no PropertyConfig is available.
|
|
49
56
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
function getDefaultIconForProperty(property) {
|
|
58
|
+
switch (property.type) {
|
|
59
|
+
case "string":
|
|
60
|
+
if (property.storage) return UploadIcon;
|
|
61
|
+
if (property.admin?.urlPreview) return GlobeIcon;
|
|
62
|
+
if (property.email) return MailIcon;
|
|
63
|
+
if (property.admin?.multiline || property.admin?.markdown) return AlignLeftIcon;
|
|
64
|
+
return TextIcon;
|
|
65
|
+
case "number": return HashIcon;
|
|
66
|
+
case "boolean": return FlagIcon;
|
|
67
|
+
case "date": return CalendarIcon;
|
|
68
|
+
case "map": return VoteIcon;
|
|
69
|
+
case "array": {
|
|
70
|
+
const of = property.of;
|
|
71
|
+
if (property.oneOf) return Rows3Icon;
|
|
72
|
+
if (of && !Array.isArray(of)) {
|
|
73
|
+
if (of.type === "reference") return LinkIcon;
|
|
74
|
+
if (of.type === "string" && of.storage) return UploadIcon;
|
|
75
|
+
}
|
|
76
|
+
return RepeatIcon;
|
|
77
|
+
}
|
|
78
|
+
case "reference": return LinkIcon;
|
|
79
|
+
case "relation": return LinkIcon;
|
|
80
|
+
case "vector": return HashIcon;
|
|
81
|
+
default: return CircleIcon;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function getIconForProperty(property, size = "small", fields = {}) {
|
|
85
|
+
const px = resolveSize(size);
|
|
86
|
+
if (isPropertyBuilder(property)) return /* @__PURE__ */ jsx(FunctionSquareIcon, { size: px });
|
|
87
|
+
const configId = property.propertyConfig || void 0;
|
|
88
|
+
const widget = configId ? fields[configId] : void 0;
|
|
89
|
+
if (widget?.Icon) return getIconForWidget(widget, size);
|
|
90
|
+
return /* @__PURE__ */ jsx(getDefaultIconForProperty(property), { size: px });
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get a property in a property tree from a path like
|
|
94
|
+
* `address.street`
|
|
95
|
+
* @param properties
|
|
96
|
+
* @param path
|
|
97
|
+
*/
|
|
98
|
+
function getPropertyInPath(properties, path) {
|
|
99
|
+
if (typeof properties === "object") {
|
|
100
|
+
if (path in properties) return properties[path];
|
|
101
|
+
if (path.includes(".")) {
|
|
102
|
+
const pathSegments = path.split(".");
|
|
103
|
+
const childProperty = properties[pathSegments[0]];
|
|
104
|
+
if (typeof childProperty === "object" && childProperty.type === "map" && childProperty.properties) return getPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function getResolvedPropertyInPath(properties, path) {
|
|
109
|
+
if (typeof properties === "object") {
|
|
110
|
+
if (path in properties) return properties[path];
|
|
111
|
+
if (path.includes(".")) {
|
|
112
|
+
const pathSegments = path.split(".");
|
|
113
|
+
const childProperty = properties[pathSegments[0]];
|
|
114
|
+
if (childProperty.type === "map" && childProperty.properties) return getResolvedPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function getBracketNotation(path) {
|
|
119
|
+
return path.replace(/\.([^.]*)/g, "[$1]");
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get properties sorted by their order, but include ALL properties.
|
|
123
|
+
* Nested keys (like "data.mode") in propertiesOrder are ignored - they're for column ordering.
|
|
124
|
+
* @param properties
|
|
125
|
+
* @param propertiesOrder
|
|
126
|
+
*/
|
|
127
|
+
function getPropertiesWithPropertiesOrder(properties, propertiesOrder) {
|
|
128
|
+
if (!propertiesOrder) return properties;
|
|
129
|
+
const propertyKeys = Object.keys(properties);
|
|
130
|
+
const validOrderKeys = propertiesOrder.filter((key) => !key.includes(".") && properties[key]);
|
|
131
|
+
const result = {};
|
|
132
|
+
validOrderKeys.forEach((key) => {
|
|
133
|
+
const property = properties[key];
|
|
134
|
+
if (typeof property === "object" && property.type === "map" && property.properties) {
|
|
135
|
+
const mapProp = property;
|
|
136
|
+
result[key] = {
|
|
137
|
+
...mapProp,
|
|
138
|
+
properties: getPropertiesWithPropertiesOrder(mapProp.properties, mapProp.propertiesOrder ?? [])
|
|
139
|
+
};
|
|
140
|
+
} else if (property) result[key] = property;
|
|
58
141
|
});
|
|
59
|
-
|
|
60
|
-
|
|
142
|
+
propertyKeys.forEach((key) => {
|
|
143
|
+
if (!result[key]) {
|
|
144
|
+
const property = properties[key];
|
|
145
|
+
if (typeof property === "object" && property.type === "map" && property.properties) {
|
|
146
|
+
const mapProp = property;
|
|
147
|
+
result[key] = {
|
|
148
|
+
...mapProp,
|
|
149
|
+
properties: getPropertiesWithPropertiesOrder(mapProp.properties, mapProp.propertiesOrder ?? [])
|
|
150
|
+
};
|
|
151
|
+
} else if (property) result[key] = property;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
function getDefaultPropertiesOrder(collection) {
|
|
157
|
+
if (collection.propertiesOrder) return collection.propertiesOrder;
|
|
158
|
+
return [...Object.keys(collection.properties), ...(collection.additionalFields ?? []).map((field) => field.key)];
|
|
159
|
+
}
|
|
61
160
|
//#endregion
|
|
62
161
|
//#region src/components/PropertyIdCopyTooltip.tsx
|
|
63
162
|
function PropertyIdCopyTooltip({ propertyKey, className, children }) {
|
|
@@ -98,6 +197,289 @@ function PropertyIdCopyTooltipContent({ propertyKey }) {
|
|
|
98
197
|
});
|
|
99
198
|
}
|
|
100
199
|
//#endregion
|
|
200
|
+
//#region src/form/components/FieldBlock.tsx
|
|
201
|
+
/**
|
|
202
|
+
* Does this property's editor supply its own heading?
|
|
203
|
+
*
|
|
204
|
+
* Arrays, maps, `oneOf` blocks and key-value editors render as a collapsible
|
|
205
|
+
* panel whose header *is* the label — hiding it would leave a panel you cannot
|
|
206
|
+
* identify or, worse, cannot expand. Those keep their own; everything else gets
|
|
207
|
+
* its label from {@link FieldBlock}, which is what makes a text field and a
|
|
208
|
+
* select finally agree on where a label goes.
|
|
209
|
+
*
|
|
210
|
+
* Exported because the layout resolver's span rules and this rule have to stay
|
|
211
|
+
* in step: the self-labelling types are exactly the ones that take a full row.
|
|
212
|
+
*/
|
|
213
|
+
function isSelfLabellingProperty(property) {
|
|
214
|
+
if (!property) return false;
|
|
215
|
+
switch (property.type) {
|
|
216
|
+
case "map": return !property.admin?.spreadChildren;
|
|
217
|
+
case "array": {
|
|
218
|
+
if (property.oneOf) return true;
|
|
219
|
+
if (Array.isArray(property.of) || !property.of) return true;
|
|
220
|
+
const of = property.of;
|
|
221
|
+
if (of.type === "string" && (of.enum || of.storage)) return false;
|
|
222
|
+
if (of.type === "number" && of.enum) return false;
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
default: return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* One field's worth of the form grid: label, control, description.
|
|
230
|
+
*
|
|
231
|
+
* The label carries a small type icon so a number reads differently from a
|
|
232
|
+
* relation at a glance, and the description sits under the control where it
|
|
233
|
+
* annotates what you just filled in.
|
|
234
|
+
*/
|
|
235
|
+
function FieldBlock({ propertyKey, property, showLabel, children }) {
|
|
236
|
+
const description = property?.description?.trim();
|
|
237
|
+
const required = Boolean(property?.validation?.required);
|
|
238
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
239
|
+
id: `form_field_${propertyKey}`,
|
|
240
|
+
className: "relative flex flex-col min-w-0",
|
|
241
|
+
children: [
|
|
242
|
+
showLabel && /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
|
|
243
|
+
propertyKey,
|
|
244
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
245
|
+
className: cls("flex items-center gap-1.5 text-sm font-medium leading-tight mb-1.5", "text-text-secondary dark:text-text-secondary-dark"),
|
|
246
|
+
children: [
|
|
247
|
+
property && /* @__PURE__ */ jsx("span", {
|
|
248
|
+
className: "shrink-0 text-text-disabled dark:text-text-disabled-dark",
|
|
249
|
+
children: getIconForProperty(property, "smallest")
|
|
250
|
+
}),
|
|
251
|
+
/* @__PURE__ */ jsx("span", {
|
|
252
|
+
className: "truncate",
|
|
253
|
+
children: property?.name ?? propertyKey
|
|
254
|
+
}),
|
|
255
|
+
required && /* @__PURE__ */ jsx("span", {
|
|
256
|
+
className: "text-red-500 dark:text-red-500 -ml-1",
|
|
257
|
+
children: "*"
|
|
258
|
+
})
|
|
259
|
+
]
|
|
260
|
+
})
|
|
261
|
+
}),
|
|
262
|
+
/* @__PURE__ */ jsx("div", {
|
|
263
|
+
className: "min-w-0",
|
|
264
|
+
children: /* @__PURE__ */ jsx(ErrorBoundary, { children })
|
|
265
|
+
}),
|
|
266
|
+
showLabel && description && /* @__PURE__ */ jsx(Typography, {
|
|
267
|
+
variant: "caption",
|
|
268
|
+
color: "disabled",
|
|
269
|
+
className: "mt-1.5 ml-0.5 leading-snug",
|
|
270
|
+
children: description
|
|
271
|
+
})
|
|
272
|
+
]
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/form/components/FormRail.tsx
|
|
277
|
+
/**
|
|
278
|
+
* The metadata column beside the form.
|
|
279
|
+
*
|
|
280
|
+
* This replaces a `w-80 2xl:w-96` rail that held a Save button and several
|
|
281
|
+
* hundred pixels of nothing. It renders only when it has something to say, and
|
|
282
|
+
* the actions it used to hold now live in the identity bar, where they stay
|
|
283
|
+
* visible while you scroll.
|
|
284
|
+
*/
|
|
285
|
+
function FormRail({ fields, showRecordMeta, entity, renderField }) {
|
|
286
|
+
if (!fields.length && !showRecordMeta) return null;
|
|
287
|
+
return /* @__PURE__ */ jsxs("aside", {
|
|
288
|
+
className: cls("flex flex-col gap-6 shrink-0 w-72 border-l overflow-y-auto", "px-5 py-6 bg-surface-50 dark:bg-surface-900", defaultBorderMixin),
|
|
289
|
+
children: [fields.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
290
|
+
className: "flex flex-col gap-5",
|
|
291
|
+
children: fields.map(renderField)
|
|
292
|
+
}), showRecordMeta && entity && /* @__PURE__ */ jsx(RecordMeta, { entity })]
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
function RecordMeta({ entity }) {
|
|
296
|
+
const createdAt = pickDate(entity.values, [
|
|
297
|
+
"created_at",
|
|
298
|
+
"createdAt",
|
|
299
|
+
"created_on"
|
|
300
|
+
]);
|
|
301
|
+
const updatedAt = pickDate(entity.values, [
|
|
302
|
+
"updated_at",
|
|
303
|
+
"updatedAt",
|
|
304
|
+
"modified_at"
|
|
305
|
+
]);
|
|
306
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
307
|
+
className: "flex flex-col gap-2.5",
|
|
308
|
+
children: [
|
|
309
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
310
|
+
variant: "caption",
|
|
311
|
+
className: "text-xs font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark",
|
|
312
|
+
children: "Record"
|
|
313
|
+
}),
|
|
314
|
+
/* @__PURE__ */ jsx(MetaRow, {
|
|
315
|
+
label: "ID",
|
|
316
|
+
value: String(entity.id),
|
|
317
|
+
copyable: true
|
|
318
|
+
}),
|
|
319
|
+
createdAt && /* @__PURE__ */ jsx(MetaRow, {
|
|
320
|
+
label: "Created",
|
|
321
|
+
value: createdAt
|
|
322
|
+
}),
|
|
323
|
+
updatedAt && /* @__PURE__ */ jsx(MetaRow, {
|
|
324
|
+
label: "Updated",
|
|
325
|
+
value: updatedAt
|
|
326
|
+
})
|
|
327
|
+
]
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
function MetaRow({ label, value, copyable }) {
|
|
331
|
+
const [copied, setCopied] = React.useState(false);
|
|
332
|
+
const copy = () => {
|
|
333
|
+
navigator.clipboard?.writeText(value).then(() => {
|
|
334
|
+
setCopied(true);
|
|
335
|
+
setTimeout(() => setCopied(false), 1600);
|
|
336
|
+
}, () => void 0);
|
|
337
|
+
};
|
|
338
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
339
|
+
className: "flex items-baseline justify-between gap-3 min-w-0",
|
|
340
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
341
|
+
className: "text-xs text-text-disabled dark:text-text-disabled-dark shrink-0",
|
|
342
|
+
children: label
|
|
343
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
344
|
+
className: "flex items-center gap-1 min-w-0",
|
|
345
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
346
|
+
className: "font-mono text-xs text-text-secondary dark:text-text-secondary-dark truncate",
|
|
347
|
+
title: value,
|
|
348
|
+
children: value
|
|
349
|
+
}), copyable && /* @__PURE__ */ jsx(Tooltip, {
|
|
350
|
+
title: copied ? "Copied" : "Copy",
|
|
351
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
352
|
+
size: "smallest",
|
|
353
|
+
onClick: copy,
|
|
354
|
+
children: copied ? /* @__PURE__ */ jsx(CheckIcon, { size: iconSize.smallest }) : /* @__PURE__ */ jsx(CopyIcon, { size: iconSize.smallest })
|
|
355
|
+
})
|
|
356
|
+
})]
|
|
357
|
+
})]
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Audit timestamps are a convention, not a contract — a collection may name them
|
|
362
|
+
* anything or not have them at all — so this reads the values it recognises and
|
|
363
|
+
* stays quiet otherwise rather than rendering an empty row.
|
|
364
|
+
*/
|
|
365
|
+
function pickDate(values, keys) {
|
|
366
|
+
if (!values) return void 0;
|
|
367
|
+
for (const key of keys) {
|
|
368
|
+
const value = values[key];
|
|
369
|
+
if (value instanceof Date) return value.toLocaleDateString();
|
|
370
|
+
if (typeof value === "string" && value) {
|
|
371
|
+
const parsed = new Date(value);
|
|
372
|
+
if (!Number.isNaN(parsed.getTime())) return parsed.toLocaleDateString();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
//#endregion
|
|
377
|
+
//#region src/form/components/FormSections.tsx
|
|
378
|
+
function FormSections({ sections, collapsed, onToggle, errorKeys, renderField }) {
|
|
379
|
+
return /* @__PURE__ */ jsx("div", {
|
|
380
|
+
className: "flex flex-col gap-8",
|
|
381
|
+
children: sections.map((section) => {
|
|
382
|
+
const hasError = section.fields.some((f) => errorKeys.has(f.key));
|
|
383
|
+
const isCollapsed = section.collapsible && collapsed.has(section.key) && !hasError;
|
|
384
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
385
|
+
className: "min-w-0",
|
|
386
|
+
children: [section.title && /* @__PURE__ */ jsx(Header, {
|
|
387
|
+
title: section.title,
|
|
388
|
+
collapsible: section.collapsible,
|
|
389
|
+
collapsed: isCollapsed,
|
|
390
|
+
controls: `form_section_${section.key}`,
|
|
391
|
+
onToggle: () => onToggle(section.key)
|
|
392
|
+
}), !isCollapsed && /* @__PURE__ */ jsx("div", {
|
|
393
|
+
id: `form_section_${section.key}`,
|
|
394
|
+
className: cls("grid gap-x-4 gap-y-5 min-w-0", "grid-cols-1 @2xl:grid-cols-4"),
|
|
395
|
+
children: section.fields.map(renderField)
|
|
396
|
+
})]
|
|
397
|
+
}, section.key);
|
|
398
|
+
})
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
function Header({ title, collapsible, collapsed, controls, onToggle }) {
|
|
402
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
403
|
+
collapsible && /* @__PURE__ */ jsx(ChevronDownIcon, {
|
|
404
|
+
size: iconSize.smallest,
|
|
405
|
+
className: cls("text-text-disabled dark:text-text-disabled-dark transition-transform duration-150", collapsed && "-rotate-90")
|
|
406
|
+
}),
|
|
407
|
+
/* @__PURE__ */ jsx("span", {
|
|
408
|
+
className: "text-xs font-semibold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark whitespace-nowrap",
|
|
409
|
+
children: title
|
|
410
|
+
}),
|
|
411
|
+
/* @__PURE__ */ jsx("span", { className: cls("flex-1 border-t", defaultBorderMixin) })
|
|
412
|
+
] });
|
|
413
|
+
const className = "flex items-center gap-2.5 w-full mb-3.5";
|
|
414
|
+
if (!collapsible) return /* @__PURE__ */ jsx("div", {
|
|
415
|
+
className,
|
|
416
|
+
children: content
|
|
417
|
+
});
|
|
418
|
+
return /* @__PURE__ */ jsx("button", {
|
|
419
|
+
type: "button",
|
|
420
|
+
onClick: onToggle,
|
|
421
|
+
"aria-expanded": !collapsed,
|
|
422
|
+
"aria-controls": collapsed ? void 0 : controls,
|
|
423
|
+
className: cls(className, "text-left hover:opacity-80 transition-opacity"),
|
|
424
|
+
children: content
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/form/components/useRailVisible.tsx
|
|
429
|
+
/**
|
|
430
|
+
* Below this the form is a single column and the rail is not shown.
|
|
431
|
+
*
|
|
432
|
+
* Roughly the 288px rail plus a content column still wide enough to hold a
|
|
433
|
+
* four-up row. Above it the rail is worth having even in the split view; below
|
|
434
|
+
* it — a narrow split pane, the side panel, the dialog, a phone — its fields
|
|
435
|
+
* fold back into the column.
|
|
436
|
+
*/
|
|
437
|
+
var RAIL_MIN_WIDTH = 1e3;
|
|
438
|
+
/**
|
|
439
|
+
* Is there room beside the form for the metadata rail?
|
|
440
|
+
*
|
|
441
|
+
* Measured on the form element rather than the viewport, because the same form
|
|
442
|
+
* renders in a side panel and a dialog inside a wide window — a media query
|
|
443
|
+
* would say "desktop" and hand a 600px panel a rail it cannot fit.
|
|
444
|
+
*
|
|
445
|
+
* This has to be a real measurement rather than a CSS `hidden` class: the
|
|
446
|
+
* layout resolver *moves* fields into the rail, so hiding it with CSS did not
|
|
447
|
+
* fold them back into the column, it made them unreachable. Status, category
|
|
448
|
+
* and any other rail field simply could not be edited in the side panel, the
|
|
449
|
+
* dialog, or on a phone.
|
|
450
|
+
*/
|
|
451
|
+
function useRailVisible(ref) {
|
|
452
|
+
const [visible, setVisible] = useState(false);
|
|
453
|
+
useLayoutEffect(() => {
|
|
454
|
+
const element = ref.current;
|
|
455
|
+
if (!element) return;
|
|
456
|
+
const measure = () => setVisible(element.getBoundingClientRect().width >= RAIL_MIN_WIDTH);
|
|
457
|
+
measure();
|
|
458
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
459
|
+
const observer = new ResizeObserver(measure);
|
|
460
|
+
observer.observe(element);
|
|
461
|
+
return () => observer.disconnect();
|
|
462
|
+
}, [ref]);
|
|
463
|
+
return visible;
|
|
464
|
+
}
|
|
465
|
+
//#endregion
|
|
466
|
+
//#region src/form/components/LabelWithIcon.tsx
|
|
467
|
+
/**
|
|
468
|
+
* Render the label of with an icon and the title of a property
|
|
469
|
+
* @group Form custom fields
|
|
470
|
+
*/
|
|
471
|
+
var LabelWithIcon = forwardRef(({ icon, title, small, className, required }, ref) => {
|
|
472
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
473
|
+
ref,
|
|
474
|
+
className: cls("align-middle inline-flex items-center my-0.5", small ? "gap-1" : "gap-2", className),
|
|
475
|
+
children: [icon, /* @__PURE__ */ jsx("span", {
|
|
476
|
+
className: `text-start font-medium text-${small ? "base" : "sm"} origin-top-left transform ${small ? "translate-x-2 scale-75" : ""}`,
|
|
477
|
+
children: (title ?? "") + (required ? " *" : "")
|
|
478
|
+
})]
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
LabelWithIcon.displayName = "LabelWithIcon";
|
|
482
|
+
//#endregion
|
|
101
483
|
//#region src/form/components/LabelWithIconAndTooltip.tsx
|
|
102
484
|
/**
|
|
103
485
|
* Render the label of with an icon and the title of a property
|
|
@@ -576,174 +958,40 @@ function StringPropertyPreview({ propertyKey, value, property, size, textOnly })
|
|
|
576
958
|
}) : /* @__PURE__ */ jsx(Fragment, { children: strValue });
|
|
577
959
|
}
|
|
578
960
|
}
|
|
579
|
-
//#endregion
|
|
580
|
-
//#region src/preview/property_previews/ArrayPropertyPreview.tsx
|
|
581
|
-
/**
|
|
582
|
-
* @group Preview components
|
|
583
|
-
*/
|
|
584
|
-
function ArrayPropertyPreview({ propertyKey, value, property, size }) {
|
|
585
|
-
if (property.type !== "array") throw Error("Picked wrong preview component ArrayPreview");
|
|
586
|
-
if (!property.of) throw Error(`You need to specify an 'of' prop (or specify a custom field) in your array property ${propertyKey}`);
|
|
587
|
-
const authController = useAuthController();
|
|
588
|
-
const resolvedProperties = resolveArrayProperties({
|
|
589
|
-
propertyKey,
|
|
590
|
-
property,
|
|
591
|
-
propertyConfigs: useCustomizationController().propertyConfigs,
|
|
592
|
-
authController
|
|
593
|
-
});
|
|
594
|
-
const values = value;
|
|
595
|
-
if (!values) return null;
|
|
596
|
-
const childSize = size === "medium" ? "medium" : "small";
|
|
597
|
-
return /* @__PURE__ */ jsx("div", {
|
|
598
|
-
className: "w-full flex flex-col gap-2",
|
|
599
|
-
children: values && values.map((val, index) => {
|
|
600
|
-
if (!resolvedProperties) throw Error("Property resolvedProperties is undefined");
|
|
601
|
-
const of = resolvedProperties[index] ?? resolvedProperties[index] ?? (Array.isArray(property.of) ? property.of[index] : property.of);
|
|
602
|
-
return of ? /* @__PURE__ */ jsx(React.Fragment, { children: /* @__PURE__ */ jsx("div", {
|
|
603
|
-
className: cls(defaultBorderMixin, "m-1 border-b last:border-b-0"),
|
|
604
|
-
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(PropertyPreview, {
|
|
605
|
-
propertyKey,
|
|
606
|
-
value: val,
|
|
607
|
-
property: of,
|
|
608
|
-
size: childSize
|
|
609
|
-
}) })
|
|
610
|
-
}) }, "preview_array_" + index) : null;
|
|
611
|
-
})
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
//#endregion
|
|
615
|
-
//#region src/util/property_utils.tsx
|
|
616
|
-
/**
|
|
617
|
-
* Resolve a size value (string token or number) to a numeric pixel value
|
|
618
|
-
* suitable for passing to Lucide icons.
|
|
619
|
-
*/
|
|
620
|
-
function resolveSize(size) {
|
|
621
|
-
if (typeof size === "number") return size;
|
|
622
|
-
return iconSize[size];
|
|
623
|
-
}
|
|
624
|
-
function isReferenceProperty(property) {
|
|
625
|
-
if (!property) return null;
|
|
626
|
-
if (property.type === "reference") return true;
|
|
627
|
-
if (property.type === "array") if (Array.isArray(property.of)) return false;
|
|
628
|
-
else return property.of?.type === "reference";
|
|
629
|
-
return false;
|
|
630
|
-
}
|
|
631
|
-
function isRelationProperty(property) {
|
|
632
|
-
if (!property) return null;
|
|
633
|
-
if (property.type === "relation") return true;
|
|
634
|
-
if (property.type === "array") if (Array.isArray(property.of)) return false;
|
|
635
|
-
else return property.of?.type === "relation";
|
|
636
|
-
return false;
|
|
637
|
-
}
|
|
638
|
-
function getIconForWidget(widget, size) {
|
|
639
|
-
return /* @__PURE__ */ jsx(widget?.Icon ?? CircleIcon, { size: resolveSize(size) });
|
|
640
|
-
}
|
|
641
|
-
/**
|
|
642
|
-
* Returns a default Lucide icon component based on property type.
|
|
643
|
-
* This provides a sensible fallback when no PropertyConfig is available.
|
|
644
|
-
*/
|
|
645
|
-
function getDefaultIconForProperty(property) {
|
|
646
|
-
switch (property.type) {
|
|
647
|
-
case "string":
|
|
648
|
-
if (property.storage) return UploadIcon;
|
|
649
|
-
if (property.admin?.urlPreview) return GlobeIcon;
|
|
650
|
-
if (property.email) return MailIcon;
|
|
651
|
-
if (property.admin?.multiline || property.admin?.markdown) return AlignLeftIcon;
|
|
652
|
-
return TextIcon;
|
|
653
|
-
case "number": return HashIcon;
|
|
654
|
-
case "boolean": return FlagIcon;
|
|
655
|
-
case "date": return CalendarIcon;
|
|
656
|
-
case "map": return VoteIcon;
|
|
657
|
-
case "array": {
|
|
658
|
-
const of = property.of;
|
|
659
|
-
if (property.oneOf) return Rows3Icon;
|
|
660
|
-
if (of && !Array.isArray(of)) {
|
|
661
|
-
if (of.type === "reference") return LinkIcon;
|
|
662
|
-
if (of.type === "string" && of.storage) return UploadIcon;
|
|
663
|
-
}
|
|
664
|
-
return RepeatIcon;
|
|
665
|
-
}
|
|
666
|
-
case "reference": return LinkIcon;
|
|
667
|
-
case "relation": return LinkIcon;
|
|
668
|
-
case "vector": return HashIcon;
|
|
669
|
-
default: return CircleIcon;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
function getIconForProperty(property, size = "small", fields = {}) {
|
|
673
|
-
const px = resolveSize(size);
|
|
674
|
-
if (isPropertyBuilder(property)) return /* @__PURE__ */ jsx(FunctionSquareIcon, { size: px });
|
|
675
|
-
const configId = property.propertyConfig || void 0;
|
|
676
|
-
const widget = configId ? fields[configId] : void 0;
|
|
677
|
-
if (widget?.Icon) return getIconForWidget(widget, size);
|
|
678
|
-
return /* @__PURE__ */ jsx(getDefaultIconForProperty(property), { size: px });
|
|
679
|
-
}
|
|
680
|
-
/**
|
|
681
|
-
* Get a property in a property tree from a path like
|
|
682
|
-
* `address.street`
|
|
683
|
-
* @param properties
|
|
684
|
-
* @param path
|
|
685
|
-
*/
|
|
686
|
-
function getPropertyInPath(properties, path) {
|
|
687
|
-
if (typeof properties === "object") {
|
|
688
|
-
if (path in properties) return properties[path];
|
|
689
|
-
if (path.includes(".")) {
|
|
690
|
-
const pathSegments = path.split(".");
|
|
691
|
-
const childProperty = properties[pathSegments[0]];
|
|
692
|
-
if (typeof childProperty === "object" && childProperty.type === "map" && childProperty.properties) return getPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."));
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
function getResolvedPropertyInPath(properties, path) {
|
|
697
|
-
if (typeof properties === "object") {
|
|
698
|
-
if (path in properties) return properties[path];
|
|
699
|
-
if (path.includes(".")) {
|
|
700
|
-
const pathSegments = path.split(".");
|
|
701
|
-
const childProperty = properties[pathSegments[0]];
|
|
702
|
-
if (childProperty.type === "map" && childProperty.properties) return getResolvedPropertyInPath(childProperty.properties, pathSegments.slice(1).join("."));
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
function getBracketNotation(path) {
|
|
707
|
-
return path.replace(/\.([^.]*)/g, "[$1]");
|
|
708
|
-
}
|
|
961
|
+
//#endregion
|
|
962
|
+
//#region src/preview/property_previews/ArrayPropertyPreview.tsx
|
|
709
963
|
/**
|
|
710
|
-
*
|
|
711
|
-
* Nested keys (like "data.mode") in propertiesOrder are ignored - they're for column ordering.
|
|
712
|
-
* @param properties
|
|
713
|
-
* @param propertiesOrder
|
|
964
|
+
* @group Preview components
|
|
714
965
|
*/
|
|
715
|
-
function
|
|
716
|
-
if (
|
|
717
|
-
|
|
718
|
-
const
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
result[key] = {
|
|
725
|
-
...mapProp,
|
|
726
|
-
properties: getPropertiesWithPropertiesOrder(mapProp.properties, mapProp.propertiesOrder ?? [])
|
|
727
|
-
};
|
|
728
|
-
} else if (property) result[key] = property;
|
|
966
|
+
function ArrayPropertyPreview({ propertyKey, value, property, size }) {
|
|
967
|
+
if (property.type !== "array") throw Error("Picked wrong preview component ArrayPreview");
|
|
968
|
+
if (!property.of) throw Error(`You need to specify an 'of' prop (or specify a custom field) in your array property ${propertyKey}`);
|
|
969
|
+
const authController = useAuthController();
|
|
970
|
+
const resolvedProperties = resolveArrayProperties({
|
|
971
|
+
propertyKey,
|
|
972
|
+
property,
|
|
973
|
+
propertyConfigs: useCustomizationController().propertyConfigs,
|
|
974
|
+
authController
|
|
729
975
|
});
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
976
|
+
const values = value;
|
|
977
|
+
if (!values) return null;
|
|
978
|
+
const childSize = size === "medium" ? "medium" : "small";
|
|
979
|
+
return /* @__PURE__ */ jsx("div", {
|
|
980
|
+
className: "w-full flex flex-col gap-2",
|
|
981
|
+
children: values && values.map((val, index) => {
|
|
982
|
+
if (!resolvedProperties) throw Error("Property resolvedProperties is undefined");
|
|
983
|
+
const of = resolvedProperties[index] ?? resolvedProperties[index] ?? (Array.isArray(property.of) ? property.of[index] : property.of);
|
|
984
|
+
return of ? /* @__PURE__ */ jsx(React.Fragment, { children: /* @__PURE__ */ jsx("div", {
|
|
985
|
+
className: cls(defaultBorderMixin, "m-1 border-b last:border-b-0"),
|
|
986
|
+
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(PropertyPreview, {
|
|
987
|
+
propertyKey,
|
|
988
|
+
value: val,
|
|
989
|
+
property: of,
|
|
990
|
+
size: childSize
|
|
991
|
+
}) })
|
|
992
|
+
}) }, "preview_array_" + index) : null;
|
|
993
|
+
})
|
|
741
994
|
});
|
|
742
|
-
return result;
|
|
743
|
-
}
|
|
744
|
-
function getDefaultPropertiesOrder(collection) {
|
|
745
|
-
if (collection.propertiesOrder) return collection.propertiesOrder;
|
|
746
|
-
return [...Object.keys(collection.properties), ...(collection.additionalFields ?? []).map((field) => field.key)];
|
|
747
995
|
}
|
|
748
996
|
//#endregion
|
|
749
997
|
//#region src/util/previews.ts
|
|
@@ -875,6 +1123,43 @@ var SidePanelControllerContext = React.createContext({});
|
|
|
875
1123
|
*/
|
|
876
1124
|
var useSidePanel = () => useContext(SidePanelControllerContext);
|
|
877
1125
|
//#endregion
|
|
1126
|
+
//#region src/components/EntityPreviewNesting.tsx
|
|
1127
|
+
/**
|
|
1128
|
+
* How deep the current entity preview is nested inside another one.
|
|
1129
|
+
*
|
|
1130
|
+
* A reference or relation rendered on its own (a form field, a table cell) is
|
|
1131
|
+
* at depth 0 and gets the full card treatment: image slot, id, title, preview
|
|
1132
|
+
* properties and a link to the side panel. A reference rendered *inside*
|
|
1133
|
+
* another entity preview is at depth 1+, where repeating that card produces a
|
|
1134
|
+
* box-inside-a-box with its own borders, id line and action button. Nested
|
|
1135
|
+
* previews collapse to a compact chip instead.
|
|
1136
|
+
*/
|
|
1137
|
+
var EntityPreviewDepthContext = React$1.createContext(0);
|
|
1138
|
+
/**
|
|
1139
|
+
* Current entity preview nesting depth. 0 means "not inside another preview".
|
|
1140
|
+
*/
|
|
1141
|
+
function useEntityPreviewDepth() {
|
|
1142
|
+
return React$1.useContext(EntityPreviewDepthContext);
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* True when the preview being rendered lives inside another entity preview,
|
|
1146
|
+
* and should therefore render its compact variant.
|
|
1147
|
+
*/
|
|
1148
|
+
function useIsNestedEntityPreview() {
|
|
1149
|
+
return useEntityPreviewDepth() > 0;
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Marks its children as being rendered inside an entity preview, so any
|
|
1153
|
+
* reference or relation below collapses to a chip.
|
|
1154
|
+
*/
|
|
1155
|
+
function NestedEntityPreviewBoundary({ children }) {
|
|
1156
|
+
const depth = useEntityPreviewDepth();
|
|
1157
|
+
return /* @__PURE__ */ jsx(EntityPreviewDepthContext.Provider, {
|
|
1158
|
+
value: depth + 1,
|
|
1159
|
+
children
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
//#endregion
|
|
878
1163
|
//#region src/components/EntityPreviewBinding.tsx
|
|
879
1164
|
/**
|
|
880
1165
|
* This component contains the main logic and content for displaying a entity preview,
|
|
@@ -906,7 +1191,7 @@ function EntityPreviewBindingData({ actions, collection: collectionProp, preview
|
|
|
906
1191
|
const restProperties = listProperties.filter((p) => p !== titleProperty && p !== imagePropertyKey);
|
|
907
1192
|
const imageValue = imagePropertyKey ? getValueInPath(entity.values, imagePropertyKey) : void 0;
|
|
908
1193
|
const usedImageValue = imageProperty !== void 0 ? "of" in imageProperty ? (imageValue ?? []).length > 0 ? imageValue[0] : void 0 : imageValue : void 0;
|
|
909
|
-
return /* @__PURE__ */ jsxs(
|
|
1194
|
+
return /* @__PURE__ */ jsxs(NestedEntityPreviewBoundary, { children: [
|
|
910
1195
|
/* @__PURE__ */ jsxs("div", {
|
|
911
1196
|
className: cls("flex shrink-0", {
|
|
912
1197
|
"w-6 h-6 mx-1 my-0.5": size === "small" || size === "smallest",
|
|
@@ -1050,15 +1335,118 @@ var EntityPreviewContainer = React$1.forwardRef(({ children, hover, onClick, siz
|
|
|
1050
1335
|
});
|
|
1051
1336
|
EntityPreviewContainer.displayName = "EntityPreviewContainer";
|
|
1052
1337
|
//#endregion
|
|
1338
|
+
//#region src/components/InlineEntityPreview.tsx
|
|
1339
|
+
var inlineMixin = "inline-flex items-center gap-1 min-w-0 max-w-full align-middle";
|
|
1340
|
+
/**
|
|
1341
|
+
* An entity rendered as one line of text: a small link glyph that marks it as
|
|
1342
|
+
* pointing elsewhere, then the target's title in whatever typography surrounds
|
|
1343
|
+
* it.
|
|
1344
|
+
*
|
|
1345
|
+
* This is what a reference or relation renders as when it is *not* the subject
|
|
1346
|
+
* of its slot — nested inside another entity preview, or filling a title slot.
|
|
1347
|
+
* The card treatment (image, id, preview properties, side-panel button) only
|
|
1348
|
+
* makes sense at the top level; repeating it one level down produces a bordered
|
|
1349
|
+
* box inside a bordered box, each with its own action buttons.
|
|
1350
|
+
*
|
|
1351
|
+
* @group Preview components
|
|
1352
|
+
*/
|
|
1353
|
+
function InlineEntityPreview({ entity, collection: collectionProp, label, disabled, onClick, includeEntityLink = true, iconSize = 12, className }) {
|
|
1354
|
+
const collectionRegistryController = useCollectionRegistryController();
|
|
1355
|
+
const analyticsController = useAnalyticsController();
|
|
1356
|
+
const sidePanelController = useSidePanel();
|
|
1357
|
+
const collection = collectionProp ?? collectionRegistryController.getCollection(entity.path);
|
|
1358
|
+
const titlePropertyKey = collection ? getEntityTitlePropertyKeyForEntity(collection, entity.values, entity.id) : void 0;
|
|
1359
|
+
const titleValue = titlePropertyKey ? getValueInPath(entity.values, titlePropertyKey) : void 0;
|
|
1360
|
+
const resolvedLabel = label ?? (titleValue !== void 0 && titleValue !== null && String(titleValue).trim().length > 0 ? String(titleValue) : String(entity.id));
|
|
1361
|
+
const interactive = !disabled && (onClick !== void 0 || includeEntityLink && collection !== void 0);
|
|
1362
|
+
const handleClick = (event) => {
|
|
1363
|
+
if (!interactive) return;
|
|
1364
|
+
event.preventDefault();
|
|
1365
|
+
event.stopPropagation();
|
|
1366
|
+
if (onClick) {
|
|
1367
|
+
onClick(event);
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
analyticsController.onAnalyticsEvent?.("entity_click_from_reference", {
|
|
1371
|
+
path: entity.path,
|
|
1372
|
+
entityId: entity.id
|
|
1373
|
+
});
|
|
1374
|
+
sidePanelController.open({
|
|
1375
|
+
entityId: entity.id,
|
|
1376
|
+
path: entity.path,
|
|
1377
|
+
collection,
|
|
1378
|
+
updateUrl: true
|
|
1379
|
+
});
|
|
1380
|
+
};
|
|
1381
|
+
const content = /* @__PURE__ */ jsxs("span", {
|
|
1382
|
+
tabIndex: interactive ? 0 : void 0,
|
|
1383
|
+
role: interactive ? "button" : void 0,
|
|
1384
|
+
onClick: interactive ? handleClick : void 0,
|
|
1385
|
+
onKeyDown: interactive ? (event) => {
|
|
1386
|
+
if (event.key === "Enter" || event.key === " ") handleClick(event);
|
|
1387
|
+
} : void 0,
|
|
1388
|
+
className: cls(inlineMixin, interactive ? "cursor-pointer hover:underline underline-offset-2" : "", className),
|
|
1389
|
+
children: [/* @__PURE__ */ jsx(LinkIcon, {
|
|
1390
|
+
size: iconSize,
|
|
1391
|
+
className: "shrink-0 opacity-40"
|
|
1392
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1393
|
+
className: "truncate",
|
|
1394
|
+
children: resolvedLabel
|
|
1395
|
+
})]
|
|
1396
|
+
});
|
|
1397
|
+
if (!interactive) return content;
|
|
1398
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
1399
|
+
title: `See details for ${resolvedLabel}`,
|
|
1400
|
+
asChild: true,
|
|
1401
|
+
children: content
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Placeholder shown while the target of an inline reference is being fetched.
|
|
1406
|
+
*/
|
|
1407
|
+
function InlineEntityPreviewSkeleton({ className }) {
|
|
1408
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
1409
|
+
className: cls(inlineMixin, className),
|
|
1410
|
+
children: [/* @__PURE__ */ jsx(LinkIcon, {
|
|
1411
|
+
size: 12,
|
|
1412
|
+
className: "shrink-0 opacity-40"
|
|
1413
|
+
}), /* @__PURE__ */ jsx(Skeleton, { className: "w-20 h-3" })]
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Shown when the target of an inline reference cannot be resolved — a deleted
|
|
1418
|
+
* row, or a collection that is not registered. Keeps the line readable instead
|
|
1419
|
+
* of dropping an error box into the middle of a card.
|
|
1420
|
+
*/
|
|
1421
|
+
function InlineEntityPreviewMissing({ label, tooltip, className }) {
|
|
1422
|
+
const content = /* @__PURE__ */ jsxs("span", {
|
|
1423
|
+
className: cls(inlineMixin, "text-text-secondary dark:text-text-secondary-dark", className),
|
|
1424
|
+
children: [/* @__PURE__ */ jsx(LinkIcon, {
|
|
1425
|
+
size: 12,
|
|
1426
|
+
className: "shrink-0 opacity-40"
|
|
1427
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1428
|
+
className: "truncate italic",
|
|
1429
|
+
children: label
|
|
1430
|
+
})]
|
|
1431
|
+
});
|
|
1432
|
+
if (!tooltip) return content;
|
|
1433
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
1434
|
+
title: tooltip,
|
|
1435
|
+
asChild: true,
|
|
1436
|
+
children: content
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
//#endregion
|
|
1053
1440
|
//#region src/preview/components/ReferencePreview.tsx
|
|
1054
1441
|
/**
|
|
1055
1442
|
* @group Preview components
|
|
1056
1443
|
*/
|
|
1057
1444
|
var ReferencePreview = function ReferencePreview(props) {
|
|
1058
1445
|
const reference = props.reference;
|
|
1446
|
+
const nested = useIsNestedEntityPreview();
|
|
1059
1447
|
if (!(typeof reference === "object" && "isEntityReference" in reference && reference.isEntityReference())) {
|
|
1060
1448
|
console.warn("Reference preview received value of type", typeof reference);
|
|
1061
|
-
if (props.textOnly) return /* @__PURE__ */ jsx("span", { children: String(reference) });
|
|
1449
|
+
if (props.textOnly || nested) return /* @__PURE__ */ jsx("span", { children: String(reference) });
|
|
1062
1450
|
return /* @__PURE__ */ jsx(EntityPreviewContainer, {
|
|
1063
1451
|
onClick: props.onClick,
|
|
1064
1452
|
size: props.size ?? "medium",
|
|
@@ -1073,9 +1461,14 @@ var ReferencePreview = function ReferencePreview(props) {
|
|
|
1073
1461
|
var DefaultMissingReference$2 = () => null;
|
|
1074
1462
|
function ReferencePreviewInternalInner({ disabled, reference, previewProperties, size, hover, onClick, includeEntityLink = true, includeId = true, textOnly, collection }) {
|
|
1075
1463
|
const ResolvedMissingReference = useComponentOverride("Entity.MissingReference", DefaultMissingReference$2);
|
|
1464
|
+
const nested = useIsNestedEntityPreview();
|
|
1076
1465
|
if (!collection) if (ResolvedMissingReference !== DefaultMissingReference$2) return /* @__PURE__ */ jsx(ResolvedMissingReference, { path: reference.path });
|
|
1077
1466
|
else {
|
|
1078
1467
|
if (textOnly) return /* @__PURE__ */ jsx("span", { children: reference.path });
|
|
1468
|
+
if (nested) return /* @__PURE__ */ jsx(InlineEntityPreviewMissing, {
|
|
1469
|
+
label: String(reference.id),
|
|
1470
|
+
tooltip: `Collection not found: ${reference.path}`
|
|
1471
|
+
});
|
|
1079
1472
|
return /* @__PURE__ */ jsx(EntityPreviewContainer, {
|
|
1080
1473
|
onClick,
|
|
1081
1474
|
size: size ?? "medium",
|
|
@@ -1113,6 +1506,7 @@ function ReferencePreviewInternal(props) {
|
|
|
1113
1506
|
function ReferencePreviewExisting({ reference, collection, previewProperties, size, disabled, includeEntityLink, includeId, onClick, hover, textOnly }) {
|
|
1114
1507
|
const ResolvedEntityPreview = useComponentOverride("EntityPreview", EntityPreviewBinding);
|
|
1115
1508
|
useCustomizationController();
|
|
1509
|
+
const inline = useIsNestedEntityPreview() || Boolean(textOnly);
|
|
1116
1510
|
const { entity, dataLoading, dataLoadingError } = useFetch({
|
|
1117
1511
|
path: reference.path,
|
|
1118
1512
|
entityId: reference.id,
|
|
@@ -1128,7 +1522,10 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
|
|
|
1128
1522
|
tooltip: reference.path
|
|
1129
1523
|
});
|
|
1130
1524
|
if (body) {
|
|
1131
|
-
if (
|
|
1525
|
+
if (inline) return /* @__PURE__ */ jsx(InlineEntityPreviewMissing, {
|
|
1526
|
+
label: String(reference.id),
|
|
1527
|
+
tooltip: reference.pathWithId
|
|
1528
|
+
});
|
|
1132
1529
|
return /* @__PURE__ */ jsx(EntityPreviewContainer, {
|
|
1133
1530
|
onClick: disabled ? void 0 : onClick,
|
|
1134
1531
|
hover: disabled ? void 0 : hover,
|
|
@@ -1137,7 +1534,7 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
|
|
|
1137
1534
|
});
|
|
1138
1535
|
}
|
|
1139
1536
|
if (dataLoading && !usedEntity) {
|
|
1140
|
-
if (
|
|
1537
|
+
if (inline) return /* @__PURE__ */ jsx(InlineEntityPreviewSkeleton, {});
|
|
1141
1538
|
return /* @__PURE__ */ jsx(EntityPreviewContainer, {
|
|
1142
1539
|
onClick: disabled ? void 0 : onClick,
|
|
1143
1540
|
hover: disabled ? void 0 : hover,
|
|
@@ -1146,7 +1543,10 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
|
|
|
1146
1543
|
});
|
|
1147
1544
|
}
|
|
1148
1545
|
if (!usedEntity) {
|
|
1149
|
-
if (
|
|
1546
|
+
if (inline) return /* @__PURE__ */ jsx(InlineEntityPreviewMissing, {
|
|
1547
|
+
label: String(reference.id),
|
|
1548
|
+
tooltip: "Entity not found"
|
|
1549
|
+
});
|
|
1150
1550
|
return /* @__PURE__ */ jsx(EntityPreviewContainer, {
|
|
1151
1551
|
onClick: disabled ? void 0 : onClick,
|
|
1152
1552
|
hover: disabled ? void 0 : hover,
|
|
@@ -1154,14 +1554,13 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
|
|
|
1154
1554
|
children: /* @__PURE__ */ jsx(ErrorView, { error: "Entity not found" })
|
|
1155
1555
|
});
|
|
1156
1556
|
}
|
|
1157
|
-
if (
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
}
|
|
1557
|
+
if (inline) return /* @__PURE__ */ jsx(InlineEntityPreview, {
|
|
1558
|
+
entity: usedEntity,
|
|
1559
|
+
collection,
|
|
1560
|
+
disabled,
|
|
1561
|
+
onClick,
|
|
1562
|
+
includeEntityLink: !textOnly && includeEntityLink !== false
|
|
1563
|
+
});
|
|
1165
1564
|
return /* @__PURE__ */ jsx(ResolvedEntityPreview, {
|
|
1166
1565
|
size,
|
|
1167
1566
|
previewKeys: previewProperties,
|
|
@@ -1176,14 +1575,53 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
|
|
|
1176
1575
|
}
|
|
1177
1576
|
var referencesCache = /* @__PURE__ */ new Map();
|
|
1178
1577
|
//#endregion
|
|
1578
|
+
//#region src/preview/components/InlineEntityListPreview.tsx
|
|
1579
|
+
/**
|
|
1580
|
+
* How many links a nested list shows before collapsing the rest into a count.
|
|
1581
|
+
*/
|
|
1582
|
+
var MAX_INLINE_ITEMS = 3;
|
|
1583
|
+
/**
|
|
1584
|
+
* One wrapping line of inline entity links, with the overflow collapsed into a
|
|
1585
|
+
* `+N`. Used for arrays of references or relations that are nested inside
|
|
1586
|
+
* another preview, where stacking full-width cards turns a card into a wall.
|
|
1587
|
+
*
|
|
1588
|
+
* @group Preview components
|
|
1589
|
+
*/
|
|
1590
|
+
function InlineEntityListPreview({ items, renderItem, max = MAX_INLINE_ITEMS, className }) {
|
|
1591
|
+
if (!items || items.length === 0) return null;
|
|
1592
|
+
const shown = items.slice(0, max);
|
|
1593
|
+
const overflow = items.length - shown.length;
|
|
1594
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
1595
|
+
className: cls("inline-flex flex-wrap items-center gap-x-2 gap-y-0.5 min-w-0 max-w-full", className),
|
|
1596
|
+
children: [shown.map((item, index) => renderItem(item, index)), overflow > 0 && /* @__PURE__ */ jsxs("span", {
|
|
1597
|
+
className: "text-text-secondary dark:text-text-secondary-dark shrink-0",
|
|
1598
|
+
children: ["+", overflow]
|
|
1599
|
+
})]
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
//#endregion
|
|
1179
1603
|
//#region src/preview/property_previews/ArrayOfReferencesPreview.tsx
|
|
1180
1604
|
/**
|
|
1181
1605
|
* @group Preview components
|
|
1182
1606
|
*/
|
|
1183
|
-
function ArrayOfReferencesPreview({ propertyKey, value, property, size }) {
|
|
1607
|
+
function ArrayOfReferencesPreview({ propertyKey, value, property, size, textOnly }) {
|
|
1608
|
+
const nested = useIsNestedEntityPreview();
|
|
1184
1609
|
if (Array.isArray(property?.of)) throw Error("Using array properties instead of single one in `of` in ArrayProperty");
|
|
1185
1610
|
if (property?.type !== "array" || !property.of || property.of.type !== "reference") throw Error("Picked wrong preview component ArrayOfReferencesPreview");
|
|
1186
1611
|
const childSize = size === "medium" ? "medium" : "small";
|
|
1612
|
+
const ofProperty = property.of;
|
|
1613
|
+
if (nested || textOnly) return /* @__PURE__ */ jsx(InlineEntityListPreview, {
|
|
1614
|
+
items: value ?? [],
|
|
1615
|
+
renderItem: (reference, index) => /* @__PURE__ */ jsx(ReferencePreview, {
|
|
1616
|
+
disabled: !ofProperty.path,
|
|
1617
|
+
previewProperties: ofProperty.admin?.previewProperties,
|
|
1618
|
+
size: childSize,
|
|
1619
|
+
reference,
|
|
1620
|
+
textOnly,
|
|
1621
|
+
includeId: ofProperty.admin?.includeId,
|
|
1622
|
+
includeEntityLink: ofProperty.admin?.includeEntityLink
|
|
1623
|
+
}, `preview_array_ref_${propertyKey}_${index}`)
|
|
1624
|
+
});
|
|
1187
1625
|
return /* @__PURE__ */ jsx("div", {
|
|
1188
1626
|
className: "flex flex-col w-full",
|
|
1189
1627
|
children: value ? value.map((reference, index) => {
|
|
@@ -1248,9 +1686,10 @@ function extractDisplayFromPlainObject(obj) {
|
|
|
1248
1686
|
*/
|
|
1249
1687
|
var RelationPreview = function RelationPreview(props) {
|
|
1250
1688
|
const relation = props.relation;
|
|
1689
|
+
const nested = useIsNestedEntityPreview();
|
|
1251
1690
|
if (!(typeof relation === "object" && "isEntityRelation" in relation && relation.isEntityRelation())) {
|
|
1252
1691
|
console.warn("Relation preview received value of type", typeof relation);
|
|
1253
|
-
if (props.textOnly) return /* @__PURE__ */ jsx("span", {
|
|
1692
|
+
if (props.textOnly || nested) return /* @__PURE__ */ jsx("span", {
|
|
1254
1693
|
className: "truncate",
|
|
1255
1694
|
children: extractDisplayFromPlainObject(relation)
|
|
1256
1695
|
});
|
|
@@ -1268,9 +1707,14 @@ var RelationPreview = function RelationPreview(props) {
|
|
|
1268
1707
|
var DefaultMissingReference$1 = () => null;
|
|
1269
1708
|
function RelationPreviewInternalInner({ disabled, relation, previewProperties, size, hover, onClick, includeEntityLink = true, includeId = true, textOnly, collection }) {
|
|
1270
1709
|
const ResolvedMissingReference = useComponentOverride("Entity.MissingReference", DefaultMissingReference$1);
|
|
1710
|
+
const nested = useIsNestedEntityPreview();
|
|
1271
1711
|
if (!collection) if (ResolvedMissingReference !== DefaultMissingReference$1) return /* @__PURE__ */ jsx(ResolvedMissingReference, { path: relation.path });
|
|
1272
1712
|
else {
|
|
1273
1713
|
if (textOnly) return /* @__PURE__ */ jsx("span", { children: relation.path });
|
|
1714
|
+
if (nested) return /* @__PURE__ */ jsx(InlineEntityPreviewMissing, {
|
|
1715
|
+
label: String(relation.id),
|
|
1716
|
+
tooltip: `Collection not found: ${relation.path}`
|
|
1717
|
+
});
|
|
1274
1718
|
return /* @__PURE__ */ jsx(EntityPreviewContainer, {
|
|
1275
1719
|
size,
|
|
1276
1720
|
children: /* @__PURE__ */ jsx(ErrorView, { error: `Collection not found: ${relation.path}` })
|
|
@@ -1305,6 +1749,7 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
|
|
|
1305
1749
|
const passedEntity = relation.data;
|
|
1306
1750
|
const ResolvedEntityPreview = useComponentOverride("EntityPreview", EntityPreviewBinding);
|
|
1307
1751
|
useCustomizationController();
|
|
1752
|
+
const inline = useIsNestedEntityPreview() || Boolean(textOnly);
|
|
1308
1753
|
const { entity, dataLoading, dataLoadingError } = useFetch({
|
|
1309
1754
|
path: relation.path,
|
|
1310
1755
|
entityId: passedEntity ? void 0 : relation.id,
|
|
@@ -1320,7 +1765,10 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
|
|
|
1320
1765
|
tooltip: relation.path
|
|
1321
1766
|
});
|
|
1322
1767
|
if (body) {
|
|
1323
|
-
if (
|
|
1768
|
+
if (inline) return /* @__PURE__ */ jsx(InlineEntityPreviewMissing, {
|
|
1769
|
+
label: String(relation.id),
|
|
1770
|
+
tooltip: relation.pathWithId
|
|
1771
|
+
});
|
|
1324
1772
|
return /* @__PURE__ */ jsx(EntityPreviewContainer, {
|
|
1325
1773
|
onClick: disabled ? void 0 : onClick,
|
|
1326
1774
|
hover: disabled ? void 0 : hover,
|
|
@@ -1329,7 +1777,7 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
|
|
|
1329
1777
|
});
|
|
1330
1778
|
}
|
|
1331
1779
|
if (dataLoading && !usedEntity) {
|
|
1332
|
-
if (
|
|
1780
|
+
if (inline) return /* @__PURE__ */ jsx(InlineEntityPreviewSkeleton, {});
|
|
1333
1781
|
return /* @__PURE__ */ jsx(EntityPreviewContainer, {
|
|
1334
1782
|
onClick: disabled ? void 0 : onClick,
|
|
1335
1783
|
hover: disabled ? void 0 : hover,
|
|
@@ -1338,7 +1786,10 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
|
|
|
1338
1786
|
});
|
|
1339
1787
|
}
|
|
1340
1788
|
if (!usedEntity) {
|
|
1341
|
-
if (
|
|
1789
|
+
if (inline) return /* @__PURE__ */ jsx(InlineEntityPreviewMissing, {
|
|
1790
|
+
label: String(relation.id),
|
|
1791
|
+
tooltip: "Entity not found"
|
|
1792
|
+
});
|
|
1342
1793
|
return /* @__PURE__ */ jsx(EntityPreviewContainer, {
|
|
1343
1794
|
onClick: disabled ? void 0 : onClick,
|
|
1344
1795
|
hover: disabled ? void 0 : hover,
|
|
@@ -1346,14 +1797,13 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
|
|
|
1346
1797
|
children: /* @__PURE__ */ jsx(ErrorView, { error: "Entity not found" })
|
|
1347
1798
|
});
|
|
1348
1799
|
}
|
|
1349
|
-
if (
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
}
|
|
1800
|
+
if (inline) return /* @__PURE__ */ jsx(InlineEntityPreview, {
|
|
1801
|
+
entity: usedEntity,
|
|
1802
|
+
collection,
|
|
1803
|
+
disabled,
|
|
1804
|
+
onClick,
|
|
1805
|
+
includeEntityLink: !textOnly && includeEntityLink !== false
|
|
1806
|
+
});
|
|
1357
1807
|
return /* @__PURE__ */ jsx(ResolvedEntityPreview, {
|
|
1358
1808
|
size,
|
|
1359
1809
|
previewKeys: previewProperties,
|
|
@@ -1372,10 +1822,23 @@ var relationsCache = /* @__PURE__ */ new Map();
|
|
|
1372
1822
|
/**
|
|
1373
1823
|
* @group Preview components
|
|
1374
1824
|
*/
|
|
1375
|
-
function ArrayOfRelationsPreview({ propertyKey, value, property, size }) {
|
|
1825
|
+
function ArrayOfRelationsPreview({ propertyKey, value, property, size, textOnly }) {
|
|
1826
|
+
const nested = useIsNestedEntityPreview();
|
|
1376
1827
|
if (Array.isArray(property?.of)) throw Error("Using array properties instead of single one in `of` in ArrayProperty");
|
|
1377
1828
|
if (property?.type !== "array" || !property.of || property.of.type !== "relation") throw Error("Picked wrong preview component ArrayOfRelationsPreview");
|
|
1378
1829
|
const ofProperty = property.of;
|
|
1830
|
+
if (nested || textOnly) return /* @__PURE__ */ jsx(InlineEntityListPreview, {
|
|
1831
|
+
items: (value ?? []).map((relation) => normalizeToEntityRelation(relation)).filter((relation) => Boolean(relation)),
|
|
1832
|
+
renderItem: (relation, index) => /* @__PURE__ */ jsx(RelationPreview, {
|
|
1833
|
+
disabled: !ofProperty.relation,
|
|
1834
|
+
previewProperties: ofProperty.admin?.previewProperties,
|
|
1835
|
+
size: "small",
|
|
1836
|
+
relation,
|
|
1837
|
+
textOnly,
|
|
1838
|
+
includeId: ofProperty.admin?.includeId,
|
|
1839
|
+
includeEntityLink: ofProperty.admin?.includeEntityLink
|
|
1840
|
+
}, `preview_array_rel_${propertyKey}_${index}`)
|
|
1841
|
+
});
|
|
1379
1842
|
return /* @__PURE__ */ jsx("div", {
|
|
1380
1843
|
className: "flex flex-col w-full gap-0.5",
|
|
1381
1844
|
children: value ? value.map((relation, index) => {
|
|
@@ -1696,11 +2159,11 @@ function getCache(baseUrl) {
|
|
|
1696
2159
|
}
|
|
1697
2160
|
return cache;
|
|
1698
2161
|
}
|
|
1699
|
-
async function fetchUsers(
|
|
2162
|
+
async function fetchUsers(apiBase, ids, token) {
|
|
1700
2163
|
const resolved = /* @__PURE__ */ new Map();
|
|
1701
2164
|
for (const id of ids) resolved.set(id, null);
|
|
1702
2165
|
const params = new URLSearchParams({ ids: ids.join(",") });
|
|
1703
|
-
const response = await fetch(`${
|
|
2166
|
+
const response = await fetch(`${apiBase}/admin/users?${params}`, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
|
|
1704
2167
|
if (!response.ok) return resolved;
|
|
1705
2168
|
const data = await response.json();
|
|
1706
2169
|
const rows = data.users ?? data.data ?? [];
|
|
@@ -1753,7 +2216,7 @@ function requestUser(baseUrl, uid, getAuthToken) {
|
|
|
1753
2216
|
function useResolvedUser(uid) {
|
|
1754
2217
|
const client = useRebaseClient();
|
|
1755
2218
|
const { getAuthToken } = useAuthController();
|
|
1756
|
-
const baseUrl = client
|
|
2219
|
+
const baseUrl = apiBaseOf(client);
|
|
1757
2220
|
const [user, setUser] = useState(() => baseUrl && uid ? getCache(baseUrl).users.get(uid) ?? null : null);
|
|
1758
2221
|
useEffect(() => {
|
|
1759
2222
|
if (!baseUrl || !uid) {
|
|
@@ -1815,6 +2278,7 @@ function UserPreview({ value }) {
|
|
|
1815
2278
|
var PropertyPreview = React.memo(function PropertyPreview(props) {
|
|
1816
2279
|
const authController = useAuthController();
|
|
1817
2280
|
const customizationController = useCustomizationController();
|
|
2281
|
+
const nested = useIsNestedEntityPreview();
|
|
1818
2282
|
let content;
|
|
1819
2283
|
const { property: inputProperty, propertyKey, value, size, height, width, interactive, fill } = props;
|
|
1820
2284
|
const property = resolveProperty({
|
|
@@ -1935,12 +2399,14 @@ var PropertyPreview = React.memo(function PropertyPreview(props) {
|
|
|
1935
2399
|
property
|
|
1936
2400
|
});
|
|
1937
2401
|
else content = buildWrongValueType(propertyKey, property.type, value);
|
|
1938
|
-
else if (property.type === "date")
|
|
1939
|
-
date
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
2402
|
+
else if (property.type === "date") {
|
|
2403
|
+
const date = asDate(value);
|
|
2404
|
+
if (date) content = /* @__PURE__ */ jsx(DatePreview, {
|
|
2405
|
+
date,
|
|
2406
|
+
mode: property.mode
|
|
2407
|
+
});
|
|
2408
|
+
else content = buildWrongValueType(propertyKey, property.type, value);
|
|
2409
|
+
} else if (property.type === "reference") if (typeof property.path === "string") if (typeof value === "object" && value !== null && "isEntityReference" in value && value.isEntityReference()) content = /* @__PURE__ */ jsx(ReferencePreview, {
|
|
1944
2410
|
disabled: !property.path,
|
|
1945
2411
|
previewProperties: property.admin?.previewProperties,
|
|
1946
2412
|
includeId: property.admin?.includeId,
|
|
@@ -1952,26 +2418,28 @@ var PropertyPreview = React.memo(function PropertyPreview(props) {
|
|
|
1952
2418
|
else content = buildWrongValueType(propertyKey, property.type, value);
|
|
1953
2419
|
else content = /* @__PURE__ */ jsx(EmptyValue, {});
|
|
1954
2420
|
else if (property.type === "relation") if (!value) content = /* @__PURE__ */ jsx(EmptyValue, {});
|
|
1955
|
-
else if (Array.isArray(value))
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2421
|
+
else if (Array.isArray(value)) {
|
|
2422
|
+
const relations = value.map((item) => normalizeToEntityRelation(item, "relation")).filter(Boolean);
|
|
2423
|
+
const renderRelation = (entityRelation, index) => /* @__PURE__ */ jsx(RelationPreview, {
|
|
2424
|
+
disabled: !property.relation,
|
|
2425
|
+
previewProperties: property.admin?.previewProperties,
|
|
2426
|
+
includeId: property.admin?.includeId,
|
|
2427
|
+
includeEntityLink: property.admin?.includeEntityLink,
|
|
2428
|
+
size: "small",
|
|
2429
|
+
relation: entityRelation,
|
|
2430
|
+
textOnly: props.textOnly
|
|
2431
|
+
}, `preview_rel_${propertyKey}_${index}`);
|
|
2432
|
+
content = nested || props.textOnly ? /* @__PURE__ */ jsx(InlineEntityListPreview, {
|
|
2433
|
+
items: relations,
|
|
2434
|
+
renderItem: renderRelation
|
|
2435
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
2436
|
+
className: "flex flex-col w-full gap-0.5",
|
|
2437
|
+
children: relations.map((entityRelation, index) => /* @__PURE__ */ jsx("div", {
|
|
1961
2438
|
className: "w-full",
|
|
1962
|
-
children:
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
includeEntityLink: property.admin?.includeEntityLink,
|
|
1967
|
-
size: "small",
|
|
1968
|
-
relation: entityRelation,
|
|
1969
|
-
textOnly: props.textOnly
|
|
1970
|
-
})
|
|
1971
|
-
}, `preview_rel_${propertyKey}_${index}`);
|
|
1972
|
-
})
|
|
1973
|
-
});
|
|
1974
|
-
else {
|
|
2439
|
+
children: renderRelation(entityRelation, index)
|
|
2440
|
+
}, `preview_rel_wrapper_${propertyKey}_${index}`))
|
|
2441
|
+
});
|
|
2442
|
+
} else {
|
|
1975
2443
|
const relationValue = normalizeToEntityRelation(value, "relation");
|
|
1976
2444
|
if (relationValue) content = /* @__PURE__ */ jsx(RelationPreview, {
|
|
1977
2445
|
disabled: !property.relation,
|
|
@@ -1999,6 +2467,16 @@ var PropertyPreview = React.memo(function PropertyPreview(props) {
|
|
|
1999
2467
|
else content = JSON.stringify(value);
|
|
2000
2468
|
return content === void 0 || content === null || Array.isArray(content) && content.length === 0 ? /* @__PURE__ */ jsx(EmptyValue, {}) : content;
|
|
2001
2469
|
}, deepEqual);
|
|
2470
|
+
/**
|
|
2471
|
+
* A `Date`, or a string/number that unambiguously names one. Returns null for
|
|
2472
|
+
* anything else, so an actual type mismatch still reports as one.
|
|
2473
|
+
*/
|
|
2474
|
+
function asDate(value) {
|
|
2475
|
+
if (value instanceof Date) return isNaN(value.getTime()) ? null : value;
|
|
2476
|
+
if (typeof value !== "string" && typeof value !== "number") return null;
|
|
2477
|
+
const parsed = new Date(value);
|
|
2478
|
+
return isNaN(parsed.getTime()) ? null : parsed;
|
|
2479
|
+
}
|
|
2002
2480
|
function buildWrongValueType(name, type, value) {
|
|
2003
2481
|
console.warn(`Unexpected value for property ${name}, of type ${type}`, value);
|
|
2004
2482
|
return /* @__PURE__ */ jsx(ErrorView, {
|
|
@@ -2104,10 +2582,10 @@ function FieldHelperText({ error, showError, property, includeDescription = true
|
|
|
2104
2582
|
* and tables to the specified properties.
|
|
2105
2583
|
* @group Form fields
|
|
2106
2584
|
*/
|
|
2107
|
-
function ReadOnlyFieldBinding({ propertyKey, value, error, showError, minimalistView, property, includeDescription, context }) {
|
|
2585
|
+
function ReadOnlyFieldBinding({ propertyKey, value, error, showError, minimalistView, property, includeDescription, hideLabel, context }) {
|
|
2108
2586
|
const skipCardWrapper = property.type === "relation" || property.type === "reference";
|
|
2109
2587
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2110
|
-
!minimalistView && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
2588
|
+
!minimalistView && !hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
2111
2589
|
propertyKey,
|
|
2112
2590
|
icon: getIconForProperty(property, "small"),
|
|
2113
2591
|
required: property.validation?.required,
|
|
@@ -2265,8 +2743,10 @@ function VirtualTableSelect(props) {
|
|
|
2265
2743
|
if (ref.current && focused) ref.current?.focus({ preventScroll: true });
|
|
2266
2744
|
}, [focused, ref]);
|
|
2267
2745
|
const onChange = useCallback((updatedValue) => {
|
|
2268
|
-
if (valueType === "number") if (multiple)
|
|
2269
|
-
|
|
2746
|
+
if (valueType === "number") if (multiple) {
|
|
2747
|
+
const newValue = updatedValue.map((v) => parseFloat(v));
|
|
2748
|
+
updateValue(newValue);
|
|
2749
|
+
} else updateValue(parseFloat(updatedValue));
|
|
2270
2750
|
else if (valueType === "string") if (!updatedValue) updateValue(null);
|
|
2271
2751
|
else updateValue(updatedValue);
|
|
2272
2752
|
else throw Error("Missing mapping in TableSelect");
|
|
@@ -2344,7 +2824,8 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE$2 } = {}) {
|
|
|
2344
2824
|
const offsetRef = useRef(0);
|
|
2345
2825
|
const userCacheRef = useRef(/* @__PURE__ */ new Map());
|
|
2346
2826
|
const fetchUsers = useCallback(async (searchStr, offset, append) => {
|
|
2347
|
-
|
|
2827
|
+
const apiBase = apiBaseOf(client);
|
|
2828
|
+
if (!apiBase) return;
|
|
2348
2829
|
setIsLoading(true);
|
|
2349
2830
|
try {
|
|
2350
2831
|
const token = await getAuthToken?.();
|
|
@@ -2353,12 +2834,12 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE$2 } = {}) {
|
|
|
2353
2834
|
offset: String(offset)
|
|
2354
2835
|
});
|
|
2355
2836
|
if (searchStr) params.set("search", searchStr);
|
|
2356
|
-
const response = await fetch(`${
|
|
2837
|
+
const response = await fetch(`${apiBase}/admin/users?${params}`, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
|
|
2357
2838
|
if (!response.ok) throw new Error("Failed to fetch users");
|
|
2358
2839
|
const data = await response.json();
|
|
2359
2840
|
const newItems = (data.data ?? data.users ?? []).map((r) => {
|
|
2360
2841
|
const user = {
|
|
2361
|
-
uid: r.id ?? "",
|
|
2842
|
+
uid: r.uid ?? r.id ?? "",
|
|
2362
2843
|
email: r.email ?? "",
|
|
2363
2844
|
displayName: r.displayName ?? r.display_name ?? null,
|
|
2364
2845
|
photoURL: r.photoURL ?? r.photo_url ?? null,
|
|
@@ -2383,6 +2864,7 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE$2 } = {}) {
|
|
|
2383
2864
|
}
|
|
2384
2865
|
}, [
|
|
2385
2866
|
client?.baseUrl,
|
|
2867
|
+
client?.apiPath,
|
|
2386
2868
|
getAuthToken,
|
|
2387
2869
|
pageSize
|
|
2388
2870
|
]);
|
|
@@ -3811,12 +4293,13 @@ function getTableBindingForProperty(property, selected) {
|
|
|
3811
4293
|
updateValue
|
|
3812
4294
|
}) };
|
|
3813
4295
|
else if (property.type === "date") return {
|
|
3814
|
-
Component: ({ propertyKey, error, validationError, disabled, selected, property, internalValue, updateValue }) => {
|
|
4296
|
+
Component: ({ propertyKey, error, validationError, disabled, selected, size, property, internalValue, updateValue }) => {
|
|
3815
4297
|
const { locale } = useCustomizationController();
|
|
3816
4298
|
return /* @__PURE__ */ jsx(VirtualTableDateField, {
|
|
3817
4299
|
name: propertyKey,
|
|
3818
4300
|
error: validationError ?? error,
|
|
3819
4301
|
disabled,
|
|
4302
|
+
small: getPreviewSizeFrom(size) !== "medium",
|
|
3820
4303
|
mode: property.mode,
|
|
3821
4304
|
focused: selected,
|
|
3822
4305
|
internalValue,
|
|
@@ -4427,6 +4910,7 @@ var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, cus
|
|
|
4427
4910
|
propertyKey
|
|
4428
4911
|
});
|
|
4429
4912
|
};
|
|
4913
|
+
const tableBinding = useMemo(() => getTableBindingForProperty(property, selected), [property, selected]);
|
|
4430
4914
|
let innerComponent;
|
|
4431
4915
|
let allowScroll = false;
|
|
4432
4916
|
let showExpandIcon = false;
|
|
@@ -4460,7 +4944,6 @@ var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, cus
|
|
|
4460
4944
|
})
|
|
4461
4945
|
}, `${propertyKey}_${entity.path}_${entity.id}`);
|
|
4462
4946
|
if (!customField && (!customPreview || selected)) {
|
|
4463
|
-
const tableBinding = getTableBindingForProperty(property, selected);
|
|
4464
4947
|
if (tableBinding) {
|
|
4465
4948
|
const Component = tableBinding.Component;
|
|
4466
4949
|
innerComponent = /* @__PURE__ */ jsx(Component, {
|
|
@@ -4828,7 +5311,7 @@ function propertiesToColumns({ properties, sortable, fixedFilter, engine, Additi
|
|
|
4828
5311
|
align: getTableCellAlignment(property),
|
|
4829
5312
|
icon: getIconForProperty(property, "small"),
|
|
4830
5313
|
title: property.name ?? key,
|
|
4831
|
-
sortable,
|
|
5314
|
+
sortable: sortable && sortableProperty(property),
|
|
4832
5315
|
filter: !disabledFilter && filterable,
|
|
4833
5316
|
width: getTablePropertyColumnWidth(property),
|
|
4834
5317
|
resizable: true,
|
|
@@ -4844,6 +5327,27 @@ function propertiesToColumns({ properties, sortable, fixedFilter, engine, Additi
|
|
|
4844
5327
|
};
|
|
4845
5328
|
});
|
|
4846
5329
|
}
|
|
5330
|
+
/**
|
|
5331
|
+
* Whether this column can be ordered by.
|
|
5332
|
+
*
|
|
5333
|
+
* `sortable` arrived as one flag for the whole table while `filter` was
|
|
5334
|
+
* computed per property, so every column advertised a sort — including the ones
|
|
5335
|
+
* no `ORDER BY` can be written for. A to-many relation is a *set* per row
|
|
5336
|
+
* (`posts.tags`), and there is no single value to order on; the driver used to
|
|
5337
|
+
* answer by silently dropping the sort and returning rows in whatever order
|
|
5338
|
+
* Postgres pleased, which is how a header that did nothing went unnoticed.
|
|
5339
|
+
*
|
|
5340
|
+
* Now that the driver refuses instead, offering the control would turn a
|
|
5341
|
+
* useless header into a failed request. Same authority as the filter side: ask
|
|
5342
|
+
* the property, not the table.
|
|
5343
|
+
*/
|
|
5344
|
+
function sortableProperty(property) {
|
|
5345
|
+
if (property.type !== "relation") return true;
|
|
5346
|
+
const relationProperty = property;
|
|
5347
|
+
const kind = relationProperty.relation?.kind ?? relationProperty.resolvedRelation?.kind;
|
|
5348
|
+
if (!kind) return true;
|
|
5349
|
+
return kind === "belongsTo";
|
|
5350
|
+
}
|
|
4847
5351
|
function filterableProperty(property, partOfArray = false, engine) {
|
|
4848
5352
|
if (!isFilterableRelation(property, engine)) return false;
|
|
4849
5353
|
if (partOfArray) return [
|
|
@@ -4974,7 +5478,8 @@ function StringNumberFilterField({ name, value, setValue, type, isArray, enumVal
|
|
|
4974
5478
|
disabled: isNullOperation,
|
|
4975
5479
|
placeholder: isNullOperation ? "null" : void 0,
|
|
4976
5480
|
onChange: (evt) => {
|
|
4977
|
-
|
|
5481
|
+
const val = type === "number" ? parseFloat(evt.target.value) : evt.target.value;
|
|
5482
|
+
updateFilter(operation, val);
|
|
4978
5483
|
},
|
|
4979
5484
|
endAdornment: internalValue !== void 0 && internalValue != null && /* @__PURE__ */ jsx(IconButton, {
|
|
4980
5485
|
onClick: (e) => updateFilter(operation, void 0),
|
|
@@ -5710,7 +6215,7 @@ var SelectableTable = function SelectableTable({ onValueChange, cellRenderer, on
|
|
|
5710
6215
|
children: /* @__PURE__ */ jsx("div", {
|
|
5711
6216
|
className: "h-full w-full flex flex-col bg-white dark:bg-surface-900",
|
|
5712
6217
|
ref,
|
|
5713
|
-
children: /* @__PURE__ */ jsx(
|
|
6218
|
+
children: /* @__PURE__ */ jsx(VirtualTable, {
|
|
5714
6219
|
data,
|
|
5715
6220
|
columns,
|
|
5716
6221
|
cellRenderer: (props) => cellRenderer(props),
|
|
@@ -5842,6 +6347,13 @@ var CollectionTableBinding = function CollectionTableBinding({ className, style,
|
|
|
5842
6347
|
}), {}) : entity;
|
|
5843
6348
|
const Builder = additionalField.Builder;
|
|
5844
6349
|
if (!Builder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
|
|
6350
|
+
const child = Builder ? /* @__PURE__ */ jsx(Builder, {
|
|
6351
|
+
entity,
|
|
6352
|
+
context
|
|
6353
|
+
}) : /* @__PURE__ */ jsx(Fragment, { children: additionalField.value?.({
|
|
6354
|
+
entity,
|
|
6355
|
+
context
|
|
6356
|
+
})?.toString() });
|
|
5845
6357
|
return /* @__PURE__ */ jsx(EntityTableCell, {
|
|
5846
6358
|
width,
|
|
5847
6359
|
size,
|
|
@@ -5858,13 +6370,7 @@ var CollectionTableBinding = function CollectionTableBinding({ className, style,
|
|
|
5858
6370
|
isDragging,
|
|
5859
6371
|
isDraggable,
|
|
5860
6372
|
frozen,
|
|
5861
|
-
children: /* @__PURE__ */ jsx(ErrorBoundary, { children:
|
|
5862
|
-
entity,
|
|
5863
|
-
context
|
|
5864
|
-
}) : /* @__PURE__ */ jsx(Fragment, { children: additionalField.value?.({
|
|
5865
|
-
entity,
|
|
5866
|
-
context
|
|
5867
|
-
})?.toString() }) })
|
|
6373
|
+
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: child })
|
|
5868
6374
|
}, `additional_table_cell_${entity.id}_${column.key}`);
|
|
5869
6375
|
}, [size]);
|
|
5870
6376
|
const engine = useCollectionScope()?.engine;
|
|
@@ -6017,51 +6523,22 @@ function useSelectionController(onSelectionChange) {
|
|
|
6017
6523
|
return prev.filter((item) => !(item.id === entity.id && item.path === entity.path));
|
|
6018
6524
|
}
|
|
6019
6525
|
return prev;
|
|
6020
|
-
});
|
|
6021
|
-
}, []);
|
|
6022
|
-
const isEntitySelected = useCallback((entity) => {
|
|
6023
|
-
return Boolean(selectedEntities.find((e) => e.id === entity.id && e.path === entity.path));
|
|
6024
|
-
}, [selectedEntities]);
|
|
6025
|
-
return useMemo(() => ({
|
|
6026
|
-
selectedEntities,
|
|
6027
|
-
setSelectedEntities,
|
|
6028
|
-
isEntitySelected,
|
|
6029
|
-
toggleEntitySelection
|
|
6030
|
-
}), [
|
|
6031
|
-
selectedEntities,
|
|
6032
|
-
setSelectedEntities,
|
|
6033
|
-
isEntitySelected,
|
|
6034
|
-
toggleEntitySelection
|
|
6035
|
-
]);
|
|
6036
|
-
}
|
|
6037
|
-
//#endregion
|
|
6038
|
-
//#region src/util/resolutions.ts
|
|
6039
|
-
/**
|
|
6040
|
-
* Built-in entity views that are resolved by token name.
|
|
6041
|
-
* These are always available without needing to be registered
|
|
6042
|
-
* in the customization controller's entityViews array.
|
|
6043
|
-
*/
|
|
6044
|
-
var BUILTIN_ENTITY_VIEWS = { "__rebase_history": {
|
|
6045
|
-
key: "__rebase_history",
|
|
6046
|
-
name: "History",
|
|
6047
|
-
tabComponent: React.createElement(HistoryIcon, { size: 20 }),
|
|
6048
|
-
position: "end"
|
|
6049
|
-
} };
|
|
6050
|
-
function resolveEntityView(entityView, contextEntityViews) {
|
|
6051
|
-
if (typeof entityView === "string") return BUILTIN_ENTITY_VIEWS[entityView] ?? contextEntityViews?.find((entry) => entry.key === entityView);
|
|
6052
|
-
else return entityView;
|
|
6053
|
-
}
|
|
6054
|
-
function resolveEntityAction(entityAction, contextEntityActions) {
|
|
6055
|
-
if (typeof entityAction === "string") return contextEntityActions?.find((entry) => entry.key === entityAction);
|
|
6056
|
-
else return entityAction;
|
|
6057
|
-
}
|
|
6058
|
-
function resolvedSelectedEntityView(customViews, customizationController, selectedTab, _canEdit) {
|
|
6059
|
-
const resolvedEntityViews = customViews ? customViews.map((e) => resolveEntityView(e, customizationController.entityViews)).filter(Boolean).filter((e) => e.key !== "__rebase_history") : [];
|
|
6060
|
-
return {
|
|
6061
|
-
resolvedEntityViews,
|
|
6062
|
-
selectedEntityView: resolvedEntityViews.find((e) => e.key === selectedTab),
|
|
6063
|
-
selectedSecondaryForm: customViews && resolvedEntityViews.filter((e) => e.includeActions).find((e) => e.key === selectedTab)
|
|
6064
|
-
};
|
|
6526
|
+
});
|
|
6527
|
+
}, []);
|
|
6528
|
+
const isEntitySelected = useCallback((entity) => {
|
|
6529
|
+
return Boolean(selectedEntities.find((e) => e.id === entity.id && e.path === entity.path));
|
|
6530
|
+
}, [selectedEntities]);
|
|
6531
|
+
return useMemo(() => ({
|
|
6532
|
+
selectedEntities,
|
|
6533
|
+
setSelectedEntities,
|
|
6534
|
+
isEntitySelected,
|
|
6535
|
+
toggleEntitySelection
|
|
6536
|
+
}), [
|
|
6537
|
+
selectedEntities,
|
|
6538
|
+
setSelectedEntities,
|
|
6539
|
+
isEntitySelected,
|
|
6540
|
+
toggleEntitySelection
|
|
6541
|
+
]);
|
|
6065
6542
|
}
|
|
6066
6543
|
//#endregion
|
|
6067
6544
|
//#region src/components/CollectionViewBinding/hooks/useCollectionInlineEditor.ts
|
|
@@ -6233,6 +6710,35 @@ function mergeEntityActions(currentActions, newActions) {
|
|
|
6233
6710
|
});
|
|
6234
6711
|
return updatedActions;
|
|
6235
6712
|
}
|
|
6713
|
+
//#endregion
|
|
6714
|
+
//#region src/util/resolutions.ts
|
|
6715
|
+
/**
|
|
6716
|
+
* Built-in entity views that are resolved by token name.
|
|
6717
|
+
* These are always available without needing to be registered
|
|
6718
|
+
* in the customization controller's entityViews array.
|
|
6719
|
+
*/
|
|
6720
|
+
var BUILTIN_ENTITY_VIEWS = { "__rebase_history": {
|
|
6721
|
+
key: "__rebase_history",
|
|
6722
|
+
name: "History",
|
|
6723
|
+
tabComponent: React.createElement(HistoryIcon, { size: 20 }),
|
|
6724
|
+
position: "end"
|
|
6725
|
+
} };
|
|
6726
|
+
function resolveEntityView(entityView, contextEntityViews) {
|
|
6727
|
+
if (typeof entityView === "string") return BUILTIN_ENTITY_VIEWS[entityView] ?? contextEntityViews?.find((entry) => entry.key === entityView);
|
|
6728
|
+
else return entityView;
|
|
6729
|
+
}
|
|
6730
|
+
function resolveEntityAction(entityAction, contextEntityActions) {
|
|
6731
|
+
if (typeof entityAction === "string") return contextEntityActions?.find((entry) => entry.key === entityAction);
|
|
6732
|
+
else return entityAction;
|
|
6733
|
+
}
|
|
6734
|
+
function resolvedSelectedEntityView(customViews, customizationController, selectedTab, _canEdit) {
|
|
6735
|
+
const resolvedEntityViews = customViews ? customViews.map((e) => resolveEntityView(e, customizationController.entityViews)).filter(Boolean).filter((e) => e.key !== "__rebase_history") : [];
|
|
6736
|
+
return {
|
|
6737
|
+
resolvedEntityViews,
|
|
6738
|
+
selectedEntityView: resolvedEntityViews.find((e) => e.key === selectedTab),
|
|
6739
|
+
selectedSecondaryForm: customViews && resolvedEntityViews.filter((e) => e.includeActions).find((e) => e.key === selectedTab)
|
|
6740
|
+
};
|
|
6741
|
+
}
|
|
6236
6742
|
var BreadcrumbContext = React.createContext({
|
|
6237
6743
|
breadcrumbs: [],
|
|
6238
6744
|
set: () => {},
|
|
@@ -6315,7 +6821,10 @@ function SearchIconsView({ selectedIcon = "", onIconSelected }) {
|
|
|
6315
6821
|
const [query, setQuery] = React.useState("");
|
|
6316
6822
|
const updateSearchResults = React.useMemo(() => debounce((value) => {
|
|
6317
6823
|
if (!value || value === "") setKeys(null);
|
|
6318
|
-
else
|
|
6824
|
+
else {
|
|
6825
|
+
const limited = iconsSearch.search(value).slice(0, 50);
|
|
6826
|
+
setKeys(limited.map((e) => e.item.key));
|
|
6827
|
+
}
|
|
6319
6828
|
}, UPDATE_SEARCH_INDEX_WAIT_MS), []);
|
|
6320
6829
|
React.useEffect(() => {
|
|
6321
6830
|
updateSearchResults(query);
|
|
@@ -6516,15 +7025,18 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
|
|
|
6516
7025
|
}, []);
|
|
6517
7026
|
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), useSensor(KeyboardSensor, {}));
|
|
6518
7027
|
useEffect(() => {
|
|
6519
|
-
if (hasValue && value && value.length !== internalIds.length)
|
|
6520
|
-
const
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
7028
|
+
if (hasValue && value && value.length !== internalIds.length) {
|
|
7029
|
+
const newInternalIds = value.map((v, index) => {
|
|
7030
|
+
const hashValue = (getHashValue(v) ?? String(index)) + index;
|
|
7031
|
+
if (hashValue in internalIdsRef.current) return internalIdsRef.current[hashValue];
|
|
7032
|
+
else {
|
|
7033
|
+
const newInternalId = getRandomId$1();
|
|
7034
|
+
internalIdsRef.current[hashValue] = newInternalId;
|
|
7035
|
+
return newInternalId;
|
|
7036
|
+
}
|
|
7037
|
+
});
|
|
7038
|
+
setInternalIds(newInternalIds);
|
|
7039
|
+
}
|
|
6528
7040
|
}, [
|
|
6529
7041
|
hasValue,
|
|
6530
7042
|
internalIds.length,
|
|
@@ -6585,7 +7097,8 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
|
|
|
6585
7097
|
const oldIndex = internalIds.indexOf(active.id);
|
|
6586
7098
|
const newIndex = internalIds.indexOf(over.id);
|
|
6587
7099
|
if (oldIndex === -1 || newIndex === -1) return;
|
|
6588
|
-
|
|
7100
|
+
const newIds = arrayMove$1(internalIds, oldIndex, newIndex);
|
|
7101
|
+
setInternalIds(newIds);
|
|
6589
7102
|
onValueChange(arrayMove$1(value ?? [], oldIndex, newIndex));
|
|
6590
7103
|
};
|
|
6591
7104
|
return sortable ? /* @__PURE__ */ jsx(DndContext, {
|
|
@@ -6921,6 +7434,7 @@ var useImportConfig = () => {
|
|
|
6921
7434
|
//#endregion
|
|
6922
7435
|
//#region src/data_import/utils/get_import_inference_type.ts
|
|
6923
7436
|
function getInferenceType(value) {
|
|
7437
|
+
if (value === null || value === void 0) return "string";
|
|
6924
7438
|
if (typeof value === "number") return "number";
|
|
6925
7439
|
else if (typeof value === "string") return "string";
|
|
6926
7440
|
else if (typeof value === "boolean") return "boolean";
|
|
@@ -7308,8 +7822,9 @@ var ConfigControllerProvider = React.memo(function ConfigControllerProvider({ ch
|
|
|
7308
7822
|
editedCollectionId
|
|
7309
7823
|
});
|
|
7310
7824
|
const namespace = propertyKey && propertyKey.includes(".") ? propertyKey.substring(0, propertyKey.lastIndexOf(".")) : void 0;
|
|
7825
|
+
const propertyKeyWithoutNamespace = propertyKey && propertyKey.includes(".") ? propertyKey.substring(propertyKey.lastIndexOf(".") + 1) : propertyKey;
|
|
7311
7826
|
setCurrentPropertyDialog({
|
|
7312
|
-
propertyKey:
|
|
7827
|
+
propertyKey: propertyKeyWithoutNamespace,
|
|
7313
7828
|
property,
|
|
7314
7829
|
namespace,
|
|
7315
7830
|
currentPropertiesOrder,
|
|
@@ -7365,7 +7880,10 @@ var ConfigControllerProvider = React.memo(function ConfigControllerProvider({ ch
|
|
|
7365
7880
|
onFetchTableMetadata,
|
|
7366
7881
|
handleClose: (collection) => {
|
|
7367
7882
|
if (currentDialog?.redirect) {
|
|
7368
|
-
if (collection && currentDialog?.isNewCollection && !currentDialog.parentCollectionSlugs.length)
|
|
7883
|
+
if (collection && currentDialog?.isNewCollection && !currentDialog.parentCollectionSlugs.length) {
|
|
7884
|
+
const url = urlController.buildUrlCollectionPath(collection.slug);
|
|
7885
|
+
navigate(url);
|
|
7886
|
+
}
|
|
7369
7887
|
}
|
|
7370
7888
|
setCurrentDialog(void 0);
|
|
7371
7889
|
}
|
|
@@ -7532,8 +8050,8 @@ function EditorCollectionAction({ path, parentCollectionSlugs, parentEntityIds,
|
|
|
7532
8050
|
}
|
|
7533
8051
|
//#endregion
|
|
7534
8052
|
//#region src/components/CollectionViewBinding/CollectionViewActions.tsx
|
|
7535
|
-
var ImportCollectionAction = lazy(() => import("./import-
|
|
7536
|
-
var ExportCollectionAction = lazy(() => import("./export-
|
|
8053
|
+
var ImportCollectionAction = lazy(() => import("./import-Cwzf4RW5.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
|
|
8054
|
+
var ExportCollectionAction = lazy(() => import("./export-B8DIn-u8.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
|
|
7537
8055
|
function CollectionViewActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, onNewClick, onAddExistingClick, onMultipleDeleteClick, selectionEnabled, path, selectionController, tableController, collectionEntitiesCount, compact, children, openNewDocument }) {
|
|
7538
8056
|
const context = useAdminContext();
|
|
7539
8057
|
const { canCreate, canDelete } = usePermissions();
|
|
@@ -7578,8 +8096,9 @@ function CollectionViewActions({ collection, relativePath, parentCollectionSlugs
|
|
|
7578
8096
|
})
|
|
7579
8097
|
}));
|
|
7580
8098
|
const multipleDeleteEnabled = canDelete(collection, path, null);
|
|
8099
|
+
const hasSelection = Boolean(selectedEntities?.length);
|
|
7581
8100
|
let multipleDeleteButton;
|
|
7582
|
-
{
|
|
8101
|
+
if (hasSelection) {
|
|
7583
8102
|
const button = largeLayout && !compact ? /* @__PURE__ */ jsxs(Button, {
|
|
7584
8103
|
variant: "text",
|
|
7585
8104
|
disabled: !selectedEntities?.length || !multipleDeleteEnabled,
|
|
@@ -7673,7 +8192,9 @@ function relationCardinality(relation) {
|
|
|
7673
8192
|
* Pure function — call inside `useMemo`.
|
|
7674
8193
|
*/
|
|
7675
8194
|
function resolveCollectionSlotKeys(collection, authController, propertyConfigs) {
|
|
7676
|
-
const
|
|
8195
|
+
const rankedCandidates = getTitlePropertyCandidates(collection);
|
|
8196
|
+
const leadingRelationKey = getLeadingRelationTitleKey(collection);
|
|
8197
|
+
const titleKeyCandidates = leadingRelationKey ? [leadingRelationKey, ...rankedCandidates.filter((key) => key !== leadingRelationKey)] : rankedCandidates;
|
|
7677
8198
|
const titleKey = titleKeyCandidates[0];
|
|
7678
8199
|
const imageKey = getEntityImagePreviewPropertyKey(collection);
|
|
7679
8200
|
let statusKey;
|
|
@@ -7721,9 +8242,9 @@ function resolveCollectionSlotKeys(collection, authController, propertyConfigs)
|
|
|
7721
8242
|
const hasExplicitOrder = !!collection.propertiesOrder;
|
|
7722
8243
|
const relationKeys = [];
|
|
7723
8244
|
if (!hasExplicitOrder) {
|
|
7724
|
-
for (const [key, prop] of Object.entries(collection.properties)) if (prop.type === "relation") relationKeys.push(key);
|
|
8245
|
+
for (const [key, prop] of Object.entries(collection.properties)) if (prop.type === "relation" && key !== titleKey) relationKeys.push(key);
|
|
7725
8246
|
}
|
|
7726
|
-
const excludeKeys = new Set([
|
|
8247
|
+
const excludeKeys = /* @__PURE__ */ new Set([
|
|
7727
8248
|
titleKey,
|
|
7728
8249
|
imageKey,
|
|
7729
8250
|
statusKey,
|
|
@@ -7882,9 +8403,7 @@ function resolveEntitySlots(entity, collection, slotKeys) {
|
|
|
7882
8403
|
}
|
|
7883
8404
|
}
|
|
7884
8405
|
if (items.length > 0) {
|
|
7885
|
-
const
|
|
7886
|
-
const chipColors = Object.values(CHIP_COLORS);
|
|
7887
|
-
const colorScheme = chipColors[relIndex % chipColors.length];
|
|
8406
|
+
const colorScheme = CHIP_COLORS[CHIP_SEED_KEYS[relationKeys.indexOf(relKey) % CHIP_SEED_KEYS.length]];
|
|
7888
8407
|
relations.push({
|
|
7889
8408
|
property: prop,
|
|
7890
8409
|
propertyKey: relKey,
|
|
@@ -8356,7 +8875,7 @@ function CollectionListViewBinding({ collection, tableController, onEntityClick,
|
|
|
8356
8875
|
const showStatus = statusPropertyKey && size !== "xs" && size !== "s";
|
|
8357
8876
|
const subtitleKey = previewKeys.length > 0 ? previewKeys[0] : void 0;
|
|
8358
8877
|
const allKeys = resolvedCollection.propertiesOrder || Object.keys(resolvedCollection.properties);
|
|
8359
|
-
const usedKeys = new Set([
|
|
8878
|
+
const usedKeys = /* @__PURE__ */ new Set([
|
|
8360
8879
|
titlePropertyKey,
|
|
8361
8880
|
imagePropertyKey,
|
|
8362
8881
|
statusPropertyKey,
|
|
@@ -8948,44 +9467,228 @@ var PropertyCollectionView = ({ data, properties, baseKey = "", suppressHeader =
|
|
|
8948
9467
|
};
|
|
8949
9468
|
//#endregion
|
|
8950
9469
|
//#region src/components/EntityViewBinding.tsx
|
|
9470
|
+
/**
|
|
9471
|
+
* The read-only rendering of a record's values.
|
|
9472
|
+
*
|
|
9473
|
+
* This used to prepend a synthetic `Id` row of its own. Between that row, the
|
|
9474
|
+
* collection's own id property, and the `path/id` chip above it, the same UUID
|
|
9475
|
+
* appeared three times — and in the ~340px split pane each copy wrapped over
|
|
9476
|
+
* four lines, so most of the first screen was one id repeated. The id is now a
|
|
9477
|
+
* copyable chip in the identity bar, once.
|
|
9478
|
+
*/
|
|
8951
9479
|
function EntityViewBinding({ entity, collection, path, className }) {
|
|
8952
9480
|
const customizationController = useCustomizationController();
|
|
8953
|
-
const properties =
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
9481
|
+
const properties = React.useMemo(() => {
|
|
9482
|
+
const entries = Object.entries(collection.properties ?? {});
|
|
9483
|
+
const idKeys = entries.filter(([, p]) => p && "isId" in p && p.isId);
|
|
9484
|
+
if (idKeys.length !== 1) return collection.properties;
|
|
9485
|
+
return Object.fromEntries(entries.filter(([key]) => key !== idKeys[0][0]));
|
|
9486
|
+
}, [collection.properties]);
|
|
9487
|
+
const externalLink = customizationController?.entityLinkBuilder?.({ entity });
|
|
9488
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9489
|
+
className: "w-full " + (className ?? ""),
|
|
9490
|
+
children: [externalLink && /* @__PURE__ */ jsx("div", {
|
|
9491
|
+
className: "flex justify-end mb-2",
|
|
9492
|
+
children: /* @__PURE__ */ jsx(Tooltip, {
|
|
9493
|
+
title: "Open in the live site",
|
|
9494
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
9495
|
+
href: externalLink,
|
|
9496
|
+
rel: "noopener noreferrer",
|
|
9497
|
+
target: "_blank",
|
|
9498
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9499
|
+
size: "small",
|
|
9500
|
+
children: /* @__PURE__ */ jsx(ExternalLinkIcon, {})
|
|
8968
9501
|
})
|
|
8969
|
-
})
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
9502
|
+
})
|
|
9503
|
+
})
|
|
9504
|
+
}), /* @__PURE__ */ jsx(PropertyCollectionView, {
|
|
9505
|
+
data: entity.values,
|
|
9506
|
+
properties,
|
|
9507
|
+
size: "medium"
|
|
9508
|
+
})]
|
|
9509
|
+
});
|
|
9510
|
+
}
|
|
9511
|
+
//#endregion
|
|
9512
|
+
//#region src/components/EntityIdentityBar.tsx
|
|
9513
|
+
/**
|
|
9514
|
+
* The persistent header of an entity view: who this record is, and what you can
|
|
9515
|
+
* do to it.
|
|
9516
|
+
*
|
|
9517
|
+
* The title and the id used to live inside the scrolling form, above 72px of
|
|
9518
|
+
* empty space — 219px of chrome that carried nothing and scrolled away the
|
|
9519
|
+
* moment you touched the wheel, leaving no indication of which record you were
|
|
9520
|
+
* editing. They live here instead, and the left half of a bar that was
|
|
9521
|
+
* previously empty now does the work.
|
|
9522
|
+
*/
|
|
9523
|
+
function EntityIdentityBar({ collection, title, entityId, status, dirty, saving, onSave, onDiscard, saveDisabled, hasErrors, onSaveAndClose, onBack, onInspect, onViewHistory, recordActions, pluginActions, trailing, leading }) {
|
|
9524
|
+
const { t } = useTranslation();
|
|
9525
|
+
const saveLabel = status === "existing" ? t("save") : status === "copy" ? t("create_copy") : t("create");
|
|
9526
|
+
const closeLabel = status === "existing" ? t("save_and_close") : status === "copy" ? t("create_copy_and_close") : t("create_and_close");
|
|
9527
|
+
const hasMenu = Boolean(recordActions) || Boolean(onInspect) || Boolean(onViewHistory);
|
|
9528
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9529
|
+
className: cls("h-[52px] shrink-0 flex items-center gap-2 pl-1.5 pr-2 border-b", "bg-surface-50 dark:bg-surface-900", defaultBorderMixin),
|
|
9530
|
+
children: [
|
|
9531
|
+
leading,
|
|
9532
|
+
onBack && /* @__PURE__ */ jsx(Tooltip, {
|
|
9533
|
+
title: t("back"),
|
|
9534
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9535
|
+
size: "small",
|
|
9536
|
+
onClick: onBack,
|
|
9537
|
+
"aria-label": t("back"),
|
|
9538
|
+
children: /* @__PURE__ */ jsx(ArrowLeftIcon, { size: iconSize.smallest })
|
|
9539
|
+
})
|
|
9540
|
+
}),
|
|
9541
|
+
/* @__PURE__ */ jsxs(Typography, {
|
|
9542
|
+
variant: "caption",
|
|
9543
|
+
className: "text-text-disabled dark:text-text-disabled-dark whitespace-nowrap hidden sm:block",
|
|
9544
|
+
children: [collection.name, "\xA0/"]
|
|
9545
|
+
}),
|
|
9546
|
+
/* @__PURE__ */ jsx("span", {
|
|
9547
|
+
className: "font-headers font-semibold text-[15px] tracking-tight truncate min-w-0",
|
|
9548
|
+
title,
|
|
9549
|
+
children: title
|
|
9550
|
+
}),
|
|
9551
|
+
entityId !== void 0 && /* @__PURE__ */ jsx(IdChip, { value: String(entityId) }),
|
|
9552
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
9553
|
+
onSave && /* @__PURE__ */ jsx(SaveState, {
|
|
9554
|
+
dirty,
|
|
9555
|
+
saving,
|
|
9556
|
+
status,
|
|
9557
|
+
t
|
|
9558
|
+
}),
|
|
9559
|
+
pluginActions,
|
|
9560
|
+
onDiscard && dirty && !saving && /* @__PURE__ */ jsx(Button, {
|
|
9561
|
+
variant: "text",
|
|
9562
|
+
size: "small",
|
|
9563
|
+
onClick: onDiscard,
|
|
9564
|
+
children: status === "existing" ? t("discard") : t("clear")
|
|
9565
|
+
}),
|
|
9566
|
+
onSave && /* @__PURE__ */ jsx(Tooltip, {
|
|
9567
|
+
title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
|
|
9568
|
+
children: /* @__PURE__ */ jsx(LoadingButton, {
|
|
9569
|
+
variant: onSaveAndClose ? "text" : "filled",
|
|
9570
|
+
color: "primary",
|
|
9571
|
+
size: "small",
|
|
9572
|
+
loading: saving && !onSaveAndClose,
|
|
9573
|
+
disabled: saveDisabled,
|
|
9574
|
+
onClick: onSave,
|
|
9575
|
+
children: saveLabel
|
|
9576
|
+
})
|
|
9577
|
+
}),
|
|
9578
|
+
onSaveAndClose && /* @__PURE__ */ jsx(LoadingButton, {
|
|
9579
|
+
variant: "filled",
|
|
9580
|
+
color: "primary",
|
|
9581
|
+
size: "small",
|
|
9582
|
+
loading: saving,
|
|
9583
|
+
disabled: saveDisabled,
|
|
9584
|
+
onClick: onSaveAndClose,
|
|
9585
|
+
children: closeLabel
|
|
9586
|
+
}),
|
|
9587
|
+
hasMenu && /* @__PURE__ */ jsxs(Menu, {
|
|
9588
|
+
align: "end",
|
|
9589
|
+
trigger: /* @__PURE__ */ jsx(IconButton, {
|
|
9590
|
+
size: "small",
|
|
9591
|
+
"aria-label": t("more_actions") ?? "More actions",
|
|
9592
|
+
children: /* @__PURE__ */ jsx(MoreVerticalIcon, { size: iconSize.smallest })
|
|
9593
|
+
}),
|
|
9594
|
+
children: [
|
|
9595
|
+
recordActions,
|
|
9596
|
+
recordActions && (onInspect || onViewHistory) && /* @__PURE__ */ jsx(Separator, { orientation: "horizontal" }),
|
|
9597
|
+
onViewHistory && /* @__PURE__ */ jsxs(MenuItem, {
|
|
9598
|
+
onClick: onViewHistory,
|
|
9599
|
+
children: [/* @__PURE__ */ jsx(HistoryIcon, { size: iconSize.smallest }), t("record_history") ?? "History"]
|
|
9600
|
+
}),
|
|
9601
|
+
onInspect && /* @__PURE__ */ jsxs(MenuItem, {
|
|
9602
|
+
onClick: onInspect,
|
|
9603
|
+
children: [/* @__PURE__ */ jsx(CodeIcon, { size: iconSize.smallest }), t("inspect_record") ?? "Inspect record"]
|
|
8982
9604
|
})
|
|
8983
|
-
|
|
8984
|
-
}),
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
9605
|
+
]
|
|
9606
|
+
}),
|
|
9607
|
+
trailing
|
|
9608
|
+
]
|
|
9609
|
+
});
|
|
9610
|
+
}
|
|
9611
|
+
/**
|
|
9612
|
+
* The words the unlabelled floating circle never said.
|
|
9613
|
+
*
|
|
9614
|
+
* That indicator was a ✓ / pencil / spinner chip stuck to the top-right of the
|
|
9615
|
+
* scroll area in an `h-0 overflow-visible` container, so it drifted across
|
|
9616
|
+
* field values as you scrolled — and it duplicated what the Save button was
|
|
9617
|
+
* already able to tell you.
|
|
9618
|
+
*/
|
|
9619
|
+
function SaveState({ dirty, saving, status, t }) {
|
|
9620
|
+
if (saving) return /* @__PURE__ */ jsx(StateText, { children: t("saving") });
|
|
9621
|
+
if (dirty) return /* @__PURE__ */ jsx(StateText, { children: t("unsaved_changes_title") });
|
|
9622
|
+
if (status === "existing") return /* @__PURE__ */ jsx(StateText, { children: t("entity_saved") ?? "Saved" });
|
|
9623
|
+
return null;
|
|
9624
|
+
}
|
|
9625
|
+
function StateText({ children }) {
|
|
9626
|
+
return /* @__PURE__ */ jsx("span", {
|
|
9627
|
+
className: "text-xs text-text-disabled dark:text-text-disabled-dark whitespace-nowrap hidden md:inline",
|
|
9628
|
+
children
|
|
9629
|
+
});
|
|
9630
|
+
}
|
|
9631
|
+
/**
|
|
9632
|
+
* The id, as a chip you can copy rather than a full-width alert holding a UUID.
|
|
9633
|
+
*/
|
|
9634
|
+
function IdChip({ value }) {
|
|
9635
|
+
const { t } = useTranslation();
|
|
9636
|
+
const [copied, setCopied] = useState(false);
|
|
9637
|
+
const truncated = value.length > 14 ? `${value.slice(0, 8)}…${value.slice(-4)}` : value;
|
|
9638
|
+
const copy = () => {
|
|
9639
|
+
navigator.clipboard?.writeText(value).then(() => {
|
|
9640
|
+
setCopied(true);
|
|
9641
|
+
setTimeout(() => setCopied(false), 1600);
|
|
9642
|
+
}, () => void 0);
|
|
9643
|
+
};
|
|
9644
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
9645
|
+
title: copied ? t("copied") : value,
|
|
9646
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
9647
|
+
type: "button",
|
|
9648
|
+
onClick: copy,
|
|
9649
|
+
"aria-label": `${t("copy_id")} ${value}`,
|
|
9650
|
+
className: cls("hidden md:inline-flex items-center gap-1.5 shrink-0 px-2 py-0.5 rounded-md", "font-mono text-[11px] text-text-secondary dark:text-text-secondary-dark", "bg-surface-accent-200/50 dark:bg-white/[0.055]", "hover:bg-surface-accent-200/75 dark:hover:bg-white/[0.09] transition-colors"),
|
|
9651
|
+
children: [truncated, copied ? /* @__PURE__ */ jsx(CheckIcon, { size: iconSize.smallest }) : /* @__PURE__ */ jsx(CopyIcon, { size: iconSize.smallest })]
|
|
9652
|
+
})
|
|
9653
|
+
});
|
|
9654
|
+
}
|
|
9655
|
+
//#endregion
|
|
9656
|
+
//#region src/components/CollectionViewBinding/SplitViewContext.tsx
|
|
9657
|
+
var SplitViewContext = React.createContext(void 0);
|
|
9658
|
+
/**
|
|
9659
|
+
* The enclosing split view, or `undefined` when the component is not rendered
|
|
9660
|
+
* inside one (a side panel, a dialog, a full-screen route).
|
|
9661
|
+
*/
|
|
9662
|
+
function useSplitView() {
|
|
9663
|
+
return useContext(SplitViewContext);
|
|
9664
|
+
}
|
|
9665
|
+
function SplitViewProvider({ value, children }) {
|
|
9666
|
+
return /* @__PURE__ */ jsx(SplitViewContext.Provider, {
|
|
9667
|
+
value,
|
|
9668
|
+
children
|
|
9669
|
+
});
|
|
9670
|
+
}
|
|
9671
|
+
//#endregion
|
|
9672
|
+
//#region src/components/CollectionViewBinding/SplitListCloseButton.tsx
|
|
9673
|
+
/**
|
|
9674
|
+
* Closes the list of a split view, leaving the open record on its own.
|
|
9675
|
+
*
|
|
9676
|
+
* It leads the record's own app bar, where the breadcrumb starts — this is the
|
|
9677
|
+
* entity pane's old "Open full screen" button, moved to that edge and wearing an
|
|
9678
|
+
* X: closing the list and opening the record full screen are the same thing, so
|
|
9679
|
+
* there is one control and one route (`#full`) rather than two.
|
|
9680
|
+
*/
|
|
9681
|
+
function SplitListCloseButton() {
|
|
9682
|
+
const { t } = useTranslation();
|
|
9683
|
+
const splitView = useSplitView();
|
|
9684
|
+
if (!splitView?.detailOpen) return null;
|
|
9685
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
9686
|
+
title: t("hide_list"),
|
|
9687
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9688
|
+
size: "small",
|
|
9689
|
+
onClick: splitView.openFullScreen,
|
|
9690
|
+
"aria-label": t("hide_list"),
|
|
9691
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.smallest })
|
|
8989
9692
|
})
|
|
8990
9693
|
});
|
|
8991
9694
|
}
|
|
@@ -9018,15 +9721,96 @@ function JsonPreviewBinding({ values }) {
|
|
|
9018
9721
|
});
|
|
9019
9722
|
}
|
|
9020
9723
|
//#endregion
|
|
9724
|
+
//#region src/components/EntityInspector.tsx
|
|
9725
|
+
var EntityHistoryView = lazy(() => import("./history-ClwDEtqN.js").then((m) => ({ default: m.EntityHistoryView })));
|
|
9726
|
+
/**
|
|
9727
|
+
* Raw values and revision history, as an inspector rather than as tabs.
|
|
9728
|
+
*
|
|
9729
|
+
* These were the first two entries in the entity tab strip — two icon-only tabs
|
|
9730
|
+
* with no label and no tooltip, sitting *before* the record you opened the page
|
|
9731
|
+
* to edit. They are developer tools, not places the record lives, so they belong
|
|
9732
|
+
* behind one affordance in the bar; a content editor should never meet `<>`.
|
|
9733
|
+
*
|
|
9734
|
+
* The panel starts below the identity bar deliberately: you keep seeing which
|
|
9735
|
+
* record you are inspecting while you inspect it.
|
|
9736
|
+
*/
|
|
9737
|
+
function EntityInspector({ tab, onTabChange, onClose, collection, entity, formContext, values, includeHistory }) {
|
|
9738
|
+
const { t } = useTranslation();
|
|
9739
|
+
const open = tab !== null;
|
|
9740
|
+
useEffect(() => {
|
|
9741
|
+
if (!open) return;
|
|
9742
|
+
const onKeyDown = (e) => {
|
|
9743
|
+
if (e.key === "Escape") {
|
|
9744
|
+
e.stopPropagation();
|
|
9745
|
+
onClose();
|
|
9746
|
+
}
|
|
9747
|
+
};
|
|
9748
|
+
window.addEventListener("keydown", onKeyDown);
|
|
9749
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
9750
|
+
}, [open, onClose]);
|
|
9751
|
+
if (!open) return null;
|
|
9752
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9753
|
+
className: "absolute inset-x-0 bottom-0 top-[52px] z-30 flex justify-end",
|
|
9754
|
+
role: "dialog",
|
|
9755
|
+
"aria-label": t("inspect_record") ?? "Inspect record",
|
|
9756
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
9757
|
+
className: "absolute inset-0 bg-black/25",
|
|
9758
|
+
onClick: onClose,
|
|
9759
|
+
"aria-hidden": true
|
|
9760
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
9761
|
+
className: cls("relative flex flex-col w-full max-w-md border-l shadow-xl", "bg-white dark:bg-surface-800", defaultBorderMixin),
|
|
9762
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
9763
|
+
className: cls("h-11 shrink-0 flex items-center gap-2 pl-4 pr-1.5 border-b", defaultBorderMixin),
|
|
9764
|
+
children: [
|
|
9765
|
+
/* @__PURE__ */ jsx("span", {
|
|
9766
|
+
className: "text-sm font-semibold",
|
|
9767
|
+
children: t("inspect") ?? "Inspect"
|
|
9768
|
+
}),
|
|
9769
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
9770
|
+
includeHistory ? /* @__PURE__ */ jsxs(Tabs, {
|
|
9771
|
+
value: tab ?? "json",
|
|
9772
|
+
className: "!w-fit",
|
|
9773
|
+
onValueChange: (v) => onTabChange(v),
|
|
9774
|
+
children: [/* @__PURE__ */ jsx(Tab, {
|
|
9775
|
+
value: "json",
|
|
9776
|
+
className: "text-xs",
|
|
9777
|
+
children: t("json")
|
|
9778
|
+
}), /* @__PURE__ */ jsx(Tab, {
|
|
9779
|
+
value: "history",
|
|
9780
|
+
className: "text-xs",
|
|
9781
|
+
children: t("record_history") ?? "History"
|
|
9782
|
+
})]
|
|
9783
|
+
}) : null,
|
|
9784
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
9785
|
+
size: "small",
|
|
9786
|
+
onClick: onClose,
|
|
9787
|
+
"aria-label": t("close_inspector") ?? "Close inspector",
|
|
9788
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.smallest })
|
|
9789
|
+
})
|
|
9790
|
+
]
|
|
9791
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
9792
|
+
className: "flex-1 min-h-0 overflow-auto",
|
|
9793
|
+
children: /* @__PURE__ */ jsxs(ErrorBoundary, { children: [tab === "json" && /* @__PURE__ */ jsx(JsonPreviewBinding, { values: values ?? entity?.values ?? {} }), tab === "history" && includeHistory && formContext && /* @__PURE__ */ jsx(Suspense, {
|
|
9794
|
+
fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
|
|
9795
|
+
children: /* @__PURE__ */ jsx(EntityHistoryView, {
|
|
9796
|
+
collection,
|
|
9797
|
+
entity,
|
|
9798
|
+
formContext,
|
|
9799
|
+
modifiedValues: values ?? entity?.values
|
|
9800
|
+
})
|
|
9801
|
+
})] })
|
|
9802
|
+
})]
|
|
9803
|
+
})]
|
|
9804
|
+
});
|
|
9805
|
+
}
|
|
9806
|
+
//#endregion
|
|
9021
9807
|
//#region src/util/view_constants.ts
|
|
9022
9808
|
/**
|
|
9023
9809
|
* Shared tab value constants used by EditViewBinding and DetailViewBinding.
|
|
9024
9810
|
*/
|
|
9025
9811
|
var MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
|
|
9026
|
-
var JSON_TAB_VALUE = "__json";
|
|
9027
9812
|
//#endregion
|
|
9028
9813
|
//#region src/components/DetailViewBinding.tsx
|
|
9029
|
-
var EntityHistoryView$1 = lazy(() => import("./history-g9688IaK.js").then((m) => ({ default: m.EntityHistoryView })));
|
|
9030
9814
|
function DetailViewBinding({ entityId, ...props }) {
|
|
9031
9815
|
const { entity, dataLoading, dataLoadingError } = useFetch({
|
|
9032
9816
|
path: props.path,
|
|
@@ -9050,7 +9834,7 @@ function DetailViewBinding({ entityId, ...props }) {
|
|
|
9050
9834
|
children: content
|
|
9051
9835
|
});
|
|
9052
9836
|
}
|
|
9053
|
-
function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onTabChange, onEditClick, entity, dataLoading, layout = "full_screen", barActions }) {
|
|
9837
|
+
function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onTabChange, onEditClick, entity, dataLoading, layout = "full_screen", barActions, barActionsStart }) {
|
|
9054
9838
|
const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
|
|
9055
9839
|
const { t } = useTranslation();
|
|
9056
9840
|
const context = useRebaseContext();
|
|
@@ -9093,22 +9877,20 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
9093
9877
|
const customViewsCount = customViews?.length ?? 0;
|
|
9094
9878
|
const includeJsonView = collection.includeJsonView === void 0 ? true : collection.includeJsonView;
|
|
9095
9879
|
const includeHistoryView = Boolean(collection.history);
|
|
9096
|
-
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0
|
|
9880
|
+
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
|
|
9881
|
+
const [inspectorTab, setInspectorTab] = useState(null);
|
|
9882
|
+
const legacyInspectorTab = selectedTab === "__json" ? "json" : selectedTab === "__rebase_history" ? "history" : void 0;
|
|
9883
|
+
useEffect(() => {
|
|
9884
|
+
if (legacyInspectorTab) setInspectorTab(legacyInspectorTab);
|
|
9885
|
+
}, [legacyInspectorTab]);
|
|
9097
9886
|
const { resolvedEntityViews } = resolvedSelectedEntityView(customViews, customizationController, void 0, canEdit);
|
|
9098
9887
|
const activeTab = useMemo(() => {
|
|
9099
|
-
return new Set([
|
|
9888
|
+
return /* @__PURE__ */ new Set([
|
|
9100
9889
|
"__main_##Q$SC^#S6",
|
|
9101
|
-
...includeJsonView ? ["__json"] : [],
|
|
9102
|
-
...includeHistoryView ? ["__rebase_history"] : [],
|
|
9103
9890
|
...resolvedEntityViews.map((v) => v.key),
|
|
9104
9891
|
...subcollections.map((s) => s.slug)
|
|
9105
9892
|
]);
|
|
9106
|
-
}, [
|
|
9107
|
-
includeJsonView,
|
|
9108
|
-
includeHistoryView,
|
|
9109
|
-
resolvedEntityViews,
|
|
9110
|
-
subcollections
|
|
9111
|
-
]).has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
|
|
9893
|
+
}, [resolvedEntityViews, subcollections]).has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
|
|
9112
9894
|
const { selectedEntityView } = resolvedSelectedEntityView(customViews, customizationController, activeTab, canEdit);
|
|
9113
9895
|
const mainViewVisible = activeTab === MAIN_TAB_VALUE;
|
|
9114
9896
|
const mountedTabsRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -9198,25 +9980,6 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
9198
9980
|
}, `custom_view_${customView.key}`);
|
|
9199
9981
|
}).filter(Boolean);
|
|
9200
9982
|
const globalLoading = dataLoading && !usedEntity;
|
|
9201
|
-
const jsonTabMounted = mountedTabsRef.current.has(JSON_TAB_VALUE);
|
|
9202
|
-
const jsonView = activeTab === "__json" || jsonTabMounted ? /* @__PURE__ */ jsx("div", {
|
|
9203
|
-
className: cls("relative flex-1 h-full overflow-auto w-full", { "hidden": activeTab !== JSON_TAB_VALUE }),
|
|
9204
|
-
role: "tabpanel",
|
|
9205
|
-
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(JsonPreviewBinding, { values: usedEntity?.values ?? {} }) })
|
|
9206
|
-
}, "json_view") : null;
|
|
9207
|
-
const historyView = includeHistoryView && activeTab === "__rebase_history" ? /* @__PURE__ */ jsx("div", {
|
|
9208
|
-
className: "relative flex-1 h-full overflow-auto w-full",
|
|
9209
|
-
role: "tabpanel",
|
|
9210
|
-
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(Suspense, {
|
|
9211
|
-
fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
|
|
9212
|
-
children: /* @__PURE__ */ jsx(EntityHistoryView$1, {
|
|
9213
|
-
collection,
|
|
9214
|
-
entity: usedEntity,
|
|
9215
|
-
formContext: readOnlyFormContext,
|
|
9216
|
-
modifiedValues: usedEntity?.values
|
|
9217
|
-
})
|
|
9218
|
-
}) })
|
|
9219
|
-
}, "history_view") : null;
|
|
9220
9983
|
const subCollectionsViews = childViews && childViews.map(({ collection: subcollection }) => {
|
|
9221
9984
|
const subcollectionId = subcollection.slug;
|
|
9222
9985
|
const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(getCollectionDataPath(subcollection))}` : void 0;
|
|
@@ -9315,13 +10078,13 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
9315
10078
|
children: /* @__PURE__ */ jsx(ArrowLeftIcon, { size: iconSize.smallest })
|
|
9316
10079
|
})
|
|
9317
10080
|
}) : null;
|
|
9318
|
-
const
|
|
9319
|
-
const fullScreenButton = !barActions && (layout === "side_panel" || layout === "split" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
|
|
10081
|
+
const fullScreenButton = !barActions && (layout === "side_panel" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
|
|
9320
10082
|
title: "Open full screen",
|
|
9321
10083
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9322
10084
|
size: "small",
|
|
9323
10085
|
onClick: () => {
|
|
9324
|
-
|
|
10086
|
+
const entityUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}`);
|
|
10087
|
+
navigate(`${entityUrl}#full`);
|
|
9325
10088
|
},
|
|
9326
10089
|
children: /* @__PURE__ */ jsx(Maximize2Icon, { size: iconSize.smallest })
|
|
9327
10090
|
})
|
|
@@ -9336,59 +10099,27 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
9336
10099
|
}) : null;
|
|
9337
10100
|
const mainView = /* @__PURE__ */ jsx("div", {
|
|
9338
10101
|
className: cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", !mainViewVisible ? "hidden" : ""),
|
|
9339
|
-
children: /* @__PURE__ */
|
|
9340
|
-
className: cls("
|
|
9341
|
-
children:
|
|
9342
|
-
className: cls("flex flex-col w-full", layout === "dialog" ? "pt-4 pb-12 px-6 sm:px-8" : "pt-12 pb-16 px-4 sm:px-8 md:px-10"),
|
|
9343
|
-
children: [
|
|
9344
|
-
/* @__PURE__ */ jsxs("div", {
|
|
9345
|
-
className: "w-full flex flex-col items-start my-4 lg:my-6",
|
|
9346
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
9347
|
-
className: "flex items-center justify-between w-full",
|
|
9348
|
-
children: [/* @__PURE__ */ jsx(Typography, {
|
|
9349
|
-
className: cls("my-4 grow line-clamp-1", collection.hideIdFromForm ? "mb-6" : ""),
|
|
9350
|
-
variant: "h4",
|
|
9351
|
-
children: title
|
|
9352
|
-
}), editButton]
|
|
9353
|
-
}), /* @__PURE__ */ jsx(Alert, {
|
|
9354
|
-
color: "base",
|
|
9355
|
-
outerClassName: "w-full",
|
|
9356
|
-
size: "small",
|
|
9357
|
-
children: /* @__PURE__ */ jsxs("code", {
|
|
9358
|
-
className: "text-xs select-all text-text-secondary dark:text-text-secondary-dark",
|
|
9359
|
-
children: [
|
|
9360
|
-
usedEntity?.path ?? path,
|
|
9361
|
-
"/",
|
|
9362
|
-
entityId
|
|
9363
|
-
]
|
|
9364
|
-
})
|
|
9365
|
-
})]
|
|
9366
|
-
}),
|
|
9367
|
-
/* @__PURE__ */ jsx("div", {
|
|
9368
|
-
className: "mt-12 flex flex-col gap-8",
|
|
9369
|
-
children: propertyDetailView()
|
|
9370
|
-
}),
|
|
9371
|
-
/* @__PURE__ */ jsx("div", { className: "h-16" })
|
|
9372
|
-
]
|
|
9373
|
-
}), canEdit && onEditClick && layout === "full_screen" && /* @__PURE__ */ jsx("div", {
|
|
9374
|
-
className: cls("overflow-auto h-full hidden @6xl:flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin),
|
|
9375
|
-
children: /* @__PURE__ */ jsx(Button, {
|
|
9376
|
-
fullWidth: true,
|
|
9377
|
-
variant: "filled",
|
|
9378
|
-
color: "primary",
|
|
9379
|
-
startIcon: /* @__PURE__ */ jsx(PencilIcon, { size: iconSize.small }),
|
|
9380
|
-
onClick: onEditClick,
|
|
9381
|
-
children: t("edit_entity")
|
|
9382
|
-
})
|
|
9383
|
-
})]
|
|
10102
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
10103
|
+
className: cls("w-full max-w-3xl 2xl:max-w-4xl flex flex-col", layout === "dialog" ? "pt-5 pb-12 px-6 sm:px-8" : "pt-6 pb-16 px-5 sm:px-8"),
|
|
10104
|
+
children: propertyDetailView()
|
|
9384
10105
|
})
|
|
9385
10106
|
});
|
|
9386
10107
|
let result = /* @__PURE__ */ jsxs("div", {
|
|
9387
10108
|
className: "relative flex flex-col h-full w-full bg-white dark:bg-surface-800",
|
|
9388
10109
|
children: [
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
10110
|
+
/* @__PURE__ */ jsx(EntityIdentityBar, {
|
|
10111
|
+
collection,
|
|
10112
|
+
title,
|
|
10113
|
+
entityId,
|
|
10114
|
+
status: "existing",
|
|
10115
|
+
dirty: false,
|
|
10116
|
+
saving: false,
|
|
10117
|
+
onInspect: includeJsonView ? () => setInspectorTab("json") : void 0,
|
|
10118
|
+
onViewHistory: includeHistoryView ? () => setInspectorTab("history") : void 0,
|
|
10119
|
+
leading: /* @__PURE__ */ jsxs(Fragment, { children: [barActionsStart, layout === "split" && /* @__PURE__ */ jsx(SplitListCloseButton, {})] }),
|
|
10120
|
+
trailing: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10121
|
+
editButton,
|
|
10122
|
+
pluginActionsTop,
|
|
9392
10123
|
backToCollectionButton,
|
|
9393
10124
|
fullScreenButton,
|
|
9394
10125
|
barActions?.({
|
|
@@ -9396,51 +10127,46 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
9396
10127
|
entityId,
|
|
9397
10128
|
values: usedEntity?.values ?? {},
|
|
9398
10129
|
status: "existing"
|
|
9399
|
-
}),
|
|
9400
|
-
pluginActionsTop,
|
|
9401
|
-
hasAdditionalViews && /* @__PURE__ */ jsx("div", {
|
|
9402
|
-
className: "flex-1 flex justify-end min-w-0 shrink-0",
|
|
9403
|
-
children: /* @__PURE__ */ jsxs(Tabs, {
|
|
9404
|
-
className: "!w-fit max-w-full",
|
|
9405
|
-
value: activeTab,
|
|
9406
|
-
onValueChange: (value) => {
|
|
9407
|
-
onSideTabClick(value);
|
|
9408
|
-
},
|
|
9409
|
-
children: [
|
|
9410
|
-
includeJsonView && /* @__PURE__ */ jsx(Tab, {
|
|
9411
|
-
disabled: !hasAdditionalViews,
|
|
9412
|
-
value: "__json",
|
|
9413
|
-
className: "text-sm",
|
|
9414
|
-
children: /* @__PURE__ */ jsx(CodeIcon, { size: iconSize.smallest })
|
|
9415
|
-
}),
|
|
9416
|
-
includeHistoryView && /* @__PURE__ */ jsx(Tab, {
|
|
9417
|
-
disabled: !hasAdditionalViews,
|
|
9418
|
-
value: "__rebase_history",
|
|
9419
|
-
className: "text-sm",
|
|
9420
|
-
children: /* @__PURE__ */ jsx(HistoryIcon, { size: iconSize.smallest })
|
|
9421
|
-
}),
|
|
9422
|
-
/* @__PURE__ */ jsx(Tab, {
|
|
9423
|
-
disabled: !hasAdditionalViews,
|
|
9424
|
-
value: "__main_##Q$SC^#S6",
|
|
9425
|
-
className: "text-sm min-w-[90px]",
|
|
9426
|
-
children: /* @__PURE__ */ jsxs("span", {
|
|
9427
|
-
className: "flex items-center gap-1.5",
|
|
9428
|
-
children: [getIcon(collection.icon, void 0, void 0, "smallest"), collection.singularName ?? collection.name]
|
|
9429
|
-
})
|
|
9430
|
-
}),
|
|
9431
|
-
customViewTabsStart,
|
|
9432
|
-
customViewTabsEnd,
|
|
9433
|
-
subcollectionTabs
|
|
9434
|
-
]
|
|
9435
|
-
})
|
|
9436
10130
|
})
|
|
9437
|
-
]
|
|
10131
|
+
] })
|
|
10132
|
+
}),
|
|
10133
|
+
hasAdditionalViews && /* @__PURE__ */ jsx("div", {
|
|
10134
|
+
className: cls("h-10 shrink-0 flex items-stretch border-b px-2 min-w-0", defaultBorderMixin),
|
|
10135
|
+
children: /* @__PURE__ */ jsxs(Tabs, {
|
|
10136
|
+
className: "!w-full",
|
|
10137
|
+
innerClassName: "h-full",
|
|
10138
|
+
variant: "boxy",
|
|
10139
|
+
value: activeTab,
|
|
10140
|
+
onValueChange: (value) => {
|
|
10141
|
+
onSideTabClick(value);
|
|
10142
|
+
},
|
|
10143
|
+
children: [
|
|
10144
|
+
/* @__PURE__ */ jsx(Tab, {
|
|
10145
|
+
value: "__main_##Q$SC^#S6",
|
|
10146
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
10147
|
+
className: "flex items-center gap-1.5",
|
|
10148
|
+
children: [getIcon(collection.icon, void 0, void 0, "smallest"), collection.singularName ?? collection.name]
|
|
10149
|
+
})
|
|
10150
|
+
}),
|
|
10151
|
+
customViewTabsStart,
|
|
10152
|
+
customViewTabsEnd,
|
|
10153
|
+
subcollectionTabs
|
|
10154
|
+
]
|
|
10155
|
+
})
|
|
9438
10156
|
}),
|
|
9439
10157
|
globalLoading ? /* @__PURE__ */ jsx(DetailViewSkeleton, { collection }) : mainView,
|
|
9440
|
-
jsonView,
|
|
9441
|
-
historyView,
|
|
9442
10158
|
customViewsView,
|
|
9443
|
-
subCollectionsViews
|
|
10159
|
+
subCollectionsViews,
|
|
10160
|
+
/* @__PURE__ */ jsx(EntityInspector, {
|
|
10161
|
+
tab: inspectorTab,
|
|
10162
|
+
onTabChange: setInspectorTab,
|
|
10163
|
+
onClose: () => setInspectorTab(null),
|
|
10164
|
+
collection,
|
|
10165
|
+
entity: usedEntity,
|
|
10166
|
+
formContext: readOnlyFormContext,
|
|
10167
|
+
values: usedEntity?.values,
|
|
10168
|
+
includeHistory: includeHistoryView
|
|
10169
|
+
})
|
|
9444
10170
|
]
|
|
9445
10171
|
});
|
|
9446
10172
|
if (plugins && plugins.length > 0) result = /* @__PURE__ */ jsx(PluginProviderStack, {
|
|
@@ -9753,27 +10479,43 @@ function SplitListView({ collection, tableController, onEntityClick: externalOnE
|
|
|
9753
10479
|
}
|
|
9754
10480
|
}, String(renderedEntityId)) })
|
|
9755
10481
|
}) : /* @__PURE__ */ jsx(Fragment, {});
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
10482
|
+
const openFullScreen = useCallback(() => {
|
|
10483
|
+
if (selectedEntityId === void 0) return;
|
|
10484
|
+
navigate(`${buildUrl(`${path}/${selectedEntityId}${isEditMode ? "/edit" : ""}`)}#full`);
|
|
10485
|
+
}, [
|
|
10486
|
+
navigate,
|
|
10487
|
+
buildUrl,
|
|
10488
|
+
path,
|
|
10489
|
+
selectedEntityId,
|
|
10490
|
+
isEditMode
|
|
10491
|
+
]);
|
|
10492
|
+
return /* @__PURE__ */ jsxs(SplitViewProvider, {
|
|
10493
|
+
value: useMemo(() => ({
|
|
10494
|
+
detailOpen: selectedEntityId !== void 0,
|
|
10495
|
+
openFullScreen
|
|
10496
|
+
}), [selectedEntityId, openFullScreen]),
|
|
10497
|
+
children: [/* @__PURE__ */ jsx(ResizablePanels, {
|
|
10498
|
+
stacked: !largeLayout,
|
|
10499
|
+
firstPanel: listPanel,
|
|
10500
|
+
secondPanel: detailPanel,
|
|
10501
|
+
showSecondPanel: isDetailVisible,
|
|
10502
|
+
panelSizePercent: animationPhase === "entering" ? 100 : panelSize,
|
|
10503
|
+
onPanelSizeChange: setPanelSize,
|
|
10504
|
+
minPanelSizePx: 240
|
|
10505
|
+
}), /* @__PURE__ */ jsx(UnsavedChangesDialog, {
|
|
10506
|
+
open: blocker?.state === "blocked",
|
|
10507
|
+
handleOk: () => {
|
|
10508
|
+
dirty.current = false;
|
|
10509
|
+
blocker?.proceed?.();
|
|
10510
|
+
},
|
|
10511
|
+
handleCancel: () => blocker?.reset?.(),
|
|
10512
|
+
body: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10513
|
+
"You have unsaved changes in this ",
|
|
10514
|
+
/* @__PURE__ */ jsx("b", { children: collection.singularName ?? collection.name }),
|
|
10515
|
+
"."
|
|
10516
|
+
] })
|
|
10517
|
+
})]
|
|
10518
|
+
});
|
|
9777
10519
|
}
|
|
9778
10520
|
//#endregion
|
|
9779
10521
|
//#region src/components/CollectionViewBinding/BoardCardBinding.tsx
|
|
@@ -9927,6 +10669,12 @@ var BoardCardBinding = memo(BoardCardBindingInner);
|
|
|
9927
10669
|
//#region src/components/CollectionViewBinding/useBoardDataController.tsx
|
|
9928
10670
|
var DEFAULT_PAGE_SIZE$1 = 20;
|
|
9929
10671
|
/**
|
|
10672
|
+
* How long a column waits for its subscription's first payload before reading
|
|
10673
|
+
* once over HTTP instead. Long enough that a healthy socket always wins the
|
|
10674
|
+
* race, short enough that a dead one does not hold a blank board.
|
|
10675
|
+
*/
|
|
10676
|
+
var LIVE_FIRST_PAINT_TIMEOUT_MS = 2500;
|
|
10677
|
+
/**
|
|
9930
10678
|
* Shallow equality for entity value records.
|
|
9931
10679
|
* Handles primitives and reference equality for nested values.
|
|
9932
10680
|
* Avoids JSON.stringify allocation on every comparison.
|
|
@@ -10152,36 +10900,66 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
10152
10900
|
};
|
|
10153
10901
|
});
|
|
10154
10902
|
};
|
|
10903
|
+
const accessor = currentDataClient.collection(currentResolvedPath);
|
|
10904
|
+
const includeParams = getRelationIncludeParams(currentCollection);
|
|
10905
|
+
const fetchOnce = () => accessor.find({
|
|
10906
|
+
where: whereFilter,
|
|
10907
|
+
limit: itemCount,
|
|
10908
|
+
orderBy: orderByParam,
|
|
10909
|
+
include: includeParams
|
|
10910
|
+
});
|
|
10155
10911
|
const onError = (error) => {
|
|
10156
10912
|
if (isCleaningUpRef.current) return;
|
|
10157
10913
|
console.error(`Error loading column ${column}:`, error);
|
|
10914
|
+
fetchOnce().then((res) => onUpdate(res.data)).catch(() => {
|
|
10915
|
+
if (isCleaningUpRef.current) return;
|
|
10916
|
+
setColumnData((prev) => ({
|
|
10917
|
+
...prev,
|
|
10918
|
+
[column]: {
|
|
10919
|
+
...prev[column],
|
|
10920
|
+
entities: prev[column]?.entities ?? [],
|
|
10921
|
+
loading: false,
|
|
10922
|
+
hasMore: false,
|
|
10923
|
+
error
|
|
10924
|
+
}
|
|
10925
|
+
}));
|
|
10926
|
+
});
|
|
10927
|
+
};
|
|
10928
|
+
if (accessor.listen) {
|
|
10929
|
+
let liveDataReceived = false;
|
|
10930
|
+
const unsubscribe = accessor.listen({
|
|
10931
|
+
where: whereFilter,
|
|
10932
|
+
limit: itemCount,
|
|
10933
|
+
orderBy: orderByParam,
|
|
10934
|
+
include: includeParams
|
|
10935
|
+
}, (res) => {
|
|
10936
|
+
liveDataReceived = true;
|
|
10937
|
+
onUpdate(res.data);
|
|
10938
|
+
}, onError);
|
|
10939
|
+
const firstPaintFallback = setTimeout(() => {
|
|
10940
|
+
if (liveDataReceived || isCleaningUpRef.current) return;
|
|
10941
|
+
fetchOnce().then((res) => {
|
|
10942
|
+
if (liveDataReceived || isCleaningUpRef.current) return;
|
|
10943
|
+
onUpdate(res.data);
|
|
10944
|
+
}).catch(() => void 0);
|
|
10945
|
+
}, LIVE_FIRST_PAINT_TIMEOUT_MS);
|
|
10946
|
+
unsubscribersRef.current[column] = () => {
|
|
10947
|
+
clearTimeout(firstPaintFallback);
|
|
10948
|
+
unsubscribe?.();
|
|
10949
|
+
};
|
|
10950
|
+
} else fetchOnce().then((res) => onUpdate(res.data)).catch((error) => {
|
|
10951
|
+
if (isCleaningUpRef.current) return;
|
|
10158
10952
|
setColumnData((prev) => ({
|
|
10159
10953
|
...prev,
|
|
10160
10954
|
[column]: {
|
|
10161
10955
|
...prev[column],
|
|
10162
|
-
entities: [],
|
|
10956
|
+
entities: prev[column]?.entities ?? [],
|
|
10163
10957
|
loading: false,
|
|
10164
10958
|
hasMore: false,
|
|
10165
10959
|
error
|
|
10166
10960
|
}
|
|
10167
10961
|
}));
|
|
10168
|
-
};
|
|
10169
|
-
const accessor = currentDataClient.collection(currentResolvedPath);
|
|
10170
|
-
const includeParams = getRelationIncludeParams(currentCollection);
|
|
10171
|
-
if (accessor.listen) {
|
|
10172
|
-
const unsubscribe = accessor.listen({
|
|
10173
|
-
where: whereFilter,
|
|
10174
|
-
limit: itemCount,
|
|
10175
|
-
orderBy: orderByParam,
|
|
10176
|
-
include: includeParams
|
|
10177
|
-
}, (res) => onUpdate(res.data), onError);
|
|
10178
|
-
unsubscribersRef.current[column] = unsubscribe;
|
|
10179
|
-
} else accessor.find({
|
|
10180
|
-
where: whereFilter,
|
|
10181
|
-
limit: itemCount,
|
|
10182
|
-
orderBy: orderByParam,
|
|
10183
|
-
include: includeParams
|
|
10184
|
-
}).then((res) => onUpdate(res.data)).catch(onError);
|
|
10962
|
+
});
|
|
10185
10963
|
}, []);
|
|
10186
10964
|
useEffect(() => {
|
|
10187
10965
|
isCleaningUpRef.current = false;
|
|
@@ -10203,7 +10981,8 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
10203
10981
|
const timeoutId = setTimeout(() => {
|
|
10204
10982
|
if (isCleaningUpRef.current) return;
|
|
10205
10983
|
currentColumns.forEach((column) => {
|
|
10206
|
-
|
|
10984
|
+
const itemCount = currentColumnItemCounts[column] ?? pageSize;
|
|
10985
|
+
subscribeToColumn(column, itemCount);
|
|
10207
10986
|
if (isColumnExcludedByFilter(column, currentFilterValues, currentColumnProperty)) return;
|
|
10208
10987
|
const accessor = currentDataClient.collection(currentResolvedPath);
|
|
10209
10988
|
if (accessor.count) {
|
|
@@ -10395,6 +11174,40 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
10395
11174
|
}
|
|
10396
11175
|
//#endregion
|
|
10397
11176
|
//#region src/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.ts
|
|
11177
|
+
/**
|
|
11178
|
+
* The digits a board order key is built from: base36, lower case only.
|
|
11179
|
+
*
|
|
11180
|
+
* The library's default alphabet is base62, and its keys only sort correctly
|
|
11181
|
+
* under byte ordering. The column is sorted by the *database*, and Postgres'
|
|
11182
|
+
* default collation is not byte ordering — under `en_US.UTF-8`, `"aa"` sorts
|
|
11183
|
+
* before `"aC"`, so as soon as a board had been dragged around enough to reach
|
|
11184
|
+
* the upper-case digits its order stopped matching the keys. One case has no
|
|
11185
|
+
* such ambiguity: digits before letters is true of every collation in use.
|
|
11186
|
+
*
|
|
11187
|
+
* A key written with the old alphabet no longer validates, which is what makes
|
|
11188
|
+
* the board offer to initialise the column — one click, and the order is
|
|
11189
|
+
* rewritten in a form the database can sort.
|
|
11190
|
+
*/
|
|
11191
|
+
var ORDER_KEY_DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
11192
|
+
/**
|
|
11193
|
+
* Whether a stored order value is a key `fractional-indexing` can interpolate
|
|
11194
|
+
* against.
|
|
11195
|
+
*
|
|
11196
|
+
* Anything else — a plain `"12"` from a seed or a migration, an empty string —
|
|
11197
|
+
* makes `generateKeyBetween` throw, and the fallback below then hands out the
|
|
11198
|
+
* same first key to every card, so each drag lands at the bottom of the column
|
|
11199
|
+
* on top of the last one that did. Asking the library itself is the only
|
|
11200
|
+
* definition of "valid" worth trusting.
|
|
11201
|
+
*/
|
|
11202
|
+
function isValidOrderKey(value) {
|
|
11203
|
+
if (typeof value !== "string" || value.length === 0) return false;
|
|
11204
|
+
try {
|
|
11205
|
+
generateKeyBetween(value, null, ORDER_KEY_DIGITS);
|
|
11206
|
+
return true;
|
|
11207
|
+
} catch {
|
|
11208
|
+
return false;
|
|
11209
|
+
}
|
|
11210
|
+
}
|
|
10398
11211
|
function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderProperty, dataClient, context, boardDataController, analyticsController }) {
|
|
10399
11212
|
const handleItemsReorder = useCallback(async (items, moveInfo) => {
|
|
10400
11213
|
const entity = items.find((item) => item.id === moveInfo?.itemId)?.data;
|
|
@@ -10411,17 +11224,21 @@ function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderPrope
|
|
|
10411
11224
|
if (orderProperty) {
|
|
10412
11225
|
const targetColumnItems = items;
|
|
10413
11226
|
const movedIndex = targetColumnItems.findIndex((item) => item.id === moveInfo.itemId);
|
|
10414
|
-
const
|
|
10415
|
-
|
|
11227
|
+
const keyAt = (index) => {
|
|
11228
|
+
const value = targetColumnItems[index]?.data.values?.[orderProperty];
|
|
11229
|
+
return isValidOrderKey(value) ? value : null;
|
|
11230
|
+
};
|
|
11231
|
+
const prevKey = movedIndex > 0 ? keyAt(movedIndex - 1) : null;
|
|
11232
|
+
const nextKey = movedIndex < targetColumnItems.length - 1 ? keyAt(movedIndex + 1) : null;
|
|
10416
11233
|
try {
|
|
10417
11234
|
const a = prevKey;
|
|
10418
11235
|
let b = nextKey;
|
|
10419
11236
|
if (a !== null && b !== null && a >= b) b = null;
|
|
10420
|
-
const newKey = generateKeyBetween(a, b);
|
|
11237
|
+
const newKey = generateKeyBetween(a, b, ORDER_KEY_DIGITS);
|
|
10421
11238
|
updatedValues = setIn(updatedValues, orderProperty, newKey);
|
|
10422
11239
|
} catch (e) {
|
|
10423
11240
|
console.warn("fractional-indexing error, falling back:", e);
|
|
10424
|
-
const newKey = generateKeyBetween(null, null);
|
|
11241
|
+
const newKey = generateKeyBetween(null, null, ORDER_KEY_DIGITS);
|
|
10425
11242
|
updatedValues = setIn(updatedValues, orderProperty, newKey);
|
|
10426
11243
|
}
|
|
10427
11244
|
}
|
|
@@ -10628,10 +11445,7 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
|
|
|
10628
11445
|
const itemsNeedBackfill = useMemo(() => {
|
|
10629
11446
|
if (!orderProperty || dataLoading) return false;
|
|
10630
11447
|
if (missingOrderCount > 0) return true;
|
|
10631
|
-
return allEntities.some((entity) =>
|
|
10632
|
-
const orderValue = entity.values?.[orderProperty];
|
|
10633
|
-
return orderValue === void 0 || orderValue === null;
|
|
10634
|
-
});
|
|
11448
|
+
return allEntities.some((entity) => !isValidOrderKey(entity.values?.[orderProperty]));
|
|
10635
11449
|
}, [
|
|
10636
11450
|
allEntities,
|
|
10637
11451
|
orderProperty,
|
|
@@ -10662,7 +11476,8 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
|
|
|
10662
11476
|
loading: colData?.loading ?? true,
|
|
10663
11477
|
hasMore: colData?.hasMore ?? false,
|
|
10664
11478
|
itemCount: colData?.entities?.length ?? 0,
|
|
10665
|
-
totalCount: colData?.totalCount
|
|
11479
|
+
totalCount: colData?.totalCount,
|
|
11480
|
+
error: colData?.error
|
|
10666
11481
|
};
|
|
10667
11482
|
});
|
|
10668
11483
|
return state;
|
|
@@ -10693,11 +11508,8 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
|
|
|
10693
11508
|
analyticsController.onAnalyticsEvent?.("kanban_backfill_order", { path: fullPath });
|
|
10694
11509
|
setBackfillLoading(true);
|
|
10695
11510
|
try {
|
|
10696
|
-
const entitiesToUpdate = (await dataClient.collection(fullPath).find({ limit: 1e4 })).data.filter((entity) =>
|
|
10697
|
-
|
|
10698
|
-
return orderValue === void 0 || orderValue === null;
|
|
10699
|
-
});
|
|
10700
|
-
const keys = generateNKeysBetween(null, null, entitiesToUpdate.length);
|
|
11511
|
+
const entitiesToUpdate = (await dataClient.collection(fullPath).find({ limit: 1e4 })).data.filter((entity) => !isValidOrderKey(entity.values?.[orderProperty]));
|
|
11512
|
+
const keys = generateNKeysBetween(null, null, entitiesToUpdate.length, ORDER_KEY_DIGITS);
|
|
10701
11513
|
const updates = [];
|
|
10702
11514
|
entitiesToUpdate.forEach((entity, index) => {
|
|
10703
11515
|
const updatedValues = setIn({ ...entity.values }, orderProperty, keys[index]);
|
|
@@ -10865,7 +11677,7 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
|
|
|
10865
11677
|
})]
|
|
10866
11678
|
}),
|
|
10867
11679
|
/* @__PURE__ */ jsx("div", {
|
|
10868
|
-
className: "flex-1
|
|
11680
|
+
className: "flex-1 min-h-0 overflow-hidden",
|
|
10869
11681
|
children: /* @__PURE__ */ jsx(KanbanView, {
|
|
10870
11682
|
data: boardItems,
|
|
10871
11683
|
columns,
|
|
@@ -11472,9 +12284,9 @@ function ResetPasswordActionDialog({ user, open, onClose }) {
|
|
|
11472
12284
|
setLoading(true);
|
|
11473
12285
|
setError(null);
|
|
11474
12286
|
try {
|
|
11475
|
-
const
|
|
12287
|
+
const apiBase = apiBaseOf(client) ?? "";
|
|
11476
12288
|
const token = await getAuthToken?.();
|
|
11477
|
-
const response = await fetch(`${
|
|
12289
|
+
const response = await fetch(`${apiBase}/admin/users/${user.uid}/reset-password`, {
|
|
11478
12290
|
method: "POST",
|
|
11479
12291
|
headers: {
|
|
11480
12292
|
"Content-Type": "application/json",
|
|
@@ -11786,7 +12598,8 @@ function PopupFormFieldInternal({ tableKey, entityId, customFieldValidator, prop
|
|
|
11786
12598
|
const updatePopupLocation = useCallback((newPositionCandidate) => {
|
|
11787
12599
|
const draggableBoundingRect = draggableRef.current?.getBoundingClientRect();
|
|
11788
12600
|
if (!cellRect || !draggableBoundingRect || draggableBoundingRect.width === 0 || draggableBoundingRect.height === 0) return;
|
|
11789
|
-
const
|
|
12601
|
+
const basePosition = newPositionCandidate ?? getInitialLocation();
|
|
12602
|
+
const newNormalizedPosition = normalizePosition(basePosition, draggableBoundingRect, windowSize);
|
|
11790
12603
|
if (!popupLocation || newNormalizedPosition.x !== popupLocation.x || newNormalizedPosition.y !== popupLocation.y) setPopupLocation(newNormalizedPosition);
|
|
11791
12604
|
}, [
|
|
11792
12605
|
cellRect,
|
|
@@ -12348,6 +13161,7 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
|
|
|
12348
13161
|
const { t } = useTranslation();
|
|
12349
13162
|
const navigate = useNavigate();
|
|
12350
13163
|
const urlController = useUrlController();
|
|
13164
|
+
const splitView = useSplitView();
|
|
12351
13165
|
const [filtersDialogOpen, setFiltersDialogOpen] = useState(false);
|
|
12352
13166
|
const filterValues = tableController.filterValues;
|
|
12353
13167
|
const fixedFilter = collection.fixedFilter;
|
|
@@ -12371,15 +13185,16 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
|
|
|
12371
13185
|
urlController,
|
|
12372
13186
|
path
|
|
12373
13187
|
]);
|
|
12374
|
-
const backButton = compact && /* @__PURE__ */ jsx(Tooltip, {
|
|
12375
|
-
title: t("
|
|
13188
|
+
const backButton = compact && !splitView?.detailOpen && /* @__PURE__ */ jsx(Tooltip, {
|
|
13189
|
+
title: t("close"),
|
|
12376
13190
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
12377
13191
|
size: "small",
|
|
12378
13192
|
onClick: handleBackClick,
|
|
13193
|
+
"aria-label": t("close"),
|
|
12379
13194
|
className: "mr-1",
|
|
12380
|
-
children: /* @__PURE__ */ jsx(
|
|
13195
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.smallest })
|
|
12381
13196
|
})
|
|
12382
|
-
}, "
|
|
13197
|
+
}, "close_tooltip");
|
|
12383
13198
|
const filtersButton = resolvedProperties && tableController.setFilterValues && /* @__PURE__ */ jsx(Tooltip, {
|
|
12384
13199
|
title: t("filters"),
|
|
12385
13200
|
children: /* @__PURE__ */ jsx(Badge, {
|
|
@@ -12392,16 +13207,15 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
|
|
|
12392
13207
|
startIcon: /* @__PURE__ */ jsx(FilterIcon, { size: iconSize.small }),
|
|
12393
13208
|
className: cls(activeFilterCount > 0 && "text-primary"),
|
|
12394
13209
|
children: activeFilterCount > 0 ? `(${activeFilterCount})` : t("filters")
|
|
12395
|
-
}) : /* @__PURE__ */ jsx(
|
|
12396
|
-
variant: "text",
|
|
13210
|
+
}) : /* @__PURE__ */ jsx(IconButton, {
|
|
12397
13211
|
size: "small",
|
|
12398
13212
|
onClick: () => setFiltersDialogOpen(true),
|
|
12399
13213
|
className: cls(activeFilterCount > 0 && "text-primary"),
|
|
12400
|
-
children: /* @__PURE__ */ jsx(FilterIcon, { size: iconSize.
|
|
13214
|
+
children: /* @__PURE__ */ jsx(FilterIcon, { size: iconSize.smallest })
|
|
12401
13215
|
})
|
|
12402
13216
|
})
|
|
12403
13217
|
}, "filters_tooltip");
|
|
12404
|
-
const filterPresetsButton = collection.filterPresets?.length ? /* @__PURE__ */ jsx(FilterPresetsButton, {
|
|
13218
|
+
const filterPresetsButton = !compact && collection.filterPresets?.length ? /* @__PURE__ */ jsx(FilterPresetsButton, {
|
|
12405
13219
|
filterPresets: collection.filterPresets,
|
|
12406
13220
|
tableController,
|
|
12407
13221
|
compact
|
|
@@ -12587,7 +13401,10 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
12587
13401
|
}, [defaultViewMode, setSearchParams]);
|
|
12588
13402
|
useEffect(() => {
|
|
12589
13403
|
if (urlView) setViewModeState(urlView);
|
|
12590
|
-
else
|
|
13404
|
+
else {
|
|
13405
|
+
const savedView = getSavedView();
|
|
13406
|
+
setViewModeState(savedView ?? defaultViewMode);
|
|
13407
|
+
}
|
|
12591
13408
|
}, [
|
|
12592
13409
|
urlView,
|
|
12593
13410
|
getSavedView,
|
|
@@ -12619,9 +13436,10 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
12619
13436
|
entityId: clickedEntity.id
|
|
12620
13437
|
});
|
|
12621
13438
|
if (collection) addRecentId(collection.slug, clickedEntity.id);
|
|
13439
|
+
const entityPath = path ?? clickedEntity.path;
|
|
12622
13440
|
navigateToEntity({
|
|
12623
13441
|
navigation: urlController,
|
|
12624
|
-
path:
|
|
13442
|
+
path: entityPath,
|
|
12625
13443
|
sidePanelController,
|
|
12626
13444
|
openEntityMode,
|
|
12627
13445
|
collection,
|
|
@@ -12765,7 +13583,8 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
12765
13583
|
const onColumnResize = useCallback(({ width, key }) => {
|
|
12766
13584
|
const collection = collectionRef.current;
|
|
12767
13585
|
if (!getPropertyInPath(collection.properties, key)) return;
|
|
12768
|
-
|
|
13586
|
+
const localCollection = buildPropertyWidthOverwrite(key, width);
|
|
13587
|
+
onCollectionModifiedForUser(path, localCollection);
|
|
12769
13588
|
}, [onCollectionModifiedForUser, path]);
|
|
12770
13589
|
const onListSizeChanged = useCallback((size) => {
|
|
12771
13590
|
setListSize(size);
|
|
@@ -12947,21 +13766,24 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
12947
13766
|
return (largeLayout ? 80 + actionsWidth : 70 + actionsWidth) + (collapsedActions.length > 0 ? largeLayout ? 40 : 30 : 0);
|
|
12948
13767
|
}, [getActionsForEntity, largeLayout]);
|
|
12949
13768
|
const tableRowActionsBuilder = useCallback(({ entity, size, width, frozen }) => {
|
|
13769
|
+
const isSelected = Boolean(usedSelectionController.selectedEntities.find((e) => e.id == entity.id && e.path == entity.path));
|
|
13770
|
+
const customEntityActions = (collection.entityActions ?? EMPTY_ARRAY).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
|
|
13771
|
+
const actions = getActionsForEntity({
|
|
13772
|
+
entity,
|
|
13773
|
+
customEntityActions
|
|
13774
|
+
});
|
|
12950
13775
|
return /* @__PURE__ */ jsx(CollectionRowActions, {
|
|
12951
13776
|
entity,
|
|
12952
13777
|
width,
|
|
12953
13778
|
frozen,
|
|
12954
|
-
isSelected
|
|
13779
|
+
isSelected,
|
|
12955
13780
|
selectionEnabled: activeSelectionEnabled,
|
|
12956
13781
|
size,
|
|
12957
13782
|
highlightEntity: setHighlightedEntity,
|
|
12958
13783
|
unhighlightEntity: unselectNavigatedEntity,
|
|
12959
13784
|
collection,
|
|
12960
13785
|
path,
|
|
12961
|
-
actions
|
|
12962
|
-
entity,
|
|
12963
|
-
customEntityActions: (collection.entityActions ?? EMPTY_ARRAY).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean)
|
|
12964
|
-
}),
|
|
13786
|
+
actions,
|
|
12965
13787
|
hideId: collection?.hideIdFromCollection,
|
|
12966
13788
|
onCollectionChange: updateLastDeleteTimestamp,
|
|
12967
13789
|
selectionController: usedSelectionController,
|
|
@@ -12983,12 +13805,13 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
12983
13805
|
const { resolvedSlots } = customizationController;
|
|
12984
13806
|
const headerActionContributions = useMemo(() => resolvedSlots.filter((s) => s.slot === "collection.header.action").sort((a, b) => (a.order ?? 50) - (b.order ?? 50)), [resolvedSlots]);
|
|
12985
13807
|
const buildAdditionalHeaderWidget = useCallback(({ property, propertyKey, onHover }) => {
|
|
13808
|
+
const collection = collectionRef.current;
|
|
12986
13809
|
const headerSlotProps = {
|
|
12987
13810
|
property,
|
|
12988
13811
|
propertyKey,
|
|
12989
13812
|
onHover,
|
|
12990
13813
|
path,
|
|
12991
|
-
collection
|
|
13814
|
+
collection,
|
|
12992
13815
|
tableController,
|
|
12993
13816
|
parentCollectionSlugs: parentCollectionSlugs ?? EMPTY_ARRAY,
|
|
12994
13817
|
parentEntityIds: parentEntityIds ?? EMPTY_ARRAY
|
|
@@ -13242,7 +14065,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
13242
14065
|
}, `split-list-view-${path}`) : /* @__PURE__ */ jsxs("div", {
|
|
13243
14066
|
className: "flex flex-col w-full h-full",
|
|
13244
14067
|
children: [toolbarNode, /* @__PURE__ */ jsx("div", {
|
|
13245
|
-
className: cls("flex-1 flex flex-col", (viewMode === "list" || viewMode === "cards") && "overflow-y-auto"),
|
|
14068
|
+
className: cls("flex-1 min-h-0 flex flex-col", (viewMode === "list" || viewMode === "cards") && "overflow-y-auto"),
|
|
13246
14069
|
children: collectionSurface
|
|
13247
14070
|
})]
|
|
13248
14071
|
}),
|
|
@@ -13455,7 +14278,18 @@ function EntityIdHeaderWidget({ collection, path, idPath, openEntityMode }) {
|
|
|
13455
14278
|
}
|
|
13456
14279
|
//#endregion
|
|
13457
14280
|
//#region src/components/EditFormActions.tsx
|
|
13458
|
-
|
|
14281
|
+
/**
|
|
14282
|
+
* The entity form's footer.
|
|
14283
|
+
*
|
|
14284
|
+
* It used to render in two shapes: this footer, and a `w-80 2xl:w-96` sticky
|
|
14285
|
+
* side rail carrying the same Save and Discard above several hundred pixels of
|
|
14286
|
+
* nothing. The rail is gone, and Save/Discard have moved to the identity bar
|
|
14287
|
+
* where they stay visible while the form scrolls — so with
|
|
14288
|
+
* `showDefaultActions={false}` this renders only what the bar cannot host:
|
|
14289
|
+
* record actions (copy, delete, custom), plugin actions, the saving error, and
|
|
14290
|
+
* the side-panel-only "save and close".
|
|
14291
|
+
*/
|
|
14292
|
+
function EditFormActions({ collection, path, entity, savingError, formex, disabled, status, pluginActions, openEntityMode, showDefaultActions = true, navigateBack, formContext }) {
|
|
13459
14293
|
const { canCreate, canDelete } = usePermissions();
|
|
13460
14294
|
const context = useAdminContext();
|
|
13461
14295
|
const sidePanelController = context.sidePanelController;
|
|
@@ -13464,31 +14298,29 @@ function EditFormActions({ collection, path, entity, layout, savingError, formex
|
|
|
13464
14298
|
const { t } = useTranslation();
|
|
13465
14299
|
const childViewSource = useChildViewSource(path);
|
|
13466
14300
|
const isLinkedChildView = childViewSource?.kind === "relation" && childViewSource.mode === "linked";
|
|
13467
|
-
|
|
13468
|
-
const customEntityActions = (collection.entityActions ?? []).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
|
|
13469
|
-
const createEnabled = canCreate(collection, path);
|
|
13470
|
-
const deleteEnabled = entity ? canDelete(collection, path, entity) : false;
|
|
13471
|
-
const disableActions = collection.disableDefaultActions ?? [];
|
|
13472
|
-
const actions = [];
|
|
13473
|
-
if (createEnabled && !disableActions.includes("copy")) actions.push(copyEntityAction);
|
|
13474
|
-
if (deleteEnabled && !disableActions.includes("delete")) actions.push(isLinkedChildView ? unlinkEntityAction : deleteEntityAction);
|
|
13475
|
-
if (customEntityActions) return mergeEntityActions(actions, customEntityActions);
|
|
13476
|
-
return actions;
|
|
13477
|
-
}, [
|
|
13478
|
-
canCreate,
|
|
13479
|
-
canDelete,
|
|
13480
|
-
collection,
|
|
13481
|
-
path,
|
|
13482
|
-
customizationController.entityActions?.length,
|
|
13483
|
-
entity,
|
|
13484
|
-
collection.disableDefaultActions,
|
|
13485
|
-
isLinkedChildView
|
|
13486
|
-
]);
|
|
13487
|
-
const formActions = showDefaultActions ? entityActions.filter((a) => a.includeInForm === void 0 || a.includeInForm) : [];
|
|
13488
|
-
const bottomActions = buildBottomActions$1({
|
|
14301
|
+
return buildBottomActions$1({
|
|
13489
14302
|
savingError,
|
|
13490
14303
|
entity,
|
|
13491
|
-
formActions
|
|
14304
|
+
formActions: useMemo(() => {
|
|
14305
|
+
const customEntityActions = (collection.entityActions ?? []).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
|
|
14306
|
+
const createEnabled = canCreate(collection, path);
|
|
14307
|
+
const deleteEnabled = entity ? canDelete(collection, path, entity) : false;
|
|
14308
|
+
const disableActions = collection.disableDefaultActions ?? [];
|
|
14309
|
+
const actions = [];
|
|
14310
|
+
if (createEnabled && !disableActions.includes("copy")) actions.push(copyEntityAction);
|
|
14311
|
+
if (deleteEnabled && !disableActions.includes("delete")) actions.push(isLinkedChildView ? unlinkEntityAction : deleteEntityAction);
|
|
14312
|
+
if (customEntityActions) return mergeEntityActions(actions, customEntityActions);
|
|
14313
|
+
return actions;
|
|
14314
|
+
}, [
|
|
14315
|
+
canCreate,
|
|
14316
|
+
canDelete,
|
|
14317
|
+
collection,
|
|
14318
|
+
path,
|
|
14319
|
+
customizationController.entityActions?.length,
|
|
14320
|
+
entity,
|
|
14321
|
+
collection.disableDefaultActions,
|
|
14322
|
+
isLinkedChildView
|
|
14323
|
+
]).filter((a) => a.includeInForm === void 0 || a.includeInForm),
|
|
13492
14324
|
collection,
|
|
13493
14325
|
context,
|
|
13494
14326
|
sidePanelController,
|
|
@@ -13501,35 +14333,17 @@ function EditFormActions({ collection, path, entity, layout, savingError, formex
|
|
|
13501
14333
|
formContext,
|
|
13502
14334
|
formex,
|
|
13503
14335
|
t,
|
|
13504
|
-
|
|
14336
|
+
showDefaultActions
|
|
13505
14337
|
});
|
|
13506
|
-
const sideActions = buildSideActions$1({
|
|
13507
|
-
savingError,
|
|
13508
|
-
entity,
|
|
13509
|
-
formActions,
|
|
13510
|
-
collection,
|
|
13511
|
-
context,
|
|
13512
|
-
sidePanelController,
|
|
13513
|
-
sideDialogContext,
|
|
13514
|
-
disabled,
|
|
13515
|
-
status,
|
|
13516
|
-
pluginActions,
|
|
13517
|
-
openEntityMode,
|
|
13518
|
-
navigateBack,
|
|
13519
|
-
formContext,
|
|
13520
|
-
formex,
|
|
13521
|
-
t,
|
|
13522
|
-
className: layout === "responsive" ? "hidden @6xl:flex" : void 0
|
|
13523
|
-
});
|
|
13524
|
-
if (layout === "responsive") return /* @__PURE__ */ jsxs(Fragment, { children: [bottomActions, sideActions] });
|
|
13525
|
-
return layout === "bottom" ? bottomActions : sideActions;
|
|
13526
14338
|
}
|
|
13527
|
-
function buildBottomActions$1({ savingError, entity, formActions, collection, context, sidePanelController, disabled, status, sideDialogContext, pluginActions, openEntityMode, navigateBack, formContext, formex, t, className }) {
|
|
14339
|
+
function buildBottomActions$1({ savingError, entity, formActions, collection, context, sidePanelController, disabled, status, sideDialogContext, pluginActions, openEntityMode, navigateBack, formContext, formex, t, showDefaultActions, className }) {
|
|
13528
14340
|
const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
13529
14341
|
const canClose = openEntityMode === "side_panel" || openEntityMode === "dialog";
|
|
14342
|
+
const showBackToDetail = collection.defaultEntityAction === "view" && status === "existing";
|
|
14343
|
+
if (!(showDefaultActions || Boolean(savingError))) return null;
|
|
13530
14344
|
return /* @__PURE__ */ jsxs(DialogActions, {
|
|
13531
14345
|
className,
|
|
13532
|
-
position: "
|
|
14346
|
+
position: "sticky",
|
|
13533
14347
|
children: [
|
|
13534
14348
|
savingError && /* @__PURE__ */ jsx("div", {
|
|
13535
14349
|
className: "text-right",
|
|
@@ -13567,21 +14381,20 @@ function buildBottomActions$1({ savingError, entity, formActions, collection, co
|
|
|
13567
14381
|
size: iconSize.smallest
|
|
13568
14382
|
})
|
|
13569
14383
|
}) : null,
|
|
13570
|
-
/* @__PURE__ */ jsx(Button, {
|
|
13571
|
-
variant: "text",
|
|
13572
|
-
color: "primary",
|
|
13573
|
-
disabled: disabled || formex.isSubmitting,
|
|
13574
|
-
type: "reset",
|
|
13575
|
-
children: status === "existing" ? t("discard") : t("clear")
|
|
13576
|
-
}),
|
|
13577
|
-
collection.defaultEntityAction === "view" && status === "existing" && /* @__PURE__ */ jsx(Button, {
|
|
14384
|
+
showBackToDetail && /* @__PURE__ */ jsx(Button, {
|
|
13578
14385
|
variant: "text",
|
|
13579
14386
|
color: "primary",
|
|
13580
14387
|
disabled,
|
|
13581
14388
|
onClick: navigateBack,
|
|
13582
14389
|
children: t("back_to_detail") ?? "Back to details"
|
|
13583
14390
|
}),
|
|
13584
|
-
/* @__PURE__ */ jsx(
|
|
14391
|
+
showDefaultActions && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
|
|
14392
|
+
variant: "text",
|
|
14393
|
+
color: "primary",
|
|
14394
|
+
disabled: disabled || formex.isSubmitting,
|
|
14395
|
+
type: "reset",
|
|
14396
|
+
children: status === "existing" ? t("discard") : t("clear")
|
|
14397
|
+
}), /* @__PURE__ */ jsx(Tooltip, {
|
|
13585
14398
|
title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
|
|
13586
14399
|
children: /* @__PURE__ */ jsxs(Button, {
|
|
13587
14400
|
variant: canClose ? "text" : "filled",
|
|
@@ -13597,7 +14410,7 @@ function buildBottomActions$1({ savingError, entity, formActions, collection, co
|
|
|
13597
14410
|
status === "new" && t("create")
|
|
13598
14411
|
]
|
|
13599
14412
|
})
|
|
13600
|
-
}),
|
|
14413
|
+
})] }),
|
|
13601
14414
|
canClose && /* @__PURE__ */ jsxs(LoadingButton, {
|
|
13602
14415
|
variant: "filled",
|
|
13603
14416
|
color: "primary",
|
|
@@ -13616,77 +14429,6 @@ function buildBottomActions$1({ savingError, entity, formActions, collection, co
|
|
|
13616
14429
|
]
|
|
13617
14430
|
});
|
|
13618
14431
|
}
|
|
13619
|
-
function buildSideActions$1({ savingError, entity, formActions, collection, context, sidePanelController, disabled, status, sideDialogContext, pluginActions, openEntityMode, navigateBack, formContext, formex, t, className }) {
|
|
13620
|
-
const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
13621
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
13622
|
-
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),
|
|
13623
|
-
children: [
|
|
13624
|
-
/* @__PURE__ */ jsx(Tooltip, {
|
|
13625
|
-
title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
|
|
13626
|
-
children: /* @__PURE__ */ jsxs(LoadingButton, {
|
|
13627
|
-
fullWidth: true,
|
|
13628
|
-
variant: "filled",
|
|
13629
|
-
color: "primary",
|
|
13630
|
-
type: "submit",
|
|
13631
|
-
loading: formex.isSubmitting,
|
|
13632
|
-
startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
|
|
13633
|
-
disabled: disabled || formex.isSubmitting,
|
|
13634
|
-
onClick: () => {
|
|
13635
|
-
sideDialogContext.setPendingClose?.(false);
|
|
13636
|
-
},
|
|
13637
|
-
children: [
|
|
13638
|
-
status === "existing" && t("save"),
|
|
13639
|
-
status === "copy" && t("create_copy"),
|
|
13640
|
-
status === "new" && t("create")
|
|
13641
|
-
]
|
|
13642
|
-
})
|
|
13643
|
-
}),
|
|
13644
|
-
/* @__PURE__ */ jsx(Button, {
|
|
13645
|
-
fullWidth: true,
|
|
13646
|
-
variant: "text",
|
|
13647
|
-
disabled: disabled || formex.isSubmitting,
|
|
13648
|
-
type: "reset",
|
|
13649
|
-
children: status === "existing" ? t("discard") : t("clear")
|
|
13650
|
-
}),
|
|
13651
|
-
collection.defaultEntityAction === "view" && status === "existing" && /* @__PURE__ */ jsx(Button, {
|
|
13652
|
-
fullWidth: true,
|
|
13653
|
-
variant: "text",
|
|
13654
|
-
disabled,
|
|
13655
|
-
onClick: navigateBack,
|
|
13656
|
-
children: t("back_to_detail") ?? "Back to details"
|
|
13657
|
-
}),
|
|
13658
|
-
pluginActions,
|
|
13659
|
-
formActions.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
13660
|
-
className: "flex flex-row flex-wrap mt-2",
|
|
13661
|
-
children: formActions.map((action, index) => {
|
|
13662
|
-
const props = {
|
|
13663
|
-
view: "form",
|
|
13664
|
-
entity,
|
|
13665
|
-
path: getCollectionDataPath(collection),
|
|
13666
|
-
collection,
|
|
13667
|
-
context,
|
|
13668
|
-
sidePanelController,
|
|
13669
|
-
openEntityMode,
|
|
13670
|
-
navigateBack,
|
|
13671
|
-
formContext
|
|
13672
|
-
};
|
|
13673
|
-
return /* @__PURE__ */ jsx(EntityActionButton, {
|
|
13674
|
-
action,
|
|
13675
|
-
enabled: !action.isEnabled || action.isEnabled(props),
|
|
13676
|
-
props
|
|
13677
|
-
}, action.key ?? action.name ?? index);
|
|
13678
|
-
})
|
|
13679
|
-
}),
|
|
13680
|
-
savingError && /* @__PURE__ */ jsx("div", {
|
|
13681
|
-
className: "text-right",
|
|
13682
|
-
children: /* @__PURE__ */ jsx(Typography, {
|
|
13683
|
-
color: "error",
|
|
13684
|
-
children: savingError.message
|
|
13685
|
-
})
|
|
13686
|
-
})
|
|
13687
|
-
]
|
|
13688
|
-
});
|
|
13689
|
-
}
|
|
13690
14432
|
function EntityActionButton({ action, enabled, props }) {
|
|
13691
14433
|
const snackbarController = useSnackbarController();
|
|
13692
14434
|
const [loading, setLoading] = React.useState(false);
|
|
@@ -13728,8 +14470,68 @@ function EntityActionButton({ action, enabled, props }) {
|
|
|
13728
14470
|
});
|
|
13729
14471
|
}
|
|
13730
14472
|
//#endregion
|
|
14473
|
+
//#region src/hooks/useRecordActions.tsx
|
|
14474
|
+
/**
|
|
14475
|
+
* The actions you can perform *on* a record — copy, delete, and whatever the
|
|
14476
|
+
* collection adds — resolved against the current user's permissions.
|
|
14477
|
+
*
|
|
14478
|
+
* Extracted from the form's footer so the identity bar can own them. They are
|
|
14479
|
+
* not ways to leave the form, so they never belonged next to Save; putting them
|
|
14480
|
+
* in the bar's overflow menu is what let the footer disappear instead of
|
|
14481
|
+
* becoming a full-width strip holding two icon buttons.
|
|
14482
|
+
*/
|
|
14483
|
+
function useRecordActions({ collection, path, entity }) {
|
|
14484
|
+
const { canCreate, canDelete } = usePermissions();
|
|
14485
|
+
const customizationController = useCustomizationController();
|
|
14486
|
+
const childViewSource = useChildViewSource(path);
|
|
14487
|
+
const isLinkedChildView = childViewSource?.kind === "relation" && childViewSource.mode === "linked";
|
|
14488
|
+
return useMemo(() => {
|
|
14489
|
+
const customEntityActions = (collection.entityActions ?? []).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
|
|
14490
|
+
const createEnabled = canCreate(collection, path);
|
|
14491
|
+
const deleteEnabled = entity ? canDelete(collection, path, entity) : false;
|
|
14492
|
+
const disableActions = collection.disableDefaultActions ?? [];
|
|
14493
|
+
const actions = [];
|
|
14494
|
+
if (createEnabled && !disableActions.includes("copy")) actions.push(copyEntityAction);
|
|
14495
|
+
if (deleteEnabled && !disableActions.includes("delete")) actions.push(isLinkedChildView ? unlinkEntityAction : deleteEntityAction);
|
|
14496
|
+
return (customEntityActions.length ? mergeEntityActions(actions, customEntityActions) : actions).filter((a) => a.includeInForm === void 0 || a.includeInForm);
|
|
14497
|
+
}, [
|
|
14498
|
+
canCreate,
|
|
14499
|
+
canDelete,
|
|
14500
|
+
collection,
|
|
14501
|
+
path,
|
|
14502
|
+
customizationController.entityActions?.length,
|
|
14503
|
+
entity,
|
|
14504
|
+
collection.disableDefaultActions,
|
|
14505
|
+
isLinkedChildView
|
|
14506
|
+
]);
|
|
14507
|
+
}
|
|
14508
|
+
//#endregion
|
|
14509
|
+
//#region src/hooks/useEntityDisplayTitle.tsx
|
|
14510
|
+
/**
|
|
14511
|
+
* The human name for a record, for the identity bar and the breadcrumb.
|
|
14512
|
+
*
|
|
14513
|
+
* The guard on `status` is the point. The title resolver walks candidate
|
|
14514
|
+
* properties and returns the first non-empty one, and on a brand new entity the
|
|
14515
|
+
* only populated values are the property defaults — so creating a blog post
|
|
14516
|
+
* opened a page headed **draft**, the default of its `status` enum. A record
|
|
14517
|
+
* that does not exist yet has no name; it has an intent.
|
|
14518
|
+
*/
|
|
14519
|
+
function useEntityDisplayTitle({ collection, values, entityId, status }) {
|
|
14520
|
+
const isNew = status === "new";
|
|
14521
|
+
const titlePropertyKey = getEntityTitlePropertyKeyForEntity(collection, values, entityId);
|
|
14522
|
+
const rawTitle = !isNew && values && titlePropertyKey ? getValueInPath(values, titlePropertyKey) : void 0;
|
|
14523
|
+
const titleUser = useResolvedUser(isUserSelectProperty(collection, titlePropertyKey) && typeof rawTitle === "string" ? rawTitle : void 0);
|
|
14524
|
+
const singular = collection.singularName ?? collection.name;
|
|
14525
|
+
if (isNew) return `New ${singular.toLowerCase()}`;
|
|
14526
|
+
if (titleUser) return getUserLabel(titleUser);
|
|
14527
|
+
if (rawTitle !== void 0 && rawTitle !== null) {
|
|
14528
|
+
const resolved = resolveTitleToString(rawTitle);
|
|
14529
|
+
if (resolved) return resolved;
|
|
14530
|
+
}
|
|
14531
|
+
return singular;
|
|
14532
|
+
}
|
|
14533
|
+
//#endregion
|
|
13731
14534
|
//#region src/components/EditViewBinding.tsx
|
|
13732
|
-
var EntityHistoryView = lazy(() => import("./history-g9688IaK.js").then((m) => ({ default: m.EntityHistoryView })));
|
|
13733
14535
|
/**
|
|
13734
14536
|
* This is the default view that is used as the content of a side panel when
|
|
13735
14537
|
* a record is opened.
|
|
@@ -13778,11 +14580,12 @@ function EditViewBinding({ entityId, ...props }) {
|
|
|
13778
14580
|
children: content
|
|
13779
14581
|
});
|
|
13780
14582
|
}
|
|
13781
|
-
function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onValuesModified, onSaved, onTabChange, navigateBack, entity, initialDirtyValues, dataLoading, layout = "side_panel", barActions, status, setStatus, formProps, canEdit }) {
|
|
14583
|
+
function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onValuesModified, onSaved, onTabChange, navigateBack, entity, initialDirtyValues, dataLoading, layout = "side_panel", barActions, barActionsStart, status, setStatus, formProps, canEdit }) {
|
|
13782
14584
|
const ResolvedFormActions = useComponentOverride("EditView.FormActions", EditFormActions);
|
|
13783
14585
|
const ResolvedEntityForm = useComponentOverride("Entity.Form", EntityFormBinding);
|
|
13784
14586
|
const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
|
|
13785
14587
|
const context = useRebaseContext();
|
|
14588
|
+
const adminContext = useAdminContext();
|
|
13786
14589
|
const urlController = useUrlController();
|
|
13787
14590
|
const navigate = useNavigate();
|
|
13788
14591
|
const [usedEntity, setUsedEntity] = useState(entity);
|
|
@@ -13793,7 +14596,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
13793
14596
|
useLargeLayout();
|
|
13794
14597
|
const customizationController = useCustomizationController();
|
|
13795
14598
|
const plugins = customizationController.plugins;
|
|
13796
|
-
const
|
|
14599
|
+
const formActionTopProps = useMemo(() => ({
|
|
13797
14600
|
entityId,
|
|
13798
14601
|
parentCollectionSlugs,
|
|
13799
14602
|
parentEntityIds,
|
|
@@ -13814,7 +14617,8 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
13814
14617
|
context,
|
|
13815
14618
|
formContext,
|
|
13816
14619
|
layout
|
|
13817
|
-
])
|
|
14620
|
+
]);
|
|
14621
|
+
const pluginActionsTop = useSlot("form.actions.top", formActionTopProps);
|
|
13818
14622
|
const defaultSelectedView = useMemo(() => resolveDefaultSelectedView(collection ? collection.defaultSelectedView : void 0, {
|
|
13819
14623
|
status,
|
|
13820
14624
|
entityId
|
|
@@ -13840,24 +14644,17 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
13840
14644
|
const customViewsCount = customViews?.length ?? 0;
|
|
13841
14645
|
const includeJsonView = collection.includeJsonView === void 0 ? true : collection.includeJsonView;
|
|
13842
14646
|
const includeHistoryView = Boolean(collection.history);
|
|
13843
|
-
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0
|
|
14647
|
+
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
|
|
13844
14648
|
const { resolvedEntityViews } = resolvedSelectedEntityView(customViews, customizationController, void 0, canEdit);
|
|
14649
|
+
const legacyInspectorTab = selectedTab === "__json" ? "json" : selectedTab === "__rebase_history" ? "history" : void 0;
|
|
13845
14650
|
const activeTab = useMemo(() => {
|
|
13846
|
-
return new Set([
|
|
14651
|
+
return /* @__PURE__ */ new Set([
|
|
13847
14652
|
"__main_##Q$SC^#S6",
|
|
13848
|
-
...includeJsonView ? ["__json"] : [],
|
|
13849
|
-
...includeHistoryView ? ["__rebase_history"] : [],
|
|
13850
14653
|
...resolvedEntityViews.map((v) => v.key),
|
|
13851
14654
|
...subcollections.map((s) => s.slug)
|
|
13852
14655
|
]);
|
|
13853
|
-
}, [
|
|
13854
|
-
includeJsonView,
|
|
13855
|
-
includeHistoryView,
|
|
13856
|
-
resolvedEntityViews,
|
|
13857
|
-
subcollections
|
|
13858
|
-
]).has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
|
|
14656
|
+
}, [resolvedEntityViews, subcollections]).has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
|
|
13859
14657
|
const { selectedEntityView, selectedSecondaryForm } = resolvedSelectedEntityView(customViews, customizationController, activeTab, canEdit);
|
|
13860
|
-
const actionsAtTheBottom = layout === "side_panel" || layout === "dialog" || selectedEntityView?.includeActions === "bottom";
|
|
13861
14658
|
const mainViewVisible = activeTab === "__main_##Q$SC^#S6" || Boolean(selectedSecondaryForm);
|
|
13862
14659
|
const mountedTabsRef = useRef(/* @__PURE__ */ new Set());
|
|
13863
14660
|
if (activeTab) mountedTabsRef.current.add(activeTab);
|
|
@@ -13922,29 +14719,49 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
13922
14719
|
modifiedValues: usedFormContext?.formex?.values ?? usedEntity?.values,
|
|
13923
14720
|
formContext: usedFormContext
|
|
13924
14721
|
})
|
|
13925
|
-
}) })
|
|
13926
|
-
}, `custom_view_${customView.key}`);
|
|
13927
|
-
}).filter(Boolean);
|
|
13928
|
-
const globalLoading = dataLoading && !usedEntity || canEdit === void 0 && (status === "existing" || status === "copy");
|
|
13929
|
-
const
|
|
13930
|
-
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
}
|
|
13935
|
-
const
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
|
|
13944
|
-
|
|
13945
|
-
|
|
13946
|
-
|
|
13947
|
-
|
|
14722
|
+
}) })
|
|
14723
|
+
}, `custom_view_${customView.key}`);
|
|
14724
|
+
}).filter(Boolean);
|
|
14725
|
+
const globalLoading = dataLoading && !usedEntity || canEdit === void 0 && (status === "existing" || status === "copy");
|
|
14726
|
+
const displayTitle = useEntityDisplayTitle({
|
|
14727
|
+
collection,
|
|
14728
|
+
values: formContext?.values ?? usedEntity?.values,
|
|
14729
|
+
entityId,
|
|
14730
|
+
status
|
|
14731
|
+
});
|
|
14732
|
+
const formex = formContext?.formex;
|
|
14733
|
+
const hasFormErrors = Boolean(formex && Object.keys(formex.errors).length > 0 && formex.submitCount > 0);
|
|
14734
|
+
const saveDisabled = Boolean(!formContext || formContext.disabled || formex?.isSubmitting || status === "existing" && !formex?.dirty);
|
|
14735
|
+
const [inspectorTab, setInspectorTab] = useState(null);
|
|
14736
|
+
const sideDialogContext = useSideDialogContext();
|
|
14737
|
+
const canCloseAfterSave = layout === "side_panel" || layout === "dialog";
|
|
14738
|
+
const recordActions = useRecordActions({
|
|
14739
|
+
collection,
|
|
14740
|
+
path,
|
|
14741
|
+
entity: usedEntity
|
|
14742
|
+
});
|
|
14743
|
+
const recordActionItems = usedEntity && recordActions.length ? recordActions.map((action, index) => {
|
|
14744
|
+
const clickProps = {
|
|
14745
|
+
view: "form",
|
|
14746
|
+
entity: usedEntity,
|
|
14747
|
+
path,
|
|
14748
|
+
collection,
|
|
14749
|
+
context,
|
|
14750
|
+
sidePanelController: adminContext.sidePanelController,
|
|
14751
|
+
openEntityMode: layout,
|
|
14752
|
+
navigateBack: navigateBack ?? (() => void 0),
|
|
14753
|
+
formContext
|
|
14754
|
+
};
|
|
14755
|
+
return /* @__PURE__ */ jsxs(MenuItem, {
|
|
14756
|
+
disabled: !(!action.isEnabled || action.isEnabled(clickProps)),
|
|
14757
|
+
onClick: () => action.onClick(clickProps),
|
|
14758
|
+
children: [getIcon(action.icon, void 0, void 0, "smallest"), action.name]
|
|
14759
|
+
}, action.key ?? action.name ?? index);
|
|
14760
|
+
}) : null;
|
|
14761
|
+
const formPluginActions = useSlot("form.actions", formActionTopProps);
|
|
14762
|
+
useEffect(() => {
|
|
14763
|
+
if (legacyInspectorTab) setInspectorTab(legacyInspectorTab);
|
|
14764
|
+
}, [legacyInspectorTab]);
|
|
13948
14765
|
const subCollectionsViews = childViews && childViews.map(({ collection: subcollection }) => {
|
|
13949
14766
|
const subcollectionId = subcollection.slug;
|
|
13950
14767
|
const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(getCollectionDataPath(subcollection))}` : void 0;
|
|
@@ -14038,7 +14855,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
14038
14855
|
entity,
|
|
14039
14856
|
initialDirtyValues,
|
|
14040
14857
|
openEntityMode: layout,
|
|
14041
|
-
|
|
14858
|
+
showDefaultActions: false,
|
|
14042
14859
|
initialStatus: status,
|
|
14043
14860
|
className: cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className),
|
|
14044
14861
|
EntityFormActionsComponent: ResolvedFormActions,
|
|
@@ -14100,14 +14917,14 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
14100
14917
|
})
|
|
14101
14918
|
}, `entity_detail_collection_tab_${view.name}`);
|
|
14102
14919
|
});
|
|
14103
|
-
const
|
|
14104
|
-
const fullScreenButton = !barActions && (layout === "side_panel" || layout === "split" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
|
|
14920
|
+
const fullScreenButton = !barActions && (layout === "side_panel" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
|
|
14105
14921
|
title: "Open full screen",
|
|
14106
14922
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
14107
14923
|
size: "small",
|
|
14108
14924
|
onClick: () => {
|
|
14109
14925
|
const editSuffix = collection.defaultEntityAction === "view" ? "/edit" : "";
|
|
14110
|
-
|
|
14926
|
+
const entityUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}${editSuffix}`);
|
|
14927
|
+
navigate(`${entityUrl}#full`);
|
|
14111
14928
|
},
|
|
14112
14929
|
children: /* @__PURE__ */ jsx(Maximize2Icon, { size: iconSize.smallest })
|
|
14113
14930
|
})
|
|
@@ -14115,60 +14932,78 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
14115
14932
|
let result = /* @__PURE__ */ jsxs("div", {
|
|
14116
14933
|
className: "relative flex flex-col h-full w-full bg-white dark:bg-surface-800",
|
|
14117
14934
|
children: [
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14935
|
+
/* @__PURE__ */ jsx(EntityIdentityBar, {
|
|
14936
|
+
collection,
|
|
14937
|
+
title: displayTitle,
|
|
14938
|
+
entityId: status === "existing" ? entityId : void 0,
|
|
14939
|
+
status,
|
|
14940
|
+
dirty: Boolean(formContext?.formex?.dirty),
|
|
14941
|
+
saving: Boolean(formContext?.isSaving),
|
|
14942
|
+
hasErrors: hasFormErrors,
|
|
14943
|
+
saveDisabled: !canEdit || saveDisabled,
|
|
14944
|
+
onBack: layout === "full_screen" ? () => navigate(withViewMode(urlController.buildUrlCollectionPath(path))) : void 0,
|
|
14945
|
+
onSave: canEdit && formContext ? () => {
|
|
14946
|
+
sideDialogContext.setPendingClose?.(false);
|
|
14947
|
+
formContext.submit();
|
|
14948
|
+
} : void 0,
|
|
14949
|
+
onSaveAndClose: canEdit && formContext && canCloseAfterSave ? () => {
|
|
14950
|
+
sideDialogContext.setPendingClose?.(true);
|
|
14951
|
+
formContext.submit();
|
|
14952
|
+
} : void 0,
|
|
14953
|
+
onDiscard: canEdit && formContext ? () => formContext.formex.resetForm() : void 0,
|
|
14954
|
+
onInspect: includeJsonView ? () => setInspectorTab("json") : void 0,
|
|
14955
|
+
onViewHistory: includeHistoryView ? () => setInspectorTab("history") : void 0,
|
|
14956
|
+
recordActions: recordActionItems,
|
|
14957
|
+
pluginActions: formPluginActions,
|
|
14958
|
+
leading: /* @__PURE__ */ jsxs(Fragment, { children: [barActionsStart, layout === "split" && /* @__PURE__ */ jsx(SplitListCloseButton, {})] }),
|
|
14959
|
+
trailing: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
14960
|
+
pluginActionsTop,
|
|
14121
14961
|
fullScreenButton,
|
|
14122
14962
|
barActions?.({
|
|
14123
14963
|
path,
|
|
14124
14964
|
entityId,
|
|
14125
14965
|
values: formContext?.values ?? usedEntity?.values ?? {},
|
|
14126
14966
|
status
|
|
14127
|
-
}),
|
|
14128
|
-
pluginActionsTop,
|
|
14129
|
-
hasAdditionalViews && /* @__PURE__ */ jsx("div", {
|
|
14130
|
-
className: "flex-1 flex justify-end min-w-0 shrink-0",
|
|
14131
|
-
children: /* @__PURE__ */ jsxs(Tabs, {
|
|
14132
|
-
className: "!w-fit max-w-full",
|
|
14133
|
-
value: activeTab,
|
|
14134
|
-
onValueChange: (value) => {
|
|
14135
|
-
onSideTabClick(value);
|
|
14136
|
-
},
|
|
14137
|
-
children: [
|
|
14138
|
-
includeJsonView && /* @__PURE__ */ jsx(Tab, {
|
|
14139
|
-
disabled: !hasAdditionalViews,
|
|
14140
|
-
value: "__json",
|
|
14141
|
-
className: "text-sm",
|
|
14142
|
-
children: /* @__PURE__ */ jsx(CodeIcon, { size: iconSize.smallest })
|
|
14143
|
-
}),
|
|
14144
|
-
includeHistoryView && /* @__PURE__ */ jsx(Tab, {
|
|
14145
|
-
disabled: !hasAdditionalViews,
|
|
14146
|
-
value: "__rebase_history",
|
|
14147
|
-
className: "text-sm",
|
|
14148
|
-
children: /* @__PURE__ */ jsx(HistoryIcon, { size: iconSize.smallest })
|
|
14149
|
-
}),
|
|
14150
|
-
/* @__PURE__ */ jsx(Tab, {
|
|
14151
|
-
disabled: !hasAdditionalViews,
|
|
14152
|
-
value: "__main_##Q$SC^#S6",
|
|
14153
|
-
className: "text-sm min-w-[90px]",
|
|
14154
|
-
children: /* @__PURE__ */ jsxs("span", {
|
|
14155
|
-
className: "flex items-center gap-1.5",
|
|
14156
|
-
children: [getIcon(collection.icon, void 0, void 0, "smallest"), collection.singularName ?? collection.name]
|
|
14157
|
-
})
|
|
14158
|
-
}),
|
|
14159
|
-
customViewTabsStart,
|
|
14160
|
-
customViewTabsEnd,
|
|
14161
|
-
subcollectionTabs
|
|
14162
|
-
]
|
|
14163
|
-
})
|
|
14164
14967
|
})
|
|
14165
|
-
]
|
|
14968
|
+
] })
|
|
14969
|
+
}),
|
|
14970
|
+
hasAdditionalViews && /* @__PURE__ */ jsx("div", {
|
|
14971
|
+
className: cls("h-10 shrink-0 flex items-stretch border-b px-2 min-w-0", defaultBorderMixin),
|
|
14972
|
+
children: /* @__PURE__ */ jsxs(Tabs, {
|
|
14973
|
+
className: "!w-full",
|
|
14974
|
+
innerClassName: "h-full",
|
|
14975
|
+
variant: "boxy",
|
|
14976
|
+
value: activeTab,
|
|
14977
|
+
onValueChange: (value) => {
|
|
14978
|
+
onSideTabClick(value);
|
|
14979
|
+
},
|
|
14980
|
+
children: [
|
|
14981
|
+
/* @__PURE__ */ jsx(Tab, {
|
|
14982
|
+
value: "__main_##Q$SC^#S6",
|
|
14983
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
14984
|
+
className: "flex items-center gap-1.5",
|
|
14985
|
+
children: [getIcon(collection.icon, void 0, void 0, "smallest"), collection.singularName ?? collection.name]
|
|
14986
|
+
})
|
|
14987
|
+
}),
|
|
14988
|
+
customViewTabsStart,
|
|
14989
|
+
customViewTabsEnd,
|
|
14990
|
+
subcollectionTabs
|
|
14991
|
+
]
|
|
14992
|
+
})
|
|
14166
14993
|
}),
|
|
14167
14994
|
globalLoading ? /* @__PURE__ */ jsx(EntityFormSkeleton, { collection }) : /* @__PURE__ */ jsxs(Fragment, { children: [entityReadOnlyView, entityView] }),
|
|
14168
|
-
jsonView,
|
|
14169
|
-
historyView,
|
|
14170
14995
|
customViewsView,
|
|
14171
|
-
subCollectionsViews
|
|
14996
|
+
subCollectionsViews,
|
|
14997
|
+
/* @__PURE__ */ jsx(EntityInspector, {
|
|
14998
|
+
tab: inspectorTab,
|
|
14999
|
+
onTabChange: setInspectorTab,
|
|
15000
|
+
onClose: () => setInspectorTab(null),
|
|
15001
|
+
collection,
|
|
15002
|
+
entity: usedEntity,
|
|
15003
|
+
formContext,
|
|
15004
|
+
values: formContext?.values ?? usedEntity?.values,
|
|
15005
|
+
includeHistory: includeHistoryView
|
|
15006
|
+
})
|
|
14172
15007
|
]
|
|
14173
15008
|
});
|
|
14174
15009
|
if (plugins && plugins.length > 0) result = /* @__PURE__ */ jsx(PluginProviderStack, {
|
|
@@ -14397,21 +15232,25 @@ function SidePanelBinding(props) {
|
|
|
14397
15232
|
parentCollectionSlugs,
|
|
14398
15233
|
parentEntityIds,
|
|
14399
15234
|
onEditClick: openEditView,
|
|
14400
|
-
|
|
15235
|
+
barActionsStart: /* @__PURE__ */ jsx(IconButton, {
|
|
15236
|
+
className: "self-center",
|
|
15237
|
+
size: "small",
|
|
15238
|
+
onClick: onClose,
|
|
15239
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
15240
|
+
}),
|
|
15241
|
+
barActions: ({ status, values }) => /* @__PURE__ */ jsx("div", {
|
|
14401
15242
|
className: "flex gap-1",
|
|
14402
|
-
children:
|
|
14403
|
-
className: "self-center",
|
|
14404
|
-
size: "small",
|
|
14405
|
-
onClick: onClose,
|
|
14406
|
-
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
14407
|
-
}), allowFullScreen && /* @__PURE__ */ jsx(IconButton, {
|
|
15243
|
+
children: allowFullScreen && /* @__PURE__ */ jsx(IconButton, {
|
|
14408
15244
|
className: "self-center",
|
|
14409
15245
|
size: "small",
|
|
14410
15246
|
onClick: () => {
|
|
14411
|
-
if (entityId)
|
|
15247
|
+
if (entityId) {
|
|
15248
|
+
const fullScreenUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}`);
|
|
15249
|
+
navigate(fullScreenUrl, { state: null });
|
|
15250
|
+
}
|
|
14412
15251
|
},
|
|
14413
15252
|
children: /* @__PURE__ */ jsx(Maximize2Icon, {})
|
|
14414
|
-
})
|
|
15253
|
+
})
|
|
14415
15254
|
}),
|
|
14416
15255
|
onTabChange: ({ entityId: tabEntityId, selectedTab, collection: paramCollection }) => {
|
|
14417
15256
|
if (collection?.openEntityMode === "dialog" || paramCollection?.openEntityMode === "dialog") return;
|
|
@@ -14432,25 +15271,31 @@ function SidePanelBinding(props) {
|
|
|
14432
15271
|
onValuesModified,
|
|
14433
15272
|
onSaved: onUpdate,
|
|
14434
15273
|
navigateBack: closeEditView,
|
|
14435
|
-
|
|
15274
|
+
barActionsStart: /* @__PURE__ */ jsx(IconButton, {
|
|
15275
|
+
className: "self-center",
|
|
15276
|
+
size: "small",
|
|
15277
|
+
onClick: onClose,
|
|
15278
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
15279
|
+
}),
|
|
15280
|
+
barActions: ({ status, values }) => /* @__PURE__ */ jsx("div", {
|
|
14436
15281
|
className: "flex gap-1",
|
|
14437
|
-
children:
|
|
14438
|
-
className: "self-center",
|
|
14439
|
-
size: "small",
|
|
14440
|
-
onClick: onClose,
|
|
14441
|
-
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
14442
|
-
}), allowFullScreen && /* @__PURE__ */ jsx(IconButton, {
|
|
15282
|
+
children: allowFullScreen && /* @__PURE__ */ jsx(IconButton, {
|
|
14443
15283
|
className: "self-center",
|
|
14444
15284
|
size: "small",
|
|
14445
15285
|
onClick: () => {
|
|
14446
15286
|
saveEntityToMemoryCache(status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId, values);
|
|
14447
15287
|
setBlocked(false);
|
|
14448
15288
|
setBlockedNavigationMessage(void 0);
|
|
14449
|
-
if (entityId)
|
|
14450
|
-
|
|
15289
|
+
if (entityId) {
|
|
15290
|
+
const fullScreenUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}`);
|
|
15291
|
+
navigate(fullScreenUrl, { state: null });
|
|
15292
|
+
} else {
|
|
15293
|
+
const fullScreenUrl = urlController.buildUrlCollectionPath(path);
|
|
15294
|
+
navigate(fullScreenUrl + "#new", { state: null });
|
|
15295
|
+
}
|
|
14451
15296
|
},
|
|
14452
15297
|
children: /* @__PURE__ */ jsx(Maximize2Icon, {})
|
|
14453
|
-
})
|
|
15298
|
+
})
|
|
14454
15299
|
}),
|
|
14455
15300
|
onTabChange: ({ entityId: tabEntityId, selectedTab, collection: paramCollection }) => {
|
|
14456
15301
|
if (collection?.openEntityMode === "dialog" || paramCollection?.openEntityMode === "dialog") return;
|
|
@@ -14485,39 +15330,25 @@ function ResolvedSidePanelBinding(props) {
|
|
|
14485
15330
|
}
|
|
14486
15331
|
var NEW_URL_HASH = "new_side";
|
|
14487
15332
|
var SIDE_URL_HASH = "side";
|
|
15333
|
+
/**
|
|
15334
|
+
* How wide the side panel opens.
|
|
15335
|
+
*
|
|
15336
|
+
* One width for the whole panel, whatever tab is showing. It used to depend on
|
|
15337
|
+
* the selected tab — the form got one width and a subcollection or custom view
|
|
15338
|
+
* got `calc(55vw + 768px)` — so the panel physically resized under the cursor
|
|
15339
|
+
* every time you switched tabs. And the "form" width was derived from how
|
|
15340
|
+
* deeply nested the collection's *properties* were, which is not a thing anyone
|
|
15341
|
+
* can predict from looking at the panel.
|
|
15342
|
+
*
|
|
15343
|
+
* Now: `width` prop, then the collection's `sideDialogWidth`, then a default
|
|
15344
|
+
* sized for the content the panel actually has to hold — the form column plus
|
|
15345
|
+
* its metadata rail — capped so it never eats the whole window.
|
|
15346
|
+
*/
|
|
14488
15347
|
function getEntityViewWidth(props, small, customizationController) {
|
|
14489
15348
|
if (small) return CONTAINER_FULL_WIDTH;
|
|
14490
|
-
|
|
14491
|
-
|
|
14492
|
-
|
|
14493
|
-
if (props.width) resolvedWidth = typeof props.width === "number" ? `${props.width}px` : props.width;
|
|
14494
|
-
else if (props.collection?.sideDialogWidth) resolvedWidth = typeof props.collection.sideDialogWidth === "number" ? `${props.collection.sideDialogWidth}px` : props.collection.sideDialogWidth;
|
|
14495
|
-
if (!shouldUseSmallLayout) return `calc(${ADDITIONAL_TAB_WIDTH} + ${resolvedWidth ?? FORM_CONTAINER_WIDTH})`;
|
|
14496
|
-
else if (resolvedWidth) return resolvedWidth;
|
|
14497
|
-
else if (!props.collection) return FORM_CONTAINER_WIDTH;
|
|
14498
|
-
else return calculateCollectionDesiredWidth(props.collection);
|
|
14499
|
-
}
|
|
14500
|
-
var collectionViewWidthCache = {};
|
|
14501
|
-
function calculateCollectionDesiredWidth(collection) {
|
|
14502
|
-
if (collectionViewWidthCache[collection.slug]) return collectionViewWidthCache[collection.slug];
|
|
14503
|
-
let result = FORM_CONTAINER_WIDTH;
|
|
14504
|
-
if (collection?.properties) {
|
|
14505
|
-
const values = Object.values(collection.properties).map((p) => getNestedPropertiesDepth(p));
|
|
14506
|
-
const maxDepth = Math.max(...values);
|
|
14507
|
-
if (maxDepth < 3) result = FORM_CONTAINER_WIDTH;
|
|
14508
|
-
else result = 768 + 32 * (maxDepth - 2) + "px";
|
|
14509
|
-
}
|
|
14510
|
-
collectionViewWidthCache[collection.slug] = result;
|
|
14511
|
-
return result;
|
|
14512
|
-
}
|
|
14513
|
-
function getNestedPropertiesDepth(property, accumulator = 0) {
|
|
14514
|
-
if (property.type === "map" && property.properties) {
|
|
14515
|
-
const values = Object.values(property.properties).flatMap((childProperty) => getNestedPropertiesDepth(childProperty, accumulator + 1));
|
|
14516
|
-
return Math.max(...values);
|
|
14517
|
-
} else if (property.type === "array" && property.oneOf) return accumulator + 3;
|
|
14518
|
-
else if (property.type === "array" && property.of) if (Array.isArray(property.of)) return Math.max(...property.of.map((p) => getNestedPropertiesDepth(p, accumulator + 1)));
|
|
14519
|
-
else return getNestedPropertiesDepth(property.of, accumulator + 1);
|
|
14520
|
-
else return accumulator + 1;
|
|
15349
|
+
if (props.width) return typeof props.width === "number" ? `${props.width}px` : props.width;
|
|
15350
|
+
if (props.collection?.sideDialogWidth) return typeof props.collection.sideDialogWidth === "number" ? `${props.collection.sideDialogWidth}px` : props.collection.sideDialogWidth;
|
|
15351
|
+
return SIDE_PANEL_DEFAULT_WIDTH;
|
|
14521
15352
|
}
|
|
14522
15353
|
var useBuildSidePanel = (collectionRegistryController, urlController, navigationStateController, sideDialogsController, authController) => {
|
|
14523
15354
|
const location = useLocation();
|
|
@@ -14761,6 +15592,7 @@ function useResolvedNavigationFrom({ path }) {
|
|
|
14761
15592
|
function useHistory(params) {
|
|
14762
15593
|
const { slug, entityId, enabled = true, pageSize = 10 } = params;
|
|
14763
15594
|
const apiConfig = useApiConfig();
|
|
15595
|
+
const apiBase = useApiBase();
|
|
14764
15596
|
const [entries, setEntries] = useState([]);
|
|
14765
15597
|
const [total, setTotal] = useState(0);
|
|
14766
15598
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -14777,7 +15609,7 @@ function useHistory(params) {
|
|
|
14777
15609
|
const token = await apiConfig.getAuthToken?.();
|
|
14778
15610
|
const headers = { "Content-Type": "application/json" };
|
|
14779
15611
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
14780
|
-
const url = `${
|
|
15612
|
+
const url = `${apiBase}/data/${slug}/${entityId}/history?limit=${pageSize}&offset=${currentOffset}`;
|
|
14781
15613
|
const response = await fetch(url, {
|
|
14782
15614
|
headers,
|
|
14783
15615
|
signal
|
|
@@ -14802,6 +15634,7 @@ function useHistory(params) {
|
|
|
14802
15634
|
}
|
|
14803
15635
|
}, [
|
|
14804
15636
|
apiConfig,
|
|
15637
|
+
apiBase,
|
|
14805
15638
|
slug,
|
|
14806
15639
|
entityId,
|
|
14807
15640
|
enabled,
|
|
@@ -14846,7 +15679,7 @@ function useHistory(params) {
|
|
|
14846
15679
|
const token = await apiConfig.getAuthToken?.();
|
|
14847
15680
|
const headers = { "Content-Type": "application/json" };
|
|
14848
15681
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
14849
|
-
const url = `${
|
|
15682
|
+
const url = `${apiBase}/data/${slug}/${entityId}/history/${historyId}/revert`;
|
|
14850
15683
|
const response = await fetch(url, {
|
|
14851
15684
|
method: "POST",
|
|
14852
15685
|
headers
|
|
@@ -14861,6 +15694,7 @@ function useHistory(params) {
|
|
|
14861
15694
|
return result.data;
|
|
14862
15695
|
}, [
|
|
14863
15696
|
apiConfig,
|
|
15697
|
+
apiBase,
|
|
14864
15698
|
slug,
|
|
14865
15699
|
entityId
|
|
14866
15700
|
]);
|
|
@@ -15161,6 +15995,24 @@ function filterViewsByRole(views, authController) {
|
|
|
15161
15995
|
//#endregion
|
|
15162
15996
|
//#region src/hooks/navigation/utils.ts
|
|
15163
15997
|
var NAVIGATION_DEFAULT_GROUP_NAME = "Views";
|
|
15998
|
+
/**
|
|
15999
|
+
* Group names that sink to the bottom of the drawer on their name alone.
|
|
16000
|
+
*
|
|
16001
|
+
* A magic string, and it stays one for compatibility: apps have been relying on
|
|
16002
|
+
* `group: "Settings"` landing last since before there was a flag, and taking it
|
|
16003
|
+
* away would silently reshuffle their navigation.
|
|
16004
|
+
*
|
|
16005
|
+
* It is a bad way to ask for the behaviour, though — it is invisible from the
|
|
16006
|
+
* `group?: string` type, and it is defeated by the most ordinary edit there is:
|
|
16007
|
+
* translating the label. `group: "Ajustes"` is the same group to a reader and a
|
|
16008
|
+
* different one to this comparison, so the ordering just quietly stops
|
|
16009
|
+
* happening. Set {@link AppView.pinToBottom} instead, which says so.
|
|
16010
|
+
*/
|
|
16011
|
+
var NAVIGATION_BOTTOM_GROUP_NAMES = ["Admin", "Settings"];
|
|
16012
|
+
/** Whether a group sinks to the bottom by virtue of its name. @see NAVIGATION_BOTTOM_GROUP_NAMES */
|
|
16013
|
+
function isBottomPinnedGroupName(groupName) {
|
|
16014
|
+
return groupName !== void 0 && NAVIGATION_BOTTOM_GROUP_NAMES.includes(groupName);
|
|
16015
|
+
}
|
|
15164
16016
|
function getGroup(collectionOrView) {
|
|
15165
16017
|
const trimmed = collectionOrView.group?.trim();
|
|
15166
16018
|
if (!trimmed || trimmed === "") return NAVIGATION_DEFAULT_GROUP_NAME;
|
|
@@ -15562,10 +16414,9 @@ function useTopLevelNavigation(props) {
|
|
|
15562
16414
|
return acc;
|
|
15563
16415
|
}, [])
|
|
15564
16416
|
];
|
|
15565
|
-
const
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
};
|
|
16417
|
+
const pinnedGroups = /* @__PURE__ */ new Set();
|
|
16418
|
+
for (const view of [...views ?? [], ...adminViews ?? []]) if (view.pinToBottom) pinnedGroups.add(getGroup(view));
|
|
16419
|
+
const groupOrderValue = (groupName) => isBottomPinnedGroupName(groupName) || groupName !== void 0 && pinnedGroups.has(groupName) ? 1 : 0;
|
|
15569
16420
|
const usedViewsOrder = viewsOrder ?? navigationEntriesOrder;
|
|
15570
16421
|
navigationEntries = navigationEntries.sort((a, b) => {
|
|
15571
16422
|
const groupDiff = groupOrderValue(a.group) - groupOrderValue(b.group);
|
|
@@ -15591,8 +16442,8 @@ function useTopLevelNavigation(props) {
|
|
|
15591
16442
|
...additionalGroups
|
|
15592
16443
|
];
|
|
15593
16444
|
const uniqueGroupsArray = [...new Set(allDefinedGroups)];
|
|
15594
|
-
const lastGroups = uniqueGroupsArray.filter((g) => g ===
|
|
15595
|
-
const uniqueGroups = [...uniqueGroupsArray.filter((g) => g
|
|
16445
|
+
const lastGroups = uniqueGroupsArray.filter((g) => groupOrderValue(g) === 1);
|
|
16446
|
+
const uniqueGroups = [...uniqueGroupsArray.filter((g) => groupOrderValue(g) === 0), ...lastGroups];
|
|
15596
16447
|
const computedTopLevelNav = {
|
|
15597
16448
|
allowDragAndDrop: plugins?.some((plugin) => plugin.hooks?.allowDragAndDrop) ?? false,
|
|
15598
16449
|
navigationEntries,
|
|
@@ -16401,7 +17252,7 @@ function BlockEntry({ name, index, value, typeField, valueField, properties, aut
|
|
|
16401
17252
|
* and tables to the specified properties.
|
|
16402
17253
|
* @group Form fields
|
|
16403
17254
|
*/
|
|
16404
|
-
function DateTimeFieldBinding({ propertyKey, value, setValue, autoFocus, error, showError, disabled, touched, property, includeDescription }) {
|
|
17255
|
+
function DateTimeFieldBinding({ propertyKey, value, setValue, autoFocus, error, showError, disabled, touched, property, includeDescription, hideLabel }) {
|
|
16405
17256
|
const { locale } = useCustomizationController();
|
|
16406
17257
|
const internalValue = value || null;
|
|
16407
17258
|
useClearRestoreValue({
|
|
@@ -16419,7 +17270,7 @@ function DateTimeFieldBinding({ propertyKey, value, setValue, autoFocus, error,
|
|
|
16419
17270
|
locale,
|
|
16420
17271
|
error: showError,
|
|
16421
17272
|
disabled,
|
|
16422
|
-
label: /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
17273
|
+
label: hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
16423
17274
|
icon: getIconForProperty(property, "small"),
|
|
16424
17275
|
required: property.validation?.required,
|
|
16425
17276
|
className: showError ? "text-red-500 dark:text-red-500" : "text-text-secondary dark:text-text-secondary-dark",
|
|
@@ -16884,7 +17735,7 @@ function MapFieldBinding({ propertyKey, value, showError, error, disabled, prope
|
|
|
16884
17735
|
if (!property.properties) throw Error(`You need to specify a 'properties' prop (or specify a custom field) in your map property '${propertyKey}'${property.name ? ` ("${property.name}")` : ""}`);
|
|
16885
17736
|
const mapProperties = property.properties;
|
|
16886
17737
|
const mapFormView = /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", {
|
|
16887
|
-
className:
|
|
17738
|
+
className: "py-1 flex flex-col space-y-2",
|
|
16888
17739
|
children: Object.entries(mapProperties).filter(([_, property]) => !isHidden(property)).map(([entryKey, childProperty], index) => {
|
|
16889
17740
|
const thisDisabled = isReadOnly(childProperty) || Boolean(childProperty.admin?.disabled);
|
|
16890
17741
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -16914,7 +17765,6 @@ function MapFieldBinding({ propertyKey, value, showError, error, disabled, prope
|
|
|
16914
17765
|
expanded
|
|
16915
17766
|
} });
|
|
16916
17767
|
},
|
|
16917
|
-
className: property.admin?.widthPercentage !== void 0 ? "mt-8" : void 0,
|
|
16918
17768
|
innerClassName: "px-2 md:px-4 pb-2 md:pb-4 pt-1 md:pt-2 bg-white dark:bg-surface-900",
|
|
16919
17769
|
title: /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
16920
17770
|
propertyKey,
|
|
@@ -16971,7 +17821,7 @@ function canonicalizeMarkdown(md) {
|
|
|
16971
17821
|
return normalizeMarkdown(md);
|
|
16972
17822
|
}
|
|
16973
17823
|
}
|
|
16974
|
-
function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, includeDescription, showError, error, minimalistView, disabled: disabledProp, isSubmitting, context, customProps }) {
|
|
17824
|
+
function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, includeDescription, hideLabel, showError, error, minimalistView, disabled: disabledProp, isSubmitting, context, customProps }) {
|
|
16975
17825
|
useAuthController();
|
|
16976
17826
|
const disabled = disabledProp || isSubmitting;
|
|
16977
17827
|
const highlight = customProps?.highlight;
|
|
@@ -17086,7 +17936,7 @@ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, in
|
|
|
17086
17936
|
/* @__PURE__ */ jsxs("div", {
|
|
17087
17937
|
className: "flex items-center w-full",
|
|
17088
17938
|
children: [
|
|
17089
|
-
/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
17939
|
+
!hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
17090
17940
|
propertyKey,
|
|
17091
17941
|
icon: getIconForProperty(property, "small"),
|
|
17092
17942
|
required: property.validation?.required,
|
|
@@ -17126,7 +17976,7 @@ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, in
|
|
|
17126
17976
|
* and tables to the specified properties.
|
|
17127
17977
|
* @group Form fields
|
|
17128
17978
|
*/
|
|
17129
|
-
function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, property, includeDescription, size = "large", autoFocus }) {
|
|
17979
|
+
function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, property, includeDescription, hideLabel, size = "large", autoFocus }) {
|
|
17130
17980
|
const of = property.of;
|
|
17131
17981
|
if (!of) throw Error("Using wrong component ArrayEnumSelect");
|
|
17132
17982
|
if (Array.isArray(of)) throw Error("Using array properties instead of single one in `of` in ArrayProperty");
|
|
@@ -17143,11 +17993,12 @@ function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showErro
|
|
|
17143
17993
|
});
|
|
17144
17994
|
const validValue = !!value && Array.isArray(value);
|
|
17145
17995
|
const renderValue = useCallback((enumKey, list) => {
|
|
17996
|
+
const enumValue = enumKey !== void 0 ? getLabelOrConfigFrom(enumValues, enumKey) : void 0;
|
|
17146
17997
|
return /* @__PURE__ */ jsxs(EnumValuesChip, {
|
|
17147
17998
|
enumKey,
|
|
17148
17999
|
enumValues,
|
|
17149
18000
|
size: "medium",
|
|
17150
|
-
children: [
|
|
18001
|
+
children: [enumValue?.label ?? enumKey, !list && /* @__PURE__ */ jsx("button", {
|
|
17151
18002
|
className: "ml-1 ring-offset-background rounded-full outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
17152
18003
|
onMouseDown: (e) => {
|
|
17153
18004
|
e.preventDefault();
|
|
@@ -17172,7 +18023,7 @@ function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showErro
|
|
|
17172
18023
|
value: validValue ? value.map((v) => v?.toString()) : [],
|
|
17173
18024
|
disabled,
|
|
17174
18025
|
modalPopover: true,
|
|
17175
|
-
label: /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
18026
|
+
label: hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
17176
18027
|
propertyKey,
|
|
17177
18028
|
icon: getIconForProperty(property, "small"),
|
|
17178
18029
|
required: property.validation?.required,
|
|
@@ -17210,7 +18061,7 @@ function ReferenceFieldBinding(props) {
|
|
|
17210
18061
|
if (typeof props.property.path !== "string") return /* @__PURE__ */ jsx(ReadOnlyFieldBinding, { ...props });
|
|
17211
18062
|
return /* @__PURE__ */ jsx(ReferenceFieldBindingInternal, { ...props });
|
|
17212
18063
|
}
|
|
17213
|
-
function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, showError, isSubmitting, disabled, minimalistView, touched, autoFocus, property, includeDescription, size = "medium" }) {
|
|
18064
|
+
function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, showError, isSubmitting, disabled, minimalistView, touched, autoFocus, property, includeDescription, hideLabel, size = "medium" }) {
|
|
17214
18065
|
if (!property.path) throw new Error("Property path is required for ReferenceFieldBinding");
|
|
17215
18066
|
const refValue = value;
|
|
17216
18067
|
useClearRestoreValue({
|
|
@@ -17240,7 +18091,7 @@ function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, sh
|
|
|
17240
18091
|
referenceDialogController.open();
|
|
17241
18092
|
};
|
|
17242
18093
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17243
|
-
!minimalistView && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
18094
|
+
!minimalistView && !hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
17244
18095
|
propertyKey,
|
|
17245
18096
|
icon: getIconForProperty(property, "small"),
|
|
17246
18097
|
required: property.validation?.required,
|
|
@@ -17321,6 +18172,7 @@ function RepeatFieldBinding({ propertyKey, value, error, showError, isSubmitting
|
|
|
17321
18172
|
context,
|
|
17322
18173
|
partOfArray: true,
|
|
17323
18174
|
minimalistView: false,
|
|
18175
|
+
hideLabel: true,
|
|
17324
18176
|
autoFocus: internalId === lastAddedId,
|
|
17325
18177
|
index
|
|
17326
18178
|
}) });
|
|
@@ -17337,8 +18189,7 @@ function RepeatFieldBinding({ propertyKey, value, error, showError, isSubmitting
|
|
|
17337
18189
|
canAddElements,
|
|
17338
18190
|
sortable,
|
|
17339
18191
|
newDefaultEntry: getDefaultValueFor(property.of),
|
|
17340
|
-
onValueChange: (value) => setFieldValue(propertyKey, value)
|
|
17341
|
-
className: property.admin?.widthPercentage !== void 0 ? "mt-8" : void 0
|
|
18192
|
+
onValueChange: (value) => setFieldValue(propertyKey, value)
|
|
17342
18193
|
});
|
|
17343
18194
|
const title = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
17344
18195
|
propertyKey,
|
|
@@ -17382,7 +18233,7 @@ function RepeatFieldBinding({ propertyKey, value, error, showError, isSubmitting
|
|
|
17382
18233
|
* and tables to the specified properties.
|
|
17383
18234
|
* @group Form fields
|
|
17384
18235
|
*/
|
|
17385
|
-
function SelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, touched, property, includeDescription, size = "large" }) {
|
|
18236
|
+
function SelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, touched, property, includeDescription, hideLabel, size = "large" }) {
|
|
17386
18237
|
const enumValues = resolveEnumValues(property.enum ?? []);
|
|
17387
18238
|
useClearRestoreValue({
|
|
17388
18239
|
property,
|
|
@@ -17401,7 +18252,7 @@ function SelectFieldBinding({ propertyKey, value, setValue, error, showError, di
|
|
|
17401
18252
|
fullWidth: true,
|
|
17402
18253
|
position: "item-aligned",
|
|
17403
18254
|
inputClassName: cls("w-full"),
|
|
17404
|
-
label: /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
|
|
18255
|
+
label: hideLabel ? void 0 : /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
|
|
17405
18256
|
propertyKey,
|
|
17406
18257
|
children: /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
17407
18258
|
icon: getIconForProperty(property, "small"),
|
|
@@ -17482,13 +18333,13 @@ function StorageItemPreview({ name, property, value, onRemove, disabled, size, p
|
|
|
17482
18333
|
}
|
|
17483
18334
|
//#endregion
|
|
17484
18335
|
//#region src/form/field_bindings/StorageUploadFieldBinding.tsx
|
|
17485
|
-
var dropZoneClasses = "box-border relative pt-[2px] items-center border border-transparent
|
|
18336
|
+
var dropZoneClasses = "box-border relative pt-[2px] h-[172px] items-center border border-transparent outline-none rounded-md duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] focus:border-primary-solid";
|
|
17486
18337
|
var disabledClasses = fieldBackgroundDisabledMixin;
|
|
17487
18338
|
var nonActiveDropClasses = fieldBackgroundHoverMixin;
|
|
17488
18339
|
var activeDropClasses = "pt-0 border-2 border-solid";
|
|
17489
18340
|
var acceptDropClasses = "transition-colors duration-200 ease-[cubic-bezier(0,0,0.2,1)] border-2 border-solid border-green-500";
|
|
17490
18341
|
var rejectDropClasses = "transition-colors duration-200 ease-[cubic-bezier(0,0,0.2,1)] border-2 border-solid border-red-500";
|
|
17491
|
-
function StorageUploadFieldBinding({ propertyKey, value, setValue, error, showError, autoFocus, minimalistView, property, includeDescription, context, isSubmitting }) {
|
|
18342
|
+
function StorageUploadFieldBinding({ propertyKey, value, setValue, error, showError, autoFocus, minimalistView, property, includeDescription, hideLabel, context, isSubmitting }) {
|
|
17492
18343
|
useAuthController();
|
|
17493
18344
|
const storageSource = useStorageSource();
|
|
17494
18345
|
const disabled = isReadOnly(property) || !!property.admin?.disabled || isSubmitting || context.disabled;
|
|
@@ -17509,7 +18360,7 @@ function StorageUploadFieldBinding({ propertyKey, value, setValue, error, showEr
|
|
|
17509
18360
|
setValue
|
|
17510
18361
|
});
|
|
17511
18362
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17512
|
-
!minimalistView && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
18363
|
+
!minimalistView && !hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
17513
18364
|
propertyKey,
|
|
17514
18365
|
icon: getIconForProperty(property, "small"),
|
|
17515
18366
|
required: property.validation?.required,
|
|
@@ -17579,7 +18430,7 @@ function SortableStorageItem({ id, entry, property, metadata, storagePathBuilder
|
|
|
17579
18430
|
style,
|
|
17580
18431
|
...attributes,
|
|
17581
18432
|
...listeners,
|
|
17582
|
-
className: cls("rounded-md
|
|
18433
|
+
className: cls("rounded-md shrink-0"),
|
|
17583
18434
|
tabIndex: -1,
|
|
17584
18435
|
children: child
|
|
17585
18436
|
});
|
|
@@ -17619,7 +18470,7 @@ function FileDropComponent({ storage, disabled, onFilesAdded, multipleFilesSuppo
|
|
|
17619
18470
|
[disabledClasses]: disabled || isDndItemDragging
|
|
17620
18471
|
}),
|
|
17621
18472
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
17622
|
-
className: cls("flex items-center p-1 px-
|
|
18473
|
+
className: cls("flex items-center gap-2 p-1 px-3 no-scrollbar", multipleFilesSupported && internalValue.length ? "overflow-x-auto overflow-y-hidden" : "", "h-[126px]"),
|
|
17623
18474
|
children: [/* @__PURE__ */ jsx("input", {
|
|
17624
18475
|
autoFocus,
|
|
17625
18476
|
...getInputProps()
|
|
@@ -17636,10 +18487,10 @@ function FileDropComponent({ storage, disabled, onFilesAdded, multipleFilesSuppo
|
|
|
17636
18487
|
isSortable: multipleFilesSupported
|
|
17637
18488
|
}, entry.id))]
|
|
17638
18489
|
}), /* @__PURE__ */ jsx("div", {
|
|
17639
|
-
className: "
|
|
18490
|
+
className: "box-border mx-3 mb-2.5 h-[34px] flex items-center justify-center text-center",
|
|
17640
18491
|
children: /* @__PURE__ */ jsx(Typography, {
|
|
17641
18492
|
align: "center",
|
|
17642
|
-
variant: "
|
|
18493
|
+
variant: "caption",
|
|
17643
18494
|
className: disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : "",
|
|
17644
18495
|
children: helpText
|
|
17645
18496
|
})
|
|
@@ -17742,7 +18593,7 @@ function StorageUpload({ property, name, value, setInternalValue, onChange, mult
|
|
|
17742
18593
|
* and tables to the specified properties.
|
|
17743
18594
|
* @group Form fields
|
|
17744
18595
|
*/
|
|
17745
|
-
var SwitchFieldBinding = function SwitchFieldBinding({ propertyKey, value, setValue, error, showError, autoFocus, disabled, size = "large", property, includeDescription }) {
|
|
18596
|
+
var SwitchFieldBinding = function SwitchFieldBinding({ propertyKey, value, setValue, error, showError, autoFocus, disabled, size = "large", property, includeDescription, hideLabel }) {
|
|
17746
18597
|
useClearRestoreValue({
|
|
17747
18598
|
property,
|
|
17748
18599
|
value,
|
|
@@ -17754,8 +18605,8 @@ var SwitchFieldBinding = function SwitchFieldBinding({ propertyKey, value, setVa
|
|
|
17754
18605
|
value,
|
|
17755
18606
|
onValueChange: (v) => setValue(v),
|
|
17756
18607
|
error: showError,
|
|
17757
|
-
|
|
17758
|
-
label: /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18608
|
+
position: hideLabel ? "start" : void 0,
|
|
18609
|
+
label: hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
17759
18610
|
icon: getIconForProperty(property, "small"),
|
|
17760
18611
|
required: property.validation?.required,
|
|
17761
18612
|
title: property.name ?? propertyKey
|
|
@@ -17780,7 +18631,7 @@ var SwitchFieldBinding = function SwitchFieldBinding({ propertyKey, value, setVa
|
|
|
17780
18631
|
* and tables to the specified properties.
|
|
17781
18632
|
* @group Form fields
|
|
17782
18633
|
*/
|
|
17783
|
-
function TextFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, size = "large" }) {
|
|
18634
|
+
function TextFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, hideLabel, size = "large" }) {
|
|
17784
18635
|
let multiline;
|
|
17785
18636
|
let url;
|
|
17786
18637
|
if (property.type === "string") {
|
|
@@ -17813,7 +18664,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
|
|
|
17813
18664
|
if (property.email) inputType = "email";
|
|
17814
18665
|
else if (property.admin?.urlPreview) inputType = "url";
|
|
17815
18666
|
}
|
|
17816
|
-
const label = /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18667
|
+
const label = hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
17817
18668
|
icon: getIconForProperty(property, "small"),
|
|
17818
18669
|
required: property.validation?.required || property.isId === true,
|
|
17819
18670
|
title: property.name ?? propertyKey
|
|
@@ -17822,9 +18673,9 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
|
|
|
17822
18673
|
/* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
|
|
17823
18674
|
propertyKey,
|
|
17824
18675
|
children: isMultiline ? /* @__PURE__ */ jsxs("div", {
|
|
17825
|
-
className: cls("rounded-md relative max-w-full min-h-[64px]", fieldBackgroundMixin, fieldBackgroundHoverMixin, showError && error ? "border border-red-500 dark:border-red-600" : ""
|
|
18676
|
+
className: cls("rounded-md relative max-w-full min-h-[64px]", fieldBackgroundMixin, fieldBackgroundHoverMixin, showError && error ? "border border-red-500 dark:border-red-600" : ""),
|
|
17826
18677
|
children: [
|
|
17827
|
-
/* @__PURE__ */ jsx("div", {
|
|
18678
|
+
label && /* @__PURE__ */ jsx("div", {
|
|
17828
18679
|
className: "pointer-events-none absolute top-1 text-xs font-medium px-3 text-text-secondary dark:text-text-secondary-dark",
|
|
17829
18680
|
children: label
|
|
17830
18681
|
}),
|
|
@@ -17833,7 +18684,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
|
|
|
17833
18684
|
onChange,
|
|
17834
18685
|
autoFocus,
|
|
17835
18686
|
disabled,
|
|
17836
|
-
className: cls("rounded-md resize-none w-full outline-none
|
|
18687
|
+
className: cls("rounded-md resize-none w-full outline-none text-base bg-transparent min-h-[64px] px-3", label ? "pt-8 pb-2" : "py-2", disabled && "outline-none opacity-50 text-surface-accent-600 dark:text-surface-accent-500", showError && error ? "text-red-500 dark:text-red-600" : "")
|
|
17837
18688
|
}),
|
|
17838
18689
|
property.admin?.clearable && /* @__PURE__ */ jsx("div", {
|
|
17839
18690
|
className: "flex flex-row justify-center items-center absolute h-full right-0 top-0 mr-4",
|
|
@@ -17848,12 +18699,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
|
|
|
17848
18699
|
value: displayValue,
|
|
17849
18700
|
onChange,
|
|
17850
18701
|
autoFocus,
|
|
17851
|
-
|
|
17852
|
-
label: /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
17853
|
-
icon: getIconForProperty(property, "small"),
|
|
17854
|
-
required: property.validation?.required || property.isId === true,
|
|
17855
|
-
title: property.name ?? propertyKey
|
|
17856
|
-
}),
|
|
18702
|
+
label,
|
|
17857
18703
|
type: inputType,
|
|
17858
18704
|
disabled,
|
|
17859
18705
|
endAdornment: property.admin?.clearable && /* @__PURE__ */ jsx(IconButton, {
|
|
@@ -17892,7 +18738,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
|
|
|
17892
18738
|
* Vector object structure expected by Rebase driver.
|
|
17893
18739
|
* @group Form fields
|
|
17894
18740
|
*/
|
|
17895
|
-
function VectorFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, size = "large" }) {
|
|
18741
|
+
function VectorFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, hideLabel, size = "large" }) {
|
|
17896
18742
|
const isVectorObject = (val) => {
|
|
17897
18743
|
return typeof val === "object" && val !== null && "value" in val;
|
|
17898
18744
|
};
|
|
@@ -17922,7 +18768,7 @@ function VectorFieldBinding({ propertyKey, value, setValue, error, showError, di
|
|
|
17922
18768
|
value: raw.split(",").map((str) => str.trim()).filter((str) => str !== "").map(Number)
|
|
17923
18769
|
});
|
|
17924
18770
|
};
|
|
17925
|
-
const label = /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18771
|
+
const label = hideLabel ? null : /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
17926
18772
|
icon: getIconForProperty(property, "small"),
|
|
17927
18773
|
required: property.validation?.required,
|
|
17928
18774
|
title: property.name ?? propertyKey
|
|
@@ -18065,7 +18911,7 @@ var inRange = (n, [min, max]) => n >= min && n <= max;
|
|
|
18065
18911
|
*
|
|
18066
18912
|
* @group Form fields
|
|
18067
18913
|
*/
|
|
18068
|
-
function GeopointFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, size = "large" }) {
|
|
18914
|
+
function GeopointFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, hideLabel, size = "large" }) {
|
|
18069
18915
|
const coordinates = readCoordinates(value);
|
|
18070
18916
|
const [latText, setLatText] = useState(() => coordinates ? String(coordinates.latitude) : "");
|
|
18071
18917
|
const [lngText, setLngText] = useState(() => coordinates ? String(coordinates.longitude) : "");
|
|
@@ -18111,7 +18957,7 @@ function GeopointFieldBinding({ propertyKey, value, setValue, error, showError,
|
|
|
18111
18957
|
children: [
|
|
18112
18958
|
/* @__PURE__ */ jsxs("div", {
|
|
18113
18959
|
className: "flex items-center justify-between mb-1",
|
|
18114
|
-
children: [/* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18960
|
+
children: [!hideLabel && /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18115
18961
|
icon: getIconForProperty(property, "small"),
|
|
18116
18962
|
required: property.validation?.required,
|
|
18117
18963
|
title: property.name ?? propertyKey
|
|
@@ -18199,7 +19045,7 @@ function humanSize(bytes) {
|
|
|
18199
19045
|
*
|
|
18200
19046
|
* @group Form fields
|
|
18201
19047
|
*/
|
|
18202
|
-
function BinaryFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, size = "large" }) {
|
|
19048
|
+
function BinaryFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, hideLabel, size = "large" }) {
|
|
18203
19049
|
const [text, setText] = useState(typeof value === "string" ? value : "");
|
|
18204
19050
|
const [isEditing, setIsEditing] = useState(false);
|
|
18205
19051
|
useEffect(() => {
|
|
@@ -18230,7 +19076,7 @@ function BinaryFieldBinding({ propertyKey, value, setValue, error, showError, di
|
|
|
18230
19076
|
children: [
|
|
18231
19077
|
/* @__PURE__ */ jsx("div", {
|
|
18232
19078
|
className: "flex items-center justify-between mb-1",
|
|
18233
|
-
children: /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
19079
|
+
children: !hideLabel && /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18234
19080
|
icon: getIconForProperty(property, "small"),
|
|
18235
19081
|
required: property.validation?.required,
|
|
18236
19082
|
title: property.name ?? propertyKey
|
|
@@ -18426,14 +19272,14 @@ function RelationFieldBinding(props) {
|
|
|
18426
19272
|
relation
|
|
18427
19273
|
});
|
|
18428
19274
|
}
|
|
18429
|
-
function RelationSelectorBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, setValue, relation, manyRelation }) {
|
|
19275
|
+
function RelationSelectorBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, hideLabel, setValue, relation, manyRelation }) {
|
|
18430
19276
|
const singleValue = (!manyRelation && value && !Array.isArray(value) ? normalizeToEntityRelation(value) : null) ?? null;
|
|
18431
19277
|
const multipleValue = manyRelation && Array.isArray(value) ? value : [];
|
|
18432
19278
|
const selectorSize = size === "large" ? "medium" : size;
|
|
18433
19279
|
return /* @__PURE__ */ jsxs("div", {
|
|
18434
19280
|
className: "",
|
|
18435
19281
|
children: [
|
|
18436
|
-
/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
19282
|
+
!hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
18437
19283
|
propertyKey,
|
|
18438
19284
|
icon: getIconForProperty(property, "small"),
|
|
18439
19285
|
required: property.validation?.required,
|
|
@@ -18461,7 +19307,7 @@ function RelationSelectorBinding({ propertyKey, value, size, error, showError, d
|
|
|
18461
19307
|
]
|
|
18462
19308
|
});
|
|
18463
19309
|
}
|
|
18464
|
-
function SingleRelationFieldBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, setValue, relation }) {
|
|
19310
|
+
function SingleRelationFieldBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, hideLabel, setValue, relation }) {
|
|
18465
19311
|
const normalizedValue = value && !Array.isArray(value) ? normalizeToEntityRelation(value) : null;
|
|
18466
19312
|
const validValue = !!normalizedValue;
|
|
18467
19313
|
const collection = relation?.target();
|
|
@@ -18483,7 +19329,7 @@ function SingleRelationFieldBinding({ propertyKey, value, size, error, showError
|
|
|
18483
19329
|
};
|
|
18484
19330
|
const usedRelation = Array.isArray(value) ? void 0 : normalizedValue ?? void 0;
|
|
18485
19331
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
18486
|
-
/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
19332
|
+
!hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
18487
19333
|
propertyKey,
|
|
18488
19334
|
icon: getIconForProperty(property, "small"),
|
|
18489
19335
|
required: property.validation?.required,
|
|
@@ -18532,10 +19378,10 @@ function SingleRelationFieldBinding({ propertyKey, value, size, error, showError
|
|
|
18532
19378
|
* and tables to the specified properties.
|
|
18533
19379
|
* @group Form fields
|
|
18534
19380
|
*/
|
|
18535
|
-
function UserSelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, touched, property, includeDescription, size = "large" }) {
|
|
19381
|
+
function UserSelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, touched, property, includeDescription, hideLabel, size = "large" }) {
|
|
18536
19382
|
const selectorSize = size === "large" ? "medium" : size;
|
|
18537
19383
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
18538
|
-
/* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
|
|
19384
|
+
!hideLabel && /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
|
|
18539
19385
|
propertyKey,
|
|
18540
19386
|
children: /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18541
19387
|
icon: getIconForProperty(property, "small"),
|
|
@@ -19047,7 +19893,7 @@ var PropertyFieldBinding = React.memo(PropertyFieldBindingInternal, (a, b) => {
|
|
|
19047
19893
|
if (shouldPropertyReRender(b.property)) return false;
|
|
19048
19894
|
return false;
|
|
19049
19895
|
});
|
|
19050
|
-
function PropertyFieldBindingInternal({ propertyKey, property, context, includeDescription, underlyingValueHasChanged, disabled: disabledProp, partOfArray, partOfBlock, minimalistView, autoFocus, index, size, onPropertyChange }) {
|
|
19896
|
+
function PropertyFieldBindingInternal({ propertyKey, property, context, includeDescription, hideLabel, underlyingValueHasChanged, disabled: disabledProp, partOfArray, partOfBlock, minimalistView, autoFocus, index, size, onPropertyChange }) {
|
|
19051
19897
|
const authController = useAuthController();
|
|
19052
19898
|
const customizationController = useCustomizationController();
|
|
19053
19899
|
return /* @__PURE__ */ jsx(Field, {
|
|
@@ -19099,6 +19945,7 @@ function PropertyFieldBindingInternal({ propertyKey, property, context, includeD
|
|
|
19099
19945
|
propertyKey,
|
|
19100
19946
|
property: resolvedProperty,
|
|
19101
19947
|
includeDescription,
|
|
19948
|
+
hideLabel,
|
|
19102
19949
|
underlyingValueHasChanged,
|
|
19103
19950
|
context,
|
|
19104
19951
|
disabled,
|
|
@@ -19114,7 +19961,7 @@ function PropertyFieldBindingInternal({ propertyKey, property, context, includeD
|
|
|
19114
19961
|
}
|
|
19115
19962
|
}, propertyKey);
|
|
19116
19963
|
}
|
|
19117
|
-
function FieldInternal({ Component, componentProps: { propertyKey, property, includeDescription, underlyingValueHasChanged, partOfArray, partOfBlock, minimalistView, autoFocus, context, disabled, size, onPropertyChange }, formexFieldProps }) {
|
|
19964
|
+
function FieldInternal({ Component, componentProps: { propertyKey, property, includeDescription, hideLabel, underlyingValueHasChanged, partOfArray, partOfBlock, minimalistView, autoFocus, context, disabled, size, onPropertyChange }, formexFieldProps }) {
|
|
19118
19965
|
const { plugins } = useCustomizationController();
|
|
19119
19966
|
const customFieldProps = property.admin?.customProps;
|
|
19120
19967
|
const value = formexFieldProps.field.value;
|
|
@@ -19148,6 +19995,7 @@ function FieldInternal({ Component, componentProps: { propertyKey, property, inc
|
|
|
19148
19995
|
showError,
|
|
19149
19996
|
isSubmitting,
|
|
19150
19997
|
includeDescription: includeDescription ?? true,
|
|
19998
|
+
hideLabel: hideLabel ?? false,
|
|
19151
19999
|
property,
|
|
19152
20000
|
disabled: disabled ?? false,
|
|
19153
20001
|
underlyingValueHasChanged: underlyingValueHasChanged ?? false,
|
|
@@ -19227,22 +20075,18 @@ var ErrorFocus = ({ containerRef }) => {
|
|
|
19227
20075
|
};
|
|
19228
20076
|
//#endregion
|
|
19229
20077
|
//#region src/form/EntityFormActions.tsx
|
|
19230
|
-
|
|
19231
|
-
|
|
19232
|
-
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19239
|
-
|
|
19240
|
-
|
|
19241
|
-
|
|
19242
|
-
formex,
|
|
19243
|
-
className: layout === "responsive" ? "@6xl:hidden" : void 0
|
|
19244
|
-
};
|
|
19245
|
-
const sideActionsProps = {
|
|
20078
|
+
/**
|
|
20079
|
+
* The form's own action bar.
|
|
20080
|
+
*
|
|
20081
|
+
* There used to be two variants: this one, and a `w-80 2xl:w-96` sticky rail
|
|
20082
|
+
* holding the same two buttons above several hundred pixels of nothing — a
|
|
20083
|
+
* fifth of a 1440px viewport reserved for a Save button. The rail is gone. When
|
|
20084
|
+
* the container supplies its own actions (the entity view's identity bar does)
|
|
20085
|
+
* it passes `showDefaultActions={false}`, and this renders only what the bar
|
|
20086
|
+
* cannot host: the saving error, plugin actions, and collection form actions.
|
|
20087
|
+
*/
|
|
20088
|
+
function EntityFormActions({ path, collection, entity, savingError, formex, disabled, status, pluginActions, openEntityMode, navigateBack, formContext, showDefaultActions = true }) {
|
|
20089
|
+
return buildBottomActions({
|
|
19246
20090
|
path,
|
|
19247
20091
|
savingError,
|
|
19248
20092
|
entity,
|
|
@@ -19254,17 +20098,16 @@ function EntityFormActions({ path, collection, entity, layout, savingError, form
|
|
|
19254
20098
|
navigateBack,
|
|
19255
20099
|
formContext,
|
|
19256
20100
|
formex,
|
|
19257
|
-
|
|
19258
|
-
};
|
|
19259
|
-
const bottomActions = buildBottomActions(bottomActionsProps);
|
|
19260
|
-
const sideActions = buildSideActions(sideActionsProps);
|
|
19261
|
-
if (layout === "responsive") return /* @__PURE__ */ jsxs(Fragment, { children: [bottomActions, sideActions] });
|
|
19262
|
-
return layout === "bottom" ? bottomActions : sideActions;
|
|
20101
|
+
showDefaultActions
|
|
20102
|
+
});
|
|
19263
20103
|
}
|
|
19264
|
-
function buildBottomActions({ savingError, entity, path, formActions, collection, disabled, status, pluginActions, openEntityMode, navigateBack, formContext, formex, className }) {
|
|
20104
|
+
function buildBottomActions({ savingError, entity, path, formActions, collection, disabled, status, pluginActions, openEntityMode, navigateBack, formContext, formex, showDefaultActions, className }) {
|
|
19265
20105
|
const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
20106
|
+
const entityActions = entity ? formActions ?? [] : [];
|
|
20107
|
+
const hasPluginActions = Boolean(pluginActions && pluginActions.length > 0);
|
|
20108
|
+
if (!showDefaultActions && !savingError && !entityActions.length && !hasPluginActions) return null;
|
|
19266
20109
|
return /* @__PURE__ */ jsxs(DialogActions, {
|
|
19267
|
-
position: "
|
|
20110
|
+
position: "sticky",
|
|
19268
20111
|
className,
|
|
19269
20112
|
children: [
|
|
19270
20113
|
savingError && /* @__PURE__ */ jsx("div", {
|
|
@@ -19274,9 +20117,9 @@ function buildBottomActions({ savingError, entity, path, formActions, collection
|
|
|
19274
20117
|
children: savingError.message
|
|
19275
20118
|
})
|
|
19276
20119
|
}),
|
|
19277
|
-
|
|
20120
|
+
entityActions.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
19278
20121
|
className: "grow flex overflow-auto no-scrollbar",
|
|
19279
|
-
children:
|
|
20122
|
+
children: entityActions.map((action) => /* @__PURE__ */ jsx(IconButton, {
|
|
19280
20123
|
color: "primary",
|
|
19281
20124
|
onClick: (event) => {
|
|
19282
20125
|
event.stopPropagation();
|
|
@@ -19296,14 +20139,13 @@ function buildBottomActions({ savingError, entity, path, formActions, collection
|
|
|
19296
20139
|
}, action.name))
|
|
19297
20140
|
}),
|
|
19298
20141
|
pluginActions,
|
|
19299
|
-
/* @__PURE__ */ jsx(Button, {
|
|
20142
|
+
showDefaultActions && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
|
|
19300
20143
|
variant: "text",
|
|
19301
20144
|
disabled: disabled || formex.isSubmitting,
|
|
19302
20145
|
color: "primary",
|
|
19303
20146
|
type: "reset",
|
|
19304
20147
|
children: status === "existing" ? "Discard" : "Clear"
|
|
19305
|
-
}),
|
|
19306
|
-
/* @__PURE__ */ jsx(Tooltip, {
|
|
20148
|
+
}), /* @__PURE__ */ jsx(Tooltip, {
|
|
19307
20149
|
title: hasErrors ? "Fix highlighted errors before saving" : void 0,
|
|
19308
20150
|
children: /* @__PURE__ */ jsxs(LoadingButton, {
|
|
19309
20151
|
variant: "filled",
|
|
@@ -19318,47 +20160,7 @@ function buildBottomActions({ savingError, entity, path, formActions, collection
|
|
|
19318
20160
|
status === "new" && "Create"
|
|
19319
20161
|
]
|
|
19320
20162
|
})
|
|
19321
|
-
})
|
|
19322
|
-
]
|
|
19323
|
-
});
|
|
19324
|
-
}
|
|
19325
|
-
function buildSideActions({ savingError, entity, formActions, path, openEntityMode, collection, disabled, status, pluginActions, formex, className }) {
|
|
19326
|
-
const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
19327
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
19328
|
-
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),
|
|
19329
|
-
children: [
|
|
19330
|
-
/* @__PURE__ */ jsx(Tooltip, {
|
|
19331
|
-
title: hasErrors ? "Fix highlighted errors before saving" : void 0,
|
|
19332
|
-
children: /* @__PURE__ */ jsxs(LoadingButton, {
|
|
19333
|
-
fullWidth: true,
|
|
19334
|
-
variant: "filled",
|
|
19335
|
-
color: "primary",
|
|
19336
|
-
type: "submit",
|
|
19337
|
-
loading: formex.isSubmitting,
|
|
19338
|
-
startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
|
|
19339
|
-
disabled: disabled || formex.isSubmitting,
|
|
19340
|
-
children: [
|
|
19341
|
-
status === "existing" && "Save",
|
|
19342
|
-
status === "copy" && "Create copy",
|
|
19343
|
-
status === "new" && "Create"
|
|
19344
|
-
]
|
|
19345
|
-
})
|
|
19346
|
-
}),
|
|
19347
|
-
/* @__PURE__ */ jsx(Button, {
|
|
19348
|
-
fullWidth: true,
|
|
19349
|
-
variant: "text",
|
|
19350
|
-
disabled: disabled || formex.isSubmitting,
|
|
19351
|
-
type: "reset",
|
|
19352
|
-
children: status === "existing" ? "Discard" : "Clear"
|
|
19353
|
-
}),
|
|
19354
|
-
pluginActions,
|
|
19355
|
-
savingError && /* @__PURE__ */ jsx("div", {
|
|
19356
|
-
className: "text-right",
|
|
19357
|
-
children: /* @__PURE__ */ jsx(Typography, {
|
|
19358
|
-
color: "error",
|
|
19359
|
-
children: savingError.message
|
|
19360
|
-
})
|
|
19361
|
-
})
|
|
20163
|
+
})] })
|
|
19362
20164
|
]
|
|
19363
20165
|
});
|
|
19364
20166
|
}
|
|
@@ -19520,7 +20322,7 @@ function getChanges(source, comparison) {
|
|
|
19520
20322
|
const changes = {};
|
|
19521
20323
|
if (!source) return {};
|
|
19522
20324
|
if (!comparison) return source;
|
|
19523
|
-
const allKeys = Array.from(new Set([...Object.keys(source), ...Object.keys(comparison)]));
|
|
20325
|
+
const allKeys = Array.from(/* @__PURE__ */ new Set([...Object.keys(source), ...Object.keys(comparison)]));
|
|
19524
20326
|
for (const key of allKeys) {
|
|
19525
20327
|
const sourceValue = source[key];
|
|
19526
20328
|
const comparisonValue = comparison[key];
|
|
@@ -19581,8 +20383,7 @@ function zodToFormErrors(zodError) {
|
|
|
19581
20383
|
*
|
|
19582
20384
|
* @group Components
|
|
19583
20385
|
*/
|
|
19584
|
-
function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified, onIdChange, onSaved, entity, initialDirtyValues, onFormContextReady,
|
|
19585
|
-
useCustomizationController();
|
|
20386
|
+
function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified, onIdChange, onSaved, entity, initialDirtyValues, onFormContextReady, initialStatus, className, onStatusChange, onEntityChange, openEntityMode = "full_screen", formex: formexProp, disabled: disabledProp, Builder, EntityFormActionsComponent = EntityFormActions, showDefaultActions = true, navigateBack: navigateBackProp, children, onSubmit: onSubmitProp, onValuesChangeDeferred: onValuesChangeDeferredProp, onReset: onResetProp, uniqueFieldValidator: uniqueFieldValidatorProp, beforeFields, afterFields, pluginActions: pluginActionsProp, computedInitialValues, hasLocalChanges: hasLocalChangesProp, localChangesData, manualApplyLocalChanges, localChangesCacheKey, onClearLocalChanges }) {
|
|
19586
20387
|
const navigateBack = useCallback(() => {
|
|
19587
20388
|
if (navigateBackProp) navigateBackProp();
|
|
19588
20389
|
}, [navigateBackProp]);
|
|
@@ -19604,7 +20405,6 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19604
20405
|
else return entityIdProp;
|
|
19605
20406
|
}, [entityIdProp, status]);
|
|
19606
20407
|
const [entityId, setEntityId] = useState(initialEntityId);
|
|
19607
|
-
const [entityIdError, setEntityIdError] = useState(false);
|
|
19608
20408
|
const [savingError, setSavingError] = useState();
|
|
19609
20409
|
const [isSavingAutoSave, setIsSavingAutoSave] = useState(false);
|
|
19610
20410
|
const [discardDialogOpen, setDiscardDialogOpen] = useState(false);
|
|
@@ -19623,7 +20423,6 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19623
20423
|
const hasLocalChanges = hasLocalChangesProp !== void 0 ? hasLocalChangesProp && !localChangesCleared : false;
|
|
19624
20424
|
const onSubmit = (values, formexController) => {
|
|
19625
20425
|
setSavingError(void 0);
|
|
19626
|
-
setEntityIdError(false);
|
|
19627
20426
|
if (status === "existing") {
|
|
19628
20427
|
if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
|
|
19629
20428
|
} else if (status !== "new" && status !== "copy") throw Error("New FormType added, check EntityForm");
|
|
@@ -19731,7 +20530,8 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19731
20530
|
savingError,
|
|
19732
20531
|
status,
|
|
19733
20532
|
openEntityMode,
|
|
19734
|
-
disabled
|
|
20533
|
+
disabled,
|
|
20534
|
+
isSaving: formex.isSubmitting || isSavingAutoSave
|
|
19735
20535
|
};
|
|
19736
20536
|
useEffect(() => {
|
|
19737
20537
|
onFormContextReady?.(formContext);
|
|
@@ -19739,13 +20539,13 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19739
20539
|
formex.version,
|
|
19740
20540
|
collection,
|
|
19741
20541
|
entityId,
|
|
19742
|
-
path
|
|
20542
|
+
path,
|
|
20543
|
+
formex.dirty,
|
|
20544
|
+
formex.isSubmitting,
|
|
20545
|
+
formex.submitCount,
|
|
20546
|
+
isSavingAutoSave
|
|
19743
20547
|
]);
|
|
19744
20548
|
const actionsDisabled = disabled || formex.isSubmitting || status === "existing" && !formex.dirty || Boolean(disabledProp);
|
|
19745
|
-
const titlePropertyKey = getEntityTitlePropertyKeyForEntity(collection, formex.values, entityId);
|
|
19746
|
-
const rawTitle = formex.values && titlePropertyKey ? getValueInPath(formex.values, titlePropertyKey) : void 0;
|
|
19747
|
-
const titleUser = useResolvedUser(isUserSelectProperty(collection, titlePropertyKey) && typeof rawTitle === "string" ? rawTitle : void 0);
|
|
19748
|
-
const title = titleUser ? getUserLabel(titleUser) : rawTitle !== void 0 && rawTitle !== null ? resolveTitleToString(rawTitle) : collection.singularName ?? collection.name;
|
|
19749
20549
|
const modified = formex.dirty;
|
|
19750
20550
|
useEffect(() => {
|
|
19751
20551
|
if (!autoSave) onValuesModified?.(modified, formex.values);
|
|
@@ -19779,6 +20579,106 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19779
20579
|
formex.setFieldValue
|
|
19780
20580
|
]);
|
|
19781
20581
|
const formFieldKeys = getFormFieldKeys(collection);
|
|
20582
|
+
const layout = useMemo(() => resolveFormLayout({
|
|
20583
|
+
collection,
|
|
20584
|
+
fieldKeys: formFieldKeys,
|
|
20585
|
+
status
|
|
20586
|
+
}), [
|
|
20587
|
+
collection,
|
|
20588
|
+
formFieldKeys.join(","),
|
|
20589
|
+
status
|
|
20590
|
+
]);
|
|
20591
|
+
const formElementRef = useRef(null);
|
|
20592
|
+
const railVisible = useRailVisible(formElementRef);
|
|
20593
|
+
const showRail = !Builder && layout.hasRail && railVisible;
|
|
20594
|
+
const [collapsedSections, setCollapsedSections] = useState(() => new Set(layout.sections.filter((s) => s.collapsed).map((s) => s.key)));
|
|
20595
|
+
const toggleSection = useCallback((key) => {
|
|
20596
|
+
setCollapsedSections((prev) => {
|
|
20597
|
+
const next = new Set(prev);
|
|
20598
|
+
if (next.has(key)) next.delete(key);
|
|
20599
|
+
else next.add(key);
|
|
20600
|
+
return next;
|
|
20601
|
+
});
|
|
20602
|
+
}, []);
|
|
20603
|
+
const errorKeys = useMemo(() => new Set(formex.submitCount > 0 ? Object.keys(formex.errors) : []), [formex.errors, formex.submitCount]);
|
|
20604
|
+
const autoFocusKey = useMemo(() => {
|
|
20605
|
+
if (status !== "new" && status !== "copy") return void 0;
|
|
20606
|
+
return [...layout.sections.flatMap((s) => s.fields), ...layout.sidebar].find((field) => {
|
|
20607
|
+
const property = collection.properties?.[field.key];
|
|
20608
|
+
return property ? !isFieldDisabled(property) : false;
|
|
20609
|
+
})?.key;
|
|
20610
|
+
}, [
|
|
20611
|
+
layout,
|
|
20612
|
+
collection,
|
|
20613
|
+
status,
|
|
20614
|
+
disabledProp
|
|
20615
|
+
]);
|
|
20616
|
+
function isFieldDisabled(property) {
|
|
20617
|
+
const isNew = status === "new" || status === "copy";
|
|
20618
|
+
const isIdAndAuto = (property.type === "string" || property.type === "number") && "isId" in property && typeof property.isId === "string" && property.isId !== "manual";
|
|
20619
|
+
return Boolean(disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.admin?.disabled) || !isNew && "isId" in property && Boolean(property.isId) || isNew && isIdAndAuto);
|
|
20620
|
+
}
|
|
20621
|
+
const renderField = (field) => {
|
|
20622
|
+
if (field.additional) {
|
|
20623
|
+
const additionalField = collection.additionalFields?.find((f) => f.key === field.key);
|
|
20624
|
+
if (!additionalField || !entity) return null;
|
|
20625
|
+
const AdditionalFieldBuilder = additionalField.Builder;
|
|
20626
|
+
if (!AdditionalFieldBuilder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
|
|
20627
|
+
const additionalFieldContext = formContext;
|
|
20628
|
+
const child = AdditionalFieldBuilder ? /* @__PURE__ */ jsx(AdditionalFieldBuilder, {
|
|
20629
|
+
entity,
|
|
20630
|
+
context: additionalFieldContext
|
|
20631
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
20632
|
+
className: "w-full",
|
|
20633
|
+
children: additionalField.value?.({
|
|
20634
|
+
entity,
|
|
20635
|
+
context: additionalFieldContext
|
|
20636
|
+
})?.toString()
|
|
20637
|
+
});
|
|
20638
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
20639
|
+
className: spanClass(field.span),
|
|
20640
|
+
children: [/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
20641
|
+
propertyKey: field.key,
|
|
20642
|
+
icon: /* @__PURE__ */ jsx(AlignLeftIcon, { size: iconSize.small }),
|
|
20643
|
+
title: additionalField.name,
|
|
20644
|
+
className: "text-text-secondary dark:text-text-secondary-dark"
|
|
20645
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
20646
|
+
className: cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-auto no-scrollbar"),
|
|
20647
|
+
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: child })
|
|
20648
|
+
})]
|
|
20649
|
+
}, `additional_${field.key}`);
|
|
20650
|
+
}
|
|
20651
|
+
const property = collection.properties?.[field.key];
|
|
20652
|
+
if (!property) {
|
|
20653
|
+
console.warn(`Property ${field.key} not found in collection ${collection.name} in properties or additional fields. Skipping.`);
|
|
20654
|
+
return null;
|
|
20655
|
+
}
|
|
20656
|
+
const underlyingValueHasChanged = !!underlyingChanges && Object.keys(underlyingChanges).includes(field.key) && formex.touched[field.key];
|
|
20657
|
+
const disabled = isFieldDisabled(property);
|
|
20658
|
+
const selfLabelling = isSelfLabellingProperty(property);
|
|
20659
|
+
const cmsFormFieldProps = {
|
|
20660
|
+
propertyKey: field.key,
|
|
20661
|
+
disabled,
|
|
20662
|
+
property,
|
|
20663
|
+
includeDescription: false,
|
|
20664
|
+
hideLabel: !selfLabelling,
|
|
20665
|
+
underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
|
|
20666
|
+
context: formContext,
|
|
20667
|
+
partOfArray: false,
|
|
20668
|
+
minimalistView: false,
|
|
20669
|
+
size: "large",
|
|
20670
|
+
autoFocus: autoFocusKey === field.key
|
|
20671
|
+
};
|
|
20672
|
+
return /* @__PURE__ */ jsx("div", {
|
|
20673
|
+
className: spanClass(field.span),
|
|
20674
|
+
children: /* @__PURE__ */ jsx(FieldBlock, {
|
|
20675
|
+
propertyKey: field.key,
|
|
20676
|
+
property,
|
|
20677
|
+
showLabel: !selfLabelling,
|
|
20678
|
+
children: /* @__PURE__ */ jsx(PropertyFieldBinding, { ...cmsFormFieldProps })
|
|
20679
|
+
})
|
|
20680
|
+
}, `field_${field.key}`);
|
|
20681
|
+
};
|
|
19782
20682
|
const formFields = () => {
|
|
19783
20683
|
if (Builder) return /* @__PURE__ */ jsx(Builder, {
|
|
19784
20684
|
collection,
|
|
@@ -19786,99 +20686,29 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19786
20686
|
modifiedValues: formex.values,
|
|
19787
20687
|
formContext
|
|
19788
20688
|
});
|
|
19789
|
-
|
|
19790
|
-
|
|
19791
|
-
|
|
19792
|
-
|
|
19793
|
-
|
|
19794
|
-
|
|
19795
|
-
|
|
19796
|
-
|
|
19797
|
-
|
|
19798
|
-
|
|
19799
|
-
|
|
19800
|
-
|
|
19801
|
-
|
|
19802
|
-
return /* @__PURE__ */ jsx(FormEntry, {
|
|
19803
|
-
propertyKey: key,
|
|
19804
|
-
widthPercentage,
|
|
19805
|
-
children: /* @__PURE__ */ jsx(PropertyFieldBinding, {
|
|
19806
|
-
propertyKey: key,
|
|
19807
|
-
disabled,
|
|
19808
|
-
property,
|
|
19809
|
-
includeDescription: Boolean(property.description),
|
|
19810
|
-
underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
|
|
19811
|
-
context: formContext,
|
|
19812
|
-
partOfArray: false,
|
|
19813
|
-
minimalistView: false,
|
|
19814
|
-
autoFocus: shouldAutoFocus
|
|
19815
|
-
})
|
|
19816
|
-
}, `field_${key}`);
|
|
19817
|
-
}
|
|
19818
|
-
const additionalField = collection.additionalFields?.find((f) => f.key === key);
|
|
19819
|
-
if (additionalField && entity) {
|
|
19820
|
-
const AdditionalFieldBuilder = additionalField.Builder;
|
|
19821
|
-
if (!AdditionalFieldBuilder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
|
|
19822
|
-
const additionalFieldContext = formContext;
|
|
19823
|
-
const child = AdditionalFieldBuilder ? /* @__PURE__ */ jsx(AdditionalFieldBuilder, {
|
|
19824
|
-
entity,
|
|
19825
|
-
context: additionalFieldContext
|
|
19826
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
19827
|
-
className: "w-full",
|
|
19828
|
-
children: additionalField.value?.({
|
|
19829
|
-
entity,
|
|
19830
|
-
context: additionalFieldContext
|
|
19831
|
-
})?.toString()
|
|
19832
|
-
});
|
|
19833
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
19834
|
-
className: "w-full",
|
|
19835
|
-
children: [/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
19836
|
-
propertyKey: key,
|
|
19837
|
-
icon: /* @__PURE__ */ jsx(AlignLeftIcon, { size: iconSize.small }),
|
|
19838
|
-
title: additionalField.name,
|
|
19839
|
-
className: "text-text-secondary dark:text-text-secondary-dark ml-3.5"
|
|
19840
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
19841
|
-
className: cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar"),
|
|
19842
|
-
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: child })
|
|
19843
|
-
})]
|
|
19844
|
-
}, `additional_${key}`);
|
|
19845
|
-
}
|
|
19846
|
-
console.warn(`Property ${key} not found in collection ${collection.name} in properties or additional fields. Skipping.`);
|
|
19847
|
-
return null;
|
|
19848
|
-
}).filter(Boolean) });
|
|
20689
|
+
return /* @__PURE__ */ jsx(FormSections, {
|
|
20690
|
+
sections: showRail || !layout.sidebar.length ? layout.sections : [...layout.sections, {
|
|
20691
|
+
key: "__rail",
|
|
20692
|
+
title: "Settings",
|
|
20693
|
+
collapsible: false,
|
|
20694
|
+
collapsed: false,
|
|
20695
|
+
fields: layout.sidebar
|
|
20696
|
+
}],
|
|
20697
|
+
collapsed: collapsedSections,
|
|
20698
|
+
onToggle: toggleSection,
|
|
20699
|
+
errorKeys,
|
|
20700
|
+
renderField
|
|
20701
|
+
});
|
|
19849
20702
|
};
|
|
19850
20703
|
const formRef = useRef(null);
|
|
19851
20704
|
const hasFormErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
19852
20705
|
const formView = /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
19853
20706
|
beforeFields,
|
|
19854
|
-
!
|
|
19855
|
-
|
|
19856
|
-
|
|
19857
|
-
|
|
19858
|
-
|
|
19859
|
-
variant: "h4",
|
|
19860
|
-
children: title ?? collection.singularName ?? collection.name
|
|
19861
|
-
}),
|
|
19862
|
-
!entity?.values && initialStatus === "existing" && /* @__PURE__ */ jsx(Alert, {
|
|
19863
|
-
color: "warning",
|
|
19864
|
-
size: "small",
|
|
19865
|
-
outerClassName: "w-full mb-4 text-xs",
|
|
19866
|
-
children: "This entity does not exist yet"
|
|
19867
|
-
}),
|
|
19868
|
-
showEntityPath && /* @__PURE__ */ jsx(Alert, {
|
|
19869
|
-
color: "base",
|
|
19870
|
-
outerClassName: "w-full",
|
|
19871
|
-
size: "small",
|
|
19872
|
-
children: /* @__PURE__ */ jsxs("code", {
|
|
19873
|
-
className: "text-xs select-all text-text-secondary dark:text-text-secondary-dark",
|
|
19874
|
-
children: [
|
|
19875
|
-
entity?.path ?? path,
|
|
19876
|
-
"/",
|
|
19877
|
-
entityId
|
|
19878
|
-
]
|
|
19879
|
-
})
|
|
19880
|
-
})
|
|
19881
|
-
]
|
|
20707
|
+
!entity?.values && initialStatus === "existing" && /* @__PURE__ */ jsx(Alert, {
|
|
20708
|
+
color: "warning",
|
|
20709
|
+
size: "small",
|
|
20710
|
+
outerClassName: "w-full mb-4 text-xs",
|
|
20711
|
+
children: "This entity does not exist yet"
|
|
19882
20712
|
}),
|
|
19883
20713
|
children,
|
|
19884
20714
|
initialEntityId && !entity && initialStatus !== "new" && /* @__PURE__ */ jsx(Alert, {
|
|
@@ -19893,12 +20723,10 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19893
20723
|
children: "Please fix the highlighted errors before saving."
|
|
19894
20724
|
}),
|
|
19895
20725
|
formContext && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
|
|
19896
|
-
className: "mt-12 flex flex-col gap-8",
|
|
19897
20726
|
ref: formRef,
|
|
19898
20727
|
children: [formFields(), /* @__PURE__ */ jsx(ErrorFocus, { containerRef: formRef })]
|
|
19899
20728
|
}) }),
|
|
19900
20729
|
afterFields,
|
|
19901
|
-
forceActionsAtTheBottom && /* @__PURE__ */ jsx("div", { className: "h-16" }),
|
|
19902
20730
|
/* @__PURE__ */ jsxs(Dialog, {
|
|
19903
20731
|
open: discardDialogOpen,
|
|
19904
20732
|
onOpenChange: setDiscardDialogOpen,
|
|
@@ -19930,7 +20758,6 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19930
20758
|
collection,
|
|
19931
20759
|
path,
|
|
19932
20760
|
entity,
|
|
19933
|
-
layout: forceActionsAtTheBottom ? "bottom" : "responsive",
|
|
19934
20761
|
savingError,
|
|
19935
20762
|
formex,
|
|
19936
20763
|
disabled: actionsDisabled,
|
|
@@ -19944,6 +20771,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19944
20771
|
return /* @__PURE__ */ jsx(Formex, {
|
|
19945
20772
|
value: formex,
|
|
19946
20773
|
children: /* @__PURE__ */ jsxs("form", {
|
|
20774
|
+
ref: formElementRef,
|
|
19947
20775
|
onSubmit: formex.handleSubmit,
|
|
19948
20776
|
onReset: (e) => {
|
|
19949
20777
|
e.preventDefault();
|
|
@@ -19951,56 +20779,56 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19951
20779
|
else formex.resetForm({ values: baseInitialValues });
|
|
19952
20780
|
},
|
|
19953
20781
|
noValidate: true,
|
|
19954
|
-
className: cls("@container flex-1 flex flex-
|
|
19955
|
-
children: [/* @__PURE__ */
|
|
19956
|
-
|
|
19957
|
-
|
|
19958
|
-
|
|
19959
|
-
|
|
19960
|
-
|
|
19961
|
-
className: "flex flex-
|
|
19962
|
-
children: [manualApplyLocalChanges && hasLocalChanges && localChangesCacheKey && /* @__PURE__ */ jsx(
|
|
19963
|
-
|
|
19964
|
-
|
|
19965
|
-
|
|
19966
|
-
|
|
19967
|
-
|
|
19968
|
-
|
|
19969
|
-
onClearLocalChanges
|
|
19970
|
-
|
|
19971
|
-
|
|
19972
|
-
|
|
19973
|
-
children: /* @__PURE__ */ jsx(Chip, {
|
|
19974
|
-
size: "small",
|
|
19975
|
-
className: "py-1",
|
|
19976
|
-
colorScheme: "blueDarker",
|
|
19977
|
-
children: /* @__PURE__ */ jsx(LoaderIcon, {
|
|
19978
|
-
size: iconSize.smallest,
|
|
19979
|
-
className: "animate-spin"
|
|
19980
|
-
})
|
|
19981
|
-
})
|
|
19982
|
-
}) : formex.dirty ? /* @__PURE__ */ jsx(Tooltip, {
|
|
19983
|
-
title: "Form has been modified",
|
|
19984
|
-
children: /* @__PURE__ */ jsx(Chip, {
|
|
19985
|
-
size: "small",
|
|
19986
|
-
className: "py-1",
|
|
19987
|
-
colorScheme: "orangeDarker",
|
|
19988
|
-
children: /* @__PURE__ */ jsx(PencilIcon, { size: iconSize.smallest })
|
|
19989
|
-
})
|
|
19990
|
-
}) : /* @__PURE__ */ jsx(Tooltip, {
|
|
19991
|
-
title: "Form is in sync",
|
|
19992
|
-
children: /* @__PURE__ */ jsx(Chip, {
|
|
19993
|
-
size: "small",
|
|
19994
|
-
className: "py-1",
|
|
19995
|
-
children: /* @__PURE__ */ jsx(CheckIcon, { size: iconSize.smallest })
|
|
20782
|
+
className: cls("@container/form flex-1 flex flex-col w-full min-h-0", className),
|
|
20783
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
20784
|
+
className: "flex-1 min-h-0 flex flex-row w-full",
|
|
20785
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
20786
|
+
className: "flex-1 min-w-0 overflow-y-auto flex justify-center items-start",
|
|
20787
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
20788
|
+
id: `form_${path}`,
|
|
20789
|
+
className: cls("@container/col w-full max-w-3xl 2xl:max-w-4xl flex flex-col", openEntityMode === "dialog" ? "pt-5 pb-8 px-6 sm:px-8" : "pt-6 pb-12 px-5 sm:px-8"),
|
|
20790
|
+
children: [manualApplyLocalChanges && hasLocalChanges && localChangesCacheKey && /* @__PURE__ */ jsx("div", {
|
|
20791
|
+
className: "flex justify-end mb-3",
|
|
20792
|
+
children: /* @__PURE__ */ jsx(LocalChangesMenu, {
|
|
20793
|
+
cacheKey: localChangesCacheKey,
|
|
20794
|
+
properties: collection.properties,
|
|
20795
|
+
cachedData: localChangesData,
|
|
20796
|
+
formex,
|
|
20797
|
+
onClearLocalChanges: () => {
|
|
20798
|
+
setLocalChangesCleared(true);
|
|
20799
|
+
onClearLocalChanges?.();
|
|
20800
|
+
}
|
|
19996
20801
|
})
|
|
19997
|
-
})]
|
|
19998
|
-
})
|
|
19999
|
-
})
|
|
20802
|
+
}), formView]
|
|
20803
|
+
})
|
|
20804
|
+
}), showRail && /* @__PURE__ */ jsx(FormRail, {
|
|
20805
|
+
fields: layout.sidebar,
|
|
20806
|
+
showRecordMeta: layout.showRecordMeta && status === "existing",
|
|
20807
|
+
entity,
|
|
20808
|
+
renderField
|
|
20809
|
+
})]
|
|
20000
20810
|
}), dialogActions]
|
|
20001
20811
|
})
|
|
20002
20812
|
});
|
|
20003
20813
|
}
|
|
20814
|
+
/**
|
|
20815
|
+
* Grid placement for a span.
|
|
20816
|
+
*
|
|
20817
|
+
* Written out as literal class strings rather than composed, because Tailwind
|
|
20818
|
+
* scans source text: a template literal would produce classes that exist at
|
|
20819
|
+
* runtime and were never generated into the stylesheet.
|
|
20820
|
+
*
|
|
20821
|
+
* Below the column breakpoint every field takes the single available column, so
|
|
20822
|
+
* the side panel, the dialog and mobile all get a plain stack.
|
|
20823
|
+
*/
|
|
20824
|
+
function spanClass(span) {
|
|
20825
|
+
switch (span) {
|
|
20826
|
+
case 1: return "@2xl/col:col-span-1";
|
|
20827
|
+
case 2: return "@2xl/col:col-span-2";
|
|
20828
|
+
case 3: return "@2xl/col:col-span-3";
|
|
20829
|
+
case 4: return "@2xl/col:col-span-4";
|
|
20830
|
+
}
|
|
20831
|
+
}
|
|
20004
20832
|
function useOnAutoSave(autoSave, formex, lastSavedValues, save) {
|
|
20005
20833
|
useEffect(() => {
|
|
20006
20834
|
if (!autoSave) return;
|
|
@@ -20219,7 +21047,6 @@ function EntityFormBinding({ path, entityId: entityIdProp, collection, entity, i
|
|
|
20219
21047
|
context,
|
|
20220
21048
|
openEntityMode
|
|
20221
21049
|
]);
|
|
20222
|
-
const pluginFormActions = useSlot("form.actions", formActionProps);
|
|
20223
21050
|
const pluginFormBefore = useSlot("form.before", formActionProps);
|
|
20224
21051
|
const pluginFormAfter = useSlot("form.after", formActionProps);
|
|
20225
21052
|
return /* @__PURE__ */ jsx(EntityForm, {
|
|
@@ -20242,7 +21069,7 @@ function EntityFormBinding({ path, entityId: entityIdProp, collection, entity, i
|
|
|
20242
21069
|
uniqueFieldValidator,
|
|
20243
21070
|
beforeFields: pluginFormBefore,
|
|
20244
21071
|
afterFields: pluginFormAfter,
|
|
20245
|
-
pluginActions:
|
|
21072
|
+
pluginActions: [],
|
|
20246
21073
|
computedInitialValues,
|
|
20247
21074
|
hasLocalChanges,
|
|
20248
21075
|
localChangesData,
|
|
@@ -20270,6 +21097,30 @@ var useCollectionsConfigController = () => useContext(ConfigControllerContext);
|
|
|
20270
21097
|
* @module
|
|
20271
21098
|
*/
|
|
20272
21099
|
/**
|
|
21100
|
+
* Rebuild a serialized relation's `target` slug into the thunk consumers call.
|
|
21101
|
+
*
|
|
21102
|
+
* Shared by the relation *property* and the collection-level `relations` array.
|
|
21103
|
+
* They are the same shape on the wire and they need the same treatment coming
|
|
21104
|
+
* back, but only the property path had it — so a collection imported from an
|
|
21105
|
+
* existing table came off disk with `relations[n].target` still a string, which
|
|
21106
|
+
* typechecks (the serializable shape erases the difference) and throws "target
|
|
21107
|
+
* is not a function" at the first consumer.
|
|
21108
|
+
*
|
|
21109
|
+
* The lookup is consulted lazily, inside the thunk, so collections may
|
|
21110
|
+
* reference each other in any order, including circularly.
|
|
21111
|
+
*/
|
|
21112
|
+
function fromSerializableRelation(relation, label, lookup) {
|
|
21113
|
+
const slug = relation.target;
|
|
21114
|
+
return {
|
|
21115
|
+
...relation,
|
|
21116
|
+
target: () => {
|
|
21117
|
+
const found = slug ? lookup?.(slug) : void 0;
|
|
21118
|
+
if (!found) throw new Error(`Relation "${relation.relationName ?? label}" targets collection "${slug ?? "(none)"}", which is not among the collections it was deserialized with. Pass the whole set to \`fromSerializableCollectionConfigs\` so slugs can be resolved against each other.`);
|
|
21119
|
+
return found;
|
|
21120
|
+
}
|
|
21121
|
+
};
|
|
21122
|
+
}
|
|
21123
|
+
/**
|
|
20273
21124
|
* Strip non-serializable fields from a `AdminPropertyOptions`.
|
|
20274
21125
|
* Removes `Field` and `Preview` (ComponentRef).
|
|
20275
21126
|
*/
|
|
@@ -20365,7 +21216,8 @@ function toSerializableRelation(link) {
|
|
|
20365
21216
|
case "hasMany": return {
|
|
20366
21217
|
kind: link.kind,
|
|
20367
21218
|
...common,
|
|
20368
|
-
...link.foreignKeyOnTarget ? { foreignKeyOnTarget: link.foreignKeyOnTarget } : {}
|
|
21219
|
+
...link.foreignKeyOnTarget ? { foreignKeyOnTarget: link.foreignKeyOnTarget } : {},
|
|
21220
|
+
...link.sourceKey ? { sourceKey: link.sourceKey } : {}
|
|
20369
21221
|
};
|
|
20370
21222
|
case "manyToMany": return {
|
|
20371
21223
|
kind: "manyToMany",
|
|
@@ -20676,17 +21528,7 @@ function fromSerializableProperty(serialized, lookup) {
|
|
|
20676
21528
|
...rest,
|
|
20677
21529
|
type: "relation"
|
|
20678
21530
|
};
|
|
20679
|
-
if (relation)
|
|
20680
|
-
const slug = relation.target;
|
|
20681
|
-
result.relation = {
|
|
20682
|
-
...relation,
|
|
20683
|
-
target: () => {
|
|
20684
|
-
const found = slug ? lookup?.(slug) : void 0;
|
|
20685
|
-
if (!found) throw new Error(`Relation "${relation.relationName ?? sp.name}" targets collection "${slug ?? "(none)"}", which is not among the collections it was deserialized with. Pass the whole set to \`fromSerializableCollectionConfigs\` so slugs can be resolved against each other.`);
|
|
20686
|
-
return found;
|
|
20687
|
-
}
|
|
20688
|
-
};
|
|
20689
|
-
}
|
|
21531
|
+
if (relation) result.relation = fromSerializableRelation(relation, sp.name ?? "", lookup);
|
|
20690
21532
|
return result;
|
|
20691
21533
|
}
|
|
20692
21534
|
default: return serialized;
|
|
@@ -20708,9 +21550,10 @@ function fromSerializableProperties(serialized, lookup) {
|
|
|
20708
21550
|
* consumer if needed.
|
|
20709
21551
|
*/
|
|
20710
21552
|
function fromSerializableCollectionConfig(serialized, lookup) {
|
|
20711
|
-
const { properties, ...rest } = serialized;
|
|
21553
|
+
const { properties, relations, ...rest } = serialized;
|
|
20712
21554
|
return {
|
|
20713
21555
|
...rest,
|
|
21556
|
+
...relations ? { relations: relations.map((relation, index) => fromSerializableRelation(relation, `relations[${index}]`, lookup)) } : {},
|
|
20714
21557
|
properties: fromSerializableProperties(properties, lookup)
|
|
20715
21558
|
};
|
|
20716
21559
|
}
|
|
@@ -20814,14 +21657,15 @@ function validateProperty(property, path, errors) {
|
|
|
20814
21657
|
});
|
|
20815
21658
|
const allowedByKind = {
|
|
20816
21659
|
belongsTo: ["localKey"],
|
|
20817
|
-
hasOne: ["foreignKeyOnTarget"],
|
|
20818
|
-
hasMany: ["foreignKeyOnTarget"],
|
|
21660
|
+
hasOne: ["foreignKeyOnTarget", "sourceKey"],
|
|
21661
|
+
hasMany: ["foreignKeyOnTarget", "sourceKey"],
|
|
20819
21662
|
manyToMany: ["through"],
|
|
20820
21663
|
via: ["joinPath", "cardinality"]
|
|
20821
21664
|
};
|
|
20822
21665
|
const linkFields = [
|
|
20823
21666
|
"localKey",
|
|
20824
21667
|
"foreignKeyOnTarget",
|
|
21668
|
+
"sourceKey",
|
|
20825
21669
|
"through",
|
|
20826
21670
|
"joinPath",
|
|
20827
21671
|
"cardinality"
|
|
@@ -21098,6 +21942,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
|
|
|
21098
21942
|
return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
|
|
21099
21943
|
}
|
|
21100
21944
|
//#endregion
|
|
21101
|
-
export { useBuildCollectionRegistryController as $,
|
|
21945
|
+
export { useBuildCollectionRegistryController as $, sanitizeUrl as $n, CollectionTableBinding as $t, getFieldId as A, ArrayOfReferencesPreview as An, convertFileToJson as At, MapFieldBinding as B, getEntityTitlePropertyKeyForEntity as Bn, mergeEntityActions as Bt, removeEmptyContainers as C, MapPropertyPreview as Cn, convertDataToEntity as Ct, getDefaultFieldConfig as D, ArrayEnumPreview as Dn, useImportConfig as Dt, DEFAULT_FIELD_CONFIGS as E, ArrayPropertyEnumPreview as En, getInferenceType as Et, SelectFieldBinding as F, useSidePanel as Fn, FieldCaption as Ft, useSelectionDialog as G, StorageThumbnailInternal as Gn, removeInitialAndTrailingSlashes$1 as Gt, DateTimeFieldBinding as H, StringPropertyPreview as Hn, getCollectionBySlugWithin as Ht, RepeatFieldBinding as I, CollectionRegistryContext as In, useBreadcrumbsController as It, useBuildUrlController as J, renderSkeletonIcon as Jn, resolveCollectionPathIds$1 as Jt, SelectionTableBinding as K, SkeletonPropertyComponent as Kn, removeInitialSlash as Kt, ReferenceFieldBinding as L, useCollectionRegistryController as Ln, BreadcrumbsProvider as Lt, TextFieldBinding as M, ReferencePreview as Mn, ArrayContainer as Mt, SwitchFieldBinding as N, EntityPreviewBinding as Nn, PropertyConfigBadge as Nt, getDefaultFieldId as O, ArrayOfStorageComponentsPreview as On, ImportSaveInProgress as Ot, StorageUploadFieldBinding as P, SidePanelControllerContext as Pn, SearchIconsView as Pt, useResolvedCollections as Q, ImagePreview as Qn, VirtualTableInput$1 as Qt, MultiSelectFieldBinding as R, getEntityPreviewKeys as Rn, resolveEntityAction as Rt, getInitialEntityValues as S, KeyValuePreview as Sn, DataNewPropertiesMapping as St, PropertyFieldBinding as T, ArrayOfStringsPreview as Tn, processValueMapping as Tt, BlockFieldBinding as U, EnumValuesChip as Un, getCollectionPathsCombinations as Ut, KeyValueFieldBinding as V, ArrayPropertyPreview as Vn, addInitialSlash as Vt, ArrayOfReferencesFieldBinding as W, StorageThumbnail as Wn, getLastSegment$1 as Wt, useTopLevelNavigation as X, renderSkeletonText as Xn, resolveViewMode as Xt, useBuildNavigationStateController as Y, renderSkeletonImageThumbnail as Yn, resolveOpenEntityMode as Yt, useResolvedViews as Z, UrlComponentPreview as Zn, useSelectionController as Zt, useCollectionsConfigController as _, getUserLabel as _n, CollectionViewActions as _t, namespaceToPropertiesPath as a, NavigationStateContext as an, FieldBlock as ar, useBuildSidePanel as at, extractTouchedValues as b, BooleanPreview as bn, useCollectionEditorDialogsState as bt, buildCollectionGenerationCallback as c, SideDialogsControllerContext as cn, getBracketNotation as cr, copyEntityAction as ct, fromSerializableCollectionConfigs as d, useClearRestoreValue as dn, getIconForWidget as dr, resetPasswordAction as dt, SelectableTable as en, EmptyValue as er, useHistory as et, fromSerializableProperties as f, ReadOnlyFieldBinding as fn, getPropertiesWithPropertiesOrder as fr, CreationResultDialog as ft, toSerializableProperty as g, UserPreview as gn, isRelationProperty as gr, EntityCardBinding as gt, toSerializableProperties as h, PropertyPreview as hn, isReferenceProperty as hr, CollectionCardViewBinding as ht, namespaceToPropertiesOrderPath as i, useUrlController as in, FormRail as ir, getEntityViewWidth as it, VectorFieldBinding as j, InlineEntityListPreview as jn, unflattenObject as jt, getFieldConfig as k, RelationPreview as kn, ImportFileUpload as kt, validateCollectionJson as l, SelectableTableContext as ln, getDefaultPropertiesOrder as lr, deleteEntityAction as lt, toSerializableCollectionConfig as m, ArrayOfMapsPreview as mn, getResolvedPropertyInPath as mr, EntityViewBinding as mt, getFullIdPath as n, useAdminContext as nn, LabelWithIcon as nr, useResolvedNavigationFrom as nt, CollectionGenerationApiError as o, useNavigationStateController as on, isSelfLabellingProperty as or, EditViewBinding as ot, fromSerializableProperty as p, FieldHelperText as pn, getPropertyInPath as pr, DetailViewBinding as pt, SideDialogs as q, renderSkeletonCaptionText as qn, removeTrailingSlash$1 as qt, idToPropertiesPath as r, UrlContext as rn, FormSections as rr, buildSidePanelsFromUrl as rt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as s, useSideDialogsController as sn, PropertyIdCopyTooltip as sr, CollectionViewBinding as st, getFullId as t, CollectionRowActions as tn, LabelWithIconAndTooltip as tr, resolveNavigationFrom as tt, fromSerializableCollectionConfig as u, ArrayCustomShapedFieldBinding as un, getIconForProperty as ur, editEntityAction as ut, EntityFormBinding as v, useResolvedUser as vn, useCollectionEditorController as vt, zodToFormErrors as w, ArrayOneOfPreview as wn, flattenEntry as wt, getChanges as x, DatePreview as xn, ImportNewPropertyFieldPreview as xt, EntityForm as y, NumberPropertyPreview as yn, ConfigControllerProvider as yt, MarkdownEditorFieldBinding as z, getEntityTitlePropertyKey as zn, resolveEntityView as zt };
|
|
21102
21946
|
|
|
21103
|
-
//# sourceMappingURL=util-
|
|
21947
|
+
//# sourceMappingURL=util-DSh3HRzK.js.map
|