@rebasepro/cms 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CollectionEditorDialog-DQjLtp1W.js → CollectionEditorDialog-C-A_NCKB.js} +15 -14
- package/dist/CollectionEditorDialog-C-A_NCKB.js.map +1 -0
- package/dist/JsonPreviewBinding-v0w3H0fp.js +35 -0
- package/dist/JsonPreviewBinding-v0w3H0fp.js.map +1 -0
- package/dist/{PropertyEditView-BMCCD2OI.js → PropertyEditView-Dv7496fx.js} +2 -57
- package/dist/{PropertyEditView-BMCCD2OI.js.map → PropertyEditView-Dv7496fx.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-DwsbIAqr.js → RouterCollectionsStudioView-DuDFEYuL.js} +4 -4
- package/dist/{RouterCollectionsStudioView-DwsbIAqr.js.map → RouterCollectionsStudioView-DuDFEYuL.js.map} +1 -1
- package/dist/collection_editor/ui/collection_editor/templates/blog_template.d.ts +126 -2
- package/dist/collection_editor/ui/collection_editor/templates/pages_template.d.ts +201 -2
- package/dist/collection_editor/ui/collection_editor/templates/products_template.d.ts +92 -2
- package/dist/collection_editor/ui/collection_editor/templates/users_template.d.ts +43 -2
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/CollectionViewBinding/CollectionViewStartActions.d.ts +6 -2
- package/dist/components/CollectionViewBinding/EntityCardBinding.d.ts +11 -1
- package/dist/components/DeleteEntityDialog.d.ts +11 -3
- package/dist/data_export/export/ExportCollectionAction.d.ts +1 -1
- package/dist/data_export/export/fetch_export_data.d.ts +10 -16
- package/dist/editor/editor.d.ts +1 -6
- package/dist/editor.js +1 -1
- package/dist/editor.js.map +1 -1
- package/dist/{export-DJoGYb89.js → export-DtSmLT6u.js} +54 -42
- package/dist/export-DtSmLT6u.js.map +1 -0
- package/dist/form/EntityForm.d.ts +1 -1
- package/dist/{history-BBpsdTrJ.js → history-C6kUDQ3f.js} +2 -2
- package/dist/{history-BBpsdTrJ.js.map → history-C6kUDQ3f.js.map} +1 -1
- package/dist/{import-C4VQ0rwO.js → import-BciQBH5t.js} +2 -2
- package/dist/{import-C4VQ0rwO.js.map → import-BciQBH5t.js.map} +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/selection/SelectionMenu.d.ts +27 -0
- package/dist/selection/index.d.ts +2 -0
- package/dist/selection/resolve_selection.d.ts +62 -0
- package/dist/types/components/EntityFormProps.d.ts +13 -0
- package/dist/util/entity_pages.d.ts +36 -0
- package/dist/util/map_with_concurrency.d.ts +16 -0
- package/dist/{util-4oPxPkbp.js → util-h6jhiiBE.js} +674 -195
- package/dist/util-h6jhiiBE.js.map +1 -0
- package/package.json +9 -9
- package/dist/CollectionEditorDialog-DQjLtp1W.js.map +0 -1
- package/dist/export-DJoGYb89.js.map +0 -1
- package/dist/util-4oPxPkbp.js.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { n as getThumbnailMeasure, r as sanitizeUrl, t as getPreviewSizeFrom } from "./util-DB31IoJE.js";
|
|
2
2
|
import * as React$1 from "react";
|
|
3
3
|
import React, { Suspense, createContext, createElement, forwardRef, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
|
-
import { deepEqual } from "fast-equals";
|
|
4
|
+
import { deepEqual, shallowEqual } from "fast-equals";
|
|
5
5
|
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, LucideIconByName, MailIcon, Markdown, Maximize2Icon, Menu, MenuItem, MinusIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PanelLeftCloseIcon, PanelLeftOpenIcon, 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, lazyChunk, paperMixin, useDebouncedCallback, useOutsideAlerter, usePortalContainer } from "@rebasepro/ui";
|
|
6
6
|
import { CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, canReadCollection, enumToObjectEntries, getArrayResolvedProperties, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityChildViews, getLabelOrConfigFrom, getReferenceFrom, getRelationFrom, getRelationTargetPath, getSubcollections, getTableName, isPropertyBuilder, normalizeOrderBy, normalizeToEntityRelation, resolveArrayProperties, resolveEnumValues, resolveProperty, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, serializeOrderBy } from "@rebasepro/common";
|
|
7
|
-
import { CONTAINER_FULL_WIDTH, CollectionScopeProvider, DEFAULT_PAGE_SIZE, EntityDisplayCache, ErrorTooltip, ErrorView, IconForView, PluginProviderStack, SIDE_PANEL_DEFAULT_WIDTH, StorageSourceContext, UnsavedChangesDialog, UserDisplay, addInitialSlash, apiBaseOf, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, entityDisplayKey, flattenKeys, getAdminEntityChildViews, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getDisplayPropertyKey, getDisplayResolver, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getEntityPreviewKeys, getFormFieldKeys, getIcon, getLastSegment as getLastSegment$1, getLeadingRelationTitleKey, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getPropertyInPath, getRedundantChildViewColumnIds, getRelationIncludeParams, getRowHeight, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, hasDeclaredDisplay, iconsSearch, isDisabled, 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, useBridgeRegistration, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataTableController, useFetch, useLargeLayout,
|
|
7
|
+
import { CONTAINER_FULL_WIDTH, CollectionScopeProvider, DEFAULT_PAGE_SIZE, EntityDisplayCache, ErrorTooltip, ErrorView, IconForView, PluginProviderStack, SIDE_PANEL_DEFAULT_WIDTH, StorageSourceContext, UnsavedChangesDialog, UserDisplay, addInitialSlash, apiBaseOf, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, entityDisplayKey, flattenKeys, getAdminEntityChildViews, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getDisplayPropertyKey, getDisplayResolver, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getEntityPreviewKeys, getFormFieldKeys, getIcon, getLastSegment as getLastSegment$1, getLeadingRelationTitleKey, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getPropertyInPath, getRedundantChildViewColumnIds, getRelationIncludeParams, getRowHeight, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, hasDeclaredDisplay, iconsSearch, isDisabled, 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, useBridgeRegistration, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataTableController, useFetch, useLargeLayout, useNavigationBlocker, usePermissions, useRebaseClient, useRebaseContext, useRelationSelector, useResolvedComponent, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/app";
|
|
8
8
|
import { Field, Formex, getIn, setIn, useCreateFormex, useFormex } from "@rebasepro/forms";
|
|
9
9
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
import { EntityReference, EntityRelation, GeoPoint, MAX_LIST_LIMIT, Vector, getCollectionDataPath, getDataSourceCapabilities, isRelationalCollectionConfig, resourceKeyOf } from "@rebasepro/types";
|
|
@@ -21,7 +21,6 @@ import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
|
|
|
21
21
|
import { SortableContext, horizontalListSortingStrategy, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
22
22
|
import { CSS } from "@dnd-kit/utilities";
|
|
23
23
|
import { inferTypeFromValue } from "@rebasepro/inference";
|
|
24
|
-
import { Highlight, themes } from "prism-react-renderer";
|
|
25
24
|
import { generateKeyBetween, generateNKeysBetween } from "fractional-indexing";
|
|
26
25
|
//#region src/util/property_utils.tsx
|
|
27
26
|
/**
|
|
@@ -962,7 +961,11 @@ function StorageThumbnailInternal({ storeUrl, interactive, storagePathOrDownload
|
|
|
962
961
|
if (!storagePathOrDownloadUrl) return null;
|
|
963
962
|
const filetype = downloadConfig?.metadata ? getFiletype(downloadConfig?.metadata.contentType) : void 0;
|
|
964
963
|
const previewType = filetype?.startsWith("image") ? "image" : filetype?.startsWith("video") ? "video" : filetype?.startsWith("audio") ? "audio" : "file";
|
|
965
|
-
if (downloadConfig?.fileNotFound) return /* @__PURE__ */ jsx(
|
|
964
|
+
if (downloadConfig?.fileNotFound) return /* @__PURE__ */ jsx(FileNotFoundThumbnail, {
|
|
965
|
+
message: t("file_not_found"),
|
|
966
|
+
size,
|
|
967
|
+
fill
|
|
968
|
+
});
|
|
966
969
|
return downloadConfig?.url ? /* @__PURE__ */ jsx(UrlComponentPreview, {
|
|
967
970
|
previewType,
|
|
968
971
|
url: downloadConfig.url,
|
|
@@ -972,6 +975,46 @@ function StorageThumbnailInternal({ storeUrl, interactive, storagePathOrDownload
|
|
|
972
975
|
hint: storagePathOrDownloadUrl
|
|
973
976
|
}) : fill ? /* @__PURE__ */ jsx(Skeleton, { className: "w-full h-full" }) : renderSkeletonImageThumbnail(size);
|
|
974
977
|
}
|
|
978
|
+
/**
|
|
979
|
+
* The placeholder for a path the storage source answered 404 for.
|
|
980
|
+
*
|
|
981
|
+
* It occupies the box the image would have — the card's whole 4:3 thumbnail
|
|
982
|
+
* when `fill`, otherwise the same measure as the skeleton it replaces — and
|
|
983
|
+
* centres its contents in it. This was `ErrorView`, which is a
|
|
984
|
+
* left-aligned block with a red triangle and `m-2` around it: laid over a card
|
|
985
|
+
* thumbnail it sat in the top-left corner with the icon colliding with the
|
|
986
|
+
* text, and a grid of them read as a page of errors rather than as missing
|
|
987
|
+
* pictures. A file that is not there is the same absence `ImagePreview`
|
|
988
|
+
* already draws for an image that fails to load, so it is drawn the same way.
|
|
989
|
+
*
|
|
990
|
+
* At `small` the box is 40px and no label fits, so the message becomes the
|
|
991
|
+
* tooltip instead of being clipped.
|
|
992
|
+
*/
|
|
993
|
+
function FileNotFoundThumbnail({ message, size, fill }) {
|
|
994
|
+
const measure = fill ? void 0 : getThumbnailMeasure(size);
|
|
995
|
+
const labelled = fill || size !== "small";
|
|
996
|
+
const body = /* @__PURE__ */ jsxs("div", {
|
|
997
|
+
className: cls("flex flex-col items-center justify-center gap-1 text-center overflow-hidden", "bg-surface-raised rounded-md p-2", fill ? "w-full h-full" : ""),
|
|
998
|
+
style: measure === void 0 ? void 0 : {
|
|
999
|
+
width: measure,
|
|
1000
|
+
height: measure,
|
|
1001
|
+
maxHeight: "100%"
|
|
1002
|
+
},
|
|
1003
|
+
children: [/* @__PURE__ */ jsx(ImageOffIcon, {
|
|
1004
|
+
size: labelled ? iconSize.small : iconSize.smallest,
|
|
1005
|
+
className: "shrink-0 text-surface-400 dark:text-surface-500"
|
|
1006
|
+
}), labelled && /* @__PURE__ */ jsx(Typography, {
|
|
1007
|
+
variant: "caption",
|
|
1008
|
+
color: "secondary",
|
|
1009
|
+
className: "max-w-full line-clamp-2 leading-tight",
|
|
1010
|
+
children: message
|
|
1011
|
+
})]
|
|
1012
|
+
});
|
|
1013
|
+
return labelled ? body : /* @__PURE__ */ jsx(Tooltip, {
|
|
1014
|
+
title: message,
|
|
1015
|
+
children: body
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
975
1018
|
function getFiletype(input) {
|
|
976
1019
|
if (input.startsWith("image")) return "image/*";
|
|
977
1020
|
else if (input.startsWith("video")) return "video/*";
|
|
@@ -1065,7 +1108,7 @@ function StringPropertyPreview({ propertyKey, value, property, size, textOnly, c
|
|
|
1065
1108
|
size,
|
|
1066
1109
|
children: strValue
|
|
1067
1110
|
}) });
|
|
1068
|
-
else if (property.admin?.urlPreview) return /* @__PURE__ */ jsx(UrlComponentPreview, {
|
|
1111
|
+
else if (property.admin?.urlPreview || property.url) return /* @__PURE__ */ jsx(UrlComponentPreview, {
|
|
1069
1112
|
size,
|
|
1070
1113
|
url: strValue,
|
|
1071
1114
|
previewType: typeof property.admin?.urlPreview === "string" ? property.admin?.urlPreview : void 0
|
|
@@ -4183,7 +4226,7 @@ function EntityTableCellActions({ showError, disabled, showExpandIcon, selected,
|
|
|
4183
4226
|
}
|
|
4184
4227
|
//#endregion
|
|
4185
4228
|
//#region src/components/CollectionTableBinding/fields/TableStorageUpload.tsx
|
|
4186
|
-
var dropZoneClasses$1 = "max-w-full box-border relative pt-[2px] items-center border border-transparent outline-hidden rounded-md duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] focus:border-primary
|
|
4229
|
+
var dropZoneClasses$1 = "max-w-full box-border relative pt-[2px] items-center border border-transparent outline-hidden rounded-md duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] focus:border-primary";
|
|
4187
4230
|
var activeDropClasses$1 = "pt-0 border-2 border-solid";
|
|
4188
4231
|
var acceptDropClasses$1 = "transition-colors duration-200 ease-[cubic-bezier(0,0,0.2,1)] border-2 border-solid border-green-500 bg-green-50 dark:bg-green-900";
|
|
4189
4232
|
var rejectDropClasses$1 = "transition-colors duration-200 ease-[cubic-bezier(0,0,0.2,1)] border-2 border-solid border-red-500 bg-red-50 dark:bg-red-900";
|
|
@@ -7375,7 +7418,7 @@ var SelectableTable = function SelectableTable({ onValueChange, cellRenderer, on
|
|
|
7375
7418
|
children: /* @__PURE__ */ jsx(VirtualTable, {
|
|
7376
7419
|
data,
|
|
7377
7420
|
columns,
|
|
7378
|
-
cellRenderer
|
|
7421
|
+
cellRenderer,
|
|
7379
7422
|
onRowClick: inlineEditing ? void 0 : onEntityClick ? onRowClick : void 0,
|
|
7380
7423
|
onEndReached: loadNextPage,
|
|
7381
7424
|
onResetPagination: resetPagination,
|
|
@@ -7434,9 +7477,19 @@ var SelectableTable = function SelectableTable({ onValueChange, cellRenderer, on
|
|
|
7434
7477
|
var CollectionTableBinding = function CollectionTableBinding({ className, style, fixedFilter, actionsStart, actions, viewModeToggle, title, tableRowActionsBuilder, uniqueFieldValidator, getPropertyFor, onValueChange, selectionController, highlightedEntities, onEntityClick, onColumnResize, initialScroll, onScroll, onSizeChanged, hoverRow = true, inlineEditing = false, additionalFields, displayedColumnIds, defaultSize, properties, tableController, filterable = true, sortable = true, endAdornment, AddColumnComponent, AdditionalHeaderWidget, additionalIDHeaderWidget, emptyComponent, getIdColumnWidth, enablePopupIcon, openEntityMode = "side_panel", onColumnsOrderChange, hideToolbar = false }) {
|
|
7435
7478
|
const ref = useRef(null);
|
|
7436
7479
|
const largeLayout = useLargeLayout();
|
|
7437
|
-
const
|
|
7438
|
-
|
|
7439
|
-
|
|
7480
|
+
const hasSelection = Boolean(selectionController?.hasSelection);
|
|
7481
|
+
const isRowHighlighted = useCallback((entity) => {
|
|
7482
|
+
if (hasSelection) return selectionController.isEntitySelected(entity);
|
|
7483
|
+
return Boolean(highlightedEntities?.some((e) => e && e.id === entity.id && e.path === entity.path));
|
|
7484
|
+
}, [
|
|
7485
|
+
hasSelection,
|
|
7486
|
+
selectionController,
|
|
7487
|
+
highlightedEntities
|
|
7488
|
+
]);
|
|
7489
|
+
const cellExtraData = useMemo(() => ({
|
|
7490
|
+
selection: selectionController?.selection,
|
|
7491
|
+
highlightedEntityIds: highlightedEntities?.map((e) => e?.id)
|
|
7492
|
+
}), [selectionController?.selection, highlightedEntities]);
|
|
7440
7493
|
const context = useRebaseContext();
|
|
7441
7494
|
const [size, setSize] = React.useState(defaultSize ?? "m");
|
|
7442
7495
|
React.useEffect(() => {
|
|
@@ -7646,7 +7699,7 @@ var CollectionTableBinding = function CollectionTableBinding({ className, style,
|
|
|
7646
7699
|
inlineEditing,
|
|
7647
7700
|
cellRenderer,
|
|
7648
7701
|
onEntityClick,
|
|
7649
|
-
highlightedRow:
|
|
7702
|
+
highlightedRow: isRowHighlighted,
|
|
7650
7703
|
tableController,
|
|
7651
7704
|
onValueChange,
|
|
7652
7705
|
initialScroll,
|
|
@@ -7658,46 +7711,290 @@ var CollectionTableBinding = function CollectionTableBinding({ className, style,
|
|
|
7658
7711
|
endAdornment,
|
|
7659
7712
|
AddColumnComponent,
|
|
7660
7713
|
onColumnsOrderChange,
|
|
7661
|
-
extraData:
|
|
7714
|
+
extraData: cellExtraData
|
|
7662
7715
|
})]
|
|
7663
7716
|
});
|
|
7664
7717
|
};
|
|
7665
7718
|
//#endregion
|
|
7666
7719
|
//#region src/components/CollectionViewBinding/useSelectionController.tsx
|
|
7720
|
+
/** Same row: id alone is not unique across a junction-backed tab's two paths. */
|
|
7721
|
+
function sameEntity(a, b) {
|
|
7722
|
+
return a.id === b.id && a.path === b.path;
|
|
7723
|
+
}
|
|
7724
|
+
var EMPTY_SELECTION = {
|
|
7725
|
+
type: "entities",
|
|
7726
|
+
entities: []
|
|
7727
|
+
};
|
|
7667
7728
|
function useSelectionController(onSelectionChange) {
|
|
7668
|
-
const [
|
|
7729
|
+
const [selection, setSelection] = useState(EMPTY_SELECTION);
|
|
7669
7730
|
const onSelectionChangeRef = useRef(onSelectionChange);
|
|
7670
7731
|
onSelectionChangeRef.current = onSelectionChange;
|
|
7671
7732
|
const toggleEntitySelection = useCallback((entity, newSelectedState) => {
|
|
7672
|
-
|
|
7673
|
-
|
|
7674
|
-
|
|
7675
|
-
|
|
7676
|
-
|
|
7677
|
-
|
|
7678
|
-
|
|
7679
|
-
|
|
7680
|
-
|
|
7733
|
+
setSelection((prev) => {
|
|
7734
|
+
if (prev.type === "query") {
|
|
7735
|
+
const isExcluded = prev.excluded.some((e) => sameEntity(e, entity));
|
|
7736
|
+
const shouldSelect = newSelectedState ?? isExcluded;
|
|
7737
|
+
if (shouldSelect === !isExcluded) return prev;
|
|
7738
|
+
onSelectionChangeRef.current?.(entity, shouldSelect);
|
|
7739
|
+
return {
|
|
7740
|
+
...prev,
|
|
7741
|
+
excluded: shouldSelect ? prev.excluded.filter((e) => !sameEntity(e, entity)) : [...prev.excluded, entity]
|
|
7742
|
+
};
|
|
7681
7743
|
}
|
|
7682
|
-
|
|
7744
|
+
const isSelected = prev.entities.some((e) => sameEntity(e, entity));
|
|
7745
|
+
const shouldSelect = newSelectedState ?? !isSelected;
|
|
7746
|
+
if (shouldSelect === isSelected) return prev;
|
|
7747
|
+
onSelectionChangeRef.current?.(entity, shouldSelect);
|
|
7748
|
+
return {
|
|
7749
|
+
type: "entities",
|
|
7750
|
+
entities: shouldSelect ? [...prev.entities, entity] : prev.entities.filter((e) => !sameEntity(e, entity))
|
|
7751
|
+
};
|
|
7683
7752
|
});
|
|
7684
7753
|
}, []);
|
|
7685
7754
|
const isEntitySelected = useCallback((entity) => {
|
|
7686
|
-
|
|
7687
|
-
|
|
7755
|
+
if (selection.type === "query") return !selection.excluded.some((e) => sameEntity(e, entity));
|
|
7756
|
+
return selection.entities.some((e) => sameEntity(e, entity));
|
|
7757
|
+
}, [selection]);
|
|
7758
|
+
const setSelectedEntities = useCallback((entities) => {
|
|
7759
|
+
setSelection({
|
|
7760
|
+
type: "entities",
|
|
7761
|
+
entities
|
|
7762
|
+
});
|
|
7763
|
+
}, []);
|
|
7764
|
+
const selectAllMatching = useCallback((query, count) => {
|
|
7765
|
+
setSelection({
|
|
7766
|
+
type: "query",
|
|
7767
|
+
query,
|
|
7768
|
+
excluded: [],
|
|
7769
|
+
count
|
|
7770
|
+
});
|
|
7771
|
+
}, []);
|
|
7772
|
+
const clearSelection = useCallback(() => {
|
|
7773
|
+
setSelection(EMPTY_SELECTION);
|
|
7774
|
+
}, []);
|
|
7775
|
+
const selectedCount = useMemo(() => {
|
|
7776
|
+
if (selection.type === "entities") return selection.entities.length;
|
|
7777
|
+
if (selection.count === void 0) return void 0;
|
|
7778
|
+
return Math.max(0, selection.count - selection.excluded.length);
|
|
7779
|
+
}, [selection]);
|
|
7780
|
+
const hasSelection = selection.type === "query" ? selectedCount === void 0 || selectedCount > 0 : selection.entities.length > 0;
|
|
7688
7781
|
return useMemo(() => ({
|
|
7689
|
-
|
|
7782
|
+
selection,
|
|
7783
|
+
setSelection,
|
|
7784
|
+
selectedCount,
|
|
7785
|
+
hasSelection,
|
|
7690
7786
|
setSelectedEntities,
|
|
7787
|
+
selectAllMatching,
|
|
7788
|
+
clearSelection,
|
|
7691
7789
|
isEntitySelected,
|
|
7692
7790
|
toggleEntitySelection
|
|
7693
7791
|
}), [
|
|
7694
|
-
|
|
7792
|
+
selection,
|
|
7793
|
+
selectedCount,
|
|
7794
|
+
hasSelection,
|
|
7695
7795
|
setSelectedEntities,
|
|
7796
|
+
selectAllMatching,
|
|
7797
|
+
clearSelection,
|
|
7696
7798
|
isEntitySelected,
|
|
7697
7799
|
toggleEntitySelection
|
|
7698
7800
|
]);
|
|
7699
7801
|
}
|
|
7700
7802
|
//#endregion
|
|
7803
|
+
//#region src/util/entity_pages.ts
|
|
7804
|
+
/**
|
|
7805
|
+
* Read **every** row matching a query, one page at a time.
|
|
7806
|
+
*
|
|
7807
|
+
* `find({})` sends no `limit`, and an absent limit is resolved server-side to
|
|
7808
|
+
* `DEFAULT_LIST_LIMIT` — 50 rows. Anything that means "all of them" and asks
|
|
7809
|
+
* once gets 50 back, with nothing to say that is what happened.
|
|
7810
|
+
*
|
|
7811
|
+
* Termination is driven by the server's `meta.hasMore` where it is reported and
|
|
7812
|
+
* by an empty page otherwise — never by comparing a page's length against the
|
|
7813
|
+
* requested limit, since a final page that happens to be exactly full is
|
|
7814
|
+
* indistinguishable that way and a walk that stops there drops rows.
|
|
7815
|
+
*
|
|
7816
|
+
* @throws When more than `maxRows` rows match. A truncated answer is worse than
|
|
7817
|
+
* no answer: it is indistinguishable from a complete one.
|
|
7818
|
+
*/
|
|
7819
|
+
async function walkEntityPages({ accessor, params, pageSize, maxRows, onCeiling, onProgress, signal }) {
|
|
7820
|
+
const rows = [];
|
|
7821
|
+
let offset = 0;
|
|
7822
|
+
for (;;) {
|
|
7823
|
+
if (signal?.aborted) break;
|
|
7824
|
+
const res = await accessor.find({
|
|
7825
|
+
...params,
|
|
7826
|
+
limit: pageSize,
|
|
7827
|
+
offset
|
|
7828
|
+
});
|
|
7829
|
+
const page = res?.data ?? [];
|
|
7830
|
+
rows.push(...page);
|
|
7831
|
+
onProgress?.(rows.length, res?.meta?.total);
|
|
7832
|
+
if (rows.length > maxRows) throw new Error(onCeiling(maxRows));
|
|
7833
|
+
if (page.length === 0) break;
|
|
7834
|
+
if (res?.meta && !res.meta.hasMore) break;
|
|
7835
|
+
offset += page.length;
|
|
7836
|
+
}
|
|
7837
|
+
return rows;
|
|
7838
|
+
}
|
|
7839
|
+
//#endregion
|
|
7840
|
+
//#region src/selection/resolve_selection.ts
|
|
7841
|
+
/**
|
|
7842
|
+
* Rows asked for per request while resolving a query selection.
|
|
7843
|
+
*
|
|
7844
|
+
* Same ceiling as the export walk, and for the same reason: the server refuses
|
|
7845
|
+
* a `limit` above its `MAX_LIST_LIMIT` rather than clamping it.
|
|
7846
|
+
*/
|
|
7847
|
+
var SELECTION_PAGE_SIZE = 500;
|
|
7848
|
+
/**
|
|
7849
|
+
* Most rows one query selection will resolve to ids for.
|
|
7850
|
+
*
|
|
7851
|
+
* The write path is per-row — `deleteMany` loops the single-row delete server
|
|
7852
|
+
* side so hooks and row-level security still run for each — so this is a
|
|
7853
|
+
* genuine ceiling on the work, not just on memory. Going over it **throws**
|
|
7854
|
+
* rather than acting on a prefix, which is the failure that cannot be seen:
|
|
7855
|
+
* "deleted 12,480 rows" reads identically whether it was all of them or the
|
|
7856
|
+
* first page.
|
|
7857
|
+
*/
|
|
7858
|
+
var MAX_SELECTION_ROWS = 2e4;
|
|
7859
|
+
/**
|
|
7860
|
+
* A stable string for a selection query, for comparing the query a selection
|
|
7861
|
+
* was made against with the one the view is showing now.
|
|
7862
|
+
*
|
|
7863
|
+
* Filter keys are sorted: `filterValues` is rebuilt on most renders and its key
|
|
7864
|
+
* order follows whatever order the user touched the columns in, so a plain
|
|
7865
|
+
* `JSON.stringify` would report the same filter as a different one and drop a
|
|
7866
|
+
* selection the moment anything re-rendered.
|
|
7867
|
+
*/
|
|
7868
|
+
function serializeSelectionQuery(query) {
|
|
7869
|
+
const filter = query.filterValues ? Object.keys(query.filterValues).sort().map((k) => `${k}=${JSON.stringify(query.filterValues[k])}`).join("&") : "";
|
|
7870
|
+
const order = query.sortBy?.map(([field, direction]) => `${String(field)}:${direction}`).join(",") ?? "";
|
|
7871
|
+
return `${query.path}|${filter}|${order}|${query.searchString ?? ""}`;
|
|
7872
|
+
}
|
|
7873
|
+
/** Turn a captured selection query into the `find` params that reproduce it. */
|
|
7874
|
+
function selectionQueryToFindParams(query) {
|
|
7875
|
+
return {
|
|
7876
|
+
where: query.filterValues && Object.keys(query.filterValues).length > 0 ? query.filterValues : void 0,
|
|
7877
|
+
orderBy: query.sortBy && query.sortBy.length > 0 ? query.sortBy.map(([field, direction]) => [String(field), direction]) : void 0,
|
|
7878
|
+
searchString: query.searchString
|
|
7879
|
+
};
|
|
7880
|
+
}
|
|
7881
|
+
/**
|
|
7882
|
+
* The rows a selection covers.
|
|
7883
|
+
*
|
|
7884
|
+
* This is the *only* way to get rows out of a query selection, and it is why
|
|
7885
|
+
* {@link EntitySelection} is a union: a bulk action has to call it, which means
|
|
7886
|
+
* it has to deal with the wait, the ceiling and the failure. The array this
|
|
7887
|
+
* replaced let the same code path read whatever the view had scrolled — 50 rows
|
|
7888
|
+
* of 12,480 — and report success.
|
|
7889
|
+
*
|
|
7890
|
+
* @throws When the query matches more than `maxRows` rows.
|
|
7891
|
+
*/
|
|
7892
|
+
async function resolveSelection({ selection, accessor, pageSize = 500, maxRows = MAX_SELECTION_ROWS, onProgress, signal }) {
|
|
7893
|
+
if (selection.type === "entities") {
|
|
7894
|
+
onProgress?.(selection.entities.length, selection.entities.length);
|
|
7895
|
+
return selection.entities;
|
|
7896
|
+
}
|
|
7897
|
+
if (selection.count !== void 0 && selection.count - selection.excluded.length > maxRows) throw new Error(selectionCeilingMessage(maxRows));
|
|
7898
|
+
const rows = await walkEntityPages({
|
|
7899
|
+
accessor,
|
|
7900
|
+
params: selectionQueryToFindParams(selection.query),
|
|
7901
|
+
pageSize,
|
|
7902
|
+
maxRows,
|
|
7903
|
+
onProgress,
|
|
7904
|
+
signal,
|
|
7905
|
+
onCeiling: selectionCeilingMessage
|
|
7906
|
+
});
|
|
7907
|
+
if (selection.excluded.length === 0) return rows;
|
|
7908
|
+
const excluded = new Set(selection.excluded.map((e) => `${e.path}/${e.id}`));
|
|
7909
|
+
return rows.filter((e) => !excluded.has(`${e.path}/${e.id}`));
|
|
7910
|
+
}
|
|
7911
|
+
function selectionCeilingMessage(maxRows) {
|
|
7912
|
+
return `This selection covers more than ${maxRows.toLocaleString()} rows, which is more than can be acted on from the browser in one go. Filter the collection down first.`;
|
|
7913
|
+
}
|
|
7914
|
+
//#endregion
|
|
7915
|
+
//#region src/selection/SelectionMenu.tsx
|
|
7916
|
+
/**
|
|
7917
|
+
* The whole of selecting rows, in one control: a checkbox for the common case
|
|
7918
|
+
* and a caret for everything else.
|
|
7919
|
+
*
|
|
7920
|
+
* The escalation from "the rows on screen" to "every row that matches" lived in
|
|
7921
|
+
* a strip under the toolbar, which was wrong in a way that only shows up in
|
|
7922
|
+
* use: it appears the moment you tick a single checkbox, and the list you were
|
|
7923
|
+
* reading jumps down by its height. A control that punishes the first click is
|
|
7924
|
+
* not worth whatever the strip was explaining.
|
|
7925
|
+
*
|
|
7926
|
+
* So the escalation is a menu item — clicked deliberately, costing no layout,
|
|
7927
|
+
* and in the place you already go to select everything.
|
|
7928
|
+
*/
|
|
7929
|
+
function SelectionMenu({ selectionController, loadedEntities, query, collectionEntitiesCount, collectionName }) {
|
|
7930
|
+
const { t } = useTranslation();
|
|
7931
|
+
const { selection, selectedCount, hasSelection, isEntitySelected, setSelectedEntities, selectAllMatching, clearSelection } = selectionController;
|
|
7932
|
+
const selectedLoaded = useMemo(() => loadedEntities.filter((e) => isEntitySelected(e)).length, [loadedEntities, isEntitySelected]);
|
|
7933
|
+
const allLoadedSelected = loadedEntities.length > 0 && selectedLoaded === loadedEntities.length;
|
|
7934
|
+
const someSelected = selectedLoaded > 0 && !allLoadedSelected;
|
|
7935
|
+
const isQuery = selection.type === "query";
|
|
7936
|
+
const moreThanLoaded = collectionEntitiesCount !== void 0 && collectionEntitiesCount > loadedEntities.length;
|
|
7937
|
+
const label = hasSelection ? t("selection_deselect_all") ?? "Deselect all" : t("selection_select_all_loaded") ?? "Select all loaded rows";
|
|
7938
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
7939
|
+
className: cls("flex items-center rounded-md", hasSelection && "bg-surface-hover"),
|
|
7940
|
+
children: [
|
|
7941
|
+
/* @__PURE__ */ jsx(Tooltip, {
|
|
7942
|
+
title: label,
|
|
7943
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
7944
|
+
className: "pl-1.5 pr-0.5 py-1 flex items-center",
|
|
7945
|
+
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
7946
|
+
size: "small",
|
|
7947
|
+
padding: false,
|
|
7948
|
+
"aria-label": label,
|
|
7949
|
+
disabled: loadedEntities.length === 0 && !isQuery,
|
|
7950
|
+
checked: allLoadedSelected || isQuery,
|
|
7951
|
+
indeterminate: someSelected,
|
|
7952
|
+
onCheckedChange: (checked) => {
|
|
7953
|
+
if (checked) setSelectedEntities(loadedEntities);
|
|
7954
|
+
else clearSelection();
|
|
7955
|
+
}
|
|
7956
|
+
})
|
|
7957
|
+
})
|
|
7958
|
+
}),
|
|
7959
|
+
/* @__PURE__ */ jsxs(Menu, {
|
|
7960
|
+
align: "start",
|
|
7961
|
+
trigger: /* @__PURE__ */ jsx("button", {
|
|
7962
|
+
type: "button",
|
|
7963
|
+
"aria-label": t("selection_options") ?? "Selection options",
|
|
7964
|
+
className: cls("pr-1 pl-0 py-1 flex items-center rounded-r-md", "hover:bg-surface-hover focus:outline-none", "text-surface-accent-500 dark:text-surface-accent-300"),
|
|
7965
|
+
children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: iconSize.smallest })
|
|
7966
|
+
}),
|
|
7967
|
+
children: [
|
|
7968
|
+
/* @__PURE__ */ jsx(MenuItem, {
|
|
7969
|
+
dense: true,
|
|
7970
|
+
onClick: () => setSelectedEntities(loadedEntities),
|
|
7971
|
+
children: t("selection_menu_all_loaded") ?? "All on this page"
|
|
7972
|
+
}),
|
|
7973
|
+
moreThanLoaded && /* @__PURE__ */ jsx(MenuItem, {
|
|
7974
|
+
dense: true,
|
|
7975
|
+
onClick: () => selectAllMatching(query, collectionEntitiesCount),
|
|
7976
|
+
children: t("selection_menu_all_matching", {
|
|
7977
|
+
total: collectionEntitiesCount.toLocaleString(),
|
|
7978
|
+
collection: collectionName
|
|
7979
|
+
}) ?? `All ${collectionEntitiesCount.toLocaleString()} ${collectionName}`
|
|
7980
|
+
}),
|
|
7981
|
+
/* @__PURE__ */ jsx(MenuItem, {
|
|
7982
|
+
dense: true,
|
|
7983
|
+
disabled: !hasSelection,
|
|
7984
|
+
onClick: clearSelection,
|
|
7985
|
+
children: t("selection_menu_none") ?? "None"
|
|
7986
|
+
})
|
|
7987
|
+
]
|
|
7988
|
+
}),
|
|
7989
|
+
hasSelection && /* @__PURE__ */ jsx("span", {
|
|
7990
|
+
"data-testid": "selection-count",
|
|
7991
|
+
className: "pl-1 pr-2 text-xs font-mono tabular-nums text-text-secondary dark:text-text-secondary-dark",
|
|
7992
|
+
children: selectedCount === void 0 ? "…" : selectedCount.toLocaleString()
|
|
7993
|
+
})
|
|
7994
|
+
]
|
|
7995
|
+
});
|
|
7996
|
+
}
|
|
7997
|
+
//#endregion
|
|
7701
7998
|
//#region src/components/CollectionViewBinding/hooks/useCollectionInlineEditor.ts
|
|
7702
7999
|
function useCollectionInlineEditor({ path, collection, dataClient, context }) {
|
|
7703
8000
|
const uniqueFieldValidator = useCallback(async ({ name, value, property, entityId }) => {
|
|
@@ -9594,8 +9891,8 @@ function EditorCollectionAction({ path, parentCollectionSlugs, parentEntityIds,
|
|
|
9594
9891
|
}
|
|
9595
9892
|
//#endregion
|
|
9596
9893
|
//#region src/components/CollectionViewBinding/CollectionViewActions.tsx
|
|
9597
|
-
var ImportCollectionAction = lazyChunk(() => import("./import-
|
|
9598
|
-
var ExportCollectionAction = lazyChunk(() => import("./export-
|
|
9894
|
+
var ImportCollectionAction = lazyChunk(() => import("./import-BciQBH5t.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
|
|
9895
|
+
var ExportCollectionAction = lazyChunk(() => import("./export-DtSmLT6u.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
|
|
9599
9896
|
function CollectionViewActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, onNewClick, onAddExistingClick, onMultipleDeleteClick, selectionEnabled, path, selectionController, tableController, collectionEntitiesCount, compact, children, openNewDocument }) {
|
|
9600
9897
|
const context = useAdminContext();
|
|
9601
9898
|
const { canCreate, canDelete } = usePermissions();
|
|
@@ -9603,7 +9900,7 @@ function CollectionViewActions({ collection, relativePath, parentCollectionSlugs
|
|
|
9603
9900
|
const { t } = useTranslation();
|
|
9604
9901
|
const collectionEditorController = useCollectionEditorController();
|
|
9605
9902
|
const hasCollectionEditor = Boolean(collectionEditorController?.editCollection);
|
|
9606
|
-
const
|
|
9903
|
+
const { selectedCount, hasSelection } = selectionController;
|
|
9607
9904
|
const addButton = canCreate(collection, path) && onNewClick && (largeLayout && !compact ? /* @__PURE__ */ jsx(Button, {
|
|
9608
9905
|
id: `add_entity_${path}`,
|
|
9609
9906
|
onClick: onNewClick,
|
|
@@ -9641,25 +9938,28 @@ function CollectionViewActions({ collection, relativePath, parentCollectionSlugs
|
|
|
9641
9938
|
})
|
|
9642
9939
|
}));
|
|
9643
9940
|
const multipleDeleteEnabled = canDelete(collection, path, null);
|
|
9644
|
-
const
|
|
9941
|
+
const countLabel = selectedCount === void 0 ? "…" : selectedCount.toLocaleString();
|
|
9645
9942
|
let multipleDeleteButton;
|
|
9646
9943
|
if (hasSelection) {
|
|
9647
9944
|
const button = largeLayout && !compact ? /* @__PURE__ */ jsxs(Button, {
|
|
9648
9945
|
variant: "text",
|
|
9649
|
-
disabled: !
|
|
9650
|
-
startIcon: /* @__PURE__ */ jsx(Trash2Icon, {
|
|
9946
|
+
disabled: !multipleDeleteEnabled,
|
|
9947
|
+
startIcon: /* @__PURE__ */ jsx(Trash2Icon, {
|
|
9948
|
+
size: iconSize.small,
|
|
9949
|
+
className: "shrink-0"
|
|
9950
|
+
}),
|
|
9651
9951
|
onClick: onMultipleDeleteClick,
|
|
9652
9952
|
color: "primary",
|
|
9653
|
-
className: "lg:w-20",
|
|
9953
|
+
className: "lg:min-w-20",
|
|
9654
9954
|
children: [
|
|
9655
9955
|
"(",
|
|
9656
|
-
|
|
9956
|
+
countLabel,
|
|
9657
9957
|
")"
|
|
9658
9958
|
]
|
|
9659
9959
|
}) : /* @__PURE__ */ jsx(IconButton, {
|
|
9660
9960
|
size: "small",
|
|
9661
9961
|
color: "primary",
|
|
9662
|
-
disabled: !
|
|
9962
|
+
disabled: !multipleDeleteEnabled,
|
|
9663
9963
|
onClick: onMultipleDeleteClick,
|
|
9664
9964
|
children: /* @__PURE__ */ jsx(Trash2Icon, { size: iconSize.small })
|
|
9665
9965
|
});
|
|
@@ -10190,7 +10490,7 @@ function EntityCardBinding({ entity, collection, onClick, selected, highlighted,
|
|
|
10190
10490
|
path: entity.path,
|
|
10191
10491
|
entityId: entity.id
|
|
10192
10492
|
});
|
|
10193
|
-
onClick(entity);
|
|
10493
|
+
onClick(entity, e);
|
|
10194
10494
|
}
|
|
10195
10495
|
};
|
|
10196
10496
|
const handleCheckboxClick = (e) => {
|
|
@@ -10302,7 +10602,7 @@ function CollectionCardViewBinding({ collection, tableController, onEntityClick,
|
|
|
10302
10602
|
const handleSelectionChange = useCallback((entity, selected) => {
|
|
10303
10603
|
selectionController?.toggleEntitySelection(entity, selected);
|
|
10304
10604
|
}, [selectionController]);
|
|
10305
|
-
const selectedIds = useMemo(() => new Set(selectionController?.
|
|
10605
|
+
const selectedIds = useMemo(() => new Set(data.filter((e) => selectionController?.isEntitySelected(e)).map((e) => e.id)), [data, selectionController]);
|
|
10306
10606
|
const highlightedIds = useMemo(() => new Set(highlightedEntities?.map((e) => e.id)), [highlightedEntities]);
|
|
10307
10607
|
const handleRowSelectionChange = useCallback((entity, selected) => {
|
|
10308
10608
|
handleSelectionChange(entity, selected);
|
|
@@ -10329,7 +10629,7 @@ function CollectionCardViewBinding({ collection, tableController, onEntityClick,
|
|
|
10329
10629
|
entity,
|
|
10330
10630
|
collection,
|
|
10331
10631
|
searchString: tableController.searchString,
|
|
10332
|
-
onClick,
|
|
10632
|
+
onClick: (_entity, event) => onClick(event),
|
|
10333
10633
|
selected,
|
|
10334
10634
|
highlighted,
|
|
10335
10635
|
onSelectionChange: handleRowSelectionChange,
|
|
@@ -10837,7 +11137,7 @@ function CollectionListViewBinding({ collection, tableController, onEntityClick,
|
|
|
10837
11137
|
return highlightedEntities?.some((e) => e.id === entity.id && e.path === entity.path) ?? false;
|
|
10838
11138
|
}, [highlightedEntities]);
|
|
10839
11139
|
const rowClasses = getRowClasses(size);
|
|
10840
|
-
const selectedIds = useMemo(() => new Set(selectionController?.
|
|
11140
|
+
const selectedIds = useMemo(() => new Set(data.filter((e) => selectionController?.isEntitySelected(e)).map((e) => e.id)), [data, selectionController]);
|
|
10841
11141
|
const highlightedIds = useMemo(() => new Set(highlightedEntities?.map((e) => e.id)), [highlightedEntities]);
|
|
10842
11142
|
const handleRowSelectionChange = useCallback((entity, selected) => {
|
|
10843
11143
|
handleSelectionChange(entity, selected);
|
|
@@ -11070,7 +11370,7 @@ var ListRow = React.memo(function ListRow({ entity, collection, onClick, selecte
|
|
|
11070
11370
|
children: slots.title?.value !== void 0 ? /* @__PURE__ */ jsx(Typography, {
|
|
11071
11371
|
component: "div",
|
|
11072
11372
|
variant: "body2",
|
|
11073
|
-
className: "font-semibold text-surface-900 dark:text-surface-50 truncate transition-colors group-hover:text-primary
|
|
11373
|
+
className: "font-semibold text-surface-900 dark:text-surface-50 truncate transition-colors group-hover:text-primary dark:group-hover:text-primary-light",
|
|
11074
11374
|
children: terms.length > 0 && typeof slots.title?.value === "string" ? /* @__PURE__ */ jsx(Highlighted, {
|
|
11075
11375
|
text: slots.title.value,
|
|
11076
11376
|
terms
|
|
@@ -11081,7 +11381,7 @@ var ListRow = React.memo(function ListRow({ entity, collection, onClick, selecte
|
|
|
11081
11381
|
}) : /* @__PURE__ */ jsx(Typography, {
|
|
11082
11382
|
component: "div",
|
|
11083
11383
|
variant: "body2",
|
|
11084
|
-
className: "font-semibold text-surface-500 dark:text-surface-400 font-mono text-xs transition-colors group-hover:text-primary
|
|
11384
|
+
className: "font-semibold text-surface-500 dark:text-surface-400 font-mono text-xs transition-colors group-hover:text-primary dark:group-hover:text-primary-light",
|
|
11085
11385
|
children: entity.id
|
|
11086
11386
|
})
|
|
11087
11387
|
}),
|
|
@@ -11527,21 +11827,21 @@ function EntityViewBinding({ entity, collection, className, asPage = false, open
|
|
|
11527
11827
|
return next;
|
|
11528
11828
|
});
|
|
11529
11829
|
}, []);
|
|
11830
|
+
const rebaseContext = useRebaseContext();
|
|
11530
11831
|
const renderField = useCallback((field) => {
|
|
11531
11832
|
if (field.additional) {
|
|
11532
11833
|
const additionalField = collection.additionalFields?.find((f) => f.key === field.key);
|
|
11533
11834
|
if (!additionalField || !formContext) return null;
|
|
11534
11835
|
const AdditionalFieldBuilder = additionalField.Builder;
|
|
11535
|
-
const additionalFieldContext = formContext;
|
|
11536
11836
|
const child = AdditionalFieldBuilder ? /* @__PURE__ */ jsx(AdditionalFieldBuilder, {
|
|
11537
11837
|
entity,
|
|
11538
|
-
context:
|
|
11838
|
+
context: rebaseContext
|
|
11539
11839
|
}) : /* @__PURE__ */ jsx("div", {
|
|
11540
11840
|
className: "w-full",
|
|
11541
11841
|
children: /* @__PURE__ */ jsx(AdditionalFieldValue, {
|
|
11542
11842
|
field: additionalField,
|
|
11543
11843
|
entity,
|
|
11544
|
-
context:
|
|
11844
|
+
context: rebaseContext
|
|
11545
11845
|
})
|
|
11546
11846
|
});
|
|
11547
11847
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -11583,7 +11883,8 @@ function EntityViewBinding({ entity, collection, className, asPage = false, open
|
|
|
11583
11883
|
}, [
|
|
11584
11884
|
collection,
|
|
11585
11885
|
entity,
|
|
11586
|
-
formContext
|
|
11886
|
+
formContext,
|
|
11887
|
+
rebaseContext
|
|
11587
11888
|
]);
|
|
11588
11889
|
/**
|
|
11589
11890
|
* One field of a `readVariant: "summary"` section: a label/value row rather
|
|
@@ -11926,36 +12227,18 @@ function SplitListShowButton({ onClick }) {
|
|
|
11926
12227
|
});
|
|
11927
12228
|
}
|
|
11928
12229
|
//#endregion
|
|
11929
|
-
//#region src/components/JsonPreviewBinding.tsx
|
|
11930
|
-
function JsonPreviewBinding({ values }) {
|
|
11931
|
-
const code = JSON.stringify(values, null, " ");
|
|
11932
|
-
const { mode } = useModeController();
|
|
11933
|
-
const preRef = useRef(null);
|
|
11934
|
-
return /* @__PURE__ */ jsx(Highlight, {
|
|
11935
|
-
theme: mode === "dark" ? themes.vsDark : themes.github,
|
|
11936
|
-
code,
|
|
11937
|
-
language: "json",
|
|
11938
|
-
children: ({ style, tokens, getLineProps, getTokenProps }) => /* @__PURE__ */ jsx("pre", {
|
|
11939
|
-
ref: preRef,
|
|
11940
|
-
style: {
|
|
11941
|
-
...style,
|
|
11942
|
-
backgroundColor: "inherit"
|
|
11943
|
-
},
|
|
11944
|
-
className: "max-w-6xl mx-auto p-8 rounded-xs text-sm",
|
|
11945
|
-
children: tokens.map((line, i) => /* @__PURE__ */ jsx("div", {
|
|
11946
|
-
...getLineProps({ line }),
|
|
11947
|
-
className: "text-wrap",
|
|
11948
|
-
children: line.map((token, key) => /* @__PURE__ */ jsx("span", {
|
|
11949
|
-
...getTokenProps({ token }),
|
|
11950
|
-
className: "word-break"
|
|
11951
|
-
}, key))
|
|
11952
|
-
}, i))
|
|
11953
|
-
})
|
|
11954
|
-
});
|
|
11955
|
-
}
|
|
11956
|
-
//#endregion
|
|
11957
12230
|
//#region src/components/EntityInspector.tsx
|
|
11958
|
-
var EntityHistoryView = lazyChunk(() => import("./history-
|
|
12231
|
+
var EntityHistoryView = lazyChunk(() => import("./history-C6kUDQ3f.js").then((m) => ({ default: m.EntityHistoryView })));
|
|
12232
|
+
/**
|
|
12233
|
+
* The JSON tab pulls `prism-react-renderer` — 85 kB, and it was EAGER.
|
|
12234
|
+
*
|
|
12235
|
+
* A static import here put the syntax highlighter in the set a browser
|
|
12236
|
+
* downloads before the login screen paints, for a preview that cannot appear
|
|
12237
|
+
* until somebody has signed in, opened a record, and chosen this tab. Its
|
|
12238
|
+
* neighbour `EntityHistoryView` is already loaded this way, behind the same
|
|
12239
|
+
* `Suspense`, for the same reason.
|
|
12240
|
+
*/
|
|
12241
|
+
var JsonPreviewBinding = lazyChunk(() => import("./JsonPreviewBinding-v0w3H0fp.js").then((m) => ({ default: m.JsonPreviewBinding })));
|
|
11959
12242
|
/**
|
|
11960
12243
|
* Raw values and revision history, as an inspector rather than as tabs.
|
|
11961
12244
|
*
|
|
@@ -12017,7 +12300,10 @@ function EntityInspector({ tab, onTabChange, onClose, collection, entity, formCo
|
|
|
12017
12300
|
]
|
|
12018
12301
|
}), /* @__PURE__ */ jsx("div", {
|
|
12019
12302
|
className: "flex-1 min-h-0 overflow-auto",
|
|
12020
|
-
children: /* @__PURE__ */ jsxs(ErrorBoundary, { children: [tab === "json" && /* @__PURE__ */ jsx(
|
|
12303
|
+
children: /* @__PURE__ */ jsxs(ErrorBoundary, { children: [tab === "json" && /* @__PURE__ */ jsx(Suspense, {
|
|
12304
|
+
fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
|
|
12305
|
+
children: /* @__PURE__ */ jsx(JsonPreviewBinding, { values: values ?? entity?.values ?? {} })
|
|
12306
|
+
}), tab === "history" && includeHistory && formContext && /* @__PURE__ */ jsx(Suspense, {
|
|
12021
12307
|
fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
|
|
12022
12308
|
children: /* @__PURE__ */ jsx(EntityHistoryView, {
|
|
12023
12309
|
collection,
|
|
@@ -14189,18 +14475,91 @@ function ViewModeToggle({ viewMode = "table", onViewModeChange, enabledViews = A
|
|
|
14189
14475
|
});
|
|
14190
14476
|
}
|
|
14191
14477
|
//#endregion
|
|
14478
|
+
//#region src/util/map_with_concurrency.ts
|
|
14479
|
+
/**
|
|
14480
|
+
* Run `task` over every item, at most `concurrency` at a time, and return the
|
|
14481
|
+
* results in the order the items were given.
|
|
14482
|
+
*
|
|
14483
|
+
* The bulk delete used to be `Promise.all(entities.map(performDelete))`. That is
|
|
14484
|
+
* fine for the handful of rows anyone could tick by hand, and it is the reason
|
|
14485
|
+
* a "select all matching" over a real collection could not simply be poured
|
|
14486
|
+
* into the existing dialog: twelve thousand entries in that array is twelve
|
|
14487
|
+
* thousand simultaneous requests, which the browser queues and the server takes
|
|
14488
|
+
* as a burst.
|
|
14489
|
+
*
|
|
14490
|
+
* Rejections are not swallowed and do not cancel: every task is run, and the
|
|
14491
|
+
* first rejection is thrown once they have all settled, so a bulk action cannot
|
|
14492
|
+
* half-apply and then report the failure as if nothing had happened.
|
|
14493
|
+
*/
|
|
14494
|
+
async function mapWithConcurrency(items, concurrency, task, onSettled) {
|
|
14495
|
+
const results = new Array(items.length);
|
|
14496
|
+
const limit = Math.max(1, Math.floor(concurrency));
|
|
14497
|
+
let next = 0;
|
|
14498
|
+
let completed = 0;
|
|
14499
|
+
let firstError;
|
|
14500
|
+
let failed = false;
|
|
14501
|
+
async function worker() {
|
|
14502
|
+
for (;;) {
|
|
14503
|
+
const index = next++;
|
|
14504
|
+
if (index >= items.length) return;
|
|
14505
|
+
try {
|
|
14506
|
+
results[index] = await task(items[index], index);
|
|
14507
|
+
} catch (e) {
|
|
14508
|
+
if (!failed) {
|
|
14509
|
+
failed = true;
|
|
14510
|
+
firstError = e;
|
|
14511
|
+
}
|
|
14512
|
+
}
|
|
14513
|
+
onSettled?.(++completed, items.length);
|
|
14514
|
+
}
|
|
14515
|
+
}
|
|
14516
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
14517
|
+
if (failed) throw firstError;
|
|
14518
|
+
return results;
|
|
14519
|
+
}
|
|
14520
|
+
//#endregion
|
|
14192
14521
|
//#region src/components/DeleteEntityDialog.tsx
|
|
14193
|
-
|
|
14194
|
-
|
|
14522
|
+
/**
|
|
14523
|
+
* Deletes in flight at once.
|
|
14524
|
+
*
|
|
14525
|
+
* The bulk delete used to be `Promise.all(entities.map(performDelete))`, which
|
|
14526
|
+
* is unremarkable for the handful of rows anyone could tick by hand and
|
|
14527
|
+
* untenable the moment "select all 12,480 matching" can reach it.
|
|
14528
|
+
*/
|
|
14529
|
+
var DELETE_CONCURRENCY = 8;
|
|
14530
|
+
function isSelection(target) {
|
|
14531
|
+
return target.type === "entities" || target.type === "query";
|
|
14532
|
+
}
|
|
14533
|
+
function DeleteEntityDialog({ target, collection, onClose, open, onEntityDelete, onMultipleEntitiesDelete, path, variant = "delete" }) {
|
|
14195
14534
|
const dataClient = useData();
|
|
14196
|
-
useCustomizationController();
|
|
14197
14535
|
const snackbarController = useSnackbarController();
|
|
14198
14536
|
const { t } = useTranslation();
|
|
14199
14537
|
const [loading, setLoading] = useState(false);
|
|
14538
|
+
const [progress, setProgress] = useState(void 0);
|
|
14539
|
+
const cancelledRef = useRef({ aborted: false });
|
|
14200
14540
|
const context = useAdminContext();
|
|
14201
|
-
const
|
|
14202
|
-
|
|
14541
|
+
const { singleEntity, selection } = useMemo(() => {
|
|
14542
|
+
if (!target) return {
|
|
14543
|
+
singleEntity: void 0,
|
|
14544
|
+
selection: void 0
|
|
14545
|
+
};
|
|
14546
|
+
if (!isSelection(target)) return {
|
|
14547
|
+
singleEntity: target,
|
|
14548
|
+
selection: void 0
|
|
14549
|
+
};
|
|
14550
|
+
if (target.type === "entities" && target.entities.length === 1) return {
|
|
14551
|
+
singleEntity: target.entities[0],
|
|
14552
|
+
selection: void 0
|
|
14553
|
+
};
|
|
14554
|
+
return {
|
|
14555
|
+
singleEntity: void 0,
|
|
14556
|
+
selection: target
|
|
14557
|
+
};
|
|
14558
|
+
}, [target]);
|
|
14559
|
+
/** How many rows this will delete; `undefined` when only the server knows. */
|
|
14560
|
+
const selectionCount = selection === void 0 ? 1 : selection.type === "entities" ? selection.entities.length : selection.count === void 0 ? void 0 : Math.max(0, selection.count - selection.excluded.length);
|
|
14203
14561
|
const handleCancel = useCallback(() => {
|
|
14562
|
+
cancelledRef.current.aborted = true;
|
|
14204
14563
|
onClose();
|
|
14205
14564
|
}, [onClose]);
|
|
14206
14565
|
const onDeleteSuccess = useCallback((entity) => {
|
|
@@ -14230,70 +14589,104 @@ function DeleteEntityDialog({ entityOrEntitiesToDelete, collection, onClose, ope
|
|
|
14230
14589
|
context
|
|
14231
14590
|
]);
|
|
14232
14591
|
const handleOk = useCallback(async () => {
|
|
14233
|
-
if (
|
|
14592
|
+
if (singleEntity) {
|
|
14234
14593
|
setLoading(true);
|
|
14235
|
-
|
|
14236
|
-
|
|
14237
|
-
|
|
14238
|
-
|
|
14594
|
+
const success = await performDelete(singleEntity);
|
|
14595
|
+
setLoading(false);
|
|
14596
|
+
if (success) {
|
|
14597
|
+
onEntityDelete?.(path, singleEntity);
|
|
14598
|
+
snackbarController.open({
|
|
14239
14599
|
type: "success",
|
|
14240
|
-
message: t("
|
|
14241
|
-
});
|
|
14242
|
-
else if (results.some(Boolean)) snackbarController.open({
|
|
14243
|
-
type: "warning",
|
|
14244
|
-
message: t("some_entities_deleted", { collection: collection.name })
|
|
14245
|
-
});
|
|
14246
|
-
else snackbarController.open({
|
|
14247
|
-
type: "error",
|
|
14248
|
-
message: t("error_deleting_entities", { collection: collection.name })
|
|
14600
|
+
message: t("deleted", { name: collection.singularName ?? collection.name })
|
|
14249
14601
|
});
|
|
14250
14602
|
onClose();
|
|
14603
|
+
}
|
|
14604
|
+
return;
|
|
14605
|
+
}
|
|
14606
|
+
if (!selection) return;
|
|
14607
|
+
setLoading(true);
|
|
14608
|
+
cancelledRef.current = { aborted: false };
|
|
14609
|
+
let entities;
|
|
14610
|
+
try {
|
|
14611
|
+
entities = await resolveSelection({
|
|
14612
|
+
selection,
|
|
14613
|
+
accessor: dataClient.collection(path),
|
|
14614
|
+
signal: cancelledRef.current,
|
|
14615
|
+
onProgress: (loaded, total) => setProgress(total === void 0 ? t("selection_reading_rows_unknown", { loaded: loaded.toLocaleString() }) ?? `Reading rows… ${loaded}` : t("selection_reading_rows", {
|
|
14616
|
+
loaded: loaded.toLocaleString(),
|
|
14617
|
+
total: total.toLocaleString()
|
|
14618
|
+
}) ?? `Reading rows… ${loaded} of ${total}`)
|
|
14251
14619
|
});
|
|
14252
|
-
|
|
14253
|
-
|
|
14254
|
-
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
});
|
|
14260
|
-
onClose();
|
|
14261
|
-
}
|
|
14620
|
+
} catch (e) {
|
|
14621
|
+
setLoading(false);
|
|
14622
|
+
setProgress(void 0);
|
|
14623
|
+
snackbarController.open({
|
|
14624
|
+
type: "error",
|
|
14625
|
+
title: t("error_deleting_entities", { collection: collection.name }),
|
|
14626
|
+
message: e?.message
|
|
14262
14627
|
});
|
|
14628
|
+
return;
|
|
14263
14629
|
}
|
|
14630
|
+
if (cancelledRef.current.aborted) {
|
|
14631
|
+
setLoading(false);
|
|
14632
|
+
setProgress(void 0);
|
|
14633
|
+
return;
|
|
14634
|
+
}
|
|
14635
|
+
let results = [];
|
|
14636
|
+
try {
|
|
14637
|
+
results = await mapWithConcurrency(entities, DELETE_CONCURRENCY, (entity) => cancelledRef.current.aborted ? Promise.resolve(false) : performDelete(entity), (completed, total) => setProgress(t("selection_deleting_progress", {
|
|
14638
|
+
completed: completed.toLocaleString(),
|
|
14639
|
+
total: total.toLocaleString()
|
|
14640
|
+
}) ?? `Deleted ${completed} of ${total}`));
|
|
14641
|
+
} catch (e) {
|
|
14642
|
+
console.error("Error deleting entities", e);
|
|
14643
|
+
}
|
|
14644
|
+
setLoading(false);
|
|
14645
|
+
setProgress(void 0);
|
|
14646
|
+
onMultipleEntitiesDelete?.(path, entities.filter((_, i) => results[i]));
|
|
14647
|
+
const deleted = results.filter(Boolean).length;
|
|
14648
|
+
if (deleted === entities.length && entities.length > 0) snackbarController.open({
|
|
14649
|
+
type: "success",
|
|
14650
|
+
message: t("multiple_deleted", { collection: collection.name })
|
|
14651
|
+
});
|
|
14652
|
+
else if (deleted > 0) snackbarController.open({
|
|
14653
|
+
type: "warning",
|
|
14654
|
+
message: t("some_entities_deleted", { collection: collection.name })
|
|
14655
|
+
});
|
|
14656
|
+
else snackbarController.open({
|
|
14657
|
+
type: "error",
|
|
14658
|
+
message: t("error_deleting_entities", { collection: collection.name })
|
|
14659
|
+
});
|
|
14660
|
+
onClose();
|
|
14264
14661
|
}, [
|
|
14265
|
-
|
|
14266
|
-
|
|
14662
|
+
singleEntity,
|
|
14663
|
+
selection,
|
|
14267
14664
|
performDelete,
|
|
14268
|
-
|
|
14665
|
+
dataClient,
|
|
14269
14666
|
path,
|
|
14667
|
+
onEntityDelete,
|
|
14668
|
+
onMultipleEntitiesDelete,
|
|
14270
14669
|
onClose,
|
|
14271
14670
|
snackbarController,
|
|
14272
14671
|
collection.name,
|
|
14273
|
-
|
|
14672
|
+
collection.singularName,
|
|
14673
|
+
t
|
|
14274
14674
|
]);
|
|
14275
|
-
let content;
|
|
14276
|
-
if (entityOrEntities && multipleEntities) content = /* @__PURE__ */ jsx(Fragment, { children: t("multiple_entities") });
|
|
14277
|
-
else {
|
|
14278
|
-
const entity = entityOrEntities;
|
|
14279
|
-
content = entity ? /* @__PURE__ */ jsx(EntityViewBinding, {
|
|
14280
|
-
entity,
|
|
14281
|
-
collection,
|
|
14282
|
-
path
|
|
14283
|
-
}) : /* @__PURE__ */ jsx(Fragment, {});
|
|
14284
|
-
}
|
|
14285
14675
|
const entityName = collection.singularName ?? collection.name;
|
|
14286
|
-
|
|
14676
|
+
let dialogTitle;
|
|
14677
|
+
if (singleEntity) dialogTitle = variant === "unlink" ? t("unlink_entity_confirm_title", { entityName }) ?? `Remove this ${entityName} from this record?` : t("delete_entity_confirm_title", { entityName });
|
|
14678
|
+
else if (variant === "unlink") dialogTitle = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
14287
14679
|
/* @__PURE__ */ jsx("b", { children: collection.name }),
|
|
14288
14680
|
": ",
|
|
14289
14681
|
t("confirm_multiple_unlink") ?? "Remove these from this record?"
|
|
14290
|
-
] })
|
|
14291
|
-
|
|
14292
|
-
|
|
14293
|
-
|
|
14294
|
-
|
|
14682
|
+
] });
|
|
14683
|
+
else if (selectionCount === void 0) dialogTitle = t("confirm_delete_selection_unknown") ?? "Delete every row matching the current filter?";
|
|
14684
|
+
else dialogTitle = t("confirm_delete_selection", {
|
|
14685
|
+
total: selectionCount.toLocaleString(),
|
|
14686
|
+
collection: collection.name
|
|
14687
|
+
}) ?? `Delete ${selectionCount.toLocaleString()} ${collection.name}?`;
|
|
14295
14688
|
return /* @__PURE__ */ jsxs(Dialog, {
|
|
14296
|
-
maxWidth:
|
|
14689
|
+
maxWidth: singleEntity ? "2xl" : "lg",
|
|
14297
14690
|
"aria-labelledby": "delete-dialog",
|
|
14298
14691
|
open,
|
|
14299
14692
|
onOpenChange: (open) => !open ? onClose() : void 0,
|
|
@@ -14304,23 +14697,42 @@ function DeleteEntityDialog({ entityOrEntitiesToDelete, collection, onClose, ope
|
|
|
14304
14697
|
}),
|
|
14305
14698
|
/* @__PURE__ */ jsxs(DialogContent, {
|
|
14306
14699
|
fullHeight: true,
|
|
14307
|
-
children: [
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14700
|
+
children: [
|
|
14701
|
+
variant === "unlink" && /* @__PURE__ */ jsx("div", {
|
|
14702
|
+
className: "px-4 pt-4",
|
|
14703
|
+
children: /* @__PURE__ */ jsx(Typography, {
|
|
14704
|
+
variant: "body2",
|
|
14705
|
+
color: "secondary",
|
|
14706
|
+
children: t("unlink_entity_confirm_body", { collectionName: collection.name }) ?? `It stays in ${collection.name} and remains available to other records.`
|
|
14707
|
+
})
|
|
14708
|
+
}),
|
|
14709
|
+
singleEntity && /* @__PURE__ */ jsx("div", {
|
|
14710
|
+
className: "p-4",
|
|
14711
|
+
children: /* @__PURE__ */ jsx(EntityViewBinding, {
|
|
14712
|
+
entity: singleEntity,
|
|
14713
|
+
collection,
|
|
14714
|
+
path
|
|
14715
|
+
})
|
|
14716
|
+
}),
|
|
14717
|
+
!singleEntity && variant === "delete" && /* @__PURE__ */ jsx("div", {
|
|
14718
|
+
className: "px-4 pt-4",
|
|
14719
|
+
children: /* @__PURE__ */ jsx(Typography, {
|
|
14720
|
+
variant: "body2",
|
|
14721
|
+
color: "secondary",
|
|
14722
|
+
children: t("confirm_delete_selection_body") ?? "This cannot be undone. Each row is deleted individually, so this may take a while."
|
|
14723
|
+
})
|
|
14313
14724
|
})
|
|
14314
|
-
|
|
14315
|
-
className: "p-4",
|
|
14316
|
-
children: content
|
|
14317
|
-
})]
|
|
14725
|
+
]
|
|
14318
14726
|
}),
|
|
14319
14727
|
/* @__PURE__ */ jsxs(DialogActions, { children: [
|
|
14320
14728
|
loading && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }),
|
|
14729
|
+
progress && /* @__PURE__ */ jsx(Typography, {
|
|
14730
|
+
variant: "caption",
|
|
14731
|
+
color: "secondary",
|
|
14732
|
+
children: progress
|
|
14733
|
+
}),
|
|
14321
14734
|
/* @__PURE__ */ jsx(Button, {
|
|
14322
14735
|
onClick: handleCancel,
|
|
14323
|
-
disabled: loading,
|
|
14324
14736
|
variant: "text",
|
|
14325
14737
|
children: t("cancel")
|
|
14326
14738
|
}),
|
|
@@ -14549,14 +14961,14 @@ function buildRemoveEntityAction({ key, name, icon, variant }) {
|
|
|
14549
14961
|
Component: ({ open }) => {
|
|
14550
14962
|
if (!collection || !path) throw new Error(`${key}EntityAction: Collection is undefined`);
|
|
14551
14963
|
return /* @__PURE__ */ jsx(DeleteEntityDialog, {
|
|
14552
|
-
|
|
14964
|
+
target: entity,
|
|
14553
14965
|
path,
|
|
14554
14966
|
collection,
|
|
14555
14967
|
variant,
|
|
14556
14968
|
open,
|
|
14557
14969
|
onEntityDelete: () => {
|
|
14558
14970
|
context?.analyticsController?.onAnalyticsEvent?.("single_entity_deleted", { path });
|
|
14559
|
-
selectionController?.
|
|
14971
|
+
selectionController?.toggleEntitySelection(entity, false);
|
|
14560
14972
|
onCollectionChange?.();
|
|
14561
14973
|
if (openEntityMode === "full_screen" && context?.urlController) {
|
|
14562
14974
|
const collectionUrl = context.urlController.buildUrlCollectionPath(path);
|
|
@@ -15683,7 +16095,7 @@ function SortButton({ tableController, properties, compact }) {
|
|
|
15683
16095
|
}
|
|
15684
16096
|
//#endregion
|
|
15685
16097
|
//#region src/components/CollectionViewBinding/CollectionViewStartActions.tsx
|
|
15686
|
-
function CollectionViewStartActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, path, selectionController, tableController, collectionEntitiesCount, entitiesCount, resolvedProperties, viewMode, compact, openNewDocument }) {
|
|
16098
|
+
function CollectionViewStartActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, path, selectionController, tableController, collectionEntitiesCount, entitiesCount, resolvedProperties, viewMode, selectionEnabled, selectionQuery, compact, openNewDocument }) {
|
|
15687
16099
|
const context = useAdminContext();
|
|
15688
16100
|
const largeLayout = useLargeLayout();
|
|
15689
16101
|
const { t } = useTranslation();
|
|
@@ -15764,6 +16176,16 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
|
|
|
15764
16176
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
15765
16177
|
[
|
|
15766
16178
|
backButton,
|
|
16179
|
+
selectionEnabled && selectionQuery ? /* @__PURE__ */ jsx("div", {
|
|
16180
|
+
className: "mr-1 flex items-center",
|
|
16181
|
+
children: /* @__PURE__ */ jsx(SelectionMenu, {
|
|
16182
|
+
selectionController,
|
|
16183
|
+
loadedEntities: tableController.data,
|
|
16184
|
+
query: selectionQuery,
|
|
16185
|
+
collectionEntitiesCount,
|
|
16186
|
+
collectionName: collection.name
|
|
16187
|
+
})
|
|
16188
|
+
}, "select_all") : null,
|
|
15767
16189
|
filtersButton,
|
|
15768
16190
|
sortButton,
|
|
15769
16191
|
/* @__PURE__ */ jsx(ClearFilterSortButton, {
|
|
@@ -15869,12 +16291,15 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
15869
16291
|
const linkedTargetSlug = isLinkedChildView && childViewSource.kind === "relation" ? childViewSource.targetSlug : void 0;
|
|
15870
16292
|
const containerRef = React.useRef(null);
|
|
15871
16293
|
const scrollRestoration = useScrollRestoration();
|
|
16294
|
+
const collectionPropRef = React.useRef(collectionProp);
|
|
16295
|
+
if (!shallowEqual(collectionPropRef.current, collectionProp)) collectionPropRef.current = collectionProp;
|
|
16296
|
+
const stableCollectionProp = collectionPropRef.current;
|
|
15872
16297
|
const collection = useMemo(() => {
|
|
15873
|
-
const registryCollection = collectionRegistry.getCollection(path) ||
|
|
16298
|
+
const registryCollection = collectionRegistry.getCollection(path) || stableCollectionProp;
|
|
15874
16299
|
const userOverride = userConfigPersistence?.getCollectionConfig(path);
|
|
15875
16300
|
return userOverride ? mergeDeep(registryCollection, userOverride) : registryCollection;
|
|
15876
16301
|
}, [
|
|
15877
|
-
|
|
16302
|
+
stableCollectionProp,
|
|
15878
16303
|
path,
|
|
15879
16304
|
userConfigPersistence,
|
|
15880
16305
|
collectionRegistry
|
|
@@ -15891,7 +16316,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
15891
16316
|
const [docsCount, setDocsCount] = useState(null);
|
|
15892
16317
|
const [localPropertiesOrder, setLocalPropertiesOrder] = useState(collection.propertiesOrder);
|
|
15893
16318
|
useEffect(() => {
|
|
15894
|
-
setLocalPropertiesOrder(collection.propertiesOrder);
|
|
16319
|
+
setLocalPropertiesOrder((previous) => deepEqual(previous, collection.propertiesOrder) ? previous : collection.propertiesOrder);
|
|
15895
16320
|
}, [collection.propertiesOrder]);
|
|
15896
16321
|
const unselectNavigatedEntity = useCallback(() => {
|
|
15897
16322
|
const currentSelection = highlightedEntity;
|
|
@@ -15968,7 +16393,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
15968
16393
|
const [customViewSize, setCustomViewSize] = useState(collection.defaultSize ?? "m");
|
|
15969
16394
|
const selectionController = useSelectionController();
|
|
15970
16395
|
const usedSelectionController = collection.selectionController ?? selectionController;
|
|
15971
|
-
const {
|
|
16396
|
+
const { selection, clearSelection, toggleEntitySelection } = usedSelectionController;
|
|
15972
16397
|
const tableController = useDataTableController({
|
|
15973
16398
|
path,
|
|
15974
16399
|
collection,
|
|
@@ -16083,17 +16508,17 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16083
16508
|
]);
|
|
16084
16509
|
const onMultipleDeleteClick = () => {
|
|
16085
16510
|
analyticsController.onAnalyticsEvent?.("multiple_delete_dialog_open", { path });
|
|
16086
|
-
setDeleteEntityClicked(
|
|
16511
|
+
setDeleteEntityClicked(selection);
|
|
16087
16512
|
};
|
|
16088
16513
|
const internalOnEntityDelete = (_path, entity) => {
|
|
16089
16514
|
analyticsController.onAnalyticsEvent?.("single_entity_deleted", { path });
|
|
16090
|
-
|
|
16515
|
+
toggleEntitySelection(entity, false);
|
|
16091
16516
|
setDeletedEntities((prev) => [...prev, entity]);
|
|
16092
16517
|
setLastDeleteTimestamp(Date.now());
|
|
16093
16518
|
};
|
|
16094
16519
|
const internalOnMultipleEntitiesDelete = (_path, entities) => {
|
|
16095
16520
|
analyticsController.onAnalyticsEvent?.("multiple_entities_deleted", { path });
|
|
16096
|
-
|
|
16521
|
+
clearSelection();
|
|
16097
16522
|
setDeleteEntityClicked(void 0);
|
|
16098
16523
|
setDeletedEntities((prev) => [...prev, ...entities]);
|
|
16099
16524
|
setLastDeleteTimestamp(Date.now());
|
|
@@ -16321,7 +16746,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16321
16746
|
return (largeLayout ? 80 + actionsWidth : 70 + actionsWidth) + (collapsedActions.length > 0 ? largeLayout ? 40 : 30 : 0);
|
|
16322
16747
|
}, [getActionsForEntity, largeLayout]);
|
|
16323
16748
|
const tableRowActionsBuilder = useCallback(({ entity, size, width, frozen }) => {
|
|
16324
|
-
const isSelected =
|
|
16749
|
+
const isSelected = usedSelectionController.isEntitySelected(entity);
|
|
16325
16750
|
const customEntityActions = (collection.entityActions ?? EMPTY_ARRAY).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
|
|
16326
16751
|
const actions = getActionsForEntity({
|
|
16327
16752
|
entity,
|
|
@@ -16353,6 +16778,26 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16353
16778
|
searchString: tableController.searchString,
|
|
16354
16779
|
onCountChange: setDocsCount
|
|
16355
16780
|
});
|
|
16781
|
+
const liveSelectionQuery = useMemo(() => ({
|
|
16782
|
+
path,
|
|
16783
|
+
filterValues: tableController.filterValues,
|
|
16784
|
+
searchString: tableController.searchString,
|
|
16785
|
+
sortBy: tableController.sortBy
|
|
16786
|
+
}), [
|
|
16787
|
+
path,
|
|
16788
|
+
tableController.filterValues,
|
|
16789
|
+
tableController.searchString,
|
|
16790
|
+
tableController.sortBy
|
|
16791
|
+
]);
|
|
16792
|
+
const selectionQueryKey = selection.type === "query" ? serializeSelectionQuery(selection.query) : void 0;
|
|
16793
|
+
const liveQueryKey = serializeSelectionQuery(liveSelectionQuery);
|
|
16794
|
+
useEffect(() => {
|
|
16795
|
+
if (selectionQueryKey !== void 0 && selectionQueryKey !== liveQueryKey) clearSelection();
|
|
16796
|
+
}, [
|
|
16797
|
+
selectionQueryKey,
|
|
16798
|
+
liveQueryKey,
|
|
16799
|
+
clearSelection
|
|
16800
|
+
]);
|
|
16356
16801
|
const { resolvedSlots } = customizationController;
|
|
16357
16802
|
const headerActionContributions = useMemo(() => resolvedSlots.filter((s) => s.slot === "collection.header.action").sort((a, b) => (a.order ?? 50) - (b.order ?? 50)), [resolvedSlots]);
|
|
16358
16803
|
const buildAdditionalHeaderWidget = useCallback(({ property, propertyKey, onHover }) => {
|
|
@@ -16460,6 +16905,8 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16460
16905
|
collectionEntitiesCount: docsCount ?? void 0,
|
|
16461
16906
|
resolvedProperties: resolvedCollection.properties,
|
|
16462
16907
|
viewMode,
|
|
16908
|
+
selectionEnabled: activeSelectionEnabled,
|
|
16909
|
+
selectionQuery: liveSelectionQuery,
|
|
16463
16910
|
entitiesCount: docsCount,
|
|
16464
16911
|
openNewDocument,
|
|
16465
16912
|
compact: isCompact
|
|
@@ -16654,7 +17101,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16654
17101
|
container: containerRef.current
|
|
16655
17102
|
}, `popup_form_${popupCell?.propertyKey}_${popupCell?.entityId}`),
|
|
16656
17103
|
deleteEntityClicked && /* @__PURE__ */ jsx(DeleteEntityDialog, {
|
|
16657
|
-
|
|
17104
|
+
target: deleteEntityClicked,
|
|
16658
17105
|
path,
|
|
16659
17106
|
collection,
|
|
16660
17107
|
open: Boolean(deleteEntityClicked),
|
|
@@ -17404,13 +17851,26 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
17404
17851
|
const mainViewVisible = activeTab === "__main_##Q$SC^#S6" || Boolean(selectedSecondaryForm);
|
|
17405
17852
|
const mountedTabsRef = useRef(/* @__PURE__ */ new Set());
|
|
17406
17853
|
if (activeTab) mountedTabsRef.current.add(activeTab);
|
|
17854
|
+
/**
|
|
17855
|
+
* The record as a context with no form behind it: what a view is handed
|
|
17856
|
+
* where nothing can edit the record — the read-only rendering for a user
|
|
17857
|
+
* without permission, and the entity-view tabs until the form has mounted
|
|
17858
|
+
* and published its own. Its writes throw, so it says `disabled`: that is
|
|
17859
|
+
* the flag every control already honours, and a stand-in reporting
|
|
17860
|
+
* `disabled: false` invited the very write it was about to refuse.
|
|
17861
|
+
*
|
|
17862
|
+
* Not dropped once the form's context arrives. The form mounts even where
|
|
17863
|
+
* it is hidden — for a user who cannot edit, the read-only view is shown
|
|
17864
|
+
* beside a hidden form — so its context always arrives, and the read-only
|
|
17865
|
+
* view lost its context the moment it did: a `formView` flipped to the
|
|
17866
|
+
* default rendering, and `additionalFields` vanished from the default one.
|
|
17867
|
+
*/
|
|
17407
17868
|
const readOnlyFormContext = useMemo(() => {
|
|
17408
|
-
if (formContext) return void 0;
|
|
17409
17869
|
if (!entityId) return void 0;
|
|
17410
17870
|
const formexStub = createFormexStub(usedEntity?.values ?? {});
|
|
17411
17871
|
return {
|
|
17412
17872
|
entityId,
|
|
17413
|
-
disabled:
|
|
17873
|
+
disabled: true,
|
|
17414
17874
|
readOnly: true,
|
|
17415
17875
|
openEntityMode: layout,
|
|
17416
17876
|
status,
|
|
@@ -17431,7 +17891,6 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
17431
17891
|
formex: formexStub
|
|
17432
17892
|
};
|
|
17433
17893
|
}, [
|
|
17434
|
-
formContext,
|
|
17435
17894
|
entityId,
|
|
17436
17895
|
layout,
|
|
17437
17896
|
status,
|
|
@@ -17594,8 +18053,32 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
17594
18053
|
collection
|
|
17595
18054
|
]);
|
|
17596
18055
|
const formViewConfig = collection.formView;
|
|
17597
|
-
const FormViewBuilder =
|
|
17598
|
-
formViewConfig?.includeActions;
|
|
18056
|
+
const FormViewBuilder = resolveComponentRef(formViewConfig?.Builder);
|
|
18057
|
+
const formViewIncludeActions = formViewConfig?.includeActions !== false;
|
|
18058
|
+
/**
|
|
18059
|
+
* What the record form renders in place of its generated fields.
|
|
18060
|
+
*
|
|
18061
|
+
* A `formView` Builder is rendered *by the form*, never instead of it. It
|
|
18062
|
+
* used to be drawn here in the form's place, and the form is the only
|
|
18063
|
+
* thing that can hand it a working context — the real one arrives through
|
|
18064
|
+
* `onFormContextReady`, and with no form mounted it never did. Every
|
|
18065
|
+
* Builder got the read-only stand-in above: each keystroke threw inside
|
|
18066
|
+
* `setFieldValue`, and Save and Discard, which wait on that same context,
|
|
18067
|
+
* never appeared. A new record was worse: with no id there is not even a
|
|
18068
|
+
* stand-in, and the screen stayed blank.
|
|
18069
|
+
*
|
|
18070
|
+
* An entity view declared with `includeActions` is a second form over the
|
|
18071
|
+
* same record, and takes the place while its tab is open.
|
|
18072
|
+
*/
|
|
18073
|
+
const FormBuilder = selectedSecondaryForm ? resolveComponentRef(selectedSecondaryForm.Builder) : FormViewBuilder;
|
|
18074
|
+
/**
|
|
18075
|
+
* `formView.includeActions: false` leaves saving to the Builder — it holds
|
|
18076
|
+
* `formContext.submit` and `save` — so the bar offers no Save or Discard of
|
|
18077
|
+
* its own. Only for the collection's form: an entity view that declared
|
|
18078
|
+
* `includeActions` is on screen because it asked for them. Delete is a
|
|
18079
|
+
* record action, in the bar's overflow menu, and is not affected.
|
|
18080
|
+
*/
|
|
18081
|
+
const formActionsContext = canEdit && formContext && (!FormViewBuilder || formViewIncludeActions || selectedSecondaryForm) ? formContext : void 0;
|
|
17599
18082
|
const entityReadOnlyView = !canEdit && entity ? FormViewBuilder && readOnlyFormContext ? /* @__PURE__ */ jsx("div", {
|
|
17600
18083
|
className: cls("@container/form flex-1 flex flex-row w-full overflow-y-auto justify-center", canEdit || !mainViewVisible || selectedSecondaryForm ? "hidden" : ""),
|
|
17601
18084
|
children: /* @__PURE__ */ jsx("div", {
|
|
@@ -17621,20 +18104,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
17621
18104
|
openEntityMode: layout,
|
|
17622
18105
|
formContext: readOnlyFormContext
|
|
17623
18106
|
}) : null;
|
|
17624
|
-
const entityView =
|
|
17625
|
-
className: cls("relative flex-1 w-full h-full overflow-auto", (!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : ""),
|
|
17626
|
-
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(Suspense, {
|
|
17627
|
-
fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
|
|
17628
|
-
children: (formContext ?? readOnlyFormContext) && /* @__PURE__ */ jsx(FormViewBuilder, {
|
|
17629
|
-
collection,
|
|
17630
|
-
parentCollectionSlugs,
|
|
17631
|
-
parentEntityIds,
|
|
17632
|
-
entity: usedEntity,
|
|
17633
|
-
modifiedValues: (formContext ?? readOnlyFormContext)?.formex?.values ?? usedEntity?.values,
|
|
17634
|
-
formContext: formContext ?? readOnlyFormContext
|
|
17635
|
-
})
|
|
17636
|
-
}) })
|
|
17637
|
-
}) : /* @__PURE__ */ jsx(ResolvedEntityForm, {
|
|
18107
|
+
const entityView = /* @__PURE__ */ jsx(ResolvedEntityForm, {
|
|
17638
18108
|
collection,
|
|
17639
18109
|
path,
|
|
17640
18110
|
entityId: entityId ?? usedEntity?.id,
|
|
@@ -17674,7 +18144,9 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
17674
18144
|
setTimeout(() => onCloseRequest?.(), 0);
|
|
17675
18145
|
}
|
|
17676
18146
|
},
|
|
17677
|
-
Builder:
|
|
18147
|
+
Builder: FormBuilder,
|
|
18148
|
+
parentCollectionSlugs,
|
|
18149
|
+
parentEntityIds
|
|
17678
18150
|
});
|
|
17679
18151
|
const subcollectionTabs = subcollections && subcollections.map((subcollection) => {
|
|
17680
18152
|
const icon = getIcon(subcollection.icon, void 0, void 0, "smallest");
|
|
@@ -17736,25 +18208,25 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
17736
18208
|
hasErrors: hasFormErrors,
|
|
17737
18209
|
saveDisabled: !canEdit || saveDisabled,
|
|
17738
18210
|
onBack: layout === "full_screen" && !onShowList ? () => navigate(withListState(urlController.buildUrlCollectionPath(path))) : void 0,
|
|
17739
|
-
onSave:
|
|
18211
|
+
onSave: formActionsContext ? () => {
|
|
17740
18212
|
sideDialogContext.setPendingClose?.(false);
|
|
17741
18213
|
pendingCloseRef.current = false;
|
|
17742
|
-
|
|
18214
|
+
formActionsContext.submit();
|
|
17743
18215
|
} : void 0,
|
|
17744
|
-
onSaveAndClose:
|
|
18216
|
+
onSaveAndClose: formActionsContext && canCloseAfterSave ? () => {
|
|
17745
18217
|
if (layout === "split") {
|
|
17746
18218
|
pendingCloseRef.current = true;
|
|
17747
|
-
Promise.resolve(
|
|
18219
|
+
Promise.resolve(formActionsContext.submit()).finally(() => {
|
|
17748
18220
|
pendingCloseRef.current = false;
|
|
17749
18221
|
});
|
|
17750
18222
|
return;
|
|
17751
18223
|
}
|
|
17752
18224
|
sideDialogContext.setPendingClose?.(true);
|
|
17753
|
-
Promise.resolve(
|
|
18225
|
+
Promise.resolve(formActionsContext.submit()).finally(() => sideDialogContext.setPendingClose?.(false));
|
|
17754
18226
|
} : void 0,
|
|
17755
18227
|
saveAndClosePlacement: layout === "split" ? "menu" : "button",
|
|
17756
18228
|
onClose: onCloseRequest,
|
|
17757
|
-
onDiscard:
|
|
18229
|
+
onDiscard: formActionsContext ? () => discard(formActionsContext.formex, status) : void 0,
|
|
17758
18230
|
onInspect: includeJsonView ? () => setInspectorTab("json") : void 0,
|
|
17759
18231
|
onViewHistory: includeHistoryView ? () => setInspectorTab("history") : void 0,
|
|
17760
18232
|
externalLink: usedEntity ? customizationController?.entityLinkBuilder?.({ entity: usedEntity }) : void 0,
|
|
@@ -19611,6 +20083,8 @@ function SideDialogView({ offsetPosition, panel, panelIndex, isTopPanel }) {
|
|
|
19611
20083
|
}
|
|
19612
20084
|
//#endregion
|
|
19613
20085
|
//#region src/components/ReferenceTable/SelectionTableBinding.tsx
|
|
20086
|
+
/** Stable identity, so the effect that propagates the picks does not re-fire. */
|
|
20087
|
+
var EMPTY_ENTITIES = [];
|
|
19614
20088
|
/**
|
|
19615
20089
|
* This component allows to select entities from a given collection.
|
|
19616
20090
|
* You probably want to open this dialog as a side view using {@link useSelectionTableBinding}
|
|
@@ -19632,11 +20106,12 @@ function SelectionTableBindingInternal({ onSingleEntitySelected, onMultipleEntit
|
|
|
19632
20106
|
const dataClient = useData();
|
|
19633
20107
|
const [entitiesDisplayedFirst, setEntitiesDisplayedFirst] = useState([]);
|
|
19634
20108
|
const selectionController = useSelectionController();
|
|
20109
|
+
const selectedEntities = selectionController.selection.type === "entities" ? selectionController.selection.entities : EMPTY_ENTITIES;
|
|
19635
20110
|
const selectionInitializedRef = useRef(false);
|
|
19636
20111
|
useEffect(() => {
|
|
19637
20112
|
if (!selectionInitializedRef.current) return;
|
|
19638
|
-
if (onMultipleEntitiesSelected) onMultipleEntitiesSelected(
|
|
19639
|
-
}, [
|
|
20113
|
+
if (onMultipleEntitiesSelected) onMultipleEntitiesSelected(selectedEntities);
|
|
20114
|
+
}, [selectedEntities]);
|
|
19640
20115
|
/**
|
|
19641
20116
|
* Fetch initially selected ids
|
|
19642
20117
|
*/
|
|
@@ -19684,7 +20159,6 @@ function SelectionTableBindingInternal({ onSingleEntitySelected, onMultipleEntit
|
|
|
19684
20159
|
path,
|
|
19685
20160
|
entityId: entity.id
|
|
19686
20161
|
});
|
|
19687
|
-
const selectedEntities = selectionController.selectedEntities;
|
|
19688
20162
|
if (selectedEntities.map((e) => e.id).indexOf(entity.id) > -1) selectionController.setSelectedEntities(selectedEntities.filter((item) => item.id !== entity.id));
|
|
19689
20163
|
else {
|
|
19690
20164
|
if (maxSelection && selectedEntities.length >= maxSelection) return;
|
|
@@ -19698,6 +20172,7 @@ function SelectionTableBindingInternal({ onSingleEntitySelected, onMultipleEntit
|
|
|
19698
20172
|
path,
|
|
19699
20173
|
sideDialogContext,
|
|
19700
20174
|
selectionController,
|
|
20175
|
+
selectedEntities,
|
|
19701
20176
|
maxSelection
|
|
19702
20177
|
]);
|
|
19703
20178
|
const onNewClick = () => {
|
|
@@ -19714,13 +20189,12 @@ function SelectionTableBindingInternal({ onSingleEntitySelected, onMultipleEntit
|
|
|
19714
20189
|
});
|
|
19715
20190
|
};
|
|
19716
20191
|
const tableRowActionsBuilder = ({ entity, size, width, frozen }) => {
|
|
19717
|
-
const selectedEntities = selectionController.selectedEntities;
|
|
19718
20192
|
return /* @__PURE__ */ jsx(CollectionRowActions, {
|
|
19719
20193
|
width,
|
|
19720
20194
|
frozen,
|
|
19721
20195
|
entity,
|
|
19722
20196
|
size,
|
|
19723
|
-
isSelected:
|
|
20197
|
+
isSelected: selectionController.isEntitySelected(entity),
|
|
19724
20198
|
selectionEnabled: multiselect,
|
|
19725
20199
|
hideId: collection?.hideIdFromCollection,
|
|
19726
20200
|
path,
|
|
@@ -21222,7 +21696,7 @@ function StorageItemPreview({ name, property, value, onRemove, disabled, size, p
|
|
|
21222
21696
|
}
|
|
21223
21697
|
//#endregion
|
|
21224
21698
|
//#region src/form/field_bindings/StorageUploadFieldBinding.tsx
|
|
21225
|
-
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
|
|
21699
|
+
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";
|
|
21226
21700
|
var disabledClasses = fieldBackgroundDisabledMixin;
|
|
21227
21701
|
var nonActiveDropClasses = fieldBackgroundHoverMixin;
|
|
21228
21702
|
var activeDropClasses = "pt-0 border-2 border-solid";
|
|
@@ -21526,7 +22000,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
|
|
|
21526
22000
|
let url;
|
|
21527
22001
|
if (property.type === "string") {
|
|
21528
22002
|
multiline = property.admin?.multiline;
|
|
21529
|
-
url = property.admin?.urlPreview;
|
|
22003
|
+
url = property.admin?.urlPreview ?? property.url;
|
|
21530
22004
|
}
|
|
21531
22005
|
useClearRestoreValue({
|
|
21532
22006
|
property,
|
|
@@ -21552,7 +22026,7 @@ function TextFieldBinding({ propertyKey, value, setValue, error, showError, disa
|
|
|
21552
22026
|
if (property.type === "number") inputType = "number";
|
|
21553
22027
|
else if (property.type === "string") {
|
|
21554
22028
|
if (property.email) inputType = "email";
|
|
21555
|
-
else if (property.admin?.urlPreview) inputType = "url";
|
|
22029
|
+
else if (property.admin?.urlPreview || property.url) inputType = "url";
|
|
21556
22030
|
}
|
|
21557
22031
|
const label = hideLabel ? void 0 : /* @__PURE__ */ jsx(LabelWithIcon, {
|
|
21558
22032
|
icon: getIconForProperty(property, "small"),
|
|
@@ -21721,7 +22195,7 @@ function VectorFieldBinding({ propertyKey, value, setValue, error, showError, di
|
|
|
21721
22195
|
]
|
|
21722
22196
|
})]
|
|
21723
22197
|
}), showValues && isPopulated && /* @__PURE__ */ jsx("div", {
|
|
21724
|
-
className: "mt-1 p-3 rounded-lg bg-surface-well border border-hairline max-h-36 overflow-y-auto font-mono text-[11px] leading-relaxed text-text-secondary dark:text-text-secondary-dark break-all selection:bg-primary
|
|
22198
|
+
className: "mt-1 p-3 rounded-lg bg-surface-well border border-hairline max-h-36 overflow-y-auto font-mono text-[11px] leading-relaxed text-text-secondary dark:text-text-secondary-dark break-all selection:bg-primary/20",
|
|
21725
22199
|
children: arrayValue.join(", ")
|
|
21726
22200
|
})]
|
|
21727
22201
|
}) : /* @__PURE__ */ jsxs("div", {
|
|
@@ -23399,7 +23873,7 @@ function LocalChangesMenu({ cachedData, formex, onClearLocalChanges, cacheKey, p
|
|
|
23399
23873
|
*
|
|
23400
23874
|
* @group Components
|
|
23401
23875
|
*/
|
|
23402
|
-
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 }) {
|
|
23876
|
+
function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified, onIdChange, onSaved, entity, initialDirtyValues, onFormContextReady, initialStatus, className, onStatusChange, onEntityChange, openEntityMode = "full_screen", formex: formexProp, disabled: disabledProp, Builder, parentCollectionSlugs, parentEntityIds, 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 }) {
|
|
23403
23877
|
const navigateBack = useCallback(() => {
|
|
23404
23878
|
if (navigateBackProp) navigateBackProp();
|
|
23405
23879
|
}, [navigateBackProp]);
|
|
@@ -23547,6 +24021,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
23547
24021
|
});
|
|
23548
24022
|
};
|
|
23549
24023
|
const disabled = formex.isSubmitting || Boolean(disabledProp);
|
|
24024
|
+
const rebaseContext = useRebaseContext();
|
|
23550
24025
|
const formContext = {
|
|
23551
24026
|
setFieldValue: useCallback((key, value) => formex.setFieldValue(key, value), []),
|
|
23552
24027
|
values: formex.values,
|
|
@@ -23654,16 +24129,15 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
23654
24129
|
if (!additionalField || !entity) return null;
|
|
23655
24130
|
const AdditionalFieldBuilder = additionalField.Builder;
|
|
23656
24131
|
if (!AdditionalFieldBuilder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
|
|
23657
|
-
const additionalFieldContext = formContext;
|
|
23658
24132
|
const child = AdditionalFieldBuilder ? /* @__PURE__ */ jsx(AdditionalFieldBuilder, {
|
|
23659
24133
|
entity,
|
|
23660
|
-
context:
|
|
24134
|
+
context: rebaseContext
|
|
23661
24135
|
}) : /* @__PURE__ */ jsx("div", {
|
|
23662
24136
|
className: "w-full",
|
|
23663
24137
|
children: /* @__PURE__ */ jsx(AdditionalFieldValue, {
|
|
23664
24138
|
field: additionalField,
|
|
23665
24139
|
entity,
|
|
23666
|
-
context:
|
|
24140
|
+
context: rebaseContext
|
|
23667
24141
|
})
|
|
23668
24142
|
});
|
|
23669
24143
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -23712,11 +24186,16 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
23712
24186
|
}, `field_${field.key}`);
|
|
23713
24187
|
};
|
|
23714
24188
|
const formFields = () => {
|
|
23715
|
-
if (Builder) return /* @__PURE__ */ jsx(
|
|
23716
|
-
|
|
23717
|
-
|
|
23718
|
-
|
|
23719
|
-
|
|
24189
|
+
if (Builder) return /* @__PURE__ */ jsx(Suspense, {
|
|
24190
|
+
fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
|
|
24191
|
+
children: /* @__PURE__ */ jsx(Builder, {
|
|
24192
|
+
collection,
|
|
24193
|
+
parentCollectionSlugs,
|
|
24194
|
+
parentEntityIds,
|
|
24195
|
+
entity,
|
|
24196
|
+
modifiedValues: formex.values,
|
|
24197
|
+
formContext
|
|
24198
|
+
})
|
|
23720
24199
|
});
|
|
23721
24200
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FormSections, {
|
|
23722
24201
|
sections: showRail || !layout.sidebar.length ? layout.sections : [...layout.sections, {
|
|
@@ -25090,6 +25569,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
|
|
|
25090
25569
|
return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
|
|
25091
25570
|
}
|
|
25092
25571
|
//#endregion
|
|
25093
|
-
export { NAVIGATION_DEFAULT_GROUP_NAME as $,
|
|
25572
|
+
export { NAVIGATION_DEFAULT_GROUP_NAME as $, useSidePanel as $n, getCollectionBySlugWithin as $t, getFieldId as A, ReadOnlyFieldBinding as An, getIconForProperty as Ar, convertDataToEntity as At, MapFieldBinding as B, KeyValuePreview as Bn, detectCsvDelimiter as Bt, EntityFormBinding as C, NavigationStateContext as Cn, FieldBlock as Cr, EntityCardBinding as Ct, getDefaultFieldConfig as D, SelectableTableContext as Dn, PropertyIdCopyTooltip as Dr, useCollectionEditorDialogsState as Dt, DEFAULT_FIELD_CONFIGS as E, SideDialogsControllerContext as En, spanClass as Er, ConfigControllerProvider as Et, SelectFieldBinding as F, PropertyPreview as Fn, isReferenceProperty as Fr, ImportSaveInProgress as Ft, useSelectionDialog as G, ArrayEnumPreview as Gn, SearchIconsView as Gt, DateTimeFieldBinding as H, ArrayOneOfPreview as Hn, parseCsvToObjects as Ht, RepeatFieldBinding as I, UserPreview as In, isRelationProperty as Ir, IMPORT_BATCH_SIZE as It, useBuildUrlController as J, ArrayOfReferencesPreview as Jn, BreadcrumbsProvider as Jt, SelectionTableBinding as K, ArrayOfStorageComponentsPreview as Kn, FieldCaption as Kt, ReferenceFieldBinding as L, NumberPropertyPreview as Ln, saveImportedEntities as Lt, TextFieldBinding as M, LabelWithIcon as Mn, getPropertiesWithPropertiesOrder as Mr, processValueMapping as Mt, SwitchFieldBinding as N, FieldHelperText as Nn, getPropertyInPath$1 as Nr, getInferenceType as Nt, getDefaultFieldId as O, ArrayCustomShapedFieldBinding as On, getBracketNotation as Or, ImportNewPropertyFieldPreview as Ot, StorageUploadFieldBinding as P, ArrayOfMapsPreview as Pn, getResolvedPropertyInPath as Pr, useImportConfig as Pt, useResolvedCollections as Q, SidePanelControllerContext as Qn, addInitialSlash as Qt, MultiSelectFieldBinding as R, BooleanPreview as Rn, ImportFileUpload as Rt, isSchemaChangeCancelled as S, useUrlController as Sn, RecordMeta as Sr, CollectionCardViewBinding as St, PropertyFieldBinding as T, useSideDialogsController as Tn, isSelfLabellingProperty as Tr, useCollectionEditorController as Tt, BlockFieldBinding as U, ArrayOfStringsPreview as Un, ArrayContainer as Ut, KeyValueFieldBinding as V, MapPropertyPreview as Vn, parseCsvRows as Vt, ArrayOfReferencesFieldBinding as W, ArrayPropertyEnumPreview as Wn, PropertyConfigBadge as Wt, useTopLevelNavigation as X, ReferencePreview as Xn, resolveEntityView as Xt, useBuildNavigationStateController as Y, InlineEntityListPreview as Yn, resolveEntityAction as Yt, useResolvedViews as Z, EntityPreviewBinding as Zn, mergeEntityActions as Zt, useCollectionsConfigController as _, CollectionTableBinding as _n, UrlComponentPreview as _r, editEntityAction as _t, namespaceToPropertiesPath as a, resolveCollectionPathIds$1 as an, getEntityTitlePropertyKey as ar, getEntityViewWidth as at, asUnavailable as b, useAdminContext as bn, FormSections as br, DetailViewBinding as bt, buildCollectionGenerationCallback as c, SelectionMenu as cn, StringPropertyPreview as cr, useSafeSnackbarController as ct, fromSerializableCollectionConfigs as d, resolveSelection as dn, StorageThumbnailInternal as dr, getInitialEntityValues as dt, getCollectionPathsCombinations as en, CollectionRegistryContext as er, useBuildCollectionRegistryController as et, fromSerializableProperties as f, selectionQueryToFindParams as fn, SkeletonPropertyComponent as fr, removeEmptyContainers as ft, toSerializableProperty as g, VirtualTableInput$1 as gn, renderSkeletonText as gr, deleteEntityAction as gt, toSerializableProperties as h, useSelectionController as hn, renderSkeletonImageThumbnail as hr, copyEntityAction as ht, namespaceToPropertiesOrderPath as i, removeTrailingSlash$1 as in, getEntityPreviewKeys as ir, buildSidePanelsFromUrl as it, VectorFieldBinding as j, LabelWithIconAndTooltip as jn, getIconForWidget as jr, flattenEntry as jt, getFieldConfig as k, useClearRestoreValue as kn, getDefaultPropertiesOrder as kr, DataNewPropertiesMapping as kt, validateCollectionJson as l, MAX_SELECTION_ROWS as ln, EnumValuesChip as lr, extractTouchedValues as lt, toSerializableCollectionConfig as m, walkEntityPages as mn, renderSkeletonIcon as mr, CollectionViewBinding as mt, getFullIdPath as n, removeInitialAndTrailingSlashes$1 as nn, getUserLabel as nr, resolveNavigationFrom as nt, CollectionGenerationApiError as o, resolveOpenEntityMode as on, getEntityTitlePropertyKeyForEntity as or, useBuildSidePanel as ot, fromSerializableProperty as p, serializeSelectionQuery as pn, renderSkeletonCaptionText as pr, zodToFormErrors as pt, SideDialogs as q, RelationPreview as qn, useBreadcrumbsController as qt, idToPropertiesPath as r, removeInitialSlash as rn, useResolvedUser as rr, useResolvedNavigationFrom as rt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as s, resolveViewMode as sn, ArrayPropertyPreview as sr, EditViewBinding as st, getFullId as t, getLastSegment$1 as tn, useCollectionRegistryController as tr, useHistory as tt, fromSerializableCollectionConfig as u, SELECTION_PAGE_SIZE as un, StorageThumbnail as ur, getChanges as ut, LiveSchemaError as v, SelectableTable as vn, ImagePreview as vr, resetPasswordAction as vt, EntityForm as w, useNavigationStateController as wn, LABEL_ICON_SIZE as wr, CollectionViewActions as wt, createLiveSchemaClient as x, UrlContext as xn, FormRail as xr, EntityViewBinding as xt, SchemaChangeCancelled as y, CollectionRowActions as yn, EmptyValue as yr, CreationResultDialog as yt, MarkdownEditorFieldBinding as z, DatePreview as zn, convertFileToJson as zt };
|
|
25094
25573
|
|
|
25095
|
-
//# sourceMappingURL=util-
|
|
25574
|
+
//# sourceMappingURL=util-h6jhiiBE.js.map
|