@rebasepro/admin 0.12.1-canary.gf5f1d39 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{CollectionEditorDialog-Dpc75wIZ.js → CollectionEditorDialog-CjXkWZ64.js} +75 -62
- package/dist/{CollectionEditorDialog-Dpc75wIZ.js.map → CollectionEditorDialog-CjXkWZ64.js.map} +1 -1
- package/dist/{PropertyEditView-DH3XZC2x.js → PropertyEditView-W8I3YV89.js} +36 -16
- package/dist/{PropertyEditView-DH3XZC2x.js.map → PropertyEditView-W8I3YV89.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-BZLq4MGp.js → RouterCollectionsStudioView-BsLTNoqX.js} +5 -5
- package/dist/{RouterCollectionsStudioView-BZLq4MGp.js.map → RouterCollectionsStudioView-BsLTNoqX.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/CollectionTableBinding/column_utils.d.ts +17 -0
- package/dist/components/CollectionViewBinding/CollectionViewStartActions.d.ts +7 -2
- package/dist/components/CollectionViewBinding/SortButton.d.ts +24 -0
- 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 +35 -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/history/EntityHistoryView.d.ts +4 -1
- 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-J79rMoNj.js} +2 -2
- package/dist/{export-BzIpUdSK.js.map → export-J79rMoNj.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-D7L_JlLO.js} +35 -33
- package/dist/history-D7L_JlLO.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/useHistory.d.ts +7 -0
- package/dist/hooks/useRecordActions.d.ts +17 -0
- package/dist/{import-BjUG-cIY.js → import-BvYl--U4.js} +2 -2
- package/dist/{import-BjUG-cIY.js.map → import-BvYl--U4.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-GiwPgxnL.js} +2106 -1117
- package/dist/util-GiwPgxnL.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 +59 -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 +9 -2
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +47 -12
- package/src/components/CollectionViewBinding/SortButton.tsx +189 -0
- 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 +112 -148
- package/src/components/DrawerNavigationItem.tsx +1 -1
- package/src/components/EditFormActions.tsx +54 -129
- package/src/components/EditViewBinding.tsx +202 -99
- package/src/components/EntityIdentityBar.tsx +288 -0
- package/src/components/EntityInspector.tsx +137 -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 +29 -24
- 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 +20 -10
- 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, ArrowDownIcon, ArrowLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, 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
|
|
@@ -580,170 +962,36 @@ function StringPropertyPreview({ propertyKey, value, property, size, textOnly })
|
|
|
580
962
|
//#region src/preview/property_previews/ArrayPropertyPreview.tsx
|
|
581
963
|
/**
|
|
582
964
|
* @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
|
-
}
|
|
709
|
-
/**
|
|
710
|
-
* Get properties sorted by their order, but include ALL properties.
|
|
711
|
-
* Nested keys (like "data.mode") in propertiesOrder are ignored - they're for column ordering.
|
|
712
|
-
* @param properties
|
|
713
|
-
* @param propertiesOrder
|
|
714
|
-
*/
|
|
715
|
-
function getPropertiesWithPropertiesOrder(properties, propertiesOrder) {
|
|
716
|
-
if (!propertiesOrder) return properties;
|
|
717
|
-
const propertyKeys = Object.keys(properties);
|
|
718
|
-
const validOrderKeys = propertiesOrder.filter((key) => !key.includes(".") && properties[key]);
|
|
719
|
-
const result = {};
|
|
720
|
-
validOrderKeys.forEach((key) => {
|
|
721
|
-
const property = properties[key];
|
|
722
|
-
if (typeof property === "object" && property.type === "map" && property.properties) {
|
|
723
|
-
const mapProp = property;
|
|
724
|
-
result[key] = {
|
|
725
|
-
...mapProp,
|
|
726
|
-
properties: getPropertiesWithPropertiesOrder(mapProp.properties, mapProp.propertiesOrder ?? [])
|
|
727
|
-
};
|
|
728
|
-
} else if (property) result[key] = property;
|
|
965
|
+
*/
|
|
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,49 @@ function propertiesToColumns({ properties, sortable, fixedFilter, engine, Additi
|
|
|
4844
5327
|
};
|
|
4845
5328
|
});
|
|
4846
5329
|
}
|
|
5330
|
+
/**
|
|
5331
|
+
* The properties a sort can be applied to, as keys and titles.
|
|
5332
|
+
*
|
|
5333
|
+
* Only the table renders headers, so the list, card and split views had no way
|
|
5334
|
+
* to order anything — the controller has carried `sortBy` all along, with no
|
|
5335
|
+
* control bound to it outside the table. This is the same walk
|
|
5336
|
+
* `propertiesToColumns` makes, under the same keys and the same
|
|
5337
|
+
* `sortableProperty` authority, so a sort picked from the toolbar means exactly
|
|
5338
|
+
* what clicking the header would have meant, and the two cannot drift into
|
|
5339
|
+
* offering different columns.
|
|
5340
|
+
*/
|
|
5341
|
+
function getSortablePropertyOptions(properties) {
|
|
5342
|
+
return Object.entries(properties).flatMap(([key, property]) => getColumnKeysForProperty(property, key)).flatMap(({ key }) => {
|
|
5343
|
+
const property = getResolvedPropertyInPath(properties, key);
|
|
5344
|
+
if (!property || !sortableProperty(property)) return [];
|
|
5345
|
+
return [{
|
|
5346
|
+
key,
|
|
5347
|
+
title: property.name ?? key,
|
|
5348
|
+
property
|
|
5349
|
+
}];
|
|
5350
|
+
});
|
|
5351
|
+
}
|
|
5352
|
+
/**
|
|
5353
|
+
* Whether this column can be ordered by.
|
|
5354
|
+
*
|
|
5355
|
+
* `sortable` arrived as one flag for the whole table while `filter` was
|
|
5356
|
+
* computed per property, so every column advertised a sort — including the ones
|
|
5357
|
+
* no `ORDER BY` can be written for. A to-many relation is a *set* per row
|
|
5358
|
+
* (`posts.tags`), and there is no single value to order on; the driver used to
|
|
5359
|
+
* answer by silently dropping the sort and returning rows in whatever order
|
|
5360
|
+
* Postgres pleased, which is how a header that did nothing went unnoticed.
|
|
5361
|
+
*
|
|
5362
|
+
* Now that the driver refuses instead, offering the control would turn a
|
|
5363
|
+
* useless header into a failed request. Same authority as the filter side: ask
|
|
5364
|
+
* the property, not the table.
|
|
5365
|
+
*/
|
|
5366
|
+
function sortableProperty(property) {
|
|
5367
|
+
if (property.type !== "relation") return true;
|
|
5368
|
+
const relationProperty = property;
|
|
5369
|
+
const kind = relationProperty.relation?.kind ?? relationProperty.resolvedRelation?.kind;
|
|
5370
|
+
if (!kind) return true;
|
|
5371
|
+
return kind === "belongsTo";
|
|
5372
|
+
}
|
|
4847
5373
|
function filterableProperty(property, partOfArray = false, engine) {
|
|
4848
5374
|
if (!isFilterableRelation(property, engine)) return false;
|
|
4849
5375
|
if (partOfArray) return [
|
|
@@ -4974,7 +5500,8 @@ function StringNumberFilterField({ name, value, setValue, type, isArray, enumVal
|
|
|
4974
5500
|
disabled: isNullOperation,
|
|
4975
5501
|
placeholder: isNullOperation ? "null" : void 0,
|
|
4976
5502
|
onChange: (evt) => {
|
|
4977
|
-
|
|
5503
|
+
const val = type === "number" ? parseFloat(evt.target.value) : evt.target.value;
|
|
5504
|
+
updateFilter(operation, val);
|
|
4978
5505
|
},
|
|
4979
5506
|
endAdornment: internalValue !== void 0 && internalValue != null && /* @__PURE__ */ jsx(IconButton, {
|
|
4980
5507
|
onClick: (e) => updateFilter(operation, void 0),
|
|
@@ -5710,7 +6237,7 @@ var SelectableTable = function SelectableTable({ onValueChange, cellRenderer, on
|
|
|
5710
6237
|
children: /* @__PURE__ */ jsx("div", {
|
|
5711
6238
|
className: "h-full w-full flex flex-col bg-white dark:bg-surface-900",
|
|
5712
6239
|
ref,
|
|
5713
|
-
children: /* @__PURE__ */ jsx(
|
|
6240
|
+
children: /* @__PURE__ */ jsx(VirtualTable, {
|
|
5714
6241
|
data,
|
|
5715
6242
|
columns,
|
|
5716
6243
|
cellRenderer: (props) => cellRenderer(props),
|
|
@@ -5842,6 +6369,13 @@ var CollectionTableBinding = function CollectionTableBinding({ className, style,
|
|
|
5842
6369
|
}), {}) : entity;
|
|
5843
6370
|
const Builder = additionalField.Builder;
|
|
5844
6371
|
if (!Builder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
|
|
6372
|
+
const child = Builder ? /* @__PURE__ */ jsx(Builder, {
|
|
6373
|
+
entity,
|
|
6374
|
+
context
|
|
6375
|
+
}) : /* @__PURE__ */ jsx(Fragment, { children: additionalField.value?.({
|
|
6376
|
+
entity,
|
|
6377
|
+
context
|
|
6378
|
+
})?.toString() });
|
|
5845
6379
|
return /* @__PURE__ */ jsx(EntityTableCell, {
|
|
5846
6380
|
width,
|
|
5847
6381
|
size,
|
|
@@ -5858,13 +6392,7 @@ var CollectionTableBinding = function CollectionTableBinding({ className, style,
|
|
|
5858
6392
|
isDragging,
|
|
5859
6393
|
isDraggable,
|
|
5860
6394
|
frozen,
|
|
5861
|
-
children: /* @__PURE__ */ jsx(ErrorBoundary, { children:
|
|
5862
|
-
entity,
|
|
5863
|
-
context
|
|
5864
|
-
}) : /* @__PURE__ */ jsx(Fragment, { children: additionalField.value?.({
|
|
5865
|
-
entity,
|
|
5866
|
-
context
|
|
5867
|
-
})?.toString() }) })
|
|
6395
|
+
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: child })
|
|
5868
6396
|
}, `additional_table_cell_${entity.id}_${column.key}`);
|
|
5869
6397
|
}, [size]);
|
|
5870
6398
|
const engine = useCollectionScope()?.engine;
|
|
@@ -6035,35 +6563,6 @@ function useSelectionController(onSelectionChange) {
|
|
|
6035
6563
|
]);
|
|
6036
6564
|
}
|
|
6037
6565
|
//#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
|
-
};
|
|
6065
|
-
}
|
|
6066
|
-
//#endregion
|
|
6067
6566
|
//#region src/components/CollectionViewBinding/hooks/useCollectionInlineEditor.ts
|
|
6068
6567
|
function useCollectionInlineEditor({ path, collection, dataClient, context }) {
|
|
6069
6568
|
const uniqueFieldValidator = useCallback(async ({ name, value, property, entityId }) => {
|
|
@@ -6233,6 +6732,35 @@ function mergeEntityActions(currentActions, newActions) {
|
|
|
6233
6732
|
});
|
|
6234
6733
|
return updatedActions;
|
|
6235
6734
|
}
|
|
6735
|
+
//#endregion
|
|
6736
|
+
//#region src/util/resolutions.ts
|
|
6737
|
+
/**
|
|
6738
|
+
* Built-in entity views that are resolved by token name.
|
|
6739
|
+
* These are always available without needing to be registered
|
|
6740
|
+
* in the customization controller's entityViews array.
|
|
6741
|
+
*/
|
|
6742
|
+
var BUILTIN_ENTITY_VIEWS = { "__rebase_history": {
|
|
6743
|
+
key: "__rebase_history",
|
|
6744
|
+
name: "History",
|
|
6745
|
+
tabComponent: React.createElement(HistoryIcon, { size: 20 }),
|
|
6746
|
+
position: "end"
|
|
6747
|
+
} };
|
|
6748
|
+
function resolveEntityView(entityView, contextEntityViews) {
|
|
6749
|
+
if (typeof entityView === "string") return BUILTIN_ENTITY_VIEWS[entityView] ?? contextEntityViews?.find((entry) => entry.key === entityView);
|
|
6750
|
+
else return entityView;
|
|
6751
|
+
}
|
|
6752
|
+
function resolveEntityAction(entityAction, contextEntityActions) {
|
|
6753
|
+
if (typeof entityAction === "string") return contextEntityActions?.find((entry) => entry.key === entityAction);
|
|
6754
|
+
else return entityAction;
|
|
6755
|
+
}
|
|
6756
|
+
function resolvedSelectedEntityView(customViews, customizationController, selectedTab, _canEdit) {
|
|
6757
|
+
const resolvedEntityViews = customViews ? customViews.map((e) => resolveEntityView(e, customizationController.entityViews)).filter(Boolean).filter((e) => e.key !== "__rebase_history") : [];
|
|
6758
|
+
return {
|
|
6759
|
+
resolvedEntityViews,
|
|
6760
|
+
selectedEntityView: resolvedEntityViews.find((e) => e.key === selectedTab),
|
|
6761
|
+
selectedSecondaryForm: customViews && resolvedEntityViews.filter((e) => e.includeActions).find((e) => e.key === selectedTab)
|
|
6762
|
+
};
|
|
6763
|
+
}
|
|
6236
6764
|
var BreadcrumbContext = React.createContext({
|
|
6237
6765
|
breadcrumbs: [],
|
|
6238
6766
|
set: () => {},
|
|
@@ -6315,7 +6843,10 @@ function SearchIconsView({ selectedIcon = "", onIconSelected }) {
|
|
|
6315
6843
|
const [query, setQuery] = React.useState("");
|
|
6316
6844
|
const updateSearchResults = React.useMemo(() => debounce((value) => {
|
|
6317
6845
|
if (!value || value === "") setKeys(null);
|
|
6318
|
-
else
|
|
6846
|
+
else {
|
|
6847
|
+
const limited = iconsSearch.search(value).slice(0, 50);
|
|
6848
|
+
setKeys(limited.map((e) => e.item.key));
|
|
6849
|
+
}
|
|
6319
6850
|
}, UPDATE_SEARCH_INDEX_WAIT_MS), []);
|
|
6320
6851
|
React.useEffect(() => {
|
|
6321
6852
|
updateSearchResults(query);
|
|
@@ -6516,15 +7047,18 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
|
|
|
6516
7047
|
}, []);
|
|
6517
7048
|
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), useSensor(KeyboardSensor, {}));
|
|
6518
7049
|
useEffect(() => {
|
|
6519
|
-
if (hasValue && value && value.length !== internalIds.length)
|
|
6520
|
-
const
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
7050
|
+
if (hasValue && value && value.length !== internalIds.length) {
|
|
7051
|
+
const newInternalIds = value.map((v, index) => {
|
|
7052
|
+
const hashValue = (getHashValue(v) ?? String(index)) + index;
|
|
7053
|
+
if (hashValue in internalIdsRef.current) return internalIdsRef.current[hashValue];
|
|
7054
|
+
else {
|
|
7055
|
+
const newInternalId = getRandomId$1();
|
|
7056
|
+
internalIdsRef.current[hashValue] = newInternalId;
|
|
7057
|
+
return newInternalId;
|
|
7058
|
+
}
|
|
7059
|
+
});
|
|
7060
|
+
setInternalIds(newInternalIds);
|
|
7061
|
+
}
|
|
6528
7062
|
}, [
|
|
6529
7063
|
hasValue,
|
|
6530
7064
|
internalIds.length,
|
|
@@ -6585,7 +7119,8 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
|
|
|
6585
7119
|
const oldIndex = internalIds.indexOf(active.id);
|
|
6586
7120
|
const newIndex = internalIds.indexOf(over.id);
|
|
6587
7121
|
if (oldIndex === -1 || newIndex === -1) return;
|
|
6588
|
-
|
|
7122
|
+
const newIds = arrayMove$1(internalIds, oldIndex, newIndex);
|
|
7123
|
+
setInternalIds(newIds);
|
|
6589
7124
|
onValueChange(arrayMove$1(value ?? [], oldIndex, newIndex));
|
|
6590
7125
|
};
|
|
6591
7126
|
return sortable ? /* @__PURE__ */ jsx(DndContext, {
|
|
@@ -6921,6 +7456,7 @@ var useImportConfig = () => {
|
|
|
6921
7456
|
//#endregion
|
|
6922
7457
|
//#region src/data_import/utils/get_import_inference_type.ts
|
|
6923
7458
|
function getInferenceType(value) {
|
|
7459
|
+
if (value === null || value === void 0) return "string";
|
|
6924
7460
|
if (typeof value === "number") return "number";
|
|
6925
7461
|
else if (typeof value === "string") return "string";
|
|
6926
7462
|
else if (typeof value === "boolean") return "boolean";
|
|
@@ -7308,8 +7844,9 @@ var ConfigControllerProvider = React.memo(function ConfigControllerProvider({ ch
|
|
|
7308
7844
|
editedCollectionId
|
|
7309
7845
|
});
|
|
7310
7846
|
const namespace = propertyKey && propertyKey.includes(".") ? propertyKey.substring(0, propertyKey.lastIndexOf(".")) : void 0;
|
|
7847
|
+
const propertyKeyWithoutNamespace = propertyKey && propertyKey.includes(".") ? propertyKey.substring(propertyKey.lastIndexOf(".") + 1) : propertyKey;
|
|
7311
7848
|
setCurrentPropertyDialog({
|
|
7312
|
-
propertyKey:
|
|
7849
|
+
propertyKey: propertyKeyWithoutNamespace,
|
|
7313
7850
|
property,
|
|
7314
7851
|
namespace,
|
|
7315
7852
|
currentPropertiesOrder,
|
|
@@ -7365,7 +7902,10 @@ var ConfigControllerProvider = React.memo(function ConfigControllerProvider({ ch
|
|
|
7365
7902
|
onFetchTableMetadata,
|
|
7366
7903
|
handleClose: (collection) => {
|
|
7367
7904
|
if (currentDialog?.redirect) {
|
|
7368
|
-
if (collection && currentDialog?.isNewCollection && !currentDialog.parentCollectionSlugs.length)
|
|
7905
|
+
if (collection && currentDialog?.isNewCollection && !currentDialog.parentCollectionSlugs.length) {
|
|
7906
|
+
const url = urlController.buildUrlCollectionPath(collection.slug);
|
|
7907
|
+
navigate(url);
|
|
7908
|
+
}
|
|
7369
7909
|
}
|
|
7370
7910
|
setCurrentDialog(void 0);
|
|
7371
7911
|
}
|
|
@@ -7532,8 +8072,8 @@ function EditorCollectionAction({ path, parentCollectionSlugs, parentEntityIds,
|
|
|
7532
8072
|
}
|
|
7533
8073
|
//#endregion
|
|
7534
8074
|
//#region src/components/CollectionViewBinding/CollectionViewActions.tsx
|
|
7535
|
-
var ImportCollectionAction = lazy(() => import("./import-
|
|
7536
|
-
var ExportCollectionAction = lazy(() => import("./export-
|
|
8075
|
+
var ImportCollectionAction = lazy(() => import("./import-BvYl--U4.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
|
|
8076
|
+
var ExportCollectionAction = lazy(() => import("./export-J79rMoNj.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
|
|
7537
8077
|
function CollectionViewActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, onNewClick, onAddExistingClick, onMultipleDeleteClick, selectionEnabled, path, selectionController, tableController, collectionEntitiesCount, compact, children, openNewDocument }) {
|
|
7538
8078
|
const context = useAdminContext();
|
|
7539
8079
|
const { canCreate, canDelete } = usePermissions();
|
|
@@ -7578,8 +8118,9 @@ function CollectionViewActions({ collection, relativePath, parentCollectionSlugs
|
|
|
7578
8118
|
})
|
|
7579
8119
|
}));
|
|
7580
8120
|
const multipleDeleteEnabled = canDelete(collection, path, null);
|
|
8121
|
+
const hasSelection = Boolean(selectedEntities?.length);
|
|
7581
8122
|
let multipleDeleteButton;
|
|
7582
|
-
{
|
|
8123
|
+
if (hasSelection) {
|
|
7583
8124
|
const button = largeLayout && !compact ? /* @__PURE__ */ jsxs(Button, {
|
|
7584
8125
|
variant: "text",
|
|
7585
8126
|
disabled: !selectedEntities?.length || !multipleDeleteEnabled,
|
|
@@ -7673,7 +8214,9 @@ function relationCardinality(relation) {
|
|
|
7673
8214
|
* Pure function — call inside `useMemo`.
|
|
7674
8215
|
*/
|
|
7675
8216
|
function resolveCollectionSlotKeys(collection, authController, propertyConfigs) {
|
|
7676
|
-
const
|
|
8217
|
+
const rankedCandidates = getTitlePropertyCandidates(collection);
|
|
8218
|
+
const leadingRelationKey = getLeadingRelationTitleKey(collection);
|
|
8219
|
+
const titleKeyCandidates = leadingRelationKey ? [leadingRelationKey, ...rankedCandidates.filter((key) => key !== leadingRelationKey)] : rankedCandidates;
|
|
7677
8220
|
const titleKey = titleKeyCandidates[0];
|
|
7678
8221
|
const imageKey = getEntityImagePreviewPropertyKey(collection);
|
|
7679
8222
|
let statusKey;
|
|
@@ -7721,9 +8264,9 @@ function resolveCollectionSlotKeys(collection, authController, propertyConfigs)
|
|
|
7721
8264
|
const hasExplicitOrder = !!collection.propertiesOrder;
|
|
7722
8265
|
const relationKeys = [];
|
|
7723
8266
|
if (!hasExplicitOrder) {
|
|
7724
|
-
for (const [key, prop] of Object.entries(collection.properties)) if (prop.type === "relation") relationKeys.push(key);
|
|
8267
|
+
for (const [key, prop] of Object.entries(collection.properties)) if (prop.type === "relation" && key !== titleKey) relationKeys.push(key);
|
|
7725
8268
|
}
|
|
7726
|
-
const excludeKeys = new Set([
|
|
8269
|
+
const excludeKeys = /* @__PURE__ */ new Set([
|
|
7727
8270
|
titleKey,
|
|
7728
8271
|
imageKey,
|
|
7729
8272
|
statusKey,
|
|
@@ -7882,9 +8425,7 @@ function resolveEntitySlots(entity, collection, slotKeys) {
|
|
|
7882
8425
|
}
|
|
7883
8426
|
}
|
|
7884
8427
|
if (items.length > 0) {
|
|
7885
|
-
const
|
|
7886
|
-
const chipColors = Object.values(CHIP_COLORS);
|
|
7887
|
-
const colorScheme = chipColors[relIndex % chipColors.length];
|
|
8428
|
+
const colorScheme = CHIP_COLORS[CHIP_SEED_KEYS[relationKeys.indexOf(relKey) % CHIP_SEED_KEYS.length]];
|
|
7888
8429
|
relations.push({
|
|
7889
8430
|
property: prop,
|
|
7890
8431
|
propertyKey: relKey,
|
|
@@ -8356,7 +8897,7 @@ function CollectionListViewBinding({ collection, tableController, onEntityClick,
|
|
|
8356
8897
|
const showStatus = statusPropertyKey && size !== "xs" && size !== "s";
|
|
8357
8898
|
const subtitleKey = previewKeys.length > 0 ? previewKeys[0] : void 0;
|
|
8358
8899
|
const allKeys = resolvedCollection.propertiesOrder || Object.keys(resolvedCollection.properties);
|
|
8359
|
-
const usedKeys = new Set([
|
|
8900
|
+
const usedKeys = /* @__PURE__ */ new Set([
|
|
8360
8901
|
titlePropertyKey,
|
|
8361
8902
|
imagePropertyKey,
|
|
8362
8903
|
statusPropertyKey,
|
|
@@ -8948,44 +9489,228 @@ var PropertyCollectionView = ({ data, properties, baseKey = "", suppressHeader =
|
|
|
8948
9489
|
};
|
|
8949
9490
|
//#endregion
|
|
8950
9491
|
//#region src/components/EntityViewBinding.tsx
|
|
9492
|
+
/**
|
|
9493
|
+
* The read-only rendering of a record's values.
|
|
9494
|
+
*
|
|
9495
|
+
* This used to prepend a synthetic `Id` row of its own. Between that row, the
|
|
9496
|
+
* collection's own id property, and the `path/id` chip above it, the same UUID
|
|
9497
|
+
* appeared three times — and in the ~340px split pane each copy wrapped over
|
|
9498
|
+
* four lines, so most of the first screen was one id repeated. The id is now a
|
|
9499
|
+
* copyable chip in the identity bar, once.
|
|
9500
|
+
*/
|
|
8951
9501
|
function EntityViewBinding({ entity, collection, path, className }) {
|
|
8952
9502
|
const customizationController = useCustomizationController();
|
|
8953
|
-
const properties =
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
9503
|
+
const properties = React.useMemo(() => {
|
|
9504
|
+
const entries = Object.entries(collection.properties ?? {});
|
|
9505
|
+
const idKeys = entries.filter(([, p]) => p && "isId" in p && p.isId);
|
|
9506
|
+
if (idKeys.length !== 1) return collection.properties;
|
|
9507
|
+
return Object.fromEntries(entries.filter(([key]) => key !== idKeys[0][0]));
|
|
9508
|
+
}, [collection.properties]);
|
|
9509
|
+
const externalLink = customizationController?.entityLinkBuilder?.({ entity });
|
|
9510
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9511
|
+
className: "w-full " + (className ?? ""),
|
|
9512
|
+
children: [externalLink && /* @__PURE__ */ jsx("div", {
|
|
9513
|
+
className: "flex justify-end mb-2",
|
|
9514
|
+
children: /* @__PURE__ */ jsx(Tooltip, {
|
|
9515
|
+
title: "Open in the live site",
|
|
9516
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
9517
|
+
href: externalLink,
|
|
9518
|
+
rel: "noopener noreferrer",
|
|
9519
|
+
target: "_blank",
|
|
9520
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9521
|
+
size: "small",
|
|
9522
|
+
children: /* @__PURE__ */ jsx(ExternalLinkIcon, {})
|
|
8968
9523
|
})
|
|
8969
|
-
})
|
|
8970
|
-
|
|
8971
|
-
|
|
8972
|
-
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
9524
|
+
})
|
|
9525
|
+
})
|
|
9526
|
+
}), /* @__PURE__ */ jsx(PropertyCollectionView, {
|
|
9527
|
+
data: entity.values,
|
|
9528
|
+
properties,
|
|
9529
|
+
size: "medium"
|
|
9530
|
+
})]
|
|
9531
|
+
});
|
|
9532
|
+
}
|
|
9533
|
+
//#endregion
|
|
9534
|
+
//#region src/components/EntityIdentityBar.tsx
|
|
9535
|
+
/**
|
|
9536
|
+
* The persistent header of an entity view: who this record is, and what you can
|
|
9537
|
+
* do to it.
|
|
9538
|
+
*
|
|
9539
|
+
* The title and the id used to live inside the scrolling form, above 72px of
|
|
9540
|
+
* empty space — 219px of chrome that carried nothing and scrolled away the
|
|
9541
|
+
* moment you touched the wheel, leaving no indication of which record you were
|
|
9542
|
+
* editing. They live here instead, and the left half of a bar that was
|
|
9543
|
+
* previously empty now does the work.
|
|
9544
|
+
*/
|
|
9545
|
+
function EntityIdentityBar({ collection, title, entityId, status, dirty, saving, onSave, onDiscard, saveDisabled, hasErrors, onSaveAndClose, onBack, onInspect, onViewHistory, recordActions, pluginActions, trailing, leading }) {
|
|
9546
|
+
const { t } = useTranslation();
|
|
9547
|
+
const saveLabel = status === "existing" ? t("save") : status === "copy" ? t("create_copy") : t("create");
|
|
9548
|
+
const closeLabel = status === "existing" ? t("save_and_close") : status === "copy" ? t("create_copy_and_close") : t("create_and_close");
|
|
9549
|
+
const hasMenu = Boolean(recordActions) || Boolean(onInspect) || Boolean(onViewHistory);
|
|
9550
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9551
|
+
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),
|
|
9552
|
+
children: [
|
|
9553
|
+
leading,
|
|
9554
|
+
onBack && /* @__PURE__ */ jsx(Tooltip, {
|
|
9555
|
+
title: t("back"),
|
|
9556
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9557
|
+
size: "small",
|
|
9558
|
+
onClick: onBack,
|
|
9559
|
+
"aria-label": t("back"),
|
|
9560
|
+
children: /* @__PURE__ */ jsx(ArrowLeftIcon, { size: iconSize.smallest })
|
|
9561
|
+
})
|
|
9562
|
+
}),
|
|
9563
|
+
/* @__PURE__ */ jsxs(Typography, {
|
|
9564
|
+
variant: "caption",
|
|
9565
|
+
className: "text-text-disabled dark:text-text-disabled-dark whitespace-nowrap hidden sm:block",
|
|
9566
|
+
children: [collection.name, "\xA0/"]
|
|
9567
|
+
}),
|
|
9568
|
+
/* @__PURE__ */ jsx("span", {
|
|
9569
|
+
className: "font-headers font-semibold text-[15px] tracking-tight truncate min-w-0",
|
|
9570
|
+
title,
|
|
9571
|
+
children: title
|
|
9572
|
+
}),
|
|
9573
|
+
entityId !== void 0 && /* @__PURE__ */ jsx(IdChip, { value: String(entityId) }),
|
|
9574
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
9575
|
+
onSave && /* @__PURE__ */ jsx(SaveState, {
|
|
9576
|
+
dirty,
|
|
9577
|
+
saving,
|
|
9578
|
+
status,
|
|
9579
|
+
t
|
|
9580
|
+
}),
|
|
9581
|
+
pluginActions,
|
|
9582
|
+
onDiscard && dirty && !saving && /* @__PURE__ */ jsx(Button, {
|
|
9583
|
+
variant: "text",
|
|
9584
|
+
size: "small",
|
|
9585
|
+
onClick: onDiscard,
|
|
9586
|
+
children: status === "existing" ? t("discard") : t("clear")
|
|
9587
|
+
}),
|
|
9588
|
+
onSave && /* @__PURE__ */ jsx(Tooltip, {
|
|
9589
|
+
title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
|
|
9590
|
+
children: /* @__PURE__ */ jsx(LoadingButton, {
|
|
9591
|
+
variant: onSaveAndClose ? "text" : "filled",
|
|
9592
|
+
color: "primary",
|
|
9593
|
+
size: "small",
|
|
9594
|
+
loading: saving && !onSaveAndClose,
|
|
9595
|
+
disabled: saveDisabled,
|
|
9596
|
+
onClick: onSave,
|
|
9597
|
+
children: saveLabel
|
|
9598
|
+
})
|
|
9599
|
+
}),
|
|
9600
|
+
onSaveAndClose && /* @__PURE__ */ jsx(LoadingButton, {
|
|
9601
|
+
variant: "filled",
|
|
9602
|
+
color: "primary",
|
|
9603
|
+
size: "small",
|
|
9604
|
+
loading: saving,
|
|
9605
|
+
disabled: saveDisabled,
|
|
9606
|
+
onClick: onSaveAndClose,
|
|
9607
|
+
children: closeLabel
|
|
9608
|
+
}),
|
|
9609
|
+
hasMenu && /* @__PURE__ */ jsxs(Menu, {
|
|
9610
|
+
align: "end",
|
|
9611
|
+
trigger: /* @__PURE__ */ jsx(IconButton, {
|
|
9612
|
+
size: "small",
|
|
9613
|
+
"aria-label": t("more_actions") ?? "More actions",
|
|
9614
|
+
children: /* @__PURE__ */ jsx(MoreVerticalIcon, { size: iconSize.smallest })
|
|
9615
|
+
}),
|
|
9616
|
+
children: [
|
|
9617
|
+
recordActions,
|
|
9618
|
+
recordActions && (onInspect || onViewHistory) && /* @__PURE__ */ jsx(Separator, { orientation: "horizontal" }),
|
|
9619
|
+
onViewHistory && /* @__PURE__ */ jsxs(MenuItem, {
|
|
9620
|
+
onClick: onViewHistory,
|
|
9621
|
+
children: [/* @__PURE__ */ jsx(HistoryIcon, { size: iconSize.smallest }), t("record_history") ?? "History"]
|
|
9622
|
+
}),
|
|
9623
|
+
onInspect && /* @__PURE__ */ jsxs(MenuItem, {
|
|
9624
|
+
onClick: onInspect,
|
|
9625
|
+
children: [/* @__PURE__ */ jsx(CodeIcon, { size: iconSize.smallest }), t("inspect_record") ?? "Inspect record"]
|
|
8982
9626
|
})
|
|
8983
|
-
|
|
8984
|
-
}),
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
9627
|
+
]
|
|
9628
|
+
}),
|
|
9629
|
+
trailing
|
|
9630
|
+
]
|
|
9631
|
+
});
|
|
9632
|
+
}
|
|
9633
|
+
/**
|
|
9634
|
+
* The words the unlabelled floating circle never said.
|
|
9635
|
+
*
|
|
9636
|
+
* That indicator was a ✓ / pencil / spinner chip stuck to the top-right of the
|
|
9637
|
+
* scroll area in an `h-0 overflow-visible` container, so it drifted across
|
|
9638
|
+
* field values as you scrolled — and it duplicated what the Save button was
|
|
9639
|
+
* already able to tell you.
|
|
9640
|
+
*/
|
|
9641
|
+
function SaveState({ dirty, saving, status, t }) {
|
|
9642
|
+
if (saving) return /* @__PURE__ */ jsx(StateText, { children: t("saving") });
|
|
9643
|
+
if (dirty) return /* @__PURE__ */ jsx(StateText, { children: t("unsaved_changes_title") });
|
|
9644
|
+
if (status === "existing") return /* @__PURE__ */ jsx(StateText, { children: t("entity_saved") ?? "Saved" });
|
|
9645
|
+
return null;
|
|
9646
|
+
}
|
|
9647
|
+
function StateText({ children }) {
|
|
9648
|
+
return /* @__PURE__ */ jsx("span", {
|
|
9649
|
+
className: "text-xs text-text-disabled dark:text-text-disabled-dark whitespace-nowrap hidden md:inline",
|
|
9650
|
+
children
|
|
9651
|
+
});
|
|
9652
|
+
}
|
|
9653
|
+
/**
|
|
9654
|
+
* The id, as a chip you can copy rather than a full-width alert holding a UUID.
|
|
9655
|
+
*/
|
|
9656
|
+
function IdChip({ value }) {
|
|
9657
|
+
const { t } = useTranslation();
|
|
9658
|
+
const [copied, setCopied] = useState(false);
|
|
9659
|
+
const truncated = value.length > 14 ? `${value.slice(0, 8)}…${value.slice(-4)}` : value;
|
|
9660
|
+
const copy = () => {
|
|
9661
|
+
navigator.clipboard?.writeText(value).then(() => {
|
|
9662
|
+
setCopied(true);
|
|
9663
|
+
setTimeout(() => setCopied(false), 1600);
|
|
9664
|
+
}, () => void 0);
|
|
9665
|
+
};
|
|
9666
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
9667
|
+
title: copied ? t("copied") : value,
|
|
9668
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
9669
|
+
type: "button",
|
|
9670
|
+
onClick: copy,
|
|
9671
|
+
"aria-label": `${t("copy_id")} ${value}`,
|
|
9672
|
+
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"),
|
|
9673
|
+
children: [truncated, copied ? /* @__PURE__ */ jsx(CheckIcon, { size: iconSize.smallest }) : /* @__PURE__ */ jsx(CopyIcon, { size: iconSize.smallest })]
|
|
9674
|
+
})
|
|
9675
|
+
});
|
|
9676
|
+
}
|
|
9677
|
+
//#endregion
|
|
9678
|
+
//#region src/components/CollectionViewBinding/SplitViewContext.tsx
|
|
9679
|
+
var SplitViewContext = React.createContext(void 0);
|
|
9680
|
+
/**
|
|
9681
|
+
* The enclosing split view, or `undefined` when the component is not rendered
|
|
9682
|
+
* inside one (a side panel, a dialog, a full-screen route).
|
|
9683
|
+
*/
|
|
9684
|
+
function useSplitView() {
|
|
9685
|
+
return useContext(SplitViewContext);
|
|
9686
|
+
}
|
|
9687
|
+
function SplitViewProvider({ value, children }) {
|
|
9688
|
+
return /* @__PURE__ */ jsx(SplitViewContext.Provider, {
|
|
9689
|
+
value,
|
|
9690
|
+
children
|
|
9691
|
+
});
|
|
9692
|
+
}
|
|
9693
|
+
//#endregion
|
|
9694
|
+
//#region src/components/CollectionViewBinding/SplitListCloseButton.tsx
|
|
9695
|
+
/**
|
|
9696
|
+
* Closes the list of a split view, leaving the open record on its own.
|
|
9697
|
+
*
|
|
9698
|
+
* It leads the record's own app bar, where the breadcrumb starts — this is the
|
|
9699
|
+
* entity pane's old "Open full screen" button, moved to that edge and wearing an
|
|
9700
|
+
* X: closing the list and opening the record full screen are the same thing, so
|
|
9701
|
+
* there is one control and one route (`#full`) rather than two.
|
|
9702
|
+
*/
|
|
9703
|
+
function SplitListCloseButton() {
|
|
9704
|
+
const { t } = useTranslation();
|
|
9705
|
+
const splitView = useSplitView();
|
|
9706
|
+
if (!splitView?.detailOpen) return null;
|
|
9707
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
9708
|
+
title: t("hide_list"),
|
|
9709
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9710
|
+
size: "small",
|
|
9711
|
+
onClick: splitView.openFullScreen,
|
|
9712
|
+
"aria-label": t("hide_list"),
|
|
9713
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.smallest })
|
|
8989
9714
|
})
|
|
8990
9715
|
});
|
|
8991
9716
|
}
|
|
@@ -9018,15 +9743,90 @@ function JsonPreviewBinding({ values }) {
|
|
|
9018
9743
|
});
|
|
9019
9744
|
}
|
|
9020
9745
|
//#endregion
|
|
9746
|
+
//#region src/components/EntityInspector.tsx
|
|
9747
|
+
var EntityHistoryView = lazy(() => import("./history-D7L_JlLO.js").then((m) => ({ default: m.EntityHistoryView })));
|
|
9748
|
+
/**
|
|
9749
|
+
* Raw values and revision history, as an inspector rather than as tabs.
|
|
9750
|
+
*
|
|
9751
|
+
* These were the first two entries in the entity tab strip — two icon-only tabs
|
|
9752
|
+
* with no label and no tooltip, sitting *before* the record you opened the page
|
|
9753
|
+
* to edit. They are developer tools, not places the record lives, so they belong
|
|
9754
|
+
* behind one affordance in the bar; a content editor should never meet `<>`.
|
|
9755
|
+
*
|
|
9756
|
+
* The panel sits below the identity bar and beside the record deliberately: you
|
|
9757
|
+
* keep seeing which record you are inspecting, and keep being able to edit it.
|
|
9758
|
+
*/
|
|
9759
|
+
function EntityInspector({ tab, onTabChange, onClose, collection, entity, formContext, values, includeHistory, refreshToken }) {
|
|
9760
|
+
const { t } = useTranslation();
|
|
9761
|
+
const open = tab !== null;
|
|
9762
|
+
useEffect(() => {
|
|
9763
|
+
if (!open) return;
|
|
9764
|
+
const onKeyDown = (e) => {
|
|
9765
|
+
if (e.key === "Escape") {
|
|
9766
|
+
e.stopPropagation();
|
|
9767
|
+
onClose();
|
|
9768
|
+
}
|
|
9769
|
+
};
|
|
9770
|
+
window.addEventListener("keydown", onKeyDown);
|
|
9771
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
9772
|
+
}, [open, onClose]);
|
|
9773
|
+
if (!open) return null;
|
|
9774
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
9775
|
+
className: cls("relative flex flex-col shrink-0 h-full w-full max-w-md border-l", "bg-white dark:bg-surface-800", defaultBorderMixin),
|
|
9776
|
+
role: "complementary",
|
|
9777
|
+
"aria-label": t("inspect_record") ?? "Inspect record",
|
|
9778
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
9779
|
+
className: cls("h-11 shrink-0 flex items-center gap-2 pl-4 pr-1.5 border-b", defaultBorderMixin),
|
|
9780
|
+
children: [
|
|
9781
|
+
/* @__PURE__ */ jsx("span", {
|
|
9782
|
+
className: "text-sm font-semibold",
|
|
9783
|
+
children: t("inspect") ?? "Inspect"
|
|
9784
|
+
}),
|
|
9785
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
9786
|
+
includeHistory ? /* @__PURE__ */ jsxs(Tabs, {
|
|
9787
|
+
value: tab ?? "json",
|
|
9788
|
+
className: "!w-fit",
|
|
9789
|
+
onValueChange: (v) => onTabChange(v),
|
|
9790
|
+
children: [/* @__PURE__ */ jsx(Tab, {
|
|
9791
|
+
value: "json",
|
|
9792
|
+
className: "text-xs",
|
|
9793
|
+
children: t("json")
|
|
9794
|
+
}), /* @__PURE__ */ jsx(Tab, {
|
|
9795
|
+
value: "history",
|
|
9796
|
+
className: "text-xs",
|
|
9797
|
+
children: t("record_history") ?? "History"
|
|
9798
|
+
})]
|
|
9799
|
+
}) : null,
|
|
9800
|
+
/* @__PURE__ */ jsx(IconButton, {
|
|
9801
|
+
size: "small",
|
|
9802
|
+
onClick: onClose,
|
|
9803
|
+
"aria-label": t("close_inspector") ?? "Close inspector",
|
|
9804
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.smallest })
|
|
9805
|
+
})
|
|
9806
|
+
]
|
|
9807
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
9808
|
+
className: "flex-1 min-h-0 overflow-auto",
|
|
9809
|
+
children: /* @__PURE__ */ jsxs(ErrorBoundary, { children: [tab === "json" && /* @__PURE__ */ jsx(JsonPreviewBinding, { values: values ?? entity?.values ?? {} }), tab === "history" && includeHistory && formContext && /* @__PURE__ */ jsx(Suspense, {
|
|
9810
|
+
fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
|
|
9811
|
+
children: /* @__PURE__ */ jsx(EntityHistoryView, {
|
|
9812
|
+
collection,
|
|
9813
|
+
entity,
|
|
9814
|
+
formContext,
|
|
9815
|
+
refreshToken,
|
|
9816
|
+
modifiedValues: values ?? entity?.values
|
|
9817
|
+
})
|
|
9818
|
+
})] })
|
|
9819
|
+
})]
|
|
9820
|
+
});
|
|
9821
|
+
}
|
|
9822
|
+
//#endregion
|
|
9021
9823
|
//#region src/util/view_constants.ts
|
|
9022
9824
|
/**
|
|
9023
9825
|
* Shared tab value constants used by EditViewBinding and DetailViewBinding.
|
|
9024
9826
|
*/
|
|
9025
9827
|
var MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
|
|
9026
|
-
var JSON_TAB_VALUE = "__json";
|
|
9027
9828
|
//#endregion
|
|
9028
9829
|
//#region src/components/DetailViewBinding.tsx
|
|
9029
|
-
var EntityHistoryView$1 = lazy(() => import("./history-g9688IaK.js").then((m) => ({ default: m.EntityHistoryView })));
|
|
9030
9830
|
function DetailViewBinding({ entityId, ...props }) {
|
|
9031
9831
|
const { entity, dataLoading, dataLoadingError } = useFetch({
|
|
9032
9832
|
path: props.path,
|
|
@@ -9050,7 +9850,7 @@ function DetailViewBinding({ entityId, ...props }) {
|
|
|
9050
9850
|
children: content
|
|
9051
9851
|
});
|
|
9052
9852
|
}
|
|
9053
|
-
function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onTabChange, onEditClick, entity, dataLoading, layout = "full_screen", barActions }) {
|
|
9853
|
+
function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onTabChange, onEditClick, entity, dataLoading, layout = "full_screen", barActions, barActionsStart }) {
|
|
9054
9854
|
const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
|
|
9055
9855
|
const { t } = useTranslation();
|
|
9056
9856
|
const context = useRebaseContext();
|
|
@@ -9093,22 +9893,20 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
9093
9893
|
const customViewsCount = customViews?.length ?? 0;
|
|
9094
9894
|
const includeJsonView = collection.includeJsonView === void 0 ? true : collection.includeJsonView;
|
|
9095
9895
|
const includeHistoryView = Boolean(collection.history);
|
|
9096
|
-
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0
|
|
9896
|
+
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
|
|
9897
|
+
const [inspectorTab, setInspectorTab] = useState(null);
|
|
9898
|
+
const legacyInspectorTab = selectedTab === "__json" ? "json" : selectedTab === "__rebase_history" ? "history" : void 0;
|
|
9899
|
+
useEffect(() => {
|
|
9900
|
+
if (legacyInspectorTab) setInspectorTab(legacyInspectorTab);
|
|
9901
|
+
}, [legacyInspectorTab]);
|
|
9097
9902
|
const { resolvedEntityViews } = resolvedSelectedEntityView(customViews, customizationController, void 0, canEdit);
|
|
9098
9903
|
const activeTab = useMemo(() => {
|
|
9099
|
-
return new Set([
|
|
9904
|
+
return /* @__PURE__ */ new Set([
|
|
9100
9905
|
"__main_##Q$SC^#S6",
|
|
9101
|
-
...includeJsonView ? ["__json"] : [],
|
|
9102
|
-
...includeHistoryView ? ["__rebase_history"] : [],
|
|
9103
9906
|
...resolvedEntityViews.map((v) => v.key),
|
|
9104
9907
|
...subcollections.map((s) => s.slug)
|
|
9105
9908
|
]);
|
|
9106
|
-
}, [
|
|
9107
|
-
includeJsonView,
|
|
9108
|
-
includeHistoryView,
|
|
9109
|
-
resolvedEntityViews,
|
|
9110
|
-
subcollections
|
|
9111
|
-
]).has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
|
|
9909
|
+
}, [resolvedEntityViews, subcollections]).has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
|
|
9112
9910
|
const { selectedEntityView } = resolvedSelectedEntityView(customViews, customizationController, activeTab, canEdit);
|
|
9113
9911
|
const mainViewVisible = activeTab === MAIN_TAB_VALUE;
|
|
9114
9912
|
const mountedTabsRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -9198,25 +9996,6 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
9198
9996
|
}, `custom_view_${customView.key}`);
|
|
9199
9997
|
}).filter(Boolean);
|
|
9200
9998
|
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
9999
|
const subCollectionsViews = childViews && childViews.map(({ collection: subcollection }) => {
|
|
9221
10000
|
const subcollectionId = subcollection.slug;
|
|
9222
10001
|
const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(getCollectionDataPath(subcollection))}` : void 0;
|
|
@@ -9315,13 +10094,13 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
9315
10094
|
children: /* @__PURE__ */ jsx(ArrowLeftIcon, { size: iconSize.smallest })
|
|
9316
10095
|
})
|
|
9317
10096
|
}) : null;
|
|
9318
|
-
const
|
|
9319
|
-
const fullScreenButton = !barActions && (layout === "side_panel" || layout === "split" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
|
|
10097
|
+
const fullScreenButton = !barActions && (layout === "side_panel" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
|
|
9320
10098
|
title: "Open full screen",
|
|
9321
10099
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
9322
10100
|
size: "small",
|
|
9323
10101
|
onClick: () => {
|
|
9324
|
-
|
|
10102
|
+
const entityUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}`);
|
|
10103
|
+
navigate(`${entityUrl}#full`);
|
|
9325
10104
|
},
|
|
9326
10105
|
children: /* @__PURE__ */ jsx(Maximize2Icon, { size: iconSize.smallest })
|
|
9327
10106
|
})
|
|
@@ -9336,59 +10115,27 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
9336
10115
|
}) : null;
|
|
9337
10116
|
const mainView = /* @__PURE__ */ jsx("div", {
|
|
9338
10117
|
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
|
-
})]
|
|
10118
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
10119
|
+
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"),
|
|
10120
|
+
children: propertyDetailView()
|
|
9384
10121
|
})
|
|
9385
10122
|
});
|
|
9386
10123
|
let result = /* @__PURE__ */ jsxs("div", {
|
|
9387
10124
|
className: "relative flex flex-col h-full w-full bg-white dark:bg-surface-800",
|
|
9388
10125
|
children: [
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
10126
|
+
/* @__PURE__ */ jsx(EntityIdentityBar, {
|
|
10127
|
+
collection,
|
|
10128
|
+
title,
|
|
10129
|
+
entityId,
|
|
10130
|
+
status: "existing",
|
|
10131
|
+
dirty: false,
|
|
10132
|
+
saving: false,
|
|
10133
|
+
onInspect: includeJsonView ? () => setInspectorTab("json") : void 0,
|
|
10134
|
+
onViewHistory: includeHistoryView ? () => setInspectorTab("history") : void 0,
|
|
10135
|
+
leading: /* @__PURE__ */ jsxs(Fragment, { children: [barActionsStart, layout === "split" && /* @__PURE__ */ jsx(SplitListCloseButton, {})] }),
|
|
10136
|
+
trailing: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10137
|
+
editButton,
|
|
10138
|
+
pluginActionsTop,
|
|
9392
10139
|
backToCollectionButton,
|
|
9393
10140
|
fullScreenButton,
|
|
9394
10141
|
barActions?.({
|
|
@@ -9396,51 +10143,53 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
9396
10143
|
entityId,
|
|
9397
10144
|
values: usedEntity?.values ?? {},
|
|
9398
10145
|
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
10146
|
})
|
|
9437
|
-
]
|
|
10147
|
+
] })
|
|
10148
|
+
}),
|
|
10149
|
+
hasAdditionalViews && /* @__PURE__ */ jsx("div", {
|
|
10150
|
+
className: cls("h-10 shrink-0 flex items-stretch border-b px-2 min-w-0", defaultBorderMixin),
|
|
10151
|
+
children: /* @__PURE__ */ jsxs(Tabs, {
|
|
10152
|
+
className: "!w-full",
|
|
10153
|
+
innerClassName: "h-full",
|
|
10154
|
+
variant: "boxy",
|
|
10155
|
+
value: activeTab,
|
|
10156
|
+
onValueChange: (value) => {
|
|
10157
|
+
onSideTabClick(value);
|
|
10158
|
+
},
|
|
10159
|
+
children: [
|
|
10160
|
+
/* @__PURE__ */ jsx(Tab, {
|
|
10161
|
+
value: "__main_##Q$SC^#S6",
|
|
10162
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
10163
|
+
className: "flex items-center gap-1.5",
|
|
10164
|
+
children: [getIcon(collection.icon, void 0, void 0, "smallest"), collection.singularName ?? collection.name]
|
|
10165
|
+
})
|
|
10166
|
+
}),
|
|
10167
|
+
customViewTabsStart,
|
|
10168
|
+
customViewTabsEnd,
|
|
10169
|
+
subcollectionTabs
|
|
10170
|
+
]
|
|
10171
|
+
})
|
|
9438
10172
|
}),
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
10173
|
+
/* @__PURE__ */ jsxs("div", {
|
|
10174
|
+
className: "flex-1 min-h-0 flex",
|
|
10175
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
10176
|
+
className: "flex-1 min-w-0 flex flex-col",
|
|
10177
|
+
children: [
|
|
10178
|
+
globalLoading ? /* @__PURE__ */ jsx(DetailViewSkeleton, { collection }) : mainView,
|
|
10179
|
+
customViewsView,
|
|
10180
|
+
subCollectionsViews
|
|
10181
|
+
]
|
|
10182
|
+
}), /* @__PURE__ */ jsx(EntityInspector, {
|
|
10183
|
+
tab: inspectorTab,
|
|
10184
|
+
onTabChange: setInspectorTab,
|
|
10185
|
+
onClose: () => setInspectorTab(null),
|
|
10186
|
+
collection,
|
|
10187
|
+
entity: usedEntity,
|
|
10188
|
+
formContext: readOnlyFormContext,
|
|
10189
|
+
values: usedEntity?.values,
|
|
10190
|
+
includeHistory: includeHistoryView
|
|
10191
|
+
})]
|
|
10192
|
+
})
|
|
9444
10193
|
]
|
|
9445
10194
|
});
|
|
9446
10195
|
if (plugins && plugins.length > 0) result = /* @__PURE__ */ jsx(PluginProviderStack, {
|
|
@@ -9753,27 +10502,43 @@ function SplitListView({ collection, tableController, onEntityClick: externalOnE
|
|
|
9753
10502
|
}
|
|
9754
10503
|
}, String(renderedEntityId)) })
|
|
9755
10504
|
}) : /* @__PURE__ */ jsx(Fragment, {});
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
10505
|
+
const openFullScreen = useCallback(() => {
|
|
10506
|
+
if (selectedEntityId === void 0) return;
|
|
10507
|
+
navigate(`${buildUrl(`${path}/${selectedEntityId}${isEditMode ? "/edit" : ""}`)}#full`);
|
|
10508
|
+
}, [
|
|
10509
|
+
navigate,
|
|
10510
|
+
buildUrl,
|
|
10511
|
+
path,
|
|
10512
|
+
selectedEntityId,
|
|
10513
|
+
isEditMode
|
|
10514
|
+
]);
|
|
10515
|
+
return /* @__PURE__ */ jsxs(SplitViewProvider, {
|
|
10516
|
+
value: useMemo(() => ({
|
|
10517
|
+
detailOpen: selectedEntityId !== void 0,
|
|
10518
|
+
openFullScreen
|
|
10519
|
+
}), [selectedEntityId, openFullScreen]),
|
|
10520
|
+
children: [/* @__PURE__ */ jsx(ResizablePanels, {
|
|
10521
|
+
stacked: !largeLayout,
|
|
10522
|
+
firstPanel: listPanel,
|
|
10523
|
+
secondPanel: detailPanel,
|
|
10524
|
+
showSecondPanel: isDetailVisible,
|
|
10525
|
+
panelSizePercent: animationPhase === "entering" ? 100 : panelSize,
|
|
10526
|
+
onPanelSizeChange: setPanelSize,
|
|
10527
|
+
minPanelSizePx: 240
|
|
10528
|
+
}), /* @__PURE__ */ jsx(UnsavedChangesDialog, {
|
|
10529
|
+
open: blocker?.state === "blocked",
|
|
10530
|
+
handleOk: () => {
|
|
10531
|
+
dirty.current = false;
|
|
10532
|
+
blocker?.proceed?.();
|
|
10533
|
+
},
|
|
10534
|
+
handleCancel: () => blocker?.reset?.(),
|
|
10535
|
+
body: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10536
|
+
"You have unsaved changes in this ",
|
|
10537
|
+
/* @__PURE__ */ jsx("b", { children: collection.singularName ?? collection.name }),
|
|
10538
|
+
"."
|
|
10539
|
+
] })
|
|
10540
|
+
})]
|
|
10541
|
+
});
|
|
9777
10542
|
}
|
|
9778
10543
|
//#endregion
|
|
9779
10544
|
//#region src/components/CollectionViewBinding/BoardCardBinding.tsx
|
|
@@ -9927,6 +10692,12 @@ var BoardCardBinding = memo(BoardCardBindingInner);
|
|
|
9927
10692
|
//#region src/components/CollectionViewBinding/useBoardDataController.tsx
|
|
9928
10693
|
var DEFAULT_PAGE_SIZE$1 = 20;
|
|
9929
10694
|
/**
|
|
10695
|
+
* How long a column waits for its subscription's first payload before reading
|
|
10696
|
+
* once over HTTP instead. Long enough that a healthy socket always wins the
|
|
10697
|
+
* race, short enough that a dead one does not hold a blank board.
|
|
10698
|
+
*/
|
|
10699
|
+
var LIVE_FIRST_PAINT_TIMEOUT_MS = 2500;
|
|
10700
|
+
/**
|
|
9930
10701
|
* Shallow equality for entity value records.
|
|
9931
10702
|
* Handles primitives and reference equality for nested values.
|
|
9932
10703
|
* Avoids JSON.stringify allocation on every comparison.
|
|
@@ -10152,36 +10923,66 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
10152
10923
|
};
|
|
10153
10924
|
});
|
|
10154
10925
|
};
|
|
10926
|
+
const accessor = currentDataClient.collection(currentResolvedPath);
|
|
10927
|
+
const includeParams = getRelationIncludeParams(currentCollection);
|
|
10928
|
+
const fetchOnce = () => accessor.find({
|
|
10929
|
+
where: whereFilter,
|
|
10930
|
+
limit: itemCount,
|
|
10931
|
+
orderBy: orderByParam,
|
|
10932
|
+
include: includeParams
|
|
10933
|
+
});
|
|
10155
10934
|
const onError = (error) => {
|
|
10156
10935
|
if (isCleaningUpRef.current) return;
|
|
10157
10936
|
console.error(`Error loading column ${column}:`, error);
|
|
10937
|
+
fetchOnce().then((res) => onUpdate(res.data)).catch(() => {
|
|
10938
|
+
if (isCleaningUpRef.current) return;
|
|
10939
|
+
setColumnData((prev) => ({
|
|
10940
|
+
...prev,
|
|
10941
|
+
[column]: {
|
|
10942
|
+
...prev[column],
|
|
10943
|
+
entities: prev[column]?.entities ?? [],
|
|
10944
|
+
loading: false,
|
|
10945
|
+
hasMore: false,
|
|
10946
|
+
error
|
|
10947
|
+
}
|
|
10948
|
+
}));
|
|
10949
|
+
});
|
|
10950
|
+
};
|
|
10951
|
+
if (accessor.listen) {
|
|
10952
|
+
let liveDataReceived = false;
|
|
10953
|
+
const unsubscribe = accessor.listen({
|
|
10954
|
+
where: whereFilter,
|
|
10955
|
+
limit: itemCount,
|
|
10956
|
+
orderBy: orderByParam,
|
|
10957
|
+
include: includeParams
|
|
10958
|
+
}, (res) => {
|
|
10959
|
+
liveDataReceived = true;
|
|
10960
|
+
onUpdate(res.data);
|
|
10961
|
+
}, onError);
|
|
10962
|
+
const firstPaintFallback = setTimeout(() => {
|
|
10963
|
+
if (liveDataReceived || isCleaningUpRef.current) return;
|
|
10964
|
+
fetchOnce().then((res) => {
|
|
10965
|
+
if (liveDataReceived || isCleaningUpRef.current) return;
|
|
10966
|
+
onUpdate(res.data);
|
|
10967
|
+
}).catch(() => void 0);
|
|
10968
|
+
}, LIVE_FIRST_PAINT_TIMEOUT_MS);
|
|
10969
|
+
unsubscribersRef.current[column] = () => {
|
|
10970
|
+
clearTimeout(firstPaintFallback);
|
|
10971
|
+
unsubscribe?.();
|
|
10972
|
+
};
|
|
10973
|
+
} else fetchOnce().then((res) => onUpdate(res.data)).catch((error) => {
|
|
10974
|
+
if (isCleaningUpRef.current) return;
|
|
10158
10975
|
setColumnData((prev) => ({
|
|
10159
10976
|
...prev,
|
|
10160
10977
|
[column]: {
|
|
10161
10978
|
...prev[column],
|
|
10162
|
-
entities: [],
|
|
10979
|
+
entities: prev[column]?.entities ?? [],
|
|
10163
10980
|
loading: false,
|
|
10164
10981
|
hasMore: false,
|
|
10165
10982
|
error
|
|
10166
10983
|
}
|
|
10167
10984
|
}));
|
|
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);
|
|
10985
|
+
});
|
|
10185
10986
|
}, []);
|
|
10186
10987
|
useEffect(() => {
|
|
10187
10988
|
isCleaningUpRef.current = false;
|
|
@@ -10203,7 +11004,8 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
10203
11004
|
const timeoutId = setTimeout(() => {
|
|
10204
11005
|
if (isCleaningUpRef.current) return;
|
|
10205
11006
|
currentColumns.forEach((column) => {
|
|
10206
|
-
|
|
11007
|
+
const itemCount = currentColumnItemCounts[column] ?? pageSize;
|
|
11008
|
+
subscribeToColumn(column, itemCount);
|
|
10207
11009
|
if (isColumnExcludedByFilter(column, currentFilterValues, currentColumnProperty)) return;
|
|
10208
11010
|
const accessor = currentDataClient.collection(currentResolvedPath);
|
|
10209
11011
|
if (accessor.count) {
|
|
@@ -10395,6 +11197,40 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
10395
11197
|
}
|
|
10396
11198
|
//#endregion
|
|
10397
11199
|
//#region src/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.ts
|
|
11200
|
+
/**
|
|
11201
|
+
* The digits a board order key is built from: base36, lower case only.
|
|
11202
|
+
*
|
|
11203
|
+
* The library's default alphabet is base62, and its keys only sort correctly
|
|
11204
|
+
* under byte ordering. The column is sorted by the *database*, and Postgres'
|
|
11205
|
+
* default collation is not byte ordering — under `en_US.UTF-8`, `"aa"` sorts
|
|
11206
|
+
* before `"aC"`, so as soon as a board had been dragged around enough to reach
|
|
11207
|
+
* the upper-case digits its order stopped matching the keys. One case has no
|
|
11208
|
+
* such ambiguity: digits before letters is true of every collation in use.
|
|
11209
|
+
*
|
|
11210
|
+
* A key written with the old alphabet no longer validates, which is what makes
|
|
11211
|
+
* the board offer to initialise the column — one click, and the order is
|
|
11212
|
+
* rewritten in a form the database can sort.
|
|
11213
|
+
*/
|
|
11214
|
+
var ORDER_KEY_DIGITS = "0123456789abcdefghijklmnopqrstuvwxyz";
|
|
11215
|
+
/**
|
|
11216
|
+
* Whether a stored order value is a key `fractional-indexing` can interpolate
|
|
11217
|
+
* against.
|
|
11218
|
+
*
|
|
11219
|
+
* Anything else — a plain `"12"` from a seed or a migration, an empty string —
|
|
11220
|
+
* makes `generateKeyBetween` throw, and the fallback below then hands out the
|
|
11221
|
+
* same first key to every card, so each drag lands at the bottom of the column
|
|
11222
|
+
* on top of the last one that did. Asking the library itself is the only
|
|
11223
|
+
* definition of "valid" worth trusting.
|
|
11224
|
+
*/
|
|
11225
|
+
function isValidOrderKey(value) {
|
|
11226
|
+
if (typeof value !== "string" || value.length === 0) return false;
|
|
11227
|
+
try {
|
|
11228
|
+
generateKeyBetween(value, null, ORDER_KEY_DIGITS);
|
|
11229
|
+
return true;
|
|
11230
|
+
} catch {
|
|
11231
|
+
return false;
|
|
11232
|
+
}
|
|
11233
|
+
}
|
|
10398
11234
|
function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderProperty, dataClient, context, boardDataController, analyticsController }) {
|
|
10399
11235
|
const handleItemsReorder = useCallback(async (items, moveInfo) => {
|
|
10400
11236
|
const entity = items.find((item) => item.id === moveInfo?.itemId)?.data;
|
|
@@ -10411,17 +11247,21 @@ function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderPrope
|
|
|
10411
11247
|
if (orderProperty) {
|
|
10412
11248
|
const targetColumnItems = items;
|
|
10413
11249
|
const movedIndex = targetColumnItems.findIndex((item) => item.id === moveInfo.itemId);
|
|
10414
|
-
const
|
|
10415
|
-
|
|
11250
|
+
const keyAt = (index) => {
|
|
11251
|
+
const value = targetColumnItems[index]?.data.values?.[orderProperty];
|
|
11252
|
+
return isValidOrderKey(value) ? value : null;
|
|
11253
|
+
};
|
|
11254
|
+
const prevKey = movedIndex > 0 ? keyAt(movedIndex - 1) : null;
|
|
11255
|
+
const nextKey = movedIndex < targetColumnItems.length - 1 ? keyAt(movedIndex + 1) : null;
|
|
10416
11256
|
try {
|
|
10417
11257
|
const a = prevKey;
|
|
10418
11258
|
let b = nextKey;
|
|
10419
11259
|
if (a !== null && b !== null && a >= b) b = null;
|
|
10420
|
-
const newKey = generateKeyBetween(a, b);
|
|
11260
|
+
const newKey = generateKeyBetween(a, b, ORDER_KEY_DIGITS);
|
|
10421
11261
|
updatedValues = setIn(updatedValues, orderProperty, newKey);
|
|
10422
11262
|
} catch (e) {
|
|
10423
11263
|
console.warn("fractional-indexing error, falling back:", e);
|
|
10424
|
-
const newKey = generateKeyBetween(null, null);
|
|
11264
|
+
const newKey = generateKeyBetween(null, null, ORDER_KEY_DIGITS);
|
|
10425
11265
|
updatedValues = setIn(updatedValues, orderProperty, newKey);
|
|
10426
11266
|
}
|
|
10427
11267
|
}
|
|
@@ -10628,10 +11468,7 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
|
|
|
10628
11468
|
const itemsNeedBackfill = useMemo(() => {
|
|
10629
11469
|
if (!orderProperty || dataLoading) return false;
|
|
10630
11470
|
if (missingOrderCount > 0) return true;
|
|
10631
|
-
return allEntities.some((entity) =>
|
|
10632
|
-
const orderValue = entity.values?.[orderProperty];
|
|
10633
|
-
return orderValue === void 0 || orderValue === null;
|
|
10634
|
-
});
|
|
11471
|
+
return allEntities.some((entity) => !isValidOrderKey(entity.values?.[orderProperty]));
|
|
10635
11472
|
}, [
|
|
10636
11473
|
allEntities,
|
|
10637
11474
|
orderProperty,
|
|
@@ -10662,7 +11499,8 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
|
|
|
10662
11499
|
loading: colData?.loading ?? true,
|
|
10663
11500
|
hasMore: colData?.hasMore ?? false,
|
|
10664
11501
|
itemCount: colData?.entities?.length ?? 0,
|
|
10665
|
-
totalCount: colData?.totalCount
|
|
11502
|
+
totalCount: colData?.totalCount,
|
|
11503
|
+
error: colData?.error
|
|
10666
11504
|
};
|
|
10667
11505
|
});
|
|
10668
11506
|
return state;
|
|
@@ -10693,11 +11531,8 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
|
|
|
10693
11531
|
analyticsController.onAnalyticsEvent?.("kanban_backfill_order", { path: fullPath });
|
|
10694
11532
|
setBackfillLoading(true);
|
|
10695
11533
|
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);
|
|
11534
|
+
const entitiesToUpdate = (await dataClient.collection(fullPath).find({ limit: 1e4 })).data.filter((entity) => !isValidOrderKey(entity.values?.[orderProperty]));
|
|
11535
|
+
const keys = generateNKeysBetween(null, null, entitiesToUpdate.length, ORDER_KEY_DIGITS);
|
|
10701
11536
|
const updates = [];
|
|
10702
11537
|
entitiesToUpdate.forEach((entity, index) => {
|
|
10703
11538
|
const updatedValues = setIn({ ...entity.values }, orderProperty, keys[index]);
|
|
@@ -10865,7 +11700,7 @@ function CollectionBoardViewBinding({ collection, tableController, fullPath, par
|
|
|
10865
11700
|
})]
|
|
10866
11701
|
}),
|
|
10867
11702
|
/* @__PURE__ */ jsx("div", {
|
|
10868
|
-
className: "flex-1
|
|
11703
|
+
className: "flex-1 min-h-0 overflow-hidden",
|
|
10869
11704
|
children: /* @__PURE__ */ jsx(KanbanView, {
|
|
10870
11705
|
data: boardItems,
|
|
10871
11706
|
columns,
|
|
@@ -11472,9 +12307,9 @@ function ResetPasswordActionDialog({ user, open, onClose }) {
|
|
|
11472
12307
|
setLoading(true);
|
|
11473
12308
|
setError(null);
|
|
11474
12309
|
try {
|
|
11475
|
-
const
|
|
12310
|
+
const apiBase = apiBaseOf(client) ?? "";
|
|
11476
12311
|
const token = await getAuthToken?.();
|
|
11477
|
-
const response = await fetch(`${
|
|
12312
|
+
const response = await fetch(`${apiBase}/admin/users/${user.uid}/reset-password`, {
|
|
11478
12313
|
method: "POST",
|
|
11479
12314
|
headers: {
|
|
11480
12315
|
"Content-Type": "application/json",
|
|
@@ -11786,7 +12621,8 @@ function PopupFormFieldInternal({ tableKey, entityId, customFieldValidator, prop
|
|
|
11786
12621
|
const updatePopupLocation = useCallback((newPositionCandidate) => {
|
|
11787
12622
|
const draggableBoundingRect = draggableRef.current?.getBoundingClientRect();
|
|
11788
12623
|
if (!cellRect || !draggableBoundingRect || draggableBoundingRect.width === 0 || draggableBoundingRect.height === 0) return;
|
|
11789
|
-
const
|
|
12624
|
+
const basePosition = newPositionCandidate ?? getInitialLocation();
|
|
12625
|
+
const newNormalizedPosition = normalizePosition(basePosition, draggableBoundingRect, windowSize);
|
|
11790
12626
|
if (!popupLocation || newNormalizedPosition.x !== popupLocation.x || newNormalizedPosition.y !== popupLocation.y) setPopupLocation(newNormalizedPosition);
|
|
11791
12627
|
}, [
|
|
11792
12628
|
cellRect,
|
|
@@ -12341,13 +13177,120 @@ function FilterPresetsButton({ filterPresets, tableController, compact }) {
|
|
|
12341
13177
|
});
|
|
12342
13178
|
}
|
|
12343
13179
|
//#endregion
|
|
13180
|
+
//#region src/components/CollectionViewBinding/SortButton.tsx
|
|
13181
|
+
/**
|
|
13182
|
+
* Toolbar control that orders the collection by one property.
|
|
13183
|
+
*
|
|
13184
|
+
* The table orders by clicking a column header, which leaves every other view
|
|
13185
|
+
* mode — list, cards, and either of them inside a split view — with no way to
|
|
13186
|
+
* order at all, even though the controller has always carried `sortBy` and the
|
|
13187
|
+
* query has always honoured it. This binds a control to it.
|
|
13188
|
+
*
|
|
13189
|
+
* Clicking the active property flips its direction, so ascending → descending
|
|
13190
|
+
* is one click rather than a trip through a direction selector; clicking a
|
|
13191
|
+
* different one starts it ascending. The popover stays open through both.
|
|
13192
|
+
*/
|
|
13193
|
+
function SortButton({ tableController, properties }) {
|
|
13194
|
+
const { t } = useTranslation();
|
|
13195
|
+
const [open, setOpen] = useState(false);
|
|
13196
|
+
const options = useMemo(() => getSortablePropertyOptions(properties), [properties]);
|
|
13197
|
+
const { sortBy, setSortBy, setItemCount, pageSize, filterValues, checkFilterCombination } = tableController;
|
|
13198
|
+
const activeKey = sortBy?.[0];
|
|
13199
|
+
const activeDirection = sortBy?.[1];
|
|
13200
|
+
const applySort = useCallback((next) => {
|
|
13201
|
+
setSortBy?.(next);
|
|
13202
|
+
if (pageSize !== void 0) setItemCount?.(pageSize);
|
|
13203
|
+
}, [
|
|
13204
|
+
setSortBy,
|
|
13205
|
+
setItemCount,
|
|
13206
|
+
pageSize
|
|
13207
|
+
]);
|
|
13208
|
+
const onOptionClick = useCallback((key) => {
|
|
13209
|
+
if (key === activeKey) applySort([key, activeDirection === "asc" ? "desc" : "asc"]);
|
|
13210
|
+
else applySort([key, "asc"]);
|
|
13211
|
+
}, [
|
|
13212
|
+
activeKey,
|
|
13213
|
+
activeDirection,
|
|
13214
|
+
applySort
|
|
13215
|
+
]);
|
|
13216
|
+
if (!setSortBy || options.length === 0) return null;
|
|
13217
|
+
const activeOption = options.find((option) => option.key === activeKey);
|
|
13218
|
+
const label = activeOption ? `${t("sort_by")}: ${activeOption.title}` : t("sort");
|
|
13219
|
+
return /* @__PURE__ */ jsx(Popover, {
|
|
13220
|
+
open,
|
|
13221
|
+
onOpenChange: setOpen,
|
|
13222
|
+
modal: false,
|
|
13223
|
+
side: "bottom",
|
|
13224
|
+
align: "start",
|
|
13225
|
+
trigger: /* @__PURE__ */ jsx(IconButton, {
|
|
13226
|
+
size: "small",
|
|
13227
|
+
"aria-label": label,
|
|
13228
|
+
title: label,
|
|
13229
|
+
className: cls(activeOption && "text-primary"),
|
|
13230
|
+
children: activeDirection === "desc" ? /* @__PURE__ */ jsx(ArrowDownIcon, { size: iconSize.smallest }) : activeDirection === "asc" ? /* @__PURE__ */ jsx(ArrowUpIcon, { size: iconSize.smallest }) : /* @__PURE__ */ jsx(ArrowUpDownIcon, { size: iconSize.smallest })
|
|
13231
|
+
}),
|
|
13232
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
13233
|
+
className: "py-2 min-w-[240px] flex flex-col",
|
|
13234
|
+
children: [
|
|
13235
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
13236
|
+
variant: "caption",
|
|
13237
|
+
color: "secondary",
|
|
13238
|
+
className: "font-medium uppercase tracking-wider px-3 pb-1",
|
|
13239
|
+
children: t("sort_by")
|
|
13240
|
+
}),
|
|
13241
|
+
/* @__PURE__ */ jsx("div", {
|
|
13242
|
+
className: "max-h-[320px] overflow-y-auto flex flex-col",
|
|
13243
|
+
children: options.map(({ key, title, property }) => {
|
|
13244
|
+
const active = key === activeKey;
|
|
13245
|
+
const disabled = checkFilterCombination ? !checkFilterCombination(filterValues ?? {}, [key, active && activeDirection === "asc" ? "desc" : "asc"]) : false;
|
|
13246
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
13247
|
+
type: "button",
|
|
13248
|
+
role: "menuitemradio",
|
|
13249
|
+
"aria-checked": active,
|
|
13250
|
+
disabled,
|
|
13251
|
+
onClick: () => onOptionClick(key),
|
|
13252
|
+
className: cls("flex items-center gap-2 px-3 py-1.5 text-sm text-left w-full", disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer hover:bg-surface-accent-100 dark:hover:bg-surface-accent-900", active && "text-primary"),
|
|
13253
|
+
children: [
|
|
13254
|
+
/* @__PURE__ */ jsx("span", {
|
|
13255
|
+
className: "flex-shrink-0 flex items-center",
|
|
13256
|
+
children: getIconForProperty(property, "smallest")
|
|
13257
|
+
}),
|
|
13258
|
+
/* @__PURE__ */ jsx("span", {
|
|
13259
|
+
className: "flex-grow truncate",
|
|
13260
|
+
children: title
|
|
13261
|
+
}),
|
|
13262
|
+
active && /* @__PURE__ */ jsx("span", {
|
|
13263
|
+
className: "flex-shrink-0 flex items-center",
|
|
13264
|
+
children: activeDirection === "desc" ? /* @__PURE__ */ jsx(ArrowDownIcon, { size: iconSize.smallest }) : /* @__PURE__ */ jsx(ArrowUpIcon, { size: iconSize.smallest })
|
|
13265
|
+
})
|
|
13266
|
+
]
|
|
13267
|
+
}, key);
|
|
13268
|
+
})
|
|
13269
|
+
}),
|
|
13270
|
+
/* @__PURE__ */ jsx("div", {
|
|
13271
|
+
className: "px-3 pt-2",
|
|
13272
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
13273
|
+
variant: "text",
|
|
13274
|
+
size: "small",
|
|
13275
|
+
className: "w-full",
|
|
13276
|
+
disabled: !activeOption,
|
|
13277
|
+
onClick: () => applySort(void 0),
|
|
13278
|
+
children: t("clear_sort")
|
|
13279
|
+
})
|
|
13280
|
+
})
|
|
13281
|
+
]
|
|
13282
|
+
})
|
|
13283
|
+
});
|
|
13284
|
+
}
|
|
13285
|
+
//#endregion
|
|
12344
13286
|
//#region src/components/CollectionViewBinding/CollectionViewStartActions.tsx
|
|
12345
|
-
function CollectionViewStartActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, path, selectionController, tableController, collectionEntitiesCount, resolvedProperties, compact, openNewDocument }) {
|
|
13287
|
+
function CollectionViewStartActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, path, selectionController, tableController, collectionEntitiesCount, resolvedProperties, viewMode, compact, openNewDocument }) {
|
|
12346
13288
|
const context = useAdminContext();
|
|
12347
13289
|
const largeLayout = useLargeLayout();
|
|
12348
13290
|
const { t } = useTranslation();
|
|
12349
13291
|
const navigate = useNavigate();
|
|
12350
13292
|
const urlController = useUrlController();
|
|
13293
|
+
const splitView = useSplitView();
|
|
12351
13294
|
const [filtersDialogOpen, setFiltersDialogOpen] = useState(false);
|
|
12352
13295
|
const filterValues = tableController.filterValues;
|
|
12353
13296
|
const fixedFilter = collection.fixedFilter;
|
|
@@ -12371,15 +13314,16 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
|
|
|
12371
13314
|
urlController,
|
|
12372
13315
|
path
|
|
12373
13316
|
]);
|
|
12374
|
-
const backButton = compact && /* @__PURE__ */ jsx(Tooltip, {
|
|
12375
|
-
title: t("
|
|
13317
|
+
const backButton = compact && !splitView?.detailOpen && /* @__PURE__ */ jsx(Tooltip, {
|
|
13318
|
+
title: t("close"),
|
|
12376
13319
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
12377
13320
|
size: "small",
|
|
12378
13321
|
onClick: handleBackClick,
|
|
13322
|
+
"aria-label": t("close"),
|
|
12379
13323
|
className: "mr-1",
|
|
12380
|
-
children: /* @__PURE__ */ jsx(
|
|
13324
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.smallest })
|
|
12381
13325
|
})
|
|
12382
|
-
}, "
|
|
13326
|
+
}, "close_tooltip");
|
|
12383
13327
|
const filtersButton = resolvedProperties && tableController.setFilterValues && /* @__PURE__ */ jsx(Tooltip, {
|
|
12384
13328
|
title: t("filters"),
|
|
12385
13329
|
children: /* @__PURE__ */ jsx(Badge, {
|
|
@@ -12392,16 +13336,19 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
|
|
|
12392
13336
|
startIcon: /* @__PURE__ */ jsx(FilterIcon, { size: iconSize.small }),
|
|
12393
13337
|
className: cls(activeFilterCount > 0 && "text-primary"),
|
|
12394
13338
|
children: activeFilterCount > 0 ? `(${activeFilterCount})` : t("filters")
|
|
12395
|
-
}) : /* @__PURE__ */ jsx(
|
|
12396
|
-
variant: "text",
|
|
13339
|
+
}) : /* @__PURE__ */ jsx(IconButton, {
|
|
12397
13340
|
size: "small",
|
|
12398
13341
|
onClick: () => setFiltersDialogOpen(true),
|
|
12399
13342
|
className: cls(activeFilterCount > 0 && "text-primary"),
|
|
12400
|
-
children: /* @__PURE__ */ jsx(FilterIcon, { size: iconSize.
|
|
13343
|
+
children: /* @__PURE__ */ jsx(FilterIcon, { size: iconSize.smallest })
|
|
12401
13344
|
})
|
|
12402
13345
|
})
|
|
12403
13346
|
}, "filters_tooltip");
|
|
12404
|
-
const
|
|
13347
|
+
const sortButton = resolvedProperties && (viewMode === "list" || viewMode === "cards") && /* @__PURE__ */ jsx(SortButton, {
|
|
13348
|
+
tableController,
|
|
13349
|
+
properties: resolvedProperties
|
|
13350
|
+
}, "sort_button");
|
|
13351
|
+
const filterPresetsButton = !compact && collection.filterPresets?.length ? /* @__PURE__ */ jsx(FilterPresetsButton, {
|
|
12405
13352
|
filterPresets: collection.filterPresets,
|
|
12406
13353
|
tableController,
|
|
12407
13354
|
compact
|
|
@@ -12410,6 +13357,7 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
|
|
|
12410
13357
|
[
|
|
12411
13358
|
backButton,
|
|
12412
13359
|
filtersButton,
|
|
13360
|
+
sortButton,
|
|
12413
13361
|
/* @__PURE__ */ jsx(ClearFilterSortButton, {
|
|
12414
13362
|
tableController,
|
|
12415
13363
|
enabled: !collection.fixedFilter
|
|
@@ -12587,7 +13535,10 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
12587
13535
|
}, [defaultViewMode, setSearchParams]);
|
|
12588
13536
|
useEffect(() => {
|
|
12589
13537
|
if (urlView) setViewModeState(urlView);
|
|
12590
|
-
else
|
|
13538
|
+
else {
|
|
13539
|
+
const savedView = getSavedView();
|
|
13540
|
+
setViewModeState(savedView ?? defaultViewMode);
|
|
13541
|
+
}
|
|
12591
13542
|
}, [
|
|
12592
13543
|
urlView,
|
|
12593
13544
|
getSavedView,
|
|
@@ -12619,9 +13570,10 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
12619
13570
|
entityId: clickedEntity.id
|
|
12620
13571
|
});
|
|
12621
13572
|
if (collection) addRecentId(collection.slug, clickedEntity.id);
|
|
13573
|
+
const entityPath = path ?? clickedEntity.path;
|
|
12622
13574
|
navigateToEntity({
|
|
12623
13575
|
navigation: urlController,
|
|
12624
|
-
path:
|
|
13576
|
+
path: entityPath,
|
|
12625
13577
|
sidePanelController,
|
|
12626
13578
|
openEntityMode,
|
|
12627
13579
|
collection,
|
|
@@ -12765,7 +13717,8 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
12765
13717
|
const onColumnResize = useCallback(({ width, key }) => {
|
|
12766
13718
|
const collection = collectionRef.current;
|
|
12767
13719
|
if (!getPropertyInPath(collection.properties, key)) return;
|
|
12768
|
-
|
|
13720
|
+
const localCollection = buildPropertyWidthOverwrite(key, width);
|
|
13721
|
+
onCollectionModifiedForUser(path, localCollection);
|
|
12769
13722
|
}, [onCollectionModifiedForUser, path]);
|
|
12770
13723
|
const onListSizeChanged = useCallback((size) => {
|
|
12771
13724
|
setListSize(size);
|
|
@@ -12947,21 +13900,24 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
12947
13900
|
return (largeLayout ? 80 + actionsWidth : 70 + actionsWidth) + (collapsedActions.length > 0 ? largeLayout ? 40 : 30 : 0);
|
|
12948
13901
|
}, [getActionsForEntity, largeLayout]);
|
|
12949
13902
|
const tableRowActionsBuilder = useCallback(({ entity, size, width, frozen }) => {
|
|
13903
|
+
const isSelected = Boolean(usedSelectionController.selectedEntities.find((e) => e.id == entity.id && e.path == entity.path));
|
|
13904
|
+
const customEntityActions = (collection.entityActions ?? EMPTY_ARRAY).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
|
|
13905
|
+
const actions = getActionsForEntity({
|
|
13906
|
+
entity,
|
|
13907
|
+
customEntityActions
|
|
13908
|
+
});
|
|
12950
13909
|
return /* @__PURE__ */ jsx(CollectionRowActions, {
|
|
12951
13910
|
entity,
|
|
12952
13911
|
width,
|
|
12953
13912
|
frozen,
|
|
12954
|
-
isSelected
|
|
13913
|
+
isSelected,
|
|
12955
13914
|
selectionEnabled: activeSelectionEnabled,
|
|
12956
13915
|
size,
|
|
12957
13916
|
highlightEntity: setHighlightedEntity,
|
|
12958
13917
|
unhighlightEntity: unselectNavigatedEntity,
|
|
12959
13918
|
collection,
|
|
12960
13919
|
path,
|
|
12961
|
-
actions
|
|
12962
|
-
entity,
|
|
12963
|
-
customEntityActions: (collection.entityActions ?? EMPTY_ARRAY).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean)
|
|
12964
|
-
}),
|
|
13920
|
+
actions,
|
|
12965
13921
|
hideId: collection?.hideIdFromCollection,
|
|
12966
13922
|
onCollectionChange: updateLastDeleteTimestamp,
|
|
12967
13923
|
selectionController: usedSelectionController,
|
|
@@ -12983,12 +13939,13 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
12983
13939
|
const { resolvedSlots } = customizationController;
|
|
12984
13940
|
const headerActionContributions = useMemo(() => resolvedSlots.filter((s) => s.slot === "collection.header.action").sort((a, b) => (a.order ?? 50) - (b.order ?? 50)), [resolvedSlots]);
|
|
12985
13941
|
const buildAdditionalHeaderWidget = useCallback(({ property, propertyKey, onHover }) => {
|
|
13942
|
+
const collection = collectionRef.current;
|
|
12986
13943
|
const headerSlotProps = {
|
|
12987
13944
|
property,
|
|
12988
13945
|
propertyKey,
|
|
12989
13946
|
onHover,
|
|
12990
13947
|
path,
|
|
12991
|
-
collection
|
|
13948
|
+
collection,
|
|
12992
13949
|
tableController,
|
|
12993
13950
|
parentCollectionSlugs: parentCollectionSlugs ?? EMPTY_ARRAY,
|
|
12994
13951
|
parentEntityIds: parentEntityIds ?? EMPTY_ARRAY
|
|
@@ -13084,6 +14041,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
13084
14041
|
selectionController: usedSelectionController,
|
|
13085
14042
|
collectionEntitiesCount: docsCount ?? void 0,
|
|
13086
14043
|
resolvedProperties: resolvedCollection.properties,
|
|
14044
|
+
viewMode,
|
|
13087
14045
|
openNewDocument,
|
|
13088
14046
|
compact: isCompact
|
|
13089
14047
|
}),
|
|
@@ -13242,7 +14200,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
13242
14200
|
}, `split-list-view-${path}`) : /* @__PURE__ */ jsxs("div", {
|
|
13243
14201
|
className: "flex flex-col w-full h-full",
|
|
13244
14202
|
children: [toolbarNode, /* @__PURE__ */ jsx("div", {
|
|
13245
|
-
className: cls("flex-1 flex flex-col", (viewMode === "list" || viewMode === "cards") && "overflow-y-auto"),
|
|
14203
|
+
className: cls("flex-1 min-h-0 flex flex-col", (viewMode === "list" || viewMode === "cards") && "overflow-y-auto"),
|
|
13246
14204
|
children: collectionSurface
|
|
13247
14205
|
})]
|
|
13248
14206
|
}),
|
|
@@ -13455,7 +14413,18 @@ function EntityIdHeaderWidget({ collection, path, idPath, openEntityMode }) {
|
|
|
13455
14413
|
}
|
|
13456
14414
|
//#endregion
|
|
13457
14415
|
//#region src/components/EditFormActions.tsx
|
|
13458
|
-
|
|
14416
|
+
/**
|
|
14417
|
+
* The entity form's footer.
|
|
14418
|
+
*
|
|
14419
|
+
* It used to render in two shapes: this footer, and a `w-80 2xl:w-96` sticky
|
|
14420
|
+
* side rail carrying the same Save and Discard above several hundred pixels of
|
|
14421
|
+
* nothing. The rail is gone, and Save/Discard have moved to the identity bar
|
|
14422
|
+
* where they stay visible while the form scrolls — so with
|
|
14423
|
+
* `showDefaultActions={false}` this renders only what the bar cannot host:
|
|
14424
|
+
* record actions (copy, delete, custom), plugin actions, the saving error, and
|
|
14425
|
+
* the side-panel-only "save and close".
|
|
14426
|
+
*/
|
|
14427
|
+
function EditFormActions({ collection, path, entity, savingError, formex, disabled, status, pluginActions, openEntityMode, showDefaultActions = true, navigateBack, formContext }) {
|
|
13459
14428
|
const { canCreate, canDelete } = usePermissions();
|
|
13460
14429
|
const context = useAdminContext();
|
|
13461
14430
|
const sidePanelController = context.sidePanelController;
|
|
@@ -13464,31 +14433,29 @@ function EditFormActions({ collection, path, entity, layout, savingError, formex
|
|
|
13464
14433
|
const { t } = useTranslation();
|
|
13465
14434
|
const childViewSource = useChildViewSource(path);
|
|
13466
14435
|
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({
|
|
14436
|
+
return buildBottomActions$1({
|
|
13489
14437
|
savingError,
|
|
13490
14438
|
entity,
|
|
13491
|
-
formActions
|
|
14439
|
+
formActions: useMemo(() => {
|
|
14440
|
+
const customEntityActions = (collection.entityActions ?? []).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
|
|
14441
|
+
const createEnabled = canCreate(collection, path);
|
|
14442
|
+
const deleteEnabled = entity ? canDelete(collection, path, entity) : false;
|
|
14443
|
+
const disableActions = collection.disableDefaultActions ?? [];
|
|
14444
|
+
const actions = [];
|
|
14445
|
+
if (createEnabled && !disableActions.includes("copy")) actions.push(copyEntityAction);
|
|
14446
|
+
if (deleteEnabled && !disableActions.includes("delete")) actions.push(isLinkedChildView ? unlinkEntityAction : deleteEntityAction);
|
|
14447
|
+
if (customEntityActions) return mergeEntityActions(actions, customEntityActions);
|
|
14448
|
+
return actions;
|
|
14449
|
+
}, [
|
|
14450
|
+
canCreate,
|
|
14451
|
+
canDelete,
|
|
14452
|
+
collection,
|
|
14453
|
+
path,
|
|
14454
|
+
customizationController.entityActions?.length,
|
|
14455
|
+
entity,
|
|
14456
|
+
collection.disableDefaultActions,
|
|
14457
|
+
isLinkedChildView
|
|
14458
|
+
]).filter((a) => a.includeInForm === void 0 || a.includeInForm),
|
|
13492
14459
|
collection,
|
|
13493
14460
|
context,
|
|
13494
14461
|
sidePanelController,
|
|
@@ -13501,35 +14468,17 @@ function EditFormActions({ collection, path, entity, layout, savingError, formex
|
|
|
13501
14468
|
formContext,
|
|
13502
14469
|
formex,
|
|
13503
14470
|
t,
|
|
13504
|
-
|
|
13505
|
-
});
|
|
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
|
|
14471
|
+
showDefaultActions
|
|
13523
14472
|
});
|
|
13524
|
-
if (layout === "responsive") return /* @__PURE__ */ jsxs(Fragment, { children: [bottomActions, sideActions] });
|
|
13525
|
-
return layout === "bottom" ? bottomActions : sideActions;
|
|
13526
14473
|
}
|
|
13527
|
-
function buildBottomActions$1({ savingError, entity, formActions, collection, context, sidePanelController, disabled, status, sideDialogContext, pluginActions, openEntityMode, navigateBack, formContext, formex, t, className }) {
|
|
14474
|
+
function buildBottomActions$1({ savingError, entity, formActions, collection, context, sidePanelController, disabled, status, sideDialogContext, pluginActions, openEntityMode, navigateBack, formContext, formex, t, showDefaultActions, className }) {
|
|
13528
14475
|
const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
13529
14476
|
const canClose = openEntityMode === "side_panel" || openEntityMode === "dialog";
|
|
14477
|
+
const showBackToDetail = collection.defaultEntityAction === "view" && status === "existing";
|
|
14478
|
+
if (!(showDefaultActions || Boolean(savingError))) return null;
|
|
13530
14479
|
return /* @__PURE__ */ jsxs(DialogActions, {
|
|
13531
14480
|
className,
|
|
13532
|
-
position: "
|
|
14481
|
+
position: "sticky",
|
|
13533
14482
|
children: [
|
|
13534
14483
|
savingError && /* @__PURE__ */ jsx("div", {
|
|
13535
14484
|
className: "text-right",
|
|
@@ -13567,21 +14516,20 @@ function buildBottomActions$1({ savingError, entity, formActions, collection, co
|
|
|
13567
14516
|
size: iconSize.smallest
|
|
13568
14517
|
})
|
|
13569
14518
|
}) : 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, {
|
|
14519
|
+
showBackToDetail && /* @__PURE__ */ jsx(Button, {
|
|
13578
14520
|
variant: "text",
|
|
13579
14521
|
color: "primary",
|
|
13580
14522
|
disabled,
|
|
13581
14523
|
onClick: navigateBack,
|
|
13582
14524
|
children: t("back_to_detail") ?? "Back to details"
|
|
13583
14525
|
}),
|
|
13584
|
-
/* @__PURE__ */ jsx(
|
|
14526
|
+
showDefaultActions && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
|
|
14527
|
+
variant: "text",
|
|
14528
|
+
color: "primary",
|
|
14529
|
+
disabled: disabled || formex.isSubmitting,
|
|
14530
|
+
type: "reset",
|
|
14531
|
+
children: status === "existing" ? t("discard") : t("clear")
|
|
14532
|
+
}), /* @__PURE__ */ jsx(Tooltip, {
|
|
13585
14533
|
title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
|
|
13586
14534
|
children: /* @__PURE__ */ jsxs(Button, {
|
|
13587
14535
|
variant: canClose ? "text" : "filled",
|
|
@@ -13597,7 +14545,7 @@ function buildBottomActions$1({ savingError, entity, formActions, collection, co
|
|
|
13597
14545
|
status === "new" && t("create")
|
|
13598
14546
|
]
|
|
13599
14547
|
})
|
|
13600
|
-
}),
|
|
14548
|
+
})] }),
|
|
13601
14549
|
canClose && /* @__PURE__ */ jsxs(LoadingButton, {
|
|
13602
14550
|
variant: "filled",
|
|
13603
14551
|
color: "primary",
|
|
@@ -13616,77 +14564,6 @@ function buildBottomActions$1({ savingError, entity, formActions, collection, co
|
|
|
13616
14564
|
]
|
|
13617
14565
|
});
|
|
13618
14566
|
}
|
|
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
14567
|
function EntityActionButton({ action, enabled, props }) {
|
|
13691
14568
|
const snackbarController = useSnackbarController();
|
|
13692
14569
|
const [loading, setLoading] = React.useState(false);
|
|
@@ -13728,8 +14605,68 @@ function EntityActionButton({ action, enabled, props }) {
|
|
|
13728
14605
|
});
|
|
13729
14606
|
}
|
|
13730
14607
|
//#endregion
|
|
14608
|
+
//#region src/hooks/useRecordActions.tsx
|
|
14609
|
+
/**
|
|
14610
|
+
* The actions you can perform *on* a record — copy, delete, and whatever the
|
|
14611
|
+
* collection adds — resolved against the current user's permissions.
|
|
14612
|
+
*
|
|
14613
|
+
* Extracted from the form's footer so the identity bar can own them. They are
|
|
14614
|
+
* not ways to leave the form, so they never belonged next to Save; putting them
|
|
14615
|
+
* in the bar's overflow menu is what let the footer disappear instead of
|
|
14616
|
+
* becoming a full-width strip holding two icon buttons.
|
|
14617
|
+
*/
|
|
14618
|
+
function useRecordActions({ collection, path, entity }) {
|
|
14619
|
+
const { canCreate, canDelete } = usePermissions();
|
|
14620
|
+
const customizationController = useCustomizationController();
|
|
14621
|
+
const childViewSource = useChildViewSource(path);
|
|
14622
|
+
const isLinkedChildView = childViewSource?.kind === "relation" && childViewSource.mode === "linked";
|
|
14623
|
+
return useMemo(() => {
|
|
14624
|
+
const customEntityActions = (collection.entityActions ?? []).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
|
|
14625
|
+
const createEnabled = canCreate(collection, path);
|
|
14626
|
+
const deleteEnabled = entity ? canDelete(collection, path, entity) : false;
|
|
14627
|
+
const disableActions = collection.disableDefaultActions ?? [];
|
|
14628
|
+
const actions = [];
|
|
14629
|
+
if (createEnabled && !disableActions.includes("copy")) actions.push(copyEntityAction);
|
|
14630
|
+
if (deleteEnabled && !disableActions.includes("delete")) actions.push(isLinkedChildView ? unlinkEntityAction : deleteEntityAction);
|
|
14631
|
+
return (customEntityActions.length ? mergeEntityActions(actions, customEntityActions) : actions).filter((a) => a.includeInForm === void 0 || a.includeInForm);
|
|
14632
|
+
}, [
|
|
14633
|
+
canCreate,
|
|
14634
|
+
canDelete,
|
|
14635
|
+
collection,
|
|
14636
|
+
path,
|
|
14637
|
+
customizationController.entityActions?.length,
|
|
14638
|
+
entity,
|
|
14639
|
+
collection.disableDefaultActions,
|
|
14640
|
+
isLinkedChildView
|
|
14641
|
+
]);
|
|
14642
|
+
}
|
|
14643
|
+
//#endregion
|
|
14644
|
+
//#region src/hooks/useEntityDisplayTitle.tsx
|
|
14645
|
+
/**
|
|
14646
|
+
* The human name for a record, for the identity bar and the breadcrumb.
|
|
14647
|
+
*
|
|
14648
|
+
* The guard on `status` is the point. The title resolver walks candidate
|
|
14649
|
+
* properties and returns the first non-empty one, and on a brand new entity the
|
|
14650
|
+
* only populated values are the property defaults — so creating a blog post
|
|
14651
|
+
* opened a page headed **draft**, the default of its `status` enum. A record
|
|
14652
|
+
* that does not exist yet has no name; it has an intent.
|
|
14653
|
+
*/
|
|
14654
|
+
function useEntityDisplayTitle({ collection, values, entityId, status }) {
|
|
14655
|
+
const isNew = status === "new";
|
|
14656
|
+
const titlePropertyKey = getEntityTitlePropertyKeyForEntity(collection, values, entityId);
|
|
14657
|
+
const rawTitle = !isNew && values && titlePropertyKey ? getValueInPath(values, titlePropertyKey) : void 0;
|
|
14658
|
+
const titleUser = useResolvedUser(isUserSelectProperty(collection, titlePropertyKey) && typeof rawTitle === "string" ? rawTitle : void 0);
|
|
14659
|
+
const singular = collection.singularName ?? collection.name;
|
|
14660
|
+
if (isNew) return `New ${singular.toLowerCase()}`;
|
|
14661
|
+
if (titleUser) return getUserLabel(titleUser);
|
|
14662
|
+
if (rawTitle !== void 0 && rawTitle !== null) {
|
|
14663
|
+
const resolved = resolveTitleToString(rawTitle);
|
|
14664
|
+
if (resolved) return resolved;
|
|
14665
|
+
}
|
|
14666
|
+
return singular;
|
|
14667
|
+
}
|
|
14668
|
+
//#endregion
|
|
13731
14669
|
//#region src/components/EditViewBinding.tsx
|
|
13732
|
-
var EntityHistoryView = lazy(() => import("./history-g9688IaK.js").then((m) => ({ default: m.EntityHistoryView })));
|
|
13733
14670
|
/**
|
|
13734
14671
|
* This is the default view that is used as the content of a side panel when
|
|
13735
14672
|
* a record is opened.
|
|
@@ -13778,11 +14715,12 @@ function EditViewBinding({ entityId, ...props }) {
|
|
|
13778
14715
|
children: content
|
|
13779
14716
|
});
|
|
13780
14717
|
}
|
|
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 }) {
|
|
14718
|
+
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
14719
|
const ResolvedFormActions = useComponentOverride("EditView.FormActions", EditFormActions);
|
|
13783
14720
|
const ResolvedEntityForm = useComponentOverride("Entity.Form", EntityFormBinding);
|
|
13784
14721
|
const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
|
|
13785
14722
|
const context = useRebaseContext();
|
|
14723
|
+
const adminContext = useAdminContext();
|
|
13786
14724
|
const urlController = useUrlController();
|
|
13787
14725
|
const navigate = useNavigate();
|
|
13788
14726
|
const [usedEntity, setUsedEntity] = useState(entity);
|
|
@@ -13793,7 +14731,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
13793
14731
|
useLargeLayout();
|
|
13794
14732
|
const customizationController = useCustomizationController();
|
|
13795
14733
|
const plugins = customizationController.plugins;
|
|
13796
|
-
const
|
|
14734
|
+
const formActionTopProps = useMemo(() => ({
|
|
13797
14735
|
entityId,
|
|
13798
14736
|
parentCollectionSlugs,
|
|
13799
14737
|
parentEntityIds,
|
|
@@ -13814,7 +14752,8 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
13814
14752
|
context,
|
|
13815
14753
|
formContext,
|
|
13816
14754
|
layout
|
|
13817
|
-
])
|
|
14755
|
+
]);
|
|
14756
|
+
const pluginActionsTop = useSlot("form.actions.top", formActionTopProps);
|
|
13818
14757
|
const defaultSelectedView = useMemo(() => resolveDefaultSelectedView(collection ? collection.defaultSelectedView : void 0, {
|
|
13819
14758
|
status,
|
|
13820
14759
|
entityId
|
|
@@ -13840,24 +14779,17 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
13840
14779
|
const customViewsCount = customViews?.length ?? 0;
|
|
13841
14780
|
const includeJsonView = collection.includeJsonView === void 0 ? true : collection.includeJsonView;
|
|
13842
14781
|
const includeHistoryView = Boolean(collection.history);
|
|
13843
|
-
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0
|
|
14782
|
+
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0;
|
|
13844
14783
|
const { resolvedEntityViews } = resolvedSelectedEntityView(customViews, customizationController, void 0, canEdit);
|
|
14784
|
+
const legacyInspectorTab = selectedTab === "__json" ? "json" : selectedTab === "__rebase_history" ? "history" : void 0;
|
|
13845
14785
|
const activeTab = useMemo(() => {
|
|
13846
|
-
return new Set([
|
|
14786
|
+
return /* @__PURE__ */ new Set([
|
|
13847
14787
|
"__main_##Q$SC^#S6",
|
|
13848
|
-
...includeJsonView ? ["__json"] : [],
|
|
13849
|
-
...includeHistoryView ? ["__rebase_history"] : [],
|
|
13850
14788
|
...resolvedEntityViews.map((v) => v.key),
|
|
13851
14789
|
...subcollections.map((s) => s.slug)
|
|
13852
14790
|
]);
|
|
13853
|
-
}, [
|
|
13854
|
-
includeJsonView,
|
|
13855
|
-
includeHistoryView,
|
|
13856
|
-
resolvedEntityViews,
|
|
13857
|
-
subcollections
|
|
13858
|
-
]).has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
|
|
14791
|
+
}, [resolvedEntityViews, subcollections]).has(selectedTab) ? selectedTab : MAIN_TAB_VALUE;
|
|
13859
14792
|
const { selectedEntityView, selectedSecondaryForm } = resolvedSelectedEntityView(customViews, customizationController, activeTab, canEdit);
|
|
13860
|
-
const actionsAtTheBottom = layout === "side_panel" || layout === "dialog" || selectedEntityView?.includeActions === "bottom";
|
|
13861
14793
|
const mainViewVisible = activeTab === "__main_##Q$SC^#S6" || Boolean(selectedSecondaryForm);
|
|
13862
14794
|
const mountedTabsRef = useRef(/* @__PURE__ */ new Set());
|
|
13863
14795
|
if (activeTab) mountedTabsRef.current.add(activeTab);
|
|
@@ -13926,25 +14858,46 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
13926
14858
|
}, `custom_view_${customView.key}`);
|
|
13927
14859
|
}).filter(Boolean);
|
|
13928
14860
|
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
|
-
|
|
14861
|
+
const displayTitle = useEntityDisplayTitle({
|
|
14862
|
+
collection,
|
|
14863
|
+
values: formContext?.values ?? usedEntity?.values,
|
|
14864
|
+
entityId,
|
|
14865
|
+
status
|
|
14866
|
+
});
|
|
14867
|
+
const formex = formContext?.formex;
|
|
14868
|
+
const hasFormErrors = Boolean(formex && Object.keys(formex.errors).length > 0 && formex.submitCount > 0);
|
|
14869
|
+
const saveDisabled = Boolean(!formContext || formContext.disabled || formex?.isSubmitting || status === "existing" && !formex?.dirty);
|
|
14870
|
+
const [inspectorTab, setInspectorTab] = useState(null);
|
|
14871
|
+
const [savedCount, setSavedCount] = useState(0);
|
|
14872
|
+
const sideDialogContext = useSideDialogContext();
|
|
14873
|
+
const canCloseAfterSave = layout === "side_panel" || layout === "dialog";
|
|
14874
|
+
const recordActions = useRecordActions({
|
|
14875
|
+
collection,
|
|
14876
|
+
path,
|
|
14877
|
+
entity: usedEntity
|
|
14878
|
+
});
|
|
14879
|
+
const recordActionItems = usedEntity && recordActions.length ? recordActions.map((action, index) => {
|
|
14880
|
+
const clickProps = {
|
|
14881
|
+
view: "form",
|
|
14882
|
+
entity: usedEntity,
|
|
14883
|
+
path,
|
|
14884
|
+
collection,
|
|
14885
|
+
context,
|
|
14886
|
+
sidePanelController: adminContext.sidePanelController,
|
|
14887
|
+
openEntityMode: layout,
|
|
14888
|
+
navigateBack: navigateBack ?? (() => void 0),
|
|
14889
|
+
formContext
|
|
14890
|
+
};
|
|
14891
|
+
return /* @__PURE__ */ jsxs(MenuItem, {
|
|
14892
|
+
disabled: !(!action.isEnabled || action.isEnabled(clickProps)),
|
|
14893
|
+
onClick: () => action.onClick(clickProps),
|
|
14894
|
+
children: [getIcon(action.icon, void 0, void 0, "smallest"), action.name]
|
|
14895
|
+
}, action.key ?? action.name ?? index);
|
|
14896
|
+
}) : null;
|
|
14897
|
+
const formPluginActions = useSlot("form.actions", formActionTopProps);
|
|
14898
|
+
useEffect(() => {
|
|
14899
|
+
if (legacyInspectorTab) setInspectorTab(legacyInspectorTab);
|
|
14900
|
+
}, [legacyInspectorTab]);
|
|
13948
14901
|
const subCollectionsViews = childViews && childViews.map(({ collection: subcollection }) => {
|
|
13949
14902
|
const subcollectionId = subcollection.slug;
|
|
13950
14903
|
const newFullPath = usedEntity ? `${path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(getCollectionDataPath(subcollection))}` : void 0;
|
|
@@ -14038,7 +14991,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
14038
14991
|
entity,
|
|
14039
14992
|
initialDirtyValues,
|
|
14040
14993
|
openEntityMode: layout,
|
|
14041
|
-
|
|
14994
|
+
showDefaultActions: false,
|
|
14042
14995
|
initialStatus: status,
|
|
14043
14996
|
className: cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className),
|
|
14044
14997
|
EntityFormActionsComponent: ResolvedFormActions,
|
|
@@ -14058,6 +15011,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
14058
15011
|
formProps?.onFormContextReady?.(formContext);
|
|
14059
15012
|
},
|
|
14060
15013
|
onSaved: (params) => {
|
|
15014
|
+
setSavedCount((count) => count + 1);
|
|
14061
15015
|
const res = {
|
|
14062
15016
|
...params,
|
|
14063
15017
|
selectedTab: "__main_##Q$SC^#S6" === activeTab ? void 0 : activeTab
|
|
@@ -14100,14 +15054,14 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
14100
15054
|
})
|
|
14101
15055
|
}, `entity_detail_collection_tab_${view.name}`);
|
|
14102
15056
|
});
|
|
14103
|
-
const
|
|
14104
|
-
const fullScreenButton = !barActions && (layout === "side_panel" || layout === "split" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
|
|
15057
|
+
const fullScreenButton = !barActions && (layout === "side_panel" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
|
|
14105
15058
|
title: "Open full screen",
|
|
14106
15059
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
14107
15060
|
size: "small",
|
|
14108
15061
|
onClick: () => {
|
|
14109
15062
|
const editSuffix = collection.defaultEntityAction === "view" ? "/edit" : "";
|
|
14110
|
-
|
|
15063
|
+
const entityUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}${editSuffix}`);
|
|
15064
|
+
navigate(`${entityUrl}#full`);
|
|
14111
15065
|
},
|
|
14112
15066
|
children: /* @__PURE__ */ jsx(Maximize2Icon, { size: iconSize.smallest })
|
|
14113
15067
|
})
|
|
@@ -14115,60 +15069,86 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
14115
15069
|
let result = /* @__PURE__ */ jsxs("div", {
|
|
14116
15070
|
className: "relative flex flex-col h-full w-full bg-white dark:bg-surface-800",
|
|
14117
15071
|
children: [
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
15072
|
+
/* @__PURE__ */ jsx(EntityIdentityBar, {
|
|
15073
|
+
collection,
|
|
15074
|
+
title: displayTitle,
|
|
15075
|
+
entityId: status === "existing" ? entityId : void 0,
|
|
15076
|
+
status,
|
|
15077
|
+
dirty: Boolean(formContext?.formex?.dirty),
|
|
15078
|
+
saving: Boolean(formContext?.isSaving),
|
|
15079
|
+
hasErrors: hasFormErrors,
|
|
15080
|
+
saveDisabled: !canEdit || saveDisabled,
|
|
15081
|
+
onBack: layout === "full_screen" ? () => navigate(withViewMode(urlController.buildUrlCollectionPath(path))) : void 0,
|
|
15082
|
+
onSave: canEdit && formContext ? () => {
|
|
15083
|
+
sideDialogContext.setPendingClose?.(false);
|
|
15084
|
+
formContext.submit();
|
|
15085
|
+
} : void 0,
|
|
15086
|
+
onSaveAndClose: canEdit && formContext && canCloseAfterSave ? () => {
|
|
15087
|
+
sideDialogContext.setPendingClose?.(true);
|
|
15088
|
+
formContext.submit();
|
|
15089
|
+
} : void 0,
|
|
15090
|
+
onDiscard: canEdit && formContext ? () => formContext.formex.resetForm() : void 0,
|
|
15091
|
+
onInspect: includeJsonView ? () => setInspectorTab("json") : void 0,
|
|
15092
|
+
onViewHistory: includeHistoryView ? () => setInspectorTab("history") : void 0,
|
|
15093
|
+
recordActions: recordActionItems,
|
|
15094
|
+
pluginActions: formPluginActions,
|
|
15095
|
+
leading: /* @__PURE__ */ jsxs(Fragment, { children: [barActionsStart, layout === "split" && /* @__PURE__ */ jsx(SplitListCloseButton, {})] }),
|
|
15096
|
+
trailing: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
15097
|
+
pluginActionsTop,
|
|
14121
15098
|
fullScreenButton,
|
|
14122
15099
|
barActions?.({
|
|
14123
15100
|
path,
|
|
14124
15101
|
entityId,
|
|
14125
15102
|
values: formContext?.values ?? usedEntity?.values ?? {},
|
|
14126
15103
|
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
15104
|
})
|
|
14165
|
-
]
|
|
15105
|
+
] })
|
|
15106
|
+
}),
|
|
15107
|
+
hasAdditionalViews && /* @__PURE__ */ jsx("div", {
|
|
15108
|
+
className: cls("h-10 shrink-0 flex items-stretch border-b px-2 min-w-0", defaultBorderMixin),
|
|
15109
|
+
children: /* @__PURE__ */ jsxs(Tabs, {
|
|
15110
|
+
className: "!w-full",
|
|
15111
|
+
innerClassName: "h-full",
|
|
15112
|
+
variant: "boxy",
|
|
15113
|
+
value: activeTab,
|
|
15114
|
+
onValueChange: (value) => {
|
|
15115
|
+
onSideTabClick(value);
|
|
15116
|
+
},
|
|
15117
|
+
children: [
|
|
15118
|
+
/* @__PURE__ */ jsx(Tab, {
|
|
15119
|
+
value: "__main_##Q$SC^#S6",
|
|
15120
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
15121
|
+
className: "flex items-center gap-1.5",
|
|
15122
|
+
children: [getIcon(collection.icon, void 0, void 0, "smallest"), collection.singularName ?? collection.name]
|
|
15123
|
+
})
|
|
15124
|
+
}),
|
|
15125
|
+
customViewTabsStart,
|
|
15126
|
+
customViewTabsEnd,
|
|
15127
|
+
subcollectionTabs
|
|
15128
|
+
]
|
|
15129
|
+
})
|
|
14166
15130
|
}),
|
|
14167
|
-
|
|
14168
|
-
|
|
14169
|
-
|
|
14170
|
-
|
|
14171
|
-
|
|
15131
|
+
/* @__PURE__ */ jsxs("div", {
|
|
15132
|
+
className: "flex-1 min-h-0 flex",
|
|
15133
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
15134
|
+
className: "flex-1 min-w-0 flex flex-col",
|
|
15135
|
+
children: [
|
|
15136
|
+
globalLoading ? /* @__PURE__ */ jsx(EntityFormSkeleton, { collection }) : /* @__PURE__ */ jsxs(Fragment, { children: [entityReadOnlyView, entityView] }),
|
|
15137
|
+
customViewsView,
|
|
15138
|
+
subCollectionsViews
|
|
15139
|
+
]
|
|
15140
|
+
}), /* @__PURE__ */ jsx(EntityInspector, {
|
|
15141
|
+
tab: inspectorTab,
|
|
15142
|
+
onTabChange: setInspectorTab,
|
|
15143
|
+
onClose: () => setInspectorTab(null),
|
|
15144
|
+
collection,
|
|
15145
|
+
entity: usedEntity,
|
|
15146
|
+
formContext,
|
|
15147
|
+
values: formContext?.values ?? usedEntity?.values,
|
|
15148
|
+
refreshToken: savedCount,
|
|
15149
|
+
includeHistory: includeHistoryView
|
|
15150
|
+
})]
|
|
15151
|
+
})
|
|
14172
15152
|
]
|
|
14173
15153
|
});
|
|
14174
15154
|
if (plugins && plugins.length > 0) result = /* @__PURE__ */ jsx(PluginProviderStack, {
|
|
@@ -14397,21 +15377,25 @@ function SidePanelBinding(props) {
|
|
|
14397
15377
|
parentCollectionSlugs,
|
|
14398
15378
|
parentEntityIds,
|
|
14399
15379
|
onEditClick: openEditView,
|
|
14400
|
-
|
|
15380
|
+
barActionsStart: /* @__PURE__ */ jsx(IconButton, {
|
|
15381
|
+
className: "self-center",
|
|
15382
|
+
size: "small",
|
|
15383
|
+
onClick: onClose,
|
|
15384
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
15385
|
+
}),
|
|
15386
|
+
barActions: ({ status, values }) => /* @__PURE__ */ jsx("div", {
|
|
14401
15387
|
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, {
|
|
15388
|
+
children: allowFullScreen && /* @__PURE__ */ jsx(IconButton, {
|
|
14408
15389
|
className: "self-center",
|
|
14409
15390
|
size: "small",
|
|
14410
15391
|
onClick: () => {
|
|
14411
|
-
if (entityId)
|
|
15392
|
+
if (entityId) {
|
|
15393
|
+
const fullScreenUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}`);
|
|
15394
|
+
navigate(fullScreenUrl, { state: null });
|
|
15395
|
+
}
|
|
14412
15396
|
},
|
|
14413
15397
|
children: /* @__PURE__ */ jsx(Maximize2Icon, {})
|
|
14414
|
-
})
|
|
15398
|
+
})
|
|
14415
15399
|
}),
|
|
14416
15400
|
onTabChange: ({ entityId: tabEntityId, selectedTab, collection: paramCollection }) => {
|
|
14417
15401
|
if (collection?.openEntityMode === "dialog" || paramCollection?.openEntityMode === "dialog") return;
|
|
@@ -14432,25 +15416,31 @@ function SidePanelBinding(props) {
|
|
|
14432
15416
|
onValuesModified,
|
|
14433
15417
|
onSaved: onUpdate,
|
|
14434
15418
|
navigateBack: closeEditView,
|
|
14435
|
-
|
|
15419
|
+
barActionsStart: /* @__PURE__ */ jsx(IconButton, {
|
|
15420
|
+
className: "self-center",
|
|
15421
|
+
size: "small",
|
|
15422
|
+
onClick: onClose,
|
|
15423
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
15424
|
+
}),
|
|
15425
|
+
barActions: ({ status, values }) => /* @__PURE__ */ jsx("div", {
|
|
14436
15426
|
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, {
|
|
15427
|
+
children: allowFullScreen && /* @__PURE__ */ jsx(IconButton, {
|
|
14443
15428
|
className: "self-center",
|
|
14444
15429
|
size: "small",
|
|
14445
15430
|
onClick: () => {
|
|
14446
15431
|
saveEntityToMemoryCache(status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId, values);
|
|
14447
15432
|
setBlocked(false);
|
|
14448
15433
|
setBlockedNavigationMessage(void 0);
|
|
14449
|
-
if (entityId)
|
|
14450
|
-
|
|
15434
|
+
if (entityId) {
|
|
15435
|
+
const fullScreenUrl = urlController.buildUrlCollectionPath(`${path}/${entityId}`);
|
|
15436
|
+
navigate(fullScreenUrl, { state: null });
|
|
15437
|
+
} else {
|
|
15438
|
+
const fullScreenUrl = urlController.buildUrlCollectionPath(path);
|
|
15439
|
+
navigate(fullScreenUrl + "#new", { state: null });
|
|
15440
|
+
}
|
|
14451
15441
|
},
|
|
14452
15442
|
children: /* @__PURE__ */ jsx(Maximize2Icon, {})
|
|
14453
|
-
})
|
|
15443
|
+
})
|
|
14454
15444
|
}),
|
|
14455
15445
|
onTabChange: ({ entityId: tabEntityId, selectedTab, collection: paramCollection }) => {
|
|
14456
15446
|
if (collection?.openEntityMode === "dialog" || paramCollection?.openEntityMode === "dialog") return;
|
|
@@ -14485,39 +15475,25 @@ function ResolvedSidePanelBinding(props) {
|
|
|
14485
15475
|
}
|
|
14486
15476
|
var NEW_URL_HASH = "new_side";
|
|
14487
15477
|
var SIDE_URL_HASH = "side";
|
|
15478
|
+
/**
|
|
15479
|
+
* How wide the side panel opens.
|
|
15480
|
+
*
|
|
15481
|
+
* One width for the whole panel, whatever tab is showing. It used to depend on
|
|
15482
|
+
* the selected tab — the form got one width and a subcollection or custom view
|
|
15483
|
+
* got `calc(55vw + 768px)` — so the panel physically resized under the cursor
|
|
15484
|
+
* every time you switched tabs. And the "form" width was derived from how
|
|
15485
|
+
* deeply nested the collection's *properties* were, which is not a thing anyone
|
|
15486
|
+
* can predict from looking at the panel.
|
|
15487
|
+
*
|
|
15488
|
+
* Now: `width` prop, then the collection's `sideDialogWidth`, then a default
|
|
15489
|
+
* sized for the content the panel actually has to hold — the form column plus
|
|
15490
|
+
* its metadata rail — capped so it never eats the whole window.
|
|
15491
|
+
*/
|
|
14488
15492
|
function getEntityViewWidth(props, small, customizationController) {
|
|
14489
15493
|
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;
|
|
15494
|
+
if (props.width) return typeof props.width === "number" ? `${props.width}px` : props.width;
|
|
15495
|
+
if (props.collection?.sideDialogWidth) return typeof props.collection.sideDialogWidth === "number" ? `${props.collection.sideDialogWidth}px` : props.collection.sideDialogWidth;
|
|
15496
|
+
return SIDE_PANEL_DEFAULT_WIDTH;
|
|
14521
15497
|
}
|
|
14522
15498
|
var useBuildSidePanel = (collectionRegistryController, urlController, navigationStateController, sideDialogsController, authController) => {
|
|
14523
15499
|
const location = useLocation();
|
|
@@ -14759,8 +15735,9 @@ function useResolvedNavigationFrom({ path }) {
|
|
|
14759
15735
|
* Replaces the old subcollection-based approach with a proper API call.
|
|
14760
15736
|
*/
|
|
14761
15737
|
function useHistory(params) {
|
|
14762
|
-
const { slug, entityId, enabled = true, pageSize = 10 } = params;
|
|
15738
|
+
const { slug, entityId, enabled = true, pageSize = 10, refreshToken } = params;
|
|
14763
15739
|
const apiConfig = useApiConfig();
|
|
15740
|
+
const apiBase = useApiBase();
|
|
14764
15741
|
const [entries, setEntries] = useState([]);
|
|
14765
15742
|
const [total, setTotal] = useState(0);
|
|
14766
15743
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -14768,6 +15745,7 @@ function useHistory(params) {
|
|
|
14768
15745
|
const [error, setError] = useState();
|
|
14769
15746
|
const [offset, setOffset] = useState(0);
|
|
14770
15747
|
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
15748
|
+
const entityKey = `${slug}/${entityId}/${refreshToken ?? 0}`;
|
|
14771
15749
|
const currentEntityRef = useRef(void 0);
|
|
14772
15750
|
const fetchEntries = useCallback(async (currentOffset, signal) => {
|
|
14773
15751
|
if (!apiConfig?.apiUrl || !entityId || !enabled) return;
|
|
@@ -14777,13 +15755,12 @@ function useHistory(params) {
|
|
|
14777
15755
|
const token = await apiConfig.getAuthToken?.();
|
|
14778
15756
|
const headers = { "Content-Type": "application/json" };
|
|
14779
15757
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
14780
|
-
const url = `${
|
|
15758
|
+
const url = `${apiBase}/data/${slug}/${entityId}/history?limit=${pageSize}&offset=${currentOffset}`;
|
|
14781
15759
|
const response = await fetch(url, {
|
|
14782
15760
|
headers,
|
|
14783
15761
|
signal
|
|
14784
15762
|
});
|
|
14785
15763
|
if (signal?.aborted) return;
|
|
14786
|
-
const entityKey = `${slug}/${entityId}`;
|
|
14787
15764
|
if (currentEntityRef.current !== entityKey) return;
|
|
14788
15765
|
if (!response.ok) {
|
|
14789
15766
|
const errorData = await response.json().catch(() => ({ error: { message: response.statusText } }));
|
|
@@ -14802,13 +15779,14 @@ function useHistory(params) {
|
|
|
14802
15779
|
}
|
|
14803
15780
|
}, [
|
|
14804
15781
|
apiConfig,
|
|
15782
|
+
apiBase,
|
|
14805
15783
|
slug,
|
|
14806
15784
|
entityId,
|
|
15785
|
+
entityKey,
|
|
14807
15786
|
enabled,
|
|
14808
15787
|
pageSize
|
|
14809
15788
|
]);
|
|
14810
15789
|
useEffect(() => {
|
|
14811
|
-
const entityKey = `${slug}/${entityId}`;
|
|
14812
15790
|
const isNewEntity = currentEntityRef.current !== entityKey;
|
|
14813
15791
|
if (isNewEntity) {
|
|
14814
15792
|
currentEntityRef.current = entityKey;
|
|
@@ -14828,7 +15806,7 @@ function useHistory(params) {
|
|
|
14828
15806
|
offset,
|
|
14829
15807
|
enabled,
|
|
14830
15808
|
entityId,
|
|
14831
|
-
|
|
15809
|
+
entityKey,
|
|
14832
15810
|
refreshTrigger
|
|
14833
15811
|
]);
|
|
14834
15812
|
const loadMore = useCallback(() => {
|
|
@@ -14846,7 +15824,7 @@ function useHistory(params) {
|
|
|
14846
15824
|
const token = await apiConfig.getAuthToken?.();
|
|
14847
15825
|
const headers = { "Content-Type": "application/json" };
|
|
14848
15826
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
14849
|
-
const url = `${
|
|
15827
|
+
const url = `${apiBase}/data/${slug}/${entityId}/history/${historyId}/revert`;
|
|
14850
15828
|
const response = await fetch(url, {
|
|
14851
15829
|
method: "POST",
|
|
14852
15830
|
headers
|
|
@@ -14861,6 +15839,7 @@ function useHistory(params) {
|
|
|
14861
15839
|
return result.data;
|
|
14862
15840
|
}, [
|
|
14863
15841
|
apiConfig,
|
|
15842
|
+
apiBase,
|
|
14864
15843
|
slug,
|
|
14865
15844
|
entityId
|
|
14866
15845
|
]);
|
|
@@ -15161,6 +16140,24 @@ function filterViewsByRole(views, authController) {
|
|
|
15161
16140
|
//#endregion
|
|
15162
16141
|
//#region src/hooks/navigation/utils.ts
|
|
15163
16142
|
var NAVIGATION_DEFAULT_GROUP_NAME = "Views";
|
|
16143
|
+
/**
|
|
16144
|
+
* Group names that sink to the bottom of the drawer on their name alone.
|
|
16145
|
+
*
|
|
16146
|
+
* A magic string, and it stays one for compatibility: apps have been relying on
|
|
16147
|
+
* `group: "Settings"` landing last since before there was a flag, and taking it
|
|
16148
|
+
* away would silently reshuffle their navigation.
|
|
16149
|
+
*
|
|
16150
|
+
* It is a bad way to ask for the behaviour, though — it is invisible from the
|
|
16151
|
+
* `group?: string` type, and it is defeated by the most ordinary edit there is:
|
|
16152
|
+
* translating the label. `group: "Ajustes"` is the same group to a reader and a
|
|
16153
|
+
* different one to this comparison, so the ordering just quietly stops
|
|
16154
|
+
* happening. Set {@link AppView.pinToBottom} instead, which says so.
|
|
16155
|
+
*/
|
|
16156
|
+
var NAVIGATION_BOTTOM_GROUP_NAMES = ["Admin", "Settings"];
|
|
16157
|
+
/** Whether a group sinks to the bottom by virtue of its name. @see NAVIGATION_BOTTOM_GROUP_NAMES */
|
|
16158
|
+
function isBottomPinnedGroupName(groupName) {
|
|
16159
|
+
return groupName !== void 0 && NAVIGATION_BOTTOM_GROUP_NAMES.includes(groupName);
|
|
16160
|
+
}
|
|
15164
16161
|
function getGroup(collectionOrView) {
|
|
15165
16162
|
const trimmed = collectionOrView.group?.trim();
|
|
15166
16163
|
if (!trimmed || trimmed === "") return NAVIGATION_DEFAULT_GROUP_NAME;
|
|
@@ -15562,10 +16559,9 @@ function useTopLevelNavigation(props) {
|
|
|
15562
16559
|
return acc;
|
|
15563
16560
|
}, [])
|
|
15564
16561
|
];
|
|
15565
|
-
const
|
|
15566
|
-
|
|
15567
|
-
|
|
15568
|
-
};
|
|
16562
|
+
const pinnedGroups = /* @__PURE__ */ new Set();
|
|
16563
|
+
for (const view of [...views ?? [], ...adminViews ?? []]) if (view.pinToBottom) pinnedGroups.add(getGroup(view));
|
|
16564
|
+
const groupOrderValue = (groupName) => isBottomPinnedGroupName(groupName) || groupName !== void 0 && pinnedGroups.has(groupName) ? 1 : 0;
|
|
15569
16565
|
const usedViewsOrder = viewsOrder ?? navigationEntriesOrder;
|
|
15570
16566
|
navigationEntries = navigationEntries.sort((a, b) => {
|
|
15571
16567
|
const groupDiff = groupOrderValue(a.group) - groupOrderValue(b.group);
|
|
@@ -15591,8 +16587,8 @@ function useTopLevelNavigation(props) {
|
|
|
15591
16587
|
...additionalGroups
|
|
15592
16588
|
];
|
|
15593
16589
|
const uniqueGroupsArray = [...new Set(allDefinedGroups)];
|
|
15594
|
-
const lastGroups = uniqueGroupsArray.filter((g) => g ===
|
|
15595
|
-
const uniqueGroups = [...uniqueGroupsArray.filter((g) => g
|
|
16590
|
+
const lastGroups = uniqueGroupsArray.filter((g) => groupOrderValue(g) === 1);
|
|
16591
|
+
const uniqueGroups = [...uniqueGroupsArray.filter((g) => groupOrderValue(g) === 0), ...lastGroups];
|
|
15596
16592
|
const computedTopLevelNav = {
|
|
15597
16593
|
allowDragAndDrop: plugins?.some((plugin) => plugin.hooks?.allowDragAndDrop) ?? false,
|
|
15598
16594
|
navigationEntries,
|
|
@@ -16401,7 +17397,7 @@ function BlockEntry({ name, index, value, typeField, valueField, properties, aut
|
|
|
16401
17397
|
* and tables to the specified properties.
|
|
16402
17398
|
* @group Form fields
|
|
16403
17399
|
*/
|
|
16404
|
-
function DateTimeFieldBinding({ propertyKey, value, setValue, autoFocus, error, showError, disabled, touched, property, includeDescription }) {
|
|
17400
|
+
function DateTimeFieldBinding({ propertyKey, value, setValue, autoFocus, error, showError, disabled, touched, property, includeDescription, hideLabel }) {
|
|
16405
17401
|
const { locale } = useCustomizationController();
|
|
16406
17402
|
const internalValue = value || null;
|
|
16407
17403
|
useClearRestoreValue({
|
|
@@ -16419,7 +17415,7 @@ function DateTimeFieldBinding({ propertyKey, value, setValue, autoFocus, error,
|
|
|
16419
17415
|
locale,
|
|
16420
17416
|
error: showError,
|
|
16421
17417
|
disabled,
|
|
16422
|
-
label: /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
17418
|
+
label: hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
16423
17419
|
icon: getIconForProperty(property, "small"),
|
|
16424
17420
|
required: property.validation?.required,
|
|
16425
17421
|
className: showError ? "text-red-500 dark:text-red-500" : "text-text-secondary dark:text-text-secondary-dark",
|
|
@@ -16884,7 +17880,7 @@ function MapFieldBinding({ propertyKey, value, showError, error, disabled, prope
|
|
|
16884
17880
|
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
17881
|
const mapProperties = property.properties;
|
|
16886
17882
|
const mapFormView = /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", {
|
|
16887
|
-
className:
|
|
17883
|
+
className: "py-1 flex flex-col space-y-2",
|
|
16888
17884
|
children: Object.entries(mapProperties).filter(([_, property]) => !isHidden(property)).map(([entryKey, childProperty], index) => {
|
|
16889
17885
|
const thisDisabled = isReadOnly(childProperty) || Boolean(childProperty.admin?.disabled);
|
|
16890
17886
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -16914,7 +17910,6 @@ function MapFieldBinding({ propertyKey, value, showError, error, disabled, prope
|
|
|
16914
17910
|
expanded
|
|
16915
17911
|
} });
|
|
16916
17912
|
},
|
|
16917
|
-
className: property.admin?.widthPercentage !== void 0 ? "mt-8" : void 0,
|
|
16918
17913
|
innerClassName: "px-2 md:px-4 pb-2 md:pb-4 pt-1 md:pt-2 bg-white dark:bg-surface-900",
|
|
16919
17914
|
title: /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
16920
17915
|
propertyKey,
|
|
@@ -16971,7 +17966,7 @@ function canonicalizeMarkdown(md) {
|
|
|
16971
17966
|
return normalizeMarkdown(md);
|
|
16972
17967
|
}
|
|
16973
17968
|
}
|
|
16974
|
-
function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, includeDescription, showError, error, minimalistView, disabled: disabledProp, isSubmitting, context, customProps }) {
|
|
17969
|
+
function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, includeDescription, hideLabel, showError, error, minimalistView, disabled: disabledProp, isSubmitting, context, customProps }) {
|
|
16975
17970
|
useAuthController();
|
|
16976
17971
|
const disabled = disabledProp || isSubmitting;
|
|
16977
17972
|
const highlight = customProps?.highlight;
|
|
@@ -17086,7 +18081,7 @@ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, in
|
|
|
17086
18081
|
/* @__PURE__ */ jsxs("div", {
|
|
17087
18082
|
className: "flex items-center w-full",
|
|
17088
18083
|
children: [
|
|
17089
|
-
/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
18084
|
+
!hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
17090
18085
|
propertyKey,
|
|
17091
18086
|
icon: getIconForProperty(property, "small"),
|
|
17092
18087
|
required: property.validation?.required,
|
|
@@ -17126,7 +18121,7 @@ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, in
|
|
|
17126
18121
|
* and tables to the specified properties.
|
|
17127
18122
|
* @group Form fields
|
|
17128
18123
|
*/
|
|
17129
|
-
function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, property, includeDescription, size = "large", autoFocus }) {
|
|
18124
|
+
function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, property, includeDescription, hideLabel, size = "large", autoFocus }) {
|
|
17130
18125
|
const of = property.of;
|
|
17131
18126
|
if (!of) throw Error("Using wrong component ArrayEnumSelect");
|
|
17132
18127
|
if (Array.isArray(of)) throw Error("Using array properties instead of single one in `of` in ArrayProperty");
|
|
@@ -17143,11 +18138,12 @@ function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showErro
|
|
|
17143
18138
|
});
|
|
17144
18139
|
const validValue = !!value && Array.isArray(value);
|
|
17145
18140
|
const renderValue = useCallback((enumKey, list) => {
|
|
18141
|
+
const enumValue = enumKey !== void 0 ? getLabelOrConfigFrom(enumValues, enumKey) : void 0;
|
|
17146
18142
|
return /* @__PURE__ */ jsxs(EnumValuesChip, {
|
|
17147
18143
|
enumKey,
|
|
17148
18144
|
enumValues,
|
|
17149
18145
|
size: "medium",
|
|
17150
|
-
children: [
|
|
18146
|
+
children: [enumValue?.label ?? enumKey, !list && /* @__PURE__ */ jsx("button", {
|
|
17151
18147
|
className: "ml-1 ring-offset-background rounded-full outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
17152
18148
|
onMouseDown: (e) => {
|
|
17153
18149
|
e.preventDefault();
|
|
@@ -17172,7 +18168,7 @@ function MultiSelectFieldBinding({ propertyKey, value, setValue, error, showErro
|
|
|
17172
18168
|
value: validValue ? value.map((v) => v?.toString()) : [],
|
|
17173
18169
|
disabled,
|
|
17174
18170
|
modalPopover: true,
|
|
17175
|
-
label: /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
18171
|
+
label: hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
17176
18172
|
propertyKey,
|
|
17177
18173
|
icon: getIconForProperty(property, "small"),
|
|
17178
18174
|
required: property.validation?.required,
|
|
@@ -17210,7 +18206,7 @@ function ReferenceFieldBinding(props) {
|
|
|
17210
18206
|
if (typeof props.property.path !== "string") return /* @__PURE__ */ jsx(ReadOnlyFieldBinding, { ...props });
|
|
17211
18207
|
return /* @__PURE__ */ jsx(ReferenceFieldBindingInternal, { ...props });
|
|
17212
18208
|
}
|
|
17213
|
-
function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, showError, isSubmitting, disabled, minimalistView, touched, autoFocus, property, includeDescription, size = "medium" }) {
|
|
18209
|
+
function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, showError, isSubmitting, disabled, minimalistView, touched, autoFocus, property, includeDescription, hideLabel, size = "medium" }) {
|
|
17214
18210
|
if (!property.path) throw new Error("Property path is required for ReferenceFieldBinding");
|
|
17215
18211
|
const refValue = value;
|
|
17216
18212
|
useClearRestoreValue({
|
|
@@ -17240,7 +18236,7 @@ function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, sh
|
|
|
17240
18236
|
referenceDialogController.open();
|
|
17241
18237
|
};
|
|
17242
18238
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17243
|
-
!minimalistView && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
18239
|
+
!minimalistView && !hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
17244
18240
|
propertyKey,
|
|
17245
18241
|
icon: getIconForProperty(property, "small"),
|
|
17246
18242
|
required: property.validation?.required,
|
|
@@ -17321,6 +18317,7 @@ function RepeatFieldBinding({ propertyKey, value, error, showError, isSubmitting
|
|
|
17321
18317
|
context,
|
|
17322
18318
|
partOfArray: true,
|
|
17323
18319
|
minimalistView: false,
|
|
18320
|
+
hideLabel: true,
|
|
17324
18321
|
autoFocus: internalId === lastAddedId,
|
|
17325
18322
|
index
|
|
17326
18323
|
}) });
|
|
@@ -17337,8 +18334,7 @@ function RepeatFieldBinding({ propertyKey, value, error, showError, isSubmitting
|
|
|
17337
18334
|
canAddElements,
|
|
17338
18335
|
sortable,
|
|
17339
18336
|
newDefaultEntry: getDefaultValueFor(property.of),
|
|
17340
|
-
onValueChange: (value) => setFieldValue(propertyKey, value)
|
|
17341
|
-
className: property.admin?.widthPercentage !== void 0 ? "mt-8" : void 0
|
|
18337
|
+
onValueChange: (value) => setFieldValue(propertyKey, value)
|
|
17342
18338
|
});
|
|
17343
18339
|
const title = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
17344
18340
|
propertyKey,
|
|
@@ -17382,7 +18378,7 @@ function RepeatFieldBinding({ propertyKey, value, error, showError, isSubmitting
|
|
|
17382
18378
|
* and tables to the specified properties.
|
|
17383
18379
|
* @group Form fields
|
|
17384
18380
|
*/
|
|
17385
|
-
function SelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, touched, property, includeDescription, size = "large" }) {
|
|
18381
|
+
function SelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, touched, property, includeDescription, hideLabel, size = "large" }) {
|
|
17386
18382
|
const enumValues = resolveEnumValues(property.enum ?? []);
|
|
17387
18383
|
useClearRestoreValue({
|
|
17388
18384
|
property,
|
|
@@ -17401,7 +18397,7 @@ function SelectFieldBinding({ propertyKey, value, setValue, error, showError, di
|
|
|
17401
18397
|
fullWidth: true,
|
|
17402
18398
|
position: "item-aligned",
|
|
17403
18399
|
inputClassName: cls("w-full"),
|
|
17404
|
-
label: /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
|
|
18400
|
+
label: hideLabel ? void 0 : /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
|
|
17405
18401
|
propertyKey,
|
|
17406
18402
|
children: /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
17407
18403
|
icon: getIconForProperty(property, "small"),
|
|
@@ -17482,13 +18478,13 @@ function StorageItemPreview({ name, property, value, onRemove, disabled, size, p
|
|
|
17482
18478
|
}
|
|
17483
18479
|
//#endregion
|
|
17484
18480
|
//#region src/form/field_bindings/StorageUploadFieldBinding.tsx
|
|
17485
|
-
var dropZoneClasses = "box-border relative pt-[2px] items-center border border-transparent
|
|
18481
|
+
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
18482
|
var disabledClasses = fieldBackgroundDisabledMixin;
|
|
17487
18483
|
var nonActiveDropClasses = fieldBackgroundHoverMixin;
|
|
17488
18484
|
var activeDropClasses = "pt-0 border-2 border-solid";
|
|
17489
18485
|
var acceptDropClasses = "transition-colors duration-200 ease-[cubic-bezier(0,0,0.2,1)] border-2 border-solid border-green-500";
|
|
17490
18486
|
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 }) {
|
|
18487
|
+
function StorageUploadFieldBinding({ propertyKey, value, setValue, error, showError, autoFocus, minimalistView, property, includeDescription, hideLabel, context, isSubmitting }) {
|
|
17492
18488
|
useAuthController();
|
|
17493
18489
|
const storageSource = useStorageSource();
|
|
17494
18490
|
const disabled = isReadOnly(property) || !!property.admin?.disabled || isSubmitting || context.disabled;
|
|
@@ -17509,7 +18505,7 @@ function StorageUploadFieldBinding({ propertyKey, value, setValue, error, showEr
|
|
|
17509
18505
|
setValue
|
|
17510
18506
|
});
|
|
17511
18507
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
17512
|
-
!minimalistView && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
18508
|
+
!minimalistView && !hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
17513
18509
|
propertyKey,
|
|
17514
18510
|
icon: getIconForProperty(property, "small"),
|
|
17515
18511
|
required: property.validation?.required,
|
|
@@ -17579,7 +18575,7 @@ function SortableStorageItem({ id, entry, property, metadata, storagePathBuilder
|
|
|
17579
18575
|
style,
|
|
17580
18576
|
...attributes,
|
|
17581
18577
|
...listeners,
|
|
17582
|
-
className: cls("rounded-md
|
|
18578
|
+
className: cls("rounded-md shrink-0"),
|
|
17583
18579
|
tabIndex: -1,
|
|
17584
18580
|
children: child
|
|
17585
18581
|
});
|
|
@@ -17619,7 +18615,7 @@ function FileDropComponent({ storage, disabled, onFilesAdded, multipleFilesSuppo
|
|
|
17619
18615
|
[disabledClasses]: disabled || isDndItemDragging
|
|
17620
18616
|
}),
|
|
17621
18617
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
17622
|
-
className: cls("flex items-center p-1 px-
|
|
18618
|
+
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
18619
|
children: [/* @__PURE__ */ jsx("input", {
|
|
17624
18620
|
autoFocus,
|
|
17625
18621
|
...getInputProps()
|
|
@@ -17636,10 +18632,10 @@ function FileDropComponent({ storage, disabled, onFilesAdded, multipleFilesSuppo
|
|
|
17636
18632
|
isSortable: multipleFilesSupported
|
|
17637
18633
|
}, entry.id))]
|
|
17638
18634
|
}), /* @__PURE__ */ jsx("div", {
|
|
17639
|
-
className: "
|
|
18635
|
+
className: "box-border mx-3 mb-2.5 h-[34px] flex items-center justify-center text-center",
|
|
17640
18636
|
children: /* @__PURE__ */ jsx(Typography, {
|
|
17641
18637
|
align: "center",
|
|
17642
|
-
variant: "
|
|
18638
|
+
variant: "caption",
|
|
17643
18639
|
className: disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : "",
|
|
17644
18640
|
children: helpText
|
|
17645
18641
|
})
|
|
@@ -17742,7 +18738,7 @@ function StorageUpload({ property, name, value, setInternalValue, onChange, mult
|
|
|
17742
18738
|
* and tables to the specified properties.
|
|
17743
18739
|
* @group Form fields
|
|
17744
18740
|
*/
|
|
17745
|
-
var SwitchFieldBinding = function SwitchFieldBinding({ propertyKey, value, setValue, error, showError, autoFocus, disabled, size = "large", property, includeDescription }) {
|
|
18741
|
+
var SwitchFieldBinding = function SwitchFieldBinding({ propertyKey, value, setValue, error, showError, autoFocus, disabled, size = "large", property, includeDescription, hideLabel }) {
|
|
17746
18742
|
useClearRestoreValue({
|
|
17747
18743
|
property,
|
|
17748
18744
|
value,
|
|
@@ -17754,8 +18750,8 @@ var SwitchFieldBinding = function SwitchFieldBinding({ propertyKey, value, setVa
|
|
|
17754
18750
|
value,
|
|
17755
18751
|
onValueChange: (v) => setValue(v),
|
|
17756
18752
|
error: showError,
|
|
17757
|
-
|
|
17758
|
-
label: /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18753
|
+
position: hideLabel ? "start" : void 0,
|
|
18754
|
+
label: hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
17759
18755
|
icon: getIconForProperty(property, "small"),
|
|
17760
18756
|
required: property.validation?.required,
|
|
17761
18757
|
title: property.name ?? propertyKey
|
|
@@ -17780,7 +18776,7 @@ var SwitchFieldBinding = function SwitchFieldBinding({ propertyKey, value, setVa
|
|
|
17780
18776
|
* and tables to the specified properties.
|
|
17781
18777
|
* @group Form fields
|
|
17782
18778
|
*/
|
|
17783
|
-
function TextFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, size = "large" }) {
|
|
18779
|
+
function TextFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, hideLabel, size = "large" }) {
|
|
17784
18780
|
let multiline;
|
|
17785
18781
|
let url;
|
|
17786
18782
|
if (property.type === "string") {
|
|
@@ -17813,7 +18809,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
|
|
|
17813
18809
|
if (property.email) inputType = "email";
|
|
17814
18810
|
else if (property.admin?.urlPreview) inputType = "url";
|
|
17815
18811
|
}
|
|
17816
|
-
const label = /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18812
|
+
const label = hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
17817
18813
|
icon: getIconForProperty(property, "small"),
|
|
17818
18814
|
required: property.validation?.required || property.isId === true,
|
|
17819
18815
|
title: property.name ?? propertyKey
|
|
@@ -17822,9 +18818,9 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
|
|
|
17822
18818
|
/* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
|
|
17823
18819
|
propertyKey,
|
|
17824
18820
|
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" : ""
|
|
18821
|
+
className: cls("rounded-md relative max-w-full min-h-[64px]", fieldBackgroundMixin, fieldBackgroundHoverMixin, showError && error ? "border border-red-500 dark:border-red-600" : ""),
|
|
17826
18822
|
children: [
|
|
17827
|
-
/* @__PURE__ */ jsx("div", {
|
|
18823
|
+
label && /* @__PURE__ */ jsx("div", {
|
|
17828
18824
|
className: "pointer-events-none absolute top-1 text-xs font-medium px-3 text-text-secondary dark:text-text-secondary-dark",
|
|
17829
18825
|
children: label
|
|
17830
18826
|
}),
|
|
@@ -17833,7 +18829,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
|
|
|
17833
18829
|
onChange,
|
|
17834
18830
|
autoFocus,
|
|
17835
18831
|
disabled,
|
|
17836
|
-
className: cls("rounded-md resize-none w-full outline-none
|
|
18832
|
+
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
18833
|
}),
|
|
17838
18834
|
property.admin?.clearable && /* @__PURE__ */ jsx("div", {
|
|
17839
18835
|
className: "flex flex-row justify-center items-center absolute h-full right-0 top-0 mr-4",
|
|
@@ -17848,12 +18844,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
|
|
|
17848
18844
|
value: displayValue,
|
|
17849
18845
|
onChange,
|
|
17850
18846
|
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
|
-
}),
|
|
18847
|
+
label,
|
|
17857
18848
|
type: inputType,
|
|
17858
18849
|
disabled,
|
|
17859
18850
|
endAdornment: property.admin?.clearable && /* @__PURE__ */ jsx(IconButton, {
|
|
@@ -17892,7 +18883,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
|
|
|
17892
18883
|
* Vector object structure expected by Rebase driver.
|
|
17893
18884
|
* @group Form fields
|
|
17894
18885
|
*/
|
|
17895
|
-
function VectorFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, size = "large" }) {
|
|
18886
|
+
function VectorFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, hideLabel, size = "large" }) {
|
|
17896
18887
|
const isVectorObject = (val) => {
|
|
17897
18888
|
return typeof val === "object" && val !== null && "value" in val;
|
|
17898
18889
|
};
|
|
@@ -17922,7 +18913,7 @@ function VectorFieldBinding({ propertyKey, value, setValue, error, showError, di
|
|
|
17922
18913
|
value: raw.split(",").map((str) => str.trim()).filter((str) => str !== "").map(Number)
|
|
17923
18914
|
});
|
|
17924
18915
|
};
|
|
17925
|
-
const label = /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18916
|
+
const label = hideLabel ? null : /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
17926
18917
|
icon: getIconForProperty(property, "small"),
|
|
17927
18918
|
required: property.validation?.required,
|
|
17928
18919
|
title: property.name ?? propertyKey
|
|
@@ -18065,7 +19056,7 @@ var inRange = (n, [min, max]) => n >= min && n <= max;
|
|
|
18065
19056
|
*
|
|
18066
19057
|
* @group Form fields
|
|
18067
19058
|
*/
|
|
18068
|
-
function GeopointFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, size = "large" }) {
|
|
19059
|
+
function GeopointFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, hideLabel, size = "large" }) {
|
|
18069
19060
|
const coordinates = readCoordinates(value);
|
|
18070
19061
|
const [latText, setLatText] = useState(() => coordinates ? String(coordinates.latitude) : "");
|
|
18071
19062
|
const [lngText, setLngText] = useState(() => coordinates ? String(coordinates.longitude) : "");
|
|
@@ -18111,7 +19102,7 @@ function GeopointFieldBinding({ propertyKey, value, setValue, error, showError,
|
|
|
18111
19102
|
children: [
|
|
18112
19103
|
/* @__PURE__ */ jsxs("div", {
|
|
18113
19104
|
className: "flex items-center justify-between mb-1",
|
|
18114
|
-
children: [/* @__PURE__ */ jsx(LabelWithIcon, {
|
|
19105
|
+
children: [!hideLabel && /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18115
19106
|
icon: getIconForProperty(property, "small"),
|
|
18116
19107
|
required: property.validation?.required,
|
|
18117
19108
|
title: property.name ?? propertyKey
|
|
@@ -18199,7 +19190,7 @@ function humanSize(bytes) {
|
|
|
18199
19190
|
*
|
|
18200
19191
|
* @group Form fields
|
|
18201
19192
|
*/
|
|
18202
|
-
function BinaryFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, size = "large" }) {
|
|
19193
|
+
function BinaryFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, property, includeDescription, hideLabel, size = "large" }) {
|
|
18203
19194
|
const [text, setText] = useState(typeof value === "string" ? value : "");
|
|
18204
19195
|
const [isEditing, setIsEditing] = useState(false);
|
|
18205
19196
|
useEffect(() => {
|
|
@@ -18230,7 +19221,7 @@ function BinaryFieldBinding({ propertyKey, value, setValue, error, showError, di
|
|
|
18230
19221
|
children: [
|
|
18231
19222
|
/* @__PURE__ */ jsx("div", {
|
|
18232
19223
|
className: "flex items-center justify-between mb-1",
|
|
18233
|
-
children: /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
19224
|
+
children: !hideLabel && /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18234
19225
|
icon: getIconForProperty(property, "small"),
|
|
18235
19226
|
required: property.validation?.required,
|
|
18236
19227
|
title: property.name ?? propertyKey
|
|
@@ -18426,14 +19417,14 @@ function RelationFieldBinding(props) {
|
|
|
18426
19417
|
relation
|
|
18427
19418
|
});
|
|
18428
19419
|
}
|
|
18429
|
-
function RelationSelectorBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, setValue, relation, manyRelation }) {
|
|
19420
|
+
function RelationSelectorBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, hideLabel, setValue, relation, manyRelation }) {
|
|
18430
19421
|
const singleValue = (!manyRelation && value && !Array.isArray(value) ? normalizeToEntityRelation(value) : null) ?? null;
|
|
18431
19422
|
const multipleValue = manyRelation && Array.isArray(value) ? value : [];
|
|
18432
19423
|
const selectorSize = size === "large" ? "medium" : size;
|
|
18433
19424
|
return /* @__PURE__ */ jsxs("div", {
|
|
18434
19425
|
className: "",
|
|
18435
19426
|
children: [
|
|
18436
|
-
/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
19427
|
+
!hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
18437
19428
|
propertyKey,
|
|
18438
19429
|
icon: getIconForProperty(property, "small"),
|
|
18439
19430
|
required: property.validation?.required,
|
|
@@ -18461,7 +19452,7 @@ function RelationSelectorBinding({ propertyKey, value, size, error, showError, d
|
|
|
18461
19452
|
]
|
|
18462
19453
|
});
|
|
18463
19454
|
}
|
|
18464
|
-
function SingleRelationFieldBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, setValue, relation }) {
|
|
19455
|
+
function SingleRelationFieldBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, hideLabel, setValue, relation }) {
|
|
18465
19456
|
const normalizedValue = value && !Array.isArray(value) ? normalizeToEntityRelation(value) : null;
|
|
18466
19457
|
const validValue = !!normalizedValue;
|
|
18467
19458
|
const collection = relation?.target();
|
|
@@ -18483,7 +19474,7 @@ function SingleRelationFieldBinding({ propertyKey, value, size, error, showError
|
|
|
18483
19474
|
};
|
|
18484
19475
|
const usedRelation = Array.isArray(value) ? void 0 : normalizedValue ?? void 0;
|
|
18485
19476
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
18486
|
-
/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
19477
|
+
!hideLabel && /* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
18487
19478
|
propertyKey,
|
|
18488
19479
|
icon: getIconForProperty(property, "small"),
|
|
18489
19480
|
required: property.validation?.required,
|
|
@@ -18532,10 +19523,10 @@ function SingleRelationFieldBinding({ propertyKey, value, size, error, showError
|
|
|
18532
19523
|
* and tables to the specified properties.
|
|
18533
19524
|
* @group Form fields
|
|
18534
19525
|
*/
|
|
18535
|
-
function UserSelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, touched, property, includeDescription, size = "large" }) {
|
|
19526
|
+
function UserSelectFieldBinding({ propertyKey, value, setValue, error, showError, disabled, autoFocus, touched, property, includeDescription, hideLabel, size = "large" }) {
|
|
18536
19527
|
const selectorSize = size === "large" ? "medium" : size;
|
|
18537
19528
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
18538
|
-
/* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
|
|
19529
|
+
!hideLabel && /* @__PURE__ */ jsx(PropertyIdCopyTooltip, {
|
|
18539
19530
|
propertyKey,
|
|
18540
19531
|
children: /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
18541
19532
|
icon: getIconForProperty(property, "small"),
|
|
@@ -19047,7 +20038,7 @@ var PropertyFieldBinding = React.memo(PropertyFieldBindingInternal, (a, b) => {
|
|
|
19047
20038
|
if (shouldPropertyReRender(b.property)) return false;
|
|
19048
20039
|
return false;
|
|
19049
20040
|
});
|
|
19050
|
-
function PropertyFieldBindingInternal({ propertyKey, property, context, includeDescription, underlyingValueHasChanged, disabled: disabledProp, partOfArray, partOfBlock, minimalistView, autoFocus, index, size, onPropertyChange }) {
|
|
20041
|
+
function PropertyFieldBindingInternal({ propertyKey, property, context, includeDescription, hideLabel, underlyingValueHasChanged, disabled: disabledProp, partOfArray, partOfBlock, minimalistView, autoFocus, index, size, onPropertyChange }) {
|
|
19051
20042
|
const authController = useAuthController();
|
|
19052
20043
|
const customizationController = useCustomizationController();
|
|
19053
20044
|
return /* @__PURE__ */ jsx(Field, {
|
|
@@ -19099,6 +20090,7 @@ function PropertyFieldBindingInternal({ propertyKey, property, context, includeD
|
|
|
19099
20090
|
propertyKey,
|
|
19100
20091
|
property: resolvedProperty,
|
|
19101
20092
|
includeDescription,
|
|
20093
|
+
hideLabel,
|
|
19102
20094
|
underlyingValueHasChanged,
|
|
19103
20095
|
context,
|
|
19104
20096
|
disabled,
|
|
@@ -19114,7 +20106,7 @@ function PropertyFieldBindingInternal({ propertyKey, property, context, includeD
|
|
|
19114
20106
|
}
|
|
19115
20107
|
}, propertyKey);
|
|
19116
20108
|
}
|
|
19117
|
-
function FieldInternal({ Component, componentProps: { propertyKey, property, includeDescription, underlyingValueHasChanged, partOfArray, partOfBlock, minimalistView, autoFocus, context, disabled, size, onPropertyChange }, formexFieldProps }) {
|
|
20109
|
+
function FieldInternal({ Component, componentProps: { propertyKey, property, includeDescription, hideLabel, underlyingValueHasChanged, partOfArray, partOfBlock, minimalistView, autoFocus, context, disabled, size, onPropertyChange }, formexFieldProps }) {
|
|
19118
20110
|
const { plugins } = useCustomizationController();
|
|
19119
20111
|
const customFieldProps = property.admin?.customProps;
|
|
19120
20112
|
const value = formexFieldProps.field.value;
|
|
@@ -19148,6 +20140,7 @@ function FieldInternal({ Component, componentProps: { propertyKey, property, inc
|
|
|
19148
20140
|
showError,
|
|
19149
20141
|
isSubmitting,
|
|
19150
20142
|
includeDescription: includeDescription ?? true,
|
|
20143
|
+
hideLabel: hideLabel ?? false,
|
|
19151
20144
|
property,
|
|
19152
20145
|
disabled: disabled ?? false,
|
|
19153
20146
|
underlyingValueHasChanged: underlyingValueHasChanged ?? false,
|
|
@@ -19227,22 +20220,18 @@ var ErrorFocus = ({ containerRef }) => {
|
|
|
19227
20220
|
};
|
|
19228
20221
|
//#endregion
|
|
19229
20222
|
//#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 = {
|
|
20223
|
+
/**
|
|
20224
|
+
* The form's own action bar.
|
|
20225
|
+
*
|
|
20226
|
+
* There used to be two variants: this one, and a `w-80 2xl:w-96` sticky rail
|
|
20227
|
+
* holding the same two buttons above several hundred pixels of nothing — a
|
|
20228
|
+
* fifth of a 1440px viewport reserved for a Save button. The rail is gone. When
|
|
20229
|
+
* the container supplies its own actions (the entity view's identity bar does)
|
|
20230
|
+
* it passes `showDefaultActions={false}`, and this renders only what the bar
|
|
20231
|
+
* cannot host: the saving error, plugin actions, and collection form actions.
|
|
20232
|
+
*/
|
|
20233
|
+
function EntityFormActions({ path, collection, entity, savingError, formex, disabled, status, pluginActions, openEntityMode, navigateBack, formContext, showDefaultActions = true }) {
|
|
20234
|
+
return buildBottomActions({
|
|
19246
20235
|
path,
|
|
19247
20236
|
savingError,
|
|
19248
20237
|
entity,
|
|
@@ -19254,17 +20243,16 @@ function EntityFormActions({ path, collection, entity, layout, savingError, form
|
|
|
19254
20243
|
navigateBack,
|
|
19255
20244
|
formContext,
|
|
19256
20245
|
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;
|
|
20246
|
+
showDefaultActions
|
|
20247
|
+
});
|
|
19263
20248
|
}
|
|
19264
|
-
function buildBottomActions({ savingError, entity, path, formActions, collection, disabled, status, pluginActions, openEntityMode, navigateBack, formContext, formex, className }) {
|
|
20249
|
+
function buildBottomActions({ savingError, entity, path, formActions, collection, disabled, status, pluginActions, openEntityMode, navigateBack, formContext, formex, showDefaultActions, className }) {
|
|
19265
20250
|
const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
20251
|
+
const entityActions = entity ? formActions ?? [] : [];
|
|
20252
|
+
const hasPluginActions = Boolean(pluginActions && pluginActions.length > 0);
|
|
20253
|
+
if (!showDefaultActions && !savingError && !entityActions.length && !hasPluginActions) return null;
|
|
19266
20254
|
return /* @__PURE__ */ jsxs(DialogActions, {
|
|
19267
|
-
position: "
|
|
20255
|
+
position: "sticky",
|
|
19268
20256
|
className,
|
|
19269
20257
|
children: [
|
|
19270
20258
|
savingError && /* @__PURE__ */ jsx("div", {
|
|
@@ -19274,9 +20262,9 @@ function buildBottomActions({ savingError, entity, path, formActions, collection
|
|
|
19274
20262
|
children: savingError.message
|
|
19275
20263
|
})
|
|
19276
20264
|
}),
|
|
19277
|
-
|
|
20265
|
+
entityActions.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
19278
20266
|
className: "grow flex overflow-auto no-scrollbar",
|
|
19279
|
-
children:
|
|
20267
|
+
children: entityActions.map((action) => /* @__PURE__ */ jsx(IconButton, {
|
|
19280
20268
|
color: "primary",
|
|
19281
20269
|
onClick: (event) => {
|
|
19282
20270
|
event.stopPropagation();
|
|
@@ -19296,14 +20284,13 @@ function buildBottomActions({ savingError, entity, path, formActions, collection
|
|
|
19296
20284
|
}, action.name))
|
|
19297
20285
|
}),
|
|
19298
20286
|
pluginActions,
|
|
19299
|
-
/* @__PURE__ */ jsx(Button, {
|
|
20287
|
+
showDefaultActions && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
|
|
19300
20288
|
variant: "text",
|
|
19301
20289
|
disabled: disabled || formex.isSubmitting,
|
|
19302
20290
|
color: "primary",
|
|
19303
20291
|
type: "reset",
|
|
19304
20292
|
children: status === "existing" ? "Discard" : "Clear"
|
|
19305
|
-
}),
|
|
19306
|
-
/* @__PURE__ */ jsx(Tooltip, {
|
|
20293
|
+
}), /* @__PURE__ */ jsx(Tooltip, {
|
|
19307
20294
|
title: hasErrors ? "Fix highlighted errors before saving" : void 0,
|
|
19308
20295
|
children: /* @__PURE__ */ jsxs(LoadingButton, {
|
|
19309
20296
|
variant: "filled",
|
|
@@ -19318,47 +20305,7 @@ function buildBottomActions({ savingError, entity, path, formActions, collection
|
|
|
19318
20305
|
status === "new" && "Create"
|
|
19319
20306
|
]
|
|
19320
20307
|
})
|
|
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
|
-
})
|
|
20308
|
+
})] })
|
|
19362
20309
|
]
|
|
19363
20310
|
});
|
|
19364
20311
|
}
|
|
@@ -19520,7 +20467,7 @@ function getChanges(source, comparison) {
|
|
|
19520
20467
|
const changes = {};
|
|
19521
20468
|
if (!source) return {};
|
|
19522
20469
|
if (!comparison) return source;
|
|
19523
|
-
const allKeys = Array.from(new Set([...Object.keys(source), ...Object.keys(comparison)]));
|
|
20470
|
+
const allKeys = Array.from(/* @__PURE__ */ new Set([...Object.keys(source), ...Object.keys(comparison)]));
|
|
19524
20471
|
for (const key of allKeys) {
|
|
19525
20472
|
const sourceValue = source[key];
|
|
19526
20473
|
const comparisonValue = comparison[key];
|
|
@@ -19581,8 +20528,7 @@ function zodToFormErrors(zodError) {
|
|
|
19581
20528
|
*
|
|
19582
20529
|
* @group Components
|
|
19583
20530
|
*/
|
|
19584
|
-
function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified, onIdChange, onSaved, entity, initialDirtyValues, onFormContextReady,
|
|
19585
|
-
useCustomizationController();
|
|
20531
|
+
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
20532
|
const navigateBack = useCallback(() => {
|
|
19587
20533
|
if (navigateBackProp) navigateBackProp();
|
|
19588
20534
|
}, [navigateBackProp]);
|
|
@@ -19604,7 +20550,6 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19604
20550
|
else return entityIdProp;
|
|
19605
20551
|
}, [entityIdProp, status]);
|
|
19606
20552
|
const [entityId, setEntityId] = useState(initialEntityId);
|
|
19607
|
-
const [entityIdError, setEntityIdError] = useState(false);
|
|
19608
20553
|
const [savingError, setSavingError] = useState();
|
|
19609
20554
|
const [isSavingAutoSave, setIsSavingAutoSave] = useState(false);
|
|
19610
20555
|
const [discardDialogOpen, setDiscardDialogOpen] = useState(false);
|
|
@@ -19623,7 +20568,6 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19623
20568
|
const hasLocalChanges = hasLocalChangesProp !== void 0 ? hasLocalChangesProp && !localChangesCleared : false;
|
|
19624
20569
|
const onSubmit = (values, formexController) => {
|
|
19625
20570
|
setSavingError(void 0);
|
|
19626
|
-
setEntityIdError(false);
|
|
19627
20571
|
if (status === "existing") {
|
|
19628
20572
|
if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
|
|
19629
20573
|
} else if (status !== "new" && status !== "copy") throw Error("New FormType added, check EntityForm");
|
|
@@ -19731,7 +20675,8 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19731
20675
|
savingError,
|
|
19732
20676
|
status,
|
|
19733
20677
|
openEntityMode,
|
|
19734
|
-
disabled
|
|
20678
|
+
disabled,
|
|
20679
|
+
isSaving: formex.isSubmitting || isSavingAutoSave
|
|
19735
20680
|
};
|
|
19736
20681
|
useEffect(() => {
|
|
19737
20682
|
onFormContextReady?.(formContext);
|
|
@@ -19739,13 +20684,13 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19739
20684
|
formex.version,
|
|
19740
20685
|
collection,
|
|
19741
20686
|
entityId,
|
|
19742
|
-
path
|
|
20687
|
+
path,
|
|
20688
|
+
formex.dirty,
|
|
20689
|
+
formex.isSubmitting,
|
|
20690
|
+
formex.submitCount,
|
|
20691
|
+
isSavingAutoSave
|
|
19743
20692
|
]);
|
|
19744
20693
|
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
20694
|
const modified = formex.dirty;
|
|
19750
20695
|
useEffect(() => {
|
|
19751
20696
|
if (!autoSave) onValuesModified?.(modified, formex.values);
|
|
@@ -19779,6 +20724,106 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19779
20724
|
formex.setFieldValue
|
|
19780
20725
|
]);
|
|
19781
20726
|
const formFieldKeys = getFormFieldKeys(collection);
|
|
20727
|
+
const layout = useMemo(() => resolveFormLayout({
|
|
20728
|
+
collection,
|
|
20729
|
+
fieldKeys: formFieldKeys,
|
|
20730
|
+
status
|
|
20731
|
+
}), [
|
|
20732
|
+
collection,
|
|
20733
|
+
formFieldKeys.join(","),
|
|
20734
|
+
status
|
|
20735
|
+
]);
|
|
20736
|
+
const formElementRef = useRef(null);
|
|
20737
|
+
const railVisible = useRailVisible(formElementRef);
|
|
20738
|
+
const showRail = !Builder && layout.hasRail && railVisible;
|
|
20739
|
+
const [collapsedSections, setCollapsedSections] = useState(() => new Set(layout.sections.filter((s) => s.collapsed).map((s) => s.key)));
|
|
20740
|
+
const toggleSection = useCallback((key) => {
|
|
20741
|
+
setCollapsedSections((prev) => {
|
|
20742
|
+
const next = new Set(prev);
|
|
20743
|
+
if (next.has(key)) next.delete(key);
|
|
20744
|
+
else next.add(key);
|
|
20745
|
+
return next;
|
|
20746
|
+
});
|
|
20747
|
+
}, []);
|
|
20748
|
+
const errorKeys = useMemo(() => new Set(formex.submitCount > 0 ? Object.keys(formex.errors) : []), [formex.errors, formex.submitCount]);
|
|
20749
|
+
const autoFocusKey = useMemo(() => {
|
|
20750
|
+
if (status !== "new" && status !== "copy") return void 0;
|
|
20751
|
+
return [...layout.sections.flatMap((s) => s.fields), ...layout.sidebar].find((field) => {
|
|
20752
|
+
const property = collection.properties?.[field.key];
|
|
20753
|
+
return property ? !isFieldDisabled(property) : false;
|
|
20754
|
+
})?.key;
|
|
20755
|
+
}, [
|
|
20756
|
+
layout,
|
|
20757
|
+
collection,
|
|
20758
|
+
status,
|
|
20759
|
+
disabledProp
|
|
20760
|
+
]);
|
|
20761
|
+
function isFieldDisabled(property) {
|
|
20762
|
+
const isNew = status === "new" || status === "copy";
|
|
20763
|
+
const isIdAndAuto = (property.type === "string" || property.type === "number") && "isId" in property && typeof property.isId === "string" && property.isId !== "manual";
|
|
20764
|
+
return Boolean(disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.admin?.disabled) || !isNew && "isId" in property && Boolean(property.isId) || isNew && isIdAndAuto);
|
|
20765
|
+
}
|
|
20766
|
+
const renderField = (field) => {
|
|
20767
|
+
if (field.additional) {
|
|
20768
|
+
const additionalField = collection.additionalFields?.find((f) => f.key === field.key);
|
|
20769
|
+
if (!additionalField || !entity) return null;
|
|
20770
|
+
const AdditionalFieldBuilder = additionalField.Builder;
|
|
20771
|
+
if (!AdditionalFieldBuilder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
|
|
20772
|
+
const additionalFieldContext = formContext;
|
|
20773
|
+
const child = AdditionalFieldBuilder ? /* @__PURE__ */ jsx(AdditionalFieldBuilder, {
|
|
20774
|
+
entity,
|
|
20775
|
+
context: additionalFieldContext
|
|
20776
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
20777
|
+
className: "w-full",
|
|
20778
|
+
children: additionalField.value?.({
|
|
20779
|
+
entity,
|
|
20780
|
+
context: additionalFieldContext
|
|
20781
|
+
})?.toString()
|
|
20782
|
+
});
|
|
20783
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
20784
|
+
className: spanClass(field.span),
|
|
20785
|
+
children: [/* @__PURE__ */ jsx(LabelWithIconAndTooltip, {
|
|
20786
|
+
propertyKey: field.key,
|
|
20787
|
+
icon: /* @__PURE__ */ jsx(AlignLeftIcon, { size: iconSize.small }),
|
|
20788
|
+
title: additionalField.name,
|
|
20789
|
+
className: "text-text-secondary dark:text-text-secondary-dark"
|
|
20790
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
20791
|
+
className: cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-auto no-scrollbar"),
|
|
20792
|
+
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: child })
|
|
20793
|
+
})]
|
|
20794
|
+
}, `additional_${field.key}`);
|
|
20795
|
+
}
|
|
20796
|
+
const property = collection.properties?.[field.key];
|
|
20797
|
+
if (!property) {
|
|
20798
|
+
console.warn(`Property ${field.key} not found in collection ${collection.name} in properties or additional fields. Skipping.`);
|
|
20799
|
+
return null;
|
|
20800
|
+
}
|
|
20801
|
+
const underlyingValueHasChanged = !!underlyingChanges && Object.keys(underlyingChanges).includes(field.key) && formex.touched[field.key];
|
|
20802
|
+
const disabled = isFieldDisabled(property);
|
|
20803
|
+
const selfLabelling = isSelfLabellingProperty(property);
|
|
20804
|
+
const cmsFormFieldProps = {
|
|
20805
|
+
propertyKey: field.key,
|
|
20806
|
+
disabled,
|
|
20807
|
+
property,
|
|
20808
|
+
includeDescription: false,
|
|
20809
|
+
hideLabel: !selfLabelling,
|
|
20810
|
+
underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
|
|
20811
|
+
context: formContext,
|
|
20812
|
+
partOfArray: false,
|
|
20813
|
+
minimalistView: false,
|
|
20814
|
+
size: "large",
|
|
20815
|
+
autoFocus: autoFocusKey === field.key
|
|
20816
|
+
};
|
|
20817
|
+
return /* @__PURE__ */ jsx("div", {
|
|
20818
|
+
className: spanClass(field.span),
|
|
20819
|
+
children: /* @__PURE__ */ jsx(FieldBlock, {
|
|
20820
|
+
propertyKey: field.key,
|
|
20821
|
+
property,
|
|
20822
|
+
showLabel: !selfLabelling,
|
|
20823
|
+
children: /* @__PURE__ */ jsx(PropertyFieldBinding, { ...cmsFormFieldProps })
|
|
20824
|
+
})
|
|
20825
|
+
}, `field_${field.key}`);
|
|
20826
|
+
};
|
|
19782
20827
|
const formFields = () => {
|
|
19783
20828
|
if (Builder) return /* @__PURE__ */ jsx(Builder, {
|
|
19784
20829
|
collection,
|
|
@@ -19786,99 +20831,29 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19786
20831
|
modifiedValues: formex.values,
|
|
19787
20832
|
formContext
|
|
19788
20833
|
});
|
|
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) });
|
|
20834
|
+
return /* @__PURE__ */ jsx(FormSections, {
|
|
20835
|
+
sections: showRail || !layout.sidebar.length ? layout.sections : [...layout.sections, {
|
|
20836
|
+
key: "__rail",
|
|
20837
|
+
title: "Settings",
|
|
20838
|
+
collapsible: false,
|
|
20839
|
+
collapsed: false,
|
|
20840
|
+
fields: layout.sidebar
|
|
20841
|
+
}],
|
|
20842
|
+
collapsed: collapsedSections,
|
|
20843
|
+
onToggle: toggleSection,
|
|
20844
|
+
errorKeys,
|
|
20845
|
+
renderField
|
|
20846
|
+
});
|
|
19849
20847
|
};
|
|
19850
20848
|
const formRef = useRef(null);
|
|
19851
20849
|
const hasFormErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
19852
20850
|
const formView = /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
19853
20851
|
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
|
-
]
|
|
20852
|
+
!entity?.values && initialStatus === "existing" && /* @__PURE__ */ jsx(Alert, {
|
|
20853
|
+
color: "warning",
|
|
20854
|
+
size: "small",
|
|
20855
|
+
outerClassName: "w-full mb-4 text-xs",
|
|
20856
|
+
children: "This entity does not exist yet"
|
|
19882
20857
|
}),
|
|
19883
20858
|
children,
|
|
19884
20859
|
initialEntityId && !entity && initialStatus !== "new" && /* @__PURE__ */ jsx(Alert, {
|
|
@@ -19893,12 +20868,10 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19893
20868
|
children: "Please fix the highlighted errors before saving."
|
|
19894
20869
|
}),
|
|
19895
20870
|
formContext && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
|
|
19896
|
-
className: "mt-12 flex flex-col gap-8",
|
|
19897
20871
|
ref: formRef,
|
|
19898
20872
|
children: [formFields(), /* @__PURE__ */ jsx(ErrorFocus, { containerRef: formRef })]
|
|
19899
20873
|
}) }),
|
|
19900
20874
|
afterFields,
|
|
19901
|
-
forceActionsAtTheBottom && /* @__PURE__ */ jsx("div", { className: "h-16" }),
|
|
19902
20875
|
/* @__PURE__ */ jsxs(Dialog, {
|
|
19903
20876
|
open: discardDialogOpen,
|
|
19904
20877
|
onOpenChange: setDiscardDialogOpen,
|
|
@@ -19930,7 +20903,6 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19930
20903
|
collection,
|
|
19931
20904
|
path,
|
|
19932
20905
|
entity,
|
|
19933
|
-
layout: forceActionsAtTheBottom ? "bottom" : "responsive",
|
|
19934
20906
|
savingError,
|
|
19935
20907
|
formex,
|
|
19936
20908
|
disabled: actionsDisabled,
|
|
@@ -19944,6 +20916,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19944
20916
|
return /* @__PURE__ */ jsx(Formex, {
|
|
19945
20917
|
value: formex,
|
|
19946
20918
|
children: /* @__PURE__ */ jsxs("form", {
|
|
20919
|
+
ref: formElementRef,
|
|
19947
20920
|
onSubmit: formex.handleSubmit,
|
|
19948
20921
|
onReset: (e) => {
|
|
19949
20922
|
e.preventDefault();
|
|
@@ -19951,56 +20924,56 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
19951
20924
|
else formex.resetForm({ values: baseInitialValues });
|
|
19952
20925
|
},
|
|
19953
20926
|
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 })
|
|
20927
|
+
className: cls("@container/form flex-1 flex flex-col w-full min-h-0", className),
|
|
20928
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
20929
|
+
className: "flex-1 min-h-0 flex flex-row w-full",
|
|
20930
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
20931
|
+
className: "flex-1 min-w-0 overflow-y-auto flex justify-center items-start",
|
|
20932
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
20933
|
+
id: `form_${path}`,
|
|
20934
|
+
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"),
|
|
20935
|
+
children: [manualApplyLocalChanges && hasLocalChanges && localChangesCacheKey && /* @__PURE__ */ jsx("div", {
|
|
20936
|
+
className: "flex justify-end mb-3",
|
|
20937
|
+
children: /* @__PURE__ */ jsx(LocalChangesMenu, {
|
|
20938
|
+
cacheKey: localChangesCacheKey,
|
|
20939
|
+
properties: collection.properties,
|
|
20940
|
+
cachedData: localChangesData,
|
|
20941
|
+
formex,
|
|
20942
|
+
onClearLocalChanges: () => {
|
|
20943
|
+
setLocalChangesCleared(true);
|
|
20944
|
+
onClearLocalChanges?.();
|
|
20945
|
+
}
|
|
19996
20946
|
})
|
|
19997
|
-
})]
|
|
19998
|
-
})
|
|
19999
|
-
})
|
|
20947
|
+
}), formView]
|
|
20948
|
+
})
|
|
20949
|
+
}), showRail && /* @__PURE__ */ jsx(FormRail, {
|
|
20950
|
+
fields: layout.sidebar,
|
|
20951
|
+
showRecordMeta: layout.showRecordMeta && status === "existing",
|
|
20952
|
+
entity,
|
|
20953
|
+
renderField
|
|
20954
|
+
})]
|
|
20000
20955
|
}), dialogActions]
|
|
20001
20956
|
})
|
|
20002
20957
|
});
|
|
20003
20958
|
}
|
|
20959
|
+
/**
|
|
20960
|
+
* Grid placement for a span.
|
|
20961
|
+
*
|
|
20962
|
+
* Written out as literal class strings rather than composed, because Tailwind
|
|
20963
|
+
* scans source text: a template literal would produce classes that exist at
|
|
20964
|
+
* runtime and were never generated into the stylesheet.
|
|
20965
|
+
*
|
|
20966
|
+
* Below the column breakpoint every field takes the single available column, so
|
|
20967
|
+
* the side panel, the dialog and mobile all get a plain stack.
|
|
20968
|
+
*/
|
|
20969
|
+
function spanClass(span) {
|
|
20970
|
+
switch (span) {
|
|
20971
|
+
case 1: return "@2xl/col:col-span-1";
|
|
20972
|
+
case 2: return "@2xl/col:col-span-2";
|
|
20973
|
+
case 3: return "@2xl/col:col-span-3";
|
|
20974
|
+
case 4: return "@2xl/col:col-span-4";
|
|
20975
|
+
}
|
|
20976
|
+
}
|
|
20004
20977
|
function useOnAutoSave(autoSave, formex, lastSavedValues, save) {
|
|
20005
20978
|
useEffect(() => {
|
|
20006
20979
|
if (!autoSave) return;
|
|
@@ -20219,7 +21192,6 @@ function EntityFormBinding({ path, entityId: entityIdProp, collection, entity, i
|
|
|
20219
21192
|
context,
|
|
20220
21193
|
openEntityMode
|
|
20221
21194
|
]);
|
|
20222
|
-
const pluginFormActions = useSlot("form.actions", formActionProps);
|
|
20223
21195
|
const pluginFormBefore = useSlot("form.before", formActionProps);
|
|
20224
21196
|
const pluginFormAfter = useSlot("form.after", formActionProps);
|
|
20225
21197
|
return /* @__PURE__ */ jsx(EntityForm, {
|
|
@@ -20242,7 +21214,7 @@ function EntityFormBinding({ path, entityId: entityIdProp, collection, entity, i
|
|
|
20242
21214
|
uniqueFieldValidator,
|
|
20243
21215
|
beforeFields: pluginFormBefore,
|
|
20244
21216
|
afterFields: pluginFormAfter,
|
|
20245
|
-
pluginActions:
|
|
21217
|
+
pluginActions: [],
|
|
20246
21218
|
computedInitialValues,
|
|
20247
21219
|
hasLocalChanges,
|
|
20248
21220
|
localChangesData,
|
|
@@ -20270,6 +21242,30 @@ var useCollectionsConfigController = () => useContext(ConfigControllerContext);
|
|
|
20270
21242
|
* @module
|
|
20271
21243
|
*/
|
|
20272
21244
|
/**
|
|
21245
|
+
* Rebuild a serialized relation's `target` slug into the thunk consumers call.
|
|
21246
|
+
*
|
|
21247
|
+
* Shared by the relation *property* and the collection-level `relations` array.
|
|
21248
|
+
* They are the same shape on the wire and they need the same treatment coming
|
|
21249
|
+
* back, but only the property path had it — so a collection imported from an
|
|
21250
|
+
* existing table came off disk with `relations[n].target` still a string, which
|
|
21251
|
+
* typechecks (the serializable shape erases the difference) and throws "target
|
|
21252
|
+
* is not a function" at the first consumer.
|
|
21253
|
+
*
|
|
21254
|
+
* The lookup is consulted lazily, inside the thunk, so collections may
|
|
21255
|
+
* reference each other in any order, including circularly.
|
|
21256
|
+
*/
|
|
21257
|
+
function fromSerializableRelation(relation, label, lookup) {
|
|
21258
|
+
const slug = relation.target;
|
|
21259
|
+
return {
|
|
21260
|
+
...relation,
|
|
21261
|
+
target: () => {
|
|
21262
|
+
const found = slug ? lookup?.(slug) : void 0;
|
|
21263
|
+
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.`);
|
|
21264
|
+
return found;
|
|
21265
|
+
}
|
|
21266
|
+
};
|
|
21267
|
+
}
|
|
21268
|
+
/**
|
|
20273
21269
|
* Strip non-serializable fields from a `AdminPropertyOptions`.
|
|
20274
21270
|
* Removes `Field` and `Preview` (ComponentRef).
|
|
20275
21271
|
*/
|
|
@@ -20365,7 +21361,8 @@ function toSerializableRelation(link) {
|
|
|
20365
21361
|
case "hasMany": return {
|
|
20366
21362
|
kind: link.kind,
|
|
20367
21363
|
...common,
|
|
20368
|
-
...link.foreignKeyOnTarget ? { foreignKeyOnTarget: link.foreignKeyOnTarget } : {}
|
|
21364
|
+
...link.foreignKeyOnTarget ? { foreignKeyOnTarget: link.foreignKeyOnTarget } : {},
|
|
21365
|
+
...link.sourceKey ? { sourceKey: link.sourceKey } : {}
|
|
20369
21366
|
};
|
|
20370
21367
|
case "manyToMany": return {
|
|
20371
21368
|
kind: "manyToMany",
|
|
@@ -20676,17 +21673,7 @@ function fromSerializableProperty(serialized, lookup) {
|
|
|
20676
21673
|
...rest,
|
|
20677
21674
|
type: "relation"
|
|
20678
21675
|
};
|
|
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
|
-
}
|
|
21676
|
+
if (relation) result.relation = fromSerializableRelation(relation, sp.name ?? "", lookup);
|
|
20690
21677
|
return result;
|
|
20691
21678
|
}
|
|
20692
21679
|
default: return serialized;
|
|
@@ -20708,9 +21695,10 @@ function fromSerializableProperties(serialized, lookup) {
|
|
|
20708
21695
|
* consumer if needed.
|
|
20709
21696
|
*/
|
|
20710
21697
|
function fromSerializableCollectionConfig(serialized, lookup) {
|
|
20711
|
-
const { properties, ...rest } = serialized;
|
|
21698
|
+
const { properties, relations, ...rest } = serialized;
|
|
20712
21699
|
return {
|
|
20713
21700
|
...rest,
|
|
21701
|
+
...relations ? { relations: relations.map((relation, index) => fromSerializableRelation(relation, `relations[${index}]`, lookup)) } : {},
|
|
20714
21702
|
properties: fromSerializableProperties(properties, lookup)
|
|
20715
21703
|
};
|
|
20716
21704
|
}
|
|
@@ -20814,14 +21802,15 @@ function validateProperty(property, path, errors) {
|
|
|
20814
21802
|
});
|
|
20815
21803
|
const allowedByKind = {
|
|
20816
21804
|
belongsTo: ["localKey"],
|
|
20817
|
-
hasOne: ["foreignKeyOnTarget"],
|
|
20818
|
-
hasMany: ["foreignKeyOnTarget"],
|
|
21805
|
+
hasOne: ["foreignKeyOnTarget", "sourceKey"],
|
|
21806
|
+
hasMany: ["foreignKeyOnTarget", "sourceKey"],
|
|
20819
21807
|
manyToMany: ["through"],
|
|
20820
21808
|
via: ["joinPath", "cardinality"]
|
|
20821
21809
|
};
|
|
20822
21810
|
const linkFields = [
|
|
20823
21811
|
"localKey",
|
|
20824
21812
|
"foreignKeyOnTarget",
|
|
21813
|
+
"sourceKey",
|
|
20825
21814
|
"through",
|
|
20826
21815
|
"joinPath",
|
|
20827
21816
|
"cardinality"
|
|
@@ -21098,6 +22087,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
|
|
|
21098
22087
|
return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
|
|
21099
22088
|
}
|
|
21100
22089
|
//#endregion
|
|
21101
|
-
export { useBuildCollectionRegistryController as $,
|
|
22090
|
+
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
22091
|
|
|
21103
|
-
//# sourceMappingURL=util-
|
|
22092
|
+
//# sourceMappingURL=util-GiwPgxnL.js.map
|