@rebasepro/admin 0.6.1 → 0.7.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-HNXGxXdO.js → CollectionEditorDialog-iGgS4rLX.js} +233 -152
- package/dist/CollectionEditorDialog-iGgS4rLX.js.map +1 -0
- package/dist/{PropertyEditView-C2wp8kQ9.js → PropertyEditView-C9yhLyja.js} +129 -15
- package/dist/PropertyEditView-C9yhLyja.js.map +1 -0
- package/dist/{CollectionsStudioView-B1mN33GB.js → RouterCollectionsStudioView-Dqx27GD8.js} +61 -39
- package/dist/RouterCollectionsStudioView-Dqx27GD8.js.map +1 -0
- package/dist/collection_editor/extensibility_types.d.ts +135 -0
- package/dist/collection_editor/index.d.ts +6 -0
- package/dist/collection_editor/serializable_types.d.ts +374 -0
- package/dist/collection_editor/serializable_utils.d.ts +50 -0
- package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +2 -1
- package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +12 -1
- package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +13 -1
- package/dist/collection_editor/ui/collection_editor/CollectionsStudioView.d.ts +24 -2
- package/dist/collection_editor/ui/collection_editor/DisplaySettingsForm.d.ts +2 -1
- package/dist/collection_editor/ui/collection_editor/GeneralSettingsForm.d.ts +4 -1
- package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +12 -1
- package/dist/collection_editor/ui/collection_editor/RouterCollectionStudioView.d.ts +15 -0
- package/dist/collection_editor/ui/collection_editor/RouterCollectionsStudioView.d.ts +14 -0
- package/dist/collection_editor/useJsonCollectionsConfigController.d.ts +37 -0
- package/dist/collection_editor/useSafeSnackbarController.d.ts +2 -0
- package/dist/collection_editor_ui.d.ts +4 -0
- package/dist/collection_editor_ui.js +33 -4
- package/dist/collection_editor_ui.js.map +1 -0
- package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +3 -3
- package/dist/components/EntityCollectionView/hooks/useKanbanDragAndDrop.d.ts +3 -3
- package/dist/components/EntityEditView.d.ts +3 -2
- package/dist/components/RebaseCMS.d.ts +1 -1
- package/dist/form/EntityForm.d.ts +10 -9
- package/dist/form/EntityFormBinding.d.ts +27 -0
- package/dist/form/form_utils.d.ts +8 -0
- package/dist/form/index.d.ts +5 -1
- package/dist/{history-BO6VT85a.js → history-ChHgyyvM.js} +2 -2
- package/dist/{history-BO6VT85a.js.map → history-ChHgyyvM.js.map} +1 -1
- package/dist/hooks/navigation/contexts/CollectionRegistryContext.d.ts +1 -1
- package/dist/hooks/navigation/useAsyncResolver.d.ts +26 -0
- package/dist/hooks/navigation/useBuildCollectionRegistryController.d.ts +2 -0
- package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
- package/dist/hooks/navigation/utils.d.ts +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -2
- package/dist/routes/CustomViewRoute.d.ts +2 -1
- package/dist/{src-BYniefVu.js → src-DOX2nOyh.js} +1586 -1283
- package/dist/src-DOX2nOyh.js.map +1 -0
- package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
- package/dist/types/components/EntityFormProps.d.ts +59 -0
- package/dist/types/fields.d.ts +6 -1
- package/dist/util/dataControllerAdapter.d.ts +30 -0
- package/dist/util/navigation_utils.d.ts +1 -1
- package/dist/util/propertyConfigMapper.d.ts +17 -0
- package/package.json +18 -18
- package/src/collection_editor/extensibility_types.ts +168 -0
- package/src/collection_editor/index.ts +46 -0
- package/src/collection_editor/serializable_types.ts +487 -0
- package/src/collection_editor/serializable_utils.ts +588 -0
- package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +4 -4
- package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +134 -70
- package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +95 -78
- package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +23 -20
- package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +79 -27
- package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +9 -7
- package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +22 -5
- package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +3 -3
- package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +155 -10
- package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
- package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -0
- package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +44 -0
- package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +41 -0
- package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +4 -3
- package/src/collection_editor/useJsonCollectionsConfigController.tsx +286 -0
- package/src/collection_editor/useSafeSnackbarController.ts +9 -0
- package/src/collection_editor_ui.ts +6 -0
- package/src/components/DefaultDrawer.tsx +13 -3
- package/src/components/DrawerNavigationGroup.tsx +2 -2
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +12 -11
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +40 -190
- package/src/components/EntityCollectionView/EntityCollectionListView.tsx +54 -183
- package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +6 -6
- package/src/components/EntityEditView.tsx +5 -4
- package/src/components/EntityEditViewFormActions.tsx +32 -26
- package/src/components/HomePage/ContentHomePage.tsx +13 -4
- package/src/components/RebaseCMS.tsx +3 -2
- package/src/components/RebaseNavigation.tsx +71 -7
- package/src/components/SelectableTable/SelectableTable.tsx +2 -2
- package/src/components/SideDialogs.tsx +0 -7
- package/src/components/common/default_entity_actions.tsx +9 -9
- package/src/contexts/BreacrumbsContext.tsx +15 -6
- package/src/editor/selectors/ai-selector.tsx +0 -1
- package/src/form/EntityForm.tsx +191 -428
- package/src/form/EntityFormActions.tsx +33 -47
- package/src/form/EntityFormBinding.tsx +323 -0
- package/src/form/components/FormLayout.tsx +1 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
- package/src/form/form_utils.ts +163 -0
- package/src/form/index.tsx +14 -5
- package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -2
- package/src/hooks/navigation/contexts/NavigationStateContext.tsx +2 -2
- package/src/hooks/navigation/contexts/UrlContext.tsx +1 -2
- package/src/hooks/navigation/useAsyncResolver.ts +100 -0
- package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -8
- package/src/hooks/navigation/useBuildNavigationStateController.tsx +12 -8
- package/src/hooks/navigation/useNavigationResolution.ts +33 -0
- package/src/hooks/navigation/useResolvedCollections.ts +29 -71
- package/src/hooks/navigation/useResolvedViews.tsx +40 -70
- package/src/hooks/navigation/useTopLevelNavigation.ts +20 -16
- package/src/hooks/navigation/utils.ts +7 -4
- package/src/index.ts +5 -0
- package/src/routes/CustomViewRoute.tsx +14 -6
- package/src/types/components/EntityFormActionsProps.tsx +1 -1
- package/src/types/components/EntityFormProps.tsx +68 -0
- package/src/types/fields.tsx +7 -1
- package/src/util/dataControllerAdapter.ts +93 -0
- package/src/util/navigation_utils.ts +1 -1
- package/src/util/propertyConfigMapper.ts +134 -0
- package/LICENSE +0 -21
- package/dist/CollectionEditorDialog-HNXGxXdO.js.map +0 -1
- package/dist/CollectionsStudioView-B1mN33GB.js.map +0 -1
- package/dist/PropertyEditView-C2wp8kQ9.js.map +0 -1
- package/dist/components/EntityCollectionView/Board.d.ts +0 -3
- package/dist/components/EntityCollectionView/BoardColumn.d.ts +0 -42
- package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +0 -9
- package/dist/components/EntityCollectionView/BoardSortableList.d.ts +0 -14
- package/dist/components/EntityCollectionView/board_types.d.ts +0 -105
- package/dist/hooks/navigation/useNavigationRegistry.d.ts +0 -12
- package/dist/hooks/navigation/useNavigationURLs.d.ts +0 -11
- package/dist/src-BYniefVu.js.map +0 -1
- package/src/components/EntityCollectionView/Board.tsx +0 -475
- package/src/components/EntityCollectionView/BoardColumn.tsx +0 -165
- package/src/components/EntityCollectionView/BoardColumnTitle.tsx +0 -46
- package/src/components/EntityCollectionView/BoardSortableList.tsx +0 -177
- package/src/components/EntityCollectionView/board_types.ts +0 -113
- package/src/hooks/navigation/useNavigationRegistry.ts +0 -164
- package/src/hooks/navigation/useNavigationURLs.ts +0 -56
|
@@ -2,9 +2,9 @@ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
|
|
|
2
2
|
import * as React$1 from "react";
|
|
3
3
|
import React, { Suspense, createContext, createElement, forwardRef, lazy, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
4
|
import { deepEqual } from "fast-equals";
|
|
5
|
-
import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowLeftIcon, ArrowRightToLineIcon, Avatar, Badge, BooleanSwitch, BooleanSwitchWithLabel, Button, CHIP_COLORS, CalendarIcon, Card, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, Container, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, DownloadIcon, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KeyRoundIcon, Label, LayoutGridIcon, LinkIcon, ListIcon, ListOrderedIcon, LoadingButton, LogOutIcon, MailIcon, Markdown, Maximize2Icon, Menu, MenuIcon, MenuItem, MinusIcon, MoonIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PencilIcon, PlusIcon, Popover, PopoverPrimitive, Portal, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SearchBar, SearchIcon, Select, SelectItem, Separator, SettingsIcon, Sheet, Skeleton, StarIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, UploadIcon, UserIcon,
|
|
6
|
-
import { CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, addInitialSlash, canReadCollection, enumToObjectEntries, getArrayResolvedProperties, getCollectionBySlugWithin, getCollectionPathsCombinations, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityImagePreviewPropertyKey, getLabelOrConfigFrom, getLastSegment, getLastSegment as getLastSegment$1, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, isHidden, isPropertyBuilder, isReadOnly, normalizeToEntityRelation, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, resolveArrayProperties, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveDefaultSelectedView, resolveEnumValues, resolveProperty, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString } from "@rebasepro/common";
|
|
7
|
-
import { ADDITIONAL_TAB_WIDTH, CONTAINER_FULL_WIDTH, CollectionComponentOverrideProvider, ConfirmationDialog, CustomizationControllerContext, DEFAULT_PAGE_SIZE, ErrorTooltip, ErrorView, FORM_CONTAINER_WIDTH, IconForView, LanguageToggle, LoginView, NotFoundPage, PluginProviderStack, RebaseLogo, RebaseRoutes, SchemaDriftBanner, StudioBridgeRegistryProvider, UIReferenceView, UnsavedChangesDialog, UserDisplay, UserSettingsView, buildCollapsedDefaults, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, flattenKeys, getColorScheme, getColumnKeysForProperty, getEntityFromCache, getEntityFromMemoryCache, getFormFieldKeys, getIcon, getRowHeight, iconsSearch, removeEntityFromCache, removeEntityFromMemoryCache, resolveComponentRef, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAdminModeController, useAnalyticsController, useApiConfig, useAuthController, useBridgeRegistration, useBuildLocalConfigurationPersistence, useCollapsedGroups, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataTableController, useEntityFetch, useLargeLayout, useModeController, usePermissions, useRebaseClient, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRelationSelector, useRestoreScroll, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/core";
|
|
5
|
+
import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowLeftIcon, ArrowRightToLineIcon, Avatar, Badge, BooleanSwitch, BooleanSwitchWithLabel, Button, CHIP_COLORS, CalendarIcon, Card, CardView, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsLeftIcon, ChevronsRightIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, Container, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, DownloadIcon, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KanbanView, KeyRoundIcon, Label, LayoutGridIcon, LinkIcon, ListIcon, ListOrderedIcon, ListView, LoaderIcon, LoadingButton, LogOutIcon, MailIcon, Markdown, Maximize2Icon, Menu, MenuIcon, MenuItem, MinusIcon, MoonIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PencilIcon, PlusIcon, Popover, PopoverPrimitive, Portal, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SearchBar, SearchIcon, Select, SelectItem, Separator, SettingsIcon, Sheet, Skeleton, StarIcon, SunIcon, SunMoonIcon, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, TableView, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, UploadIcon, UserIcon, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled, getColorSchemeForSeed, iconSize, lucideIcons, paperMixin, useDebouncedCallback, useInjectStyles, useOutsideAlerter } from "@rebasepro/ui";
|
|
6
|
+
import { CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, addInitialSlash, buildRoutedRebaseData, canReadCollection, enumToObjectEntries, getArrayResolvedProperties, getCollectionBySlugWithin, getCollectionPathsCombinations, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityImagePreviewPropertyKey, getLabelOrConfigFrom, getLastSegment, getLastSegment as getLastSegment$1, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, isHidden, isPropertyBuilder, isReadOnly, normalizeToEntityRelation, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, resolveArrayProperties, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveDataSource, resolveDefaultSelectedView, resolveEnumValues, resolveProperty, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString } from "@rebasepro/common";
|
|
7
|
+
import { ADDITIONAL_TAB_WIDTH, CONTAINER_FULL_WIDTH, CollectionComponentOverrideProvider, ConfirmationDialog, CustomizationControllerContext, DEFAULT_PAGE_SIZE, ErrorTooltip, ErrorView, FORM_CONTAINER_WIDTH, IconForView, LanguageToggle, LoginView, NotFoundPage, PluginProviderStack, RebaseDataContext, RebaseLogo, RebaseRoutes, SchemaDriftBanner, StudioBridgeRegistryProvider, UIReferenceView, UnsavedChangesDialog, UserDisplay, UserSettingsView, buildCollapsedDefaults, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, flattenKeys, getColorScheme, getColumnKeysForProperty, getEntityFromCache, getEntityFromMemoryCache, getFormFieldKeys, getIcon, getRowHeight, iconsSearch, removeEntityFromCache, removeEntityFromMemoryCache, resolveComponentRef, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAdminModeController, useAnalyticsController, useApiConfig, useAuthController, useBridgeRegistration, useBuildLocalConfigurationPersistence, useCollapsedGroups, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataSources, useDataTableController, useEntityFetch, useLargeLayout, useModeController, usePermissions, useRebaseClient, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRelationSelector, useRestoreScroll, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/core";
|
|
8
8
|
import { Field, Formex, getIn, setIn, useCreateFormex, useFormex } from "@rebasepro/formex";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { defaultDateFormat, getArrayValuesCount, getHashValue, getValueInPath, hydrateRegExp, isObject, mergeDeep, prettifyIdentifier, randomString, slugify, toArray } from "@rebasepro/utils";
|
|
@@ -21,7 +21,6 @@ import { SortableContext, arrayMove, defaultAnimateLayoutChanges, horizontalList
|
|
|
21
21
|
import { CSS } from "@dnd-kit/utilities";
|
|
22
22
|
import ExcelJS from "exceljs";
|
|
23
23
|
import { buildEntityPropertiesFromData, inferTypeFromValue } from "@rebasepro/schema-inference";
|
|
24
|
-
import { createPortal } from "react-dom";
|
|
25
24
|
import { generateKeyBetween, generateNKeysBetween } from "fractional-indexing";
|
|
26
25
|
import Fuse from "fuse.js";
|
|
27
26
|
import { format } from "date-fns";
|
|
@@ -293,16 +292,11 @@ function getZodBinarySchema({ property }) {
|
|
|
293
292
|
//#endregion
|
|
294
293
|
//#region src/form/EntityFormActions.tsx
|
|
295
294
|
function EntityFormActions({ path, collection, entity, layout, savingError, formex, disabled, status, pluginActions, openEntityMode, navigateBack, formContext }) {
|
|
296
|
-
const context = useCMSContext();
|
|
297
|
-
const sideEntityController = context.sideEntityController;
|
|
298
|
-
const { t } = useTranslation();
|
|
299
295
|
const bottomActionsProps = {
|
|
300
296
|
path,
|
|
301
297
|
savingError,
|
|
302
298
|
entity,
|
|
303
299
|
collection,
|
|
304
|
-
context,
|
|
305
|
-
sideEntityController,
|
|
306
300
|
disabled,
|
|
307
301
|
status,
|
|
308
302
|
pluginActions,
|
|
@@ -310,7 +304,6 @@ function EntityFormActions({ path, collection, entity, layout, savingError, form
|
|
|
310
304
|
navigateBack,
|
|
311
305
|
formContext,
|
|
312
306
|
formex,
|
|
313
|
-
t,
|
|
314
307
|
className: layout === "responsive" ? "@6xl:hidden" : void 0
|
|
315
308
|
};
|
|
316
309
|
const sideActionsProps = {
|
|
@@ -318,8 +311,6 @@ function EntityFormActions({ path, collection, entity, layout, savingError, form
|
|
|
318
311
|
savingError,
|
|
319
312
|
entity,
|
|
320
313
|
collection,
|
|
321
|
-
context,
|
|
322
|
-
sideEntityController,
|
|
323
314
|
disabled,
|
|
324
315
|
status,
|
|
325
316
|
pluginActions,
|
|
@@ -327,7 +318,6 @@ function EntityFormActions({ path, collection, entity, layout, savingError, form
|
|
|
327
318
|
navigateBack,
|
|
328
319
|
formContext,
|
|
329
320
|
formex,
|
|
330
|
-
t,
|
|
331
321
|
className: layout === "responsive" ? "hidden @6xl:flex" : void 0
|
|
332
322
|
};
|
|
333
323
|
const bottomActions = buildBottomActions$1(bottomActionsProps);
|
|
@@ -335,7 +325,7 @@ function EntityFormActions({ path, collection, entity, layout, savingError, form
|
|
|
335
325
|
if (layout === "responsive") return /* @__PURE__ */ jsxs(Fragment, { children: [bottomActions, sideActions] });
|
|
336
326
|
return layout === "bottom" ? bottomActions : sideActions;
|
|
337
327
|
}
|
|
338
|
-
function buildBottomActions$1({ savingError, entity, path, formActions, collection,
|
|
328
|
+
function buildBottomActions$1({ savingError, entity, path, formActions, collection, disabled, status, pluginActions, openEntityMode, navigateBack, formContext, formex, className }) {
|
|
339
329
|
const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
340
330
|
return /* @__PURE__ */ jsxs(DialogActions, {
|
|
341
331
|
position: "absolute",
|
|
@@ -359,8 +349,8 @@ function buildBottomActions$1({ savingError, entity, path, formActions, collecti
|
|
|
359
349
|
entity,
|
|
360
350
|
path: path ?? collection.slug,
|
|
361
351
|
collection,
|
|
362
|
-
context,
|
|
363
|
-
sideEntityController,
|
|
352
|
+
context: void 0,
|
|
353
|
+
sideEntityController: void 0,
|
|
364
354
|
openEntityMode,
|
|
365
355
|
navigateBack,
|
|
366
356
|
formContext
|
|
@@ -375,47 +365,55 @@ function buildBottomActions$1({ savingError, entity, path, formActions, collecti
|
|
|
375
365
|
disabled: disabled || formex.isSubmitting,
|
|
376
366
|
color: "primary",
|
|
377
367
|
type: "reset",
|
|
378
|
-
children: status === "existing" ?
|
|
368
|
+
children: status === "existing" ? "Discard" : "Clear"
|
|
379
369
|
}),
|
|
380
|
-
/* @__PURE__ */
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
370
|
+
/* @__PURE__ */ jsx(Tooltip, {
|
|
371
|
+
title: hasErrors ? "Fix highlighted errors before saving" : void 0,
|
|
372
|
+
children: /* @__PURE__ */ jsxs(LoadingButton, {
|
|
373
|
+
variant: "filled",
|
|
374
|
+
color: "primary",
|
|
375
|
+
type: "submit",
|
|
376
|
+
loading: formex.isSubmitting,
|
|
377
|
+
disabled: disabled || formex.isSubmitting,
|
|
378
|
+
startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
|
|
379
|
+
children: [
|
|
380
|
+
status === "existing" && "Save",
|
|
381
|
+
status === "copy" && "Create copy",
|
|
382
|
+
status === "new" && "Create"
|
|
383
|
+
]
|
|
384
|
+
})
|
|
391
385
|
})
|
|
392
386
|
]
|
|
393
387
|
});
|
|
394
388
|
}
|
|
395
|
-
function buildSideActions$1({ savingError, entity, formActions, path, openEntityMode, collection,
|
|
389
|
+
function buildSideActions$1({ savingError, entity, formActions, path, openEntityMode, collection, disabled, status, pluginActions, formex, className }) {
|
|
396
390
|
const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
397
391
|
return /* @__PURE__ */ jsxs("div", {
|
|
398
392
|
className: cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin, className),
|
|
399
393
|
children: [
|
|
400
|
-
/* @__PURE__ */
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
394
|
+
/* @__PURE__ */ jsx(Tooltip, {
|
|
395
|
+
title: hasErrors ? "Fix highlighted errors before saving" : void 0,
|
|
396
|
+
children: /* @__PURE__ */ jsxs(LoadingButton, {
|
|
397
|
+
fullWidth: true,
|
|
398
|
+
variant: "filled",
|
|
399
|
+
color: "primary",
|
|
400
|
+
type: "submit",
|
|
401
|
+
loading: formex.isSubmitting,
|
|
402
|
+
startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
|
|
403
|
+
disabled: disabled || formex.isSubmitting,
|
|
404
|
+
children: [
|
|
405
|
+
status === "existing" && "Save",
|
|
406
|
+
status === "copy" && "Create copy",
|
|
407
|
+
status === "new" && "Create"
|
|
408
|
+
]
|
|
409
|
+
})
|
|
412
410
|
}),
|
|
413
411
|
/* @__PURE__ */ jsx(Button, {
|
|
414
412
|
fullWidth: true,
|
|
415
413
|
variant: "text",
|
|
416
414
|
disabled: disabled || formex.isSubmitting,
|
|
417
415
|
type: "reset",
|
|
418
|
-
children: status === "existing" ?
|
|
416
|
+
children: status === "existing" ? "Discard" : "Clear"
|
|
419
417
|
}),
|
|
420
418
|
pluginActions,
|
|
421
419
|
savingError && /* @__PURE__ */ jsx("div", {
|
|
@@ -1578,7 +1576,7 @@ var MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
|
|
|
1578
1576
|
var JSON_TAB_VALUE = "__json";
|
|
1579
1577
|
//#endregion
|
|
1580
1578
|
//#region src/components/EntityDetailView.tsx
|
|
1581
|
-
var EntityHistoryView$1 = lazy(() => import("./history-
|
|
1579
|
+
var EntityHistoryView$1 = lazy(() => import("./history-ChHgyyvM.js").then((m) => ({ default: m.EntityHistoryView })));
|
|
1582
1580
|
function EntityDetailView({ entityId, ...props }) {
|
|
1583
1581
|
const { entity, dataLoading, dataLoadingError } = useEntityFetch({
|
|
1584
1582
|
path: props.path,
|
|
@@ -5206,7 +5204,7 @@ var SelectableTable = function SelectableTable({ onValueChange, cellRenderer, on
|
|
|
5206
5204
|
children: /* @__PURE__ */ jsx("div", {
|
|
5207
5205
|
className: "h-full w-full flex flex-col bg-white dark:bg-surface-900",
|
|
5208
5206
|
ref,
|
|
5209
|
-
children: /* @__PURE__ */ jsx(
|
|
5207
|
+
children: /* @__PURE__ */ jsx(TableView, {
|
|
5210
5208
|
data,
|
|
5211
5209
|
columns,
|
|
5212
5210
|
cellRenderer: (props) => cellRenderer(props),
|
|
@@ -5734,19 +5732,22 @@ function buildBottomActions({ savingError, entity, formActions, collection, cont
|
|
|
5734
5732
|
onClick: navigateBack,
|
|
5735
5733
|
children: t("back_to_detail") ?? "Back to details"
|
|
5736
5734
|
}),
|
|
5737
|
-
/* @__PURE__ */
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5735
|
+
/* @__PURE__ */ jsx(Tooltip, {
|
|
5736
|
+
title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
|
|
5737
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
5738
|
+
variant: canClose ? "text" : "filled",
|
|
5739
|
+
color: "primary",
|
|
5740
|
+
type: "submit",
|
|
5741
|
+
disabled: disabled || formex.isSubmitting,
|
|
5742
|
+
onClick: () => {
|
|
5743
|
+
sideDialogContext.setPendingClose(false);
|
|
5744
|
+
},
|
|
5745
|
+
children: [
|
|
5746
|
+
status === "existing" && t("save"),
|
|
5747
|
+
status === "copy" && t("create_copy"),
|
|
5748
|
+
status === "new" && t("create")
|
|
5749
|
+
]
|
|
5750
|
+
})
|
|
5750
5751
|
}),
|
|
5751
5752
|
canClose && /* @__PURE__ */ jsxs(LoadingButton, {
|
|
5752
5753
|
variant: "filled",
|
|
@@ -5771,21 +5772,25 @@ function buildSideActions({ savingError, entity, formActions, collection, contex
|
|
|
5771
5772
|
return /* @__PURE__ */ jsxs("div", {
|
|
5772
5773
|
className: cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin, className),
|
|
5773
5774
|
children: [
|
|
5774
|
-
/* @__PURE__ */
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5775
|
+
/* @__PURE__ */ jsx(Tooltip, {
|
|
5776
|
+
title: hasErrors ? t("fix_errors_before_saving") ?? "Fix highlighted errors before saving" : void 0,
|
|
5777
|
+
children: /* @__PURE__ */ jsxs(LoadingButton, {
|
|
5778
|
+
fullWidth: true,
|
|
5779
|
+
variant: "filled",
|
|
5780
|
+
color: "primary",
|
|
5781
|
+
type: "submit",
|
|
5782
|
+
loading: formex.isSubmitting,
|
|
5783
|
+
startIcon: hasErrors ? /* @__PURE__ */ jsx(AlertCircleIcon, {}) : void 0,
|
|
5784
|
+
disabled: disabled || formex.isSubmitting,
|
|
5785
|
+
onClick: () => {
|
|
5786
|
+
sideDialogContext.setPendingClose?.(false);
|
|
5787
|
+
},
|
|
5788
|
+
children: [
|
|
5789
|
+
status === "existing" && t("save"),
|
|
5790
|
+
status === "copy" && t("create_copy"),
|
|
5791
|
+
status === "new" && t("create")
|
|
5792
|
+
]
|
|
5793
|
+
})
|
|
5789
5794
|
}),
|
|
5790
5795
|
/* @__PURE__ */ jsx(Button, {
|
|
5791
5796
|
fullWidth: true,
|
|
@@ -5875,7 +5880,7 @@ function EntityActionButton({ action, enabled, props }) {
|
|
|
5875
5880
|
}
|
|
5876
5881
|
//#endregion
|
|
5877
5882
|
//#region src/components/EntityEditView.tsx
|
|
5878
|
-
var EntityHistoryView = lazy(() => import("./history-
|
|
5883
|
+
var EntityHistoryView = lazy(() => import("./history-ChHgyyvM.js").then((m) => ({ default: m.EntityHistoryView })));
|
|
5879
5884
|
/**
|
|
5880
5885
|
* This is the default view that is used as the content of a side panel when
|
|
5881
5886
|
* an entity is opened.
|
|
@@ -5927,7 +5932,7 @@ function EntityEditView({ entityId, ...props }) {
|
|
|
5927
5932
|
}
|
|
5928
5933
|
function EntityEditViewInner({ path, entityId, selectedTab: selectedTabProp, collection, parentCollectionSlugs, parentEntityIds, onValuesModified, onSaved, onTabChange, navigateBack, entity, initialDirtyValues, dataLoading, layout = "side_panel", barActions, status, setStatus, formProps, canEdit }) {
|
|
5929
5934
|
const ResolvedFormActions = useComponentOverride("Entity.FormActions", EntityEditViewFormActions);
|
|
5930
|
-
const ResolvedEntityForm = useComponentOverride("Entity.Form",
|
|
5935
|
+
const ResolvedEntityForm = useComponentOverride("Entity.Form", EntityFormBinding);
|
|
5931
5936
|
const ResolvedCollectionView = useComponentOverride("Collection.View", EntityCollectionView);
|
|
5932
5937
|
const context = useRebaseContext();
|
|
5933
5938
|
const urlController = useUrlController();
|
|
@@ -7070,8 +7075,9 @@ function useEntityHistory(params) {
|
|
|
7070
7075
|
//#endregion
|
|
7071
7076
|
//#region src/hooks/navigation/useBuildCollectionRegistryController.tsx
|
|
7072
7077
|
function useBuildCollectionRegistryController(props) {
|
|
7073
|
-
const { userConfigPersistence } = props;
|
|
7074
|
-
const collectionRegistryRef = useRef(new CollectionRegistry());
|
|
7078
|
+
const { userConfigPersistence, dataSources } = props;
|
|
7079
|
+
const collectionRegistryRef = useRef(new CollectionRegistry(void 0, dataSources));
|
|
7080
|
+
if (dataSources) collectionRegistryRef.current.setDataSources(dataSources);
|
|
7075
7081
|
const [initialised, setInitialised] = useState(false);
|
|
7076
7082
|
const getCollection = useCallback((slugOrPath, includeUserOverride = false) => {
|
|
7077
7083
|
const registry = collectionRegistryRef.current;
|
|
@@ -7163,7 +7169,9 @@ function useBuildCollectionRegistryController(props) {
|
|
|
7163
7169
|
return paths;
|
|
7164
7170
|
}, []);
|
|
7165
7171
|
const collections = collectionRegistryRef.current.getCollections();
|
|
7166
|
-
|
|
7172
|
+
useEffect(() => {
|
|
7173
|
+
if (!initialised && collections.length > 0) setInitialised(true);
|
|
7174
|
+
}, [initialised, collections.length]);
|
|
7167
7175
|
return useMemo(() => ({
|
|
7168
7176
|
collections,
|
|
7169
7177
|
initialised,
|
|
@@ -7491,7 +7499,7 @@ var editEntityAction = {
|
|
|
7491
7499
|
if (!entity) throw new Error("INTERNAL: editEntityAction: Entity is undefined");
|
|
7492
7500
|
if (!sideEntityController) throw new Error("INTERNAL: editEntityAction: sideEntityController is undefined");
|
|
7493
7501
|
highlightEntity?.(entity);
|
|
7494
|
-
context
|
|
7502
|
+
context?.analyticsController?.onAnalyticsEvent?.("entity_click", {
|
|
7495
7503
|
path: entity.path,
|
|
7496
7504
|
entityId: entity.id
|
|
7497
7505
|
});
|
|
@@ -7508,7 +7516,7 @@ var editEntityAction = {
|
|
|
7508
7516
|
path: newFullIdPath,
|
|
7509
7517
|
sideEntityController,
|
|
7510
7518
|
onClose: () => unhighlightEntity?.(entity),
|
|
7511
|
-
navigation: context
|
|
7519
|
+
navigation: context?.urlController,
|
|
7512
7520
|
selectedTab: defaultSelectedView
|
|
7513
7521
|
});
|
|
7514
7522
|
return Promise.resolve(void 0);
|
|
@@ -7523,7 +7531,7 @@ var copyEntityAction = {
|
|
|
7523
7531
|
if (!entity) throw new Error("INTERNAL: copyEntityAction: Entity is undefined");
|
|
7524
7532
|
if (!sideEntityController) throw new Error("INTERNAL: copyEntityAction: sideEntityController is undefined");
|
|
7525
7533
|
highlightEntity?.(entity);
|
|
7526
|
-
context
|
|
7534
|
+
context?.analyticsController?.onAnalyticsEvent?.("copy_entity_click", {
|
|
7527
7535
|
path: entity.path,
|
|
7528
7536
|
entityId: entity.id
|
|
7529
7537
|
});
|
|
@@ -7536,7 +7544,7 @@ var copyEntityAction = {
|
|
|
7536
7544
|
copy: true,
|
|
7537
7545
|
sideEntityController,
|
|
7538
7546
|
onClose: () => unhighlightEntity?.(entity),
|
|
7539
|
-
navigation: context
|
|
7547
|
+
navigation: context?.urlController
|
|
7540
7548
|
});
|
|
7541
7549
|
return Promise.resolve(void 0);
|
|
7542
7550
|
}
|
|
@@ -7548,7 +7556,7 @@ var deleteEntityAction = {
|
|
|
7548
7556
|
isEnabled: ({ entity }) => Boolean(entity),
|
|
7549
7557
|
onClick({ entity, path, collection, context, selectionController, onCollectionChange, navigateBack, openEntityMode }) {
|
|
7550
7558
|
if (!entity) throw new Error("INTERNAL: deleteEntityAction: Entity is undefined");
|
|
7551
|
-
if (!context
|
|
7559
|
+
if (!context?.dialogsController) throw new Error("INTERNAL: deleteEntityAction: context.dialogsController is undefined");
|
|
7552
7560
|
const { closeDialog } = context.dialogsController.open({
|
|
7553
7561
|
key: "delete_entity_dialog_" + entity.id,
|
|
7554
7562
|
Component: ({ open }) => {
|
|
@@ -7560,10 +7568,10 @@ var deleteEntityAction = {
|
|
|
7560
7568
|
callbacks: collection.callbacks,
|
|
7561
7569
|
open,
|
|
7562
7570
|
onEntityDelete: () => {
|
|
7563
|
-
context
|
|
7571
|
+
context?.analyticsController?.onAnalyticsEvent?.("single_entity_deleted", { path });
|
|
7564
7572
|
selectionController?.setSelectedEntities(selectionController.selectedEntities.filter((e) => e.id !== entity.id));
|
|
7565
7573
|
onCollectionChange?.();
|
|
7566
|
-
if (openEntityMode === "full_screen" && context
|
|
7574
|
+
if (openEntityMode === "full_screen" && context?.urlController) {
|
|
7567
7575
|
const collectionUrl = context.urlController.buildUrlCollectionPath(path);
|
|
7568
7576
|
context.urlController.navigate(collectionUrl, { replace: true });
|
|
7569
7577
|
} else navigateBack?.();
|
|
@@ -7643,7 +7651,7 @@ var resetPasswordAction = {
|
|
|
7643
7651
|
isEnabled: ({ entity }) => Boolean(entity),
|
|
7644
7652
|
onClick({ entity, context }) {
|
|
7645
7653
|
if (!entity) throw new Error("INTERNAL: resetPasswordAction: Entity is undefined");
|
|
7646
|
-
if (!context
|
|
7654
|
+
if (!context?.dialogsController) throw new Error("INTERNAL: resetPasswordAction: context.dialogsController is undefined");
|
|
7647
7655
|
const user = {
|
|
7648
7656
|
uid: entity.id,
|
|
7649
7657
|
email: entity.values?.email,
|
|
@@ -7787,12 +7795,34 @@ async function resolveAppViews(baseViews, authController, data, plugins) {
|
|
|
7787
7795
|
if (plugins) {
|
|
7788
7796
|
for (const plugin of plugins) if (plugin.views && plugin.views.length > 0) resolvedViews = [...resolvedViews, ...plugin.views];
|
|
7789
7797
|
}
|
|
7798
|
+
if (process.env.NODE_ENV !== "production") {
|
|
7799
|
+
const slugCounts = /* @__PURE__ */ new Map();
|
|
7800
|
+
resolvedViews.forEach((v) => {
|
|
7801
|
+
slugCounts.set(v.slug, (slugCounts.get(v.slug) ?? 0) + 1);
|
|
7802
|
+
});
|
|
7803
|
+
slugCounts.forEach((count, slug) => {
|
|
7804
|
+
if (count > 1) console.warn(`[Rebase] Duplicate view slug "${slug}" detected (${count} views). Last-write-wins. Ensure unique slugs across CMS views and plugins.`);
|
|
7805
|
+
});
|
|
7806
|
+
}
|
|
7807
|
+
resolvedViews = filterViewsByRole(resolvedViews, authController);
|
|
7790
7808
|
return resolvedViews;
|
|
7791
7809
|
}
|
|
7810
|
+
/**
|
|
7811
|
+
* Filter views by the `roles` field on AppView.
|
|
7812
|
+
* When `roles` is set, the view is only included if the current user
|
|
7813
|
+
* has at least one of the listed roles. Views without `roles` (or with
|
|
7814
|
+
* an empty array) are always included.
|
|
7815
|
+
*/
|
|
7816
|
+
function filterViewsByRole(views, authController) {
|
|
7817
|
+
const userRoles = authController.user?.roles ?? [];
|
|
7818
|
+
return views.filter((view) => {
|
|
7819
|
+
if (!view.roles || view.roles.length === 0) return true;
|
|
7820
|
+
return view.roles.some((role) => userRoles.includes(role));
|
|
7821
|
+
});
|
|
7822
|
+
}
|
|
7792
7823
|
//#endregion
|
|
7793
7824
|
//#region src/hooks/navigation/utils.ts
|
|
7794
7825
|
var NAVIGATION_DEFAULT_GROUP_NAME = "Views";
|
|
7795
|
-
var NAVIGATION_ADMIN_GROUP_NAME = "Admin";
|
|
7796
7826
|
function getGroup(collectionOrView) {
|
|
7797
7827
|
const trimmed = collectionOrView.group?.trim();
|
|
7798
7828
|
if (!trimmed || trimmed === "") return NAVIGATION_DEFAULT_GROUP_NAME;
|
|
@@ -7801,7 +7831,8 @@ function getGroup(collectionOrView) {
|
|
|
7801
7831
|
function computeNavigationGroups({ navigationGroupMappings, collections, views, plugins }) {
|
|
7802
7832
|
let result = navigationGroupMappings ? navigationGroupMappings.map((g) => ({
|
|
7803
7833
|
name: g.name,
|
|
7804
|
-
entries: [...g.entries]
|
|
7834
|
+
entries: [...g.entries],
|
|
7835
|
+
...g.collapsedByDefault !== void 0 && { collapsedByDefault: g.collapsedByDefault }
|
|
7805
7836
|
})) : navigationGroupMappings;
|
|
7806
7837
|
if (plugins) result = plugins.reduce((acc, plugin) => {
|
|
7807
7838
|
if (plugin.hooks?.navigationEntries) plugin.hooks.navigationEntries.forEach((entry) => {
|
|
@@ -7891,48 +7922,38 @@ function areCollectionsEqual(a, b, visitedSlugs = []) {
|
|
|
7891
7922
|
return deepEqual(Object.fromEntries(Object.entries(a).filter(([k, v]) => typeof v !== "function" && k !== "subcollections")), Object.fromEntries(Object.entries(b).filter(([k, v]) => typeof v !== "function" && k !== "subcollections")));
|
|
7892
7923
|
}
|
|
7893
7924
|
//#endregion
|
|
7894
|
-
//#region src/hooks/navigation/
|
|
7925
|
+
//#region src/hooks/navigation/useAsyncResolver.ts
|
|
7895
7926
|
/**
|
|
7896
|
-
*
|
|
7897
|
-
*
|
|
7898
|
-
*
|
|
7899
|
-
* When userManagement is provided, the default users collection is always
|
|
7900
|
-
* prepended. Developer collections override via generic slug-based dedup
|
|
7901
|
-
* (Map keyed by slug, last-write-wins). No hardcoded string checks.
|
|
7927
|
+
* Generic hook that resolves an async value with loading/error/refresh
|
|
7928
|
+
* state management, cancellation on unmount or deps change, and
|
|
7929
|
+
* ref-based change detection to prevent unnecessary state updates.
|
|
7902
7930
|
*
|
|
7903
|
-
*
|
|
7904
|
-
*
|
|
7931
|
+
* Extracted from the common pattern in useResolvedCollections and
|
|
7932
|
+
* useResolvedViews.
|
|
7905
7933
|
*/
|
|
7906
|
-
function
|
|
7907
|
-
const
|
|
7934
|
+
function useAsyncResolver({ resolver, initialValue, isEqual, deps, disabled }) {
|
|
7935
|
+
const [data, setData] = useState(initialValue);
|
|
7908
7936
|
const [loading, setLoading] = useState(true);
|
|
7909
7937
|
const [error, setError] = useState(void 0);
|
|
7910
|
-
const [resolvedCollections, setResolvedCollections] = useState([]);
|
|
7911
7938
|
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
7912
7939
|
const refresh = useCallback(() => {
|
|
7913
7940
|
setRefreshTrigger((prev) => prev + 1);
|
|
7914
7941
|
}, []);
|
|
7915
|
-
const dataRef = useRef(
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
pluginsRef.current = plugins;
|
|
7921
|
-
const resolvedCollectionsRef = useRef([]);
|
|
7922
|
-
const initialLoading = authController.initialLoading;
|
|
7923
|
-
const user = authController.user;
|
|
7942
|
+
const dataRef = useRef(initialValue);
|
|
7943
|
+
const resolverRef = useRef(resolver);
|
|
7944
|
+
resolverRef.current = resolver;
|
|
7945
|
+
const isEqualRef = useRef(isEqual);
|
|
7946
|
+
isEqualRef.current = isEqual;
|
|
7924
7947
|
useEffect(() => {
|
|
7925
|
-
if (disabled
|
|
7948
|
+
if (disabled) return;
|
|
7926
7949
|
let cancelled = false;
|
|
7927
7950
|
(async () => {
|
|
7928
7951
|
try {
|
|
7929
|
-
const
|
|
7952
|
+
const result = await resolverRef.current();
|
|
7930
7953
|
if (cancelled) return;
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
resolvedCollectionsRef.current = deduped;
|
|
7935
|
-
setResolvedCollections(deduped);
|
|
7954
|
+
if (!isEqualRef.current(dataRef.current, result)) {
|
|
7955
|
+
dataRef.current = result;
|
|
7956
|
+
setData(result);
|
|
7936
7957
|
}
|
|
7937
7958
|
setError(void 0);
|
|
7938
7959
|
} catch (e) {
|
|
@@ -7948,20 +7969,66 @@ function useResolvedCollections(props) {
|
|
|
7948
7969
|
cancelled = true;
|
|
7949
7970
|
};
|
|
7950
7971
|
}, [
|
|
7951
|
-
|
|
7952
|
-
disabled,
|
|
7953
|
-
collectionRegistryController.collectionRegistryRef,
|
|
7972
|
+
...deps,
|
|
7954
7973
|
refreshTrigger,
|
|
7955
|
-
|
|
7956
|
-
|
|
7974
|
+
disabled
|
|
7975
|
+
]);
|
|
7976
|
+
return useMemo(() => ({
|
|
7977
|
+
data,
|
|
7978
|
+
loading,
|
|
7979
|
+
error,
|
|
7980
|
+
refresh
|
|
7981
|
+
}), [
|
|
7982
|
+
data,
|
|
7983
|
+
loading,
|
|
7984
|
+
error,
|
|
7985
|
+
refresh
|
|
7957
7986
|
]);
|
|
7987
|
+
}
|
|
7988
|
+
//#endregion
|
|
7989
|
+
//#region src/hooks/navigation/useResolvedCollections.ts
|
|
7990
|
+
/**
|
|
7991
|
+
* Hook that resolves collection props (which may be async builders or arrays)
|
|
7992
|
+
* into concrete EntityCollection[], and registers them with the CollectionRegistry.
|
|
7993
|
+
*
|
|
7994
|
+
* When userManagement is provided, the default users collection is always
|
|
7995
|
+
* prepended. Developer collections override via generic slug-based dedup
|
|
7996
|
+
* (Map keyed by slug, last-write-wins). No hardcoded string checks.
|
|
7997
|
+
*
|
|
7998
|
+
* Uses refs for potentially-unstable dependencies (driver, authController,
|
|
7999
|
+
* plugins) to avoid re-triggering effects when their object identity changes.
|
|
8000
|
+
*/
|
|
8001
|
+
function useResolvedCollections(props) {
|
|
8002
|
+
const { authController, collections: collectionsProp, data, plugins, disabled, collectionRegistryController } = props;
|
|
8003
|
+
const userIdentity = authController.user ? `${authController.user.uid}:${(authController.user.roles ?? []).sort().join(",")}` : null;
|
|
8004
|
+
const dataRef = useRef(data);
|
|
8005
|
+
dataRef.current = data;
|
|
8006
|
+
const authControllerRef = useRef(authController);
|
|
8007
|
+
authControllerRef.current = authController;
|
|
8008
|
+
const pluginsRef = useRef(plugins);
|
|
8009
|
+
pluginsRef.current = plugins;
|
|
8010
|
+
const { data: collections, loading, error, refresh } = useAsyncResolver({
|
|
8011
|
+
resolver: async () => {
|
|
8012
|
+
const deduped = [...await resolveCollections(collectionsProp, authControllerRef.current, dataRef.current, pluginsRef.current)];
|
|
8013
|
+
if (collectionRegistryController.collectionRegistryRef.current.registerMultiple(deduped)) console.debug("Collections have changed", deduped);
|
|
8014
|
+
return deduped;
|
|
8015
|
+
},
|
|
8016
|
+
initialValue: [],
|
|
8017
|
+
isEqual: areCollectionListsEqual,
|
|
8018
|
+
deps: [
|
|
8019
|
+
collectionsProp,
|
|
8020
|
+
userIdentity,
|
|
8021
|
+
disabled
|
|
8022
|
+
],
|
|
8023
|
+
disabled: disabled || authController.initialLoading
|
|
8024
|
+
});
|
|
7958
8025
|
return useMemo(() => ({
|
|
7959
|
-
collections
|
|
8026
|
+
collections,
|
|
7960
8027
|
loading,
|
|
7961
8028
|
error,
|
|
7962
8029
|
refresh
|
|
7963
8030
|
}), [
|
|
7964
|
-
|
|
8031
|
+
collections,
|
|
7965
8032
|
loading,
|
|
7966
8033
|
error,
|
|
7967
8034
|
refresh
|
|
@@ -7982,6 +8049,13 @@ function viewSlugsEqual(a, b) {
|
|
|
7982
8049
|
return true;
|
|
7983
8050
|
}
|
|
7984
8051
|
/**
|
|
8052
|
+
* Equality check for the combined views data.
|
|
8053
|
+
* Compares both views and adminViews by slug identity.
|
|
8054
|
+
*/
|
|
8055
|
+
function areResolvedViewsEqual(a, b) {
|
|
8056
|
+
return viewSlugsEqual(a.views, b.views) && viewSlugsEqual(a.adminViews, b.adminViews);
|
|
8057
|
+
}
|
|
8058
|
+
/**
|
|
7985
8059
|
* Hook that resolves view and admin view props (which may be async builders or arrays)
|
|
7986
8060
|
* into concrete AppView[].
|
|
7987
8061
|
*
|
|
@@ -7990,16 +8064,7 @@ function viewSlugsEqual(a, b) {
|
|
|
7990
8064
|
*/
|
|
7991
8065
|
function useResolvedViews(props) {
|
|
7992
8066
|
const { authController, views: viewsProp, adminViews: adminViewsProp, data, plugins, adminMode = "content", effectiveRoleController } = props;
|
|
7993
|
-
const
|
|
7994
|
-
const [error, setError] = useState(void 0);
|
|
7995
|
-
const [resolvedViews, setResolvedViews] = useState(void 0);
|
|
7996
|
-
const [resolvedAdminViews, setResolvedAdminViews] = useState(void 0);
|
|
7997
|
-
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
7998
|
-
const refresh = useCallback(() => {
|
|
7999
|
-
setRefreshTrigger((prev) => prev + 1);
|
|
8000
|
-
}, []);
|
|
8001
|
-
const viewsRef = useRef(void 0);
|
|
8002
|
-
const adminViewsRef = useRef(void 0);
|
|
8067
|
+
const userIdentity = authController.user ? `${authController.user.uid}:${(authController.user.roles ?? []).sort().join(",")}` : null;
|
|
8003
8068
|
const dataRef = useRef(data);
|
|
8004
8069
|
dataRef.current = data;
|
|
8005
8070
|
const authControllerRef = useRef(authController);
|
|
@@ -8023,51 +8088,35 @@ function useResolvedViews(props) {
|
|
|
8023
8088
|
const resolvedAuthControllerRef = useRef(resolvedAuthController);
|
|
8024
8089
|
resolvedAuthControllerRef.current = resolvedAuthController;
|
|
8025
8090
|
const initialLoading = resolvedAuthController.initialLoading;
|
|
8026
|
-
const
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
} finally {
|
|
8048
|
-
if (!cancelled) setLoading(false);
|
|
8049
|
-
}
|
|
8050
|
-
})();
|
|
8051
|
-
return () => {
|
|
8052
|
-
cancelled = true;
|
|
8053
|
-
};
|
|
8054
|
-
}, [
|
|
8055
|
-
viewsProp,
|
|
8056
|
-
adminViewsProp,
|
|
8057
|
-
refreshTrigger,
|
|
8058
|
-
adminMode,
|
|
8059
|
-
initialLoading,
|
|
8060
|
-
user
|
|
8061
|
-
]);
|
|
8091
|
+
const { data: resolvedData, loading, error, refresh } = useAsyncResolver({
|
|
8092
|
+
resolver: async () => {
|
|
8093
|
+
const [newViews, newAdminViews] = await Promise.all([resolveAppViews(viewsProp, resolvedAuthControllerRef.current, dataRef.current, pluginsRef.current), resolveAppViews(adminViewsProp, resolvedAuthControllerRef.current, dataRef.current)]);
|
|
8094
|
+
return {
|
|
8095
|
+
views: newViews,
|
|
8096
|
+
adminViews: newAdminViews
|
|
8097
|
+
};
|
|
8098
|
+
},
|
|
8099
|
+
initialValue: {
|
|
8100
|
+
views: void 0,
|
|
8101
|
+
adminViews: void 0
|
|
8102
|
+
},
|
|
8103
|
+
isEqual: areResolvedViewsEqual,
|
|
8104
|
+
deps: [
|
|
8105
|
+
viewsProp,
|
|
8106
|
+
adminViewsProp,
|
|
8107
|
+
adminMode,
|
|
8108
|
+
userIdentity
|
|
8109
|
+
],
|
|
8110
|
+
disabled: initialLoading
|
|
8111
|
+
});
|
|
8062
8112
|
return useMemo(() => ({
|
|
8063
|
-
views:
|
|
8064
|
-
adminViews:
|
|
8113
|
+
views: resolvedData.views,
|
|
8114
|
+
adminViews: resolvedData.adminViews,
|
|
8065
8115
|
loading,
|
|
8066
8116
|
error,
|
|
8067
8117
|
refresh
|
|
8068
8118
|
}), [
|
|
8069
|
-
|
|
8070
|
-
resolvedAdminViews,
|
|
8119
|
+
resolvedData,
|
|
8071
8120
|
loading,
|
|
8072
8121
|
error,
|
|
8073
8122
|
refresh
|
|
@@ -8176,21 +8225,24 @@ function useTopLevelNavigation(props) {
|
|
|
8176
8225
|
}, [])
|
|
8177
8226
|
];
|
|
8178
8227
|
const groupOrderValue = (groupName) => {
|
|
8179
|
-
if (groupName === "Admin") return 1;
|
|
8228
|
+
if (groupName === "Admin" || groupName === "Settings" || groupName === "Admin") return 1;
|
|
8180
8229
|
return 0;
|
|
8181
8230
|
};
|
|
8182
|
-
navigationEntries = navigationEntries.sort((a, b) => {
|
|
8183
|
-
return groupOrderValue(a.group) - groupOrderValue(b.group);
|
|
8184
|
-
});
|
|
8185
8231
|
const usedViewsOrder = viewsOrder ?? navigationEntriesOrder;
|
|
8186
|
-
|
|
8187
|
-
const
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8232
|
+
navigationEntries = navigationEntries.sort((a, b) => {
|
|
8233
|
+
const groupDiff = groupOrderValue(a.group) - groupOrderValue(b.group);
|
|
8234
|
+
if (groupDiff !== 0) return groupDiff;
|
|
8235
|
+
if (usedViewsOrder) {
|
|
8236
|
+
const getSortPath = (navEntry) => typeof navEntry.slug === "string" ? navEntry.slug : navEntry.slug[0];
|
|
8237
|
+
const aIndex = usedViewsOrder.indexOf(getSortPath(a));
|
|
8238
|
+
const bIndex = usedViewsOrder.indexOf(getSortPath(b));
|
|
8239
|
+
if (aIndex !== -1 || bIndex !== -1) {
|
|
8240
|
+
if (aIndex === -1) return 1;
|
|
8241
|
+
if (bIndex === -1) return -1;
|
|
8242
|
+
return aIndex - bIndex;
|
|
8243
|
+
}
|
|
8244
|
+
}
|
|
8245
|
+
return 0;
|
|
8194
8246
|
});
|
|
8195
8247
|
const collectedGroupsFromEntries = navigationEntries.map((e) => e.group).filter(Boolean);
|
|
8196
8248
|
const groupsFromMappings = finalNavigationGroupMappings.map((g) => g.name);
|
|
@@ -8201,8 +8253,8 @@ function useTopLevelNavigation(props) {
|
|
|
8201
8253
|
...additionalGroups
|
|
8202
8254
|
];
|
|
8203
8255
|
const uniqueGroupsArray = [...new Set(allDefinedGroups)];
|
|
8204
|
-
const
|
|
8205
|
-
const uniqueGroups = [...uniqueGroupsArray.filter((g) => g !==
|
|
8256
|
+
const lastGroups = uniqueGroupsArray.filter((g) => g === "Admin" || g === "Settings" || g === "Admin");
|
|
8257
|
+
const uniqueGroups = [...uniqueGroupsArray.filter((g) => g !== "Admin" && g !== "Settings" && g !== "Admin"), ...lastGroups];
|
|
8206
8258
|
const computedTopLevelNav = {
|
|
8207
8259
|
allowDragAndDrop: plugins?.some((plugin) => plugin.hooks?.allowDragAndDrop) ?? false,
|
|
8208
8260
|
navigationEntries,
|
|
@@ -8269,9 +8321,15 @@ function useBuildNavigationStateController(props) {
|
|
|
8269
8321
|
adminMode,
|
|
8270
8322
|
collectionRegistryController
|
|
8271
8323
|
});
|
|
8324
|
+
const pendingRefreshRef = useRef(false);
|
|
8272
8325
|
const refreshNavigation = useCallback(() => {
|
|
8273
|
-
|
|
8274
|
-
|
|
8326
|
+
if (pendingRefreshRef.current) return;
|
|
8327
|
+
pendingRefreshRef.current = true;
|
|
8328
|
+
queueMicrotask(() => {
|
|
8329
|
+
pendingRefreshRef.current = false;
|
|
8330
|
+
refreshCollections();
|
|
8331
|
+
refreshViews();
|
|
8332
|
+
});
|
|
8275
8333
|
}, [refreshCollections, refreshViews]);
|
|
8276
8334
|
return useMemo(() => ({
|
|
8277
8335
|
views,
|
|
@@ -8279,8 +8337,7 @@ function useBuildNavigationStateController(props) {
|
|
|
8279
8337
|
topLevelNavigation,
|
|
8280
8338
|
loading: collectionsLoading || viewsLoading,
|
|
8281
8339
|
navigationLoadingError: collectionsError ?? viewsError,
|
|
8282
|
-
refreshNavigation
|
|
8283
|
-
plugins
|
|
8340
|
+
refreshNavigation
|
|
8284
8341
|
}), [
|
|
8285
8342
|
views,
|
|
8286
8343
|
adminViews,
|
|
@@ -8289,8 +8346,7 @@ function useBuildNavigationStateController(props) {
|
|
|
8289
8346
|
viewsLoading,
|
|
8290
8347
|
collectionsError,
|
|
8291
8348
|
viewsError,
|
|
8292
|
-
refreshNavigation
|
|
8293
|
-
plugins
|
|
8349
|
+
refreshNavigation
|
|
8294
8350
|
]);
|
|
8295
8351
|
}
|
|
8296
8352
|
//#endregion
|
|
@@ -8352,14 +8408,18 @@ var BreadcrumbContext = React.createContext({
|
|
|
8352
8408
|
var BreadcrumbsProvider = ({ children }) => {
|
|
8353
8409
|
const [breadcrumbs, setBreadcrumbs] = useState([]);
|
|
8354
8410
|
const set = useCallback((props) => {
|
|
8355
|
-
setBreadcrumbs((prev) =>
|
|
8356
|
-
const
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8411
|
+
setBreadcrumbs((prev) => {
|
|
8412
|
+
const next = props.breadcrumbs.map((newEntry) => {
|
|
8413
|
+
const prevEntry = newEntry.id ? prev.find((p) => p.id === newEntry.id) : void 0;
|
|
8414
|
+
if (prevEntry && newEntry.count === null && typeof prevEntry.count === "number") return {
|
|
8415
|
+
...newEntry,
|
|
8416
|
+
count: prevEntry.count
|
|
8417
|
+
};
|
|
8418
|
+
return newEntry;
|
|
8419
|
+
});
|
|
8420
|
+
if (prev.length === next.length && prev.every((p, i) => p.title === next[i].title && p.url === next[i].url && p.id === next[i].id && p.count === next[i].count)) return prev;
|
|
8421
|
+
return next;
|
|
8422
|
+
});
|
|
8363
8423
|
}, []);
|
|
8364
8424
|
const updateCount = useCallback((id, count) => {
|
|
8365
8425
|
setBreadcrumbs((prev) => prev.map((entry) => entry.id === id ? {
|
|
@@ -8502,11 +8562,7 @@ function SideDialogView({ offsetPosition, panel, panelIndex, isTopPanel }) {
|
|
|
8502
8562
|
overlayStyle: { zIndex: 40 + panelIndex * 10 },
|
|
8503
8563
|
style: { zIndex: 45 + panelIndex * 10 },
|
|
8504
8564
|
onOpenChange: (open) => {
|
|
8505
|
-
if (!open)
|
|
8506
|
-
const suggestionMenu = document.querySelector("[data-suggestion-menu=\"true\"]");
|
|
8507
|
-
if (suggestionMenu && window.getComputedStyle(suggestionMenu).visibility !== "hidden") return;
|
|
8508
|
-
onCloseRequest();
|
|
8509
|
-
}
|
|
8565
|
+
if (!open) onCloseRequest();
|
|
8510
8566
|
},
|
|
8511
8567
|
onPointerDownOutside: !isTopPanel ? preventDismiss : void 0,
|
|
8512
8568
|
onInteractOutside: !isTopPanel ? preventDismiss : void 0,
|
|
@@ -8969,7 +9025,7 @@ function FormEntry({ propertyKey, widthPercentage = 100, children }) {
|
|
|
8969
9025
|
//#region src/form/components/FormLayout.tsx
|
|
8970
9026
|
function FormLayout({ children, className }) {
|
|
8971
9027
|
return /* @__PURE__ */ jsx("div", {
|
|
8972
|
-
className: cls("flex flex-wrap gap-x-4 w-full space-y-
|
|
9028
|
+
className: cls("flex flex-wrap gap-x-4 w-full space-y-6", className),
|
|
8973
9029
|
children
|
|
8974
9030
|
});
|
|
8975
9031
|
}
|
|
@@ -8994,7 +9050,7 @@ function ReadOnlyFieldBinding({ propertyKey, value, error, showError, minimalist
|
|
|
8994
9050
|
className: "h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"
|
|
8995
9051
|
}),
|
|
8996
9052
|
/* @__PURE__ */ jsx("div", {
|
|
8997
|
-
className: cls(
|
|
9053
|
+
className: cls("w-full overflow-x-scroll no-scrollbar flex items-center", skipCardWrapper ? "" : "rounded-lg border border-surface-200 dark:border-surface-700 px-4 md:px-6 min-h-10 opacity-80"),
|
|
8998
9054
|
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(PropertyPreview, {
|
|
8999
9055
|
propertyKey,
|
|
9000
9056
|
value,
|
|
@@ -9281,7 +9337,7 @@ var buildIdsMap = (value) => value && Array.isArray(value) && value.length > 0 ?
|
|
|
9281
9337
|
...a,
|
|
9282
9338
|
...b
|
|
9283
9339
|
}), {}) : {};
|
|
9284
|
-
function SortableItem
|
|
9340
|
+
function SortableItem({ id, index, size, disabled, buildEntry, remove, copy, addInIndex, canAddElements, sortable, storedProps, updateItemCustomProps }) {
|
|
9285
9341
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
|
|
9286
9342
|
return /* @__PURE__ */ jsx(ArrayContainerItem, {
|
|
9287
9343
|
nodeRef: setNodeRef,
|
|
@@ -9510,7 +9566,7 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
|
|
|
9510
9566
|
children: /* @__PURE__ */ jsxs("div", {
|
|
9511
9567
|
className: cls("space-y-1", className),
|
|
9512
9568
|
id: droppableId,
|
|
9513
|
-
children: [hasValue && internalIds.map((internalId, index) => /* @__PURE__ */ jsx(SortableItem
|
|
9569
|
+
children: [hasValue && internalIds.map((internalId, index) => /* @__PURE__ */ jsx(SortableItem, {
|
|
9514
9570
|
id: internalId,
|
|
9515
9571
|
index,
|
|
9516
9572
|
size,
|
|
@@ -13592,189 +13648,57 @@ function EntityCard({ entity, collection, onClick, selected, highlighted, onSele
|
|
|
13592
13648
|
//#endregion
|
|
13593
13649
|
//#region src/components/EntityCollectionView/EntityCollectionCardView.tsx
|
|
13594
13650
|
/**
|
|
13595
|
-
* Get grid column classes based on the size.
|
|
13596
|
-
* Smaller size = more columns (smaller cards)
|
|
13597
|
-
* Larger size = fewer columns (larger cards)
|
|
13598
|
-
*/
|
|
13599
|
-
function getGridColumnsClass(size) {
|
|
13600
|
-
switch (size) {
|
|
13601
|
-
case "xs": return "grid-cols-4 sm:grid-cols-5 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10";
|
|
13602
|
-
case "s": return "grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-8";
|
|
13603
|
-
case "m": return "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6";
|
|
13604
|
-
case "l": return "grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-4";
|
|
13605
|
-
case "xl": return "grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3";
|
|
13606
|
-
default: return "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6";
|
|
13607
|
-
}
|
|
13608
|
-
}
|
|
13609
|
-
function getScrollParent$1(element) {
|
|
13610
|
-
if (!element) return null;
|
|
13611
|
-
let parent = element.parentElement;
|
|
13612
|
-
while (parent) {
|
|
13613
|
-
const overflowY = window.getComputedStyle(parent).overflowY;
|
|
13614
|
-
if (overflowY === "auto" || overflowY === "scroll") return parent;
|
|
13615
|
-
parent = parent.parentElement;
|
|
13616
|
-
}
|
|
13617
|
-
return document.documentElement;
|
|
13618
|
-
}
|
|
13619
|
-
/**
|
|
13620
13651
|
* Card grid view for displaying entities with infinite scroll.
|
|
13621
13652
|
* Alternative to the EntityCollectionTable for visual browsing.
|
|
13622
13653
|
*/
|
|
13623
13654
|
function EntityCollectionCardView({ collection, tableController, onEntityClick, selectionController, selectionEnabled = true, highlightedEntities, emptyComponent, onScroll, initialScroll, size = "m" }) {
|
|
13624
13655
|
const ResolvedEntityCard = useComponentOverride("Collection.Card", EntityCard);
|
|
13625
|
-
const containerRef = useRef(null);
|
|
13626
|
-
const hasRestoredScroll = useRef(false);
|
|
13627
13656
|
const { data, dataLoading, noMoreToLoad, dataLoadingError, itemCount, setItemCount, pageSize = 50, paginationEnabled } = tableController;
|
|
13628
|
-
const isLoadingMore = useRef(false);
|
|
13629
|
-
const paginationStateRef = useRef({
|
|
13630
|
-
paginationEnabled,
|
|
13631
|
-
noMoreToLoad,
|
|
13632
|
-
dataLoading,
|
|
13633
|
-
itemCount,
|
|
13634
|
-
pageSize
|
|
13635
|
-
});
|
|
13636
|
-
useEffect(() => {
|
|
13637
|
-
paginationStateRef.current = {
|
|
13638
|
-
paginationEnabled,
|
|
13639
|
-
noMoreToLoad,
|
|
13640
|
-
dataLoading,
|
|
13641
|
-
itemCount,
|
|
13642
|
-
pageSize
|
|
13643
|
-
};
|
|
13644
|
-
}, [
|
|
13645
|
-
paginationEnabled,
|
|
13646
|
-
noMoreToLoad,
|
|
13647
|
-
dataLoading,
|
|
13648
|
-
itemCount,
|
|
13649
|
-
pageSize
|
|
13650
|
-
]);
|
|
13651
|
-
useEffect(() => {
|
|
13652
|
-
if (!dataLoading) isLoadingMore.current = false;
|
|
13653
|
-
}, [dataLoading]);
|
|
13654
|
-
useEffect(() => {
|
|
13655
|
-
const el = containerRef.current;
|
|
13656
|
-
if (!el) return;
|
|
13657
|
-
const scrollEl = getScrollParent$1(el);
|
|
13658
|
-
if (!scrollEl) return;
|
|
13659
|
-
let rafId = null;
|
|
13660
|
-
const update = () => {
|
|
13661
|
-
rafId = null;
|
|
13662
|
-
const { paginationEnabled: pe, noMoreToLoad: nm, itemCount: ic, pageSize: ps } = paginationStateRef.current;
|
|
13663
|
-
if (pe && !nm && !isLoadingMore.current && scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < 400) {
|
|
13664
|
-
isLoadingMore.current = true;
|
|
13665
|
-
setItemCount?.((ic ?? ps) + ps);
|
|
13666
|
-
}
|
|
13667
|
-
};
|
|
13668
|
-
const onScrollEvent = () => {
|
|
13669
|
-
if (rafId === null) rafId = requestAnimationFrame(update);
|
|
13670
|
-
};
|
|
13671
|
-
scrollEl.addEventListener("scroll", onScrollEvent, { passive: true });
|
|
13672
|
-
const ro = new ResizeObserver(() => update());
|
|
13673
|
-
ro.observe(scrollEl);
|
|
13674
|
-
update();
|
|
13675
|
-
return () => {
|
|
13676
|
-
scrollEl.removeEventListener("scroll", onScrollEvent);
|
|
13677
|
-
ro.disconnect();
|
|
13678
|
-
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
13679
|
-
};
|
|
13680
|
-
}, [setItemCount]);
|
|
13681
|
-
useEffect(() => {
|
|
13682
|
-
if (!containerRef.current || !initialScroll || hasRestoredScroll.current || data.length === 0) return;
|
|
13683
|
-
const scrollEl = getScrollParent$1(containerRef.current);
|
|
13684
|
-
if (!scrollEl) return;
|
|
13685
|
-
let attempts = 0;
|
|
13686
|
-
const maxAttempts = 5;
|
|
13687
|
-
const tryRestore = () => {
|
|
13688
|
-
if (scrollEl.scrollHeight >= initialScroll || attempts >= maxAttempts) {
|
|
13689
|
-
scrollEl.scrollTop = initialScroll;
|
|
13690
|
-
hasRestoredScroll.current = true;
|
|
13691
|
-
} else {
|
|
13692
|
-
attempts++;
|
|
13693
|
-
requestAnimationFrame(tryRestore);
|
|
13694
|
-
}
|
|
13695
|
-
};
|
|
13696
|
-
requestAnimationFrame(tryRestore);
|
|
13697
|
-
}, [initialScroll, data.length]);
|
|
13698
|
-
const lastScrollOffset = useRef(0);
|
|
13699
|
-
useEffect(() => {
|
|
13700
|
-
const el = containerRef.current;
|
|
13701
|
-
if (!el || !onScroll) return;
|
|
13702
|
-
const scrollEl = getScrollParent$1(el);
|
|
13703
|
-
if (!scrollEl) return;
|
|
13704
|
-
const handleScroll = () => {
|
|
13705
|
-
const currentOffset = scrollEl.scrollTop;
|
|
13706
|
-
const direction = currentOffset > lastScrollOffset.current ? "forward" : "backward";
|
|
13707
|
-
lastScrollOffset.current = currentOffset;
|
|
13708
|
-
onScroll({
|
|
13709
|
-
scrollDirection: direction,
|
|
13710
|
-
scrollOffset: currentOffset,
|
|
13711
|
-
scrollUpdateWasRequested: false
|
|
13712
|
-
});
|
|
13713
|
-
};
|
|
13714
|
-
scrollEl.addEventListener("scroll", handleScroll, { passive: true });
|
|
13715
|
-
return () => scrollEl.removeEventListener("scroll", handleScroll);
|
|
13716
|
-
}, [onScroll]);
|
|
13717
13657
|
const handleEntityClick = useCallback((entity) => {
|
|
13718
13658
|
onEntityClick?.(entity);
|
|
13719
13659
|
}, [onEntityClick]);
|
|
13720
13660
|
const handleSelectionChange = useCallback((entity, selected) => {
|
|
13721
13661
|
selectionController?.toggleEntitySelection(entity, selected);
|
|
13722
13662
|
}, [selectionController]);
|
|
13723
|
-
const
|
|
13724
|
-
|
|
13725
|
-
|
|
13726
|
-
|
|
13727
|
-
|
|
13728
|
-
|
|
13729
|
-
|
|
13730
|
-
|
|
13731
|
-
|
|
13732
|
-
|
|
13733
|
-
|
|
13734
|
-
|
|
13735
|
-
|
|
13736
|
-
|
|
13737
|
-
|
|
13738
|
-
|
|
13739
|
-
|
|
13740
|
-
|
|
13741
|
-
|
|
13742
|
-
|
|
13743
|
-
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
|
|
13747
|
-
|
|
13748
|
-
|
|
13749
|
-
|
|
13750
|
-
|
|
13751
|
-
|
|
13752
|
-
|
|
13753
|
-
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
|
|
13762
|
-
selectionEnabled,
|
|
13763
|
-
size
|
|
13764
|
-
}, `${entity.path}_${entity.id}`))
|
|
13765
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
13766
|
-
className: "flex items-center justify-center py-8",
|
|
13767
|
-
children: [dataLoading && /* @__PURE__ */ jsx(CircularProgress, { size: "small" }), !dataLoading && noMoreToLoad && data.length > 0 && /* @__PURE__ */ jsxs(Typography, {
|
|
13768
|
-
variant: "caption",
|
|
13769
|
-
color: "secondary",
|
|
13770
|
-
children: [
|
|
13771
|
-
"All ",
|
|
13772
|
-
data.length,
|
|
13773
|
-
" entries loaded"
|
|
13774
|
-
]
|
|
13775
|
-
})]
|
|
13776
|
-
})]
|
|
13777
|
-
}) })
|
|
13663
|
+
const selectedIds = useMemo(() => new Set(selectionController?.selectedEntities.map((e) => e.id)), [selectionController?.selectedEntities]);
|
|
13664
|
+
const highlightedIds = useMemo(() => new Set(highlightedEntities?.map((e) => e.id)), [highlightedEntities]);
|
|
13665
|
+
const handleRowSelectionChange = useCallback((entity, selected) => {
|
|
13666
|
+
handleSelectionChange(entity, selected);
|
|
13667
|
+
}, [handleSelectionChange]);
|
|
13668
|
+
return /* @__PURE__ */ jsx(CardView, {
|
|
13669
|
+
data,
|
|
13670
|
+
dataLoading,
|
|
13671
|
+
noMoreToLoad,
|
|
13672
|
+
dataLoadingError,
|
|
13673
|
+
itemCount,
|
|
13674
|
+
setItemCount,
|
|
13675
|
+
pageSize,
|
|
13676
|
+
paginationEnabled,
|
|
13677
|
+
onItemClick: handleEntityClick,
|
|
13678
|
+
selectedIds,
|
|
13679
|
+
highlightedIds,
|
|
13680
|
+
selectionEnabled,
|
|
13681
|
+
onSelectionChange: handleRowSelectionChange,
|
|
13682
|
+
onScroll,
|
|
13683
|
+
initialScroll,
|
|
13684
|
+
size,
|
|
13685
|
+
emptyComponent,
|
|
13686
|
+
renderCard: useCallback((entity, { selected, highlighted, onClick }) => /* @__PURE__ */ jsx(ResolvedEntityCard, {
|
|
13687
|
+
entity,
|
|
13688
|
+
collection,
|
|
13689
|
+
onClick,
|
|
13690
|
+
selected,
|
|
13691
|
+
highlighted,
|
|
13692
|
+
onSelectionChange: handleRowSelectionChange,
|
|
13693
|
+
selectionEnabled,
|
|
13694
|
+
size
|
|
13695
|
+
}, `${entity.path}_${entity.id}`), [
|
|
13696
|
+
collection,
|
|
13697
|
+
selectionEnabled,
|
|
13698
|
+
size,
|
|
13699
|
+
handleRowSelectionChange,
|
|
13700
|
+
ResolvedEntityCard
|
|
13701
|
+
])
|
|
13778
13702
|
});
|
|
13779
13703
|
}
|
|
13780
13704
|
//#endregion
|
|
@@ -13792,38 +13716,9 @@ function getRowClasses(size) {
|
|
|
13792
13716
|
default: return "py-3 px-5";
|
|
13793
13717
|
}
|
|
13794
13718
|
}
|
|
13795
|
-
/**
|
|
13796
|
-
* Estimated row height in pixels for virtualization, based on size.
|
|
13797
|
-
*/
|
|
13798
|
-
function getEstimatedRowHeight(size) {
|
|
13799
|
-
switch (size) {
|
|
13800
|
-
case "xs": return 44;
|
|
13801
|
-
case "s": return 52;
|
|
13802
|
-
case "m": return 64;
|
|
13803
|
-
case "l": return 76;
|
|
13804
|
-
case "xl": return 88;
|
|
13805
|
-
default: return 64;
|
|
13806
|
-
}
|
|
13807
|
-
}
|
|
13808
|
-
/** Number of extra rows rendered above/below the viewport. */
|
|
13809
|
-
var OVERSCAN_COUNT = 8;
|
|
13810
|
-
/** Threshold in pixels from the bottom of the scroll area to trigger loading more. */
|
|
13811
|
-
var LOAD_MORE_THRESHOLD = 400;
|
|
13812
13719
|
/** Stable empty array for when no list-view actions are available. */
|
|
13813
13720
|
var EMPTY_LIST_VIEW_ACTIONS = [];
|
|
13814
13721
|
/**
|
|
13815
|
-
* Walk up the DOM from `element` to find the nearest scrollable ancestor.
|
|
13816
|
-
*/
|
|
13817
|
-
function getScrollParent(element) {
|
|
13818
|
-
let parent = element?.parentElement ?? null;
|
|
13819
|
-
while (parent) {
|
|
13820
|
-
const style = getComputedStyle(parent);
|
|
13821
|
-
if (style.overflowY === "auto" || style.overflowY === "scroll" || style.overflow === "auto" || style.overflow === "scroll") return parent;
|
|
13822
|
-
parent = parent.parentElement;
|
|
13823
|
-
}
|
|
13824
|
-
return document.documentElement;
|
|
13825
|
-
}
|
|
13826
|
-
/**
|
|
13827
13722
|
* Returns true if a property type should NOT be rendered via
|
|
13828
13723
|
* PropertyPreview in list row columns (because it would blow up height).
|
|
13829
13724
|
*/
|
|
@@ -14077,10 +13972,10 @@ function EntityCollectionListView({ collection, tableController, onEntityClick,
|
|
|
14077
13972
|
const handleSelectionChange = useCallback((entity, selected) => {
|
|
14078
13973
|
selectionController?.toggleEntitySelection(entity, selected);
|
|
14079
13974
|
}, [selectionController]);
|
|
14080
|
-
|
|
13975
|
+
useCallback((entity) => {
|
|
14081
13976
|
return selectionController?.isEntitySelected(entity) ?? false;
|
|
14082
13977
|
}, [selectionController]);
|
|
14083
|
-
|
|
13978
|
+
useCallback((entity) => {
|
|
14084
13979
|
return highlightedEntities?.some((e) => e.id === entity.id && e.path === entity.path) ?? false;
|
|
14085
13980
|
}, [highlightedEntities]);
|
|
14086
13981
|
const getListViewActions = useCallback((entity) => {
|
|
@@ -14095,158 +13990,70 @@ function EntityCollectionListView({ collection, tableController, onEntityClick,
|
|
|
14095
13990
|
customizationController.entityActions
|
|
14096
13991
|
]);
|
|
14097
13992
|
const rowClasses = getRowClasses(size);
|
|
14098
|
-
const
|
|
14099
|
-
const
|
|
14100
|
-
const
|
|
14101
|
-
|
|
14102
|
-
|
|
14103
|
-
|
|
14104
|
-
|
|
13993
|
+
const selectedIds = useMemo(() => new Set(selectionController?.selectedEntities.map((e) => e.id)), [selectionController?.selectedEntities]);
|
|
13994
|
+
const highlightedIds = useMemo(() => new Set(highlightedEntities?.map((e) => e.id)), [highlightedEntities]);
|
|
13995
|
+
const handleRowSelectionChange = useCallback((entity, selected) => {
|
|
13996
|
+
handleSelectionChange(entity, selected);
|
|
13997
|
+
}, [handleSelectionChange]);
|
|
13998
|
+
return /* @__PURE__ */ jsx(ListView, {
|
|
13999
|
+
data,
|
|
14000
|
+
dataLoading,
|
|
14105
14001
|
noMoreToLoad,
|
|
14002
|
+
dataLoadingError,
|
|
14106
14003
|
itemCount,
|
|
14107
|
-
|
|
14108
|
-
|
|
14109
|
-
useEffect(() => {
|
|
14110
|
-
paginationStateRef.current = {
|
|
14111
|
-
paginationEnabled,
|
|
14112
|
-
noMoreToLoad,
|
|
14113
|
-
itemCount,
|
|
14114
|
-
pageSize
|
|
14115
|
-
};
|
|
14116
|
-
}, [
|
|
14004
|
+
setItemCount,
|
|
14005
|
+
pageSize,
|
|
14117
14006
|
paginationEnabled,
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
14123
|
-
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
|
|
14135
|
-
|
|
14136
|
-
|
|
14137
|
-
|
|
14138
|
-
|
|
14139
|
-
|
|
14140
|
-
|
|
14141
|
-
|
|
14142
|
-
|
|
14143
|
-
|
|
14144
|
-
|
|
14145
|
-
|
|
14146
|
-
|
|
14147
|
-
|
|
14148
|
-
|
|
14149
|
-
ro.disconnect();
|
|
14150
|
-
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
14151
|
-
};
|
|
14152
|
-
}, [setItemCount]);
|
|
14153
|
-
const totalHeight = data.length * estimatedRowHeight;
|
|
14154
|
-
const startIndex = Math.max(0, Math.floor(effectiveScrollTop / estimatedRowHeight) - OVERSCAN_COUNT);
|
|
14155
|
-
const endIndex = Math.min(data.length, Math.ceil((effectiveScrollTop + viewportHeight) / estimatedRowHeight) + OVERSCAN_COUNT);
|
|
14156
|
-
const visibleData = data.slice(startIndex, endIndex);
|
|
14157
|
-
const offsetY = startIndex * estimatedRowHeight;
|
|
14158
|
-
const footerHeight = dataLoading ? 48 : !dataLoading && noMoreToLoad && data.length > 0 ? 32 : 0;
|
|
14159
|
-
return /* @__PURE__ */ jsx("div", {
|
|
14160
|
-
ref: containerRef,
|
|
14161
|
-
className: cls("w-full", selectedEntityId === void 0 && "rounded-lg overflow-hidden border " + defaultBorderMixin),
|
|
14162
|
-
children: dataLoadingError && data.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
14163
|
-
className: "flex items-center justify-center p-8",
|
|
14164
|
-
children: /* @__PURE__ */ jsxs(Typography, {
|
|
14165
|
-
className: "text-red-500",
|
|
14166
|
-
children: ["Error loading data: ", dataLoadingError.message]
|
|
14167
|
-
})
|
|
14168
|
-
}) : isInitialLoading ? /* @__PURE__ */ jsx("div", {
|
|
14169
|
-
className: "flex items-center justify-center py-12 px-8",
|
|
14170
|
-
children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
|
|
14171
|
-
}) : isEmpty ? /* @__PURE__ */ jsx("div", {
|
|
14172
|
-
className: "w-full flex items-center justify-center py-12 px-8",
|
|
14173
|
-
children: emptyComponent ?? /* @__PURE__ */ jsx(Typography, {
|
|
14174
|
-
variant: "label",
|
|
14175
|
-
color: "secondary",
|
|
14176
|
-
children: "No entries found"
|
|
14177
|
-
})
|
|
14178
|
-
}) : /* @__PURE__ */ jsxs("div", {
|
|
14179
|
-
style: {
|
|
14180
|
-
height: totalHeight + footerHeight,
|
|
14181
|
-
position: "relative"
|
|
14182
|
-
},
|
|
14183
|
-
children: [
|
|
14184
|
-
/* @__PURE__ */ jsx("div", {
|
|
14185
|
-
style: {
|
|
14186
|
-
position: "absolute",
|
|
14187
|
-
top: offsetY,
|
|
14188
|
-
left: 0,
|
|
14189
|
-
right: 0
|
|
14190
|
-
},
|
|
14191
|
-
children: visibleData.map((entity, i) => {
|
|
14192
|
-
const isLast = startIndex + i === data.length - 1;
|
|
14193
|
-
return /* @__PURE__ */ jsx("div", {
|
|
14194
|
-
style: { height: estimatedRowHeight },
|
|
14195
|
-
className: cls(!isLast && "border-b", !isLast && defaultBorderMixin),
|
|
14196
|
-
children: /* @__PURE__ */ jsx(ListRow, {
|
|
14197
|
-
entity,
|
|
14198
|
-
collection: resolvedCollection,
|
|
14199
|
-
onClick: handleEntityClick,
|
|
14200
|
-
selected: isEntitySelected(entity),
|
|
14201
|
-
highlighted: isEntityHighlighted(entity),
|
|
14202
|
-
onSelectionChange: handleSelectionChange,
|
|
14203
|
-
selectionEnabled,
|
|
14204
|
-
columns: visibleColumns,
|
|
14205
|
-
slotKeys,
|
|
14206
|
-
rowClasses,
|
|
14207
|
-
showImage,
|
|
14208
|
-
size,
|
|
14209
|
-
isLast,
|
|
14210
|
-
isActive: selectedEntityId !== void 0 && entity.id === selectedEntityId,
|
|
14211
|
-
listViewActions: getListViewActions(entity),
|
|
14212
|
-
context,
|
|
14213
|
-
path,
|
|
14214
|
-
selectionController,
|
|
14215
|
-
openEntityMode
|
|
14216
|
-
})
|
|
14217
|
-
}, entity.id);
|
|
14218
|
-
})
|
|
14219
|
-
}),
|
|
14220
|
-
dataLoading && /* @__PURE__ */ jsx("div", {
|
|
14221
|
-
className: "flex items-center justify-center py-3",
|
|
14222
|
-
style: {
|
|
14223
|
-
position: "absolute",
|
|
14224
|
-
top: totalHeight,
|
|
14225
|
-
left: 0,
|
|
14226
|
-
right: 0
|
|
14227
|
-
},
|
|
14228
|
-
children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
|
|
14229
|
-
}),
|
|
14230
|
-
!dataLoading && noMoreToLoad && data.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
14231
|
-
className: "flex items-center justify-center py-2 dark:bg-surface-900",
|
|
14232
|
-
style: {
|
|
14233
|
-
position: "absolute",
|
|
14234
|
-
top: totalHeight,
|
|
14235
|
-
left: 0,
|
|
14236
|
-
right: 0
|
|
14237
|
-
},
|
|
14238
|
-
children: /* @__PURE__ */ jsxs(Typography, {
|
|
14239
|
-
variant: "caption",
|
|
14240
|
-
color: "secondary",
|
|
14241
|
-
children: [
|
|
14242
|
-
"All ",
|
|
14243
|
-
data.length,
|
|
14244
|
-
" entries loaded"
|
|
14245
|
-
]
|
|
14246
|
-
})
|
|
14007
|
+
onItemClick: handleEntityClick,
|
|
14008
|
+
selectedIds,
|
|
14009
|
+
highlightedIds,
|
|
14010
|
+
selectionEnabled,
|
|
14011
|
+
emptyComponent,
|
|
14012
|
+
size,
|
|
14013
|
+
selectedEntityId,
|
|
14014
|
+
renderRow: useCallback(({ item: entity, style, className, selected, highlighted, isLast, onClick, onSelectionChange }) => {
|
|
14015
|
+
return /* @__PURE__ */ jsx("div", {
|
|
14016
|
+
style,
|
|
14017
|
+
className,
|
|
14018
|
+
children: /* @__PURE__ */ jsx(ListRow, {
|
|
14019
|
+
entity,
|
|
14020
|
+
collection: resolvedCollection,
|
|
14021
|
+
onClick: handleEntityClick,
|
|
14022
|
+
selected,
|
|
14023
|
+
highlighted,
|
|
14024
|
+
onSelectionChange: handleRowSelectionChange,
|
|
14025
|
+
selectionEnabled,
|
|
14026
|
+
columns: visibleColumns,
|
|
14027
|
+
slotKeys,
|
|
14028
|
+
rowClasses,
|
|
14029
|
+
showImage,
|
|
14030
|
+
size,
|
|
14031
|
+
isLast,
|
|
14032
|
+
isActive: selectedEntityId !== void 0 && entity.id === selectedEntityId,
|
|
14033
|
+
listViewActions: getListViewActions(entity),
|
|
14034
|
+
context,
|
|
14035
|
+
path,
|
|
14036
|
+
selectionController,
|
|
14037
|
+
openEntityMode
|
|
14247
14038
|
})
|
|
14248
|
-
|
|
14249
|
-
}
|
|
14039
|
+
}, entity.id);
|
|
14040
|
+
}, [
|
|
14041
|
+
resolvedCollection,
|
|
14042
|
+
selectionEnabled,
|
|
14043
|
+
visibleColumns,
|
|
14044
|
+
slotKeys,
|
|
14045
|
+
rowClasses,
|
|
14046
|
+
showImage,
|
|
14047
|
+
size,
|
|
14048
|
+
selectedEntityId,
|
|
14049
|
+
getListViewActions,
|
|
14050
|
+
context,
|
|
14051
|
+
path,
|
|
14052
|
+
selectionController,
|
|
14053
|
+
openEntityMode,
|
|
14054
|
+
handleRowSelectionChange,
|
|
14055
|
+
handleEntityClick
|
|
14056
|
+
])
|
|
14250
14057
|
});
|
|
14251
14058
|
}
|
|
14252
14059
|
/**
|
|
@@ -14682,457 +14489,6 @@ function SplitListView({ collection, tableController, onEntityClick: externalOnE
|
|
|
14682
14489
|
});
|
|
14683
14490
|
}
|
|
14684
14491
|
//#endregion
|
|
14685
|
-
//#region src/components/EntityCollectionView/BoardSortableList.tsx
|
|
14686
|
-
function BoardSortableList({ columnId, items, ItemComponent, isDragging, isDragOverColumn, loading = false, hasMore = false, onLoadMore }) {
|
|
14687
|
-
const { setNodeRef } = useDroppable({
|
|
14688
|
-
id: columnId,
|
|
14689
|
-
data: { type: "ITEM-LIST" }
|
|
14690
|
-
});
|
|
14691
|
-
const sentinelRef = useRef(null);
|
|
14692
|
-
const isLoadingRef = useRef(false);
|
|
14693
|
-
isLoadingRef.current = loading;
|
|
14694
|
-
const lastLoadTimeRef = useRef(0);
|
|
14695
|
-
useEffect(() => {
|
|
14696
|
-
if (!sentinelRef.current || !hasMore || !onLoadMore) return;
|
|
14697
|
-
const sentinel = sentinelRef.current;
|
|
14698
|
-
const observer = new IntersectionObserver((entries) => {
|
|
14699
|
-
const now = Date.now();
|
|
14700
|
-
if (entries[0].isIntersecting && hasMore && !isLoadingRef.current && now - lastLoadTimeRef.current > 500) {
|
|
14701
|
-
lastLoadTimeRef.current = now;
|
|
14702
|
-
onLoadMore();
|
|
14703
|
-
}
|
|
14704
|
-
}, { threshold: .1 });
|
|
14705
|
-
observer.observe(sentinel);
|
|
14706
|
-
const rect = sentinel.getBoundingClientRect();
|
|
14707
|
-
const containerRect = sentinel.parentElement?.getBoundingClientRect();
|
|
14708
|
-
if (containerRect && rect.top < containerRect.bottom && rect.bottom > containerRect.top) {
|
|
14709
|
-
const now = Date.now();
|
|
14710
|
-
if (hasMore && !isLoadingRef.current && now - lastLoadTimeRef.current > 500) {
|
|
14711
|
-
lastLoadTimeRef.current = now;
|
|
14712
|
-
onLoadMore();
|
|
14713
|
-
}
|
|
14714
|
-
}
|
|
14715
|
-
return () => observer.disconnect();
|
|
14716
|
-
}, [hasMore, onLoadMore]);
|
|
14717
|
-
return /* @__PURE__ */ jsx("div", {
|
|
14718
|
-
ref: setNodeRef,
|
|
14719
|
-
className: useMemo(() => cls("flex flex-col p-2 transition-opacity duration-100 transition-bg ease-linear w-full overflow-y-auto no-scrollbar flex-1 rounded-md", isDragging && isDragOverColumn ? "bg-surface-accent-200 dark:bg-surface-900" : isDragging ? "bg-surface-50 dark:bg-surface-900 hover:bg-surface-accent-100 dark:hover:bg-surface-800" : "bg-surface-50 dark:bg-surface-900"), [isDragging, isDragOverColumn]),
|
|
14720
|
-
style: { minHeight: 80 },
|
|
14721
|
-
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
14722
|
-
items: items.map((i) => i.id),
|
|
14723
|
-
strategy: verticalListSortingStrategy,
|
|
14724
|
-
children: items.length === 0 && !loading ? /* @__PURE__ */ jsx("div", {
|
|
14725
|
-
className: "flex-1 flex items-center justify-center",
|
|
14726
|
-
children: /* @__PURE__ */ jsx("span", {
|
|
14727
|
-
className: "text-xs text-surface-400 dark:text-surface-500",
|
|
14728
|
-
children: "No items"
|
|
14729
|
-
})
|
|
14730
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [items.map((item, index) => /* @__PURE__ */ jsx(SortableItem, {
|
|
14731
|
-
item,
|
|
14732
|
-
index,
|
|
14733
|
-
columnId,
|
|
14734
|
-
ItemComponent
|
|
14735
|
-
}, item.id)), (loading || hasMore) && /* @__PURE__ */ jsx("div", {
|
|
14736
|
-
ref: sentinelRef,
|
|
14737
|
-
className: "flex items-center justify-center py-2 min-h-6",
|
|
14738
|
-
children: loading && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" })
|
|
14739
|
-
})] })
|
|
14740
|
-
})
|
|
14741
|
-
});
|
|
14742
|
-
}
|
|
14743
|
-
var SortableItem = memo(function SortableItem({ item, index, columnId, ItemComponent }) {
|
|
14744
|
-
const { setNodeRef, attributes, listeners, isDragging: isItemBeingDragged, transform, transition } = useSortable({
|
|
14745
|
-
id: item.id,
|
|
14746
|
-
data: {
|
|
14747
|
-
type: "ITEM",
|
|
14748
|
-
columnId
|
|
14749
|
-
}
|
|
14750
|
-
});
|
|
14751
|
-
return /* @__PURE__ */ jsx("div", {
|
|
14752
|
-
ref: setNodeRef,
|
|
14753
|
-
style: useMemo(() => ({
|
|
14754
|
-
transform: CSS.Transform.toString(transform),
|
|
14755
|
-
transition,
|
|
14756
|
-
zIndex: isItemBeingDragged ? 2 : 1,
|
|
14757
|
-
opacity: isItemBeingDragged ? 0 : 1
|
|
14758
|
-
}), [
|
|
14759
|
-
transform,
|
|
14760
|
-
transition,
|
|
14761
|
-
isItemBeingDragged
|
|
14762
|
-
]),
|
|
14763
|
-
...attributes,
|
|
14764
|
-
...listeners,
|
|
14765
|
-
children: /* @__PURE__ */ jsx(ItemComponent, {
|
|
14766
|
-
item,
|
|
14767
|
-
isDragging: isItemBeingDragged,
|
|
14768
|
-
index
|
|
14769
|
-
})
|
|
14770
|
-
});
|
|
14771
|
-
});
|
|
14772
|
-
//#endregion
|
|
14773
|
-
//#region src/components/EntityCollectionView/BoardColumnTitle.tsx
|
|
14774
|
-
function BoardColumnTitle({ children, className, color, ...props }) {
|
|
14775
|
-
const colorScheme = useMemo(() => {
|
|
14776
|
-
if (!color) return void 0;
|
|
14777
|
-
if (typeof color === "string") return getColorSchemeForKey(color);
|
|
14778
|
-
return color;
|
|
14779
|
-
}, [color]);
|
|
14780
|
-
return /* @__PURE__ */ jsxs(Typography, {
|
|
14781
|
-
variant: "subtitle2",
|
|
14782
|
-
component: "h4",
|
|
14783
|
-
className: cls("py-3 px-3 transition-colors duration-200 flex-grow select-none relative outline-none focus:outline focus:outline-2 focus:outline-offset-2 flex items-center gap-3", className),
|
|
14784
|
-
...props,
|
|
14785
|
-
children: [colorScheme && /* @__PURE__ */ jsx("div", {
|
|
14786
|
-
className: "w-3 h-3 rounded-full flex-shrink-0",
|
|
14787
|
-
style: { backgroundColor: colorScheme.darkColor ?? colorScheme.color }
|
|
14788
|
-
}), children]
|
|
14789
|
-
});
|
|
14790
|
-
}
|
|
14791
|
-
//#endregion
|
|
14792
|
-
//#region src/components/EntityCollectionView/BoardColumn.tsx
|
|
14793
|
-
var BoardColumn = memo(function BoardColumn({ id, title, items, ItemComponent, isDragging, isDragOverColumn, allowReorder = false, loading = false, hasMore = false, onLoadMore, onAddItem, totalCount, color, style }) {
|
|
14794
|
-
const { setNodeRef, attributes, listeners, isDragging: isColumnBeingDragged, transform, transition } = useSortable({
|
|
14795
|
-
id,
|
|
14796
|
-
data: { type: "COLUMN" },
|
|
14797
|
-
disabled: !allowReorder
|
|
14798
|
-
});
|
|
14799
|
-
const combinedStyle = useMemo(() => ({
|
|
14800
|
-
...style,
|
|
14801
|
-
transform: CSS.Translate.toString(transform),
|
|
14802
|
-
transition,
|
|
14803
|
-
zIndex: isColumnBeingDragged ? 2 : 1
|
|
14804
|
-
}), [
|
|
14805
|
-
style,
|
|
14806
|
-
transform,
|
|
14807
|
-
transition,
|
|
14808
|
-
isColumnBeingDragged
|
|
14809
|
-
]);
|
|
14810
|
-
const dragListeners = allowReorder ? listeners : {};
|
|
14811
|
-
const columnClassName = useMemo(() => cls("border h-full w-80 min-w-80 mx-2 flex flex-col rounded-md", defaultBorderMixin, isColumnBeingDragged ? "ring-2 ring-primary" : ""), [isColumnBeingDragged]);
|
|
14812
|
-
const headerClassName = useMemo(() => cls("flex items-center justify-between px-2 rounded-t-md transition-colors duration-200 ease-in-out", isColumnBeingDragged ? "bg-surface-100 dark:bg-surface-700" : "bg-surface-50 hover:bg-surface-100 dark:bg-surface-800 dark:hover:bg-surface-700", allowReorder ? "cursor-grab" : ""), [isColumnBeingDragged, allowReorder]);
|
|
14813
|
-
const itemIds = useMemo(() => items.map((i) => i.id), [items]);
|
|
14814
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
14815
|
-
ref: setNodeRef,
|
|
14816
|
-
style: combinedStyle,
|
|
14817
|
-
...attributes,
|
|
14818
|
-
className: columnClassName,
|
|
14819
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
14820
|
-
...dragListeners,
|
|
14821
|
-
className: headerClassName,
|
|
14822
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
14823
|
-
className: "flex items-center gap-2",
|
|
14824
|
-
children: [/* @__PURE__ */ jsx(BoardColumnTitle, {
|
|
14825
|
-
"aria-label": `${title} item list`,
|
|
14826
|
-
color,
|
|
14827
|
-
children: title
|
|
14828
|
-
}), totalCount !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
14829
|
-
className: "text-xs text-surface-500 dark:text-surface-400",
|
|
14830
|
-
children: totalCount
|
|
14831
|
-
})]
|
|
14832
|
-
}), onAddItem && /* @__PURE__ */ jsx(IconButton, {
|
|
14833
|
-
size: "small",
|
|
14834
|
-
onClick: (e) => {
|
|
14835
|
-
e.stopPropagation();
|
|
14836
|
-
onAddItem();
|
|
14837
|
-
},
|
|
14838
|
-
className: "opacity-60 hover:opacity-100",
|
|
14839
|
-
children: /* @__PURE__ */ jsx(PlusIcon, { size: iconSize.small })
|
|
14840
|
-
})]
|
|
14841
|
-
}), /* @__PURE__ */ jsx(SortableContext, {
|
|
14842
|
-
items: itemIds,
|
|
14843
|
-
strategy: verticalListSortingStrategy,
|
|
14844
|
-
children: /* @__PURE__ */ jsx(BoardSortableList, {
|
|
14845
|
-
columnId: id,
|
|
14846
|
-
items,
|
|
14847
|
-
ItemComponent,
|
|
14848
|
-
isDragging,
|
|
14849
|
-
isDragOverColumn,
|
|
14850
|
-
loading,
|
|
14851
|
-
hasMore,
|
|
14852
|
-
onLoadMore
|
|
14853
|
-
})
|
|
14854
|
-
})]
|
|
14855
|
-
});
|
|
14856
|
-
});
|
|
14857
|
-
//#endregion
|
|
14858
|
-
//#region src/components/EntityCollectionView/Board.tsx
|
|
14859
|
-
function Board({ data, columns: columnsProp, columnLabels, columnColors, className, assignColumn, allowColumnReorder = false, onColumnReorder, onItemsReorder, ItemComponent, columnLoadingState, onLoadMoreColumn, onAddItemToColumn, AddColumnComponent }) {
|
|
14860
|
-
const [activeItem, setActiveItem] = useState(null);
|
|
14861
|
-
const [activeColumn, setActiveColumn] = useState(null);
|
|
14862
|
-
const [isDragging, setIsDragging] = useState(false);
|
|
14863
|
-
const [dragOverColumnId, setDragOverColumnId] = useState(null);
|
|
14864
|
-
const grabOffsetRef = useRef({
|
|
14865
|
-
x: 0,
|
|
14866
|
-
y: 0
|
|
14867
|
-
});
|
|
14868
|
-
const [overlayPos, setOverlayPos] = useState(null);
|
|
14869
|
-
const handleMouseMove = useCallback((e) => {
|
|
14870
|
-
setOverlayPos({
|
|
14871
|
-
x: e.clientX - grabOffsetRef.current.x,
|
|
14872
|
-
y: e.clientY - grabOffsetRef.current.y
|
|
14873
|
-
});
|
|
14874
|
-
}, []);
|
|
14875
|
-
useEffect(() => {
|
|
14876
|
-
return () => {
|
|
14877
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
14878
|
-
window.removeEventListener("pointermove", handleMouseMove);
|
|
14879
|
-
};
|
|
14880
|
-
}, [handleMouseMove]);
|
|
14881
|
-
const [itemMapState, setItemMapState] = useState(() => {
|
|
14882
|
-
const dataColumnMap = data.reduce((prev, item) => ({
|
|
14883
|
-
...prev,
|
|
14884
|
-
[item.id]: assignColumn(item)
|
|
14885
|
-
}), {});
|
|
14886
|
-
return columnsProp.reduce((previous, column) => ({
|
|
14887
|
-
...previous,
|
|
14888
|
-
[column]: data.filter((item) => dataColumnMap[item.id] === column)
|
|
14889
|
-
}), {});
|
|
14890
|
-
});
|
|
14891
|
-
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }));
|
|
14892
|
-
useEffect(() => {
|
|
14893
|
-
if (isDragging) return;
|
|
14894
|
-
const dataColumnMap = data.reduce((prev, item) => ({
|
|
14895
|
-
...prev,
|
|
14896
|
-
[item.id]: assignColumn(item)
|
|
14897
|
-
}), {});
|
|
14898
|
-
const newItemMap = columnsProp.reduce((previous, column) => ({
|
|
14899
|
-
...previous,
|
|
14900
|
-
[column]: data.filter((item) => dataColumnMap[item.id] === column)
|
|
14901
|
-
}), {});
|
|
14902
|
-
setItemMapState((prevMap) => {
|
|
14903
|
-
let changed = false;
|
|
14904
|
-
for (const col of columnsProp) {
|
|
14905
|
-
const prevItems = prevMap[col] ?? [];
|
|
14906
|
-
const newItems = newItemMap[col] ?? [];
|
|
14907
|
-
if (prevItems.length !== newItems.length) {
|
|
14908
|
-
changed = true;
|
|
14909
|
-
continue;
|
|
14910
|
-
}
|
|
14911
|
-
for (let i = 0; i < prevItems.length; i++) if (prevItems[i].id !== newItems[i].id || prevItems[i].entity !== newItems[i].entity) {
|
|
14912
|
-
changed = true;
|
|
14913
|
-
break;
|
|
14914
|
-
}
|
|
14915
|
-
}
|
|
14916
|
-
if (!changed) return prevMap;
|
|
14917
|
-
const updated = {};
|
|
14918
|
-
for (const col of columnsProp) {
|
|
14919
|
-
const prevItems = prevMap[col] ?? [];
|
|
14920
|
-
const newItems = newItemMap[col] ?? [];
|
|
14921
|
-
const prevById = /* @__PURE__ */ new Map();
|
|
14922
|
-
for (const item of prevItems) prevById.set(item.id, item);
|
|
14923
|
-
updated[col] = newItems.map((newItem) => {
|
|
14924
|
-
const prev = prevById.get(newItem.id);
|
|
14925
|
-
if (prev && prev.entity === newItem.entity) return prev;
|
|
14926
|
-
return newItem;
|
|
14927
|
-
});
|
|
14928
|
-
}
|
|
14929
|
-
return updated;
|
|
14930
|
-
});
|
|
14931
|
-
}, [
|
|
14932
|
-
data,
|
|
14933
|
-
columnsProp,
|
|
14934
|
-
assignColumn
|
|
14935
|
-
]);
|
|
14936
|
-
const findColumnByItemId = (id) => {
|
|
14937
|
-
return Object.keys(itemMapState).find((col) => itemMapState[col]?.some((i) => i.id === id));
|
|
14938
|
-
};
|
|
14939
|
-
const handleDragStart = (event) => {
|
|
14940
|
-
setIsDragging(true);
|
|
14941
|
-
setDragOverColumnId(null);
|
|
14942
|
-
const { active } = event;
|
|
14943
|
-
const activatorEvt = event.activatorEvent;
|
|
14944
|
-
if (activatorEvt) {
|
|
14945
|
-
const target = activatorEvt.target;
|
|
14946
|
-
const rect = (target.closest("[role='button']") ?? target).getBoundingClientRect();
|
|
14947
|
-
grabOffsetRef.current = {
|
|
14948
|
-
x: activatorEvt.clientX - rect.left,
|
|
14949
|
-
y: activatorEvt.clientY - rect.top
|
|
14950
|
-
};
|
|
14951
|
-
setOverlayPos({
|
|
14952
|
-
x: rect.left,
|
|
14953
|
-
y: rect.top
|
|
14954
|
-
});
|
|
14955
|
-
}
|
|
14956
|
-
window.addEventListener("mousemove", handleMouseMove);
|
|
14957
|
-
window.addEventListener("pointermove", handleMouseMove);
|
|
14958
|
-
if (active.data.current?.type === "COLUMN") {
|
|
14959
|
-
const columnId = active.id;
|
|
14960
|
-
const column = columnsProp.find((col) => String(col) === columnId);
|
|
14961
|
-
if (column) setActiveColumn(column);
|
|
14962
|
-
} else if (active.data.current?.type === "ITEM") {
|
|
14963
|
-
const columnId = findColumnByItemId(active.id);
|
|
14964
|
-
if (columnId) {
|
|
14965
|
-
const item = itemMapState[columnId]?.find((i) => i.id === active.id);
|
|
14966
|
-
setActiveItem(item || null);
|
|
14967
|
-
}
|
|
14968
|
-
}
|
|
14969
|
-
};
|
|
14970
|
-
const handleDragOver = (event) => {
|
|
14971
|
-
const { active, over } = event;
|
|
14972
|
-
if (!over) {
|
|
14973
|
-
setDragOverColumnId(null);
|
|
14974
|
-
return;
|
|
14975
|
-
}
|
|
14976
|
-
let currentHoveredColumnId = null;
|
|
14977
|
-
const overId = over.id;
|
|
14978
|
-
const overDataType = over.data.current?.type;
|
|
14979
|
-
if (overDataType === "ITEM-LIST" || overDataType === "COLUMN") currentHoveredColumnId = overId;
|
|
14980
|
-
else if (overDataType === "ITEM") currentHoveredColumnId = findColumnByItemId(overId) || null;
|
|
14981
|
-
else if (columnsProp.includes(overId)) currentHoveredColumnId = overId;
|
|
14982
|
-
setDragOverColumnId(currentHoveredColumnId);
|
|
14983
|
-
if (active.data.current?.type !== "ITEM") return;
|
|
14984
|
-
const activeId = active.id;
|
|
14985
|
-
const activeColumn = findColumnByItemId(activeId);
|
|
14986
|
-
let overColumnForMove = findColumnByItemId(overId);
|
|
14987
|
-
if (!overColumnForMove && overDataType === "ITEM-LIST") overColumnForMove = overId;
|
|
14988
|
-
if (!overColumnForMove && columnsProp.includes(overId)) overColumnForMove = overId;
|
|
14989
|
-
if (!activeColumn || !overColumnForMove) return;
|
|
14990
|
-
if (activeColumn === overColumnForMove) return;
|
|
14991
|
-
if (itemMapState[overColumnForMove]?.some((i) => i.id === activeId)) return;
|
|
14992
|
-
setItemMapState((currentMap) => {
|
|
14993
|
-
const activeItems = [...currentMap[activeColumn] || []];
|
|
14994
|
-
const overItems = [...currentMap[overColumnForMove] || []];
|
|
14995
|
-
const activeIndex = activeItems.findIndex((i) => i.id === activeId);
|
|
14996
|
-
if (activeIndex === -1) return currentMap;
|
|
14997
|
-
let overIndex;
|
|
14998
|
-
if (overDataType === "ITEM-LIST" || columnsProp.includes(overId) && !findColumnByItemId(overId)) overIndex = overItems.length;
|
|
14999
|
-
else {
|
|
15000
|
-
overIndex = overItems.findIndex((i) => i.id === overId);
|
|
15001
|
-
if (overIndex !== -1) {
|
|
15002
|
-
const modifier = (active.rect.current.translated?.top ?? 0) + (active.rect.current.translated?.height ?? 0) / 2 > (over?.rect.top ?? 0) + (over?.rect.height ?? 0) / 2 ? 1 : 0;
|
|
15003
|
-
overIndex = overIndex >= 0 ? overIndex + modifier : overItems.length;
|
|
15004
|
-
} else overIndex = overItems.length;
|
|
15005
|
-
}
|
|
15006
|
-
const newItemMap = { ...currentMap };
|
|
15007
|
-
const [moved] = activeItems.splice(activeIndex, 1);
|
|
15008
|
-
overItems.splice(overIndex, 0, moved);
|
|
15009
|
-
newItemMap[activeColumn] = activeItems;
|
|
15010
|
-
newItemMap[overColumnForMove] = overItems;
|
|
15011
|
-
return newItemMap;
|
|
15012
|
-
});
|
|
15013
|
-
};
|
|
15014
|
-
const handleDragEnd = (event) => {
|
|
15015
|
-
const { active, over } = event;
|
|
15016
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
15017
|
-
window.removeEventListener("pointermove", handleMouseMove);
|
|
15018
|
-
setOverlayPos(null);
|
|
15019
|
-
setIsDragging(false);
|
|
15020
|
-
setActiveItem(null);
|
|
15021
|
-
setActiveColumn(null);
|
|
15022
|
-
setDragOverColumnId(null);
|
|
15023
|
-
if (!over) return;
|
|
15024
|
-
const activeId = active.id;
|
|
15025
|
-
const overId = over.id;
|
|
15026
|
-
if (active.data.current?.type === "COLUMN" && over.data.current?.type === "COLUMN" && activeId !== overId) {
|
|
15027
|
-
const oldIndex = columnsProp.findIndex((col) => String(col) === activeId);
|
|
15028
|
-
const newIndex = columnsProp.findIndex((col) => String(col) === overId);
|
|
15029
|
-
if (oldIndex !== -1 && newIndex !== -1 && onColumnReorder && allowColumnReorder) onColumnReorder(arrayMove([...columnsProp], oldIndex, newIndex));
|
|
15030
|
-
} else if (active.data.current?.type === "ITEM" && onItemsReorder) {
|
|
15031
|
-
const originalColumn = data.find((item) => item.id === activeId) ? assignColumn(data.find((item) => item.id === activeId)) : void 0;
|
|
15032
|
-
const currentColumn = findColumnByItemId(activeId);
|
|
15033
|
-
const overColumn = findColumnByItemId(overId) || currentColumn;
|
|
15034
|
-
const finalItemMapState = { ...itemMapState };
|
|
15035
|
-
if (currentColumn && currentColumn === overColumn) {
|
|
15036
|
-
const activeIndex = finalItemMapState[currentColumn].findIndex((i) => i.id === activeId);
|
|
15037
|
-
let overIndex = finalItemMapState[overColumn].findIndex((i) => i.id === overId);
|
|
15038
|
-
const overType = over.data.current?.type;
|
|
15039
|
-
if (overIndex === -1 && (overType === "COLUMN" || overType === "ITEM-LIST")) if (originalColumn === currentColumn) overIndex = finalItemMapState[overColumn].length;
|
|
15040
|
-
else overIndex = activeIndex;
|
|
15041
|
-
else if (overIndex === -1 && typeof over.data.current?.sortable?.index === "number") overIndex = over.data.current.sortable.index;
|
|
15042
|
-
if (activeIndex !== -1 && overIndex !== -1 && activeIndex !== overIndex) {
|
|
15043
|
-
finalItemMapState[currentColumn] = arrayMove(finalItemMapState[currentColumn], activeIndex, overIndex);
|
|
15044
|
-
setItemMapState(finalItemMapState);
|
|
15045
|
-
}
|
|
15046
|
-
}
|
|
15047
|
-
if (originalColumn !== currentColumn && originalColumn && currentColumn) onItemsReorder(finalItemMapState[currentColumn], {
|
|
15048
|
-
itemId: activeId,
|
|
15049
|
-
sourceColumn: originalColumn,
|
|
15050
|
-
targetColumn: currentColumn
|
|
15051
|
-
});
|
|
15052
|
-
else if (currentColumn) onItemsReorder(finalItemMapState[currentColumn], {
|
|
15053
|
-
itemId: activeId,
|
|
15054
|
-
sourceColumn: currentColumn,
|
|
15055
|
-
targetColumn: currentColumn
|
|
15056
|
-
});
|
|
15057
|
-
}
|
|
15058
|
-
};
|
|
15059
|
-
const customCollisionDetection = (args) => {
|
|
15060
|
-
const pointerCollisions = pointerWithin(args);
|
|
15061
|
-
if (pointerCollisions.length > 0) return pointerCollisions;
|
|
15062
|
-
return rectIntersection(args);
|
|
15063
|
-
};
|
|
15064
|
-
return /* @__PURE__ */ jsxs(DndContext, {
|
|
15065
|
-
sensors,
|
|
15066
|
-
collisionDetection: customCollisionDetection,
|
|
15067
|
-
onDragStart: handleDragStart,
|
|
15068
|
-
onDragOver: handleDragOver,
|
|
15069
|
-
onDragEnd: handleDragEnd,
|
|
15070
|
-
children: [
|
|
15071
|
-
/* @__PURE__ */ jsx(DragOverlay, {
|
|
15072
|
-
dropAnimation: null,
|
|
15073
|
-
style: { opacity: 0 },
|
|
15074
|
-
children: activeItem || activeColumn ? /* @__PURE__ */ jsx("div", { style: {
|
|
15075
|
-
width: 1,
|
|
15076
|
-
height: 1
|
|
15077
|
-
} }) : null
|
|
15078
|
-
}),
|
|
15079
|
-
overlayPos && (activeItem || activeColumn) && createPortal(/* @__PURE__ */ jsx("div", {
|
|
15080
|
-
className: document.documentElement.classList.contains("dark") ? "dark" : "",
|
|
15081
|
-
style: {
|
|
15082
|
-
position: "fixed",
|
|
15083
|
-
top: overlayPos.y,
|
|
15084
|
-
left: overlayPos.x,
|
|
15085
|
-
zIndex: 9999,
|
|
15086
|
-
pointerEvents: "none",
|
|
15087
|
-
touchAction: "none"
|
|
15088
|
-
},
|
|
15089
|
-
children: activeItem ? /* @__PURE__ */ jsx(ItemComponent, {
|
|
15090
|
-
item: activeItem,
|
|
15091
|
-
isDragging: true,
|
|
15092
|
-
index: -1,
|
|
15093
|
-
style: {
|
|
15094
|
-
boxShadow: "0 4px 16px rgba(0,0,0,0.15)",
|
|
15095
|
-
opacity: .9,
|
|
15096
|
-
width: "304px"
|
|
15097
|
-
}
|
|
15098
|
-
}) : activeColumn ? /* @__PURE__ */ jsx(BoardColumn, {
|
|
15099
|
-
index: -1,
|
|
15100
|
-
id: String(activeColumn),
|
|
15101
|
-
title: columnLabels?.[activeColumn] ?? String(activeColumn),
|
|
15102
|
-
items: itemMapState[String(activeColumn)] || [],
|
|
15103
|
-
ItemComponent,
|
|
15104
|
-
isDragging: true,
|
|
15105
|
-
isDragOverColumn: false
|
|
15106
|
-
}, String(activeColumn)) : null
|
|
15107
|
-
}), document.body),
|
|
15108
|
-
/* @__PURE__ */ jsx(SortableContext, {
|
|
15109
|
-
items: columnsProp.map(String),
|
|
15110
|
-
strategy: horizontalListSortingStrategy,
|
|
15111
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
15112
|
-
className: cls("p-2 md:p-3 lg:p-4 h-full min-w-full inline-flex", className),
|
|
15113
|
-
children: [columnsProp.map((key, index) => /* @__PURE__ */ jsx(BoardColumn, {
|
|
15114
|
-
index,
|
|
15115
|
-
id: String(key),
|
|
15116
|
-
title: columnLabels?.[key] ?? String(key),
|
|
15117
|
-
color: columnColors?.[key],
|
|
15118
|
-
items: itemMapState[String(key)] || [],
|
|
15119
|
-
ItemComponent,
|
|
15120
|
-
isDragging,
|
|
15121
|
-
isDragOverColumn: String(key) === dragOverColumnId,
|
|
15122
|
-
allowReorder: allowColumnReorder,
|
|
15123
|
-
loading: columnLoadingState?.[String(key)]?.loading,
|
|
15124
|
-
hasMore: columnLoadingState?.[String(key)]?.hasMore,
|
|
15125
|
-
totalCount: columnLoadingState?.[String(key)]?.totalCount,
|
|
15126
|
-
onLoadMore: onLoadMoreColumn ? () => onLoadMoreColumn(key) : void 0,
|
|
15127
|
-
onAddItem: onAddItemToColumn ? () => onAddItemToColumn(key) : void 0,
|
|
15128
|
-
style: { opacity: activeColumn === key ? 0 : 1 }
|
|
15129
|
-
}, String(key))), AddColumnComponent]
|
|
15130
|
-
})
|
|
15131
|
-
})
|
|
15132
|
-
]
|
|
15133
|
-
});
|
|
15134
|
-
}
|
|
15135
|
-
//#endregion
|
|
15136
14492
|
//#region src/components/EntityCollectionView/EntityBoardCard.tsx
|
|
15137
14493
|
/**
|
|
15138
14494
|
* Compact card component for displaying an entity in a Kanban board.
|
|
@@ -15143,7 +14499,7 @@ function Board({ data, columns: columnsProp, columnLabels, columnColors, classNa
|
|
|
15143
14499
|
* layout clean while providing intuitive selection affordance.
|
|
15144
14500
|
*/
|
|
15145
14501
|
function EntityBoardCardInner({ item, isDragging, isGroupedOver, style, collection, onClick, selected, onSelectionChange, selectionEnabled = false }) {
|
|
15146
|
-
const entity = item.
|
|
14502
|
+
const entity = item.data;
|
|
15147
14503
|
const slotKeys = useCollectionSlotKeys(collection, useAuthController(), useCustomizationController().propertyConfigs);
|
|
15148
14504
|
const slots = useMemo(() => resolveEntitySlots(entity, collection, slotKeys), [
|
|
15149
14505
|
entity,
|
|
@@ -15758,7 +15114,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
15758
15114
|
//#region src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts
|
|
15759
15115
|
function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderProperty, dataClient, context, boardDataController, analyticsController }) {
|
|
15760
15116
|
const handleItemsReorder = useCallback(async (items, moveInfo) => {
|
|
15761
|
-
const entity = items.find((item) => item.id === moveInfo?.itemId)?.
|
|
15117
|
+
const entity = items.find((item) => item.id === moveInfo?.itemId)?.data;
|
|
15762
15118
|
if (!entity || !moveInfo) return;
|
|
15763
15119
|
analyticsController.onAnalyticsEvent?.("kanban_card_moved", {
|
|
15764
15120
|
path: fullPath,
|
|
@@ -15772,8 +15128,8 @@ function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderPrope
|
|
|
15772
15128
|
if (orderProperty) {
|
|
15773
15129
|
const targetColumnItems = items;
|
|
15774
15130
|
const movedIndex = targetColumnItems.findIndex((item) => item.id === moveInfo.itemId);
|
|
15775
|
-
const prevKey = movedIndex > 0 ? targetColumnItems[movedIndex - 1].
|
|
15776
|
-
const nextKey = movedIndex < targetColumnItems.length - 1 ? targetColumnItems[movedIndex + 1].
|
|
15131
|
+
const prevKey = movedIndex > 0 ? targetColumnItems[movedIndex - 1].data.values?.[orderProperty] ?? null : null;
|
|
15132
|
+
const nextKey = movedIndex < targetColumnItems.length - 1 ? targetColumnItems[movedIndex + 1].data.values?.[orderProperty] ?? null : null;
|
|
15777
15133
|
try {
|
|
15778
15134
|
const a = prevKey;
|
|
15779
15135
|
let b = nextKey;
|
|
@@ -16012,7 +15368,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
|
|
|
16012
15368
|
const boardItems = useMemo(() => {
|
|
16013
15369
|
return allEntities.map((entity) => ({
|
|
16014
15370
|
id: String(entity.id),
|
|
16015
|
-
entity
|
|
15371
|
+
data: entity
|
|
16016
15372
|
}));
|
|
16017
15373
|
}, [allEntities]);
|
|
16018
15374
|
const columnLoadingState = useMemo(() => {
|
|
@@ -16031,7 +15387,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
|
|
|
16031
15387
|
const assignColumn = useCallback((item) => {
|
|
16032
15388
|
const column = entityColumnMap[item.id];
|
|
16033
15389
|
if (column) return column;
|
|
16034
|
-
const value = item.
|
|
15390
|
+
const value = item.data.values?.[columnProperty];
|
|
16035
15391
|
if (value && columns.includes(String(value))) return String(value);
|
|
16036
15392
|
return columns[0] || "";
|
|
16037
15393
|
}, [
|
|
@@ -16125,7 +15481,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
|
|
|
16125
15481
|
...props,
|
|
16126
15482
|
collection: collectionRef.current,
|
|
16127
15483
|
onClick: stableOnClick,
|
|
16128
|
-
selected: isEntitySelectedRef.current(props.item.
|
|
15484
|
+
selected: isEntitySelectedRef.current(props.item.data),
|
|
16129
15485
|
onSelectionChange: stableOnSelectionChange,
|
|
16130
15486
|
selectionEnabled: selectionEnabledRef.current
|
|
16131
15487
|
});
|
|
@@ -16227,7 +15583,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
|
|
|
16227
15583
|
}),
|
|
16228
15584
|
/* @__PURE__ */ jsx("div", {
|
|
16229
15585
|
className: "flex-1 overflow-auto no-scrollbar",
|
|
16230
|
-
children: /* @__PURE__ */ jsx(
|
|
15586
|
+
children: /* @__PURE__ */ jsx(KanbanView, {
|
|
16231
15587
|
data: boardItems,
|
|
16232
15588
|
columns,
|
|
16233
15589
|
columnLabels,
|
|
@@ -18733,7 +18089,7 @@ function DrawerNavigationGroup({ group, entries, collapsed, onToggleCollapsed, d
|
|
|
18733
18089
|
})
|
|
18734
18090
|
]
|
|
18735
18091
|
}), /* @__PURE__ */ jsx("div", {
|
|
18736
|
-
className: cls("transition-all duration-200 ease-in-out", "overflow-hidden", !hideHeader && "dark:bg-transparent",
|
|
18092
|
+
className: cls("transition-all duration-200 ease-in-out", "overflow-hidden", !hideHeader && "dark:bg-transparent", "rounded-lg", !hideHeader && collapsed ? "max-h-0 opacity-0" : "max-h-[2000px] opacity-100"),
|
|
18737
18093
|
children: entries.map((entry) => /* @__PURE__ */ jsx(ResolvedDrawerNavigationItem, {
|
|
18738
18094
|
icon: /* @__PURE__ */ jsx(IconForView, {
|
|
18739
18095
|
collectionOrView: entry.collection ?? entry.view,
|
|
@@ -18823,7 +18179,13 @@ function CMSNavigationContent() {
|
|
|
18823
18179
|
const registry = useRebaseRegistry();
|
|
18824
18180
|
const ResolvedDrawerNavigationGroup = useComponentOverride("Shell.DrawerNavigationGroup", DrawerNavigationGroup);
|
|
18825
18181
|
const allNavigationEntries = navigationState.topLevelNavigation?.navigationEntries ?? [];
|
|
18826
|
-
const
|
|
18182
|
+
const studioViewSlugs = useMemo(() => {
|
|
18183
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
18184
|
+
(registry.studioConfig?.devViews ?? []).forEach((v) => slugs.add(v.slug));
|
|
18185
|
+
if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
|
|
18186
|
+
return slugs;
|
|
18187
|
+
}, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
|
|
18188
|
+
const filteredEntries = adminModeController.mode === "studio" ? allNavigationEntries.filter((e) => e.type === "view" || e.type === "admin") : allNavigationEntries.filter((e) => e.type !== "view" || !studioViewSlugs.has(e.slug));
|
|
18827
18189
|
const entryGroups = new Set(filteredEntries.map((e) => e.group).filter(Boolean));
|
|
18828
18190
|
const orderedGroups = navigationState.topLevelNavigation?.groups ?? [];
|
|
18829
18191
|
const groupsToRender = [...orderedGroups.filter((g) => entryGroups.has(g)), ...[...entryGroups].filter((g) => !orderedGroups.includes(g))];
|
|
@@ -19261,11 +18623,12 @@ function DrawerWrapper(props) {
|
|
|
19261
18623
|
* is auto-wired as a native feature (slots, provider, Studio view) without
|
|
19262
18624
|
* needing any external plugin.
|
|
19263
18625
|
*/
|
|
19264
|
-
function RebaseCMS({ collections, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings }) {
|
|
18626
|
+
function RebaseCMS({ collections, views, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings }) {
|
|
19265
18627
|
const dispatch = useRebaseRegistryDispatch();
|
|
19266
18628
|
useLayoutEffect(() => {
|
|
19267
18629
|
dispatch.registerCMS({
|
|
19268
18630
|
collections,
|
|
18631
|
+
views,
|
|
19269
18632
|
homePage,
|
|
19270
18633
|
entityViews,
|
|
19271
18634
|
entityActions,
|
|
@@ -19277,6 +18640,7 @@ function RebaseCMS({ collections, homePage, entityViews, entityActions, plugins,
|
|
|
19277
18640
|
}, [
|
|
19278
18641
|
dispatch,
|
|
19279
18642
|
collections,
|
|
18643
|
+
views,
|
|
19280
18644
|
homePage,
|
|
19281
18645
|
entityViews,
|
|
19282
18646
|
entityActions,
|
|
@@ -19416,7 +18780,7 @@ function useLocalCollectionsConfigController(clientOrUrl, baseCollections = [],
|
|
|
19416
18780
|
//#region src/components/RebaseNavigation.tsx
|
|
19417
18781
|
var EMPTY_PLUGINS = [];
|
|
19418
18782
|
var EMPTY_COLLECTIONS = [];
|
|
19419
|
-
var CollectionsStudioView = lazy(() => import("./
|
|
18783
|
+
var CollectionsStudioView = lazy(() => import("./RouterCollectionsStudioView-Dqx27GD8.js").then((n) => n.n).then((m) => ({ default: m.RouterCollectionsStudioView })));
|
|
19420
18784
|
/**
|
|
19421
18785
|
* Navigation layer — builds and provides all CMS navigation controllers:
|
|
19422
18786
|
* collection registry, URL controller, navigation state, side entity,
|
|
@@ -19449,7 +18813,19 @@ function RebaseNavigation({ children }) {
|
|
|
19449
18813
|
}, [collectionEditorConfig]);
|
|
19450
18814
|
const cmsCollections = registry.cmsConfig?.collections ?? EMPTY_COLLECTIONS;
|
|
19451
18815
|
const collectionsBuilder = useMemo(() => Array.isArray(cmsCollections) ? () => [...cmsCollections] : cmsCollections, [cmsCollections]);
|
|
19452
|
-
const
|
|
18816
|
+
const dataSources = useDataSources();
|
|
18817
|
+
const collectionRegistryController = useBuildCollectionRegistryController({
|
|
18818
|
+
userConfigPersistence,
|
|
18819
|
+
dataSources: dataSources.registry
|
|
18820
|
+
});
|
|
18821
|
+
const defaultData = useData();
|
|
18822
|
+
const getCollectionRef = useRef(collectionRegistryController.getCollection);
|
|
18823
|
+
getCollectionRef.current = collectionRegistryController.getCollection;
|
|
18824
|
+
const routedData = useMemo(() => buildRoutedRebaseData({
|
|
18825
|
+
defaultData,
|
|
18826
|
+
sources: dataSources.sources,
|
|
18827
|
+
resolveKey: (slugOrPath) => resolveDataSource(getCollectionRef.current(slugOrPath), dataSources.registry).key
|
|
18828
|
+
}), [defaultData, dataSources]);
|
|
19453
18829
|
const urlController = useBuildUrlController({
|
|
19454
18830
|
basePath: "/",
|
|
19455
18831
|
baseCollectionPath: "/c",
|
|
@@ -19457,7 +18833,14 @@ function RebaseNavigation({ children }) {
|
|
|
19457
18833
|
});
|
|
19458
18834
|
const rebaseClient = useRebaseClient();
|
|
19459
18835
|
const authController = useAuthController();
|
|
19460
|
-
const
|
|
18836
|
+
const resolvedCollections = useMemo(() => Array.isArray(cmsCollections) ? cmsCollections : [], [cmsCollections]);
|
|
18837
|
+
useEffect(() => {
|
|
18838
|
+
if (process.env.NODE_ENV === "production") return;
|
|
18839
|
+
const { registry, sources } = dataSources;
|
|
18840
|
+
const missing = Object.values(registry).filter((d) => (d.transport === "direct" || d.transport === "custom") && !sources[d.key]).map((d) => d.key);
|
|
18841
|
+
if (missing.length > 0) console.warn(`[Rebase] These data source(s) declare a direct/custom transport but have no client-side driver and will fall back to the default data source: ${missing.map((k) => `"${k}"`).join(", ")}. Provide a \`driver\` for them in \`dataSources\` on <Rebase>.`);
|
|
18842
|
+
}, [dataSources]);
|
|
18843
|
+
const internalConfigController = useLocalCollectionsConfigController(rebaseClient, resolvedCollections, collectionEditorEnabled ? {
|
|
19461
18844
|
readOnly: collectionEditorOptions?.readOnly ?? process.env.NODE_ENV === "production",
|
|
19462
18845
|
getAuthToken: collectionEditorOptions?.getAuthToken ?? authController?.getAuthToken
|
|
19463
18846
|
} : { readOnly: true });
|
|
@@ -19474,6 +18857,7 @@ function RebaseNavigation({ children }) {
|
|
|
19474
18857
|
return {
|
|
19475
18858
|
slug: "schema",
|
|
19476
18859
|
name: "Edit collections",
|
|
18860
|
+
group: "Database",
|
|
19477
18861
|
icon: "LayoutList",
|
|
19478
18862
|
nestedRoutes: true,
|
|
19479
18863
|
view: /* @__PURE__ */ jsx(Suspense, {
|
|
@@ -19491,13 +18875,22 @@ function RebaseNavigation({ children }) {
|
|
|
19491
18875
|
if (schemaView) return [...base, schemaView];
|
|
19492
18876
|
return base;
|
|
19493
18877
|
}, [registry.studioConfig?.devViews, schemaView]);
|
|
18878
|
+
const cmsViews = registry.cmsConfig?.views;
|
|
18879
|
+
const mergedViews = useMemo(() => {
|
|
18880
|
+
if (!cmsViews) return devViews;
|
|
18881
|
+
if (Array.isArray(cmsViews) && cmsViews.length === 0) return devViews;
|
|
18882
|
+
if (typeof cmsViews === "function") return async (params) => {
|
|
18883
|
+
return [...await cmsViews(params) ?? [], ...devViews];
|
|
18884
|
+
};
|
|
18885
|
+
return [...cmsViews, ...devViews];
|
|
18886
|
+
}, [cmsViews, devViews]);
|
|
19494
18887
|
const navigationStateController = useBuildNavigationStateController({
|
|
19495
18888
|
plugins: registry.cmsConfig?.plugins ?? EMPTY_PLUGINS,
|
|
19496
18889
|
collections: collectionsBuilder,
|
|
19497
|
-
views:
|
|
18890
|
+
views: mergedViews,
|
|
19498
18891
|
navigationGroupMappings: registry.cmsConfig?.navigationGroupMappings,
|
|
19499
18892
|
authController: context.authController,
|
|
19500
|
-
data:
|
|
18893
|
+
data: routedData,
|
|
19501
18894
|
collectionRegistryController,
|
|
19502
18895
|
urlController,
|
|
19503
18896
|
adminMode: adminModeController?.mode
|
|
@@ -19517,22 +18910,25 @@ function RebaseNavigation({ children }) {
|
|
|
19517
18910
|
registry.cmsConfig?.entityViews,
|
|
19518
18911
|
registry.cmsConfig?.entityActions
|
|
19519
18912
|
]);
|
|
19520
|
-
const navigationContent = /* @__PURE__ */ jsx(
|
|
19521
|
-
value:
|
|
19522
|
-
children: /* @__PURE__ */ jsx(
|
|
19523
|
-
value:
|
|
19524
|
-
children: /* @__PURE__ */ jsx(
|
|
19525
|
-
value:
|
|
19526
|
-
children: /* @__PURE__ */ jsx(
|
|
19527
|
-
value:
|
|
19528
|
-
children: /* @__PURE__ */
|
|
19529
|
-
|
|
19530
|
-
|
|
19531
|
-
|
|
19532
|
-
|
|
18913
|
+
const navigationContent = /* @__PURE__ */ jsx(RebaseDataContext.Provider, {
|
|
18914
|
+
value: routedData,
|
|
18915
|
+
children: /* @__PURE__ */ jsx(CustomizationControllerContext.Provider, {
|
|
18916
|
+
value: enrichedCustomizationController,
|
|
18917
|
+
children: /* @__PURE__ */ jsx(StudioBridgeRegistryProvider, { children: /* @__PURE__ */ jsx(CollectionRegistryContext.Provider, {
|
|
18918
|
+
value: collectionRegistryController,
|
|
18919
|
+
children: /* @__PURE__ */ jsx(UrlContext.Provider, {
|
|
18920
|
+
value: urlController,
|
|
18921
|
+
children: /* @__PURE__ */ jsx(NavigationStateContext.Provider, {
|
|
18922
|
+
value: navigationStateController,
|
|
18923
|
+
children: /* @__PURE__ */ jsxs(SideEntityProvider, { children: [/* @__PURE__ */ jsx(BridgeAutoRegistrar, {
|
|
18924
|
+
collectionRegistryController,
|
|
18925
|
+
urlController,
|
|
18926
|
+
navigationStateController
|
|
18927
|
+
}), children] })
|
|
18928
|
+
})
|
|
19533
18929
|
})
|
|
19534
|
-
})
|
|
19535
|
-
})
|
|
18930
|
+
}) })
|
|
18931
|
+
})
|
|
19536
18932
|
});
|
|
19537
18933
|
if (collectionEditorEnabled) return /* @__PURE__ */ jsx(ConfigControllerProvider, {
|
|
19538
18934
|
collectionConfigController: internalConfigController,
|
|
@@ -20317,10 +19713,20 @@ function ContentHomePage({ additionalActions, additionalChildrenStart, additiona
|
|
|
20317
19713
|
breadcrumbs.set({ breadcrumbs: [] });
|
|
20318
19714
|
}, [breadcrumbs.set]);
|
|
20319
19715
|
const { allowDragAndDrop = false, navigationEntries: unFilteredNavigationEntries = [], groups: unFilteredGroupOrder = [], onNavigationEntriesUpdate = () => {} } = navigationStateController.topLevelNavigation || {};
|
|
19716
|
+
const studioViewSlugs = useMemo(() => {
|
|
19717
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
19718
|
+
(registry.studioConfig?.devViews ?? []).forEach((v) => slugs.add(v.slug));
|
|
19719
|
+
if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
|
|
19720
|
+
return slugs;
|
|
19721
|
+
}, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
|
|
20320
19722
|
const rawNavigationEntries = useMemo(() => {
|
|
20321
19723
|
if (adminModeController.mode === "studio") return unFilteredNavigationEntries.filter((e) => e.type === "view" || e.type === "admin");
|
|
20322
|
-
return unFilteredNavigationEntries.filter((e) => e.type !== "view");
|
|
20323
|
-
}, [
|
|
19724
|
+
return unFilteredNavigationEntries.filter((e) => e.type !== "view" || !studioViewSlugs.has(e.slug));
|
|
19725
|
+
}, [
|
|
19726
|
+
unFilteredNavigationEntries,
|
|
19727
|
+
adminModeController.mode,
|
|
19728
|
+
studioViewSlugs
|
|
19729
|
+
]);
|
|
20324
19730
|
const groupOrderFromNavController = useMemo(() => {
|
|
20325
19731
|
const entryGroups = new Set(rawNavigationEntries.map((e) => e.group).filter(Boolean));
|
|
20326
19732
|
return unFilteredGroupOrder.filter((g) => entryGroups.has(g));
|
|
@@ -20850,18 +20256,24 @@ function EntityFullScreenRoute({ pathname, navigationEntries, isNew, isCopy }) {
|
|
|
20850
20256
|
function CustomViewRoute({ view }) {
|
|
20851
20257
|
const breadcrumbs = useBreadcrumbsController();
|
|
20852
20258
|
const urlController = useUrlController();
|
|
20259
|
+
const breadcrumbsRef = useRef(breadcrumbs);
|
|
20260
|
+
breadcrumbsRef.current = breadcrumbs;
|
|
20853
20261
|
useEffect(() => {
|
|
20854
|
-
|
|
20262
|
+
breadcrumbsRef.current.set({ breadcrumbs: [{
|
|
20855
20263
|
title: view.name,
|
|
20856
20264
|
url: urlController.buildAppUrlPath(view.slug)
|
|
20857
20265
|
}] });
|
|
20858
20266
|
}, [view.slug, urlController]);
|
|
20859
|
-
|
|
20267
|
+
if (typeof view.view === "function") {
|
|
20268
|
+
const ViewComponent = view.view;
|
|
20269
|
+
return /* @__PURE__ */ jsx(ViewComponent, {});
|
|
20270
|
+
}
|
|
20271
|
+
return /* @__PURE__ */ jsx(Fragment, { children: view.view });
|
|
20860
20272
|
}
|
|
20861
20273
|
//#endregion
|
|
20862
20274
|
//#region src/components/CollectionEditorDialogs.tsx
|
|
20863
|
-
var CollectionEditorDialog = lazy(() => import("./CollectionEditorDialog-
|
|
20864
|
-
var PropertyFormDialog = lazy(() => import("./PropertyEditView-
|
|
20275
|
+
var CollectionEditorDialog = lazy(() => import("./CollectionEditorDialog-iGgS4rLX.js").then((n) => n.r).then((m) => ({ default: m.CollectionEditorDialog })));
|
|
20276
|
+
var PropertyFormDialog = lazy(() => import("./PropertyEditView-C9yhLyja.js").then((n) => n.t).then((m) => ({ default: m.PropertyFormDialog })));
|
|
20865
20277
|
/**
|
|
20866
20278
|
* Renders the CollectionEditorDialog and PropertyFormDialog inside
|
|
20867
20279
|
* the RebaseShell tree where CMS-internal contexts
|
|
@@ -22185,7 +21597,7 @@ function LocalChangesMenu({ cachedData, formex, onClearLocalChanges, cacheKey, p
|
|
|
22185
21597
|
})] });
|
|
22186
21598
|
}
|
|
22187
21599
|
//#endregion
|
|
22188
|
-
//#region src/form/
|
|
21600
|
+
//#region src/form/form_utils.ts
|
|
22189
21601
|
function extractTouchedValues(values, touched) {
|
|
22190
21602
|
let acc = {};
|
|
22191
21603
|
if (!touched || typeof touched !== "object") return acc;
|
|
@@ -22201,7 +21613,7 @@ function extractTouchedValues(values, touched) {
|
|
|
22201
21613
|
* from falsely triggering the unsaved local changes indicator.
|
|
22202
21614
|
*/
|
|
22203
21615
|
/**
|
|
22204
|
-
*
|
|
21616
|
+
* Check if a value is semantically empty (null, undefined, or empty string).
|
|
22205
21617
|
*/
|
|
22206
21618
|
function isSemanticEmpty(v) {
|
|
22207
21619
|
return v === null || v === void 0 || v === "";
|
|
@@ -22245,23 +21657,51 @@ function getChanges(source, comparison) {
|
|
|
22245
21657
|
}
|
|
22246
21658
|
return changes;
|
|
22247
21659
|
}
|
|
22248
|
-
function
|
|
22249
|
-
const
|
|
22250
|
-
|
|
22251
|
-
|
|
22252
|
-
|
|
22253
|
-
|
|
22254
|
-
|
|
22255
|
-
|
|
21660
|
+
function getInitialEntityValues(authController, collection, path, status, entity, propertyConfigs) {
|
|
21661
|
+
const properties = collection.properties;
|
|
21662
|
+
if ((status === "existing" || status === "copy") && entity) {
|
|
21663
|
+
let values;
|
|
21664
|
+
if (!collection.alwaysApplyDefaultValues) values = entity.values ?? getDefaultValuesFor(properties);
|
|
21665
|
+
else values = mergeDeep(getDefaultValuesFor(properties), entity.values ?? {});
|
|
21666
|
+
if (status === "copy") {
|
|
21667
|
+
const result = { ...values };
|
|
21668
|
+
for (const [key, property] of Object.entries(properties)) if (property && "isId" in property && property.isId) delete result[key];
|
|
21669
|
+
return result;
|
|
22256
21670
|
}
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
|
|
22264
|
-
|
|
21671
|
+
return values;
|
|
21672
|
+
} else if (status === "new") return getDefaultValuesFor(properties);
|
|
21673
|
+
else {
|
|
21674
|
+
console.error({
|
|
21675
|
+
status,
|
|
21676
|
+
entity
|
|
21677
|
+
});
|
|
21678
|
+
throw new Error("Form has not been initialised with the correct parameters");
|
|
21679
|
+
}
|
|
21680
|
+
}
|
|
21681
|
+
function zodToFormErrors(zodError) {
|
|
21682
|
+
let errors = {};
|
|
21683
|
+
for (const issue of zodError.issues) {
|
|
21684
|
+
const path = issue.path.join(".");
|
|
21685
|
+
if (path && !getIn(errors, path)) errors = setIn(errors, path, issue.message);
|
|
21686
|
+
}
|
|
21687
|
+
return errors;
|
|
21688
|
+
}
|
|
21689
|
+
//#endregion
|
|
21690
|
+
//#region src/form/EntityForm.tsx
|
|
21691
|
+
/**
|
|
21692
|
+
* Headless entity form component.
|
|
21693
|
+
*
|
|
21694
|
+
* Renders a form for an entity collection without any CMS or backend dependencies.
|
|
21695
|
+
* All backend concerns (save, caching, analytics, plugin slots) are provided via
|
|
21696
|
+
* callback props. For CMS-connected usage, use {@link EntityFormBinding} instead.
|
|
21697
|
+
*
|
|
21698
|
+
* @group Components
|
|
21699
|
+
*/
|
|
21700
|
+
function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified, onIdChange, onSaved, entity, initialDirtyValues, onFormContextReady, forceActionsAtTheBottom, initialStatus, className, onStatusChange, onEntityChange, openEntityMode = "full_screen", formex: formexProp, disabled: disabledProp, Builder, EntityFormActionsComponent = EntityFormActions, showDefaultActions = true, showEntityPath = true, navigateBack: navigateBackProp, children, onSubmit: onSubmitProp, onValuesChangeDeferred: onValuesChangeDeferredProp, onReset: onResetProp, uniqueFieldValidator: uniqueFieldValidatorProp, beforeFields, afterFields, pluginActions: pluginActionsProp, computedInitialValues, hasLocalChanges: hasLocalChangesProp, localChangesData, manualApplyLocalChanges, localChangesCacheKey, onClearLocalChanges }) {
|
|
21701
|
+
const customizationController = useCustomizationController();
|
|
21702
|
+
const navigateBack = useCallback(() => {
|
|
21703
|
+
if (navigateBackProp) navigateBackProp();
|
|
21704
|
+
}, [navigateBackProp]);
|
|
22265
21705
|
const [status, setStatus] = useState(initialStatus);
|
|
22266
21706
|
const updateStatus = (status) => {
|
|
22267
21707
|
setStatus(status);
|
|
@@ -22269,18 +21709,11 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22269
21709
|
};
|
|
22270
21710
|
const [valuesToBeSaved, setValuesToBeSaved] = useState(void 0);
|
|
22271
21711
|
useDebouncedCallback(valuesToBeSaved, () => {
|
|
22272
|
-
if (valuesToBeSaved)
|
|
22273
|
-
|
|
22274
|
-
|
|
22275
|
-
|
|
22276
|
-
values: valuesToBeSaved
|
|
22277
|
-
});
|
|
21712
|
+
if (valuesToBeSaved && onSubmitProp) {
|
|
21713
|
+
setIsSavingAutoSave(true);
|
|
21714
|
+
Promise.resolve(onSubmitProp(valuesToBeSaved, formex)).finally(() => setIsSavingAutoSave(false));
|
|
21715
|
+
}
|
|
22278
21716
|
}, false, 2e3);
|
|
22279
|
-
const dataClient = useData();
|
|
22280
|
-
const snackbarController = useSnackbarController();
|
|
22281
|
-
const customizationController = useCustomizationController();
|
|
22282
|
-
const context = useCMSContext();
|
|
22283
|
-
const analyticsController = useAnalyticsController();
|
|
22284
21717
|
const [underlyingChanges] = useState({});
|
|
22285
21718
|
const initialEntityId = useMemo(() => {
|
|
22286
21719
|
if (status === "new" || status === "copy") return;
|
|
@@ -22289,58 +21722,45 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22289
21722
|
const [entityId, setEntityId] = useState(initialEntityId);
|
|
22290
21723
|
const [entityIdError, setEntityIdError] = useState(false);
|
|
22291
21724
|
const [savingError, setSavingError] = useState();
|
|
21725
|
+
const [isSavingAutoSave, setIsSavingAutoSave] = useState(false);
|
|
21726
|
+
const [discardDialogOpen, setDiscardDialogOpen] = useState(false);
|
|
22292
21727
|
const autoSave = collection.formAutoSave;
|
|
22293
|
-
const baseInitialValues = useMemo(() =>
|
|
22294
|
-
|
|
22295
|
-
collection
|
|
22296
|
-
|
|
21728
|
+
const baseInitialValues = useMemo(() => {
|
|
21729
|
+
if (computedInitialValues !== void 0) return computedInitialValues;
|
|
21730
|
+
if ((status === "existing" || status === "copy") && entity) return entity.values ?? getDefaultValuesFor(collection.properties);
|
|
21731
|
+
return getDefaultValuesFor(collection.properties);
|
|
21732
|
+
}, [
|
|
21733
|
+
computedInitialValues,
|
|
21734
|
+
collection.properties,
|
|
22297
21735
|
status,
|
|
22298
|
-
entity
|
|
22299
|
-
customizationController.propertyConfigs
|
|
21736
|
+
entity
|
|
22300
21737
|
]);
|
|
22301
|
-
const localChangesDataRaw = useMemo(() => entityId ? getEntityFromCache(path + "/" + entityId) : getEntityFromCache(path + "#new"), [entityId, path]);
|
|
22302
21738
|
const [localChangesCleared, setLocalChangesCleared] = useState(false);
|
|
22303
|
-
const
|
|
22304
|
-
const autoApplyLocalChanges = localChangesBackup === "auto_apply";
|
|
22305
|
-
const manualApplyLocalChanges = localChangesBackup === "manual_apply";
|
|
21739
|
+
const hasLocalChanges = hasLocalChangesProp !== void 0 ? hasLocalChangesProp && !localChangesCleared : false;
|
|
22306
21740
|
const onSubmit = (values, formexController) => {
|
|
22307
21741
|
setSavingError(void 0);
|
|
22308
21742
|
setEntityIdError(false);
|
|
22309
21743
|
if (status === "existing") {
|
|
22310
21744
|
if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
|
|
22311
21745
|
} else if (status !== "new" && status !== "copy") throw Error("New FormType added, check EntityForm");
|
|
22312
|
-
|
|
22313
|
-
|
|
22314
|
-
|
|
22315
|
-
|
|
22316
|
-
|
|
22317
|
-
|
|
22318
|
-
|
|
22319
|
-
|
|
22320
|
-
|
|
22321
|
-
|
|
22322
|
-
|
|
22323
|
-
}
|
|
21746
|
+
if (!onSubmitProp) {
|
|
21747
|
+
console.warn("EntityForm: no onSubmit callback provided. Form submission has no effect.");
|
|
21748
|
+
formexController.setSubmitting(false);
|
|
21749
|
+
return;
|
|
21750
|
+
}
|
|
21751
|
+
return Promise.resolve(save(values))?.then((savedEntity) => {
|
|
21752
|
+
if (savedEntity) formexController.resetForm({
|
|
21753
|
+
values: savedEntity.values || values,
|
|
21754
|
+
submitCount: 0,
|
|
21755
|
+
touched: {}
|
|
21756
|
+
});
|
|
22324
21757
|
}).finally(() => {
|
|
22325
21758
|
formexController.setSubmitting(false);
|
|
22326
21759
|
});
|
|
22327
21760
|
};
|
|
22328
21761
|
const [initialValues, initialDirty] = useMemo(() => {
|
|
22329
|
-
|
|
22330
|
-
|
|
22331
|
-
}, [
|
|
22332
|
-
autoApplyLocalChanges,
|
|
22333
|
-
localChangesDataRaw,
|
|
22334
|
-
baseInitialValues,
|
|
22335
|
-
initialDirtyValues
|
|
22336
|
-
]);
|
|
22337
|
-
const localChangesData = useMemo(() => {
|
|
22338
|
-
if (!localChangesDataRaw) return;
|
|
22339
|
-
const cleaned = removeEmptyContainers(getChanges(localChangesDataRaw, initialValues));
|
|
22340
|
-
if (cleaned && typeof cleaned === "object" && Object.keys(cleaned).length === 0) return;
|
|
22341
|
-
return cleaned;
|
|
22342
|
-
}, [localChangesDataRaw, initialValues]);
|
|
22343
|
-
const hasLocalChanges = !localChangesCleared && !!localChangesData && Object.keys(localChangesData).length > 0;
|
|
21762
|
+
return [initialDirtyValues ? mergeDeep(baseInitialValues, initialDirtyValues) : baseInitialValues, Boolean(initialDirtyValues) && initialDirtyValues && Object.keys(initialDirtyValues).length > 0];
|
|
21763
|
+
}, [baseInitialValues, initialDirtyValues]);
|
|
22344
21764
|
const internalFormex = useCreateFormex({
|
|
22345
21765
|
initialValues,
|
|
22346
21766
|
initialDirty,
|
|
@@ -22351,17 +21771,10 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22351
21771
|
}), {}) : {},
|
|
22352
21772
|
onSubmit,
|
|
22353
21773
|
onReset: () => {
|
|
22354
|
-
|
|
21774
|
+
onResetProp?.();
|
|
22355
21775
|
onValuesModified?.(false, initialValues);
|
|
22356
21776
|
},
|
|
22357
|
-
onValuesChangeDeferred:
|
|
22358
|
-
const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
|
|
22359
|
-
if (controller.dirty) {
|
|
22360
|
-
const touchedValues = removeEmptyContainers(extractTouchedValues(values, controller.touched));
|
|
22361
|
-
if (touchedValues && Object.keys(touchedValues).length > 0) saveEntityToCache(key, touchedValues);
|
|
22362
|
-
else removeEntityFromCache(key);
|
|
22363
|
-
}
|
|
22364
|
-
},
|
|
21777
|
+
onValuesChangeDeferred: onValuesChangeDeferredProp,
|
|
22365
21778
|
validation: async (values) => {
|
|
22366
21779
|
if (!validationSchema) return {};
|
|
22367
21780
|
const result = await validationSchema.safeParseAsync(values);
|
|
@@ -22374,7 +21787,10 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22374
21787
|
const handleKeyDown = (e) => {
|
|
22375
21788
|
const isUndo = (e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "z";
|
|
22376
21789
|
const isRedo = (e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "z" || (e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "y";
|
|
22377
|
-
if (
|
|
21790
|
+
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "s" && !disabled && formex.dirty) {
|
|
21791
|
+
e.preventDefault();
|
|
21792
|
+
formex.handleSubmit();
|
|
21793
|
+
} else if (isUndo && formex.canUndo) {
|
|
22378
21794
|
e.preventDefault();
|
|
22379
21795
|
formex.undo();
|
|
22380
21796
|
} else if (isRedo && formex.canRedo) {
|
|
@@ -22385,31 +21801,8 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22385
21801
|
window.addEventListener("keydown", handleKeyDown);
|
|
22386
21802
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
22387
21803
|
}, [formex]);
|
|
22388
|
-
useCallback((e) => {
|
|
22389
|
-
snackbarController.open({
|
|
22390
|
-
type: "error",
|
|
22391
|
-
message: `${t("error_before_saving")}: ${e?.message}`
|
|
22392
|
-
});
|
|
22393
|
-
console.error(e);
|
|
22394
|
-
}, [snackbarController]);
|
|
22395
|
-
useCallback((e) => {
|
|
22396
|
-
snackbarController.open({
|
|
22397
|
-
type: "error",
|
|
22398
|
-
message: `${t("error_after_saving")}: ${e?.message}`
|
|
22399
|
-
});
|
|
22400
|
-
console.error(e);
|
|
22401
|
-
}, [snackbarController]);
|
|
22402
|
-
function clearDirtyCache() {
|
|
22403
|
-
if (status === "new" || status === "copy") {
|
|
22404
|
-
removeEntityFromMemoryCache(path + "#new");
|
|
22405
|
-
removeEntityFromCache(path + "#new");
|
|
22406
|
-
} else {
|
|
22407
|
-
removeEntityFromMemoryCache(path + "/" + entityId);
|
|
22408
|
-
removeEntityFromCache(path + "/" + entityId);
|
|
22409
|
-
}
|
|
22410
|
-
}
|
|
22411
21804
|
const afterSave = (updatedEntity) => {
|
|
22412
|
-
|
|
21805
|
+
onResetProp?.();
|
|
22413
21806
|
onValuesModified?.(false, updatedEntity.values);
|
|
22414
21807
|
onEntityChange?.(updatedEntity);
|
|
22415
21808
|
updateStatus("existing");
|
|
@@ -22422,62 +21815,19 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22422
21815
|
collection
|
|
22423
21816
|
});
|
|
22424
21817
|
};
|
|
22425
|
-
const afterSaveError = useCallback((e) => {
|
|
22426
|
-
snackbarController.open({
|
|
22427
|
-
type: "error",
|
|
22428
|
-
title: t("error_saving_entity"),
|
|
22429
|
-
message: e?.message
|
|
22430
|
-
});
|
|
22431
|
-
console.error("Error saving entity", path, entityId, e);
|
|
22432
|
-
console.error(e);
|
|
22433
|
-
}, [
|
|
22434
|
-
entityId,
|
|
22435
|
-
path,
|
|
22436
|
-
snackbarController
|
|
22437
|
-
]);
|
|
22438
|
-
const saveEntity = ({ values, previousValues, entityId, collection, path }) => {
|
|
22439
|
-
return saveEntityWithCallbacks({
|
|
22440
|
-
path,
|
|
22441
|
-
entityId,
|
|
22442
|
-
values,
|
|
22443
|
-
previousValues,
|
|
22444
|
-
collection,
|
|
22445
|
-
status,
|
|
22446
|
-
data: dataClient,
|
|
22447
|
-
context,
|
|
22448
|
-
afterSave,
|
|
22449
|
-
afterSaveError
|
|
22450
|
-
});
|
|
22451
|
-
};
|
|
22452
|
-
const onSaveEntityRequest = async ({ collection, path, entityId, values, previousValues, autoSave }) => {
|
|
22453
|
-
if (!status) return;
|
|
22454
|
-
if (autoSave) {
|
|
22455
|
-
setValuesToBeSaved(values);
|
|
22456
|
-
return Promise.resolve();
|
|
22457
|
-
} else return saveEntity({
|
|
22458
|
-
collection,
|
|
22459
|
-
path,
|
|
22460
|
-
entityId,
|
|
22461
|
-
values,
|
|
22462
|
-
previousValues
|
|
22463
|
-
});
|
|
22464
|
-
};
|
|
22465
21818
|
const lastSavedValues = useRef(entity?.values);
|
|
22466
21819
|
const save = async (values) => {
|
|
22467
21820
|
const valuesToSave = status === "existing" ? getChanges(values, entity?.values || {}) : values;
|
|
22468
21821
|
if (status === "existing" && Object.keys(valuesToSave).length === 0 && entity) return Promise.resolve(entity);
|
|
22469
21822
|
lastSavedValues.current = values;
|
|
22470
|
-
|
|
22471
|
-
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
|
|
22475
|
-
|
|
22476
|
-
|
|
22477
|
-
|
|
22478
|
-
const eventName = status === "new" ? "new_entity_saved" : status === "copy" ? "entity_copied" : status === "existing" ? "entity_edited" : "unmapped_event";
|
|
22479
|
-
analyticsController.onAnalyticsEvent?.(eventName, { path });
|
|
22480
|
-
return savedEntity;
|
|
21823
|
+
if (!onSubmitProp) return;
|
|
21824
|
+
if (autoSave ?? false) {
|
|
21825
|
+
setValuesToBeSaved(values);
|
|
21826
|
+
return Promise.resolve();
|
|
21827
|
+
}
|
|
21828
|
+
return Promise.resolve(onSubmitProp(valuesToSave, formex)).then((result) => {
|
|
21829
|
+
if (result) afterSave(result);
|
|
21830
|
+
return result;
|
|
22481
21831
|
}).catch((e) => {
|
|
22482
21832
|
console.error(e);
|
|
22483
21833
|
setSavingError(e);
|
|
@@ -22508,44 +21858,18 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22508
21858
|
path
|
|
22509
21859
|
]);
|
|
22510
21860
|
const actionsDisabled = disabled || formex.isSubmitting || status === "existing" && !formex.dirty || Boolean(disabledProp);
|
|
22511
|
-
const formActionProps = {
|
|
22512
|
-
entityId,
|
|
22513
|
-
parentCollectionSlugs: collectionRegistryController.getParentCollectionSlugs(path),
|
|
22514
|
-
parentEntityIds: collectionRegistryController.getParentEntityIds(path),
|
|
22515
|
-
path,
|
|
22516
|
-
status,
|
|
22517
|
-
collection,
|
|
22518
|
-
context,
|
|
22519
|
-
formContext,
|
|
22520
|
-
openEntityMode,
|
|
22521
|
-
disabled: actionsDisabled
|
|
22522
|
-
};
|
|
22523
|
-
const pluginFormActions = useSlot("form.actions", formActionProps);
|
|
22524
|
-
const pluginFormBefore = useSlot("form.before", formActionProps);
|
|
22525
|
-
const pluginFormAfter = useSlot("form.after", formActionProps);
|
|
22526
21861
|
const titlePropertyKey = getEntityTitlePropertyKey(collection, customizationController.propertyConfigs);
|
|
22527
21862
|
const rawTitle = formex.values && titlePropertyKey ? getValueInPath(formex.values, titlePropertyKey) : void 0;
|
|
22528
21863
|
const title = rawTitle !== void 0 && rawTitle !== null ? resolveTitleToString(rawTitle) : collection.singularName ?? collection.name;
|
|
21864
|
+
const modified = formex.dirty;
|
|
22529
21865
|
useEffect(() => {
|
|
22530
21866
|
if (!autoSave) onValuesModified?.(modified, formex.values);
|
|
22531
21867
|
}, [formex.dirty]);
|
|
22532
|
-
const
|
|
22533
|
-
const uniqueFieldValidator =
|
|
22534
|
-
|
|
22535
|
-
|
|
22536
|
-
|
|
22537
|
-
limit: 2
|
|
22538
|
-
});
|
|
22539
|
-
return (entityId ? data.filter((e) => e.id !== entityId) : data).length === 0;
|
|
22540
|
-
} catch (e) {
|
|
22541
|
-
console.error("Error checking unique field", e);
|
|
22542
|
-
return true;
|
|
22543
|
-
}
|
|
22544
|
-
}, [
|
|
22545
|
-
dataClient,
|
|
22546
|
-
path,
|
|
22547
|
-
entityId
|
|
22548
|
-
]);
|
|
21868
|
+
const defaultUniqueFieldValidator = useCallback(async () => true, []);
|
|
21869
|
+
const uniqueFieldValidator = useMemo(() => {
|
|
21870
|
+
if (uniqueFieldValidatorProp) return uniqueFieldValidatorProp;
|
|
21871
|
+
return defaultUniqueFieldValidator;
|
|
21872
|
+
}, [uniqueFieldValidatorProp, defaultUniqueFieldValidator]);
|
|
22549
21873
|
const validationSchema = useMemo(() => getEntitySchema(entityId, collection.properties, uniqueFieldValidator), [
|
|
22550
21874
|
entityId,
|
|
22551
21875
|
collection.properties,
|
|
@@ -22577,6 +21901,8 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22577
21901
|
modifiedValues: formex.values,
|
|
22578
21902
|
formContext
|
|
22579
21903
|
});
|
|
21904
|
+
const isNewEntity = status === "new" || status === "copy";
|
|
21905
|
+
let firstFocusableIndex = -1;
|
|
22580
21906
|
return /* @__PURE__ */ jsx(FormLayout, { children: formFieldKeys.map((key) => {
|
|
22581
21907
|
const property = collection.properties?.[key];
|
|
22582
21908
|
if (property) {
|
|
@@ -22585,9 +21911,12 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22585
21911
|
const isIdAndAuto = (property.type === "string" || property.type === "number") && "isId" in property && typeof property.isId === "string" && property.isId !== "manual";
|
|
22586
21912
|
const disabled = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.ui?.disabled) || !isNew && "isId" in property && Boolean(property.isId) || isNew && isIdAndAuto;
|
|
22587
21913
|
if (isHidden(property)) return null;
|
|
21914
|
+
const widthPercentage = property.ui?.widthPercentage ?? 100;
|
|
21915
|
+
const shouldAutoFocus = isNewEntity && !disabled && firstFocusableIndex === -1;
|
|
21916
|
+
if (shouldAutoFocus) firstFocusableIndex = 0;
|
|
22588
21917
|
return /* @__PURE__ */ jsx(FormEntry, {
|
|
22589
21918
|
propertyKey: key,
|
|
22590
|
-
widthPercentage
|
|
21919
|
+
widthPercentage,
|
|
22591
21920
|
children: /* @__PURE__ */ jsx(PropertyFieldBinding, {
|
|
22592
21921
|
propertyKey: key,
|
|
22593
21922
|
disabled,
|
|
@@ -22597,22 +21926,23 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22597
21926
|
context: formContext,
|
|
22598
21927
|
partOfArray: false,
|
|
22599
21928
|
minimalistView: false,
|
|
22600
|
-
autoFocus:
|
|
21929
|
+
autoFocus: shouldAutoFocus
|
|
22601
21930
|
})
|
|
22602
21931
|
}, `field_${key}`);
|
|
22603
21932
|
}
|
|
22604
21933
|
const additionalField = collection.additionalFields?.find((f) => f.key === key);
|
|
22605
21934
|
if (additionalField && entity) {
|
|
22606
|
-
const
|
|
22607
|
-
if (!
|
|
22608
|
-
const
|
|
21935
|
+
const AdditionalFieldBuilder = additionalField.Builder;
|
|
21936
|
+
if (!AdditionalFieldBuilder && !additionalField.value) throw new Error("When using additional fields you need to provide a Builder or a value");
|
|
21937
|
+
const additionalFieldContext = formContext;
|
|
21938
|
+
const child = AdditionalFieldBuilder ? /* @__PURE__ */ jsx(AdditionalFieldBuilder, {
|
|
22609
21939
|
entity,
|
|
22610
|
-
context
|
|
21940
|
+
context: additionalFieldContext
|
|
22611
21941
|
}) : /* @__PURE__ */ jsx("div", {
|
|
22612
21942
|
className: "w-full",
|
|
22613
21943
|
children: additionalField.value?.({
|
|
22614
21944
|
entity,
|
|
22615
|
-
context
|
|
21945
|
+
context: additionalFieldContext
|
|
22616
21946
|
})?.toString()
|
|
22617
21947
|
});
|
|
22618
21948
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -22633,8 +21963,9 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22633
21963
|
}).filter(Boolean) });
|
|
22634
21964
|
};
|
|
22635
21965
|
const formRef = useRef(null);
|
|
21966
|
+
const hasFormErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
22636
21967
|
const formView = /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22637
|
-
|
|
21968
|
+
beforeFields,
|
|
22638
21969
|
!Builder && /* @__PURE__ */ jsxs("div", {
|
|
22639
21970
|
className: "w-full flex flex-col items-start my-4 lg:my-6",
|
|
22640
21971
|
children: [
|
|
@@ -22647,7 +21978,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22647
21978
|
color: "warning",
|
|
22648
21979
|
size: "small",
|
|
22649
21980
|
outerClassName: "w-full mb-4 text-xs",
|
|
22650
|
-
children:
|
|
21981
|
+
children: "This entity does not exist yet"
|
|
22651
21982
|
}),
|
|
22652
21983
|
showEntityPath && /* @__PURE__ */ jsx(Alert, {
|
|
22653
21984
|
color: "base",
|
|
@@ -22668,15 +21999,43 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22668
21999
|
initialEntityId && !entity && initialStatus !== "new" && /* @__PURE__ */ jsx(Alert, {
|
|
22669
22000
|
color: "info",
|
|
22670
22001
|
size: "small",
|
|
22671
|
-
children:
|
|
22002
|
+
children: "This entity does not exist yet"
|
|
22003
|
+
}),
|
|
22004
|
+
hasFormErrors && /* @__PURE__ */ jsx(Alert, {
|
|
22005
|
+
color: "error",
|
|
22006
|
+
size: "small",
|
|
22007
|
+
outerClassName: "w-full mt-2",
|
|
22008
|
+
children: "Please fix the highlighted errors before saving."
|
|
22672
22009
|
}),
|
|
22673
22010
|
formContext && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
|
|
22674
22011
|
className: "mt-12 flex flex-col gap-8",
|
|
22675
22012
|
ref: formRef,
|
|
22676
22013
|
children: [formFields(), /* @__PURE__ */ jsx(ErrorFocus, { containerRef: formRef })]
|
|
22677
22014
|
}) }),
|
|
22678
|
-
|
|
22679
|
-
forceActionsAtTheBottom && /* @__PURE__ */ jsx("div", { className: "h-16" })
|
|
22015
|
+
afterFields,
|
|
22016
|
+
forceActionsAtTheBottom && /* @__PURE__ */ jsx("div", { className: "h-16" }),
|
|
22017
|
+
/* @__PURE__ */ jsxs(Dialog, {
|
|
22018
|
+
open: discardDialogOpen,
|
|
22019
|
+
onOpenChange: setDiscardDialogOpen,
|
|
22020
|
+
maxWidth: "sm",
|
|
22021
|
+
children: [
|
|
22022
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: status === "existing" ? "Discard changes?" : "Clear form?" }),
|
|
22023
|
+
/* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx(Typography, { children: status === "existing" ? "All unsaved changes will be lost. This cannot be undone." : "All entered values will be cleared. This cannot be undone." }) }),
|
|
22024
|
+
/* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
|
|
22025
|
+
variant: "text",
|
|
22026
|
+
onClick: () => setDiscardDialogOpen(false),
|
|
22027
|
+
children: "Cancel"
|
|
22028
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
22029
|
+
variant: "filled",
|
|
22030
|
+
color: "error",
|
|
22031
|
+
onClick: () => {
|
|
22032
|
+
setDiscardDialogOpen(false);
|
|
22033
|
+
formex.resetForm({ values: baseInitialValues });
|
|
22034
|
+
},
|
|
22035
|
+
children: status === "existing" ? "Discard" : "Clear"
|
|
22036
|
+
})] })
|
|
22037
|
+
]
|
|
22038
|
+
})
|
|
22680
22039
|
] }) });
|
|
22681
22040
|
useEffect(() => {
|
|
22682
22041
|
if (entityId && onIdChange) onIdChange(entityId);
|
|
@@ -22691,7 +22050,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22691
22050
|
formex,
|
|
22692
22051
|
disabled: actionsDisabled,
|
|
22693
22052
|
status,
|
|
22694
|
-
pluginActions:
|
|
22053
|
+
pluginActions: pluginActionsProp ?? [],
|
|
22695
22054
|
openEntityMode,
|
|
22696
22055
|
showDefaultActions,
|
|
22697
22056
|
navigateBack,
|
|
@@ -22701,7 +22060,11 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22701
22060
|
value: formex,
|
|
22702
22061
|
children: /* @__PURE__ */ jsxs("form", {
|
|
22703
22062
|
onSubmit: formex.handleSubmit,
|
|
22704
|
-
onReset: () =>
|
|
22063
|
+
onReset: (e) => {
|
|
22064
|
+
e.preventDefault();
|
|
22065
|
+
if (formex.dirty) setDiscardDialogOpen(true);
|
|
22066
|
+
else formex.resetForm({ values: baseInitialValues });
|
|
22067
|
+
},
|
|
22705
22068
|
noValidate: true,
|
|
22706
22069
|
className: cls("@container flex-1 flex flex-row w-full overflow-y-auto justify-center", className),
|
|
22707
22070
|
children: [/* @__PURE__ */ jsx("div", {
|
|
@@ -22710,15 +22073,29 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22710
22073
|
children: /* @__PURE__ */ jsxs("div", {
|
|
22711
22074
|
className: cls("flex flex-col w-full", openEntityMode === "dialog" ? "pt-4 pb-12 px-6 sm:px-8" : "pt-12 pb-16 px-4 sm:px-8 md:px-10"),
|
|
22712
22075
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
22713
|
-
className: "flex flex-row gap-4
|
|
22714
|
-
children: [manualApplyLocalChanges && hasLocalChanges && /* @__PURE__ */ jsx(LocalChangesMenu, {
|
|
22715
|
-
cacheKey:
|
|
22076
|
+
className: "flex flex-row gap-4 justify-end h-0 overflow-visible sticky top-4 z-10",
|
|
22077
|
+
children: [manualApplyLocalChanges && hasLocalChanges && localChangesCacheKey && /* @__PURE__ */ jsx(LocalChangesMenu, {
|
|
22078
|
+
cacheKey: localChangesCacheKey,
|
|
22716
22079
|
properties: collection.properties,
|
|
22717
22080
|
cachedData: localChangesData,
|
|
22718
22081
|
formex,
|
|
22719
|
-
onClearLocalChanges: () =>
|
|
22720
|
-
|
|
22721
|
-
|
|
22082
|
+
onClearLocalChanges: () => {
|
|
22083
|
+
setLocalChangesCleared(true);
|
|
22084
|
+
onClearLocalChanges?.();
|
|
22085
|
+
}
|
|
22086
|
+
}), isSavingAutoSave ? /* @__PURE__ */ jsx(Tooltip, {
|
|
22087
|
+
title: "Saving…",
|
|
22088
|
+
children: /* @__PURE__ */ jsx(Chip, {
|
|
22089
|
+
size: "small",
|
|
22090
|
+
className: "py-1",
|
|
22091
|
+
colorScheme: "blueDarker",
|
|
22092
|
+
children: /* @__PURE__ */ jsx(LoaderIcon, {
|
|
22093
|
+
size: iconSize.smallest,
|
|
22094
|
+
className: "animate-spin"
|
|
22095
|
+
})
|
|
22096
|
+
})
|
|
22097
|
+
}) : formex.dirty ? /* @__PURE__ */ jsx(Tooltip, {
|
|
22098
|
+
title: "Form has been modified",
|
|
22722
22099
|
children: /* @__PURE__ */ jsx(Chip, {
|
|
22723
22100
|
size: "small",
|
|
22724
22101
|
className: "py-1",
|
|
@@ -22726,7 +22103,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22726
22103
|
children: /* @__PURE__ */ jsx(PencilIcon, { size: iconSize.smallest })
|
|
22727
22104
|
})
|
|
22728
22105
|
}) : /* @__PURE__ */ jsx(Tooltip, {
|
|
22729
|
-
title:
|
|
22106
|
+
title: "Form is in sync",
|
|
22730
22107
|
children: /* @__PURE__ */ jsx(Chip, {
|
|
22731
22108
|
size: "small",
|
|
22732
22109
|
className: "py-1",
|
|
@@ -22739,40 +22116,401 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22739
22116
|
})
|
|
22740
22117
|
});
|
|
22741
22118
|
}
|
|
22742
|
-
function
|
|
22743
|
-
|
|
22744
|
-
|
|
22745
|
-
|
|
22746
|
-
|
|
22747
|
-
|
|
22748
|
-
|
|
22749
|
-
|
|
22750
|
-
|
|
22751
|
-
|
|
22752
|
-
|
|
22753
|
-
|
|
22754
|
-
|
|
22755
|
-
|
|
22756
|
-
|
|
22757
|
-
|
|
22758
|
-
|
|
22759
|
-
|
|
22760
|
-
|
|
22119
|
+
function useOnAutoSave(autoSave, formex, lastSavedValues, save) {
|
|
22120
|
+
useEffect(() => {
|
|
22121
|
+
if (!autoSave) return;
|
|
22122
|
+
if (formex.values && !deepEqual(formex.values, lastSavedValues.current)) save(formex.values);
|
|
22123
|
+
}, [autoSave, formex.values]);
|
|
22124
|
+
}
|
|
22125
|
+
//#endregion
|
|
22126
|
+
//#region src/form/EntityFormBinding.tsx
|
|
22127
|
+
/**
|
|
22128
|
+
* CMS-connected wrapper around EntityForm.
|
|
22129
|
+
*
|
|
22130
|
+
* Provides all backend concerns that the headless EntityForm delegates via callbacks:
|
|
22131
|
+
* - Save via the data layer (`saveEntityWithCallbacks`)
|
|
22132
|
+
* - Unique field validation via the data layer
|
|
22133
|
+
* - Entity caching (local changes backup/restore)
|
|
22134
|
+
* - Plugin slots (form.before, form.after, form.actions)
|
|
22135
|
+
* - Snackbar notifications on save success/error
|
|
22136
|
+
* - Analytics events
|
|
22137
|
+
* - Navigation via side entity controller
|
|
22138
|
+
*
|
|
22139
|
+
* Use this component inside the CMS (`<RebaseShell>`).
|
|
22140
|
+
* For headless usage outside the CMS, use `EntityForm` directly.
|
|
22141
|
+
*
|
|
22142
|
+
* @group Components
|
|
22143
|
+
*/
|
|
22144
|
+
function EntityFormBinding({ path, entityId: entityIdProp, collection, entity, initialStatus, onSaved, onValuesModified, onEntityChange, onStatusChange, openEntityMode = "full_screen", navigateBack: navigateBackProp, initialDirtyValues, ...restProps }) {
|
|
22145
|
+
const { t } = useTranslation();
|
|
22146
|
+
const sideEntityController = useSideEntityController();
|
|
22147
|
+
const collectionRegistryController = useCollectionRegistryController();
|
|
22148
|
+
const authController = useAuthController();
|
|
22149
|
+
const dataClient = useData();
|
|
22150
|
+
const snackbarController = useSnackbarController();
|
|
22151
|
+
const customizationController = useCustomizationController();
|
|
22152
|
+
const context = useCMSContext();
|
|
22153
|
+
const analyticsController = useAnalyticsController();
|
|
22154
|
+
const [status, setStatus] = useState(initialStatus);
|
|
22155
|
+
const [entityId, setEntityId] = useState(() => {
|
|
22156
|
+
if (status === "new" || status === "copy") return void 0;
|
|
22157
|
+
return entityIdProp;
|
|
22158
|
+
});
|
|
22159
|
+
const baseInitialValues = useMemo(() => getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs), [
|
|
22160
|
+
authController,
|
|
22161
|
+
collection,
|
|
22162
|
+
path,
|
|
22163
|
+
status,
|
|
22164
|
+
entity,
|
|
22165
|
+
customizationController.propertyConfigs
|
|
22166
|
+
]);
|
|
22167
|
+
const localChangesBackup = getLocalChangesBackup(collection);
|
|
22168
|
+
const autoApplyLocalChanges = localChangesBackup === "auto_apply";
|
|
22169
|
+
const manualApplyLocalChanges = localChangesBackup === "manual_apply";
|
|
22170
|
+
const localChangesDataRaw = useMemo(() => entityId ? getEntityFromCache(path + "/" + entityId) : getEntityFromCache(path + "#new"), [entityId, path]);
|
|
22171
|
+
const [localChangesCleared, setLocalChangesCleared] = useState(false);
|
|
22172
|
+
const computedInitialValues = useMemo(() => {
|
|
22173
|
+
const withLocalChanges = autoApplyLocalChanges && localChangesDataRaw ? mergeDeep(baseInitialValues, localChangesDataRaw) : baseInitialValues;
|
|
22174
|
+
return initialDirtyValues ? mergeDeep(withLocalChanges, initialDirtyValues) : withLocalChanges;
|
|
22175
|
+
}, [
|
|
22176
|
+
autoApplyLocalChanges,
|
|
22177
|
+
localChangesDataRaw,
|
|
22178
|
+
baseInitialValues,
|
|
22179
|
+
initialDirtyValues
|
|
22180
|
+
]);
|
|
22181
|
+
const localChangesData = useMemo(() => {
|
|
22182
|
+
if (!localChangesDataRaw) return void 0;
|
|
22183
|
+
const cleaned = removeEmptyContainers(getChanges(localChangesDataRaw, computedInitialValues));
|
|
22184
|
+
if (cleaned && typeof cleaned === "object" && Object.keys(cleaned).length === 0) return;
|
|
22185
|
+
return cleaned;
|
|
22186
|
+
}, [localChangesDataRaw, computedInitialValues]);
|
|
22187
|
+
const hasLocalChanges = !localChangesCleared && !!localChangesData && Object.keys(localChangesData).length > 0;
|
|
22188
|
+
const localChangesCacheKey = useMemo(() => {
|
|
22189
|
+
return status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
|
|
22190
|
+
}, [
|
|
22191
|
+
status,
|
|
22192
|
+
path,
|
|
22193
|
+
entityId
|
|
22194
|
+
]);
|
|
22195
|
+
const navigateBack = useCallback(() => {
|
|
22196
|
+
if (navigateBackProp) {
|
|
22197
|
+
navigateBackProp();
|
|
22198
|
+
return;
|
|
22199
|
+
}
|
|
22200
|
+
if (openEntityMode === "side_panel" || openEntityMode === "dialog") sideEntityController.close();
|
|
22201
|
+
else window.history.back();
|
|
22202
|
+
}, [
|
|
22203
|
+
navigateBackProp,
|
|
22204
|
+
openEntityMode,
|
|
22205
|
+
sideEntityController
|
|
22206
|
+
]);
|
|
22207
|
+
const clearDirtyCache = useCallback(() => {
|
|
22208
|
+
if (status === "new" || status === "copy") {
|
|
22209
|
+
removeEntityFromMemoryCache(path + "#new");
|
|
22210
|
+
removeEntityFromCache(path + "#new");
|
|
22211
|
+
} else {
|
|
22212
|
+
removeEntityFromMemoryCache(path + "/" + entityId);
|
|
22213
|
+
removeEntityFromCache(path + "/" + entityId);
|
|
22214
|
+
}
|
|
22215
|
+
}, [
|
|
22216
|
+
status,
|
|
22217
|
+
path,
|
|
22218
|
+
entityId
|
|
22219
|
+
]);
|
|
22220
|
+
const onReset = useCallback(() => {
|
|
22221
|
+
clearDirtyCache();
|
|
22222
|
+
}, [clearDirtyCache]);
|
|
22223
|
+
const onValuesChangeDeferred = useCallback((values, controller) => {
|
|
22224
|
+
const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
|
|
22225
|
+
if (controller.dirty) {
|
|
22226
|
+
const touchedValues = removeEmptyContainers(extractTouchedValues(values, controller.touched));
|
|
22227
|
+
if (touchedValues && Object.keys(touchedValues).length > 0) saveEntityToCache(key, touchedValues);
|
|
22228
|
+
else removeEntityFromCache(key);
|
|
22229
|
+
}
|
|
22230
|
+
}, [
|
|
22231
|
+
status,
|
|
22232
|
+
path,
|
|
22233
|
+
entityId
|
|
22234
|
+
]);
|
|
22235
|
+
const onSubmit = useCallback(async (values, formex) => {
|
|
22236
|
+
try {
|
|
22237
|
+
const savedEntity = await saveEntityWithCallbacks({
|
|
22238
|
+
path,
|
|
22239
|
+
entityId,
|
|
22240
|
+
values,
|
|
22241
|
+
previousValues: entity?.values,
|
|
22242
|
+
collection,
|
|
22243
|
+
status,
|
|
22244
|
+
data: dataClient,
|
|
22245
|
+
context,
|
|
22246
|
+
afterSave: (updatedEntity) => {
|
|
22247
|
+
clearDirtyCache();
|
|
22248
|
+
onValuesModified?.(false, updatedEntity.values);
|
|
22249
|
+
onEntityChange?.(updatedEntity);
|
|
22250
|
+
setStatus("existing");
|
|
22251
|
+
setEntityId(updatedEntity.id);
|
|
22252
|
+
onStatusChange?.("existing");
|
|
22253
|
+
if (onSaved) onSaved({
|
|
22254
|
+
entity: updatedEntity,
|
|
22255
|
+
status,
|
|
22256
|
+
path,
|
|
22257
|
+
entityId: updatedEntity.id,
|
|
22258
|
+
collection
|
|
22259
|
+
});
|
|
22260
|
+
},
|
|
22261
|
+
afterSaveError: (e) => {
|
|
22262
|
+
snackbarController.open({
|
|
22263
|
+
type: "error",
|
|
22264
|
+
title: t("error_saving_entity"),
|
|
22265
|
+
message: e?.message
|
|
22266
|
+
});
|
|
22267
|
+
console.error("Error saving entity", path, entityId, e);
|
|
22268
|
+
}
|
|
22269
|
+
});
|
|
22270
|
+
if (!collection.formAutoSave) snackbarController.open({
|
|
22271
|
+
type: "success",
|
|
22272
|
+
message: `${collection.singularName ?? collection.name}: ${t("saved_correctly")}`
|
|
22273
|
+
});
|
|
22274
|
+
const eventName = status === "new" ? "new_entity_saved" : status === "copy" ? "entity_copied" : status === "existing" ? "entity_edited" : "unmapped_event";
|
|
22275
|
+
analyticsController.onAnalyticsEvent?.(eventName, { path });
|
|
22276
|
+
return savedEntity;
|
|
22277
|
+
} catch (e) {
|
|
22278
|
+
console.error(e);
|
|
22279
|
+
throw e;
|
|
22280
|
+
}
|
|
22281
|
+
}, [
|
|
22282
|
+
path,
|
|
22283
|
+
entityId,
|
|
22284
|
+
entity,
|
|
22285
|
+
collection,
|
|
22286
|
+
status,
|
|
22287
|
+
dataClient,
|
|
22288
|
+
context,
|
|
22289
|
+
clearDirtyCache,
|
|
22290
|
+
onValuesModified,
|
|
22291
|
+
onEntityChange,
|
|
22292
|
+
onSaved,
|
|
22293
|
+
onStatusChange,
|
|
22294
|
+
snackbarController,
|
|
22295
|
+
analyticsController,
|
|
22296
|
+
t
|
|
22297
|
+
]);
|
|
22298
|
+
const uniqueFieldValidator = useCallback(async ({ name, value }) => {
|
|
22299
|
+
try {
|
|
22300
|
+
const { data } = await dataClient.collection(path).find({
|
|
22301
|
+
where: { [name]: `eq.${value}` },
|
|
22302
|
+
limit: 2
|
|
22303
|
+
});
|
|
22304
|
+
return (entityId ? data.filter((e) => e.id !== entityId) : data).length === 0;
|
|
22305
|
+
} catch (e) {
|
|
22306
|
+
console.error("Error checking unique field", e);
|
|
22307
|
+
return true;
|
|
22308
|
+
}
|
|
22309
|
+
}, [
|
|
22310
|
+
dataClient,
|
|
22311
|
+
path,
|
|
22312
|
+
entityId
|
|
22313
|
+
]);
|
|
22314
|
+
const parentCollectionSlugs = collectionRegistryController.getParentCollectionSlugs(path);
|
|
22315
|
+
const parentEntityIds = collectionRegistryController.getParentEntityIds(path);
|
|
22316
|
+
const formActionProps = useMemo(() => ({
|
|
22317
|
+
entityId,
|
|
22318
|
+
parentCollectionSlugs,
|
|
22319
|
+
parentEntityIds,
|
|
22320
|
+
path,
|
|
22321
|
+
status,
|
|
22322
|
+
collection,
|
|
22323
|
+
context,
|
|
22324
|
+
formContext: void 0,
|
|
22325
|
+
openEntityMode,
|
|
22326
|
+
disabled: false
|
|
22327
|
+
}), [
|
|
22328
|
+
entityId,
|
|
22329
|
+
parentCollectionSlugs,
|
|
22330
|
+
parentEntityIds,
|
|
22331
|
+
path,
|
|
22332
|
+
status,
|
|
22333
|
+
collection,
|
|
22334
|
+
context,
|
|
22335
|
+
openEntityMode
|
|
22336
|
+
]);
|
|
22337
|
+
const pluginFormActions = useSlot("form.actions", formActionProps);
|
|
22338
|
+
const pluginFormBefore = useSlot("form.before", formActionProps);
|
|
22339
|
+
const pluginFormAfter = useSlot("form.after", formActionProps);
|
|
22340
|
+
return /* @__PURE__ */ jsx(EntityForm, {
|
|
22341
|
+
...restProps,
|
|
22342
|
+
path,
|
|
22343
|
+
entityId: entityIdProp,
|
|
22344
|
+
collection,
|
|
22345
|
+
entity,
|
|
22346
|
+
initialStatus,
|
|
22347
|
+
initialDirtyValues,
|
|
22348
|
+
onSaved,
|
|
22349
|
+
onValuesModified,
|
|
22350
|
+
onEntityChange,
|
|
22351
|
+
onStatusChange,
|
|
22352
|
+
openEntityMode,
|
|
22353
|
+
navigateBack,
|
|
22354
|
+
onSubmit,
|
|
22355
|
+
onValuesChangeDeferred,
|
|
22356
|
+
onReset,
|
|
22357
|
+
uniqueFieldValidator,
|
|
22358
|
+
beforeFields: pluginFormBefore,
|
|
22359
|
+
afterFields: pluginFormAfter,
|
|
22360
|
+
pluginActions: pluginFormActions ?? [],
|
|
22361
|
+
computedInitialValues,
|
|
22362
|
+
hasLocalChanges,
|
|
22363
|
+
localChangesData,
|
|
22364
|
+
manualApplyLocalChanges,
|
|
22365
|
+
localChangesCacheKey,
|
|
22366
|
+
onClearLocalChanges: () => setLocalChangesCleared(true)
|
|
22367
|
+
});
|
|
22368
|
+
}
|
|
22369
|
+
//#endregion
|
|
22370
|
+
//#region src/util/propertyConfigMapper.ts
|
|
22371
|
+
/**
|
|
22372
|
+
* Map Rebase property types to headless-supported types.
|
|
22373
|
+
* `vector` and `binary` are not directly renderable, so we fall back to `string`.
|
|
22374
|
+
*/
|
|
22375
|
+
var TYPE_MAP = {
|
|
22376
|
+
string: "string",
|
|
22377
|
+
number: "number",
|
|
22378
|
+
boolean: "boolean",
|
|
22379
|
+
date: "date",
|
|
22380
|
+
geopoint: "geopoint",
|
|
22381
|
+
reference: "reference",
|
|
22382
|
+
relation: "relation",
|
|
22383
|
+
array: "array",
|
|
22384
|
+
map: "map",
|
|
22385
|
+
vector: "string",
|
|
22386
|
+
binary: "string"
|
|
22387
|
+
};
|
|
22388
|
+
/**
|
|
22389
|
+
* Convert a single Property to a CollectionPropertyConfig.
|
|
22390
|
+
*/
|
|
22391
|
+
function mapPropertyToConfig(property) {
|
|
22392
|
+
const base = {
|
|
22393
|
+
type: TYPE_MAP[property.type] ?? "string",
|
|
22394
|
+
name: property.name,
|
|
22395
|
+
description: property.description,
|
|
22396
|
+
columnWidth: property.ui?.columnWidth,
|
|
22397
|
+
hideFromCollection: property.ui?.hideFromCollection
|
|
22398
|
+
};
|
|
22399
|
+
if (property.type === "string") {
|
|
22400
|
+
if (property.enum) base.enum = mapEnumValues(property.enum);
|
|
22401
|
+
if (property.multiline) base.multiline = true;
|
|
22402
|
+
if (property.ui?.previewAsTag) base.previewAsTag = true;
|
|
22403
|
+
if (property.url || property.ui?.url) base.url = property.url ?? property.ui?.url;
|
|
22404
|
+
if (property.ui?.markdown || property.markdown) base.markdown = true;
|
|
22405
|
+
if (property.storage) base.storage = true;
|
|
22406
|
+
if (property.email) base.email = true;
|
|
22407
|
+
}
|
|
22408
|
+
if (property.type === "number" && property.enum) base.enum = mapEnumValues(property.enum);
|
|
22409
|
+
if (property.type === "array" && property.of && !Array.isArray(property.of)) base.of = mapPropertyToConfig(property.of);
|
|
22410
|
+
if (property.type === "map" && property.properties) {
|
|
22411
|
+
base.properties = mapPropertiesToConfigs(property.properties);
|
|
22412
|
+
if (property.propertiesOrder) base.propertiesOrder = property.propertiesOrder;
|
|
22761
22413
|
}
|
|
22414
|
+
if (property.type === "date" && property.mode) base.mode = property.mode;
|
|
22415
|
+
if (property.ui?.Preview) {}
|
|
22416
|
+
return base;
|
|
22762
22417
|
}
|
|
22763
|
-
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
|
|
22767
|
-
|
|
22418
|
+
/**
|
|
22419
|
+
* Convert a Properties record to a CollectionPropertyConfig record.
|
|
22420
|
+
*/
|
|
22421
|
+
function mapPropertiesToConfigs(properties) {
|
|
22422
|
+
const result = {};
|
|
22423
|
+
for (const [key, property] of Object.entries(properties)) result[key] = mapPropertyToConfig(property);
|
|
22424
|
+
return result;
|
|
22425
|
+
}
|
|
22426
|
+
/**
|
|
22427
|
+
* Convert Rebase EnumValues to CollectionEnumValueConfig format.
|
|
22428
|
+
* Handles both array form (EnumValueConfig[]) and record form.
|
|
22429
|
+
*/
|
|
22430
|
+
function mapEnumValues(enumValues) {
|
|
22431
|
+
if (!enumValues) return void 0;
|
|
22432
|
+
if (Array.isArray(enumValues)) {
|
|
22433
|
+
const result = {};
|
|
22434
|
+
for (const item of enumValues) result[String(item.id)] = normalizeEnumValueConfig(item);
|
|
22435
|
+
return result;
|
|
22768
22436
|
}
|
|
22769
|
-
|
|
22437
|
+
const result = {};
|
|
22438
|
+
for (const [key, value] of Object.entries(enumValues)) result[key] = typeof value === "string" ? value : normalizeEnumValueConfig(value);
|
|
22439
|
+
return result;
|
|
22770
22440
|
}
|
|
22771
|
-
function
|
|
22772
|
-
|
|
22773
|
-
|
|
22774
|
-
|
|
22775
|
-
|
|
22441
|
+
function normalizeEnumValueConfig(config) {
|
|
22442
|
+
return {
|
|
22443
|
+
label: config.label,
|
|
22444
|
+
color: typeof config.color === "string" ? config.color : void 0,
|
|
22445
|
+
disabled: config.disabled
|
|
22446
|
+
};
|
|
22447
|
+
}
|
|
22448
|
+
//#endregion
|
|
22449
|
+
//#region src/util/dataControllerAdapter.ts
|
|
22450
|
+
/**
|
|
22451
|
+
* Adapts an `EntityTableController<M>` (from @rebasepro/types) to a
|
|
22452
|
+
* `CollectionDataController<T>` (from @rebasepro/ui), bridging the
|
|
22453
|
+
* entity-aware data layer to the headless collection view.
|
|
22454
|
+
*/
|
|
22455
|
+
/**
|
|
22456
|
+
* Flatten Entity<M> objects to plain row objects.
|
|
22457
|
+
* The headless view works with flat `Record<string, unknown>` rows,
|
|
22458
|
+
* while the entity system uses `{ id, path, values: M }`.
|
|
22459
|
+
*
|
|
22460
|
+
* The flattened row includes `id` and `path` at the top level,
|
|
22461
|
+
* plus all values spread.
|
|
22462
|
+
*/
|
|
22463
|
+
function flattenEntities(entities) {
|
|
22464
|
+
return entities.map((entity) => ({
|
|
22465
|
+
id: entity.id,
|
|
22466
|
+
path: entity.path,
|
|
22467
|
+
...entity.values
|
|
22468
|
+
}));
|
|
22469
|
+
}
|
|
22470
|
+
/**
|
|
22471
|
+
* React hook that adapts an EntityTableController to a CollectionDataController.
|
|
22472
|
+
*
|
|
22473
|
+
* Usage:
|
|
22474
|
+
* ```tsx
|
|
22475
|
+
* const tableController = useDataTableController({ path, collection });
|
|
22476
|
+
* const dataController = useCollectionDataController(tableController);
|
|
22477
|
+
*
|
|
22478
|
+
* <CollectionView dataController={dataController} ... />
|
|
22479
|
+
* ```
|
|
22480
|
+
*/
|
|
22481
|
+
function useCollectionDataController(tableController) {
|
|
22482
|
+
const flatData = useMemo(() => flattenEntities(tableController.data), [tableController.data]);
|
|
22483
|
+
return useMemo(() => ({
|
|
22484
|
+
data: flatData,
|
|
22485
|
+
loading: tableController.dataLoading,
|
|
22486
|
+
noMoreToLoad: tableController.noMoreToLoad,
|
|
22487
|
+
error: tableController.dataLoadingError,
|
|
22488
|
+
filterValues: tableController.filterValues,
|
|
22489
|
+
setFilterValues: tableController.setFilterValues,
|
|
22490
|
+
sortBy: tableController.sortBy,
|
|
22491
|
+
setSortBy: tableController.setSortBy,
|
|
22492
|
+
searchString: tableController.searchString,
|
|
22493
|
+
setSearchString: tableController.setSearchString,
|
|
22494
|
+
clearFilter: tableController.clearFilter,
|
|
22495
|
+
initialScroll: tableController.initialScroll,
|
|
22496
|
+
onScroll: tableController.onScroll,
|
|
22497
|
+
paginationEnabled: tableController.paginationEnabled,
|
|
22498
|
+
pageSize: tableController.pageSize,
|
|
22499
|
+
itemCount: tableController.itemCount,
|
|
22500
|
+
setItemCount: tableController.setItemCount
|
|
22501
|
+
}), [flatData, tableController]);
|
|
22502
|
+
}
|
|
22503
|
+
/**
|
|
22504
|
+
* Non-hook version for cases where you already have the data
|
|
22505
|
+
* and just need to build a static controller.
|
|
22506
|
+
*/
|
|
22507
|
+
function createStaticDataController(data, options) {
|
|
22508
|
+
return {
|
|
22509
|
+
data,
|
|
22510
|
+
loading: options?.loading ?? false,
|
|
22511
|
+
noMoreToLoad: true,
|
|
22512
|
+
error: options?.error
|
|
22513
|
+
};
|
|
22776
22514
|
}
|
|
22777
22515
|
//#endregion
|
|
22778
22516
|
//#region src/data_import/import/ImportCollectionAction.tsx
|
|
@@ -23606,6 +23344,571 @@ var export_exports = /* @__PURE__ */ __exportAll({
|
|
|
23606
23344
|
*/
|
|
23607
23345
|
var useCollectionsConfigController = () => useContext(ConfigControllerContext);
|
|
23608
23346
|
//#endregion
|
|
23347
|
+
//#region src/collection_editor/serializable_utils.ts
|
|
23348
|
+
/**
|
|
23349
|
+
* Strip non-serializable fields from a `BaseUIConfig`.
|
|
23350
|
+
* Removes `Field` and `Preview` (ComponentRef).
|
|
23351
|
+
*/
|
|
23352
|
+
function toSerializableUIConfig(ui) {
|
|
23353
|
+
if (!ui) return void 0;
|
|
23354
|
+
const { Field, Preview, ...rest } = ui;
|
|
23355
|
+
const result = {};
|
|
23356
|
+
for (const [key, value] of Object.entries(rest)) if (key !== "Field" && key !== "Preview" && value !== void 0) result[key] = value;
|
|
23357
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
23358
|
+
}
|
|
23359
|
+
/**
|
|
23360
|
+
* Convert a `StorageConfig` to its serializable form.
|
|
23361
|
+
* Drops function variants of `storagePath`, `fileName`, and all function fields.
|
|
23362
|
+
*/
|
|
23363
|
+
function toSerializableStorageConfig(storage) {
|
|
23364
|
+
if (!storage) return void 0;
|
|
23365
|
+
const storagePath = typeof storage.storagePath === "string" ? storage.storagePath : void 0;
|
|
23366
|
+
if (!storagePath) return void 0;
|
|
23367
|
+
const result = { storagePath };
|
|
23368
|
+
if (storage.acceptedFiles) result.acceptedFiles = storage.acceptedFiles;
|
|
23369
|
+
if (storage.imageResize) result.imageResize = storage.imageResize;
|
|
23370
|
+
if (storage.metadata) result.metadata = storage.metadata;
|
|
23371
|
+
if (typeof storage.fileName === "string") result.fileName = storage.fileName;
|
|
23372
|
+
if (storage.includeBucketUrl !== void 0) result.includeBucketUrl = storage.includeBucketUrl;
|
|
23373
|
+
if (storage.storeUrl !== void 0) result.storeUrl = storage.storeUrl;
|
|
23374
|
+
if (storage.maxSize !== void 0) result.maxSize = storage.maxSize;
|
|
23375
|
+
return result;
|
|
23376
|
+
}
|
|
23377
|
+
/**
|
|
23378
|
+
* Convert a RegExp or string `matches` validation to a plain string.
|
|
23379
|
+
*/
|
|
23380
|
+
function serializeMatches(matches) {
|
|
23381
|
+
if (matches === void 0) return void 0;
|
|
23382
|
+
if (typeof matches === "string") return matches;
|
|
23383
|
+
return matches.source;
|
|
23384
|
+
}
|
|
23385
|
+
/**
|
|
23386
|
+
* Resolve a relation target to a string slug.
|
|
23387
|
+
* Functions are called to extract the target; EntityCollection objects
|
|
23388
|
+
* use their slug.
|
|
23389
|
+
*/
|
|
23390
|
+
function resolveRelationTarget(target) {
|
|
23391
|
+
if (!target) return void 0;
|
|
23392
|
+
if (typeof target === "string") return target;
|
|
23393
|
+
if (typeof target === "function") try {
|
|
23394
|
+
const resolved = target();
|
|
23395
|
+
if (typeof resolved === "string") return resolved;
|
|
23396
|
+
if (resolved && typeof resolved === "object" && "slug" in resolved) return resolved.slug;
|
|
23397
|
+
} catch {}
|
|
23398
|
+
}
|
|
23399
|
+
/**
|
|
23400
|
+
* Strip non-serializable base property fields.
|
|
23401
|
+
* Returns a clean object with only the common serializable fields from BaseProperty.
|
|
23402
|
+
* Validation is intentionally excluded — each property type handles its own.
|
|
23403
|
+
*/
|
|
23404
|
+
function toSerializableBaseFields(property) {
|
|
23405
|
+
const result = { name: property.name };
|
|
23406
|
+
if (property.description) result.description = property.description;
|
|
23407
|
+
if (property.propertyConfig) result.propertyConfig = property.propertyConfig;
|
|
23408
|
+
if (property.columnName) result.columnName = property.columnName;
|
|
23409
|
+
if (property.defaultValue !== void 0) result.defaultValue = property.defaultValue;
|
|
23410
|
+
if (property.conditions) result.conditions = property.conditions;
|
|
23411
|
+
if (property.metadata) result.metadata = property.metadata;
|
|
23412
|
+
const ui = toSerializableUIConfig(property.ui);
|
|
23413
|
+
if (ui) result.ui = ui;
|
|
23414
|
+
return result;
|
|
23415
|
+
}
|
|
23416
|
+
/**
|
|
23417
|
+
* Convert a single `Property` to its JSON-serializable form.
|
|
23418
|
+
* Strips functions, converts RegExp to string, Date to ISO string.
|
|
23419
|
+
*/
|
|
23420
|
+
function toSerializableProperty(property) {
|
|
23421
|
+
const base = toSerializableBaseFields(property);
|
|
23422
|
+
switch (property.type) {
|
|
23423
|
+
case "string": {
|
|
23424
|
+
const sp = property;
|
|
23425
|
+
const result = {
|
|
23426
|
+
...base,
|
|
23427
|
+
type: "string"
|
|
23428
|
+
};
|
|
23429
|
+
if (sp.columnType) result.columnType = sp.columnType;
|
|
23430
|
+
if (sp.isId !== void 0) result.isId = sp.isId;
|
|
23431
|
+
if (sp.enum) result.enum = sp.enum;
|
|
23432
|
+
if (sp.multiline) result.multiline = sp.multiline;
|
|
23433
|
+
if (sp.markdown) result.markdown = sp.markdown;
|
|
23434
|
+
if (sp.userSelect) result.userSelect = sp.userSelect;
|
|
23435
|
+
if (sp.url) result.url = sp.url;
|
|
23436
|
+
if (sp.email) result.email = sp.email;
|
|
23437
|
+
if (sp.previewAsTag) result.previewAsTag = sp.previewAsTag;
|
|
23438
|
+
if (sp.validation) {
|
|
23439
|
+
const { matches, ...restValidation } = sp.validation;
|
|
23440
|
+
const serializedValidation = { ...restValidation };
|
|
23441
|
+
const matchStr = serializeMatches(matches);
|
|
23442
|
+
if (matchStr !== void 0) serializedValidation.matches = matchStr;
|
|
23443
|
+
result.validation = serializedValidation;
|
|
23444
|
+
}
|
|
23445
|
+
const storage = toSerializableStorageConfig(sp.storage);
|
|
23446
|
+
if (storage) result.storage = storage;
|
|
23447
|
+
if (sp.reference) result.reference = toSerializableProperty(sp.reference);
|
|
23448
|
+
return result;
|
|
23449
|
+
}
|
|
23450
|
+
case "number": {
|
|
23451
|
+
const np = property;
|
|
23452
|
+
const result = {
|
|
23453
|
+
...base,
|
|
23454
|
+
type: "number"
|
|
23455
|
+
};
|
|
23456
|
+
if (np.columnType) result.columnType = np.columnType;
|
|
23457
|
+
if (np.validation) result.validation = np.validation;
|
|
23458
|
+
if (np.isId !== void 0) result.isId = np.isId;
|
|
23459
|
+
if (np.enum) result.enum = np.enum;
|
|
23460
|
+
return result;
|
|
23461
|
+
}
|
|
23462
|
+
case "boolean": {
|
|
23463
|
+
const bp = property;
|
|
23464
|
+
const result = {
|
|
23465
|
+
...base,
|
|
23466
|
+
type: "boolean"
|
|
23467
|
+
};
|
|
23468
|
+
if (bp.validation) result.validation = bp.validation;
|
|
23469
|
+
return result;
|
|
23470
|
+
}
|
|
23471
|
+
case "date": {
|
|
23472
|
+
const dp = property;
|
|
23473
|
+
const result = {
|
|
23474
|
+
...base,
|
|
23475
|
+
type: "date"
|
|
23476
|
+
};
|
|
23477
|
+
if (dp.columnType) result.columnType = dp.columnType;
|
|
23478
|
+
if (dp.mode) result.mode = dp.mode;
|
|
23479
|
+
if (dp.timezone) result.timezone = dp.timezone;
|
|
23480
|
+
if (dp.autoValue) result.autoValue = dp.autoValue;
|
|
23481
|
+
if (dp.clearable) result.clearable = dp.clearable;
|
|
23482
|
+
if (dp.validation) {
|
|
23483
|
+
const serializedValidation = {};
|
|
23484
|
+
if (dp.validation.required !== void 0) serializedValidation.required = dp.validation.required;
|
|
23485
|
+
if (dp.validation.requiredMessage) serializedValidation.requiredMessage = dp.validation.requiredMessage;
|
|
23486
|
+
if (dp.validation.unique) serializedValidation.unique = dp.validation.unique;
|
|
23487
|
+
if (dp.validation.uniqueInArray) serializedValidation.uniqueInArray = dp.validation.uniqueInArray;
|
|
23488
|
+
if (dp.validation.min instanceof Date) serializedValidation.min = dp.validation.min.toISOString();
|
|
23489
|
+
else if (typeof dp.validation.min === "string") serializedValidation.min = dp.validation.min;
|
|
23490
|
+
if (dp.validation.max instanceof Date) serializedValidation.max = dp.validation.max.toISOString();
|
|
23491
|
+
else if (typeof dp.validation.max === "string") serializedValidation.max = dp.validation.max;
|
|
23492
|
+
result.validation = serializedValidation;
|
|
23493
|
+
}
|
|
23494
|
+
return result;
|
|
23495
|
+
}
|
|
23496
|
+
case "geopoint": {
|
|
23497
|
+
const gp = property;
|
|
23498
|
+
const result = {
|
|
23499
|
+
...base,
|
|
23500
|
+
type: "geopoint"
|
|
23501
|
+
};
|
|
23502
|
+
if (gp.validation) result.validation = gp.validation;
|
|
23503
|
+
return result;
|
|
23504
|
+
}
|
|
23505
|
+
case "reference": {
|
|
23506
|
+
const rp = property;
|
|
23507
|
+
const result = {
|
|
23508
|
+
...base,
|
|
23509
|
+
type: "reference"
|
|
23510
|
+
};
|
|
23511
|
+
if (rp.isId !== void 0) result.isId = rp.isId;
|
|
23512
|
+
if (rp.path) result.path = rp.path;
|
|
23513
|
+
if (rp.fixedFilter) result.fixedFilter = rp.fixedFilter;
|
|
23514
|
+
if (rp.includeId !== void 0) result.includeId = rp.includeId;
|
|
23515
|
+
if (rp.includeEntityLink !== void 0) result.includeEntityLink = rp.includeEntityLink;
|
|
23516
|
+
return result;
|
|
23517
|
+
}
|
|
23518
|
+
case "relation": {
|
|
23519
|
+
const rl = property;
|
|
23520
|
+
const result = {
|
|
23521
|
+
...base,
|
|
23522
|
+
type: "relation"
|
|
23523
|
+
};
|
|
23524
|
+
if (rl.isId !== void 0) result.isId = rl.isId;
|
|
23525
|
+
const target = resolveRelationTarget(rl.target);
|
|
23526
|
+
if (target) result.target = target;
|
|
23527
|
+
if (rl.cardinality) result.cardinality = rl.cardinality;
|
|
23528
|
+
if (rl.direction) result.direction = rl.direction;
|
|
23529
|
+
if (rl.inverseRelationName) result.inverseRelationName = rl.inverseRelationName;
|
|
23530
|
+
if (rl.localKey) result.localKey = rl.localKey;
|
|
23531
|
+
if (rl.foreignKeyOnTarget) result.foreignKeyOnTarget = rl.foreignKeyOnTarget;
|
|
23532
|
+
if (rl.through) result.through = rl.through;
|
|
23533
|
+
if (rl.joinPath) result.joinPath = rl.joinPath;
|
|
23534
|
+
if (rl.onUpdate) result.onUpdate = rl.onUpdate;
|
|
23535
|
+
if (rl.onDelete) result.onDelete = rl.onDelete;
|
|
23536
|
+
if (rl.relationName) result.relationName = rl.relationName;
|
|
23537
|
+
if (rl.fixedFilter) result.fixedFilter = rl.fixedFilter;
|
|
23538
|
+
if (rl.includeId !== void 0) result.includeId = rl.includeId;
|
|
23539
|
+
if (rl.includeEntityLink !== void 0) result.includeEntityLink = rl.includeEntityLink;
|
|
23540
|
+
if (rl.widget) result.widget = rl.widget;
|
|
23541
|
+
return result;
|
|
23542
|
+
}
|
|
23543
|
+
case "array": {
|
|
23544
|
+
const ap = property;
|
|
23545
|
+
const result = {
|
|
23546
|
+
...base,
|
|
23547
|
+
type: "array"
|
|
23548
|
+
};
|
|
23549
|
+
if (ap.columnType) result.columnType = ap.columnType;
|
|
23550
|
+
if (ap.validation) result.validation = ap.validation;
|
|
23551
|
+
if (ap.sortable !== void 0) result.sortable = ap.sortable;
|
|
23552
|
+
if (ap.canAddElements !== void 0) result.canAddElements = ap.canAddElements;
|
|
23553
|
+
if (ap.of) if (Array.isArray(ap.of)) result.of = ap.of.map(toSerializableProperty);
|
|
23554
|
+
else result.of = toSerializableProperty(ap.of);
|
|
23555
|
+
if (ap.oneOf) {
|
|
23556
|
+
result.oneOf = { properties: toSerializableProperties(ap.oneOf.properties) };
|
|
23557
|
+
if (ap.oneOf.propertiesOrder) result.oneOf.propertiesOrder = ap.oneOf.propertiesOrder;
|
|
23558
|
+
if (ap.oneOf.typeField) result.oneOf.typeField = ap.oneOf.typeField;
|
|
23559
|
+
if (ap.oneOf.valueField) result.oneOf.valueField = ap.oneOf.valueField;
|
|
23560
|
+
}
|
|
23561
|
+
return result;
|
|
23562
|
+
}
|
|
23563
|
+
case "map": {
|
|
23564
|
+
const mp = property;
|
|
23565
|
+
const result = {
|
|
23566
|
+
...base,
|
|
23567
|
+
type: "map"
|
|
23568
|
+
};
|
|
23569
|
+
if (mp.columnType) result.columnType = mp.columnType;
|
|
23570
|
+
if (mp.validation) result.validation = mp.validation;
|
|
23571
|
+
if (mp.propertiesOrder) result.propertiesOrder = mp.propertiesOrder;
|
|
23572
|
+
if (mp.previewProperties) result.previewProperties = mp.previewProperties;
|
|
23573
|
+
if (mp.keyValue) result.keyValue = mp.keyValue;
|
|
23574
|
+
if (mp.properties) result.properties = toSerializableProperties(mp.properties);
|
|
23575
|
+
return result;
|
|
23576
|
+
}
|
|
23577
|
+
case "vector": {
|
|
23578
|
+
const vp = property;
|
|
23579
|
+
const result = {
|
|
23580
|
+
...base,
|
|
23581
|
+
type: "vector",
|
|
23582
|
+
dimensions: vp.dimensions
|
|
23583
|
+
};
|
|
23584
|
+
if (vp.validation) result.validation = vp.validation;
|
|
23585
|
+
return result;
|
|
23586
|
+
}
|
|
23587
|
+
case "binary": {
|
|
23588
|
+
const bp = property;
|
|
23589
|
+
const result = {
|
|
23590
|
+
...base,
|
|
23591
|
+
type: "binary"
|
|
23592
|
+
};
|
|
23593
|
+
if (bp.validation) result.validation = bp.validation;
|
|
23594
|
+
return result;
|
|
23595
|
+
}
|
|
23596
|
+
default: return base;
|
|
23597
|
+
}
|
|
23598
|
+
}
|
|
23599
|
+
/**
|
|
23600
|
+
* Convert a `Properties` record to its serializable form.
|
|
23601
|
+
*/
|
|
23602
|
+
function toSerializableProperties(properties) {
|
|
23603
|
+
const result = {};
|
|
23604
|
+
for (const [key, property] of Object.entries(properties)) result[key] = toSerializableProperty(property);
|
|
23605
|
+
return result;
|
|
23606
|
+
}
|
|
23607
|
+
/**
|
|
23608
|
+
* Convert an `EntityCollection` to its JSON-serializable form.
|
|
23609
|
+
*
|
|
23610
|
+
* Strips all non-serializable fields (functions, React nodes, class instances)
|
|
23611
|
+
* while preserving the structural schema that the collection editor works with.
|
|
23612
|
+
*
|
|
23613
|
+
* The result is safe for `JSON.stringify()` and database storage.
|
|
23614
|
+
*/
|
|
23615
|
+
function toSerializableCollection(collection) {
|
|
23616
|
+
const result = {
|
|
23617
|
+
slug: collection.slug,
|
|
23618
|
+
name: collection.name,
|
|
23619
|
+
properties: toSerializableProperties(collection.properties)
|
|
23620
|
+
};
|
|
23621
|
+
if (collection.singularName) result.singularName = collection.singularName;
|
|
23622
|
+
if (collection.description) result.description = collection.description;
|
|
23623
|
+
if (collection.group) result.group = collection.group;
|
|
23624
|
+
if (collection.driver) result.driver = collection.driver;
|
|
23625
|
+
if (collection.databaseId) result.databaseId = collection.databaseId;
|
|
23626
|
+
if (collection.titleProperty) result.titleProperty = collection.titleProperty;
|
|
23627
|
+
if (collection.ownerId) result.ownerId = collection.ownerId;
|
|
23628
|
+
if (collection.metadata) result.metadata = collection.metadata;
|
|
23629
|
+
if (collection.table) result.table = collection.table;
|
|
23630
|
+
if ("schema" in collection && collection.schema) result.schema = collection.schema;
|
|
23631
|
+
if (collection.orderProperty) result.orderProperty = collection.orderProperty;
|
|
23632
|
+
if (typeof collection.icon === "string") result.icon = collection.icon;
|
|
23633
|
+
if (collection.propertiesOrder) result.propertiesOrder = collection.propertiesOrder;
|
|
23634
|
+
if (collection.previewProperties) result.previewProperties = collection.previewProperties;
|
|
23635
|
+
if (collection.listProperties) result.listProperties = collection.listProperties;
|
|
23636
|
+
if (collection.enabledViews) result.enabledViews = collection.enabledViews;
|
|
23637
|
+
if (collection.disableDefaultActions) result.disableDefaultActions = collection.disableDefaultActions;
|
|
23638
|
+
if (collection.securityRules) result.securityRules = collection.securityRules;
|
|
23639
|
+
if (collection.openEntityMode) result.openEntityMode = collection.openEntityMode;
|
|
23640
|
+
if (collection.defaultEntityAction) result.defaultEntityAction = collection.defaultEntityAction;
|
|
23641
|
+
if (collection.defaultViewMode) result.defaultViewMode = collection.defaultViewMode;
|
|
23642
|
+
if (collection.defaultSize) result.defaultSize = collection.defaultSize;
|
|
23643
|
+
if (collection.localChangesBackup !== void 0) result.localChangesBackup = collection.localChangesBackup;
|
|
23644
|
+
if (collection.kanban) result.kanban = collection.kanban;
|
|
23645
|
+
if (collection.fixedFilter) result.fixedFilter = collection.fixedFilter;
|
|
23646
|
+
if (collection.defaultFilter) result.defaultFilter = collection.defaultFilter;
|
|
23647
|
+
if (collection.filterPresets) result.filterPresets = collection.filterPresets;
|
|
23648
|
+
if (collection.sort) result.sort = collection.sort;
|
|
23649
|
+
if (collection.pagination !== void 0) result.pagination = collection.pagination;
|
|
23650
|
+
if (collection.selectionEnabled !== void 0) result.selectionEnabled = collection.selectionEnabled;
|
|
23651
|
+
if (collection.inlineEditing !== void 0) result.inlineEditing = collection.inlineEditing;
|
|
23652
|
+
if (collection.hideFromNavigation !== void 0) result.hideFromNavigation = collection.hideFromNavigation;
|
|
23653
|
+
if (collection.hideIdFromForm !== void 0) result.hideIdFromForm = collection.hideIdFromForm;
|
|
23654
|
+
if (collection.hideIdFromCollection !== void 0) result.hideIdFromCollection = collection.hideIdFromCollection;
|
|
23655
|
+
if (collection.formAutoSave !== void 0) result.formAutoSave = collection.formAutoSave;
|
|
23656
|
+
if (collection.alwaysApplyDefaultValues !== void 0) result.alwaysApplyDefaultValues = collection.alwaysApplyDefaultValues;
|
|
23657
|
+
if (collection.includeJsonView !== void 0) result.includeJsonView = collection.includeJsonView;
|
|
23658
|
+
if (collection.history !== void 0) result.history = collection.history;
|
|
23659
|
+
if (collection.sideDialogWidth !== void 0) result.sideDialogWidth = collection.sideDialogWidth;
|
|
23660
|
+
if (typeof collection.exportable === "boolean") result.exportable = collection.exportable;
|
|
23661
|
+
if (typeof collection.auth === "boolean") result.auth = collection.auth;
|
|
23662
|
+
if (typeof collection.defaultSelectedView === "string") result.defaultSelectedView = collection.defaultSelectedView;
|
|
23663
|
+
return result;
|
|
23664
|
+
}
|
|
23665
|
+
/**
|
|
23666
|
+
* Convert a serializable property back to the original `Property` type.
|
|
23667
|
+
*
|
|
23668
|
+
* This is a mostly pass-through operation since serializable properties
|
|
23669
|
+
* are a subset of the original types. The main conversions are:
|
|
23670
|
+
* - `validation.matches` (string) stays as string (compatible with Property)
|
|
23671
|
+
* - `validation.min/max` on dates: ISO strings → Date objects
|
|
23672
|
+
*/
|
|
23673
|
+
function fromSerializableProperty(serialized) {
|
|
23674
|
+
switch (serialized.type) {
|
|
23675
|
+
case "date": {
|
|
23676
|
+
const { validation: dateValidation, ...dateRest } = serialized;
|
|
23677
|
+
const result = {
|
|
23678
|
+
...dateRest,
|
|
23679
|
+
type: "date"
|
|
23680
|
+
};
|
|
23681
|
+
if (dateValidation) {
|
|
23682
|
+
const convertedValidation = { ...dateValidation };
|
|
23683
|
+
if (typeof dateValidation.min === "string") convertedValidation.min = new Date(dateValidation.min);
|
|
23684
|
+
if (typeof dateValidation.max === "string") convertedValidation.max = new Date(dateValidation.max);
|
|
23685
|
+
result.validation = convertedValidation;
|
|
23686
|
+
}
|
|
23687
|
+
return result;
|
|
23688
|
+
}
|
|
23689
|
+
case "array": {
|
|
23690
|
+
const sp = serialized;
|
|
23691
|
+
const result = { ...sp };
|
|
23692
|
+
if (sp.of) if (Array.isArray(sp.of)) result.of = sp.of.map(fromSerializableProperty);
|
|
23693
|
+
else result.of = fromSerializableProperty(sp.of);
|
|
23694
|
+
if (sp.oneOf) result.oneOf = {
|
|
23695
|
+
...sp.oneOf,
|
|
23696
|
+
properties: fromSerializableProperties(sp.oneOf.properties)
|
|
23697
|
+
};
|
|
23698
|
+
return result;
|
|
23699
|
+
}
|
|
23700
|
+
case "map": {
|
|
23701
|
+
const sp = serialized;
|
|
23702
|
+
const result = { ...sp };
|
|
23703
|
+
if (sp.properties) result.properties = fromSerializableProperties(sp.properties);
|
|
23704
|
+
return result;
|
|
23705
|
+
}
|
|
23706
|
+
case "string": {
|
|
23707
|
+
const sp = serialized;
|
|
23708
|
+
const result = { ...sp };
|
|
23709
|
+
if (sp.reference) result.reference = fromSerializableProperty(sp.reference);
|
|
23710
|
+
return result;
|
|
23711
|
+
}
|
|
23712
|
+
default: return serialized;
|
|
23713
|
+
}
|
|
23714
|
+
}
|
|
23715
|
+
/**
|
|
23716
|
+
* Convert a `SerializableProperties` record back to `Properties`.
|
|
23717
|
+
*/
|
|
23718
|
+
function fromSerializableProperties(serialized) {
|
|
23719
|
+
const result = {};
|
|
23720
|
+
for (const [key, property] of Object.entries(serialized)) result[key] = fromSerializableProperty(property);
|
|
23721
|
+
return result;
|
|
23722
|
+
}
|
|
23723
|
+
/**
|
|
23724
|
+
* Convert a `SerializableCollection` back to an `EntityCollection`.
|
|
23725
|
+
*
|
|
23726
|
+
* The result will NOT contain any of the non-serializable fields
|
|
23727
|
+
* (callbacks, entityActions, etc.) — those must be re-attached by the
|
|
23728
|
+
* consumer if needed.
|
|
23729
|
+
*/
|
|
23730
|
+
function fromSerializableCollection(serialized) {
|
|
23731
|
+
const { properties, ...rest } = serialized;
|
|
23732
|
+
return {
|
|
23733
|
+
...rest,
|
|
23734
|
+
properties: fromSerializableProperties(properties)
|
|
23735
|
+
};
|
|
23736
|
+
}
|
|
23737
|
+
//#endregion
|
|
23738
|
+
//#region src/collection_editor/useJsonCollectionsConfigController.tsx
|
|
23739
|
+
/**
|
|
23740
|
+
* A `CollectionsConfigController` implementation that persists collection
|
|
23741
|
+
* configurations as JSON via a pluggable `JsonCollectionStore` adapter.
|
|
23742
|
+
*
|
|
23743
|
+
* Use this when you want to store collection schemas in a database, API,
|
|
23744
|
+
* localStorage, or any other JSON-compatible backend — as opposed to the
|
|
23745
|
+
* default code-based persistence.
|
|
23746
|
+
*
|
|
23747
|
+
* @example
|
|
23748
|
+
* ```tsx
|
|
23749
|
+
* const store: JsonCollectionStore = {
|
|
23750
|
+
* load: async () => { const res = await fetch('/api/schemas'); return res.json(); },
|
|
23751
|
+
* save: async (slug, data) => { await fetch(`/api/schemas/${slug}`, { method: 'PUT', body: JSON.stringify(data) }); },
|
|
23752
|
+
* delete: async (slug) => { await fetch(`/api/schemas/${slug}`, { method: 'DELETE' }); },
|
|
23753
|
+
* };
|
|
23754
|
+
*
|
|
23755
|
+
* const configController = useJsonCollectionsConfigController({ store });
|
|
23756
|
+
* ```
|
|
23757
|
+
*/
|
|
23758
|
+
function useJsonCollectionsConfigController({ store, readOnly = false, autoLoad = true }) {
|
|
23759
|
+
const [collections, setCollections] = useState([]);
|
|
23760
|
+
const [loading, setLoading] = useState(autoLoad);
|
|
23761
|
+
const [navigationEntries, setNavigationEntries] = useState([]);
|
|
23762
|
+
const storeRef = useRef(store);
|
|
23763
|
+
storeRef.current = store;
|
|
23764
|
+
useEffect(() => {
|
|
23765
|
+
if (!autoLoad) return;
|
|
23766
|
+
let cancelled = false;
|
|
23767
|
+
(async () => {
|
|
23768
|
+
try {
|
|
23769
|
+
const [serialized, navEntries] = await Promise.all([storeRef.current.load(), storeRef.current.loadNavigationEntries?.() ?? Promise.resolve([])]);
|
|
23770
|
+
if (cancelled) return;
|
|
23771
|
+
setCollections(serialized.map(fromSerializableCollection));
|
|
23772
|
+
setNavigationEntries(navEntries);
|
|
23773
|
+
} catch (e) {
|
|
23774
|
+
console.error("useJsonCollectionsConfigController: failed to load collections", e);
|
|
23775
|
+
} finally {
|
|
23776
|
+
if (!cancelled) setLoading(false);
|
|
23777
|
+
}
|
|
23778
|
+
})();
|
|
23779
|
+
return () => {
|
|
23780
|
+
cancelled = true;
|
|
23781
|
+
};
|
|
23782
|
+
}, [autoLoad]);
|
|
23783
|
+
/**
|
|
23784
|
+
* Update local state and persist a collection to the store.
|
|
23785
|
+
*/
|
|
23786
|
+
const persistCollection = useCallback(async (collection) => {
|
|
23787
|
+
const serializable = toSerializableCollection(collection);
|
|
23788
|
+
await storeRef.current.save(collection.slug, serializable);
|
|
23789
|
+
}, []);
|
|
23790
|
+
/**
|
|
23791
|
+
* Find collection by id (slug).
|
|
23792
|
+
*/
|
|
23793
|
+
const getCollection = useCallback((id) => {
|
|
23794
|
+
const found = collections.find((c) => c.slug === id);
|
|
23795
|
+
if (found) return found;
|
|
23796
|
+
throw new Error(`Collection "${id}" not found`);
|
|
23797
|
+
}, [collections]);
|
|
23798
|
+
const saveCollection = useCallback(async ({ id, collectionData }) => {
|
|
23799
|
+
const collection = collectionData;
|
|
23800
|
+
await persistCollection(collection);
|
|
23801
|
+
setCollections((prev) => {
|
|
23802
|
+
const idx = prev.findIndex((c) => c.slug === id);
|
|
23803
|
+
if (idx >= 0) {
|
|
23804
|
+
const next = [...prev];
|
|
23805
|
+
next[idx] = collection;
|
|
23806
|
+
return next;
|
|
23807
|
+
}
|
|
23808
|
+
return [...prev, collection];
|
|
23809
|
+
});
|
|
23810
|
+
}, [persistCollection]);
|
|
23811
|
+
const updateCollection = useCallback(async ({ id, collectionData, previousId }) => {
|
|
23812
|
+
setCollections((prev) => {
|
|
23813
|
+
const lookupId = previousId ?? id;
|
|
23814
|
+
const idx = prev.findIndex((c) => c.slug === lookupId);
|
|
23815
|
+
if (idx < 0) return prev;
|
|
23816
|
+
const merged = {
|
|
23817
|
+
...prev[idx],
|
|
23818
|
+
...collectionData
|
|
23819
|
+
};
|
|
23820
|
+
const next = [...prev];
|
|
23821
|
+
next[idx] = merged;
|
|
23822
|
+
persistCollection(merged).catch((e) => console.error("useJsonCollectionsConfigController: failed to update collection", e));
|
|
23823
|
+
if (previousId && previousId !== id) storeRef.current.delete(previousId).catch((e) => console.error("useJsonCollectionsConfigController: failed to delete old slug", e));
|
|
23824
|
+
return next;
|
|
23825
|
+
});
|
|
23826
|
+
}, [persistCollection]);
|
|
23827
|
+
const deleteCollection = useCallback(async ({ id }) => {
|
|
23828
|
+
await storeRef.current.delete(id);
|
|
23829
|
+
setCollections((prev) => prev.filter((c) => c.slug !== id));
|
|
23830
|
+
}, []);
|
|
23831
|
+
const saveProperty = useCallback(async ({ path, propertyKey, property, newPropertiesOrder }) => {
|
|
23832
|
+
setCollections((prev) => {
|
|
23833
|
+
const idx = prev.findIndex((c) => c.slug === path);
|
|
23834
|
+
if (idx < 0) return prev;
|
|
23835
|
+
const collection = { ...prev[idx] };
|
|
23836
|
+
collection.properties = {
|
|
23837
|
+
...collection.properties,
|
|
23838
|
+
[propertyKey]: property
|
|
23839
|
+
};
|
|
23840
|
+
if (newPropertiesOrder) collection.propertiesOrder = newPropertiesOrder;
|
|
23841
|
+
const next = [...prev];
|
|
23842
|
+
next[idx] = collection;
|
|
23843
|
+
persistCollection(next[idx]).catch((e) => console.error("useJsonCollectionsConfigController: failed to save property", e));
|
|
23844
|
+
return next;
|
|
23845
|
+
});
|
|
23846
|
+
}, [persistCollection]);
|
|
23847
|
+
const deleteProperty = useCallback(async ({ path, propertyKey, newPropertiesOrder }) => {
|
|
23848
|
+
setCollections((prev) => {
|
|
23849
|
+
const idx = prev.findIndex((c) => c.slug === path);
|
|
23850
|
+
if (idx < 0) return prev;
|
|
23851
|
+
const collection = { ...prev[idx] };
|
|
23852
|
+
const { [propertyKey]: _removed, ...remaining } = collection.properties;
|
|
23853
|
+
collection.properties = remaining;
|
|
23854
|
+
if (newPropertiesOrder) collection.propertiesOrder = newPropertiesOrder;
|
|
23855
|
+
const next = [...prev];
|
|
23856
|
+
next[idx] = collection;
|
|
23857
|
+
persistCollection(next[idx]).catch((e) => console.error("useJsonCollectionsConfigController: failed to delete property", e));
|
|
23858
|
+
return next;
|
|
23859
|
+
});
|
|
23860
|
+
}, [persistCollection]);
|
|
23861
|
+
const updatePropertiesOrder = useCallback(async ({ collection, newPropertiesOrder }) => {
|
|
23862
|
+
const updated = {
|
|
23863
|
+
...collection,
|
|
23864
|
+
propertiesOrder: newPropertiesOrder
|
|
23865
|
+
};
|
|
23866
|
+
setCollections((prev) => {
|
|
23867
|
+
const idx = prev.findIndex((c) => c.slug === collection.slug);
|
|
23868
|
+
if (idx < 0) return prev;
|
|
23869
|
+
const next = [...prev];
|
|
23870
|
+
next[idx] = updated;
|
|
23871
|
+
return next;
|
|
23872
|
+
});
|
|
23873
|
+
await persistCollection(updated);
|
|
23874
|
+
}, [persistCollection]);
|
|
23875
|
+
const updateKanbanColumnsOrder = useCallback(async ({ collection, kanbanColumnProperty, newColumnsOrder }) => {}, []);
|
|
23876
|
+
const saveNavigationEntriesHandler = useCallback(async (entries) => {
|
|
23877
|
+
setNavigationEntries(entries);
|
|
23878
|
+
if (storeRef.current.saveNavigationEntries) await storeRef.current.saveNavigationEntries(entries);
|
|
23879
|
+
}, []);
|
|
23880
|
+
return useMemo(() => ({
|
|
23881
|
+
loading,
|
|
23882
|
+
readOnly,
|
|
23883
|
+
readOnlyReason: readOnly ? "Configuration is read-only." : void 0,
|
|
23884
|
+
collections,
|
|
23885
|
+
getCollection,
|
|
23886
|
+
saveCollection,
|
|
23887
|
+
updateCollection,
|
|
23888
|
+
deleteCollection,
|
|
23889
|
+
saveProperty,
|
|
23890
|
+
deleteProperty,
|
|
23891
|
+
updatePropertiesOrder,
|
|
23892
|
+
updateKanbanColumnsOrder,
|
|
23893
|
+
navigationEntries,
|
|
23894
|
+
saveNavigationEntries: saveNavigationEntriesHandler
|
|
23895
|
+
}), [
|
|
23896
|
+
loading,
|
|
23897
|
+
readOnly,
|
|
23898
|
+
collections,
|
|
23899
|
+
getCollection,
|
|
23900
|
+
saveCollection,
|
|
23901
|
+
updateCollection,
|
|
23902
|
+
deleteCollection,
|
|
23903
|
+
saveProperty,
|
|
23904
|
+
deleteProperty,
|
|
23905
|
+
updatePropertiesOrder,
|
|
23906
|
+
updateKanbanColumnsOrder,
|
|
23907
|
+
navigationEntries,
|
|
23908
|
+
saveNavigationEntriesHandler
|
|
23909
|
+
]);
|
|
23910
|
+
}
|
|
23911
|
+
//#endregion
|
|
23609
23912
|
//#region src/collection_editor/validateCollectionJson.ts
|
|
23610
23913
|
/**
|
|
23611
23914
|
* Valid dataType values for properties
|
|
@@ -24006,6 +24309,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
|
|
|
24006
24309
|
return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
|
|
24007
24310
|
}
|
|
24008
24311
|
//#endregion
|
|
24009
|
-
export {
|
|
24312
|
+
export { RelationPreview as $, SelectableTable as $n, DateTimeFieldBinding as $t, useCollectionDataController as A, getLastSegment$1 as An, renderSkeletonCaptionText as Ar, useImportConfig as At, ArrayOfMapsPreview as B, UrlContext as Bn, getFieldId as Bt, downloadEntitiesExport as C, deleteEntityAction as Cn, isRelationProperty as Cr, useCollectionEditorController as Ct, ImportDataPreview as D, addInitialSlash as Dn, StorageThumbnail as Dr, flattenEntry as Dt, ImportCollectionAction as E, CreationResultDialog as En, EnumValuesChip as Er, convertDataToEntity as Et, extractTouchedValues as F, useBuildCollectionRegistryController as Fn, sanitizeUrl as Fr, PropertyConfigBadge as Ft, DatePreview as G, useCollectionRegistryController as Gn, SelectFieldBinding as Gt, UserPreview as H, NavigationStateContext as Hn, TextFieldBinding as Ht, getChanges as I, useEntityHistory as In, ImagePreview as Ir, DEFAULT_FIELD_CONFIGS as It, ArrayOneOfPreview as J, getEntityViewWidth as Jn, ReferenceAsStringFieldBinding as Jt, KeyValuePreview as K, useSideDialogsController as Kn, RepeatFieldBinding as Kt, getInitialEntityValues as L, resolveNavigationFrom as Ln, EmptyValue as Lr, getDefaultFieldConfig as Lt, mapPropertyToConfig as M, removeInitialSlash as Mn, renderSkeletonImageThumbnail as Mr, ImportFileUpload as Mt, EntityFormBinding as N, removeTrailingSlash as Nn, renderSkeletonText as Nr, convertFileToJson as Nt, PropertySelectEntry as O, getCollectionBySlugWithin as On, StorageThumbnailInternal as Or, processValueMapping as Ot, EntityForm as P, resolveCollectionPathIds$1 as Pn, UrlComponentPreview as Pr, unflattenObject as Pt, ArrayOfStorageComponentsPreview as Q, EntityCollectionTable as Qn, KeyValueFieldBinding as Qt, removeEmptyContainers as R, useResolvedNavigationFrom as Rn, getDefaultFieldId as Rt, downloadDataAsCsv as S, copyEntityAction as Sn, isReferenceProperty as Sr, EntityCollectionViewActions as St, getEntityJsonExportableData as T, resetPasswordAction as Tn, StringPropertyPreview as Tr, DataNewPropertiesMapping as Tt, NumberPropertyPreview as U, useNavigationStateController as Un, SwitchFieldBinding as Ut, PropertyPreview as V, useUrlController as Vn, VectorFieldBinding as Vt, BooleanPreview as W, CollectionRegistryContext as Wn, StorageUploadFieldBinding as Wt, ArrayPropertyEnumPreview as X, mergeEntityActions as Xn, MarkdownEditorFieldBinding as Xt, ArrayOfStringsPreview as Y, useBuildSideEntityController as Yn, MultiSelectFieldBinding as Yt, ArrayEnumPreview as Z, useSelectionController as Zn, MapFieldBinding as Zt, toSerializableProperty as _, useBuildUrlController as _n, getIconForProperty as _r, SideEntityProvider as _t, namespaceToPropertiesPath as a, ReadOnlyFieldBinding as an, useEntitySelectionDialog as ar, RebaseLayout as at, BasicExportAction as b, useResolvedViews as bn, getPropertyInPath as br, EntityCollectionCardView as bt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as c, LabelWithIconAndTooltip as cn, resolveEntityView as cr, RebaseAuthGate as ct, useJsonCollectionsConfigController as d, FieldHelperText as dn, ReferencePreview as dr, Drawer as dt, BlockFieldBinding as en, EntityCollectionRowActions as er, CollectionPanel as et, fromSerializableCollection as f, SearchIconsView as fn, EntityPreview as fr, DefaultDrawer as ft, toSerializableProperties as g, useBreadcrumbsController as gn, getDefaultPropertiesOrder as gr, AdminModeSyncer as gt, toSerializableCollection as h, SideDialogs as hn, getBracketNotation as hr, useApp as ht, namespaceToPropertiesOrderPath as i, PropertyFieldBinding as in, useSideEntityController as ir, RebaseRoute as it, mapPropertiesToConfigs as j, removeInitialAndTrailingSlashes$1 as jn, renderSkeletonIcon as jr, ImportSaveInProgress as jt, createStaticDataController as k, getCollectionPathsCombinations as kn, SkeletonPropertyComponent as kr, getInferenceType as kt, buildCollectionGenerationCallback as l, PropertyIdCopyTooltip as ln, EntityView as lr, RebaseCMS as lt, fromSerializableProperty as m, EntitySelectionTable as mn, getEntityTitlePropertyKey as mr, AppBar as mt, getFullIdPath as n, ArrayContainer as nn, SelectableTableContext as nr, RebaseRouteDefs as nt, MissingReferenceWidget as o, FormLayout as on, VirtualTableInput as or, RebaseNavigation as ot, fromSerializableProperties as p, FieldCaption as pn, getEntityPreviewKeys as pr, DrawerFooterActions as pt, MapPropertyPreview as q, buildSidePanelsFromUrl as qn, ReferenceFieldBinding as qt, idToPropertiesPath as r, ArrayCustomShapedFieldBinding as rn, SideEntityControllerContext as rr, CustomViewRoute as rt, CollectionGenerationApiError as s, FormEntry as sn, resolveEntityAction as sr, useLocalCollectionsConfigController as st, getFullId as t, ArrayOfReferencesFieldBinding as tn, useClearRestoreValue as tr, RebaseShell as tt, validateCollectionJson as u, LabelWithIcon as un, ArrayOfReferencesPreview as ur, Scaffold as ut, useCollectionsConfigController as v, useBuildNavigationStateController as vn, getIconForWidget as vr, ReferenceWidget as vt, getEntityCSVExportableData as w, editEntityAction as wn, ArrayPropertyPreview as wr, ImportNewPropertyFieldPreview as wt, downloadBlob as x, useResolvedCollections as xn, getResolvedPropertyInPath as xr, EntityCard as xt, ExportCollectionAction as y, useTopLevelNavigation as yn, getPropertiesWithPropertiesOrder as yr, EntityCollectionView as yt, zodToFormErrors as z, useCMSContext as zn, getFieldConfig as zt };
|
|
24010
24313
|
|
|
24011
|
-
//# sourceMappingURL=src-
|
|
24314
|
+
//# sourceMappingURL=src-DOX2nOyh.js.map
|