@rebasepro/admin 0.6.0 → 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/README.md +7 -3
- package/dist/{CollectionEditorDialog-jA-PLh6N.js → CollectionEditorDialog-iGgS4rLX.js} +233 -152
- package/dist/CollectionEditorDialog-iGgS4rLX.js.map +1 -0
- package/dist/{PropertyEditView-DEUddmg_.js → PropertyEditView-C9yhLyja.js} +129 -15
- package/dist/PropertyEditView-C9yhLyja.js.map +1 -0
- package/dist/{CollectionsStudioView-UKouBPOm.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/editor/editor.d.ts +10 -4
- package/dist/editor.js +6 -4
- package/dist/editor.js.map +1 -1
- package/dist/form/EntityForm.d.ts +10 -9
- package/dist/form/EntityFormBinding.d.ts +27 -0
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +2 -2
- package/dist/form/form_utils.d.ts +8 -0
- package/dist/form/index.d.ts +5 -1
- package/dist/{history-BL49TIDb.js → history-ChHgyyvM.js} +2 -2
- package/dist/{history-BL49TIDb.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 +3 -1
- package/dist/index.js +2 -2
- package/dist/routes/CustomViewRoute.d.ts +2 -1
- package/dist/{src-BJ8fkM8y.js → src-DOX2nOyh.js} +1634 -1330
- 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/AddKanbanColumnAction.tsx +2 -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/ArrayContainer.tsx +5 -5
- package/src/components/DefaultDrawer.tsx +13 -3
- package/src/components/DrawerNavigationGroup.tsx +2 -2
- package/src/components/DrawerNavigationItem.tsx +4 -4
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +14 -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/HomePage/NavigationCard.tsx +8 -14
- package/src/components/HomePage/NavigationGroup.tsx +1 -1
- package/src/components/HomePage/SmallNavigationCard.tsx +1 -5
- 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/SelectableTable/filters/StringNumberFilterField.tsx +28 -21
- package/src/components/SideDialogs.tsx +0 -7
- package/src/components/common/default_entity_actions.tsx +18 -10
- package/src/contexts/BreacrumbsContext.tsx +15 -6
- package/src/editor/editor.tsx +14 -8
- 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/MarkdownEditorFieldBinding.tsx +4 -4
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +1 -1
- 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 +7 -2
- 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-jA-PLh6N.js.map +0 -1
- package/dist/CollectionsStudioView-UKouBPOm.js.map +0 -1
- package/dist/PropertyEditView-DEUddmg_.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-BJ8fkM8y.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,
|
|
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,
|
|
@@ -4889,7 +4887,7 @@ function StringNumberFilterField({ name, value, setValue, type, isArray, enumVal
|
|
|
4889
4887
|
value: Array.isArray(internalValue) ? internalValue.map((e) => String(e)) : [],
|
|
4890
4888
|
disabled: isNullOperation,
|
|
4891
4889
|
onValueChange: (value) => {
|
|
4892
|
-
updateFilter(operation, type === "number" ? value.map((v) => parseInt(v)) : value);
|
|
4890
|
+
updateFilter(operation, type === "number" ? (value ?? []).map((v) => parseInt(v)) : value);
|
|
4893
4891
|
},
|
|
4894
4892
|
multiple,
|
|
4895
4893
|
endAdornment: internalValue && /* @__PURE__ */ jsx(IconButton, {
|
|
@@ -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
|
}
|
|
@@ -7546,9 +7554,9 @@ var deleteEntityAction = {
|
|
|
7546
7554
|
name: "Delete",
|
|
7547
7555
|
key: "delete",
|
|
7548
7556
|
isEnabled: ({ entity }) => Boolean(entity),
|
|
7549
|
-
onClick({ entity, path, collection, context, selectionController, onCollectionChange, navigateBack }) {
|
|
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,13 @@ 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
|
-
|
|
7574
|
+
if (openEntityMode === "full_screen" && context?.urlController) {
|
|
7575
|
+
const collectionUrl = context.urlController.buildUrlCollectionPath(path);
|
|
7576
|
+
context.urlController.navigate(collectionUrl, { replace: true });
|
|
7577
|
+
} else navigateBack?.();
|
|
7567
7578
|
},
|
|
7568
7579
|
onClose: closeDialog
|
|
7569
7580
|
});
|
|
@@ -7640,7 +7651,7 @@ var resetPasswordAction = {
|
|
|
7640
7651
|
isEnabled: ({ entity }) => Boolean(entity),
|
|
7641
7652
|
onClick({ entity, context }) {
|
|
7642
7653
|
if (!entity) throw new Error("INTERNAL: resetPasswordAction: Entity is undefined");
|
|
7643
|
-
if (!context
|
|
7654
|
+
if (!context?.dialogsController) throw new Error("INTERNAL: resetPasswordAction: context.dialogsController is undefined");
|
|
7644
7655
|
const user = {
|
|
7645
7656
|
uid: entity.id,
|
|
7646
7657
|
email: entity.values?.email,
|
|
@@ -7784,12 +7795,34 @@ async function resolveAppViews(baseViews, authController, data, plugins) {
|
|
|
7784
7795
|
if (plugins) {
|
|
7785
7796
|
for (const plugin of plugins) if (plugin.views && plugin.views.length > 0) resolvedViews = [...resolvedViews, ...plugin.views];
|
|
7786
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);
|
|
7787
7808
|
return resolvedViews;
|
|
7788
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
|
+
}
|
|
7789
7823
|
//#endregion
|
|
7790
7824
|
//#region src/hooks/navigation/utils.ts
|
|
7791
7825
|
var NAVIGATION_DEFAULT_GROUP_NAME = "Views";
|
|
7792
|
-
var NAVIGATION_ADMIN_GROUP_NAME = "Admin";
|
|
7793
7826
|
function getGroup(collectionOrView) {
|
|
7794
7827
|
const trimmed = collectionOrView.group?.trim();
|
|
7795
7828
|
if (!trimmed || trimmed === "") return NAVIGATION_DEFAULT_GROUP_NAME;
|
|
@@ -7798,7 +7831,8 @@ function getGroup(collectionOrView) {
|
|
|
7798
7831
|
function computeNavigationGroups({ navigationGroupMappings, collections, views, plugins }) {
|
|
7799
7832
|
let result = navigationGroupMappings ? navigationGroupMappings.map((g) => ({
|
|
7800
7833
|
name: g.name,
|
|
7801
|
-
entries: [...g.entries]
|
|
7834
|
+
entries: [...g.entries],
|
|
7835
|
+
...g.collapsedByDefault !== void 0 && { collapsedByDefault: g.collapsedByDefault }
|
|
7802
7836
|
})) : navigationGroupMappings;
|
|
7803
7837
|
if (plugins) result = plugins.reduce((acc, plugin) => {
|
|
7804
7838
|
if (plugin.hooks?.navigationEntries) plugin.hooks.navigationEntries.forEach((entry) => {
|
|
@@ -7888,48 +7922,38 @@ function areCollectionsEqual(a, b, visitedSlugs = []) {
|
|
|
7888
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")));
|
|
7889
7923
|
}
|
|
7890
7924
|
//#endregion
|
|
7891
|
-
//#region src/hooks/navigation/
|
|
7925
|
+
//#region src/hooks/navigation/useAsyncResolver.ts
|
|
7892
7926
|
/**
|
|
7893
|
-
*
|
|
7894
|
-
*
|
|
7895
|
-
*
|
|
7896
|
-
* When userManagement is provided, the default users collection is always
|
|
7897
|
-
* prepended. Developer collections override via generic slug-based dedup
|
|
7898
|
-
* (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.
|
|
7899
7930
|
*
|
|
7900
|
-
*
|
|
7901
|
-
*
|
|
7931
|
+
* Extracted from the common pattern in useResolvedCollections and
|
|
7932
|
+
* useResolvedViews.
|
|
7902
7933
|
*/
|
|
7903
|
-
function
|
|
7904
|
-
const
|
|
7934
|
+
function useAsyncResolver({ resolver, initialValue, isEqual, deps, disabled }) {
|
|
7935
|
+
const [data, setData] = useState(initialValue);
|
|
7905
7936
|
const [loading, setLoading] = useState(true);
|
|
7906
7937
|
const [error, setError] = useState(void 0);
|
|
7907
|
-
const [resolvedCollections, setResolvedCollections] = useState([]);
|
|
7908
7938
|
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
7909
7939
|
const refresh = useCallback(() => {
|
|
7910
7940
|
setRefreshTrigger((prev) => prev + 1);
|
|
7911
7941
|
}, []);
|
|
7912
|
-
const dataRef = useRef(
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
pluginsRef.current = plugins;
|
|
7918
|
-
const resolvedCollectionsRef = useRef([]);
|
|
7919
|
-
const initialLoading = authController.initialLoading;
|
|
7920
|
-
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;
|
|
7921
7947
|
useEffect(() => {
|
|
7922
|
-
if (disabled
|
|
7948
|
+
if (disabled) return;
|
|
7923
7949
|
let cancelled = false;
|
|
7924
7950
|
(async () => {
|
|
7925
7951
|
try {
|
|
7926
|
-
const
|
|
7952
|
+
const result = await resolverRef.current();
|
|
7927
7953
|
if (cancelled) return;
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
resolvedCollectionsRef.current = deduped;
|
|
7932
|
-
setResolvedCollections(deduped);
|
|
7954
|
+
if (!isEqualRef.current(dataRef.current, result)) {
|
|
7955
|
+
dataRef.current = result;
|
|
7956
|
+
setData(result);
|
|
7933
7957
|
}
|
|
7934
7958
|
setError(void 0);
|
|
7935
7959
|
} catch (e) {
|
|
@@ -7945,20 +7969,66 @@ function useResolvedCollections(props) {
|
|
|
7945
7969
|
cancelled = true;
|
|
7946
7970
|
};
|
|
7947
7971
|
}, [
|
|
7948
|
-
|
|
7949
|
-
disabled,
|
|
7950
|
-
collectionRegistryController.collectionRegistryRef,
|
|
7972
|
+
...deps,
|
|
7951
7973
|
refreshTrigger,
|
|
7952
|
-
|
|
7953
|
-
|
|
7974
|
+
disabled
|
|
7975
|
+
]);
|
|
7976
|
+
return useMemo(() => ({
|
|
7977
|
+
data,
|
|
7978
|
+
loading,
|
|
7979
|
+
error,
|
|
7980
|
+
refresh
|
|
7981
|
+
}), [
|
|
7982
|
+
data,
|
|
7983
|
+
loading,
|
|
7984
|
+
error,
|
|
7985
|
+
refresh
|
|
7954
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
|
+
});
|
|
7955
8025
|
return useMemo(() => ({
|
|
7956
|
-
collections
|
|
8026
|
+
collections,
|
|
7957
8027
|
loading,
|
|
7958
8028
|
error,
|
|
7959
8029
|
refresh
|
|
7960
8030
|
}), [
|
|
7961
|
-
|
|
8031
|
+
collections,
|
|
7962
8032
|
loading,
|
|
7963
8033
|
error,
|
|
7964
8034
|
refresh
|
|
@@ -7979,6 +8049,13 @@ function viewSlugsEqual(a, b) {
|
|
|
7979
8049
|
return true;
|
|
7980
8050
|
}
|
|
7981
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
|
+
/**
|
|
7982
8059
|
* Hook that resolves view and admin view props (which may be async builders or arrays)
|
|
7983
8060
|
* into concrete AppView[].
|
|
7984
8061
|
*
|
|
@@ -7987,16 +8064,7 @@ function viewSlugsEqual(a, b) {
|
|
|
7987
8064
|
*/
|
|
7988
8065
|
function useResolvedViews(props) {
|
|
7989
8066
|
const { authController, views: viewsProp, adminViews: adminViewsProp, data, plugins, adminMode = "content", effectiveRoleController } = props;
|
|
7990
|
-
const
|
|
7991
|
-
const [error, setError] = useState(void 0);
|
|
7992
|
-
const [resolvedViews, setResolvedViews] = useState(void 0);
|
|
7993
|
-
const [resolvedAdminViews, setResolvedAdminViews] = useState(void 0);
|
|
7994
|
-
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
7995
|
-
const refresh = useCallback(() => {
|
|
7996
|
-
setRefreshTrigger((prev) => prev + 1);
|
|
7997
|
-
}, []);
|
|
7998
|
-
const viewsRef = useRef(void 0);
|
|
7999
|
-
const adminViewsRef = useRef(void 0);
|
|
8067
|
+
const userIdentity = authController.user ? `${authController.user.uid}:${(authController.user.roles ?? []).sort().join(",")}` : null;
|
|
8000
8068
|
const dataRef = useRef(data);
|
|
8001
8069
|
dataRef.current = data;
|
|
8002
8070
|
const authControllerRef = useRef(authController);
|
|
@@ -8020,51 +8088,35 @@ function useResolvedViews(props) {
|
|
|
8020
8088
|
const resolvedAuthControllerRef = useRef(resolvedAuthController);
|
|
8021
8089
|
resolvedAuthControllerRef.current = resolvedAuthController;
|
|
8022
8090
|
const initialLoading = resolvedAuthController.initialLoading;
|
|
8023
|
-
const
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
} finally {
|
|
8045
|
-
if (!cancelled) setLoading(false);
|
|
8046
|
-
}
|
|
8047
|
-
})();
|
|
8048
|
-
return () => {
|
|
8049
|
-
cancelled = true;
|
|
8050
|
-
};
|
|
8051
|
-
}, [
|
|
8052
|
-
viewsProp,
|
|
8053
|
-
adminViewsProp,
|
|
8054
|
-
refreshTrigger,
|
|
8055
|
-
adminMode,
|
|
8056
|
-
initialLoading,
|
|
8057
|
-
user
|
|
8058
|
-
]);
|
|
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
|
+
});
|
|
8059
8112
|
return useMemo(() => ({
|
|
8060
|
-
views:
|
|
8061
|
-
adminViews:
|
|
8113
|
+
views: resolvedData.views,
|
|
8114
|
+
adminViews: resolvedData.adminViews,
|
|
8062
8115
|
loading,
|
|
8063
8116
|
error,
|
|
8064
8117
|
refresh
|
|
8065
8118
|
}), [
|
|
8066
|
-
|
|
8067
|
-
resolvedAdminViews,
|
|
8119
|
+
resolvedData,
|
|
8068
8120
|
loading,
|
|
8069
8121
|
error,
|
|
8070
8122
|
refresh
|
|
@@ -8173,21 +8225,24 @@ function useTopLevelNavigation(props) {
|
|
|
8173
8225
|
}, [])
|
|
8174
8226
|
];
|
|
8175
8227
|
const groupOrderValue = (groupName) => {
|
|
8176
|
-
if (groupName === "Admin") return 1;
|
|
8228
|
+
if (groupName === "Admin" || groupName === "Settings" || groupName === "Admin") return 1;
|
|
8177
8229
|
return 0;
|
|
8178
8230
|
};
|
|
8179
|
-
navigationEntries = navigationEntries.sort((a, b) => {
|
|
8180
|
-
return groupOrderValue(a.group) - groupOrderValue(b.group);
|
|
8181
|
-
});
|
|
8182
8231
|
const usedViewsOrder = viewsOrder ?? navigationEntriesOrder;
|
|
8183
|
-
|
|
8184
|
-
const
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
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;
|
|
8191
8246
|
});
|
|
8192
8247
|
const collectedGroupsFromEntries = navigationEntries.map((e) => e.group).filter(Boolean);
|
|
8193
8248
|
const groupsFromMappings = finalNavigationGroupMappings.map((g) => g.name);
|
|
@@ -8198,8 +8253,8 @@ function useTopLevelNavigation(props) {
|
|
|
8198
8253
|
...additionalGroups
|
|
8199
8254
|
];
|
|
8200
8255
|
const uniqueGroupsArray = [...new Set(allDefinedGroups)];
|
|
8201
|
-
const
|
|
8202
|
-
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];
|
|
8203
8258
|
const computedTopLevelNav = {
|
|
8204
8259
|
allowDragAndDrop: plugins?.some((plugin) => plugin.hooks?.allowDragAndDrop) ?? false,
|
|
8205
8260
|
navigationEntries,
|
|
@@ -8266,9 +8321,15 @@ function useBuildNavigationStateController(props) {
|
|
|
8266
8321
|
adminMode,
|
|
8267
8322
|
collectionRegistryController
|
|
8268
8323
|
});
|
|
8324
|
+
const pendingRefreshRef = useRef(false);
|
|
8269
8325
|
const refreshNavigation = useCallback(() => {
|
|
8270
|
-
|
|
8271
|
-
|
|
8326
|
+
if (pendingRefreshRef.current) return;
|
|
8327
|
+
pendingRefreshRef.current = true;
|
|
8328
|
+
queueMicrotask(() => {
|
|
8329
|
+
pendingRefreshRef.current = false;
|
|
8330
|
+
refreshCollections();
|
|
8331
|
+
refreshViews();
|
|
8332
|
+
});
|
|
8272
8333
|
}, [refreshCollections, refreshViews]);
|
|
8273
8334
|
return useMemo(() => ({
|
|
8274
8335
|
views,
|
|
@@ -8276,8 +8337,7 @@ function useBuildNavigationStateController(props) {
|
|
|
8276
8337
|
topLevelNavigation,
|
|
8277
8338
|
loading: collectionsLoading || viewsLoading,
|
|
8278
8339
|
navigationLoadingError: collectionsError ?? viewsError,
|
|
8279
|
-
refreshNavigation
|
|
8280
|
-
plugins
|
|
8340
|
+
refreshNavigation
|
|
8281
8341
|
}), [
|
|
8282
8342
|
views,
|
|
8283
8343
|
adminViews,
|
|
@@ -8286,8 +8346,7 @@ function useBuildNavigationStateController(props) {
|
|
|
8286
8346
|
viewsLoading,
|
|
8287
8347
|
collectionsError,
|
|
8288
8348
|
viewsError,
|
|
8289
|
-
refreshNavigation
|
|
8290
|
-
plugins
|
|
8349
|
+
refreshNavigation
|
|
8291
8350
|
]);
|
|
8292
8351
|
}
|
|
8293
8352
|
//#endregion
|
|
@@ -8349,14 +8408,18 @@ var BreadcrumbContext = React.createContext({
|
|
|
8349
8408
|
var BreadcrumbsProvider = ({ children }) => {
|
|
8350
8409
|
const [breadcrumbs, setBreadcrumbs] = useState([]);
|
|
8351
8410
|
const set = useCallback((props) => {
|
|
8352
|
-
setBreadcrumbs((prev) =>
|
|
8353
|
-
const
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
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
|
+
});
|
|
8360
8423
|
}, []);
|
|
8361
8424
|
const updateCount = useCallback((id, count) => {
|
|
8362
8425
|
setBreadcrumbs((prev) => prev.map((entry) => entry.id === id ? {
|
|
@@ -8499,11 +8562,7 @@ function SideDialogView({ offsetPosition, panel, panelIndex, isTopPanel }) {
|
|
|
8499
8562
|
overlayStyle: { zIndex: 40 + panelIndex * 10 },
|
|
8500
8563
|
style: { zIndex: 45 + panelIndex * 10 },
|
|
8501
8564
|
onOpenChange: (open) => {
|
|
8502
|
-
if (!open)
|
|
8503
|
-
const suggestionMenu = document.querySelector("[data-suggestion-menu=\"true\"]");
|
|
8504
|
-
if (suggestionMenu && window.getComputedStyle(suggestionMenu).visibility !== "hidden") return;
|
|
8505
|
-
onCloseRequest();
|
|
8506
|
-
}
|
|
8565
|
+
if (!open) onCloseRequest();
|
|
8507
8566
|
},
|
|
8508
8567
|
onPointerDownOutside: !isTopPanel ? preventDismiss : void 0,
|
|
8509
8568
|
onInteractOutside: !isTopPanel ? preventDismiss : void 0,
|
|
@@ -8966,7 +9025,7 @@ function FormEntry({ propertyKey, widthPercentage = 100, children }) {
|
|
|
8966
9025
|
//#region src/form/components/FormLayout.tsx
|
|
8967
9026
|
function FormLayout({ children, className }) {
|
|
8968
9027
|
return /* @__PURE__ */ jsx("div", {
|
|
8969
|
-
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),
|
|
8970
9029
|
children
|
|
8971
9030
|
});
|
|
8972
9031
|
}
|
|
@@ -8991,7 +9050,7 @@ function ReadOnlyFieldBinding({ propertyKey, value, error, showError, minimalist
|
|
|
8991
9050
|
className: "h-8 text-text-secondary dark:text-text-secondary-dark ml-3.5"
|
|
8992
9051
|
}),
|
|
8993
9052
|
/* @__PURE__ */ jsx("div", {
|
|
8994
|
-
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"),
|
|
8995
9054
|
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(PropertyPreview, {
|
|
8996
9055
|
propertyKey,
|
|
8997
9056
|
value,
|
|
@@ -9278,7 +9337,7 @@ var buildIdsMap = (value) => value && Array.isArray(value) && value.length > 0 ?
|
|
|
9278
9337
|
...a,
|
|
9279
9338
|
...b
|
|
9280
9339
|
}), {}) : {};
|
|
9281
|
-
function SortableItem
|
|
9340
|
+
function SortableItem({ id, index, size, disabled, buildEntry, remove, copy, addInIndex, canAddElements, sortable, storedProps, updateItemCustomProps }) {
|
|
9282
9341
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
|
|
9283
9342
|
return /* @__PURE__ */ jsx(ArrayContainerItem, {
|
|
9284
9343
|
nodeRef: setNodeRef,
|
|
@@ -9452,12 +9511,12 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
|
|
|
9452
9511
|
const newIds = [...internalIds];
|
|
9453
9512
|
newIds.splice(index, 1);
|
|
9454
9513
|
setInternalIds(newIds);
|
|
9455
|
-
onValueChange(value.filter((_, i) => i !== index));
|
|
9514
|
+
onValueChange((value ?? []).filter((_, i) => i !== index));
|
|
9456
9515
|
};
|
|
9457
9516
|
const copy = (index) => {
|
|
9458
9517
|
if ((value ?? []).length >= max) return;
|
|
9459
9518
|
const id = getRandomId$1();
|
|
9460
|
-
const copyingItem = value[index];
|
|
9519
|
+
const copyingItem = (value ?? [])[index];
|
|
9461
9520
|
const newIds = [
|
|
9462
9521
|
...internalIds.slice(0, index + 1),
|
|
9463
9522
|
id,
|
|
@@ -9466,9 +9525,9 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
|
|
|
9466
9525
|
if (onInternalIdAdded) onInternalIdAdded(id);
|
|
9467
9526
|
setInternalIds(newIds);
|
|
9468
9527
|
onValueChange([
|
|
9469
|
-
...value.slice(0, index + 1),
|
|
9528
|
+
...(value ?? []).slice(0, index + 1),
|
|
9470
9529
|
copyingItem,
|
|
9471
|
-
...value.slice(index + 1)
|
|
9530
|
+
...(value ?? []).slice(index + 1)
|
|
9472
9531
|
]);
|
|
9473
9532
|
};
|
|
9474
9533
|
const addInIndex = (index) => {
|
|
@@ -9482,9 +9541,9 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
|
|
|
9482
9541
|
if (onInternalIdAdded) onInternalIdAdded(id);
|
|
9483
9542
|
setInternalIds(newIds);
|
|
9484
9543
|
onValueChange([
|
|
9485
|
-
...value.slice(0, index),
|
|
9544
|
+
...(value ?? []).slice(0, index),
|
|
9486
9545
|
newDefaultEntry,
|
|
9487
|
-
...value.slice(index)
|
|
9546
|
+
...(value ?? []).slice(index)
|
|
9488
9547
|
]);
|
|
9489
9548
|
};
|
|
9490
9549
|
const onDragEnd = (event) => {
|
|
@@ -9494,7 +9553,7 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
|
|
|
9494
9553
|
const newIndex = internalIds.indexOf(over.id);
|
|
9495
9554
|
if (oldIndex === -1 || newIndex === -1) return;
|
|
9496
9555
|
setInternalIds(arrayMove$1(internalIds, oldIndex, newIndex));
|
|
9497
|
-
onValueChange(arrayMove$1(value, oldIndex, newIndex));
|
|
9556
|
+
onValueChange(arrayMove$1(value ?? [], oldIndex, newIndex));
|
|
9498
9557
|
};
|
|
9499
9558
|
return sortable ? /* @__PURE__ */ jsx(DndContext, {
|
|
9500
9559
|
sensors,
|
|
@@ -9507,7 +9566,7 @@ function ArrayContainer({ droppableId, addLabel, value, disabled = false, buildE
|
|
|
9507
9566
|
children: /* @__PURE__ */ jsxs("div", {
|
|
9508
9567
|
className: cls("space-y-1", className),
|
|
9509
9568
|
id: droppableId,
|
|
9510
|
-
children: [hasValue && internalIds.map((internalId, index) => /* @__PURE__ */ jsx(SortableItem
|
|
9569
|
+
children: [hasValue && internalIds.map((internalId, index) => /* @__PURE__ */ jsx(SortableItem, {
|
|
9511
9570
|
id: internalId,
|
|
9512
9571
|
index,
|
|
9513
9572
|
size,
|
|
@@ -10391,7 +10450,7 @@ function MapFieldBinding({ propertyKey, value, showError, error, disabled, prope
|
|
|
10391
10450
|
}
|
|
10392
10451
|
//#endregion
|
|
10393
10452
|
//#region src/form/field_bindings/MarkdownEditorFieldBinding.tsx
|
|
10394
|
-
var
|
|
10453
|
+
var RichTextEditor = lazy(() => import("./editor.js").then((m) => ({ default: m.RichTextEditor })));
|
|
10395
10454
|
var loadMarkdownUtils = () => import("./markdown-CklalUUk.js").then((n) => n.t);
|
|
10396
10455
|
var _markdownUtils = null;
|
|
10397
10456
|
var getMarkdownUtils = async () => {
|
|
@@ -10506,7 +10565,7 @@ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, in
|
|
|
10506
10565
|
height: 200,
|
|
10507
10566
|
className: "w-full rounded-md"
|
|
10508
10567
|
}),
|
|
10509
|
-
children: /* @__PURE__ */ jsx(
|
|
10568
|
+
children: /* @__PURE__ */ jsx(RichTextEditor, {
|
|
10510
10569
|
content: value,
|
|
10511
10570
|
onMarkdownContentChange: onContentChange,
|
|
10512
10571
|
version: context.formex.version + fieldVersion,
|
|
@@ -11249,7 +11308,7 @@ function StorageUpload({ property, name, value, setInternalValue, onChange, mult
|
|
|
11249
11308
|
onDragStart: handleDragStart,
|
|
11250
11309
|
onDragEnd: handleDragEnd,
|
|
11251
11310
|
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
11252
|
-
items: value.map((v) => v.id),
|
|
11311
|
+
items: (value ?? []).map((v) => v.id),
|
|
11253
11312
|
strategy: horizontalListSortingStrategy,
|
|
11254
11313
|
children: /* @__PURE__ */ jsx(FileDropComponent, { ...fileDropProps })
|
|
11255
11314
|
})
|
|
@@ -13589,189 +13648,57 @@ function EntityCard({ entity, collection, onClick, selected, highlighted, onSele
|
|
|
13589
13648
|
//#endregion
|
|
13590
13649
|
//#region src/components/EntityCollectionView/EntityCollectionCardView.tsx
|
|
13591
13650
|
/**
|
|
13592
|
-
* Get grid column classes based on the size.
|
|
13593
|
-
* Smaller size = more columns (smaller cards)
|
|
13594
|
-
* Larger size = fewer columns (larger cards)
|
|
13595
|
-
*/
|
|
13596
|
-
function getGridColumnsClass(size) {
|
|
13597
|
-
switch (size) {
|
|
13598
|
-
case "xs": return "grid-cols-4 sm:grid-cols-5 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10";
|
|
13599
|
-
case "s": return "grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-8";
|
|
13600
|
-
case "m": return "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6";
|
|
13601
|
-
case "l": return "grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-4";
|
|
13602
|
-
case "xl": return "grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3";
|
|
13603
|
-
default: return "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6";
|
|
13604
|
-
}
|
|
13605
|
-
}
|
|
13606
|
-
function getScrollParent$1(element) {
|
|
13607
|
-
if (!element) return null;
|
|
13608
|
-
let parent = element.parentElement;
|
|
13609
|
-
while (parent) {
|
|
13610
|
-
const overflowY = window.getComputedStyle(parent).overflowY;
|
|
13611
|
-
if (overflowY === "auto" || overflowY === "scroll") return parent;
|
|
13612
|
-
parent = parent.parentElement;
|
|
13613
|
-
}
|
|
13614
|
-
return document.documentElement;
|
|
13615
|
-
}
|
|
13616
|
-
/**
|
|
13617
13651
|
* Card grid view for displaying entities with infinite scroll.
|
|
13618
13652
|
* Alternative to the EntityCollectionTable for visual browsing.
|
|
13619
13653
|
*/
|
|
13620
13654
|
function EntityCollectionCardView({ collection, tableController, onEntityClick, selectionController, selectionEnabled = true, highlightedEntities, emptyComponent, onScroll, initialScroll, size = "m" }) {
|
|
13621
13655
|
const ResolvedEntityCard = useComponentOverride("Collection.Card", EntityCard);
|
|
13622
|
-
const containerRef = useRef(null);
|
|
13623
|
-
const hasRestoredScroll = useRef(false);
|
|
13624
13656
|
const { data, dataLoading, noMoreToLoad, dataLoadingError, itemCount, setItemCount, pageSize = 50, paginationEnabled } = tableController;
|
|
13625
|
-
const isLoadingMore = useRef(false);
|
|
13626
|
-
const paginationStateRef = useRef({
|
|
13627
|
-
paginationEnabled,
|
|
13628
|
-
noMoreToLoad,
|
|
13629
|
-
dataLoading,
|
|
13630
|
-
itemCount,
|
|
13631
|
-
pageSize
|
|
13632
|
-
});
|
|
13633
|
-
useEffect(() => {
|
|
13634
|
-
paginationStateRef.current = {
|
|
13635
|
-
paginationEnabled,
|
|
13636
|
-
noMoreToLoad,
|
|
13637
|
-
dataLoading,
|
|
13638
|
-
itemCount,
|
|
13639
|
-
pageSize
|
|
13640
|
-
};
|
|
13641
|
-
}, [
|
|
13642
|
-
paginationEnabled,
|
|
13643
|
-
noMoreToLoad,
|
|
13644
|
-
dataLoading,
|
|
13645
|
-
itemCount,
|
|
13646
|
-
pageSize
|
|
13647
|
-
]);
|
|
13648
|
-
useEffect(() => {
|
|
13649
|
-
if (!dataLoading) isLoadingMore.current = false;
|
|
13650
|
-
}, [dataLoading]);
|
|
13651
|
-
useEffect(() => {
|
|
13652
|
-
const el = containerRef.current;
|
|
13653
|
-
if (!el) return;
|
|
13654
|
-
const scrollEl = getScrollParent$1(el);
|
|
13655
|
-
if (!scrollEl) return;
|
|
13656
|
-
let rafId = null;
|
|
13657
|
-
const update = () => {
|
|
13658
|
-
rafId = null;
|
|
13659
|
-
const { paginationEnabled: pe, noMoreToLoad: nm, itemCount: ic, pageSize: ps } = paginationStateRef.current;
|
|
13660
|
-
if (pe && !nm && !isLoadingMore.current && scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < 400) {
|
|
13661
|
-
isLoadingMore.current = true;
|
|
13662
|
-
setItemCount?.((ic ?? ps) + ps);
|
|
13663
|
-
}
|
|
13664
|
-
};
|
|
13665
|
-
const onScrollEvent = () => {
|
|
13666
|
-
if (rafId === null) rafId = requestAnimationFrame(update);
|
|
13667
|
-
};
|
|
13668
|
-
scrollEl.addEventListener("scroll", onScrollEvent, { passive: true });
|
|
13669
|
-
const ro = new ResizeObserver(() => update());
|
|
13670
|
-
ro.observe(scrollEl);
|
|
13671
|
-
update();
|
|
13672
|
-
return () => {
|
|
13673
|
-
scrollEl.removeEventListener("scroll", onScrollEvent);
|
|
13674
|
-
ro.disconnect();
|
|
13675
|
-
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
13676
|
-
};
|
|
13677
|
-
}, [setItemCount]);
|
|
13678
|
-
useEffect(() => {
|
|
13679
|
-
if (!containerRef.current || !initialScroll || hasRestoredScroll.current || data.length === 0) return;
|
|
13680
|
-
const scrollEl = getScrollParent$1(containerRef.current);
|
|
13681
|
-
if (!scrollEl) return;
|
|
13682
|
-
let attempts = 0;
|
|
13683
|
-
const maxAttempts = 5;
|
|
13684
|
-
const tryRestore = () => {
|
|
13685
|
-
if (scrollEl.scrollHeight >= initialScroll || attempts >= maxAttempts) {
|
|
13686
|
-
scrollEl.scrollTop = initialScroll;
|
|
13687
|
-
hasRestoredScroll.current = true;
|
|
13688
|
-
} else {
|
|
13689
|
-
attempts++;
|
|
13690
|
-
requestAnimationFrame(tryRestore);
|
|
13691
|
-
}
|
|
13692
|
-
};
|
|
13693
|
-
requestAnimationFrame(tryRestore);
|
|
13694
|
-
}, [initialScroll, data.length]);
|
|
13695
|
-
const lastScrollOffset = useRef(0);
|
|
13696
|
-
useEffect(() => {
|
|
13697
|
-
const el = containerRef.current;
|
|
13698
|
-
if (!el || !onScroll) return;
|
|
13699
|
-
const scrollEl = getScrollParent$1(el);
|
|
13700
|
-
if (!scrollEl) return;
|
|
13701
|
-
const handleScroll = () => {
|
|
13702
|
-
const currentOffset = scrollEl.scrollTop;
|
|
13703
|
-
const direction = currentOffset > lastScrollOffset.current ? "forward" : "backward";
|
|
13704
|
-
lastScrollOffset.current = currentOffset;
|
|
13705
|
-
onScroll({
|
|
13706
|
-
scrollDirection: direction,
|
|
13707
|
-
scrollOffset: currentOffset,
|
|
13708
|
-
scrollUpdateWasRequested: false
|
|
13709
|
-
});
|
|
13710
|
-
};
|
|
13711
|
-
scrollEl.addEventListener("scroll", handleScroll, { passive: true });
|
|
13712
|
-
return () => scrollEl.removeEventListener("scroll", handleScroll);
|
|
13713
|
-
}, [onScroll]);
|
|
13714
13657
|
const handleEntityClick = useCallback((entity) => {
|
|
13715
13658
|
onEntityClick?.(entity);
|
|
13716
13659
|
}, [onEntityClick]);
|
|
13717
13660
|
const handleSelectionChange = useCallback((entity, selected) => {
|
|
13718
13661
|
selectionController?.toggleEntitySelection(entity, selected);
|
|
13719
13662
|
}, [selectionController]);
|
|
13720
|
-
const
|
|
13721
|
-
|
|
13722
|
-
|
|
13723
|
-
|
|
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
|
-
selectionEnabled,
|
|
13760
|
-
size
|
|
13761
|
-
}, `${entity.path}_${entity.id}`))
|
|
13762
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
13763
|
-
className: "flex items-center justify-center py-8",
|
|
13764
|
-
children: [dataLoading && /* @__PURE__ */ jsx(CircularProgress, { size: "small" }), !dataLoading && noMoreToLoad && data.length > 0 && /* @__PURE__ */ jsxs(Typography, {
|
|
13765
|
-
variant: "caption",
|
|
13766
|
-
color: "secondary",
|
|
13767
|
-
children: [
|
|
13768
|
-
"All ",
|
|
13769
|
-
data.length,
|
|
13770
|
-
" entries loaded"
|
|
13771
|
-
]
|
|
13772
|
-
})]
|
|
13773
|
-
})]
|
|
13774
|
-
}) })
|
|
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
|
+
])
|
|
13775
13702
|
});
|
|
13776
13703
|
}
|
|
13777
13704
|
//#endregion
|
|
@@ -13789,38 +13716,9 @@ function getRowClasses(size) {
|
|
|
13789
13716
|
default: return "py-3 px-5";
|
|
13790
13717
|
}
|
|
13791
13718
|
}
|
|
13792
|
-
/**
|
|
13793
|
-
* Estimated row height in pixels for virtualization, based on size.
|
|
13794
|
-
*/
|
|
13795
|
-
function getEstimatedRowHeight(size) {
|
|
13796
|
-
switch (size) {
|
|
13797
|
-
case "xs": return 44;
|
|
13798
|
-
case "s": return 52;
|
|
13799
|
-
case "m": return 64;
|
|
13800
|
-
case "l": return 76;
|
|
13801
|
-
case "xl": return 88;
|
|
13802
|
-
default: return 64;
|
|
13803
|
-
}
|
|
13804
|
-
}
|
|
13805
|
-
/** Number of extra rows rendered above/below the viewport. */
|
|
13806
|
-
var OVERSCAN_COUNT = 8;
|
|
13807
|
-
/** Threshold in pixels from the bottom of the scroll area to trigger loading more. */
|
|
13808
|
-
var LOAD_MORE_THRESHOLD = 400;
|
|
13809
13719
|
/** Stable empty array for when no list-view actions are available. */
|
|
13810
13720
|
var EMPTY_LIST_VIEW_ACTIONS = [];
|
|
13811
13721
|
/**
|
|
13812
|
-
* Walk up the DOM from `element` to find the nearest scrollable ancestor.
|
|
13813
|
-
*/
|
|
13814
|
-
function getScrollParent(element) {
|
|
13815
|
-
let parent = element?.parentElement ?? null;
|
|
13816
|
-
while (parent) {
|
|
13817
|
-
const style = getComputedStyle(parent);
|
|
13818
|
-
if (style.overflowY === "auto" || style.overflowY === "scroll" || style.overflow === "auto" || style.overflow === "scroll") return parent;
|
|
13819
|
-
parent = parent.parentElement;
|
|
13820
|
-
}
|
|
13821
|
-
return document.documentElement;
|
|
13822
|
-
}
|
|
13823
|
-
/**
|
|
13824
13722
|
* Returns true if a property type should NOT be rendered via
|
|
13825
13723
|
* PropertyPreview in list row columns (because it would blow up height).
|
|
13826
13724
|
*/
|
|
@@ -14074,10 +13972,10 @@ function EntityCollectionListView({ collection, tableController, onEntityClick,
|
|
|
14074
13972
|
const handleSelectionChange = useCallback((entity, selected) => {
|
|
14075
13973
|
selectionController?.toggleEntitySelection(entity, selected);
|
|
14076
13974
|
}, [selectionController]);
|
|
14077
|
-
|
|
13975
|
+
useCallback((entity) => {
|
|
14078
13976
|
return selectionController?.isEntitySelected(entity) ?? false;
|
|
14079
13977
|
}, [selectionController]);
|
|
14080
|
-
|
|
13978
|
+
useCallback((entity) => {
|
|
14081
13979
|
return highlightedEntities?.some((e) => e.id === entity.id && e.path === entity.path) ?? false;
|
|
14082
13980
|
}, [highlightedEntities]);
|
|
14083
13981
|
const getListViewActions = useCallback((entity) => {
|
|
@@ -14092,158 +13990,70 @@ function EntityCollectionListView({ collection, tableController, onEntityClick,
|
|
|
14092
13990
|
customizationController.entityActions
|
|
14093
13991
|
]);
|
|
14094
13992
|
const rowClasses = getRowClasses(size);
|
|
14095
|
-
const
|
|
14096
|
-
const
|
|
14097
|
-
const
|
|
14098
|
-
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
|
|
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,
|
|
14102
14001
|
noMoreToLoad,
|
|
14002
|
+
dataLoadingError,
|
|
14103
14003
|
itemCount,
|
|
14104
|
-
|
|
14105
|
-
|
|
14106
|
-
useEffect(() => {
|
|
14107
|
-
paginationStateRef.current = {
|
|
14108
|
-
paginationEnabled,
|
|
14109
|
-
noMoreToLoad,
|
|
14110
|
-
itemCount,
|
|
14111
|
-
pageSize
|
|
14112
|
-
};
|
|
14113
|
-
}, [
|
|
14004
|
+
setItemCount,
|
|
14005
|
+
pageSize,
|
|
14114
14006
|
paginationEnabled,
|
|
14115
|
-
|
|
14116
|
-
|
|
14117
|
-
|
|
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
|
-
ro.disconnect();
|
|
14147
|
-
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
14148
|
-
};
|
|
14149
|
-
}, [setItemCount]);
|
|
14150
|
-
const totalHeight = data.length * estimatedRowHeight;
|
|
14151
|
-
const startIndex = Math.max(0, Math.floor(effectiveScrollTop / estimatedRowHeight) - OVERSCAN_COUNT);
|
|
14152
|
-
const endIndex = Math.min(data.length, Math.ceil((effectiveScrollTop + viewportHeight) / estimatedRowHeight) + OVERSCAN_COUNT);
|
|
14153
|
-
const visibleData = data.slice(startIndex, endIndex);
|
|
14154
|
-
const offsetY = startIndex * estimatedRowHeight;
|
|
14155
|
-
const footerHeight = dataLoading ? 48 : !dataLoading && noMoreToLoad && data.length > 0 ? 32 : 0;
|
|
14156
|
-
return /* @__PURE__ */ jsx("div", {
|
|
14157
|
-
ref: containerRef,
|
|
14158
|
-
className: cls("w-full", selectedEntityId === void 0 && "rounded-lg overflow-hidden border " + defaultBorderMixin),
|
|
14159
|
-
children: dataLoadingError && data.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
14160
|
-
className: "flex items-center justify-center p-8",
|
|
14161
|
-
children: /* @__PURE__ */ jsxs(Typography, {
|
|
14162
|
-
className: "text-red-500",
|
|
14163
|
-
children: ["Error loading data: ", dataLoadingError.message]
|
|
14164
|
-
})
|
|
14165
|
-
}) : isInitialLoading ? /* @__PURE__ */ jsx("div", {
|
|
14166
|
-
className: "flex items-center justify-center py-12 px-8",
|
|
14167
|
-
children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
|
|
14168
|
-
}) : isEmpty ? /* @__PURE__ */ jsx("div", {
|
|
14169
|
-
className: "w-full flex items-center justify-center py-12 px-8",
|
|
14170
|
-
children: emptyComponent ?? /* @__PURE__ */ jsx(Typography, {
|
|
14171
|
-
variant: "label",
|
|
14172
|
-
color: "secondary",
|
|
14173
|
-
children: "No entries found"
|
|
14174
|
-
})
|
|
14175
|
-
}) : /* @__PURE__ */ jsxs("div", {
|
|
14176
|
-
style: {
|
|
14177
|
-
height: totalHeight + footerHeight,
|
|
14178
|
-
position: "relative"
|
|
14179
|
-
},
|
|
14180
|
-
children: [
|
|
14181
|
-
/* @__PURE__ */ jsx("div", {
|
|
14182
|
-
style: {
|
|
14183
|
-
position: "absolute",
|
|
14184
|
-
top: offsetY,
|
|
14185
|
-
left: 0,
|
|
14186
|
-
right: 0
|
|
14187
|
-
},
|
|
14188
|
-
children: visibleData.map((entity, i) => {
|
|
14189
|
-
const isLast = startIndex + i === data.length - 1;
|
|
14190
|
-
return /* @__PURE__ */ jsx("div", {
|
|
14191
|
-
style: { height: estimatedRowHeight },
|
|
14192
|
-
className: cls(!isLast && "border-b", !isLast && defaultBorderMixin),
|
|
14193
|
-
children: /* @__PURE__ */ jsx(ListRow, {
|
|
14194
|
-
entity,
|
|
14195
|
-
collection: resolvedCollection,
|
|
14196
|
-
onClick: handleEntityClick,
|
|
14197
|
-
selected: isEntitySelected(entity),
|
|
14198
|
-
highlighted: isEntityHighlighted(entity),
|
|
14199
|
-
onSelectionChange: handleSelectionChange,
|
|
14200
|
-
selectionEnabled,
|
|
14201
|
-
columns: visibleColumns,
|
|
14202
|
-
slotKeys,
|
|
14203
|
-
rowClasses,
|
|
14204
|
-
showImage,
|
|
14205
|
-
size,
|
|
14206
|
-
isLast,
|
|
14207
|
-
isActive: selectedEntityId !== void 0 && entity.id === selectedEntityId,
|
|
14208
|
-
listViewActions: getListViewActions(entity),
|
|
14209
|
-
context,
|
|
14210
|
-
path,
|
|
14211
|
-
selectionController,
|
|
14212
|
-
openEntityMode
|
|
14213
|
-
})
|
|
14214
|
-
}, entity.id);
|
|
14215
|
-
})
|
|
14216
|
-
}),
|
|
14217
|
-
dataLoading && /* @__PURE__ */ jsx("div", {
|
|
14218
|
-
className: "flex items-center justify-center py-3",
|
|
14219
|
-
style: {
|
|
14220
|
-
position: "absolute",
|
|
14221
|
-
top: totalHeight,
|
|
14222
|
-
left: 0,
|
|
14223
|
-
right: 0
|
|
14224
|
-
},
|
|
14225
|
-
children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
|
|
14226
|
-
}),
|
|
14227
|
-
!dataLoading && noMoreToLoad && data.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
14228
|
-
className: "flex items-center justify-center py-2 dark:bg-surface-900",
|
|
14229
|
-
style: {
|
|
14230
|
-
position: "absolute",
|
|
14231
|
-
top: totalHeight,
|
|
14232
|
-
left: 0,
|
|
14233
|
-
right: 0
|
|
14234
|
-
},
|
|
14235
|
-
children: /* @__PURE__ */ jsxs(Typography, {
|
|
14236
|
-
variant: "caption",
|
|
14237
|
-
color: "secondary",
|
|
14238
|
-
children: [
|
|
14239
|
-
"All ",
|
|
14240
|
-
data.length,
|
|
14241
|
-
" entries loaded"
|
|
14242
|
-
]
|
|
14243
|
-
})
|
|
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
|
|
14244
14038
|
})
|
|
14245
|
-
|
|
14246
|
-
}
|
|
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
|
+
])
|
|
14247
14057
|
});
|
|
14248
14058
|
}
|
|
14249
14059
|
/**
|
|
@@ -14679,457 +14489,6 @@ function SplitListView({ collection, tableController, onEntityClick: externalOnE
|
|
|
14679
14489
|
});
|
|
14680
14490
|
}
|
|
14681
14491
|
//#endregion
|
|
14682
|
-
//#region src/components/EntityCollectionView/BoardSortableList.tsx
|
|
14683
|
-
function BoardSortableList({ columnId, items, ItemComponent, isDragging, isDragOverColumn, loading = false, hasMore = false, onLoadMore }) {
|
|
14684
|
-
const { setNodeRef } = useDroppable({
|
|
14685
|
-
id: columnId,
|
|
14686
|
-
data: { type: "ITEM-LIST" }
|
|
14687
|
-
});
|
|
14688
|
-
const sentinelRef = useRef(null);
|
|
14689
|
-
const isLoadingRef = useRef(false);
|
|
14690
|
-
isLoadingRef.current = loading;
|
|
14691
|
-
const lastLoadTimeRef = useRef(0);
|
|
14692
|
-
useEffect(() => {
|
|
14693
|
-
if (!sentinelRef.current || !hasMore || !onLoadMore) return;
|
|
14694
|
-
const sentinel = sentinelRef.current;
|
|
14695
|
-
const observer = new IntersectionObserver((entries) => {
|
|
14696
|
-
const now = Date.now();
|
|
14697
|
-
if (entries[0].isIntersecting && hasMore && !isLoadingRef.current && now - lastLoadTimeRef.current > 500) {
|
|
14698
|
-
lastLoadTimeRef.current = now;
|
|
14699
|
-
onLoadMore();
|
|
14700
|
-
}
|
|
14701
|
-
}, { threshold: .1 });
|
|
14702
|
-
observer.observe(sentinel);
|
|
14703
|
-
const rect = sentinel.getBoundingClientRect();
|
|
14704
|
-
const containerRect = sentinel.parentElement?.getBoundingClientRect();
|
|
14705
|
-
if (containerRect && rect.top < containerRect.bottom && rect.bottom > containerRect.top) {
|
|
14706
|
-
const now = Date.now();
|
|
14707
|
-
if (hasMore && !isLoadingRef.current && now - lastLoadTimeRef.current > 500) {
|
|
14708
|
-
lastLoadTimeRef.current = now;
|
|
14709
|
-
onLoadMore();
|
|
14710
|
-
}
|
|
14711
|
-
}
|
|
14712
|
-
return () => observer.disconnect();
|
|
14713
|
-
}, [hasMore, onLoadMore]);
|
|
14714
|
-
return /* @__PURE__ */ jsx("div", {
|
|
14715
|
-
ref: setNodeRef,
|
|
14716
|
-
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]),
|
|
14717
|
-
style: { minHeight: 80 },
|
|
14718
|
-
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
14719
|
-
items: items.map((i) => i.id),
|
|
14720
|
-
strategy: verticalListSortingStrategy,
|
|
14721
|
-
children: items.length === 0 && !loading ? /* @__PURE__ */ jsx("div", {
|
|
14722
|
-
className: "flex-1 flex items-center justify-center",
|
|
14723
|
-
children: /* @__PURE__ */ jsx("span", {
|
|
14724
|
-
className: "text-xs text-surface-400 dark:text-surface-500",
|
|
14725
|
-
children: "No items"
|
|
14726
|
-
})
|
|
14727
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [items.map((item, index) => /* @__PURE__ */ jsx(SortableItem, {
|
|
14728
|
-
item,
|
|
14729
|
-
index,
|
|
14730
|
-
columnId,
|
|
14731
|
-
ItemComponent
|
|
14732
|
-
}, item.id)), (loading || hasMore) && /* @__PURE__ */ jsx("div", {
|
|
14733
|
-
ref: sentinelRef,
|
|
14734
|
-
className: "flex items-center justify-center py-2 min-h-6",
|
|
14735
|
-
children: loading && /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" })
|
|
14736
|
-
})] })
|
|
14737
|
-
})
|
|
14738
|
-
});
|
|
14739
|
-
}
|
|
14740
|
-
var SortableItem = memo(function SortableItem({ item, index, columnId, ItemComponent }) {
|
|
14741
|
-
const { setNodeRef, attributes, listeners, isDragging: isItemBeingDragged, transform, transition } = useSortable({
|
|
14742
|
-
id: item.id,
|
|
14743
|
-
data: {
|
|
14744
|
-
type: "ITEM",
|
|
14745
|
-
columnId
|
|
14746
|
-
}
|
|
14747
|
-
});
|
|
14748
|
-
return /* @__PURE__ */ jsx("div", {
|
|
14749
|
-
ref: setNodeRef,
|
|
14750
|
-
style: useMemo(() => ({
|
|
14751
|
-
transform: CSS.Transform.toString(transform),
|
|
14752
|
-
transition,
|
|
14753
|
-
zIndex: isItemBeingDragged ? 2 : 1,
|
|
14754
|
-
opacity: isItemBeingDragged ? 0 : 1
|
|
14755
|
-
}), [
|
|
14756
|
-
transform,
|
|
14757
|
-
transition,
|
|
14758
|
-
isItemBeingDragged
|
|
14759
|
-
]),
|
|
14760
|
-
...attributes,
|
|
14761
|
-
...listeners,
|
|
14762
|
-
children: /* @__PURE__ */ jsx(ItemComponent, {
|
|
14763
|
-
item,
|
|
14764
|
-
isDragging: isItemBeingDragged,
|
|
14765
|
-
index
|
|
14766
|
-
})
|
|
14767
|
-
});
|
|
14768
|
-
});
|
|
14769
|
-
//#endregion
|
|
14770
|
-
//#region src/components/EntityCollectionView/BoardColumnTitle.tsx
|
|
14771
|
-
function BoardColumnTitle({ children, className, color, ...props }) {
|
|
14772
|
-
const colorScheme = useMemo(() => {
|
|
14773
|
-
if (!color) return void 0;
|
|
14774
|
-
if (typeof color === "string") return getColorSchemeForKey(color);
|
|
14775
|
-
return color;
|
|
14776
|
-
}, [color]);
|
|
14777
|
-
return /* @__PURE__ */ jsxs(Typography, {
|
|
14778
|
-
variant: "subtitle2",
|
|
14779
|
-
component: "h4",
|
|
14780
|
-
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),
|
|
14781
|
-
...props,
|
|
14782
|
-
children: [colorScheme && /* @__PURE__ */ jsx("div", {
|
|
14783
|
-
className: "w-3 h-3 rounded-full flex-shrink-0",
|
|
14784
|
-
style: { backgroundColor: colorScheme.darkColor ?? colorScheme.color }
|
|
14785
|
-
}), children]
|
|
14786
|
-
});
|
|
14787
|
-
}
|
|
14788
|
-
//#endregion
|
|
14789
|
-
//#region src/components/EntityCollectionView/BoardColumn.tsx
|
|
14790
|
-
var BoardColumn = memo(function BoardColumn({ id, title, items, ItemComponent, isDragging, isDragOverColumn, allowReorder = false, loading = false, hasMore = false, onLoadMore, onAddItem, totalCount, color, style }) {
|
|
14791
|
-
const { setNodeRef, attributes, listeners, isDragging: isColumnBeingDragged, transform, transition } = useSortable({
|
|
14792
|
-
id,
|
|
14793
|
-
data: { type: "COLUMN" },
|
|
14794
|
-
disabled: !allowReorder
|
|
14795
|
-
});
|
|
14796
|
-
const combinedStyle = useMemo(() => ({
|
|
14797
|
-
...style,
|
|
14798
|
-
transform: CSS.Translate.toString(transform),
|
|
14799
|
-
transition,
|
|
14800
|
-
zIndex: isColumnBeingDragged ? 2 : 1
|
|
14801
|
-
}), [
|
|
14802
|
-
style,
|
|
14803
|
-
transform,
|
|
14804
|
-
transition,
|
|
14805
|
-
isColumnBeingDragged
|
|
14806
|
-
]);
|
|
14807
|
-
const dragListeners = allowReorder ? listeners : {};
|
|
14808
|
-
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]);
|
|
14809
|
-
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]);
|
|
14810
|
-
const itemIds = useMemo(() => items.map((i) => i.id), [items]);
|
|
14811
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
14812
|
-
ref: setNodeRef,
|
|
14813
|
-
style: combinedStyle,
|
|
14814
|
-
...attributes,
|
|
14815
|
-
className: columnClassName,
|
|
14816
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
14817
|
-
...dragListeners,
|
|
14818
|
-
className: headerClassName,
|
|
14819
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
14820
|
-
className: "flex items-center gap-2",
|
|
14821
|
-
children: [/* @__PURE__ */ jsx(BoardColumnTitle, {
|
|
14822
|
-
"aria-label": `${title} item list`,
|
|
14823
|
-
color,
|
|
14824
|
-
children: title
|
|
14825
|
-
}), totalCount !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
14826
|
-
className: "text-xs text-surface-500 dark:text-surface-400",
|
|
14827
|
-
children: totalCount
|
|
14828
|
-
})]
|
|
14829
|
-
}), onAddItem && /* @__PURE__ */ jsx(IconButton, {
|
|
14830
|
-
size: "small",
|
|
14831
|
-
onClick: (e) => {
|
|
14832
|
-
e.stopPropagation();
|
|
14833
|
-
onAddItem();
|
|
14834
|
-
},
|
|
14835
|
-
className: "opacity-60 hover:opacity-100",
|
|
14836
|
-
children: /* @__PURE__ */ jsx(PlusIcon, { size: iconSize.small })
|
|
14837
|
-
})]
|
|
14838
|
-
}), /* @__PURE__ */ jsx(SortableContext, {
|
|
14839
|
-
items: itemIds,
|
|
14840
|
-
strategy: verticalListSortingStrategy,
|
|
14841
|
-
children: /* @__PURE__ */ jsx(BoardSortableList, {
|
|
14842
|
-
columnId: id,
|
|
14843
|
-
items,
|
|
14844
|
-
ItemComponent,
|
|
14845
|
-
isDragging,
|
|
14846
|
-
isDragOverColumn,
|
|
14847
|
-
loading,
|
|
14848
|
-
hasMore,
|
|
14849
|
-
onLoadMore
|
|
14850
|
-
})
|
|
14851
|
-
})]
|
|
14852
|
-
});
|
|
14853
|
-
});
|
|
14854
|
-
//#endregion
|
|
14855
|
-
//#region src/components/EntityCollectionView/Board.tsx
|
|
14856
|
-
function Board({ data, columns: columnsProp, columnLabels, columnColors, className, assignColumn, allowColumnReorder = false, onColumnReorder, onItemsReorder, ItemComponent, columnLoadingState, onLoadMoreColumn, onAddItemToColumn, AddColumnComponent }) {
|
|
14857
|
-
const [activeItem, setActiveItem] = useState(null);
|
|
14858
|
-
const [activeColumn, setActiveColumn] = useState(null);
|
|
14859
|
-
const [isDragging, setIsDragging] = useState(false);
|
|
14860
|
-
const [dragOverColumnId, setDragOverColumnId] = useState(null);
|
|
14861
|
-
const grabOffsetRef = useRef({
|
|
14862
|
-
x: 0,
|
|
14863
|
-
y: 0
|
|
14864
|
-
});
|
|
14865
|
-
const [overlayPos, setOverlayPos] = useState(null);
|
|
14866
|
-
const handleMouseMove = useCallback((e) => {
|
|
14867
|
-
setOverlayPos({
|
|
14868
|
-
x: e.clientX - grabOffsetRef.current.x,
|
|
14869
|
-
y: e.clientY - grabOffsetRef.current.y
|
|
14870
|
-
});
|
|
14871
|
-
}, []);
|
|
14872
|
-
useEffect(() => {
|
|
14873
|
-
return () => {
|
|
14874
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
14875
|
-
window.removeEventListener("pointermove", handleMouseMove);
|
|
14876
|
-
};
|
|
14877
|
-
}, [handleMouseMove]);
|
|
14878
|
-
const [itemMapState, setItemMapState] = useState(() => {
|
|
14879
|
-
const dataColumnMap = data.reduce((prev, item) => ({
|
|
14880
|
-
...prev,
|
|
14881
|
-
[item.id]: assignColumn(item)
|
|
14882
|
-
}), {});
|
|
14883
|
-
return columnsProp.reduce((previous, column) => ({
|
|
14884
|
-
...previous,
|
|
14885
|
-
[column]: data.filter((item) => dataColumnMap[item.id] === column)
|
|
14886
|
-
}), {});
|
|
14887
|
-
});
|
|
14888
|
-
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }));
|
|
14889
|
-
useEffect(() => {
|
|
14890
|
-
if (isDragging) return;
|
|
14891
|
-
const dataColumnMap = data.reduce((prev, item) => ({
|
|
14892
|
-
...prev,
|
|
14893
|
-
[item.id]: assignColumn(item)
|
|
14894
|
-
}), {});
|
|
14895
|
-
const newItemMap = columnsProp.reduce((previous, column) => ({
|
|
14896
|
-
...previous,
|
|
14897
|
-
[column]: data.filter((item) => dataColumnMap[item.id] === column)
|
|
14898
|
-
}), {});
|
|
14899
|
-
setItemMapState((prevMap) => {
|
|
14900
|
-
let changed = false;
|
|
14901
|
-
for (const col of columnsProp) {
|
|
14902
|
-
const prevItems = prevMap[col] ?? [];
|
|
14903
|
-
const newItems = newItemMap[col] ?? [];
|
|
14904
|
-
if (prevItems.length !== newItems.length) {
|
|
14905
|
-
changed = true;
|
|
14906
|
-
continue;
|
|
14907
|
-
}
|
|
14908
|
-
for (let i = 0; i < prevItems.length; i++) if (prevItems[i].id !== newItems[i].id || prevItems[i].entity !== newItems[i].entity) {
|
|
14909
|
-
changed = true;
|
|
14910
|
-
break;
|
|
14911
|
-
}
|
|
14912
|
-
}
|
|
14913
|
-
if (!changed) return prevMap;
|
|
14914
|
-
const updated = {};
|
|
14915
|
-
for (const col of columnsProp) {
|
|
14916
|
-
const prevItems = prevMap[col] ?? [];
|
|
14917
|
-
const newItems = newItemMap[col] ?? [];
|
|
14918
|
-
const prevById = /* @__PURE__ */ new Map();
|
|
14919
|
-
for (const item of prevItems) prevById.set(item.id, item);
|
|
14920
|
-
updated[col] = newItems.map((newItem) => {
|
|
14921
|
-
const prev = prevById.get(newItem.id);
|
|
14922
|
-
if (prev && prev.entity === newItem.entity) return prev;
|
|
14923
|
-
return newItem;
|
|
14924
|
-
});
|
|
14925
|
-
}
|
|
14926
|
-
return updated;
|
|
14927
|
-
});
|
|
14928
|
-
}, [
|
|
14929
|
-
data,
|
|
14930
|
-
columnsProp,
|
|
14931
|
-
assignColumn
|
|
14932
|
-
]);
|
|
14933
|
-
const findColumnByItemId = (id) => {
|
|
14934
|
-
return Object.keys(itemMapState).find((col) => itemMapState[col]?.some((i) => i.id === id));
|
|
14935
|
-
};
|
|
14936
|
-
const handleDragStart = (event) => {
|
|
14937
|
-
setIsDragging(true);
|
|
14938
|
-
setDragOverColumnId(null);
|
|
14939
|
-
const { active } = event;
|
|
14940
|
-
const activatorEvt = event.activatorEvent;
|
|
14941
|
-
if (activatorEvt) {
|
|
14942
|
-
const target = activatorEvt.target;
|
|
14943
|
-
const rect = (target.closest("[role='button']") ?? target).getBoundingClientRect();
|
|
14944
|
-
grabOffsetRef.current = {
|
|
14945
|
-
x: activatorEvt.clientX - rect.left,
|
|
14946
|
-
y: activatorEvt.clientY - rect.top
|
|
14947
|
-
};
|
|
14948
|
-
setOverlayPos({
|
|
14949
|
-
x: rect.left,
|
|
14950
|
-
y: rect.top
|
|
14951
|
-
});
|
|
14952
|
-
}
|
|
14953
|
-
window.addEventListener("mousemove", handleMouseMove);
|
|
14954
|
-
window.addEventListener("pointermove", handleMouseMove);
|
|
14955
|
-
if (active.data.current?.type === "COLUMN") {
|
|
14956
|
-
const columnId = active.id;
|
|
14957
|
-
const column = columnsProp.find((col) => String(col) === columnId);
|
|
14958
|
-
if (column) setActiveColumn(column);
|
|
14959
|
-
} else if (active.data.current?.type === "ITEM") {
|
|
14960
|
-
const columnId = findColumnByItemId(active.id);
|
|
14961
|
-
if (columnId) {
|
|
14962
|
-
const item = itemMapState[columnId]?.find((i) => i.id === active.id);
|
|
14963
|
-
setActiveItem(item || null);
|
|
14964
|
-
}
|
|
14965
|
-
}
|
|
14966
|
-
};
|
|
14967
|
-
const handleDragOver = (event) => {
|
|
14968
|
-
const { active, over } = event;
|
|
14969
|
-
if (!over) {
|
|
14970
|
-
setDragOverColumnId(null);
|
|
14971
|
-
return;
|
|
14972
|
-
}
|
|
14973
|
-
let currentHoveredColumnId = null;
|
|
14974
|
-
const overId = over.id;
|
|
14975
|
-
const overDataType = over.data.current?.type;
|
|
14976
|
-
if (overDataType === "ITEM-LIST" || overDataType === "COLUMN") currentHoveredColumnId = overId;
|
|
14977
|
-
else if (overDataType === "ITEM") currentHoveredColumnId = findColumnByItemId(overId) || null;
|
|
14978
|
-
else if (columnsProp.includes(overId)) currentHoveredColumnId = overId;
|
|
14979
|
-
setDragOverColumnId(currentHoveredColumnId);
|
|
14980
|
-
if (active.data.current?.type !== "ITEM") return;
|
|
14981
|
-
const activeId = active.id;
|
|
14982
|
-
const activeColumn = findColumnByItemId(activeId);
|
|
14983
|
-
let overColumnForMove = findColumnByItemId(overId);
|
|
14984
|
-
if (!overColumnForMove && overDataType === "ITEM-LIST") overColumnForMove = overId;
|
|
14985
|
-
if (!overColumnForMove && columnsProp.includes(overId)) overColumnForMove = overId;
|
|
14986
|
-
if (!activeColumn || !overColumnForMove) return;
|
|
14987
|
-
if (activeColumn === overColumnForMove) return;
|
|
14988
|
-
if (itemMapState[overColumnForMove]?.some((i) => i.id === activeId)) return;
|
|
14989
|
-
setItemMapState((currentMap) => {
|
|
14990
|
-
const activeItems = [...currentMap[activeColumn] || []];
|
|
14991
|
-
const overItems = [...currentMap[overColumnForMove] || []];
|
|
14992
|
-
const activeIndex = activeItems.findIndex((i) => i.id === activeId);
|
|
14993
|
-
if (activeIndex === -1) return currentMap;
|
|
14994
|
-
let overIndex;
|
|
14995
|
-
if (overDataType === "ITEM-LIST" || columnsProp.includes(overId) && !findColumnByItemId(overId)) overIndex = overItems.length;
|
|
14996
|
-
else {
|
|
14997
|
-
overIndex = overItems.findIndex((i) => i.id === overId);
|
|
14998
|
-
if (overIndex !== -1) {
|
|
14999
|
-
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;
|
|
15000
|
-
overIndex = overIndex >= 0 ? overIndex + modifier : overItems.length;
|
|
15001
|
-
} else overIndex = overItems.length;
|
|
15002
|
-
}
|
|
15003
|
-
const newItemMap = { ...currentMap };
|
|
15004
|
-
const [moved] = activeItems.splice(activeIndex, 1);
|
|
15005
|
-
overItems.splice(overIndex, 0, moved);
|
|
15006
|
-
newItemMap[activeColumn] = activeItems;
|
|
15007
|
-
newItemMap[overColumnForMove] = overItems;
|
|
15008
|
-
return newItemMap;
|
|
15009
|
-
});
|
|
15010
|
-
};
|
|
15011
|
-
const handleDragEnd = (event) => {
|
|
15012
|
-
const { active, over } = event;
|
|
15013
|
-
window.removeEventListener("mousemove", handleMouseMove);
|
|
15014
|
-
window.removeEventListener("pointermove", handleMouseMove);
|
|
15015
|
-
setOverlayPos(null);
|
|
15016
|
-
setIsDragging(false);
|
|
15017
|
-
setActiveItem(null);
|
|
15018
|
-
setActiveColumn(null);
|
|
15019
|
-
setDragOverColumnId(null);
|
|
15020
|
-
if (!over) return;
|
|
15021
|
-
const activeId = active.id;
|
|
15022
|
-
const overId = over.id;
|
|
15023
|
-
if (active.data.current?.type === "COLUMN" && over.data.current?.type === "COLUMN" && activeId !== overId) {
|
|
15024
|
-
const oldIndex = columnsProp.findIndex((col) => String(col) === activeId);
|
|
15025
|
-
const newIndex = columnsProp.findIndex((col) => String(col) === overId);
|
|
15026
|
-
if (oldIndex !== -1 && newIndex !== -1 && onColumnReorder && allowColumnReorder) onColumnReorder(arrayMove([...columnsProp], oldIndex, newIndex));
|
|
15027
|
-
} else if (active.data.current?.type === "ITEM" && onItemsReorder) {
|
|
15028
|
-
const originalColumn = data.find((item) => item.id === activeId) ? assignColumn(data.find((item) => item.id === activeId)) : void 0;
|
|
15029
|
-
const currentColumn = findColumnByItemId(activeId);
|
|
15030
|
-
const overColumn = findColumnByItemId(overId) || currentColumn;
|
|
15031
|
-
const finalItemMapState = { ...itemMapState };
|
|
15032
|
-
if (currentColumn && currentColumn === overColumn) {
|
|
15033
|
-
const activeIndex = finalItemMapState[currentColumn].findIndex((i) => i.id === activeId);
|
|
15034
|
-
let overIndex = finalItemMapState[overColumn].findIndex((i) => i.id === overId);
|
|
15035
|
-
const overType = over.data.current?.type;
|
|
15036
|
-
if (overIndex === -1 && (overType === "COLUMN" || overType === "ITEM-LIST")) if (originalColumn === currentColumn) overIndex = finalItemMapState[overColumn].length;
|
|
15037
|
-
else overIndex = activeIndex;
|
|
15038
|
-
else if (overIndex === -1 && typeof over.data.current?.sortable?.index === "number") overIndex = over.data.current.sortable.index;
|
|
15039
|
-
if (activeIndex !== -1 && overIndex !== -1 && activeIndex !== overIndex) {
|
|
15040
|
-
finalItemMapState[currentColumn] = arrayMove(finalItemMapState[currentColumn], activeIndex, overIndex);
|
|
15041
|
-
setItemMapState(finalItemMapState);
|
|
15042
|
-
}
|
|
15043
|
-
}
|
|
15044
|
-
if (originalColumn !== currentColumn && originalColumn && currentColumn) onItemsReorder(finalItemMapState[currentColumn], {
|
|
15045
|
-
itemId: activeId,
|
|
15046
|
-
sourceColumn: originalColumn,
|
|
15047
|
-
targetColumn: currentColumn
|
|
15048
|
-
});
|
|
15049
|
-
else if (currentColumn) onItemsReorder(finalItemMapState[currentColumn], {
|
|
15050
|
-
itemId: activeId,
|
|
15051
|
-
sourceColumn: currentColumn,
|
|
15052
|
-
targetColumn: currentColumn
|
|
15053
|
-
});
|
|
15054
|
-
}
|
|
15055
|
-
};
|
|
15056
|
-
const customCollisionDetection = (args) => {
|
|
15057
|
-
const pointerCollisions = pointerWithin(args);
|
|
15058
|
-
if (pointerCollisions.length > 0) return pointerCollisions;
|
|
15059
|
-
return rectIntersection(args);
|
|
15060
|
-
};
|
|
15061
|
-
return /* @__PURE__ */ jsxs(DndContext, {
|
|
15062
|
-
sensors,
|
|
15063
|
-
collisionDetection: customCollisionDetection,
|
|
15064
|
-
onDragStart: handleDragStart,
|
|
15065
|
-
onDragOver: handleDragOver,
|
|
15066
|
-
onDragEnd: handleDragEnd,
|
|
15067
|
-
children: [
|
|
15068
|
-
/* @__PURE__ */ jsx(DragOverlay, {
|
|
15069
|
-
dropAnimation: null,
|
|
15070
|
-
style: { opacity: 0 },
|
|
15071
|
-
children: activeItem || activeColumn ? /* @__PURE__ */ jsx("div", { style: {
|
|
15072
|
-
width: 1,
|
|
15073
|
-
height: 1
|
|
15074
|
-
} }) : null
|
|
15075
|
-
}),
|
|
15076
|
-
overlayPos && (activeItem || activeColumn) && createPortal(/* @__PURE__ */ jsx("div", {
|
|
15077
|
-
className: document.documentElement.classList.contains("dark") ? "dark" : "",
|
|
15078
|
-
style: {
|
|
15079
|
-
position: "fixed",
|
|
15080
|
-
top: overlayPos.y,
|
|
15081
|
-
left: overlayPos.x,
|
|
15082
|
-
zIndex: 9999,
|
|
15083
|
-
pointerEvents: "none",
|
|
15084
|
-
touchAction: "none"
|
|
15085
|
-
},
|
|
15086
|
-
children: activeItem ? /* @__PURE__ */ jsx(ItemComponent, {
|
|
15087
|
-
item: activeItem,
|
|
15088
|
-
isDragging: true,
|
|
15089
|
-
index: -1,
|
|
15090
|
-
style: {
|
|
15091
|
-
boxShadow: "0 4px 16px rgba(0,0,0,0.15)",
|
|
15092
|
-
opacity: .9,
|
|
15093
|
-
width: "304px"
|
|
15094
|
-
}
|
|
15095
|
-
}) : activeColumn ? /* @__PURE__ */ jsx(BoardColumn, {
|
|
15096
|
-
index: -1,
|
|
15097
|
-
id: String(activeColumn),
|
|
15098
|
-
title: columnLabels?.[activeColumn] ?? String(activeColumn),
|
|
15099
|
-
items: itemMapState[String(activeColumn)] || [],
|
|
15100
|
-
ItemComponent,
|
|
15101
|
-
isDragging: true,
|
|
15102
|
-
isDragOverColumn: false
|
|
15103
|
-
}, String(activeColumn)) : null
|
|
15104
|
-
}), document.body),
|
|
15105
|
-
/* @__PURE__ */ jsx(SortableContext, {
|
|
15106
|
-
items: columnsProp.map(String),
|
|
15107
|
-
strategy: horizontalListSortingStrategy,
|
|
15108
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
15109
|
-
className: cls("p-2 md:p-3 lg:p-4 h-full min-w-full inline-flex", className),
|
|
15110
|
-
children: [columnsProp.map((key, index) => /* @__PURE__ */ jsx(BoardColumn, {
|
|
15111
|
-
index,
|
|
15112
|
-
id: String(key),
|
|
15113
|
-
title: columnLabels?.[key] ?? String(key),
|
|
15114
|
-
color: columnColors?.[key],
|
|
15115
|
-
items: itemMapState[String(key)] || [],
|
|
15116
|
-
ItemComponent,
|
|
15117
|
-
isDragging,
|
|
15118
|
-
isDragOverColumn: String(key) === dragOverColumnId,
|
|
15119
|
-
allowReorder: allowColumnReorder,
|
|
15120
|
-
loading: columnLoadingState?.[String(key)]?.loading,
|
|
15121
|
-
hasMore: columnLoadingState?.[String(key)]?.hasMore,
|
|
15122
|
-
totalCount: columnLoadingState?.[String(key)]?.totalCount,
|
|
15123
|
-
onLoadMore: onLoadMoreColumn ? () => onLoadMoreColumn(key) : void 0,
|
|
15124
|
-
onAddItem: onAddItemToColumn ? () => onAddItemToColumn(key) : void 0,
|
|
15125
|
-
style: { opacity: activeColumn === key ? 0 : 1 }
|
|
15126
|
-
}, String(key))), AddColumnComponent]
|
|
15127
|
-
})
|
|
15128
|
-
})
|
|
15129
|
-
]
|
|
15130
|
-
});
|
|
15131
|
-
}
|
|
15132
|
-
//#endregion
|
|
15133
14492
|
//#region src/components/EntityCollectionView/EntityBoardCard.tsx
|
|
15134
14493
|
/**
|
|
15135
14494
|
* Compact card component for displaying an entity in a Kanban board.
|
|
@@ -15140,7 +14499,7 @@ function Board({ data, columns: columnsProp, columnLabels, columnColors, classNa
|
|
|
15140
14499
|
* layout clean while providing intuitive selection affordance.
|
|
15141
14500
|
*/
|
|
15142
14501
|
function EntityBoardCardInner({ item, isDragging, isGroupedOver, style, collection, onClick, selected, onSelectionChange, selectionEnabled = false }) {
|
|
15143
|
-
const entity = item.
|
|
14502
|
+
const entity = item.data;
|
|
15144
14503
|
const slotKeys = useCollectionSlotKeys(collection, useAuthController(), useCustomizationController().propertyConfigs);
|
|
15145
14504
|
const slots = useMemo(() => resolveEntitySlots(entity, collection, slotKeys), [
|
|
15146
14505
|
entity,
|
|
@@ -15755,7 +15114,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
15755
15114
|
//#region src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts
|
|
15756
15115
|
function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderProperty, dataClient, context, boardDataController, analyticsController }) {
|
|
15757
15116
|
const handleItemsReorder = useCallback(async (items, moveInfo) => {
|
|
15758
|
-
const entity = items.find((item) => item.id === moveInfo?.itemId)?.
|
|
15117
|
+
const entity = items.find((item) => item.id === moveInfo?.itemId)?.data;
|
|
15759
15118
|
if (!entity || !moveInfo) return;
|
|
15760
15119
|
analyticsController.onAnalyticsEvent?.("kanban_card_moved", {
|
|
15761
15120
|
path: fullPath,
|
|
@@ -15769,8 +15128,8 @@ function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderPrope
|
|
|
15769
15128
|
if (orderProperty) {
|
|
15770
15129
|
const targetColumnItems = items;
|
|
15771
15130
|
const movedIndex = targetColumnItems.findIndex((item) => item.id === moveInfo.itemId);
|
|
15772
|
-
const prevKey = movedIndex > 0 ? targetColumnItems[movedIndex - 1].
|
|
15773
|
-
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;
|
|
15774
15133
|
try {
|
|
15775
15134
|
const a = prevKey;
|
|
15776
15135
|
let b = nextKey;
|
|
@@ -16009,7 +15368,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
|
|
|
16009
15368
|
const boardItems = useMemo(() => {
|
|
16010
15369
|
return allEntities.map((entity) => ({
|
|
16011
15370
|
id: String(entity.id),
|
|
16012
|
-
entity
|
|
15371
|
+
data: entity
|
|
16013
15372
|
}));
|
|
16014
15373
|
}, [allEntities]);
|
|
16015
15374
|
const columnLoadingState = useMemo(() => {
|
|
@@ -16028,7 +15387,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
|
|
|
16028
15387
|
const assignColumn = useCallback((item) => {
|
|
16029
15388
|
const column = entityColumnMap[item.id];
|
|
16030
15389
|
if (column) return column;
|
|
16031
|
-
const value = item.
|
|
15390
|
+
const value = item.data.values?.[columnProperty];
|
|
16032
15391
|
if (value && columns.includes(String(value))) return String(value);
|
|
16033
15392
|
return columns[0] || "";
|
|
16034
15393
|
}, [
|
|
@@ -16122,7 +15481,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
|
|
|
16122
15481
|
...props,
|
|
16123
15482
|
collection: collectionRef.current,
|
|
16124
15483
|
onClick: stableOnClick,
|
|
16125
|
-
selected: isEntitySelectedRef.current(props.item.
|
|
15484
|
+
selected: isEntitySelectedRef.current(props.item.data),
|
|
16126
15485
|
onSelectionChange: stableOnSelectionChange,
|
|
16127
15486
|
selectionEnabled: selectionEnabledRef.current
|
|
16128
15487
|
});
|
|
@@ -16224,7 +15583,7 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
|
|
|
16224
15583
|
}),
|
|
16225
15584
|
/* @__PURE__ */ jsx("div", {
|
|
16226
15585
|
className: "flex-1 overflow-auto no-scrollbar",
|
|
16227
|
-
children: /* @__PURE__ */ jsx(
|
|
15586
|
+
children: /* @__PURE__ */ jsx(KanbanView, {
|
|
16228
15587
|
data: boardItems,
|
|
16229
15588
|
columns,
|
|
16230
15589
|
columnLabels,
|
|
@@ -16255,23 +15614,30 @@ function EntityCollectionBoardView({ collection, tableController, fullPath, pare
|
|
|
16255
15614
|
/* @__PURE__ */ jsxs(Dialog, {
|
|
16256
15615
|
open: showBackfillDialog,
|
|
16257
15616
|
onOpenChange: setShowBackfillDialog,
|
|
16258
|
-
children: [
|
|
16259
|
-
|
|
16260
|
-
|
|
16261
|
-
|
|
16262
|
-
|
|
16263
|
-
|
|
16264
|
-
|
|
16265
|
-
|
|
16266
|
-
|
|
16267
|
-
|
|
16268
|
-
|
|
16269
|
-
|
|
16270
|
-
|
|
16271
|
-
|
|
16272
|
-
|
|
16273
|
-
|
|
16274
|
-
|
|
15617
|
+
children: [
|
|
15618
|
+
/* @__PURE__ */ jsx(DialogTitle, {
|
|
15619
|
+
hidden: true,
|
|
15620
|
+
children: t("initialize_kanban_order")
|
|
15621
|
+
}),
|
|
15622
|
+
/* @__PURE__ */ jsxs(DialogContent, { children: [/* @__PURE__ */ jsx(Typography, {
|
|
15623
|
+
variant: "h6",
|
|
15624
|
+
className: "mb-4",
|
|
15625
|
+
children: t("initialize_kanban_order")
|
|
15626
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
15627
|
+
variant: "body2",
|
|
15628
|
+
children: t("initialize_kanban_order_desc")
|
|
15629
|
+
})] }),
|
|
15630
|
+
/* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
|
|
15631
|
+
variant: "text",
|
|
15632
|
+
onClick: () => setShowBackfillDialog(false),
|
|
15633
|
+
disabled: backfillLoading,
|
|
15634
|
+
children: t("cancel")
|
|
15635
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
15636
|
+
onClick: handleBackfill,
|
|
15637
|
+
disabled: backfillLoading,
|
|
15638
|
+
children: backfillLoading ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : t("initialize")
|
|
15639
|
+
})] })
|
|
15640
|
+
]
|
|
16275
15641
|
})
|
|
16276
15642
|
]
|
|
16277
15643
|
});
|
|
@@ -18675,10 +18041,10 @@ function DrawerNavigationItem({ name, icon, drawerOpen, adminMenuOpen, tooltipsO
|
|
|
18675
18041
|
width: "100%",
|
|
18676
18042
|
transition: drawerOpen ? "width 150ms ease-in" : void 0
|
|
18677
18043
|
},
|
|
18678
|
-
className: ({ isActive }) => cls("rounded-
|
|
18044
|
+
className: ({ isActive }) => cls("rounded-lg truncate group/nav", "hover:bg-primary/5 dark:hover:bg-primary/5 text-surface-700 dark:text-surface-300 hover:text-surface-900 dark:hover:text-white", "flex flex-row items-center", drawerOpen ? "pr-4 h-[30px]" : "h-[30px]", "font-medium text-[13px]", isActive ? "bg-primary/8 dark:bg-primary/10 text-primary dark:text-primary [&_div]:text-primary" : ""),
|
|
18679
18045
|
to: url,
|
|
18680
18046
|
children: [/* @__PURE__ */ jsx("div", {
|
|
18681
|
-
className: "shrink-0 flex items-center justify-center w-[44px] h-[30px] text-surface-500 dark:text-text-secondary-dark [&>svg]:size-4",
|
|
18047
|
+
className: "shrink-0 flex items-center justify-center w-[44px] h-[30px] text-surface-500 dark:text-text-secondary-dark [&>svg]:size-4 group-hover/nav:text-primary transition-colors duration-150",
|
|
18682
18048
|
children: icon
|
|
18683
18049
|
}), /* @__PURE__ */ jsx("div", {
|
|
18684
18050
|
className: cls("text-text-primary dark:text-surface-200", drawerOpen ? "opacity-100" : "opacity-0 hidden", "font-inherit truncate space-x-2"),
|
|
@@ -18723,7 +18089,7 @@ function DrawerNavigationGroup({ group, entries, collapsed, onToggleCollapsed, d
|
|
|
18723
18089
|
})
|
|
18724
18090
|
]
|
|
18725
18091
|
}), /* @__PURE__ */ jsx("div", {
|
|
18726
|
-
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"),
|
|
18727
18093
|
children: entries.map((entry) => /* @__PURE__ */ jsx(ResolvedDrawerNavigationItem, {
|
|
18728
18094
|
icon: /* @__PURE__ */ jsx(IconForView, {
|
|
18729
18095
|
collectionOrView: entry.collection ?? entry.view,
|
|
@@ -18813,7 +18179,13 @@ function CMSNavigationContent() {
|
|
|
18813
18179
|
const registry = useRebaseRegistry();
|
|
18814
18180
|
const ResolvedDrawerNavigationGroup = useComponentOverride("Shell.DrawerNavigationGroup", DrawerNavigationGroup);
|
|
18815
18181
|
const allNavigationEntries = navigationState.topLevelNavigation?.navigationEntries ?? [];
|
|
18816
|
-
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));
|
|
18817
18189
|
const entryGroups = new Set(filteredEntries.map((e) => e.group).filter(Boolean));
|
|
18818
18190
|
const orderedGroups = navigationState.topLevelNavigation?.groups ?? [];
|
|
18819
18191
|
const groupsToRender = [...orderedGroups.filter((g) => entryGroups.has(g)), ...[...entryGroups].filter((g) => !orderedGroups.includes(g))];
|
|
@@ -19251,11 +18623,12 @@ function DrawerWrapper(props) {
|
|
|
19251
18623
|
* is auto-wired as a native feature (slots, provider, Studio view) without
|
|
19252
18624
|
* needing any external plugin.
|
|
19253
18625
|
*/
|
|
19254
|
-
function RebaseCMS({ collections, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings }) {
|
|
18626
|
+
function RebaseCMS({ collections, views, homePage, entityViews, entityActions, plugins, collectionEditor, navigationGroupMappings }) {
|
|
19255
18627
|
const dispatch = useRebaseRegistryDispatch();
|
|
19256
18628
|
useLayoutEffect(() => {
|
|
19257
18629
|
dispatch.registerCMS({
|
|
19258
18630
|
collections,
|
|
18631
|
+
views,
|
|
19259
18632
|
homePage,
|
|
19260
18633
|
entityViews,
|
|
19261
18634
|
entityActions,
|
|
@@ -19267,6 +18640,7 @@ function RebaseCMS({ collections, homePage, entityViews, entityActions, plugins,
|
|
|
19267
18640
|
}, [
|
|
19268
18641
|
dispatch,
|
|
19269
18642
|
collections,
|
|
18643
|
+
views,
|
|
19270
18644
|
homePage,
|
|
19271
18645
|
entityViews,
|
|
19272
18646
|
entityActions,
|
|
@@ -19406,7 +18780,7 @@ function useLocalCollectionsConfigController(clientOrUrl, baseCollections = [],
|
|
|
19406
18780
|
//#region src/components/RebaseNavigation.tsx
|
|
19407
18781
|
var EMPTY_PLUGINS = [];
|
|
19408
18782
|
var EMPTY_COLLECTIONS = [];
|
|
19409
|
-
var CollectionsStudioView = lazy(() => import("./
|
|
18783
|
+
var CollectionsStudioView = lazy(() => import("./RouterCollectionsStudioView-Dqx27GD8.js").then((n) => n.n).then((m) => ({ default: m.RouterCollectionsStudioView })));
|
|
19410
18784
|
/**
|
|
19411
18785
|
* Navigation layer — builds and provides all CMS navigation controllers:
|
|
19412
18786
|
* collection registry, URL controller, navigation state, side entity,
|
|
@@ -19439,7 +18813,19 @@ function RebaseNavigation({ children }) {
|
|
|
19439
18813
|
}, [collectionEditorConfig]);
|
|
19440
18814
|
const cmsCollections = registry.cmsConfig?.collections ?? EMPTY_COLLECTIONS;
|
|
19441
18815
|
const collectionsBuilder = useMemo(() => Array.isArray(cmsCollections) ? () => [...cmsCollections] : cmsCollections, [cmsCollections]);
|
|
19442
|
-
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]);
|
|
19443
18829
|
const urlController = useBuildUrlController({
|
|
19444
18830
|
basePath: "/",
|
|
19445
18831
|
baseCollectionPath: "/c",
|
|
@@ -19447,7 +18833,14 @@ function RebaseNavigation({ children }) {
|
|
|
19447
18833
|
});
|
|
19448
18834
|
const rebaseClient = useRebaseClient();
|
|
19449
18835
|
const authController = useAuthController();
|
|
19450
|
-
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 ? {
|
|
19451
18844
|
readOnly: collectionEditorOptions?.readOnly ?? process.env.NODE_ENV === "production",
|
|
19452
18845
|
getAuthToken: collectionEditorOptions?.getAuthToken ?? authController?.getAuthToken
|
|
19453
18846
|
} : { readOnly: true });
|
|
@@ -19464,6 +18857,7 @@ function RebaseNavigation({ children }) {
|
|
|
19464
18857
|
return {
|
|
19465
18858
|
slug: "schema",
|
|
19466
18859
|
name: "Edit collections",
|
|
18860
|
+
group: "Database",
|
|
19467
18861
|
icon: "LayoutList",
|
|
19468
18862
|
nestedRoutes: true,
|
|
19469
18863
|
view: /* @__PURE__ */ jsx(Suspense, {
|
|
@@ -19481,13 +18875,22 @@ function RebaseNavigation({ children }) {
|
|
|
19481
18875
|
if (schemaView) return [...base, schemaView];
|
|
19482
18876
|
return base;
|
|
19483
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]);
|
|
19484
18887
|
const navigationStateController = useBuildNavigationStateController({
|
|
19485
18888
|
plugins: registry.cmsConfig?.plugins ?? EMPTY_PLUGINS,
|
|
19486
18889
|
collections: collectionsBuilder,
|
|
19487
|
-
views:
|
|
18890
|
+
views: mergedViews,
|
|
19488
18891
|
navigationGroupMappings: registry.cmsConfig?.navigationGroupMappings,
|
|
19489
18892
|
authController: context.authController,
|
|
19490
|
-
data:
|
|
18893
|
+
data: routedData,
|
|
19491
18894
|
collectionRegistryController,
|
|
19492
18895
|
urlController,
|
|
19493
18896
|
adminMode: adminModeController?.mode
|
|
@@ -19507,22 +18910,25 @@ function RebaseNavigation({ children }) {
|
|
|
19507
18910
|
registry.cmsConfig?.entityViews,
|
|
19508
18911
|
registry.cmsConfig?.entityActions
|
|
19509
18912
|
]);
|
|
19510
|
-
const navigationContent = /* @__PURE__ */ jsx(
|
|
19511
|
-
value:
|
|
19512
|
-
children: /* @__PURE__ */ jsx(
|
|
19513
|
-
value:
|
|
19514
|
-
children: /* @__PURE__ */ jsx(
|
|
19515
|
-
value:
|
|
19516
|
-
children: /* @__PURE__ */ jsx(
|
|
19517
|
-
value:
|
|
19518
|
-
children: /* @__PURE__ */
|
|
19519
|
-
|
|
19520
|
-
|
|
19521
|
-
|
|
19522
|
-
|
|
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
|
+
})
|
|
19523
18929
|
})
|
|
19524
|
-
})
|
|
19525
|
-
})
|
|
18930
|
+
}) })
|
|
18931
|
+
})
|
|
19526
18932
|
});
|
|
19527
18933
|
if (collectionEditorEnabled) return /* @__PURE__ */ jsx(ConfigControllerProvider, {
|
|
19528
18934
|
collectionConfigController: internalConfigController,
|
|
@@ -19635,7 +19041,7 @@ function NavigationGroup({ children, group, minimised, isPreview, isPotentialCar
|
|
|
19635
19041
|
variant: isPreview ? "body2" : "caption",
|
|
19636
19042
|
component: "h2",
|
|
19637
19043
|
color: "secondary",
|
|
19638
|
-
className: cls("px-4 py-1 rounded", "font-
|
|
19044
|
+
className: cls("px-4 py-1 rounded", "font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70"),
|
|
19639
19045
|
children: currentGroupName
|
|
19640
19046
|
}), !isPreview && onEditGroup && !dndDisabled && /* @__PURE__ */ jsx(IconButton, {
|
|
19641
19047
|
size: "smallest",
|
|
@@ -19698,7 +19104,7 @@ function NavigationGroup({ children, group, minimised, isPreview, isPotentialCar
|
|
|
19698
19104
|
//#region src/components/HomePage/NavigationCard.tsx
|
|
19699
19105
|
var NavigationCard = React.memo(function NavigationCard({ name, description, icon, actions, additionalContent, onClick, shrink }) {
|
|
19700
19106
|
return /* @__PURE__ */ jsx(Card, {
|
|
19701
|
-
className: cls("group h-full p-4 cursor-pointer transition-
|
|
19107
|
+
className: cls("group h-full p-4 cursor-pointer transition-colors duration-150 ease-in-out", "hover:bg-primary/5 dark:hover:bg-primary/5", shrink && "w-full max-w-full min-h-0 scale-75"),
|
|
19702
19108
|
onClick: () => {
|
|
19703
19109
|
onClick?.();
|
|
19704
19110
|
},
|
|
@@ -19708,9 +19114,9 @@ var NavigationCard = React.memo(function NavigationCard({ name, description, ico
|
|
|
19708
19114
|
/* @__PURE__ */ jsxs("div", {
|
|
19709
19115
|
className: "flex items-center w-full justify-between mb-1",
|
|
19710
19116
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
19711
|
-
className: "flex items-center gap-
|
|
19117
|
+
className: "flex items-center gap-3",
|
|
19712
19118
|
children: [/* @__PURE__ */ jsx("div", {
|
|
19713
|
-
className: "flex items-center justify-center w-
|
|
19119
|
+
className: "flex items-center justify-center w-5 h-5 text-surface-400 dark:text-surface-500 transition-colors duration-150 group-hover:text-primary dark:group-hover:text-primary",
|
|
19714
19120
|
children: icon
|
|
19715
19121
|
}), /* @__PURE__ */ jsx(Typography, {
|
|
19716
19122
|
variant: "subtitle1",
|
|
@@ -19727,7 +19133,7 @@ var NavigationCard = React.memo(function NavigationCard({ name, description, ico
|
|
|
19727
19133
|
})]
|
|
19728
19134
|
}),
|
|
19729
19135
|
/* @__PURE__ */ jsx("div", {
|
|
19730
|
-
className: "
|
|
19136
|
+
className: "pl-8",
|
|
19731
19137
|
children: description && /* @__PURE__ */ jsx(Typography, {
|
|
19732
19138
|
variant: "caption",
|
|
19733
19139
|
color: "secondary",
|
|
@@ -19739,19 +19145,10 @@ var NavigationCard = React.memo(function NavigationCard({ name, description, ico
|
|
|
19739
19145
|
})
|
|
19740
19146
|
}),
|
|
19741
19147
|
additionalContent && /* @__PURE__ */ jsx("div", {
|
|
19742
|
-
className: "pl-
|
|
19148
|
+
className: "pl-8 pointer-events-none",
|
|
19743
19149
|
children: additionalContent
|
|
19744
19150
|
}),
|
|
19745
|
-
/* @__PURE__ */ jsx("div", {
|
|
19746
|
-
className: "self-end mt-1",
|
|
19747
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
19748
|
-
className: "transition-transform duration-200 group-hover:translate-x-0.5",
|
|
19749
|
-
children: /* @__PURE__ */ jsx(ArrowRightIcon, {
|
|
19750
|
-
className: "text-primary",
|
|
19751
|
-
size: iconSize.small
|
|
19752
|
-
})
|
|
19753
|
-
})
|
|
19754
|
-
})
|
|
19151
|
+
/* @__PURE__ */ jsx("div", { className: "grow" })
|
|
19755
19152
|
]
|
|
19756
19153
|
})
|
|
19757
19154
|
});
|
|
@@ -20316,10 +19713,20 @@ function ContentHomePage({ additionalActions, additionalChildrenStart, additiona
|
|
|
20316
19713
|
breadcrumbs.set({ breadcrumbs: [] });
|
|
20317
19714
|
}, [breadcrumbs.set]);
|
|
20318
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]);
|
|
20319
19722
|
const rawNavigationEntries = useMemo(() => {
|
|
20320
19723
|
if (adminModeController.mode === "studio") return unFilteredNavigationEntries.filter((e) => e.type === "view" || e.type === "admin");
|
|
20321
|
-
return unFilteredNavigationEntries.filter((e) => e.type !== "view");
|
|
20322
|
-
}, [
|
|
19724
|
+
return unFilteredNavigationEntries.filter((e) => e.type !== "view" || !studioViewSlugs.has(e.slug));
|
|
19725
|
+
}, [
|
|
19726
|
+
unFilteredNavigationEntries,
|
|
19727
|
+
adminModeController.mode,
|
|
19728
|
+
studioViewSlugs
|
|
19729
|
+
]);
|
|
20323
19730
|
const groupOrderFromNavController = useMemo(() => {
|
|
20324
19731
|
const entryGroups = new Set(rawNavigationEntries.map((e) => e.group).filter(Boolean));
|
|
20325
19732
|
return unFilteredGroupOrder.filter((g) => entryGroups.has(g));
|
|
@@ -20849,18 +20256,24 @@ function EntityFullScreenRoute({ pathname, navigationEntries, isNew, isCopy }) {
|
|
|
20849
20256
|
function CustomViewRoute({ view }) {
|
|
20850
20257
|
const breadcrumbs = useBreadcrumbsController();
|
|
20851
20258
|
const urlController = useUrlController();
|
|
20259
|
+
const breadcrumbsRef = useRef(breadcrumbs);
|
|
20260
|
+
breadcrumbsRef.current = breadcrumbs;
|
|
20852
20261
|
useEffect(() => {
|
|
20853
|
-
|
|
20262
|
+
breadcrumbsRef.current.set({ breadcrumbs: [{
|
|
20854
20263
|
title: view.name,
|
|
20855
20264
|
url: urlController.buildAppUrlPath(view.slug)
|
|
20856
20265
|
}] });
|
|
20857
20266
|
}, [view.slug, urlController]);
|
|
20858
|
-
|
|
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 });
|
|
20859
20272
|
}
|
|
20860
20273
|
//#endregion
|
|
20861
20274
|
//#region src/components/CollectionEditorDialogs.tsx
|
|
20862
|
-
var CollectionEditorDialog = lazy(() => import("./CollectionEditorDialog-
|
|
20863
|
-
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 })));
|
|
20864
20277
|
/**
|
|
20865
20278
|
* Renders the CollectionEditorDialog and PropertyFormDialog inside
|
|
20866
20279
|
* the RebaseShell tree where CMS-internal contexts
|
|
@@ -22184,7 +21597,7 @@ function LocalChangesMenu({ cachedData, formex, onClearLocalChanges, cacheKey, p
|
|
|
22184
21597
|
})] });
|
|
22185
21598
|
}
|
|
22186
21599
|
//#endregion
|
|
22187
|
-
//#region src/form/
|
|
21600
|
+
//#region src/form/form_utils.ts
|
|
22188
21601
|
function extractTouchedValues(values, touched) {
|
|
22189
21602
|
let acc = {};
|
|
22190
21603
|
if (!touched || typeof touched !== "object") return acc;
|
|
@@ -22200,7 +21613,7 @@ function extractTouchedValues(values, touched) {
|
|
|
22200
21613
|
* from falsely triggering the unsaved local changes indicator.
|
|
22201
21614
|
*/
|
|
22202
21615
|
/**
|
|
22203
|
-
*
|
|
21616
|
+
* Check if a value is semantically empty (null, undefined, or empty string).
|
|
22204
21617
|
*/
|
|
22205
21618
|
function isSemanticEmpty(v) {
|
|
22206
21619
|
return v === null || v === void 0 || v === "";
|
|
@@ -22244,23 +21657,51 @@ function getChanges(source, comparison) {
|
|
|
22244
21657
|
}
|
|
22245
21658
|
return changes;
|
|
22246
21659
|
}
|
|
22247
|
-
function
|
|
22248
|
-
const
|
|
22249
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
22252
|
-
|
|
22253
|
-
|
|
22254
|
-
|
|
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;
|
|
22255
21670
|
}
|
|
22256
|
-
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
|
|
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]);
|
|
22264
21705
|
const [status, setStatus] = useState(initialStatus);
|
|
22265
21706
|
const updateStatus = (status) => {
|
|
22266
21707
|
setStatus(status);
|
|
@@ -22268,18 +21709,11 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22268
21709
|
};
|
|
22269
21710
|
const [valuesToBeSaved, setValuesToBeSaved] = useState(void 0);
|
|
22270
21711
|
useDebouncedCallback(valuesToBeSaved, () => {
|
|
22271
|
-
if (valuesToBeSaved)
|
|
22272
|
-
|
|
22273
|
-
|
|
22274
|
-
|
|
22275
|
-
values: valuesToBeSaved
|
|
22276
|
-
});
|
|
21712
|
+
if (valuesToBeSaved && onSubmitProp) {
|
|
21713
|
+
setIsSavingAutoSave(true);
|
|
21714
|
+
Promise.resolve(onSubmitProp(valuesToBeSaved, formex)).finally(() => setIsSavingAutoSave(false));
|
|
21715
|
+
}
|
|
22277
21716
|
}, false, 2e3);
|
|
22278
|
-
const dataClient = useData();
|
|
22279
|
-
const snackbarController = useSnackbarController();
|
|
22280
|
-
const customizationController = useCustomizationController();
|
|
22281
|
-
const context = useCMSContext();
|
|
22282
|
-
const analyticsController = useAnalyticsController();
|
|
22283
21717
|
const [underlyingChanges] = useState({});
|
|
22284
21718
|
const initialEntityId = useMemo(() => {
|
|
22285
21719
|
if (status === "new" || status === "copy") return;
|
|
@@ -22288,58 +21722,45 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22288
21722
|
const [entityId, setEntityId] = useState(initialEntityId);
|
|
22289
21723
|
const [entityIdError, setEntityIdError] = useState(false);
|
|
22290
21724
|
const [savingError, setSavingError] = useState();
|
|
21725
|
+
const [isSavingAutoSave, setIsSavingAutoSave] = useState(false);
|
|
21726
|
+
const [discardDialogOpen, setDiscardDialogOpen] = useState(false);
|
|
22291
21727
|
const autoSave = collection.formAutoSave;
|
|
22292
|
-
const baseInitialValues = useMemo(() =>
|
|
22293
|
-
|
|
22294
|
-
collection
|
|
22295
|
-
|
|
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,
|
|
22296
21735
|
status,
|
|
22297
|
-
entity
|
|
22298
|
-
customizationController.propertyConfigs
|
|
21736
|
+
entity
|
|
22299
21737
|
]);
|
|
22300
|
-
const localChangesDataRaw = useMemo(() => entityId ? getEntityFromCache(path + "/" + entityId) : getEntityFromCache(path + "#new"), [entityId, path]);
|
|
22301
21738
|
const [localChangesCleared, setLocalChangesCleared] = useState(false);
|
|
22302
|
-
const
|
|
22303
|
-
const autoApplyLocalChanges = localChangesBackup === "auto_apply";
|
|
22304
|
-
const manualApplyLocalChanges = localChangesBackup === "manual_apply";
|
|
21739
|
+
const hasLocalChanges = hasLocalChangesProp !== void 0 ? hasLocalChangesProp && !localChangesCleared : false;
|
|
22305
21740
|
const onSubmit = (values, formexController) => {
|
|
22306
21741
|
setSavingError(void 0);
|
|
22307
21742
|
setEntityIdError(false);
|
|
22308
21743
|
if (status === "existing") {
|
|
22309
21744
|
if (!entity?.id) throw Error("Form misconfiguration when saving, no id for existing entity");
|
|
22310
21745
|
} else if (status !== "new" && status !== "copy") throw Error("New FormType added, check EntityForm");
|
|
22311
|
-
|
|
22312
|
-
|
|
22313
|
-
|
|
22314
|
-
|
|
22315
|
-
|
|
22316
|
-
|
|
22317
|
-
|
|
22318
|
-
|
|
22319
|
-
|
|
22320
|
-
|
|
22321
|
-
|
|
22322
|
-
}
|
|
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
|
+
});
|
|
22323
21757
|
}).finally(() => {
|
|
22324
21758
|
formexController.setSubmitting(false);
|
|
22325
21759
|
});
|
|
22326
21760
|
};
|
|
22327
21761
|
const [initialValues, initialDirty] = useMemo(() => {
|
|
22328
|
-
|
|
22329
|
-
|
|
22330
|
-
}, [
|
|
22331
|
-
autoApplyLocalChanges,
|
|
22332
|
-
localChangesDataRaw,
|
|
22333
|
-
baseInitialValues,
|
|
22334
|
-
initialDirtyValues
|
|
22335
|
-
]);
|
|
22336
|
-
const localChangesData = useMemo(() => {
|
|
22337
|
-
if (!localChangesDataRaw) return;
|
|
22338
|
-
const cleaned = removeEmptyContainers(getChanges(localChangesDataRaw, initialValues));
|
|
22339
|
-
if (cleaned && typeof cleaned === "object" && Object.keys(cleaned).length === 0) return;
|
|
22340
|
-
return cleaned;
|
|
22341
|
-
}, [localChangesDataRaw, initialValues]);
|
|
22342
|
-
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]);
|
|
22343
21764
|
const internalFormex = useCreateFormex({
|
|
22344
21765
|
initialValues,
|
|
22345
21766
|
initialDirty,
|
|
@@ -22350,17 +21771,10 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22350
21771
|
}), {}) : {},
|
|
22351
21772
|
onSubmit,
|
|
22352
21773
|
onReset: () => {
|
|
22353
|
-
|
|
21774
|
+
onResetProp?.();
|
|
22354
21775
|
onValuesModified?.(false, initialValues);
|
|
22355
21776
|
},
|
|
22356
|
-
onValuesChangeDeferred:
|
|
22357
|
-
const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
|
|
22358
|
-
if (controller.dirty) {
|
|
22359
|
-
const touchedValues = removeEmptyContainers(extractTouchedValues(values, controller.touched));
|
|
22360
|
-
if (touchedValues && Object.keys(touchedValues).length > 0) saveEntityToCache(key, touchedValues);
|
|
22361
|
-
else removeEntityFromCache(key);
|
|
22362
|
-
}
|
|
22363
|
-
},
|
|
21777
|
+
onValuesChangeDeferred: onValuesChangeDeferredProp,
|
|
22364
21778
|
validation: async (values) => {
|
|
22365
21779
|
if (!validationSchema) return {};
|
|
22366
21780
|
const result = await validationSchema.safeParseAsync(values);
|
|
@@ -22373,7 +21787,10 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22373
21787
|
const handleKeyDown = (e) => {
|
|
22374
21788
|
const isUndo = (e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "z";
|
|
22375
21789
|
const isRedo = (e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "z" || (e.metaKey || e.ctrlKey) && !e.shiftKey && e.key.toLowerCase() === "y";
|
|
22376
|
-
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) {
|
|
22377
21794
|
e.preventDefault();
|
|
22378
21795
|
formex.undo();
|
|
22379
21796
|
} else if (isRedo && formex.canRedo) {
|
|
@@ -22384,31 +21801,8 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22384
21801
|
window.addEventListener("keydown", handleKeyDown);
|
|
22385
21802
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
22386
21803
|
}, [formex]);
|
|
22387
|
-
useCallback((e) => {
|
|
22388
|
-
snackbarController.open({
|
|
22389
|
-
type: "error",
|
|
22390
|
-
message: `${t("error_before_saving")}: ${e?.message}`
|
|
22391
|
-
});
|
|
22392
|
-
console.error(e);
|
|
22393
|
-
}, [snackbarController]);
|
|
22394
|
-
useCallback((e) => {
|
|
22395
|
-
snackbarController.open({
|
|
22396
|
-
type: "error",
|
|
22397
|
-
message: `${t("error_after_saving")}: ${e?.message}`
|
|
22398
|
-
});
|
|
22399
|
-
console.error(e);
|
|
22400
|
-
}, [snackbarController]);
|
|
22401
|
-
function clearDirtyCache() {
|
|
22402
|
-
if (status === "new" || status === "copy") {
|
|
22403
|
-
removeEntityFromMemoryCache(path + "#new");
|
|
22404
|
-
removeEntityFromCache(path + "#new");
|
|
22405
|
-
} else {
|
|
22406
|
-
removeEntityFromMemoryCache(path + "/" + entityId);
|
|
22407
|
-
removeEntityFromCache(path + "/" + entityId);
|
|
22408
|
-
}
|
|
22409
|
-
}
|
|
22410
21804
|
const afterSave = (updatedEntity) => {
|
|
22411
|
-
|
|
21805
|
+
onResetProp?.();
|
|
22412
21806
|
onValuesModified?.(false, updatedEntity.values);
|
|
22413
21807
|
onEntityChange?.(updatedEntity);
|
|
22414
21808
|
updateStatus("existing");
|
|
@@ -22421,62 +21815,19 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22421
21815
|
collection
|
|
22422
21816
|
});
|
|
22423
21817
|
};
|
|
22424
|
-
const afterSaveError = useCallback((e) => {
|
|
22425
|
-
snackbarController.open({
|
|
22426
|
-
type: "error",
|
|
22427
|
-
title: t("error_saving_entity"),
|
|
22428
|
-
message: e?.message
|
|
22429
|
-
});
|
|
22430
|
-
console.error("Error saving entity", path, entityId, e);
|
|
22431
|
-
console.error(e);
|
|
22432
|
-
}, [
|
|
22433
|
-
entityId,
|
|
22434
|
-
path,
|
|
22435
|
-
snackbarController
|
|
22436
|
-
]);
|
|
22437
|
-
const saveEntity = ({ values, previousValues, entityId, collection, path }) => {
|
|
22438
|
-
return saveEntityWithCallbacks({
|
|
22439
|
-
path,
|
|
22440
|
-
entityId,
|
|
22441
|
-
values,
|
|
22442
|
-
previousValues,
|
|
22443
|
-
collection,
|
|
22444
|
-
status,
|
|
22445
|
-
data: dataClient,
|
|
22446
|
-
context,
|
|
22447
|
-
afterSave,
|
|
22448
|
-
afterSaveError
|
|
22449
|
-
});
|
|
22450
|
-
};
|
|
22451
|
-
const onSaveEntityRequest = async ({ collection, path, entityId, values, previousValues, autoSave }) => {
|
|
22452
|
-
if (!status) return;
|
|
22453
|
-
if (autoSave) {
|
|
22454
|
-
setValuesToBeSaved(values);
|
|
22455
|
-
return Promise.resolve();
|
|
22456
|
-
} else return saveEntity({
|
|
22457
|
-
collection,
|
|
22458
|
-
path,
|
|
22459
|
-
entityId,
|
|
22460
|
-
values,
|
|
22461
|
-
previousValues
|
|
22462
|
-
});
|
|
22463
|
-
};
|
|
22464
21818
|
const lastSavedValues = useRef(entity?.values);
|
|
22465
21819
|
const save = async (values) => {
|
|
22466
21820
|
const valuesToSave = status === "existing" ? getChanges(values, entity?.values || {}) : values;
|
|
22467
21821
|
if (status === "existing" && Object.keys(valuesToSave).length === 0 && entity) return Promise.resolve(entity);
|
|
22468
21822
|
lastSavedValues.current = values;
|
|
22469
|
-
|
|
22470
|
-
|
|
22471
|
-
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
|
|
22475
|
-
|
|
22476
|
-
|
|
22477
|
-
const eventName = status === "new" ? "new_entity_saved" : status === "copy" ? "entity_copied" : status === "existing" ? "entity_edited" : "unmapped_event";
|
|
22478
|
-
analyticsController.onAnalyticsEvent?.(eventName, { path });
|
|
22479
|
-
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;
|
|
22480
21831
|
}).catch((e) => {
|
|
22481
21832
|
console.error(e);
|
|
22482
21833
|
setSavingError(e);
|
|
@@ -22507,44 +21858,18 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22507
21858
|
path
|
|
22508
21859
|
]);
|
|
22509
21860
|
const actionsDisabled = disabled || formex.isSubmitting || status === "existing" && !formex.dirty || Boolean(disabledProp);
|
|
22510
|
-
const formActionProps = {
|
|
22511
|
-
entityId,
|
|
22512
|
-
parentCollectionSlugs: collectionRegistryController.getParentCollectionSlugs(path),
|
|
22513
|
-
parentEntityIds: collectionRegistryController.getParentEntityIds(path),
|
|
22514
|
-
path,
|
|
22515
|
-
status,
|
|
22516
|
-
collection,
|
|
22517
|
-
context,
|
|
22518
|
-
formContext,
|
|
22519
|
-
openEntityMode,
|
|
22520
|
-
disabled: actionsDisabled
|
|
22521
|
-
};
|
|
22522
|
-
const pluginFormActions = useSlot("form.actions", formActionProps);
|
|
22523
|
-
const pluginFormBefore = useSlot("form.before", formActionProps);
|
|
22524
|
-
const pluginFormAfter = useSlot("form.after", formActionProps);
|
|
22525
21861
|
const titlePropertyKey = getEntityTitlePropertyKey(collection, customizationController.propertyConfigs);
|
|
22526
21862
|
const rawTitle = formex.values && titlePropertyKey ? getValueInPath(formex.values, titlePropertyKey) : void 0;
|
|
22527
21863
|
const title = rawTitle !== void 0 && rawTitle !== null ? resolveTitleToString(rawTitle) : collection.singularName ?? collection.name;
|
|
21864
|
+
const modified = formex.dirty;
|
|
22528
21865
|
useEffect(() => {
|
|
22529
21866
|
if (!autoSave) onValuesModified?.(modified, formex.values);
|
|
22530
21867
|
}, [formex.dirty]);
|
|
22531
|
-
const
|
|
22532
|
-
const uniqueFieldValidator =
|
|
22533
|
-
|
|
22534
|
-
|
|
22535
|
-
|
|
22536
|
-
limit: 2
|
|
22537
|
-
});
|
|
22538
|
-
return (entityId ? data.filter((e) => e.id !== entityId) : data).length === 0;
|
|
22539
|
-
} catch (e) {
|
|
22540
|
-
console.error("Error checking unique field", e);
|
|
22541
|
-
return true;
|
|
22542
|
-
}
|
|
22543
|
-
}, [
|
|
22544
|
-
dataClient,
|
|
22545
|
-
path,
|
|
22546
|
-
entityId
|
|
22547
|
-
]);
|
|
21868
|
+
const defaultUniqueFieldValidator = useCallback(async () => true, []);
|
|
21869
|
+
const uniqueFieldValidator = useMemo(() => {
|
|
21870
|
+
if (uniqueFieldValidatorProp) return uniqueFieldValidatorProp;
|
|
21871
|
+
return defaultUniqueFieldValidator;
|
|
21872
|
+
}, [uniqueFieldValidatorProp, defaultUniqueFieldValidator]);
|
|
22548
21873
|
const validationSchema = useMemo(() => getEntitySchema(entityId, collection.properties, uniqueFieldValidator), [
|
|
22549
21874
|
entityId,
|
|
22550
21875
|
collection.properties,
|
|
@@ -22576,6 +21901,8 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22576
21901
|
modifiedValues: formex.values,
|
|
22577
21902
|
formContext
|
|
22578
21903
|
});
|
|
21904
|
+
const isNewEntity = status === "new" || status === "copy";
|
|
21905
|
+
let firstFocusableIndex = -1;
|
|
22579
21906
|
return /* @__PURE__ */ jsx(FormLayout, { children: formFieldKeys.map((key) => {
|
|
22580
21907
|
const property = collection.properties?.[key];
|
|
22581
21908
|
if (property) {
|
|
@@ -22584,9 +21911,12 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22584
21911
|
const isIdAndAuto = (property.type === "string" || property.type === "number") && "isId" in property && typeof property.isId === "string" && property.isId !== "manual";
|
|
22585
21912
|
const disabled = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.ui?.disabled) || !isNew && "isId" in property && Boolean(property.isId) || isNew && isIdAndAuto;
|
|
22586
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;
|
|
22587
21917
|
return /* @__PURE__ */ jsx(FormEntry, {
|
|
22588
21918
|
propertyKey: key,
|
|
22589
|
-
widthPercentage
|
|
21919
|
+
widthPercentage,
|
|
22590
21920
|
children: /* @__PURE__ */ jsx(PropertyFieldBinding, {
|
|
22591
21921
|
propertyKey: key,
|
|
22592
21922
|
disabled,
|
|
@@ -22596,22 +21926,23 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22596
21926
|
context: formContext,
|
|
22597
21927
|
partOfArray: false,
|
|
22598
21928
|
minimalistView: false,
|
|
22599
|
-
autoFocus:
|
|
21929
|
+
autoFocus: shouldAutoFocus
|
|
22600
21930
|
})
|
|
22601
21931
|
}, `field_${key}`);
|
|
22602
21932
|
}
|
|
22603
21933
|
const additionalField = collection.additionalFields?.find((f) => f.key === key);
|
|
22604
21934
|
if (additionalField && entity) {
|
|
22605
|
-
const
|
|
22606
|
-
if (!
|
|
22607
|
-
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, {
|
|
22608
21939
|
entity,
|
|
22609
|
-
context
|
|
21940
|
+
context: additionalFieldContext
|
|
22610
21941
|
}) : /* @__PURE__ */ jsx("div", {
|
|
22611
21942
|
className: "w-full",
|
|
22612
21943
|
children: additionalField.value?.({
|
|
22613
21944
|
entity,
|
|
22614
|
-
context
|
|
21945
|
+
context: additionalFieldContext
|
|
22615
21946
|
})?.toString()
|
|
22616
21947
|
});
|
|
22617
21948
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -22632,8 +21963,9 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22632
21963
|
}).filter(Boolean) });
|
|
22633
21964
|
};
|
|
22634
21965
|
const formRef = useRef(null);
|
|
21966
|
+
const hasFormErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
|
|
22635
21967
|
const formView = /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
22636
|
-
|
|
21968
|
+
beforeFields,
|
|
22637
21969
|
!Builder && /* @__PURE__ */ jsxs("div", {
|
|
22638
21970
|
className: "w-full flex flex-col items-start my-4 lg:my-6",
|
|
22639
21971
|
children: [
|
|
@@ -22646,7 +21978,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22646
21978
|
color: "warning",
|
|
22647
21979
|
size: "small",
|
|
22648
21980
|
outerClassName: "w-full mb-4 text-xs",
|
|
22649
|
-
children:
|
|
21981
|
+
children: "This entity does not exist yet"
|
|
22650
21982
|
}),
|
|
22651
21983
|
showEntityPath && /* @__PURE__ */ jsx(Alert, {
|
|
22652
21984
|
color: "base",
|
|
@@ -22667,15 +21999,43 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22667
21999
|
initialEntityId && !entity && initialStatus !== "new" && /* @__PURE__ */ jsx(Alert, {
|
|
22668
22000
|
color: "info",
|
|
22669
22001
|
size: "small",
|
|
22670
|
-
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."
|
|
22671
22009
|
}),
|
|
22672
22010
|
formContext && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
|
|
22673
22011
|
className: "mt-12 flex flex-col gap-8",
|
|
22674
22012
|
ref: formRef,
|
|
22675
22013
|
children: [formFields(), /* @__PURE__ */ jsx(ErrorFocus, { containerRef: formRef })]
|
|
22676
22014
|
}) }),
|
|
22677
|
-
|
|
22678
|
-
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
|
+
})
|
|
22679
22039
|
] }) });
|
|
22680
22040
|
useEffect(() => {
|
|
22681
22041
|
if (entityId && onIdChange) onIdChange(entityId);
|
|
@@ -22690,7 +22050,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22690
22050
|
formex,
|
|
22691
22051
|
disabled: actionsDisabled,
|
|
22692
22052
|
status,
|
|
22693
|
-
pluginActions:
|
|
22053
|
+
pluginActions: pluginActionsProp ?? [],
|
|
22694
22054
|
openEntityMode,
|
|
22695
22055
|
showDefaultActions,
|
|
22696
22056
|
navigateBack,
|
|
@@ -22700,7 +22060,11 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22700
22060
|
value: formex,
|
|
22701
22061
|
children: /* @__PURE__ */ jsxs("form", {
|
|
22702
22062
|
onSubmit: formex.handleSubmit,
|
|
22703
|
-
onReset: () =>
|
|
22063
|
+
onReset: (e) => {
|
|
22064
|
+
e.preventDefault();
|
|
22065
|
+
if (formex.dirty) setDiscardDialogOpen(true);
|
|
22066
|
+
else formex.resetForm({ values: baseInitialValues });
|
|
22067
|
+
},
|
|
22704
22068
|
noValidate: true,
|
|
22705
22069
|
className: cls("@container flex-1 flex flex-row w-full overflow-y-auto justify-center", className),
|
|
22706
22070
|
children: [/* @__PURE__ */ jsx("div", {
|
|
@@ -22709,15 +22073,29 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22709
22073
|
children: /* @__PURE__ */ jsxs("div", {
|
|
22710
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"),
|
|
22711
22075
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
22712
|
-
className: "flex flex-row gap-4
|
|
22713
|
-
children: [manualApplyLocalChanges && hasLocalChanges && /* @__PURE__ */ jsx(LocalChangesMenu, {
|
|
22714
|
-
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,
|
|
22715
22079
|
properties: collection.properties,
|
|
22716
22080
|
cachedData: localChangesData,
|
|
22717
22081
|
formex,
|
|
22718
|
-
onClearLocalChanges: () =>
|
|
22719
|
-
|
|
22720
|
-
|
|
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",
|
|
22721
22099
|
children: /* @__PURE__ */ jsx(Chip, {
|
|
22722
22100
|
size: "small",
|
|
22723
22101
|
className: "py-1",
|
|
@@ -22725,7 +22103,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22725
22103
|
children: /* @__PURE__ */ jsx(PencilIcon, { size: iconSize.smallest })
|
|
22726
22104
|
})
|
|
22727
22105
|
}) : /* @__PURE__ */ jsx(Tooltip, {
|
|
22728
|
-
title:
|
|
22106
|
+
title: "Form is in sync",
|
|
22729
22107
|
children: /* @__PURE__ */ jsx(Chip, {
|
|
22730
22108
|
size: "small",
|
|
22731
22109
|
className: "py-1",
|
|
@@ -22738,40 +22116,401 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
22738
22116
|
})
|
|
22739
22117
|
});
|
|
22740
22118
|
}
|
|
22741
|
-
function
|
|
22742
|
-
|
|
22743
|
-
|
|
22744
|
-
|
|
22745
|
-
|
|
22746
|
-
|
|
22747
|
-
|
|
22748
|
-
|
|
22749
|
-
|
|
22750
|
-
|
|
22751
|
-
|
|
22752
|
-
|
|
22753
|
-
|
|
22754
|
-
|
|
22755
|
-
|
|
22756
|
-
|
|
22757
|
-
|
|
22758
|
-
|
|
22759
|
-
|
|
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;
|
|
22760
22413
|
}
|
|
22414
|
+
if (property.type === "date" && property.mode) base.mode = property.mode;
|
|
22415
|
+
if (property.ui?.Preview) {}
|
|
22416
|
+
return base;
|
|
22761
22417
|
}
|
|
22762
|
-
|
|
22763
|
-
|
|
22764
|
-
|
|
22765
|
-
|
|
22766
|
-
|
|
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;
|
|
22767
22436
|
}
|
|
22768
|
-
|
|
22437
|
+
const result = {};
|
|
22438
|
+
for (const [key, value] of Object.entries(enumValues)) result[key] = typeof value === "string" ? value : normalizeEnumValueConfig(value);
|
|
22439
|
+
return result;
|
|
22769
22440
|
}
|
|
22770
|
-
function
|
|
22771
|
-
|
|
22772
|
-
|
|
22773
|
-
|
|
22774
|
-
|
|
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
|
+
};
|
|
22775
22514
|
}
|
|
22776
22515
|
//#endregion
|
|
22777
22516
|
//#region src/data_import/import/ImportCollectionAction.tsx
|
|
@@ -23605,6 +23344,571 @@ var export_exports = /* @__PURE__ */ __exportAll({
|
|
|
23605
23344
|
*/
|
|
23606
23345
|
var useCollectionsConfigController = () => useContext(ConfigControllerContext);
|
|
23607
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
|
|
23608
23912
|
//#region src/collection_editor/validateCollectionJson.ts
|
|
23609
23913
|
/**
|
|
23610
23914
|
* Valid dataType values for properties
|
|
@@ -24005,6 +24309,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
|
|
|
24005
24309
|
return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
|
|
24006
24310
|
}
|
|
24007
24311
|
//#endregion
|
|
24008
|
-
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 };
|
|
24009
24313
|
|
|
24010
|
-
//# sourceMappingURL=src-
|
|
24314
|
+
//# sourceMappingURL=src-DOX2nOyh.js.map
|