@rebasepro/admin 0.9.0 → 0.9.1-canary.0de22e0
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 -7
- package/dist/{CollectionEditorDialog-B-7uW3iJ.js → CollectionEditorDialog-D7odTn0N.js} +150 -36
- package/dist/CollectionEditorDialog-D7odTn0N.js.map +1 -0
- package/dist/{PropertyEditView-7KMTcrqi.js → PropertyEditView-i51tFOfM.js} +6 -6
- package/dist/PropertyEditView-i51tFOfM.js.map +1 -0
- package/dist/{RouterCollectionsStudioView-epqIbZrJ.js → RouterCollectionsStudioView-B9AjTCjT.js} +5 -5
- package/dist/RouterCollectionsStudioView-B9AjTCjT.js.map +1 -0
- package/dist/collection_editor/api/generateCollectionApi.d.ts +4 -4
- package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +1 -1
- package/dist/collection_editor/ui/collection_editor/SwitchControl.d.ts +1 -1
- package/dist/collection_editor/useSafeSnackbarController.d.ts +1 -1
- package/dist/collection_editor_ui.js +5 -5
- package/dist/collection_editor_ui.js.map +1 -1
- package/dist/components/CollectionTableBinding/CollectionTableBindingProps.d.ts +1 -1
- package/dist/components/CollectionTableBinding/internal/EntityTableCell.d.ts +1 -1
- package/dist/components/CollectionTableBinding/internal/popup_field/PopupFormField.d.ts +1 -1
- package/dist/components/CollectionTableBinding/table_bindings.d.ts +1 -1
- package/dist/components/CollectionViewBinding/hooks/useCollectionInlineEditor.d.ts +1 -1
- package/dist/components/{RebaseCMS.d.ts → RebaseAdmin.d.ts} +2 -2
- package/dist/components/RebaseLayout.d.ts +5 -4
- package/dist/components/RebaseShell.d.ts +1 -0
- package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
- package/dist/components/SelectableTable/SelectableTableContext.d.ts +1 -1
- package/dist/components/SidePanelProvider.d.ts +1 -1
- package/dist/components/field_configs.d.ts +17 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/contexts/BreacrumbsContext.d.ts +1 -1
- package/dist/editor.js +1 -1
- package/dist/editor.js.map +1 -1
- package/dist/export-BM6HPujv.js +518 -0
- package/dist/export-BM6HPujv.js.map +1 -0
- package/dist/form/components/LocalChangesMenu.d.ts +1 -1
- package/dist/form/components/StorageUploadProgress.d.ts +1 -1
- package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +1 -1
- package/dist/{history-7X5bON_n.js → history-CtIPJxvQ.js} +6 -6
- package/dist/history-CtIPJxvQ.js.map +1 -0
- package/dist/hooks/useSelectionDialog.d.ts +1 -1
- package/dist/import-DzQnT03v.js +328 -0
- package/dist/import-DzQnT03v.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3300 -2
- package/dist/index.js.map +1 -0
- package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
- package/dist/preview/util.d.ts +1 -0
- package/dist/types/components/formex.d.ts +1 -1
- package/dist/util/view_mode.d.ts +54 -0
- package/dist/{src-sUZfe29F.js → util-XFwQ9FEc.js} +17554 -21391
- package/dist/util-XFwQ9FEc.js.map +1 -0
- package/package.json +12 -17
- package/src/collection_editor/ConfigControllerProvider.tsx +1 -1
- package/src/collection_editor/api/generateCollectionApi.ts +4 -4
- package/src/collection_editor/ui/AddKanbanColumnAction.tsx +1 -1
- package/src/collection_editor/ui/CollectionViewHeaderAction.tsx +1 -1
- package/src/collection_editor/ui/EditorCollectionAction.tsx +1 -1
- package/src/collection_editor/ui/EditorCollectionActionStart.tsx +1 -1
- package/src/collection_editor/ui/EditorEntityAction.tsx +1 -1
- package/src/collection_editor/ui/HomePageEditorCollectionAction.tsx +1 -1
- package/src/collection_editor/ui/KanbanSetupAction.tsx +1 -1
- package/src/collection_editor/ui/MissingReferenceWidget.tsx +1 -1
- package/src/collection_editor/ui/NewCollectionButton.tsx +1 -1
- package/src/collection_editor/ui/NewCollectionCard.tsx +1 -1
- package/src/collection_editor/ui/PropertyAddColumnComponent.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +3 -3
- package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +3 -3
- package/src/collection_editor/ui/collection_editor/CollectionEditorWelcomeView.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +139 -13
- package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/EntityActionsEditTab.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/EntityActionsSelectDialog.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/EnumForm.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/KanbanConfigSection.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/SubcollectionsEditTab.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/SwitchControl.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/import/CollectionEditorImportMapping.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/properties/BlockPropertyField.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/BooleanPropertyField.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/CommonPropertyFields.tsx +3 -3
- package/src/collection_editor/ui/collection_editor/properties/DateTimePropertyField.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +3 -3
- package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/properties/MarkdownPropertyField.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/properties/NumberPropertyField.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/properties/ReferencePropertyField.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/properties/RelationPropertyField.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/RepeatPropertyField.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/StoragePropertyField.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/properties/StringPropertyField.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/properties/UrlPropertyField.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/VectorPropertyField.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +1 -1
- package/src/collection_editor/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +2 -2
- package/src/collection_editor/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +2 -2
- package/src/collection_editor/useSafeSnackbarController.ts +1 -1
- package/src/components/AdminModeSyncer.tsx +1 -1
- package/src/components/ArrayContainer.tsx +1 -1
- package/src/components/ClearFilterSortButton.tsx +1 -1
- package/src/components/CollectionPanel.tsx +1 -1
- package/src/components/CollectionTableBinding/CollectionRowActions.tsx +3 -3
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +3 -3
- package/src/components/CollectionTableBinding/CollectionTableBindingProps.tsx +1 -1
- package/src/components/CollectionTableBinding/PropertyTableCell.tsx +1 -1
- package/src/components/CollectionTableBinding/column_utils.tsx +1 -1
- package/src/components/CollectionTableBinding/fields/TableMultipleRelationField.tsx +2 -2
- package/src/components/CollectionTableBinding/fields/TableReferenceField.tsx +2 -2
- package/src/components/CollectionTableBinding/fields/TableRelationField.tsx +2 -2
- package/src/components/CollectionTableBinding/fields/TableStorageUpload.tsx +1 -1
- package/src/components/CollectionTableBinding/internal/CollectionTableToolbar.tsx +1 -1
- package/src/components/CollectionTableBinding/internal/EntityTableCell.tsx +2 -2
- package/src/components/CollectionTableBinding/internal/EntityTableCellActions.tsx +1 -1
- package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +4 -4
- package/src/components/CollectionTableBinding/table_bindings.tsx +1 -1
- package/src/components/CollectionViewBinding/BoardCardBinding.tsx +2 -2
- package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +4 -4
- package/src/components/CollectionViewBinding/CollectionCardViewBinding.tsx +1 -1
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +3 -3
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +3 -3
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +77 -127
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +5 -9
- package/src/components/CollectionViewBinding/EntityCardBinding.tsx +3 -3
- package/src/components/CollectionViewBinding/FiltersDialog.tsx +19 -1
- package/src/components/CollectionViewBinding/SplitListView.tsx +114 -63
- package/src/components/CollectionViewBinding/ViewModeToggle.tsx +1 -1
- package/src/components/CollectionViewBinding/hooks/useCollectionInlineEditor.ts +2 -2
- package/src/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.ts +2 -2
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +9 -3
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +1 -1
- package/src/components/DefaultAppBar.tsx +3 -3
- package/src/components/DefaultDrawer.tsx +3 -3
- package/src/components/DeleteEntityDialog.tsx +1 -1
- package/src/components/DetailViewBinding.tsx +7 -5
- package/src/components/DrawerNavigationGroup.tsx +2 -2
- package/src/components/EditFormActions.tsx +6 -6
- package/src/components/EditViewBinding.tsx +9 -8
- package/src/components/EntityPreviewBinding.tsx +5 -4
- package/src/components/EntityViewBinding.tsx +2 -2
- package/src/components/HomePage/ContentHomePage.tsx +6 -5
- package/src/components/HomePage/FavouritesView.tsx +2 -2
- package/src/components/HomePage/NavigationCardBinding.tsx +3 -3
- package/src/components/HomePage/NavigationGroup.tsx +1 -1
- package/src/components/HomePage/RenameGroupDialog.tsx +1 -1
- package/src/components/JsonPreviewBinding.tsx +1 -1
- package/src/components/{RebaseCMS.tsx → RebaseAdmin.tsx} +3 -3
- package/src/components/RebaseAuthGate.tsx +2 -2
- package/src/components/RebaseLayout.tsx +7 -13
- package/src/components/RebaseNavigation.tsx +18 -3
- package/src/components/RebaseRouteDefs.tsx +3 -3
- package/src/components/RebaseShell.tsx +2 -1
- package/src/components/ReferenceTable/SelectionTableBinding.tsx +7 -6
- package/src/components/ReferenceWidget.tsx +1 -1
- package/src/components/RelationSelector.tsx +1 -1
- package/src/components/SearchIconsView.tsx +2 -2
- package/src/components/SelectableTable/SelectableTable.tsx +2 -2
- package/src/components/SelectableTable/SelectableTableContext.tsx +1 -1
- package/src/components/SelectableTable/filters/BooleanFilterField.tsx +1 -1
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +1 -1
- package/src/components/SelectableTable/filters/FilterFieldBinding.tsx +1 -1
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +2 -2
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -1
- package/src/components/SideDialogs.tsx +1 -1
- package/src/components/SidePanelBinding.tsx +47 -9
- package/src/components/SidePanelProvider.tsx +15 -12
- package/src/components/UserSelector.tsx +1 -1
- package/src/components/admin/CreationResultDialog.tsx +6 -3
- package/src/components/app/Scaffold.tsx +6 -2
- package/src/components/common/default_entity_actions.tsx +123 -25
- package/src/components/field_configs.tsx +42 -25
- package/src/components/history/EntityHistoryEntry.tsx +1 -1
- package/src/components/history/EntityHistoryView.tsx +5 -5
- package/src/components/history/LastEditedByIndicator.tsx +2 -2
- package/src/components/index.ts +1 -1
- package/src/contexts/BreacrumbsContext.tsx +1 -1
- package/src/data_export/export/BasicExportAction.tsx +1 -1
- package/src/data_export/export/ExportCollectionAction.tsx +1 -1
- package/src/data_import/components/DataNewPropertiesMapping.tsx +1 -1
- package/src/data_import/components/ImportFileUpload.tsx +1 -1
- package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +3 -5
- package/src/data_import/components/ImportSaveInProgress.tsx +1 -1
- package/src/data_import/import/ImportCollectionAction.tsx +8 -8
- package/src/data_import/utils/data.ts +2 -2
- package/src/data_import/utils/get_properties_mapping.ts +1 -1
- package/src/editor/components/image-bubble.tsx +1 -1
- package/src/editor/components/table-bubble.tsx +1 -1
- package/src/editor/editor.tsx +1 -1
- package/src/editor/selectors/color-selector.tsx +1 -1
- package/src/editor/selectors/link-selector.tsx +1 -1
- package/src/editor/selectors/node-selector.tsx +2 -2
- package/src/editor/selectors/text-buttons.tsx +1 -1
- package/src/form/EntityForm.tsx +8 -5
- package/src/form/EntityFormActions.tsx +1 -1
- package/src/form/EntityFormBinding.tsx +7 -5
- package/src/form/PropertyFieldBinding.tsx +3 -3
- package/src/form/components/ErrorFocus.tsx +1 -1
- package/src/form/components/LocalChangesMenu.tsx +3 -3
- package/src/form/components/StorageItemPreview.tsx +1 -1
- package/src/form/components/StorageUploadProgress.tsx +3 -3
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +3 -2
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -3
- package/src/form/field_bindings/BlockFieldBinding.tsx +4 -3
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +3 -2
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +4 -3
- package/src/form/field_bindings/MapFieldBinding.tsx +3 -2
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +4 -3
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +2 -1
- package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +3 -2
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +2 -1
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +5 -4
- package/src/form/field_bindings/RelationFieldBinding.tsx +3 -2
- package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -2
- package/src/form/field_bindings/SelectFieldBinding.tsx +2 -1
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +5 -4
- package/src/form/field_bindings/SwitchFieldBinding.tsx +2 -1
- package/src/form/field_bindings/TextFieldBinding.tsx +2 -1
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +2 -1
- package/src/form/field_bindings/VectorFieldBinding.tsx +2 -1
- package/src/form/form_utils.ts +1 -1
- package/src/hooks/navigation/useNavigationResolution.ts +19 -4
- package/src/hooks/useBuildSidePanel.tsx +10 -2
- package/src/hooks/useCMSContext.tsx +1 -1
- package/src/hooks/useHistory.ts +1 -1
- package/src/hooks/useResolvedNavigationFrom.tsx +4 -2
- package/src/hooks/useSelectionDialog.tsx +3 -3
- package/src/index.ts +3 -3
- package/src/preview/PropertyPreview.tsx +2 -2
- package/src/preview/components/DatePreview.tsx +1 -1
- package/src/preview/components/EnumValuesChip.tsx +1 -1
- package/src/preview/components/ImagePreview.tsx +2 -2
- package/src/preview/components/ReferencePreview.tsx +3 -3
- package/src/preview/components/RelationPreview.tsx +3 -3
- package/src/preview/components/StorageThumbnail.tsx +1 -1
- package/src/preview/components/UrlComponentPreview.tsx +2 -9
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +1 -1
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +1 -1
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +1 -1
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +1 -1
- package/src/preview/property_previews/NumberPropertyPreview.tsx +1 -1
- package/src/preview/util.ts +9 -0
- package/src/routes/CustomViewRoute.tsx +2 -1
- package/src/routes/RebaseRoute.tsx +21 -26
- package/src/types/components/formex.tsx +1 -1
- package/src/util/navigation_utils.ts +2 -5
- package/src/util/previews.ts +4 -2
- package/src/util/view_mode.ts +102 -0
- package/dist/CollectionEditorDialog-B-7uW3iJ.js.map +0 -1
- package/dist/PropertyEditView-7KMTcrqi.js.map +0 -1
- package/dist/RouterCollectionsStudioView-epqIbZrJ.js.map +0 -1
- package/dist/history-7X5bON_n.js.map +0 -1
- package/dist/src-sUZfe29F.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,2 +1,3300 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
|
|
1
|
+
import { $ as useHistory, $n as renderSkeletonImageThumbnail, $t as SelectableTable, A as VectorFieldBinding, An as SidePanelControllerContext, At as unflattenObject, B as KeyValueFieldBinding, Bn as getPropertiesWithPropertiesOrder, Bt as getCollectionPathsCombinations, C as zodToFormErrors, Cn as ArrayPropertyEnumPreview, Ct as flattenEntry, D as getDefaultFieldId, Dn as ArrayOfReferencesPreview, Dt as ImportSaveInProgress, E as getDefaultFieldConfig, En as RelationPreview, Et as useImportConfig, F as RepeatFieldBinding, Fn as getEntityTitlePropertyKey, Ft as useBreadcrumbsController, G as SelectionTableBinding, Gn as ArrayPropertyPreview, Gt as resolveCollectionPathIds, H as BlockFieldBinding, Hn as getResolvedPropertyInPath, Ht as removeInitialAndTrailingSlashes, I as ReferenceFieldBinding, In as getBracketNotation, It as BreadcrumbsProvider, J as useBuildNavigationStateController, Jn as StorageThumbnail, Jt as resolveEntityAction, K as SideDialogs, Kn as StringPropertyPreview, Kt as resolveOpenEntityMode, L as MultiSelectFieldBinding, Ln as getDefaultPropertiesOrder, Lt as mergeEntityActions, M as SwitchFieldBinding, Mn as CollectionRegistryContext, Mt as PropertyConfigBadge, N as StorageUploadFieldBinding, Nn as useCollectionRegistryController, Nt as SearchIconsView, O as getFieldConfig, On as ReferencePreview, Ot as ImportFileUpload, P as SelectFieldBinding, Pn as getEntityPreviewKeys, Pt as FieldCaption, Q as useBuildCollectionRegistryController, Qn as renderSkeletonIcon, Qt as CollectionTableBinding, R as MarkdownEditorFieldBinding, Rn as getIconForProperty, Rt as addInitialSlash, S as removeEmptyContainers, Sn as ArrayOfStringsPreview, St as convertDataToEntity, T as DEFAULT_FIELD_CONFIGS, Tn as ArrayOfStorageComponentsPreview, Tt as getInferenceType, U as ArrayOfReferencesFieldBinding, Un as isReferenceProperty, Ut as removeInitialSlash, V as DateTimeFieldBinding, Vn as getPropertyInPath, Vt as getLastSegment, W as useSelectionDialog, Wn as isRelationProperty, Wt as removeTrailingSlash, X as useResolvedViews, Xn as SkeletonPropertyComponent, Xt as useSelectionController, Y as useTopLevelNavigation, Yn as StorageThumbnailInternal, Yt as resolveEntityView, Z as useResolvedCollections, Zn as renderSkeletonCaptionText, Zt as VirtualTableInput, _ as EntityFormBinding, _n as BooleanPreview, _t as useCollectionEditorController, a as namespaceToPropertiesPath, an as useNavigationStateController, ar as LabelWithIconAndTooltip, at as EditViewBinding, b as getChanges, bn as MapPropertyPreview, bt as ImportNewPropertyFieldPreview, c as buildCollectionGenerationCallback, cn as SelectableTableContext, cr as FormLayout, ct as deleteEntityAction, d as fromSerializableProperties, dn as ReadOnlyFieldBinding, dt as CreationResultDialog, en as CollectionRowActions, er as renderSkeletonText, et as resolveNavigationFrom, f as fromSerializableProperty, fn as FieldHelperText, ft as DetailViewBinding, g as useCollectionsConfigController, gn as NumberPropertyPreview, gt as CollectionViewActions, h as toSerializableProperty, hn as UserPreview, ht as EntityCardBinding, i as namespaceToPropertiesOrderPath, in as NavigationStateContext, ir as EmptyValue, it as useBuildSidePanel, j as TextFieldBinding, jn as useSidePanel, jt as ArrayContainer, k as getFieldId, kn as EntityPreviewBinding, kt as convertFileToJson, l as validateCollectionJson, ln as ArrayCustomShapedFieldBinding, lr as FormEntry, lt as editEntityAction, m as toSerializableProperties, mn as PropertyPreview, mt as CollectionCardViewBinding, n as getFullIdPath, nn as UrlContext, nr as ImagePreview, nt as buildSidePanelsFromUrl, o as CollectionGenerationApiError, on as useSideDialogsController, or as PropertyIdCopyTooltip, ot as CollectionViewBinding, p as toSerializableCollectionConfig, pn as ArrayOfMapsPreview, pt as EntityViewBinding, q as useBuildUrlController, qn as EnumValuesChip, qt as resolveViewMode, r as idToPropertiesPath, rn as useUrlController, rr as sanitizeUrl, rt as getEntityViewWidth, s as DEFAULT_COLLECTION_GENERATION_ENDPOINT, sn as SideDialogsControllerContext, sr as LabelWithIcon, st as copyEntityAction, t as getFullId, tn as useCMSContext, tr as UrlComponentPreview, tt as useResolvedNavigationFrom, u as fromSerializableCollectionConfig, un as useClearRestoreValue, ut as resetPasswordAction, v as EntityForm, vn as DatePreview, vt as ConfigControllerProvider, w as PropertyFieldBinding, wn as ArrayEnumPreview, wt as processValueMapping, x as getInitialEntityValues, xn as ArrayOneOfPreview, xt as DataNewPropertiesMapping, y as extractTouchedValues, yn as KeyValuePreview, yt as useCollectionEditorDialogsState, z as MapFieldBinding, zn as getIconForWidget, zt as getCollectionBySlugWithin } from "./util-XFwQ9FEc.js";
|
|
2
|
+
import { i as PropertySelectEntry, n as ImportCollectionAction, r as ImportDataPreview } from "./import-DzQnT03v.js";
|
|
3
|
+
import { a as downloadDataAsCsv, c as getEntityJsonExportableData, i as downloadBlob, n as ExportCollectionAction, o as downloadEntitiesExport, r as BasicExportAction, s as getEntityCSVExportableData } from "./export-BM6HPujv.js";
|
|
4
|
+
import React, { Suspense, lazy, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { deepEqual } from "fast-equals";
|
|
6
|
+
import { Avatar, Button, Card, ChevronDownIcon, ChevronsLeftIcon, ChevronsRightIcon, Chip, CircularProgressCenter, Collapse, Container, Dialog, DialogActions, DialogContent, DialogTitle, ErrorBoundary, ExpandablePanel, IconButton, LogOutIcon, Markdown, Menu, MenuIcon, MenuItem, MoonIcon, PencilIcon, SearchBar, SettingsIcon, Sheet, Skeleton, StarIcon, SunIcon, SunMoonIcon, TextField, Tooltip, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
|
|
7
|
+
import { buildRoutedRebaseData, getLastSegment as getLastSegment$1, getNavigationEntriesFromPath, getReferenceFrom, resolveDataSource } from "@rebasepro/common";
|
|
8
|
+
import { CollectionResolverRegistrationContext, CollectionScopeProvider, CustomizationControllerContext, ErrorView, IconForView, LanguageToggle, LoginView, NavigationBlockerProvider, NotFoundPage, RebaseDataContext, RebaseLogo, RebaseRoutes, SchemaDriftBanner, StudioBridgeRegistryProvider, UIReferenceView, UnsavedChangesDialog, UserSettingsView, buildCollapsedDefaults, useAdminModeController, useAnalyticsController, useAuthController, useBridgeRegistration, useBuildLocalConfigurationPersistence, useCollapsedGroups, useComponentOverride, useCustomizationController, useData, useDataSources, useLargeLayout, useModeController, useNavigationBlocker, useRebaseClient, useRebaseContext, useRebaseRegistry, useRebaseRegistryDispatch, useRestoreScroll, useSlot, useTranslation, useUserConfigurationPersistence } from "@rebasepro/app";
|
|
9
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { prettifyIdentifier, toArray } from "@rebasepro/utils";
|
|
11
|
+
import { Link, NavLink, Outlet, Route, useLocation, useNavigate } from "react-router-dom";
|
|
12
|
+
import { DndContext, DragOverlay, KeyboardSensor, MeasuringStrategy, MouseSensor, TouchSensor, closestCenter, closestCorners, getFirstCollision, pointerWithin, rectIntersection, useDndMonitor, useDroppable, useSensor, useSensors } from "@dnd-kit/core";
|
|
13
|
+
import { restrictToVerticalAxis, restrictToWindowEdges } from "@dnd-kit/modifiers";
|
|
14
|
+
import { SortableContext, arrayMove, defaultAnimateLayoutChanges, rectSortingStrategy, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
15
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
16
|
+
import Fuse from "fuse.js";
|
|
17
|
+
//#region src/routes/CustomViewRoute.tsx
|
|
18
|
+
function CustomViewRoute({ view }) {
|
|
19
|
+
const breadcrumbs = useBreadcrumbsController();
|
|
20
|
+
const urlController = useUrlController();
|
|
21
|
+
const breadcrumbsRef = useRef(breadcrumbs);
|
|
22
|
+
breadcrumbsRef.current = breadcrumbs;
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
breadcrumbsRef.current.set({ breadcrumbs: [{
|
|
25
|
+
title: view.name,
|
|
26
|
+
url: urlController.buildAppUrlPath(view.slug)
|
|
27
|
+
}] });
|
|
28
|
+
}, [view.slug, urlController]);
|
|
29
|
+
if (typeof view.view === "function") {
|
|
30
|
+
const ViewComponent = view.view;
|
|
31
|
+
return /* @__PURE__ */ jsx(ViewComponent, {});
|
|
32
|
+
}
|
|
33
|
+
return /* @__PURE__ */ jsx(Fragment, { children: view.view });
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/routes/RebaseRoute.tsx
|
|
37
|
+
function RebaseRoute() {
|
|
38
|
+
const location = useLocation();
|
|
39
|
+
const collectionRegistry = useCollectionRegistryController();
|
|
40
|
+
const urlController = useUrlController();
|
|
41
|
+
const breadcrumbs = useBreadcrumbsController();
|
|
42
|
+
const userConfigPersistence = useUserConfigurationPersistence();
|
|
43
|
+
const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
|
|
44
|
+
const hash = location.hash;
|
|
45
|
+
const isSidePanel = hash.includes("#side");
|
|
46
|
+
const isNew = hash.includes("#new") || hash.includes("#new_side");
|
|
47
|
+
const isCopy = hash.includes("#copy");
|
|
48
|
+
const isFullScreen = hash.includes("#full");
|
|
49
|
+
const pathname = location.pathname;
|
|
50
|
+
const navigationEntries = getNavigationEntriesFromPath({
|
|
51
|
+
path: urlController.urlPathToDataPath(pathname),
|
|
52
|
+
collections: collectionRegistry.collections ?? []
|
|
53
|
+
});
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const isViewingCollection = navigationEntries[navigationEntries.length - 1]?.type === "collection";
|
|
56
|
+
breadcrumbs.set({ breadcrumbs: navigationEntries.map((entry, index) => {
|
|
57
|
+
const isLastEntry = index === navigationEntries.length - 1;
|
|
58
|
+
if (entry.type === "entity") return {
|
|
59
|
+
title: String(entry.entityId),
|
|
60
|
+
url: urlController.buildUrlCollectionPath(entry.path)
|
|
61
|
+
};
|
|
62
|
+
else if (entry.type === "custom_view") return {
|
|
63
|
+
title: String(entry.view.name ?? entry.view.key),
|
|
64
|
+
url: urlController.buildUrlCollectionPath(entry.path)
|
|
65
|
+
};
|
|
66
|
+
else if (entry.type === "collection") {
|
|
67
|
+
const showCount = isLastEntry && isViewingCollection;
|
|
68
|
+
return {
|
|
69
|
+
title: entry.collection.name,
|
|
70
|
+
url: urlController.buildUrlCollectionPath(entry.path),
|
|
71
|
+
id: entry.path,
|
|
72
|
+
...showCount ? { count: null } : {}
|
|
73
|
+
};
|
|
74
|
+
} else throw new Error("Unexpected navigation entry type");
|
|
75
|
+
}) });
|
|
76
|
+
}, [navigationEntries.map((entry) => entry.path).join(",")]);
|
|
77
|
+
if (isNew) return /* @__PURE__ */ jsx(EntityFullScreenRoute, {
|
|
78
|
+
pathname,
|
|
79
|
+
navigationEntries,
|
|
80
|
+
isNew: true,
|
|
81
|
+
isCopy: false
|
|
82
|
+
});
|
|
83
|
+
if (navigationEntries.length === 1 && navigationEntries[0].type === "collection") {
|
|
84
|
+
let collection;
|
|
85
|
+
collection = collectionRegistry.getCollection(navigationEntries[0].id);
|
|
86
|
+
if (!collection) collection = collectionRegistry.getCollection(navigationEntries[0].slug);
|
|
87
|
+
if (!collection) {
|
|
88
|
+
if (!collectionRegistry.initialised) return /* @__PURE__ */ jsx(CircularProgressCenter, {});
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return /* @__PURE__ */ jsx(ResolvedCollectionView, {
|
|
92
|
+
...collection,
|
|
93
|
+
parentCollectionSlugs: [],
|
|
94
|
+
parentEntityIds: [],
|
|
95
|
+
path: collection.slug,
|
|
96
|
+
updateUrl: true,
|
|
97
|
+
Actions: toArray(collection.Actions)
|
|
98
|
+
}, `collection_view_${collection.slug}`);
|
|
99
|
+
}
|
|
100
|
+
if (isSidePanel) {
|
|
101
|
+
if ([...navigationEntries].reverse().find((entry) => entry.type === "collection")) {
|
|
102
|
+
let collection;
|
|
103
|
+
const firstEntry = navigationEntries[0];
|
|
104
|
+
collection = collectionRegistry.getCollection(firstEntry.id);
|
|
105
|
+
if (!collection) collection = collectionRegistry.getCollection(firstEntry.slug);
|
|
106
|
+
if (!collection) {
|
|
107
|
+
if (!collectionRegistry.initialised) return /* @__PURE__ */ jsx(CircularProgressCenter, {});
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
return /* @__PURE__ */ jsx(ResolvedCollectionView, {
|
|
111
|
+
...collection,
|
|
112
|
+
parentCollectionSlugs: [],
|
|
113
|
+
parentEntityIds: [],
|
|
114
|
+
path: collection.slug,
|
|
115
|
+
updateUrl: true,
|
|
116
|
+
Actions: toArray(collection.Actions)
|
|
117
|
+
}, `collection_view_${collection.slug}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const lastEntityEntry = navigationEntries.find((entry) => entry.type === "entity");
|
|
121
|
+
const firstCollectionEntry = navigationEntries[0];
|
|
122
|
+
if (!isFullScreen && !isCopy && firstCollectionEntry?.type === "collection" && lastEntityEntry?.type === "entity" && (navigationEntries.length === 2 || navigationEntries.length === 3)) {
|
|
123
|
+
let collection;
|
|
124
|
+
collection = collectionRegistry.getCollection(firstCollectionEntry.id);
|
|
125
|
+
if (!collection) collection = collectionRegistry.getCollection(firstCollectionEntry.slug);
|
|
126
|
+
const effectiveOpenMode = collection ? resolveOpenEntityMode({
|
|
127
|
+
collection,
|
|
128
|
+
viewMode: resolveViewMode({
|
|
129
|
+
collection,
|
|
130
|
+
search: location.search,
|
|
131
|
+
savedViewMode: userConfigPersistence?.getCollectionConfig(collection.slug)?.defaultViewMode
|
|
132
|
+
})
|
|
133
|
+
}) : void 0;
|
|
134
|
+
if (collection && effectiveOpenMode === "split") {
|
|
135
|
+
let selectedTab;
|
|
136
|
+
if (navigationEntries.length === 3) {
|
|
137
|
+
const thirdEntry = navigationEntries[2];
|
|
138
|
+
if (thirdEntry.type === "collection") selectedTab = thirdEntry.collection.slug;
|
|
139
|
+
else if (thirdEntry.type === "custom_view") selectedTab = thirdEntry.view.key;
|
|
140
|
+
}
|
|
141
|
+
if (!selectedTab && navigationEntries.length === 2 && lastEntityEntry) {
|
|
142
|
+
const entityIdStr = String(lastEntityEntry.entityId);
|
|
143
|
+
const entityIdIdx = pathname.lastIndexOf(`/${entityIdStr}`);
|
|
144
|
+
if (entityIdIdx >= 0) {
|
|
145
|
+
const afterEntity = pathname.substring(entityIdIdx + 1 + entityIdStr.length);
|
|
146
|
+
const trailingSegment = afterEntity.startsWith("/") ? afterEntity.substring(1) : afterEntity;
|
|
147
|
+
if (trailingSegment.length > 0) selectedTab = trailingSegment;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return /* @__PURE__ */ jsx(ResolvedCollectionView, {
|
|
151
|
+
...collection,
|
|
152
|
+
parentCollectionSlugs: [],
|
|
153
|
+
parentEntityIds: [],
|
|
154
|
+
path: collection.slug,
|
|
155
|
+
updateUrl: true,
|
|
156
|
+
selectedEntityId: lastEntityEntry.entityId,
|
|
157
|
+
selectedTab,
|
|
158
|
+
Actions: toArray(collection.Actions)
|
|
159
|
+
}, `collection_view_${collection.slug}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return /* @__PURE__ */ jsx(EntityFullScreenRoute, {
|
|
163
|
+
pathname,
|
|
164
|
+
navigationEntries,
|
|
165
|
+
isNew,
|
|
166
|
+
isCopy
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function getSelectedTabFromUrl(isNew, lastCustomView) {
|
|
170
|
+
if (isNew) return;
|
|
171
|
+
else if (lastCustomView) {
|
|
172
|
+
if (lastCustomView.type === "custom_view") return lastCustomView.view.key;
|
|
173
|
+
else if (lastCustomView.type === "collection") return lastCustomView.id ?? lastCustomView.slug;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function EntityFullScreenRoute({ pathname, navigationEntries, isNew, isCopy }) {
|
|
177
|
+
const collectionRegistry = useCollectionRegistryController();
|
|
178
|
+
const urlController = useUrlController();
|
|
179
|
+
const navigate = useNavigate();
|
|
180
|
+
const location = useLocation();
|
|
181
|
+
const defaultValues = location.state?.defaultValues;
|
|
182
|
+
const hash = location.hash;
|
|
183
|
+
const navigationPath = urlController.urlPathToDataPath(pathname);
|
|
184
|
+
const blocked = useRef(false);
|
|
185
|
+
const lastEntityEntry = [...navigationEntries].reverse().find((entry) => entry.type === "entity");
|
|
186
|
+
const lastCustomView = [...lastEntityEntry ? navigationEntries.slice(navigationEntries.indexOf(lastEntityEntry) + 1) : []].reverse().find((entry) => entry.type === "custom_view" || entry.type === "collection");
|
|
187
|
+
const entityId = lastEntityEntry && "entityId" in lastEntityEntry ? lastEntityEntry.entityId : void 0;
|
|
188
|
+
let urlTab = getSelectedTabFromUrl(isNew, lastCustomView);
|
|
189
|
+
if (!urlTab && entityId && !isNew) {
|
|
190
|
+
const entityIdStr = String(entityId);
|
|
191
|
+
const entityIdIdx = pathname.lastIndexOf(`/${entityIdStr}`);
|
|
192
|
+
if (entityIdIdx >= 0) {
|
|
193
|
+
const afterEntity = pathname.substring(entityIdIdx + 1 + entityIdStr.length);
|
|
194
|
+
const trailingSegment = afterEntity.startsWith("/") ? afterEntity.substring(1) : afterEntity;
|
|
195
|
+
if (trailingSegment.length > 0 && trailingSegment !== "edit") urlTab = trailingSegment;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const [selectedTab, setSelectedTab] = useState(urlTab);
|
|
199
|
+
const parentCollectionSlugs = collectionRegistry.getParentCollectionSlugs(navigationPath);
|
|
200
|
+
const parentEntityIds = collectionRegistry.getParentEntityIds(navigationPath);
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (urlTab !== selectedTab) setSelectedTab(urlTab);
|
|
203
|
+
}, [urlTab]);
|
|
204
|
+
const basePath = !entityId || isNew ? pathname : pathname.substring(0, pathname.lastIndexOf(`/${entityId}`));
|
|
205
|
+
const entityPath = basePath + `/${entityId}`;
|
|
206
|
+
const blocker = useNavigationBlocker(useCallback(({ currentLocation, nextLocation }) => {
|
|
207
|
+
if (nextLocation.pathname.startsWith(entityPath)) return false;
|
|
208
|
+
const nextHash = nextLocation.hash;
|
|
209
|
+
if (nextHash === "#side" || nextHash === "#new_side") return false;
|
|
210
|
+
const currentHash = currentLocation.hash;
|
|
211
|
+
if ((currentHash === "#side" || currentHash === "#new_side") && (nextLocation.pathname === basePath || nextLocation.pathname.startsWith(entityPath))) return false;
|
|
212
|
+
return blocked.current;
|
|
213
|
+
}, [entityPath, basePath]));
|
|
214
|
+
const lastCollectionEntry = [...navigationEntries].reverse().find((entry) => entry.type === "collection");
|
|
215
|
+
if (isNew && !lastCollectionEntry) {
|
|
216
|
+
if (!collectionRegistry.initialised) return /* @__PURE__ */ jsx(CircularProgressCenter, {});
|
|
217
|
+
throw new Error("INTERNAL: No collection found in the navigation");
|
|
218
|
+
}
|
|
219
|
+
if (!isNew && !lastEntityEntry) {
|
|
220
|
+
if (!collectionRegistry.initialised) return /* @__PURE__ */ jsx(CircularProgressCenter, {});
|
|
221
|
+
return /* @__PURE__ */ jsx(NotFoundPage, {});
|
|
222
|
+
}
|
|
223
|
+
const rawCollection = isNew ? lastCollectionEntry && "collection" in lastCollectionEntry ? lastCollectionEntry.collection : void 0 : lastEntityEntry && "parentCollection" in lastEntityEntry ? lastEntityEntry.parentCollection : void 0;
|
|
224
|
+
const collection = collectionRegistry.getCollection(rawCollection.slug) || rawCollection;
|
|
225
|
+
const fullIdPath = isNew ? lastCollectionEntry.slug : lastEntityEntry.slug;
|
|
226
|
+
const collectionPath = urlController.resolveDatabasePathsFrom(fullIdPath);
|
|
227
|
+
const isEditRoute = pathname.endsWith("/edit") || pathname.split("/").pop() === "edit";
|
|
228
|
+
if (collection.defaultEntityAction === "view" && !isNew && !isCopy && entityId && !isEditRoute) return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(DetailViewBinding, {
|
|
229
|
+
entityId,
|
|
230
|
+
collection,
|
|
231
|
+
layout: "full_screen",
|
|
232
|
+
path: collectionPath,
|
|
233
|
+
selectedTab: selectedTab ?? void 0,
|
|
234
|
+
onEditClick: () => {
|
|
235
|
+
navigate(urlController.buildUrlCollectionPath(`${collectionPath}/${entityId}`) + "/edit" + hash);
|
|
236
|
+
},
|
|
237
|
+
onTabChange: (params) => {
|
|
238
|
+
setSelectedTab(params.selectedTab);
|
|
239
|
+
const newSelectedTab = params.selectedTab;
|
|
240
|
+
if (newSelectedTab) navigate(`${basePath}/${entityId}/${newSelectedTab}${hash}`, { replace: true });
|
|
241
|
+
else navigate(`${basePath}/${entityId}${hash}`, { replace: true });
|
|
242
|
+
},
|
|
243
|
+
parentCollectionSlugs,
|
|
244
|
+
parentEntityIds
|
|
245
|
+
}, collection.slug + "_view_" + entityId) });
|
|
246
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(EditViewBinding, {
|
|
247
|
+
entityId: isNew ? void 0 : entityId,
|
|
248
|
+
collection,
|
|
249
|
+
layout: "full_screen",
|
|
250
|
+
path: collectionPath,
|
|
251
|
+
copy: isCopy,
|
|
252
|
+
selectedTab: selectedTab ?? void 0,
|
|
253
|
+
defaultValues: isNew ? defaultValues : void 0,
|
|
254
|
+
onValuesModified: (modified) => blocked.current = modified,
|
|
255
|
+
navigateBack: () => {
|
|
256
|
+
navigate(urlController.buildUrlCollectionPath(`${collectionPath}/${entityId}`) + hash);
|
|
257
|
+
},
|
|
258
|
+
onSaved: (params) => {
|
|
259
|
+
const newSelectedTab = params.selectedTab;
|
|
260
|
+
const newEntityId = params.entityId;
|
|
261
|
+
const savedHash = isNew ? "" : hash;
|
|
262
|
+
if (newSelectedTab) navigate(`${basePath}/${newEntityId}/${newSelectedTab}${savedHash}`, { replace: true });
|
|
263
|
+
else navigate(`${basePath}/${newEntityId}${savedHash}`, { replace: true });
|
|
264
|
+
},
|
|
265
|
+
onTabChange: (params) => {
|
|
266
|
+
setSelectedTab(params.selectedTab);
|
|
267
|
+
if (isNew) return;
|
|
268
|
+
const newSelectedTab = params.selectedTab;
|
|
269
|
+
if (newSelectedTab) navigate(`${basePath}/${entityId}/${newSelectedTab}${hash}`, { replace: true });
|
|
270
|
+
else navigate(`${basePath}/${entityId}${hash}`, { replace: true });
|
|
271
|
+
},
|
|
272
|
+
parentCollectionSlugs,
|
|
273
|
+
parentEntityIds
|
|
274
|
+
}, collection.slug + "_" + (isNew ? "new" : isCopy ? entityId + "_copy" : entityId)), /* @__PURE__ */ jsx(UnsavedChangesDialog, {
|
|
275
|
+
open: blocker?.state === "blocked",
|
|
276
|
+
handleOk: () => blocker?.proceed?.(),
|
|
277
|
+
handleCancel: () => blocker?.reset?.(),
|
|
278
|
+
body: "You have unsaved changes in this entity."
|
|
279
|
+
})] });
|
|
280
|
+
}
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/components/ReferenceWidget.tsx
|
|
283
|
+
/**
|
|
284
|
+
* This field allows selecting reference/s.
|
|
285
|
+
*/
|
|
286
|
+
function ReferenceWidget({ name, multiselect = false, path, disabled, value, onReferenceSelected, onMultipleReferenceSelected, previewProperties, fixedFilter, size, className, includeId, includeEntityLink }) {
|
|
287
|
+
const collectionRegistryController = useCollectionRegistryController();
|
|
288
|
+
const collection = useMemo(() => {
|
|
289
|
+
return collectionRegistryController.getCollection(path);
|
|
290
|
+
}, [path, collectionRegistryController.getCollection]);
|
|
291
|
+
const onSingleEntitySelected = useCallback((entity) => {
|
|
292
|
+
if (disabled) return;
|
|
293
|
+
if (onReferenceSelected) {
|
|
294
|
+
const reference = entity ? getReferenceFrom(entity) : null;
|
|
295
|
+
onReferenceSelected?.({
|
|
296
|
+
reference,
|
|
297
|
+
entity
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}, [disabled, onReferenceSelected]);
|
|
301
|
+
const onMultipleEntitiesSelected = useCallback((entities) => {
|
|
302
|
+
if (disabled) return;
|
|
303
|
+
if (onMultipleReferenceSelected) onMultipleReferenceSelected({
|
|
304
|
+
references: entities ? entities.map((e) => getReferenceFrom(e)) : null,
|
|
305
|
+
entities
|
|
306
|
+
});
|
|
307
|
+
}, [disabled, onReferenceSelected]);
|
|
308
|
+
const referenceDialogController = useSelectionDialog({
|
|
309
|
+
multiselect,
|
|
310
|
+
path,
|
|
311
|
+
collection,
|
|
312
|
+
onSingleEntitySelected,
|
|
313
|
+
onMultipleEntitiesSelected,
|
|
314
|
+
fixedFilter
|
|
315
|
+
});
|
|
316
|
+
useCallback((e) => {
|
|
317
|
+
e.stopPropagation();
|
|
318
|
+
if (multiselect) onMultipleEntitiesSelected([]);
|
|
319
|
+
else onSingleEntitySelected(null);
|
|
320
|
+
}, [onReferenceSelected]);
|
|
321
|
+
let child;
|
|
322
|
+
const onEntryClick = () => {
|
|
323
|
+
if (disabled) return;
|
|
324
|
+
referenceDialogController.open();
|
|
325
|
+
};
|
|
326
|
+
if (Array.isArray(value)) child = /* @__PURE__ */ jsx("div", {
|
|
327
|
+
className: "flex flex-col gap-4",
|
|
328
|
+
children: value.map((ref, index) => {
|
|
329
|
+
return /* @__PURE__ */ jsx(ReferencePreview, {
|
|
330
|
+
onClick: onEntryClick,
|
|
331
|
+
reference: ref,
|
|
332
|
+
disabled,
|
|
333
|
+
previewProperties,
|
|
334
|
+
size,
|
|
335
|
+
includeId,
|
|
336
|
+
includeEntityLink
|
|
337
|
+
}, `reference_preview_${index}`);
|
|
338
|
+
})
|
|
339
|
+
});
|
|
340
|
+
else if (value?.isEntityReference && value?.isEntityReference()) child = /* @__PURE__ */ jsx(ReferencePreview, {
|
|
341
|
+
reference: value,
|
|
342
|
+
onClick: onEntryClick,
|
|
343
|
+
disabled,
|
|
344
|
+
previewProperties,
|
|
345
|
+
size,
|
|
346
|
+
includeId,
|
|
347
|
+
includeEntityLink
|
|
348
|
+
});
|
|
349
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
350
|
+
className: cls("text-sm font-medium", "min-w-80 flex flex-col gap-4", "relative transition-colors duration-200 ease-in rounded-xs font-medium", disabled ? "bg-opacity-50" : "hover:bg-opacity-75", "dark:text-white/50 text-text-primary/50 dark:text-text-primary-dark/50 dark:text-white/50", className),
|
|
351
|
+
children: [child, !value && /* @__PURE__ */ jsx("div", {
|
|
352
|
+
className: "justify-center text-left",
|
|
353
|
+
children: /* @__PURE__ */ jsxs(Button, {
|
|
354
|
+
disabled,
|
|
355
|
+
onClick: onEntryClick,
|
|
356
|
+
children: ["Edit ", name]
|
|
357
|
+
})
|
|
358
|
+
})]
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region src/hooks/useBuildSideDialogsController.tsx
|
|
363
|
+
function useBuildSideDialogsController() {
|
|
364
|
+
const location = useLocation();
|
|
365
|
+
const navigate = useNavigate();
|
|
366
|
+
const locationRef = useRef(location);
|
|
367
|
+
locationRef.current = location;
|
|
368
|
+
const [sidePanels, setSidePanels] = useState([]);
|
|
369
|
+
const sidePanelsRef = useRef(sidePanels);
|
|
370
|
+
const routesStore = useRef({});
|
|
371
|
+
const routesCount = useRef(0);
|
|
372
|
+
const updateSidePanels = useCallback((newPanels) => {
|
|
373
|
+
sidePanelsRef.current = newPanels;
|
|
374
|
+
setSidePanels(newPanels);
|
|
375
|
+
}, []);
|
|
376
|
+
useEffect(() => {
|
|
377
|
+
const newPanels = (location.state?.panels ?? []).map((key) => routesStore.current[key]).filter((p) => Boolean(p));
|
|
378
|
+
if (!deepEqual(sidePanelsRef.current.map((p) => p.key), newPanels.map((p) => p.key))) {
|
|
379
|
+
updateSidePanels(newPanels);
|
|
380
|
+
if (newPanels.length === 0) routesCount.current = 0;
|
|
381
|
+
}
|
|
382
|
+
}, [location]);
|
|
383
|
+
const close = useCallback(() => {
|
|
384
|
+
const currentPanels = sidePanelsRef.current;
|
|
385
|
+
if (currentPanels.length === 0) return;
|
|
386
|
+
const lastSidePanel = currentPanels[currentPanels.length - 1];
|
|
387
|
+
const updatedPanels = [...currentPanels.slice(0, -1)];
|
|
388
|
+
updateSidePanels(updatedPanels);
|
|
389
|
+
if (routesCount.current > 0) {
|
|
390
|
+
if (lastSidePanel.urlPath) navigate(-1);
|
|
391
|
+
routesCount.current--;
|
|
392
|
+
} else if (lastSidePanel.parentUrlPath) {
|
|
393
|
+
const baseLocation = locationRef.current.state?.base_location ?? locationRef.current;
|
|
394
|
+
navigate(lastSidePanel.parentUrlPath, {
|
|
395
|
+
replace: true,
|
|
396
|
+
state: {
|
|
397
|
+
base_location: baseLocation,
|
|
398
|
+
panels: updatedPanels.map((p) => p.key)
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}, [navigate]);
|
|
403
|
+
const open = useCallback((panelProps) => {
|
|
404
|
+
const newPanels = Array.isArray(panelProps) ? panelProps : [panelProps];
|
|
405
|
+
newPanels.forEach((panel) => {
|
|
406
|
+
routesStore.current[panel.key] = panel;
|
|
407
|
+
});
|
|
408
|
+
routesCount.current = routesCount.current + newPanels.length;
|
|
409
|
+
const baseLocation = locationRef.current.state?.base_location ?? locationRef.current;
|
|
410
|
+
const updatedPanels = [...sidePanelsRef.current, ...newPanels];
|
|
411
|
+
updateSidePanels(updatedPanels);
|
|
412
|
+
newPanels.forEach((panel) => {
|
|
413
|
+
if (panel.urlPath) navigate(panel.urlPath, { state: {
|
|
414
|
+
base_location: baseLocation,
|
|
415
|
+
panels: updatedPanels.map((p) => p.key)
|
|
416
|
+
} });
|
|
417
|
+
});
|
|
418
|
+
}, [navigate]);
|
|
419
|
+
const replace = useCallback((panelProps) => {
|
|
420
|
+
const newPanels = Array.isArray(panelProps) ? panelProps : [panelProps];
|
|
421
|
+
newPanels.forEach((panel) => {
|
|
422
|
+
routesStore.current[panel.key] = panel;
|
|
423
|
+
});
|
|
424
|
+
const baseLocation = locationRef.current.state?.base_location ?? locationRef.current;
|
|
425
|
+
const updatedPanels = [...sidePanelsRef.current.slice(0, -newPanels.length), ...newPanels];
|
|
426
|
+
updateSidePanels(updatedPanels);
|
|
427
|
+
newPanels.forEach((panel) => {
|
|
428
|
+
if (panel.urlPath) navigate(panel.urlPath, {
|
|
429
|
+
replace: true,
|
|
430
|
+
state: {
|
|
431
|
+
base_location: baseLocation,
|
|
432
|
+
panels: updatedPanels.map((p) => p.key)
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
}, [navigate]);
|
|
437
|
+
return useMemo(() => ({
|
|
438
|
+
sidePanels,
|
|
439
|
+
setSidePanels: updateSidePanels,
|
|
440
|
+
close,
|
|
441
|
+
open,
|
|
442
|
+
replace
|
|
443
|
+
}), [
|
|
444
|
+
sidePanels,
|
|
445
|
+
updateSidePanels,
|
|
446
|
+
close,
|
|
447
|
+
open,
|
|
448
|
+
replace
|
|
449
|
+
]);
|
|
450
|
+
}
|
|
451
|
+
//#endregion
|
|
452
|
+
//#region src/components/SidePanelProvider.tsx
|
|
453
|
+
/**
|
|
454
|
+
* Provider that builds the SidePanelController and makes it available
|
|
455
|
+
* via the SidePanelControllerContext from @rebasepro/app.
|
|
456
|
+
*
|
|
457
|
+
* After the CMS extraction refactor, `useBuildSidePanel` lives
|
|
458
|
+
* in the CMS package while the context it feeds into lives in core.
|
|
459
|
+
* This provider bridges the two: place it inside the `<Rebase>` tree and
|
|
460
|
+
* above any component that calls `useSidePanel()`.
|
|
461
|
+
*
|
|
462
|
+
* Also auto-registers the side entity controller and breadcrumbs into the
|
|
463
|
+
* self-assembling Studio bridge (when a StudioBridgeRegistryProvider is
|
|
464
|
+
* mounted above in the tree).
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* ```tsx
|
|
468
|
+
* <Rebase ...>
|
|
469
|
+
* {({ loading }) => (
|
|
470
|
+
* <SidePanelProvider>
|
|
471
|
+
* <RebaseRoutes>
|
|
472
|
+
* ...
|
|
473
|
+
* </RebaseRoutes>
|
|
474
|
+
* </SidePanelProvider>
|
|
475
|
+
* )}
|
|
476
|
+
* </Rebase>
|
|
477
|
+
* ```
|
|
478
|
+
*
|
|
479
|
+
* @group Components
|
|
480
|
+
*/
|
|
481
|
+
function SidePanelProvider({ children }) {
|
|
482
|
+
const collectionRegistryController = useCollectionRegistryController();
|
|
483
|
+
const urlController = useUrlController();
|
|
484
|
+
const navigationStateController = useNavigationStateController();
|
|
485
|
+
const sideDialogsController = useBuildSideDialogsController();
|
|
486
|
+
const sidePanelController = useBuildSidePanel(collectionRegistryController, urlController, navigationStateController, sideDialogsController, useAuthController());
|
|
487
|
+
return /* @__PURE__ */ jsx(NavigationBlockerProvider, { children: /* @__PURE__ */ jsx(BreadcrumbsProvider, { children: /* @__PURE__ */ jsx(SideDialogsControllerContext.Provider, {
|
|
488
|
+
value: sideDialogsController,
|
|
489
|
+
children: /* @__PURE__ */ jsxs(SidePanelControllerContext.Provider, {
|
|
490
|
+
value: sidePanelController,
|
|
491
|
+
children: [/* @__PURE__ */ jsx(BridgeAutoRegistrar$1, { sidePanelController }), children]
|
|
492
|
+
})
|
|
493
|
+
}) }) });
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Internal component that auto-registers side entity and breadcrumbs
|
|
497
|
+
* into the Studio bridge. Must be a child of BreadcrumbsProvider.
|
|
498
|
+
*/
|
|
499
|
+
function BridgeAutoRegistrar$1({ sidePanelController }) {
|
|
500
|
+
const breadcrumbs = useBreadcrumbsController();
|
|
501
|
+
useBridgeRegistration("sidePanelController", sidePanelController);
|
|
502
|
+
useBridgeRegistration("breadcrumbs", breadcrumbs);
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region src/components/AdminModeSyncer.tsx
|
|
507
|
+
/**
|
|
508
|
+
* A highly customizable utility component that observes react-router routes and
|
|
509
|
+
* actively shifts the internal Rebase `adminModeController` context to match
|
|
510
|
+
* the user's active window segment (e.g., Content vs Studio mode).
|
|
511
|
+
*
|
|
512
|
+
* Placing this anywhere safely inside the Rebase layout automatically triggers
|
|
513
|
+
* UI mode synchronization.
|
|
514
|
+
*/
|
|
515
|
+
function AdminModeSyncer({ devViews }) {
|
|
516
|
+
const location = useLocation();
|
|
517
|
+
const urlController = useUrlController();
|
|
518
|
+
const adminModeController = useAdminModeController();
|
|
519
|
+
const currentModeRef = React.useRef(adminModeController.mode);
|
|
520
|
+
currentModeRef.current = adminModeController.mode;
|
|
521
|
+
const setModeRef = React.useRef(adminModeController.setMode);
|
|
522
|
+
setModeRef.current = adminModeController.setMode;
|
|
523
|
+
useEffect(() => {
|
|
524
|
+
const path = location.pathname;
|
|
525
|
+
const isContentRoute = urlController.isUrlCollectionPath(path) || path === urlController.basePath;
|
|
526
|
+
const studioHomePath = urlController.basePath === "/" ? "/s" : `${urlController.basePath}/s`;
|
|
527
|
+
if ((path === studioHomePath || path.startsWith(studioHomePath + "/") || devViews?.some((view) => {
|
|
528
|
+
const viewPath = urlController.buildAppUrlPath(view.slug);
|
|
529
|
+
return path.startsWith(`${viewPath}/`) || path === viewPath;
|
|
530
|
+
})) && currentModeRef.current !== "studio") setModeRef.current("studio");
|
|
531
|
+
else if (isContentRoute && currentModeRef.current !== "content") setModeRef.current("content");
|
|
532
|
+
}, [
|
|
533
|
+
location.pathname,
|
|
534
|
+
urlController,
|
|
535
|
+
devViews
|
|
536
|
+
]);
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region src/components/app/useApp.tsx
|
|
541
|
+
var AppContext = React.createContext({
|
|
542
|
+
hasDrawer: false,
|
|
543
|
+
drawerHovered: false,
|
|
544
|
+
drawerOpen: false,
|
|
545
|
+
openDrawer: () => {
|
|
546
|
+
throw new Error("openDrawer not implemented");
|
|
547
|
+
},
|
|
548
|
+
closeDrawer: () => {
|
|
549
|
+
throw new Error("closeDrawer not implemented");
|
|
550
|
+
},
|
|
551
|
+
closeHover: () => {
|
|
552
|
+
throw new Error("closeHover not implemented");
|
|
553
|
+
},
|
|
554
|
+
autoOpenDrawer: false
|
|
555
|
+
});
|
|
556
|
+
function useApp() {
|
|
557
|
+
return React.useContext(AppContext);
|
|
558
|
+
}
|
|
559
|
+
//#endregion
|
|
560
|
+
//#region src/components/DefaultAppBar.tsx
|
|
561
|
+
/**
|
|
562
|
+
* This component renders the main app bar of Rebase.
|
|
563
|
+
* You will likely not need to use this component directly.
|
|
564
|
+
*
|
|
565
|
+
|
|
566
|
+
*/
|
|
567
|
+
var DefaultAppBar = function DefaultAppBar({ title, endAdornment, startAdornment, dropDownActions, includeModeToggle = true, includeLanguageToggle = true, includeUserMenu = true, className, style, user: userProp, logo: logoProp }) {
|
|
568
|
+
const { hasDrawer, drawerOpen, logo: appLogo } = useApp();
|
|
569
|
+
const logo = logoProp ?? appLogo;
|
|
570
|
+
const navigation = useUrlController();
|
|
571
|
+
const breadcrumbs = useBreadcrumbsController();
|
|
572
|
+
const authController = useAuthController();
|
|
573
|
+
useAdminModeController();
|
|
574
|
+
const { mode, setMode } = useModeController();
|
|
575
|
+
const navigate = useNavigate();
|
|
576
|
+
const largeLayout = useLargeLayout();
|
|
577
|
+
const user = userProp ?? authController.user;
|
|
578
|
+
const { t } = useTranslation();
|
|
579
|
+
let avatarComponent;
|
|
580
|
+
if (user) {
|
|
581
|
+
const initial = user?.displayName ? user.displayName[0].toUpperCase() : user?.email ? user.email[0].toUpperCase() : "A";
|
|
582
|
+
avatarComponent = /* @__PURE__ */ jsx(Avatar, {
|
|
583
|
+
src: user.photoURL ?? void 0,
|
|
584
|
+
children: initial
|
|
585
|
+
});
|
|
586
|
+
} else if (user === void 0 || authController.initialLoading) avatarComponent = /* @__PURE__ */ jsx("div", {
|
|
587
|
+
className: "p-1 flex justify-center",
|
|
588
|
+
children: /* @__PURE__ */ jsx(Skeleton, { className: "w-10 h-10 rounded-full" })
|
|
589
|
+
});
|
|
590
|
+
else avatarComponent = null;
|
|
591
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
592
|
+
style,
|
|
593
|
+
role: "banner",
|
|
594
|
+
className: cls("w-full h-14 transition-all ease-in duration-75 absolute top-0 max-w-full overflow-x-auto no-scrollbar", "flex flex-row gap-2 px-4 items-center", "backdrop-blur-sm bg-surface-50/95 dark:bg-surface-900/80", {
|
|
595
|
+
"pl-[19rem]": drawerOpen && largeLayout,
|
|
596
|
+
"pl-24": hasDrawer && !(drawerOpen && largeLayout),
|
|
597
|
+
"z-10": largeLayout,
|
|
598
|
+
"duration-100": drawerOpen && largeLayout
|
|
599
|
+
}, className),
|
|
600
|
+
children: [
|
|
601
|
+
navigation && (!hasDrawer || title) && /* @__PURE__ */ jsx("div", {
|
|
602
|
+
className: "mr-2 hidden lg:block",
|
|
603
|
+
children: /* @__PURE__ */ jsx(Link, {
|
|
604
|
+
className: "visited:text-inherit dark:visited:text-inherit block",
|
|
605
|
+
to: navigation?.basePath ?? "/",
|
|
606
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
607
|
+
className: "flex flex-row gap-4",
|
|
608
|
+
children: [!hasDrawer && (logo ? /* @__PURE__ */ jsx("img", {
|
|
609
|
+
src: logo,
|
|
610
|
+
alt: "Logo",
|
|
611
|
+
className: cls("w-[32px] h-[32px] object-contain")
|
|
612
|
+
}) : /* @__PURE__ */ jsx(RebaseLogo, {
|
|
613
|
+
width: "32px",
|
|
614
|
+
height: "32px"
|
|
615
|
+
})), typeof title === "string" ? /* @__PURE__ */ jsx(Typography, {
|
|
616
|
+
variant: "subtitle1",
|
|
617
|
+
noWrap: true,
|
|
618
|
+
children: title
|
|
619
|
+
}) : title]
|
|
620
|
+
})
|
|
621
|
+
})
|
|
622
|
+
}),
|
|
623
|
+
/* @__PURE__ */ jsx("div", {
|
|
624
|
+
className: "mr-8 hidden lg:block",
|
|
625
|
+
children: /* @__PURE__ */ jsx("nav", {
|
|
626
|
+
"aria-label": "Breadcrumb",
|
|
627
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
628
|
+
className: "flex flex-row gap-2 items-center",
|
|
629
|
+
children: breadcrumbs.breadcrumbs.map((breadcrumb, index) => {
|
|
630
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [index > 0 && /* @__PURE__ */ jsx(Typography, {
|
|
631
|
+
variant: "caption",
|
|
632
|
+
color: "secondary",
|
|
633
|
+
children: "/"
|
|
634
|
+
}), /* @__PURE__ */ jsx(Link, {
|
|
635
|
+
className: "visited:text-inherit dark:visited:text-inherit block",
|
|
636
|
+
to: breadcrumb.url,
|
|
637
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
638
|
+
className: "flex flex-row items-center gap-2 whitespace-nowrap",
|
|
639
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
640
|
+
variant: "body2",
|
|
641
|
+
children: breadcrumb.title
|
|
642
|
+
}), breadcrumb.count !== void 0 && (breadcrumb.count !== null ? /* @__PURE__ */ jsx("span", {
|
|
643
|
+
className: "text-xs text-surface-accent-500 dark:text-surface-accent-400 bg-surface-100 dark:bg-surface-800 px-1 py-0 rounded",
|
|
644
|
+
children: breadcrumb.count
|
|
645
|
+
}) : /* @__PURE__ */ jsx(Skeleton, { className: "w-8 h-4 rounded-md" }))]
|
|
646
|
+
})
|
|
647
|
+
}, index)] }, breadcrumb.url + "_" + index);
|
|
648
|
+
})
|
|
649
|
+
})
|
|
650
|
+
})
|
|
651
|
+
}),
|
|
652
|
+
startAdornment,
|
|
653
|
+
/* @__PURE__ */ jsx("div", { className: "grow" }),
|
|
654
|
+
endAdornment && /* @__PURE__ */ jsx(ErrorBoundary, { children: endAdornment }),
|
|
655
|
+
includeLanguageToggle && /* @__PURE__ */ jsx(LanguageToggle, {}),
|
|
656
|
+
includeModeToggle && /* @__PURE__ */ jsxs(Menu, {
|
|
657
|
+
trigger: /* @__PURE__ */ jsx(IconButton, {
|
|
658
|
+
color: "inherit",
|
|
659
|
+
"aria-label": "Toggle theme",
|
|
660
|
+
children: mode === "dark" ? /* @__PURE__ */ jsx(MoonIcon, { size: iconSize.small }) : /* @__PURE__ */ jsx(SunIcon, { size: iconSize.small })
|
|
661
|
+
}),
|
|
662
|
+
children: [
|
|
663
|
+
/* @__PURE__ */ jsxs(MenuItem, {
|
|
664
|
+
onClick: () => setMode("dark"),
|
|
665
|
+
children: [
|
|
666
|
+
/* @__PURE__ */ jsx(MoonIcon, { size: iconSize.smallest }),
|
|
667
|
+
" ",
|
|
668
|
+
t("dark_mode")
|
|
669
|
+
]
|
|
670
|
+
}),
|
|
671
|
+
/* @__PURE__ */ jsxs(MenuItem, {
|
|
672
|
+
onClick: () => setMode("light"),
|
|
673
|
+
children: [
|
|
674
|
+
/* @__PURE__ */ jsx(SunIcon, { size: iconSize.smallest }),
|
|
675
|
+
" ",
|
|
676
|
+
t("light_mode"),
|
|
677
|
+
" "
|
|
678
|
+
]
|
|
679
|
+
}),
|
|
680
|
+
/* @__PURE__ */ jsxs(MenuItem, {
|
|
681
|
+
onClick: () => setMode("system"),
|
|
682
|
+
children: [
|
|
683
|
+
" ",
|
|
684
|
+
/* @__PURE__ */ jsx(SunMoonIcon, { size: iconSize.smallest }),
|
|
685
|
+
t("system_mode")
|
|
686
|
+
]
|
|
687
|
+
})
|
|
688
|
+
]
|
|
689
|
+
}),
|
|
690
|
+
includeUserMenu && /* @__PURE__ */ jsxs(Menu, {
|
|
691
|
+
trigger: /* @__PURE__ */ jsx("div", {
|
|
692
|
+
"aria-label": "User menu",
|
|
693
|
+
role: "button",
|
|
694
|
+
children: avatarComponent
|
|
695
|
+
}),
|
|
696
|
+
children: [
|
|
697
|
+
user && /* @__PURE__ */ jsxs("div", {
|
|
698
|
+
className: "px-4 py-2 mb-2",
|
|
699
|
+
children: [user.displayName && /* @__PURE__ */ jsx(Typography, {
|
|
700
|
+
variant: "body1",
|
|
701
|
+
color: "secondary",
|
|
702
|
+
children: user.displayName
|
|
703
|
+
}), user.email && /* @__PURE__ */ jsx(Typography, {
|
|
704
|
+
variant: "body2",
|
|
705
|
+
color: "secondary",
|
|
706
|
+
children: user.email
|
|
707
|
+
})]
|
|
708
|
+
}),
|
|
709
|
+
dropDownActions,
|
|
710
|
+
!dropDownActions && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(MenuItem, {
|
|
711
|
+
onClick: () => navigate("/settings"),
|
|
712
|
+
children: [/* @__PURE__ */ jsx(SettingsIcon, {}), t("account_settings")]
|
|
713
|
+
}), /* @__PURE__ */ jsxs(MenuItem, {
|
|
714
|
+
onClick: async () => {
|
|
715
|
+
await authController.signOut();
|
|
716
|
+
navigate("/");
|
|
717
|
+
},
|
|
718
|
+
children: [/* @__PURE__ */ jsx(LogOutIcon, {}), t("log_out")]
|
|
719
|
+
})] })
|
|
720
|
+
]
|
|
721
|
+
})
|
|
722
|
+
]
|
|
723
|
+
});
|
|
724
|
+
};
|
|
725
|
+
//#endregion
|
|
726
|
+
//#region src/components/app/AppBar.tsx
|
|
727
|
+
/**
|
|
728
|
+
* This component renders the main app bar of Rebase.
|
|
729
|
+
*/
|
|
730
|
+
function AppBar({ children, ...props }) {
|
|
731
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children ?? /* @__PURE__ */ jsx(DefaultAppBar, { ...props }) });
|
|
732
|
+
}
|
|
733
|
+
AppBar.componentType = "AppBar";
|
|
734
|
+
//#endregion
|
|
735
|
+
//#region src/components/DrawerNavigationItem.tsx
|
|
736
|
+
function DrawerNavigationItem({ name, icon, drawerOpen, adminMenuOpen, tooltipsOpen, url, onClick }) {
|
|
737
|
+
const listItem = /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(NavLink, {
|
|
738
|
+
onClick,
|
|
739
|
+
style: {
|
|
740
|
+
width: "100%",
|
|
741
|
+
transition: drawerOpen ? "width 150ms ease-in" : void 0
|
|
742
|
+
},
|
|
743
|
+
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" : ""),
|
|
744
|
+
to: url,
|
|
745
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
746
|
+
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",
|
|
747
|
+
children: icon
|
|
748
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
749
|
+
className: cls("text-text-primary dark:text-surface-200", drawerOpen ? "opacity-100" : "opacity-0 hidden", "font-inherit truncate space-x-2"),
|
|
750
|
+
children: name
|
|
751
|
+
})]
|
|
752
|
+
}) });
|
|
753
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
754
|
+
open: drawerOpen || adminMenuOpen ? false : tooltipsOpen,
|
|
755
|
+
side: "right",
|
|
756
|
+
title: name,
|
|
757
|
+
children: listItem
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
//#endregion
|
|
761
|
+
//#region src/components/DrawerNavigationGroup.tsx
|
|
762
|
+
/**
|
|
763
|
+
* Shared drawer navigation group component used by both DefaultDrawer and RebaseCloudDrawer.
|
|
764
|
+
* Renders a collapsible group with header and navigation items.
|
|
765
|
+
*/
|
|
766
|
+
function DrawerNavigationGroup({ group, entries, collapsed, onToggleCollapsed, drawerOpen, tooltipsOpen, adminMenuOpen, headerActions, onItemClick, hideHeader }) {
|
|
767
|
+
const { t } = useTranslation();
|
|
768
|
+
const ResolvedDrawerNavigationItem = useComponentOverride("Shell.DrawerNavigationItem", DrawerNavigationItem);
|
|
769
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
770
|
+
className: "my-2 mx-2 flex flex-col",
|
|
771
|
+
children: [!hideHeader && /* @__PURE__ */ jsxs("div", {
|
|
772
|
+
className: cls("pl-3 pr-2 py-0.5 flex flex-row items-center transition-colors", drawerOpen ? "cursor-pointer hover:bg-surface-100 dark:hover:bg-surface-800/40 rounded-lg" : "opacity-0 invisible pointer-events-none"),
|
|
773
|
+
onClick: drawerOpen ? onToggleCollapsed : void 0,
|
|
774
|
+
children: [
|
|
775
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, {
|
|
776
|
+
size: iconSize.small,
|
|
777
|
+
className: cls("text-surface-400 dark:text-surface-400 transition-transform duration-200 mr-1", collapsed ? "-rotate-90" : "rotate-0")
|
|
778
|
+
}),
|
|
779
|
+
/* @__PURE__ */ jsx(Typography, {
|
|
780
|
+
variant: "caption",
|
|
781
|
+
color: "secondary",
|
|
782
|
+
className: "font-semibold text-[11px] uppercase tracking-wider flex-grow line-clamp-1 text-surface-400 dark:text-surface-400",
|
|
783
|
+
children: group || t("views_group")
|
|
784
|
+
}),
|
|
785
|
+
headerActions && /* @__PURE__ */ jsx("div", {
|
|
786
|
+
onClick: (e) => e.stopPropagation(),
|
|
787
|
+
children: headerActions
|
|
788
|
+
})
|
|
789
|
+
]
|
|
790
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
791
|
+
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"),
|
|
792
|
+
children: entries.map((entry) => /* @__PURE__ */ jsx(ResolvedDrawerNavigationItem, {
|
|
793
|
+
icon: /* @__PURE__ */ jsx(IconForView, {
|
|
794
|
+
collectionOrView: entry.collection ?? entry.view,
|
|
795
|
+
size: "small"
|
|
796
|
+
}),
|
|
797
|
+
tooltipsOpen: !collapsed && tooltipsOpen,
|
|
798
|
+
adminMenuOpen,
|
|
799
|
+
drawerOpen,
|
|
800
|
+
onClick: () => onItemClick?.(entry),
|
|
801
|
+
url: entry.url,
|
|
802
|
+
name: entry.name
|
|
803
|
+
}, entry.id))
|
|
804
|
+
})]
|
|
805
|
+
}, `drawer_group_${group}`);
|
|
806
|
+
}
|
|
807
|
+
//#endregion
|
|
808
|
+
//#region src/components/DefaultDrawer.tsx
|
|
809
|
+
/**
|
|
810
|
+
* Default drawer used in the CMS.
|
|
811
|
+
*
|
|
812
|
+
* When no `children` are provided, renders the full CMS navigation
|
|
813
|
+
* (collection groups, mode switch, plugin slots).
|
|
814
|
+
*
|
|
815
|
+
* When `children` **are** provided, renders the shared drawer shell
|
|
816
|
+
* (logo, scrollable area, footer actions, collapse toggle) with the
|
|
817
|
+
* custom content injected in the scrollable area. This lets consumers
|
|
818
|
+
* like the SaaS dashboard reuse the identical structural layout while
|
|
819
|
+
* supplying their own navigation items.
|
|
820
|
+
*
|
|
821
|
+
* @group Core
|
|
822
|
+
*/
|
|
823
|
+
function DefaultDrawer({ title, logo, logoDestination, children, footerActions, className, style }) {
|
|
824
|
+
const { drawerHovered, drawerOpen, openDrawer, closeDrawer, logo: appLogo } = useApp();
|
|
825
|
+
const resolvedLogo = logo ?? appLogo;
|
|
826
|
+
const scrollRef = React.useRef(null);
|
|
827
|
+
const [scrolled, setScrolled] = React.useState(false);
|
|
828
|
+
const handleScroll = () => {
|
|
829
|
+
if (scrollRef.current) setScrolled(scrollRef.current.scrollTop > 0);
|
|
830
|
+
};
|
|
831
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
832
|
+
role: "navigation",
|
|
833
|
+
"aria-label": "Main navigation",
|
|
834
|
+
className: cls("flex flex-col h-full relative grow w-full", className),
|
|
835
|
+
style,
|
|
836
|
+
children: [
|
|
837
|
+
/* @__PURE__ */ jsx(DrawerLogo, {
|
|
838
|
+
logo: resolvedLogo,
|
|
839
|
+
title,
|
|
840
|
+
logoDestination,
|
|
841
|
+
drawerOpen,
|
|
842
|
+
drawerHovered
|
|
843
|
+
}),
|
|
844
|
+
/* @__PURE__ */ jsx("div", {
|
|
845
|
+
ref: scrollRef,
|
|
846
|
+
onScroll: handleScroll,
|
|
847
|
+
className: "flex-grow min-h-0 overflow-y-auto overflow-x-hidden no-scrollbar px-2",
|
|
848
|
+
style: { maskImage: scrolled ? "linear-gradient(to bottom, transparent 0, black 20px, black calc(100% - 20px), transparent 100%)" : "linear-gradient(to bottom, black 0, black calc(100% - 20px), transparent 100%)" },
|
|
849
|
+
children: children ?? /* @__PURE__ */ jsx(CMSNavigationContent, {})
|
|
850
|
+
}),
|
|
851
|
+
footerActions !== null && (footerActions !== void 0 ? footerActions : /* @__PURE__ */ jsx(DrawerFooterActions, {
|
|
852
|
+
drawerOpen,
|
|
853
|
+
drawerHovered
|
|
854
|
+
})),
|
|
855
|
+
/* @__PURE__ */ jsx(DrawerToggle, {
|
|
856
|
+
drawerOpen,
|
|
857
|
+
drawerHovered,
|
|
858
|
+
openDrawer,
|
|
859
|
+
closeDrawer
|
|
860
|
+
})
|
|
861
|
+
]
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Default CMS navigation content — rendered inside DefaultDrawer when no
|
|
866
|
+
* custom `children` are provided. Contains the mode switch, navigation
|
|
867
|
+
* groups, and header / footer plugin slots.
|
|
868
|
+
*/
|
|
869
|
+
function CMSNavigationContent() {
|
|
870
|
+
const { drawerHovered, drawerOpen, closeDrawer, closeHover } = useApp();
|
|
871
|
+
const [adminMenuOpen, setAdminMenuOpen] = React.useState(false);
|
|
872
|
+
const analyticsController = useAnalyticsController();
|
|
873
|
+
const navigationState = useNavigationStateController();
|
|
874
|
+
const context = useRebaseContext();
|
|
875
|
+
const tooltipsOpen = drawerHovered && !drawerOpen && !adminMenuOpen;
|
|
876
|
+
const largeLayout = useLargeLayout();
|
|
877
|
+
const adminModeController = useAdminModeController();
|
|
878
|
+
const registry = useRebaseRegistry();
|
|
879
|
+
const ResolvedDrawerNavigationGroup = useComponentOverride("Shell.DrawerNavigationGroup", DrawerNavigationGroup);
|
|
880
|
+
const allNavigationEntries = navigationState.topLevelNavigation?.navigationEntries ?? [];
|
|
881
|
+
const studioViewSlugs = useMemo(() => {
|
|
882
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
883
|
+
(registry.studioConfig?.devViews ?? []).forEach((v) => slugs.add(v.slug));
|
|
884
|
+
if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
|
|
885
|
+
return slugs;
|
|
886
|
+
}, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
|
|
887
|
+
const filteredEntries = adminModeController.mode === "studio" ? allNavigationEntries.filter((e) => e.type === "view" && studioViewSlugs.has(e.slug) || e.type === "admin") : allNavigationEntries.filter((e) => e.type !== "view" || !studioViewSlugs.has(e.slug));
|
|
888
|
+
const entryGroups = new Set(filteredEntries.map((e) => e.group).filter(Boolean));
|
|
889
|
+
const orderedGroups = navigationState.topLevelNavigation?.groups ?? [];
|
|
890
|
+
const groupsToRender = [...orderedGroups.filter((g) => entryGroups.has(g)), ...[...entryGroups].filter((g) => !orderedGroups.includes(g))];
|
|
891
|
+
const { isGroupCollapsed, toggleGroupCollapsed } = useCollapsedGroups(groupsToRender, "drawer", useMemo(() => buildCollapsedDefaults(registry.cmsConfig?.navigationGroupMappings, "drawer"), [registry.cmsConfig?.navigationGroupMappings]));
|
|
892
|
+
const headerSlot = useSlot("navigation.header", {
|
|
893
|
+
drawerOpen,
|
|
894
|
+
drawerHovered,
|
|
895
|
+
context
|
|
896
|
+
});
|
|
897
|
+
const footerSlot = useSlot("navigation.footer", {
|
|
898
|
+
drawerOpen,
|
|
899
|
+
drawerHovered,
|
|
900
|
+
context
|
|
901
|
+
});
|
|
902
|
+
if (!navigationState.topLevelNavigation) return null;
|
|
903
|
+
const onItemClick = (view) => {
|
|
904
|
+
const eventName = view.type === "collection" ? "drawer_navigate_to_collection" : view.type === "view" ? "drawer_navigate_to_view" : "unmapped_event";
|
|
905
|
+
analyticsController.onAnalyticsEvent?.(eventName, { url: view.url });
|
|
906
|
+
if (!largeLayout) closeDrawer();
|
|
907
|
+
else if (!drawerOpen) closeHover();
|
|
908
|
+
};
|
|
909
|
+
const drawerVisuallyOpen = drawerOpen || drawerHovered;
|
|
910
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
911
|
+
registry.studioConfig && /* @__PURE__ */ jsx(DrawerModeSwitch, {
|
|
912
|
+
drawerOpen,
|
|
913
|
+
drawerHovered
|
|
914
|
+
}),
|
|
915
|
+
headerSlot,
|
|
916
|
+
groupsToRender.map((group) => {
|
|
917
|
+
return /* @__PURE__ */ jsx(ResolvedDrawerNavigationGroup, {
|
|
918
|
+
group,
|
|
919
|
+
entries: filteredEntries.filter((e) => e.group === group),
|
|
920
|
+
collapsed: isGroupCollapsed(group),
|
|
921
|
+
onToggleCollapsed: () => toggleGroupCollapsed(group),
|
|
922
|
+
drawerOpen: drawerVisuallyOpen,
|
|
923
|
+
tooltipsOpen,
|
|
924
|
+
adminMenuOpen,
|
|
925
|
+
onItemClick
|
|
926
|
+
}, `drawer_group_${group}`);
|
|
927
|
+
}),
|
|
928
|
+
footerSlot
|
|
929
|
+
] });
|
|
930
|
+
}
|
|
931
|
+
function DrawerLogo({ logo, title, logoDestination = "/", drawerOpen, drawerHovered }) {
|
|
932
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
933
|
+
className: "flex flex-row items-center shrink-0 pt-4 pb-0 px-2",
|
|
934
|
+
children: [/* @__PURE__ */ jsx(Link, {
|
|
935
|
+
className: "shrink-0 flex items-center justify-center w-[56px] h-[40px]",
|
|
936
|
+
to: logoDestination,
|
|
937
|
+
children: logo ? /* @__PURE__ */ jsx("img", {
|
|
938
|
+
src: logo,
|
|
939
|
+
alt: "Logo",
|
|
940
|
+
className: "w-[28px] h-[28px] object-contain"
|
|
941
|
+
}) : /* @__PURE__ */ jsx(RebaseLogo, {
|
|
942
|
+
width: "28px",
|
|
943
|
+
height: "28px"
|
|
944
|
+
})
|
|
945
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
946
|
+
className: cls("flex flex-row items-center overflow-hidden transition-all duration-200 ease-in-out", drawerOpen || drawerHovered && !drawerOpen ? "opacity-100 w-full ml-1" : "opacity-0 w-0 ml-0"),
|
|
947
|
+
children: title && /* @__PURE__ */ jsx(Link, {
|
|
948
|
+
className: "visited:text-inherit dark:visited:text-inherit block truncate",
|
|
949
|
+
to: logoDestination,
|
|
950
|
+
children: typeof title === "string" ? /* @__PURE__ */ jsx(Typography, {
|
|
951
|
+
variant: "subtitle1",
|
|
952
|
+
noWrap: true,
|
|
953
|
+
className: "truncate",
|
|
954
|
+
children: title
|
|
955
|
+
}) : title
|
|
956
|
+
})
|
|
957
|
+
})]
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Toggle button at the bottom of the drawer.
|
|
962
|
+
* Uses double-chevron icons to indicate collapse/expand direction.
|
|
963
|
+
*/
|
|
964
|
+
function DrawerToggle({ drawerOpen, drawerHovered, openDrawer, closeDrawer }) {
|
|
965
|
+
const isExpanded = drawerOpen;
|
|
966
|
+
const isHovered = drawerHovered && !drawerOpen;
|
|
967
|
+
const isFloating = isHovered;
|
|
968
|
+
const showFullContent = isExpanded || isHovered;
|
|
969
|
+
const { t } = useTranslation();
|
|
970
|
+
return /* @__PURE__ */ jsx("div", {
|
|
971
|
+
className: "shrink-0 mt-auto px-4 pt-0.5 pb-2",
|
|
972
|
+
children: /* @__PURE__ */ jsx(Tooltip, {
|
|
973
|
+
title: isExpanded ? t("collapse") : t("expand"),
|
|
974
|
+
side: "right",
|
|
975
|
+
sideOffset: 12,
|
|
976
|
+
asChild: true,
|
|
977
|
+
open: isFloating ? false : void 0,
|
|
978
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
979
|
+
className: cls("flex flex-row items-center rounded-lg cursor-pointer", "hover:bg-surface-accent-100 dark:hover:bg-surface-800", "transition-colors duration-150", "py-2"),
|
|
980
|
+
role: "button",
|
|
981
|
+
tabIndex: 0,
|
|
982
|
+
"aria-expanded": isExpanded,
|
|
983
|
+
"aria-label": isExpanded ? t("collapse") : t("expand"),
|
|
984
|
+
onClick: () => isExpanded ? closeDrawer() : openDrawer(),
|
|
985
|
+
onKeyDown: (e) => {
|
|
986
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
987
|
+
e.preventDefault();
|
|
988
|
+
if (isExpanded) closeDrawer();
|
|
989
|
+
else openDrawer();
|
|
990
|
+
}
|
|
991
|
+
},
|
|
992
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
993
|
+
className: "shrink-0 flex items-center justify-center w-[44px] h-[24px] text-surface-500 dark:text-surface-400",
|
|
994
|
+
children: isExpanded ? /* @__PURE__ */ jsx(ChevronsLeftIcon, { size: iconSize.small }) : /* @__PURE__ */ jsx(ChevronsRightIcon, { size: iconSize.small })
|
|
995
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
996
|
+
className: cls("overflow-hidden transition-all duration-200 ease-in-out", showFullContent ? "opacity-100 w-auto" : "opacity-0 w-0"),
|
|
997
|
+
children: /* @__PURE__ */ jsx(Typography, {
|
|
998
|
+
variant: "body2",
|
|
999
|
+
className: "text-surface-500 dark:text-surface-400 select-none whitespace-nowrap",
|
|
1000
|
+
children: isExpanded ? t("collapse") : t("expand")
|
|
1001
|
+
})
|
|
1002
|
+
})]
|
|
1003
|
+
})
|
|
1004
|
+
})
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Segmented Content / Studio switch rendered inside the drawer.
|
|
1009
|
+
* Animates in/out with the drawer open state via opacity + max-height.
|
|
1010
|
+
*/
|
|
1011
|
+
function DrawerModeSwitch({ drawerOpen, drawerHovered }) {
|
|
1012
|
+
const adminModeController = useAdminModeController();
|
|
1013
|
+
const urlController = useUrlController();
|
|
1014
|
+
const navigate = useNavigate();
|
|
1015
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1016
|
+
className: cls("shrink-0 overflow-hidden transition-all duration-200 ease-in-out px-3", drawerOpen || drawerHovered ? "opacity-100 h-7 mt-2 mb-0" : "opacity-0 pointer-events-none h-7 mt-2 mb-0"),
|
|
1017
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1018
|
+
role: "group",
|
|
1019
|
+
"aria-label": "Content mode",
|
|
1020
|
+
className: "flex bg-surface-100 dark:bg-surface-900 rounded-lg p-0.5 border border-surface-200 dark:border-surface-700/60",
|
|
1021
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
1022
|
+
onClick: () => {
|
|
1023
|
+
adminModeController.setMode("content");
|
|
1024
|
+
navigate(urlController.basePath ?? "/");
|
|
1025
|
+
},
|
|
1026
|
+
"aria-pressed": adminModeController.mode === "content",
|
|
1027
|
+
className: cls("flex-1 px-3 py-0.5 text-xs font-semibold rounded-md transition-all text-center", adminModeController.mode === "content" ? "bg-white dark:bg-surface-800 shadow-sm text-primary dark:text-primary-400" : "text-surface-500 hover:text-surface-900 dark:hover:text-white"),
|
|
1028
|
+
children: "Content"
|
|
1029
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
1030
|
+
onClick: () => {
|
|
1031
|
+
adminModeController.setMode("studio");
|
|
1032
|
+
navigate(urlController.basePath === "/" ? "/s" : `${urlController.basePath ?? ""}/s`);
|
|
1033
|
+
},
|
|
1034
|
+
"aria-pressed": adminModeController.mode === "studio",
|
|
1035
|
+
className: cls("flex-1 px-3 py-0.5 text-xs font-semibold rounded-md transition-all text-center", adminModeController.mode === "studio" ? "bg-white dark:bg-surface-800 shadow-sm text-primary dark:text-primary-400" : "text-surface-500 hover:text-surface-900 dark:hover:text-white"),
|
|
1036
|
+
children: "Studio"
|
|
1037
|
+
})]
|
|
1038
|
+
})
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Footer actions rendered at the bottom of the drawer, above the collapse/expand toggle.
|
|
1043
|
+
* Replaces the app bar icons (language, theme, user avatar) with a drawer-native layout
|
|
1044
|
+
* that adapts between collapsed (icons only) and expanded (full labels + user bar) states.
|
|
1045
|
+
*
|
|
1046
|
+
* @group Core
|
|
1047
|
+
*/
|
|
1048
|
+
function DrawerFooterActions({ drawerOpen, drawerHovered, dropDownActions, user: userProp }) {
|
|
1049
|
+
const authController = useAuthController();
|
|
1050
|
+
const { mode, setMode } = useModeController();
|
|
1051
|
+
const navigate = useNavigate();
|
|
1052
|
+
const { t } = useTranslation();
|
|
1053
|
+
const user = userProp ?? authController.user;
|
|
1054
|
+
const showFullContent = drawerOpen || drawerHovered;
|
|
1055
|
+
const portalRef = React.useRef(null);
|
|
1056
|
+
let avatarComponent;
|
|
1057
|
+
if (user) {
|
|
1058
|
+
const initial = user?.displayName ? user.displayName[0].toUpperCase() : user?.email ? user.email[0].toUpperCase() : "A";
|
|
1059
|
+
avatarComponent = /* @__PURE__ */ jsx(Avatar, {
|
|
1060
|
+
src: user.photoURL ?? void 0,
|
|
1061
|
+
className: "w-8 h-8 text-xs",
|
|
1062
|
+
children: initial
|
|
1063
|
+
});
|
|
1064
|
+
} else if (user === void 0 || authController.initialLoading) avatarComponent = /* @__PURE__ */ jsx(Skeleton, { className: "w-8 h-8 rounded-full" });
|
|
1065
|
+
else avatarComponent = null;
|
|
1066
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1067
|
+
className: "shrink-0 pt-2 pb-0",
|
|
1068
|
+
ref: portalRef,
|
|
1069
|
+
children: avatarComponent && /* @__PURE__ */ jsxs("div", {
|
|
1070
|
+
className: "flex items-center px-[16px] py-1",
|
|
1071
|
+
children: [/* @__PURE__ */ jsxs(Menu, {
|
|
1072
|
+
trigger: /* @__PURE__ */ jsx("div", {
|
|
1073
|
+
className: cls("shrink-0 flex items-center justify-center w-[44px] cursor-pointer", "rounded-md py-1", "hover:bg-surface-accent-100 dark:hover:bg-surface-800", "transition-colors duration-150"),
|
|
1074
|
+
role: "button",
|
|
1075
|
+
tabIndex: 0,
|
|
1076
|
+
"aria-label": t("user_menu") || "User menu",
|
|
1077
|
+
children: avatarComponent
|
|
1078
|
+
}),
|
|
1079
|
+
side: "top",
|
|
1080
|
+
align: "start",
|
|
1081
|
+
children: [
|
|
1082
|
+
user && /* @__PURE__ */ jsxs("div", {
|
|
1083
|
+
className: "px-4 py-2 mb-1",
|
|
1084
|
+
children: [user.displayName && /* @__PURE__ */ jsx(Typography, {
|
|
1085
|
+
variant: "body1",
|
|
1086
|
+
color: "secondary",
|
|
1087
|
+
children: user.displayName
|
|
1088
|
+
}), user.email && /* @__PURE__ */ jsx(Typography, {
|
|
1089
|
+
variant: "body2",
|
|
1090
|
+
color: "secondary",
|
|
1091
|
+
children: user.email
|
|
1092
|
+
})]
|
|
1093
|
+
}),
|
|
1094
|
+
dropDownActions,
|
|
1095
|
+
!dropDownActions && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(MenuItem, {
|
|
1096
|
+
onClick: () => navigate("/settings"),
|
|
1097
|
+
children: [/* @__PURE__ */ jsx(SettingsIcon, {}), t("account_settings")]
|
|
1098
|
+
}), /* @__PURE__ */ jsxs(MenuItem, {
|
|
1099
|
+
onClick: async () => {
|
|
1100
|
+
await authController.signOut();
|
|
1101
|
+
navigate("/");
|
|
1102
|
+
},
|
|
1103
|
+
children: [/* @__PURE__ */ jsx(LogOutIcon, {}), t("log_out")]
|
|
1104
|
+
})] })
|
|
1105
|
+
]
|
|
1106
|
+
}), showFullContent && /* @__PURE__ */ jsxs("div", {
|
|
1107
|
+
className: "flex items-center gap-0",
|
|
1108
|
+
children: [/* @__PURE__ */ jsx(LanguageToggle, {}), /* @__PURE__ */ jsxs(Menu, {
|
|
1109
|
+
trigger: /* @__PURE__ */ jsx(IconButton, {
|
|
1110
|
+
color: "inherit",
|
|
1111
|
+
"aria-label": t("toggle_theme") || "Toggle theme",
|
|
1112
|
+
className: "text-surface-500 dark:text-surface-400",
|
|
1113
|
+
children: mode === "dark" ? /* @__PURE__ */ jsx(MoonIcon, { size: iconSize.small }) : mode === "light" ? /* @__PURE__ */ jsx(SunIcon, { size: iconSize.small }) : /* @__PURE__ */ jsx(SunMoonIcon, { size: iconSize.small })
|
|
1114
|
+
}),
|
|
1115
|
+
portalContainer: portalRef.current,
|
|
1116
|
+
side: "top",
|
|
1117
|
+
children: [
|
|
1118
|
+
/* @__PURE__ */ jsxs(MenuItem, {
|
|
1119
|
+
onClick: () => setMode("dark"),
|
|
1120
|
+
children: [
|
|
1121
|
+
/* @__PURE__ */ jsx(MoonIcon, { size: iconSize.smallest }),
|
|
1122
|
+
" ",
|
|
1123
|
+
t("dark_mode")
|
|
1124
|
+
]
|
|
1125
|
+
}),
|
|
1126
|
+
/* @__PURE__ */ jsxs(MenuItem, {
|
|
1127
|
+
onClick: () => setMode("light"),
|
|
1128
|
+
children: [
|
|
1129
|
+
/* @__PURE__ */ jsx(SunIcon, { size: iconSize.smallest }),
|
|
1130
|
+
" ",
|
|
1131
|
+
t("light_mode")
|
|
1132
|
+
]
|
|
1133
|
+
}),
|
|
1134
|
+
/* @__PURE__ */ jsxs(MenuItem, {
|
|
1135
|
+
onClick: () => setMode("system"),
|
|
1136
|
+
children: [
|
|
1137
|
+
/* @__PURE__ */ jsx(SunMoonIcon, { size: iconSize.smallest }),
|
|
1138
|
+
" ",
|
|
1139
|
+
t("system_mode")
|
|
1140
|
+
]
|
|
1141
|
+
})
|
|
1142
|
+
]
|
|
1143
|
+
})]
|
|
1144
|
+
})]
|
|
1145
|
+
})
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
//#endregion
|
|
1149
|
+
//#region src/components/app/Drawer.tsx
|
|
1150
|
+
/**
|
|
1151
|
+
* This component is in charge of rendering the drawer.
|
|
1152
|
+
* If you add this component under your {@link Scaffold}, it will be rendered
|
|
1153
|
+
* as a drawer, and the open and close functionality will be handled automatically.
|
|
1154
|
+
* If you want to customise the drawer, you can create your own component and pass it as a child.
|
|
1155
|
+
* For custom drawers, you can use the {@link useApp} to open and close the drawer.
|
|
1156
|
+
*
|
|
1157
|
+
*/
|
|
1158
|
+
function Drawer({ children, title, logo, logoDestination, footerActions, className, style }) {
|
|
1159
|
+
return /* @__PURE__ */ jsx(Fragment, { children: children ?? /* @__PURE__ */ jsx(DefaultDrawer, {
|
|
1160
|
+
title,
|
|
1161
|
+
logo,
|
|
1162
|
+
logoDestination,
|
|
1163
|
+
footerActions,
|
|
1164
|
+
className,
|
|
1165
|
+
style
|
|
1166
|
+
}) });
|
|
1167
|
+
}
|
|
1168
|
+
Drawer.componentType = "Drawer";
|
|
1169
|
+
/**
|
|
1170
|
+
* This view acts as a scaffold for Rebase.
|
|
1171
|
+
*
|
|
1172
|
+
* It is in charge of displaying the navigation drawer, top bar and main
|
|
1173
|
+
* collection views.
|
|
1174
|
+
* This component needs a parent {@link Rebase}
|
|
1175
|
+
*
|
|
1176
|
+
* @param props
|
|
1177
|
+
|
|
1178
|
+
* @group Core
|
|
1179
|
+
*/
|
|
1180
|
+
var Scaffold = React.memo(function Scaffold(props) {
|
|
1181
|
+
const { children, autoOpenDrawer, logo, className, style, padding = true } = props;
|
|
1182
|
+
const drawerChildren = React.Children.toArray(children).filter((child) => child.type.componentType === "Drawer");
|
|
1183
|
+
if (drawerChildren.length > 1) throw Error("Only one Drawer component is allowed in Scaffold");
|
|
1184
|
+
const appBarChildren = React.Children.toArray(children).filter((child) => child.type.componentType === "AppBar");
|
|
1185
|
+
if (appBarChildren.length > 1) throw Error("Only one AppBar component is allowed in Scaffold");
|
|
1186
|
+
const otherChildren = React.Children.toArray(children).filter((child) => child.type.componentType !== "Drawer" && child.type.componentType !== "AppBar");
|
|
1187
|
+
const includeDrawer = drawerChildren.length > 0;
|
|
1188
|
+
const largeLayout = useLargeLayout();
|
|
1189
|
+
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
|
1190
|
+
const [onHover, setOnHover] = React.useState(false);
|
|
1191
|
+
const setOnHoverTrue = useCallback(() => setOnHover(true), []);
|
|
1192
|
+
const setOnHoverFalse = useCallback(() => {
|
|
1193
|
+
if (document.querySelector("[data-radix-popper-content-wrapper]")) return;
|
|
1194
|
+
setOnHover(false);
|
|
1195
|
+
}, []);
|
|
1196
|
+
const handleDrawerOpen = useCallback(() => {
|
|
1197
|
+
setDrawerOpen(true);
|
|
1198
|
+
}, []);
|
|
1199
|
+
const handleDrawerClose = useCallback(() => {
|
|
1200
|
+
setDrawerOpen(false);
|
|
1201
|
+
}, []);
|
|
1202
|
+
const computedDrawerOpen = drawerOpen;
|
|
1203
|
+
const computedDrawerHovered = Boolean(largeLayout && onHover);
|
|
1204
|
+
const isStudioDark = useAdminModeController().mode === "studio";
|
|
1205
|
+
const hasAppBar = Boolean(appBarChildren.length > 0);
|
|
1206
|
+
const appContextValue = useMemo(() => ({
|
|
1207
|
+
hasDrawer: Boolean(includeDrawer),
|
|
1208
|
+
drawerOpen: computedDrawerOpen,
|
|
1209
|
+
drawerHovered: computedDrawerHovered,
|
|
1210
|
+
openDrawer: handleDrawerOpen,
|
|
1211
|
+
closeDrawer: handleDrawerClose,
|
|
1212
|
+
closeHover: setOnHoverFalse,
|
|
1213
|
+
logo
|
|
1214
|
+
}), [
|
|
1215
|
+
includeDrawer,
|
|
1216
|
+
computedDrawerOpen,
|
|
1217
|
+
computedDrawerHovered,
|
|
1218
|
+
handleDrawerOpen,
|
|
1219
|
+
handleDrawerClose,
|
|
1220
|
+
setOnHoverFalse,
|
|
1221
|
+
logo
|
|
1222
|
+
]);
|
|
1223
|
+
return /* @__PURE__ */ jsx(AppContext.Provider, {
|
|
1224
|
+
value: appContextValue,
|
|
1225
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1226
|
+
className: cls("flex h-screen w-screen overflow-hidden", "bg-surface-50 dark:bg-surface-900", "text-surface-900 dark:text-white", className),
|
|
1227
|
+
style: {
|
|
1228
|
+
paddingTop: "env(safe-area-inset-top)",
|
|
1229
|
+
paddingLeft: "env(safe-area-inset-left)",
|
|
1230
|
+
paddingRight: "env(safe-area-inset-right)",
|
|
1231
|
+
paddingBottom: "env(safe-area-inset-bottom)",
|
|
1232
|
+
height: "100dvh",
|
|
1233
|
+
...style
|
|
1234
|
+
},
|
|
1235
|
+
children: [
|
|
1236
|
+
appBarChildren,
|
|
1237
|
+
/* @__PURE__ */ jsx(DrawerWrapper, {
|
|
1238
|
+
displayed: includeDrawer,
|
|
1239
|
+
onMouseEnter: setOnHoverTrue,
|
|
1240
|
+
onMouseMove: setOnHoverTrue,
|
|
1241
|
+
onMouseLeave: setOnHoverFalse,
|
|
1242
|
+
open: drawerOpen,
|
|
1243
|
+
hovered: onHover,
|
|
1244
|
+
isStudioDark,
|
|
1245
|
+
setDrawerOpen,
|
|
1246
|
+
children: includeDrawer && drawerChildren
|
|
1247
|
+
}),
|
|
1248
|
+
/* @__PURE__ */ jsxs("main", {
|
|
1249
|
+
className: "flex flex-col grow overflow-auto",
|
|
1250
|
+
children: [hasAppBar && /* @__PURE__ */ jsx(DrawerHeader, {}), /* @__PURE__ */ jsx("div", {
|
|
1251
|
+
className: cls(defaultBorderMixin, "bg-surface-50 dark:bg-surface-800", "grow overflow-auto m-0", {
|
|
1252
|
+
"mt-1 lg:m-0 lg:mx-2 lg:mb-2 lg:rounded-lg lg:border-t lg:border-x lg:border-solid": padding,
|
|
1253
|
+
"lg:mt-2": !hasAppBar && padding,
|
|
1254
|
+
"lg:mt-4": !hasAppBar && !padding,
|
|
1255
|
+
"border-t": hasAppBar && !padding
|
|
1256
|
+
}),
|
|
1257
|
+
children: /* @__PURE__ */ jsx(ErrorBoundary, { children: otherChildren })
|
|
1258
|
+
})]
|
|
1259
|
+
})
|
|
1260
|
+
]
|
|
1261
|
+
})
|
|
1262
|
+
});
|
|
1263
|
+
}, deepEqual);
|
|
1264
|
+
var DrawerHeader = () => {
|
|
1265
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-col min-h-14" });
|
|
1266
|
+
};
|
|
1267
|
+
function DrawerWrapper(props) {
|
|
1268
|
+
const layoutWidth = !props.displayed ? 0 : props.open ? 280 : 72;
|
|
1269
|
+
const visualWidth = !props.displayed ? 0 : props.open || props.hovered ? 280 : 72;
|
|
1270
|
+
const isFloating = props.hovered && !props.open;
|
|
1271
|
+
const darkBg = "dark:bg-surface-900";
|
|
1272
|
+
const darkBgFloating = "dark:bg-surface-900";
|
|
1273
|
+
const { t } = useTranslation();
|
|
1274
|
+
const innerDrawer = /* @__PURE__ */ jsx("div", {
|
|
1275
|
+
className: cls("h-full overflow-hidden", defaultBorderMixin, isFloating ? `absolute top-0 left-0 bottom-0 z-50 bg-surface-50 ${darkBgFloating} shadow-lg border-r` : `relative bg-surface-50 ${darkBg}`),
|
|
1276
|
+
style: {
|
|
1277
|
+
width: visualWidth,
|
|
1278
|
+
transition: "left 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, opacity 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, width 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms"
|
|
1279
|
+
},
|
|
1280
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1281
|
+
className: "flex flex-col h-full",
|
|
1282
|
+
children: props.children
|
|
1283
|
+
})
|
|
1284
|
+
});
|
|
1285
|
+
if (!useLargeLayout()) {
|
|
1286
|
+
if (!props.displayed) return null;
|
|
1287
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(IconButton, {
|
|
1288
|
+
color: "inherit",
|
|
1289
|
+
"aria-label": t("open_menu"),
|
|
1290
|
+
onClick: () => props.setDrawerOpen(true),
|
|
1291
|
+
className: "absolute sm:top-2 sm:left-4 top-1 left-2",
|
|
1292
|
+
children: /* @__PURE__ */ jsx(MenuIcon, {})
|
|
1293
|
+
}), /* @__PURE__ */ jsx(Sheet, {
|
|
1294
|
+
side: "left",
|
|
1295
|
+
transparent: true,
|
|
1296
|
+
open: props.open,
|
|
1297
|
+
onOpenChange: props.setDrawerOpen,
|
|
1298
|
+
title: t("navigation_drawer"),
|
|
1299
|
+
overlayClassName: "bg-white/80 dark:bg-surface-900/80",
|
|
1300
|
+
children: innerDrawer
|
|
1301
|
+
})] });
|
|
1302
|
+
}
|
|
1303
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1304
|
+
className: "z-20 relative flex-shrink-0 overflow-visible",
|
|
1305
|
+
onMouseEnter: props.onMouseEnter,
|
|
1306
|
+
onMouseMove: props.onMouseMove,
|
|
1307
|
+
onMouseLeave: props.onMouseLeave,
|
|
1308
|
+
style: {
|
|
1309
|
+
width: layoutWidth,
|
|
1310
|
+
minWidth: layoutWidth,
|
|
1311
|
+
transition: "left 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, opacity 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, width 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms"
|
|
1312
|
+
},
|
|
1313
|
+
children: innerDrawer
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
//#endregion
|
|
1317
|
+
//#region src/components/RebaseAdmin.tsx
|
|
1318
|
+
/**
|
|
1319
|
+
* Declarative component to configure the CMS in Rebase.
|
|
1320
|
+
* Renders nothing — purely registers config into the RebaseRegistry.
|
|
1321
|
+
*
|
|
1322
|
+
* When `collectionEditor` is provided, the built-in visual schema editor
|
|
1323
|
+
* is auto-wired as a native feature (slots, provider, Studio view) without
|
|
1324
|
+
* needing any external plugin.
|
|
1325
|
+
*/
|
|
1326
|
+
function RebaseAdmin({ collections, views, homePage, entityViews, entityActions, collectionEditor, navigationGroupMappings, basePath }) {
|
|
1327
|
+
const dispatch = useRebaseRegistryDispatch();
|
|
1328
|
+
useLayoutEffect(() => {
|
|
1329
|
+
dispatch.registerCMS({
|
|
1330
|
+
collections,
|
|
1331
|
+
views,
|
|
1332
|
+
homePage,
|
|
1333
|
+
entityViews,
|
|
1334
|
+
entityActions,
|
|
1335
|
+
collectionEditor,
|
|
1336
|
+
navigationGroupMappings,
|
|
1337
|
+
basePath
|
|
1338
|
+
});
|
|
1339
|
+
return () => dispatch.unregisterCMS();
|
|
1340
|
+
}, [
|
|
1341
|
+
dispatch,
|
|
1342
|
+
collections,
|
|
1343
|
+
views,
|
|
1344
|
+
homePage,
|
|
1345
|
+
entityViews,
|
|
1346
|
+
entityActions,
|
|
1347
|
+
collectionEditor,
|
|
1348
|
+
navigationGroupMappings,
|
|
1349
|
+
basePath
|
|
1350
|
+
]);
|
|
1351
|
+
return null;
|
|
1352
|
+
}
|
|
1353
|
+
//#endregion
|
|
1354
|
+
//#region src/components/RebaseAuthGate.tsx
|
|
1355
|
+
/**
|
|
1356
|
+
* Auth gate component that handles the authentication flow.
|
|
1357
|
+
*
|
|
1358
|
+
* - Shows a loading spinner while `authController.initialLoading` is true.
|
|
1359
|
+
* - Shows the login view when no user is authenticated.
|
|
1360
|
+
* - Renders `children` when a user is authenticated.
|
|
1361
|
+
*
|
|
1362
|
+
* **Independently usable**: Use this alone when you want auth gating
|
|
1363
|
+
* without the full CMS layout or navigation.
|
|
1364
|
+
*
|
|
1365
|
+
* @example
|
|
1366
|
+
* ```tsx
|
|
1367
|
+
* <RebaseAuthGate>
|
|
1368
|
+
* <MyCustomApp />
|
|
1369
|
+
* </RebaseAuthGate>
|
|
1370
|
+
* ```
|
|
1371
|
+
*/
|
|
1372
|
+
function RebaseAuthGate({ children }) {
|
|
1373
|
+
const registry = useRebaseRegistry();
|
|
1374
|
+
const authController = useAuthController();
|
|
1375
|
+
if (authController?.initialLoading) return /* @__PURE__ */ jsx(CircularProgressCenter, { size: "large" });
|
|
1376
|
+
if (!authController?.user) return /* @__PURE__ */ jsx(Fragment, { children: registry.authConfig?.loginView ?? /* @__PURE__ */ jsx(LoginView, { authController }) });
|
|
1377
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
1378
|
+
}
|
|
1379
|
+
//#endregion
|
|
1380
|
+
//#region src/collection_editor/useLocalCollectionsConfigController.tsx
|
|
1381
|
+
function useLocalCollectionsConfigController(clientOrUrl, baseCollections = [], options) {
|
|
1382
|
+
const parsedCollections = baseCollections;
|
|
1383
|
+
const optionsRef = useRef(options);
|
|
1384
|
+
optionsRef.current = options;
|
|
1385
|
+
const request = async (endpoint, payload) => {
|
|
1386
|
+
try {
|
|
1387
|
+
let token = optionsRef.current?.getAuthToken ? await optionsRef.current.getAuthToken() : null;
|
|
1388
|
+
let baseUrl = typeof clientOrUrl === "string" ? clientOrUrl : "";
|
|
1389
|
+
if (typeof clientOrUrl === "object" && clientOrUrl !== null) {
|
|
1390
|
+
baseUrl = clientOrUrl.baseUrl || baseUrl;
|
|
1391
|
+
if (!token && clientOrUrl.resolveToken) token = await clientOrUrl.resolveToken();
|
|
1392
|
+
}
|
|
1393
|
+
const headers = { "Content-Type": "application/json" };
|
|
1394
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
1395
|
+
const response = await fetch(`${baseUrl.replace(/\/$/, "")}/api/schema-editor${endpoint}`, {
|
|
1396
|
+
method: "POST",
|
|
1397
|
+
headers,
|
|
1398
|
+
body: JSON.stringify(payload)
|
|
1399
|
+
});
|
|
1400
|
+
if (!response.ok) {
|
|
1401
|
+
const text = await response.text();
|
|
1402
|
+
let err = {};
|
|
1403
|
+
try {
|
|
1404
|
+
err = JSON.parse(text);
|
|
1405
|
+
} catch (e) {}
|
|
1406
|
+
if (Object.keys(err).length === 0) err = { message: text };
|
|
1407
|
+
console.error("dev server error payload:", err);
|
|
1408
|
+
const errObj = err.error;
|
|
1409
|
+
const errMessage = typeof errObj === "object" && errObj !== null ? errObj.message : typeof errObj === "string" ? errObj : err.message;
|
|
1410
|
+
throw new Error(errMessage || "Error communicating with local dev server");
|
|
1411
|
+
}
|
|
1412
|
+
} catch (e) {
|
|
1413
|
+
console.error("fetch request failed", e);
|
|
1414
|
+
throw e;
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
return useMemo(() => ({
|
|
1418
|
+
loading: false,
|
|
1419
|
+
readOnly: options?.readOnly ?? false,
|
|
1420
|
+
readOnlyReason: "Local collection editing is only available in development mode.",
|
|
1421
|
+
collections: parsedCollections,
|
|
1422
|
+
getCollection: (id) => {
|
|
1423
|
+
const found = parsedCollections.find((c) => c.id === id || c.slug === id);
|
|
1424
|
+
if (found) return found;
|
|
1425
|
+
throw Error(`Collection ${id} not found in local mode`);
|
|
1426
|
+
},
|
|
1427
|
+
saveCollection: async ({ id, collectionData }) => {
|
|
1428
|
+
await request("/collection/save", {
|
|
1429
|
+
collectionId: id,
|
|
1430
|
+
collectionData
|
|
1431
|
+
});
|
|
1432
|
+
},
|
|
1433
|
+
updateCollection: async ({ id, collectionData }) => {
|
|
1434
|
+
await request("/collection/save", {
|
|
1435
|
+
collectionId: id,
|
|
1436
|
+
collectionData
|
|
1437
|
+
});
|
|
1438
|
+
},
|
|
1439
|
+
deleteCollection: async ({ id }) => {
|
|
1440
|
+
await request("/collection/delete", { collectionId: id });
|
|
1441
|
+
},
|
|
1442
|
+
saveProperty: async ({ path, propertyKey, property, newPropertiesOrder }) => {
|
|
1443
|
+
await request("/property/save", {
|
|
1444
|
+
collectionId: path,
|
|
1445
|
+
propertyKey,
|
|
1446
|
+
propertyConfig: property
|
|
1447
|
+
});
|
|
1448
|
+
if (newPropertiesOrder) await request("/collection/save", {
|
|
1449
|
+
collectionId: path,
|
|
1450
|
+
collectionData: { propertiesOrder: newPropertiesOrder }
|
|
1451
|
+
});
|
|
1452
|
+
},
|
|
1453
|
+
deleteProperty: async ({ path, propertyKey, newPropertiesOrder }) => {
|
|
1454
|
+
await request("/property/delete", {
|
|
1455
|
+
collectionId: path,
|
|
1456
|
+
propertyKey
|
|
1457
|
+
});
|
|
1458
|
+
if (newPropertiesOrder) await request("/collection/save", {
|
|
1459
|
+
collectionId: path,
|
|
1460
|
+
collectionData: { propertiesOrder: newPropertiesOrder }
|
|
1461
|
+
});
|
|
1462
|
+
},
|
|
1463
|
+
updatePropertiesOrder: async ({ collection, fullPath, newPropertiesOrder }) => {
|
|
1464
|
+
await request("/collection/save", {
|
|
1465
|
+
collectionId: collection.id || fullPath.split("/").pop(),
|
|
1466
|
+
collectionData: { propertiesOrder: newPropertiesOrder }
|
|
1467
|
+
});
|
|
1468
|
+
},
|
|
1469
|
+
updateKanbanColumnsOrder: async () => {},
|
|
1470
|
+
navigationEntries: [],
|
|
1471
|
+
saveNavigationEntries: async () => {}
|
|
1472
|
+
}), [
|
|
1473
|
+
clientOrUrl,
|
|
1474
|
+
parsedCollections,
|
|
1475
|
+
options?.readOnly,
|
|
1476
|
+
options?.getAuthToken
|
|
1477
|
+
]);
|
|
1478
|
+
}
|
|
1479
|
+
//#endregion
|
|
1480
|
+
//#region src/components/RebaseNavigation.tsx
|
|
1481
|
+
var EMPTY_PLUGINS = [];
|
|
1482
|
+
var EMPTY_COLLECTIONS = [];
|
|
1483
|
+
var CollectionsStudioView = lazy(() => import("./RouterCollectionsStudioView-B9AjTCjT.js").then((n) => n.n).then((m) => ({ default: m.RouterCollectionsStudioView })));
|
|
1484
|
+
/**
|
|
1485
|
+
* Navigation layer — builds and provides all CMS navigation controllers:
|
|
1486
|
+
* collection registry, URL controller, navigation state, side entity,
|
|
1487
|
+
* and the self-assembling Studio bridge.
|
|
1488
|
+
*
|
|
1489
|
+
* Also handles the collection editor config controller when enabled.
|
|
1490
|
+
*
|
|
1491
|
+
* **Independently usable**: Use this when you need CMS navigation
|
|
1492
|
+
* (entity tables, side panels) in a custom layout.
|
|
1493
|
+
*
|
|
1494
|
+
* @example
|
|
1495
|
+
* ```tsx
|
|
1496
|
+
* <RebaseNavigation>
|
|
1497
|
+
* <MyCustomLayout>
|
|
1498
|
+
* <CollectionViewBinding ... />
|
|
1499
|
+
* </MyCustomLayout>
|
|
1500
|
+
* </RebaseNavigation>
|
|
1501
|
+
* ```
|
|
1502
|
+
*/
|
|
1503
|
+
function RebaseNavigation({ children }) {
|
|
1504
|
+
const registry = useRebaseRegistry();
|
|
1505
|
+
const context = useRebaseContext();
|
|
1506
|
+
const adminModeController = useAdminModeController();
|
|
1507
|
+
const userConfigPersistence = useBuildLocalConfigurationPersistence();
|
|
1508
|
+
const collectionEditorConfig = registry.cmsConfig?.collectionEditor;
|
|
1509
|
+
const collectionEditorEnabled = Boolean(collectionEditorConfig) || Boolean(registry.studioConfig);
|
|
1510
|
+
const collectionEditorOptions = useMemo(() => {
|
|
1511
|
+
if (collectionEditorConfig === true || !collectionEditorConfig) return {};
|
|
1512
|
+
return collectionEditorConfig;
|
|
1513
|
+
}, [collectionEditorConfig]);
|
|
1514
|
+
const cmsCollections = registry.cmsConfig?.collections ?? EMPTY_COLLECTIONS;
|
|
1515
|
+
const collectionsBuilder = useMemo(() => Array.isArray(cmsCollections) ? () => [...cmsCollections] : cmsCollections, [cmsCollections]);
|
|
1516
|
+
const dataSources = useDataSources();
|
|
1517
|
+
const collectionRegistryController = useBuildCollectionRegistryController({
|
|
1518
|
+
userConfigPersistence,
|
|
1519
|
+
dataSources: dataSources.registry
|
|
1520
|
+
});
|
|
1521
|
+
const defaultData = useData();
|
|
1522
|
+
const getCollectionRef = useRef(collectionRegistryController.getCollection);
|
|
1523
|
+
getCollectionRef.current = collectionRegistryController.getCollection;
|
|
1524
|
+
useContext(CollectionResolverRegistrationContext)(useCallback((slug) => getCollectionRef.current(slug), []));
|
|
1525
|
+
const routedData = useMemo(() => buildRoutedRebaseData({
|
|
1526
|
+
defaultData,
|
|
1527
|
+
sources: dataSources.sources,
|
|
1528
|
+
resolveKey: (slugOrPath) => resolveDataSource(getCollectionRef.current(slugOrPath), dataSources.registry).key
|
|
1529
|
+
}), [defaultData, dataSources]);
|
|
1530
|
+
const urlController = useBuildUrlController({
|
|
1531
|
+
basePath: useMemo(() => {
|
|
1532
|
+
const raw = registry.cmsConfig?.basePath;
|
|
1533
|
+
if (!raw || raw === "/") return "/";
|
|
1534
|
+
const trimmed = raw.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
1535
|
+
return trimmed ? `/${trimmed}` : "/";
|
|
1536
|
+
}, [registry.cmsConfig?.basePath]),
|
|
1537
|
+
baseCollectionPath: "/c",
|
|
1538
|
+
collectionRegistryController
|
|
1539
|
+
});
|
|
1540
|
+
const rebaseClient = useRebaseClient();
|
|
1541
|
+
const authController = useAuthController();
|
|
1542
|
+
const resolvedCollections = useMemo(() => Array.isArray(cmsCollections) ? cmsCollections : [], [cmsCollections]);
|
|
1543
|
+
useEffect(() => {
|
|
1544
|
+
if (process.env.NODE_ENV === "production") return;
|
|
1545
|
+
const { registry, sources } = dataSources;
|
|
1546
|
+
const missing = Object.values(registry).filter((d) => (d.transport === "direct" || d.transport === "custom") && !sources[d.key]).map((d) => d.key);
|
|
1547
|
+
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>.`);
|
|
1548
|
+
}, [dataSources]);
|
|
1549
|
+
const internalConfigController = useLocalCollectionsConfigController(rebaseClient, resolvedCollections, collectionEditorEnabled ? {
|
|
1550
|
+
readOnly: collectionEditorOptions?.readOnly ?? process.env.NODE_ENV === "production",
|
|
1551
|
+
getAuthToken: collectionEditorOptions?.getAuthToken ?? authController?.getAuthToken
|
|
1552
|
+
} : { readOnly: true });
|
|
1553
|
+
const schemaView = useMemo(() => {
|
|
1554
|
+
if (!collectionEditorEnabled) return void 0;
|
|
1555
|
+
const studioTools = registry.studioConfig?.tools ?? [
|
|
1556
|
+
"sql",
|
|
1557
|
+
"js",
|
|
1558
|
+
"rls",
|
|
1559
|
+
"schema",
|
|
1560
|
+
"storage"
|
|
1561
|
+
];
|
|
1562
|
+
if (!registry.studioConfig || !studioTools.includes("schema")) return void 0;
|
|
1563
|
+
return {
|
|
1564
|
+
slug: "schema",
|
|
1565
|
+
name: "Edit collections",
|
|
1566
|
+
group: "Database",
|
|
1567
|
+
icon: "LayoutList",
|
|
1568
|
+
nestedRoutes: true,
|
|
1569
|
+
view: /* @__PURE__ */ jsx(Suspense, {
|
|
1570
|
+
fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
|
|
1571
|
+
children: /* @__PURE__ */ jsx(CollectionsStudioView, { configController: internalConfigController })
|
|
1572
|
+
})
|
|
1573
|
+
};
|
|
1574
|
+
}, [
|
|
1575
|
+
collectionEditorEnabled,
|
|
1576
|
+
registry.studioConfig,
|
|
1577
|
+
internalConfigController
|
|
1578
|
+
]);
|
|
1579
|
+
const devViews = useMemo(() => {
|
|
1580
|
+
const base = registry.studioConfig?.devViews ?? [];
|
|
1581
|
+
if (schemaView) return [...base, schemaView];
|
|
1582
|
+
return base;
|
|
1583
|
+
}, [registry.studioConfig?.devViews, schemaView]);
|
|
1584
|
+
const cmsViews = registry.cmsConfig?.views;
|
|
1585
|
+
const mergedViews = useMemo(() => {
|
|
1586
|
+
if (!cmsViews) return devViews;
|
|
1587
|
+
if (Array.isArray(cmsViews) && cmsViews.length === 0) return devViews;
|
|
1588
|
+
if (typeof cmsViews === "function") return async (params) => {
|
|
1589
|
+
return [...await cmsViews(params) ?? [], ...devViews];
|
|
1590
|
+
};
|
|
1591
|
+
return [...cmsViews, ...devViews];
|
|
1592
|
+
}, [cmsViews, devViews]);
|
|
1593
|
+
const parentCustomizationController = useCustomizationController();
|
|
1594
|
+
const navigationStateController = useBuildNavigationStateController({
|
|
1595
|
+
plugins: parentCustomizationController.plugins ?? EMPTY_PLUGINS,
|
|
1596
|
+
collections: collectionsBuilder,
|
|
1597
|
+
views: mergedViews,
|
|
1598
|
+
navigationGroupMappings: registry.cmsConfig?.navigationGroupMappings,
|
|
1599
|
+
authController: context.authController,
|
|
1600
|
+
data: routedData,
|
|
1601
|
+
collectionRegistryController,
|
|
1602
|
+
urlController,
|
|
1603
|
+
adminMode: adminModeController?.mode
|
|
1604
|
+
});
|
|
1605
|
+
const enrichedCustomizationController = useMemo(() => {
|
|
1606
|
+
const cmsEntityViews = registry.cmsConfig?.entityViews ?? [];
|
|
1607
|
+
const cmsEntityActions = registry.cmsConfig?.entityActions ?? [];
|
|
1608
|
+
if (cmsEntityViews.length === 0 && cmsEntityActions.length === 0) return parentCustomizationController;
|
|
1609
|
+
return {
|
|
1610
|
+
...parentCustomizationController,
|
|
1611
|
+
entityViews: [...parentCustomizationController.entityViews ?? [], ...cmsEntityViews.filter((v) => !(parentCustomizationController.entityViews ?? []).some((ev) => ev.key === v.key))],
|
|
1612
|
+
entityActions: [...parentCustomizationController.entityActions ?? [], ...cmsEntityActions.filter((a) => !(parentCustomizationController.entityActions ?? []).some((ea) => ea.key === a.key))]
|
|
1613
|
+
};
|
|
1614
|
+
}, [
|
|
1615
|
+
parentCustomizationController,
|
|
1616
|
+
registry.cmsConfig?.entityViews,
|
|
1617
|
+
registry.cmsConfig?.entityActions
|
|
1618
|
+
]);
|
|
1619
|
+
const navigationContent = /* @__PURE__ */ jsx(RebaseDataContext.Provider, {
|
|
1620
|
+
value: routedData,
|
|
1621
|
+
children: /* @__PURE__ */ jsx(CustomizationControllerContext.Provider, {
|
|
1622
|
+
value: enrichedCustomizationController,
|
|
1623
|
+
children: /* @__PURE__ */ jsx(StudioBridgeRegistryProvider, { children: /* @__PURE__ */ jsx(CollectionRegistryContext.Provider, {
|
|
1624
|
+
value: collectionRegistryController,
|
|
1625
|
+
children: /* @__PURE__ */ jsx(UrlContext.Provider, {
|
|
1626
|
+
value: urlController,
|
|
1627
|
+
children: /* @__PURE__ */ jsx(NavigationStateContext.Provider, {
|
|
1628
|
+
value: navigationStateController,
|
|
1629
|
+
children: /* @__PURE__ */ jsxs(SidePanelProvider, { children: [/* @__PURE__ */ jsx(BridgeAutoRegistrar, {
|
|
1630
|
+
collectionRegistryController,
|
|
1631
|
+
urlController,
|
|
1632
|
+
navigationStateController
|
|
1633
|
+
}), children] })
|
|
1634
|
+
})
|
|
1635
|
+
})
|
|
1636
|
+
}) })
|
|
1637
|
+
})
|
|
1638
|
+
});
|
|
1639
|
+
if (collectionEditorEnabled) return /* @__PURE__ */ jsx(ConfigControllerProvider, {
|
|
1640
|
+
collectionConfigController: internalConfigController,
|
|
1641
|
+
pathSuggestions: collectionEditorOptions?.pathSuggestions,
|
|
1642
|
+
children: navigationContent
|
|
1643
|
+
});
|
|
1644
|
+
return navigationContent;
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Internal component that auto-registers CMS controllers into the
|
|
1648
|
+
* self-assembling Studio bridge. Must be rendered inside both the
|
|
1649
|
+
* navigation contexts and the StudioBridgeRegistryProvider.
|
|
1650
|
+
*/
|
|
1651
|
+
function BridgeAutoRegistrar({ collectionRegistryController, urlController, navigationStateController }) {
|
|
1652
|
+
useBridgeRegistration("collectionRegistry", collectionRegistryController);
|
|
1653
|
+
useBridgeRegistration("urlController", urlController);
|
|
1654
|
+
useBridgeRegistration("navigationState", navigationStateController);
|
|
1655
|
+
return null;
|
|
1656
|
+
}
|
|
1657
|
+
//#endregion
|
|
1658
|
+
//#region src/components/RebaseLayout.tsx
|
|
1659
|
+
/**
|
|
1660
|
+
* Layout layer — provides the Scaffold, Drawer, and SideDialogs.
|
|
1661
|
+
*
|
|
1662
|
+
* Wraps the route outlet with the standard Rebase admin layout.
|
|
1663
|
+
* Override the drawer via props; pass `appBar` to add a top bar (there is
|
|
1664
|
+
* none by default — the drawer owns navigation and user actions).
|
|
1665
|
+
*
|
|
1666
|
+
* **Independently usable**: Use this when you want the Rebase layout
|
|
1667
|
+
* without its default route definitions.
|
|
1668
|
+
*
|
|
1669
|
+
* @example
|
|
1670
|
+
* ```tsx
|
|
1671
|
+
* <RebaseLayout title="My Admin" appBar={<AppBar/>}>
|
|
1672
|
+
* <Route path="/" element={<MyHomePage />} />
|
|
1673
|
+
* <Route path="/custom" element={<CustomView />} />
|
|
1674
|
+
* </RebaseLayout>
|
|
1675
|
+
* ```
|
|
1676
|
+
*/
|
|
1677
|
+
function RebaseLayout(props) {
|
|
1678
|
+
const { title = "Rebase", appBar, drawer, autoOpenDrawer = false, devViews = [] } = props;
|
|
1679
|
+
const adminModeController = useAdminModeController();
|
|
1680
|
+
const ResolvedDrawer = useComponentOverride("Shell.Drawer", Drawer);
|
|
1681
|
+
const ActiveDrawer = drawer ?? /* @__PURE__ */ jsx(ResolvedDrawer, {
|
|
1682
|
+
title,
|
|
1683
|
+
logoDestination: adminModeController.mode === "studio" ? "/s" : "/"
|
|
1684
|
+
});
|
|
1685
|
+
return /* @__PURE__ */ jsxs(Scaffold, {
|
|
1686
|
+
autoOpenDrawer,
|
|
1687
|
+
children: [
|
|
1688
|
+
/* @__PURE__ */ jsx(AdminModeSyncer, { devViews }),
|
|
1689
|
+
appBar,
|
|
1690
|
+
ActiveDrawer,
|
|
1691
|
+
/* @__PURE__ */ jsx(Outlet, {}),
|
|
1692
|
+
/* @__PURE__ */ jsx(SideDialogs, {})
|
|
1693
|
+
]
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
//#endregion
|
|
1697
|
+
//#region src/components/HomePage/FavouritesView.tsx
|
|
1698
|
+
function NavigationChip({ entry }) {
|
|
1699
|
+
const navigate = useNavigate();
|
|
1700
|
+
const userConfigurationPersistence = useUserConfigurationPersistence();
|
|
1701
|
+
if (!userConfigurationPersistence) return null;
|
|
1702
|
+
const favourite = userConfigurationPersistence.favouritePaths.includes(entry.slug);
|
|
1703
|
+
const onIconClick = (e) => {
|
|
1704
|
+
e.preventDefault();
|
|
1705
|
+
e.stopPropagation();
|
|
1706
|
+
if (favourite) userConfigurationPersistence.setFavouritePaths(userConfigurationPersistence.favouritePaths.filter((p) => p !== entry.slug));
|
|
1707
|
+
else userConfigurationPersistence.setFavouritePaths([...userConfigurationPersistence.favouritePaths, entry.slug]);
|
|
1708
|
+
};
|
|
1709
|
+
return /* @__PURE__ */ jsx(Chip, {
|
|
1710
|
+
onClick: () => navigate(entry.url),
|
|
1711
|
+
icon: /* @__PURE__ */ jsx(StarIcon, {
|
|
1712
|
+
onClick: onIconClick,
|
|
1713
|
+
size: "small",
|
|
1714
|
+
className: favourite ? "text-secondary" : "text-surface-400 dark:text-surface-500"
|
|
1715
|
+
}),
|
|
1716
|
+
children: entry.name
|
|
1717
|
+
}, entry.slug);
|
|
1718
|
+
}
|
|
1719
|
+
function FavouritesView({ hidden }) {
|
|
1720
|
+
const navigationStateController = useNavigationStateController();
|
|
1721
|
+
const userConfigurationPersistence = useUserConfigurationPersistence();
|
|
1722
|
+
if (!userConfigurationPersistence) return null;
|
|
1723
|
+
const favouriteCollections = (userConfigurationPersistence?.favouritePaths ?? []).map((path) => navigationStateController.topLevelNavigation?.navigationEntries.find((entry) => entry.slug === path)).filter(Boolean);
|
|
1724
|
+
return /* @__PURE__ */ jsx(Collapse, {
|
|
1725
|
+
in: favouriteCollections.length > 0,
|
|
1726
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1727
|
+
className: "flex flex-row flex-wrap gap-2 pb-2 min-h-[32px]",
|
|
1728
|
+
children: favouriteCollections.map((entry) => /* @__PURE__ */ jsx(NavigationChip, { entry }, entry.slug))
|
|
1729
|
+
})
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
//#endregion
|
|
1733
|
+
//#region src/components/HomePage/NavigationGroup.tsx
|
|
1734
|
+
function NavigationGroup({ children, group, minimised, isPreview, isPotentialCardDropTarget, onEditGroup, dndDisabled, collapsed, onToggleCollapsed }) {
|
|
1735
|
+
const { t } = useTranslation();
|
|
1736
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
1737
|
+
const currentGroupName = group ?? t("views_group");
|
|
1738
|
+
const showCaret = !isPreview && !!onToggleCollapsed;
|
|
1739
|
+
const TitleContent = /* @__PURE__ */ jsxs("div", {
|
|
1740
|
+
className: cls("flex items-center", isPreview ? "px-1 py-0.5" : ""),
|
|
1741
|
+
children: [/* @__PURE__ */ jsx(Typography, {
|
|
1742
|
+
variant: isPreview ? "body2" : "caption",
|
|
1743
|
+
component: "h2",
|
|
1744
|
+
color: "secondary",
|
|
1745
|
+
className: cls("px-4 py-1 rounded", "font-medium text-[10px] uppercase tracking-[0.08em] text-primary/50 dark:text-primary/70"),
|
|
1746
|
+
children: currentGroupName
|
|
1747
|
+
}), !isPreview && onEditGroup && !dndDisabled && /* @__PURE__ */ jsx(IconButton, {
|
|
1748
|
+
size: "smallest",
|
|
1749
|
+
onClick: (e) => {
|
|
1750
|
+
e.stopPropagation();
|
|
1751
|
+
onEditGroup(currentGroupName);
|
|
1752
|
+
},
|
|
1753
|
+
className: cls("ml-2 ", isHovered ? "opacity-100" : "opacity-0", "transition-opacity duration-100"),
|
|
1754
|
+
children: /* @__PURE__ */ jsx(PencilIcon, { size: iconSize.smallest })
|
|
1755
|
+
})]
|
|
1756
|
+
});
|
|
1757
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1758
|
+
className: cls(!isPotentialCardDropTarget ? "my-10" : "my-6", "transition-all duration-200 ease-in-out"),
|
|
1759
|
+
children: [
|
|
1760
|
+
isPreview && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
1761
|
+
className: cls("flex items-center justify-between w-full", "p-4 py-2"),
|
|
1762
|
+
onMouseEnter: () => setIsHovered(true),
|
|
1763
|
+
onMouseLeave: () => setIsHovered(false),
|
|
1764
|
+
children: TitleContent
|
|
1765
|
+
}), children] }),
|
|
1766
|
+
!isPreview && showCaret && /* @__PURE__ */ jsx(ExpandablePanel, {
|
|
1767
|
+
invisible: true,
|
|
1768
|
+
expanded: !collapsed,
|
|
1769
|
+
onExpandedChange: (open) => {
|
|
1770
|
+
if (open !== !collapsed) onToggleCollapsed?.();
|
|
1771
|
+
},
|
|
1772
|
+
className: cls("mt-6"),
|
|
1773
|
+
titleClassName: cls("min-h-0 p-0 border-none", "rounded flex items-center justify-between w-full", "hover:bg-transparent", "cursor-pointer select-none", collapsed && "bg-surface-100 dark:bg-surface-900/50"),
|
|
1774
|
+
innerClassName: cls("mt-4", !minimised ? "pt-0" : ""),
|
|
1775
|
+
title: /* @__PURE__ */ jsx("div", {
|
|
1776
|
+
onMouseEnter: () => setIsHovered(true),
|
|
1777
|
+
onMouseLeave: () => setIsHovered(false),
|
|
1778
|
+
className: "flex items-center",
|
|
1779
|
+
children: TitleContent
|
|
1780
|
+
}),
|
|
1781
|
+
children: minimised ? /* @__PURE__ */ jsx("div", {
|
|
1782
|
+
className: cls("mt-4 p-8 bg-surface-accent-200 dark:bg-surface-accent-800 rounded-lg"),
|
|
1783
|
+
style: { minHeight: "50px" }
|
|
1784
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
1785
|
+
className: cls("mt-4", !minimised ? "pt-0" : ""),
|
|
1786
|
+
children
|
|
1787
|
+
})
|
|
1788
|
+
}),
|
|
1789
|
+
!isPreview && !showCaret && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
1790
|
+
className: cls("flex items-center justify-between w-full", "mt-6"),
|
|
1791
|
+
onMouseEnter: () => setIsHovered(true),
|
|
1792
|
+
onMouseLeave: () => setIsHovered(false),
|
|
1793
|
+
children: TitleContent
|
|
1794
|
+
}), !collapsed && (minimised ? /* @__PURE__ */ jsx("div", {
|
|
1795
|
+
className: cls("mt-4 p-8 bg-surface-accent-200 dark:bg-surface-accent-800 rounded-lg"),
|
|
1796
|
+
style: { minHeight: "50px" }
|
|
1797
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
1798
|
+
className: cls("mt-4", !minimised ? "pt-0" : ""),
|
|
1799
|
+
children
|
|
1800
|
+
}))] })
|
|
1801
|
+
]
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
1804
|
+
//#endregion
|
|
1805
|
+
//#region src/components/HomePage/NavigationCard.tsx
|
|
1806
|
+
var NavigationCard = React.memo(function NavigationCard({ name, description, icon, actions, additionalContent, onClick, shrink }) {
|
|
1807
|
+
return /* @__PURE__ */ jsx(Card, {
|
|
1808
|
+
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"),
|
|
1809
|
+
onClick: () => {
|
|
1810
|
+
onClick?.();
|
|
1811
|
+
},
|
|
1812
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1813
|
+
className: "flex flex-col h-full",
|
|
1814
|
+
children: [
|
|
1815
|
+
/* @__PURE__ */ jsxs("div", {
|
|
1816
|
+
className: "flex items-center w-full justify-between mb-1",
|
|
1817
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1818
|
+
className: "flex items-center gap-3",
|
|
1819
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1820
|
+
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",
|
|
1821
|
+
children: icon
|
|
1822
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
1823
|
+
variant: "subtitle1",
|
|
1824
|
+
component: "h2",
|
|
1825
|
+
children: name
|
|
1826
|
+
})]
|
|
1827
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1828
|
+
className: "flex items-center gap-0.5",
|
|
1829
|
+
onClick: (event) => {
|
|
1830
|
+
event.preventDefault();
|
|
1831
|
+
event.stopPropagation();
|
|
1832
|
+
},
|
|
1833
|
+
children: actions
|
|
1834
|
+
})]
|
|
1835
|
+
}),
|
|
1836
|
+
/* @__PURE__ */ jsx("div", {
|
|
1837
|
+
className: "pl-8",
|
|
1838
|
+
children: description && /* @__PURE__ */ jsx(Typography, {
|
|
1839
|
+
variant: "caption",
|
|
1840
|
+
color: "secondary",
|
|
1841
|
+
component: "div",
|
|
1842
|
+
children: /* @__PURE__ */ jsx(Markdown, {
|
|
1843
|
+
source: description,
|
|
1844
|
+
size: "small"
|
|
1845
|
+
})
|
|
1846
|
+
})
|
|
1847
|
+
}),
|
|
1848
|
+
additionalContent && /* @__PURE__ */ jsx("div", {
|
|
1849
|
+
className: "pl-8 pointer-events-none",
|
|
1850
|
+
children: additionalContent
|
|
1851
|
+
}),
|
|
1852
|
+
/* @__PURE__ */ jsx("div", { className: "grow" })
|
|
1853
|
+
]
|
|
1854
|
+
})
|
|
1855
|
+
});
|
|
1856
|
+
});
|
|
1857
|
+
//#endregion
|
|
1858
|
+
//#region src/components/HomePage/NavigationCardBinding.tsx
|
|
1859
|
+
/**
|
|
1860
|
+
* This is the component used in the home page to render a card for each
|
|
1861
|
+
* collection or view.
|
|
1862
|
+
* @group Components
|
|
1863
|
+
* @param view
|
|
1864
|
+
* @param path
|
|
1865
|
+
* @param collection
|
|
1866
|
+
* @param url
|
|
1867
|
+
* @param name
|
|
1868
|
+
* @param description
|
|
1869
|
+
* @param onClick
|
|
1870
|
+
|
|
1871
|
+
*/
|
|
1872
|
+
function NavigationCardBinding({ slug, collection, view, url, name, description, onClick, type, shrink }) {
|
|
1873
|
+
const userConfigurationPersistence = useUserConfigurationPersistence();
|
|
1874
|
+
const collectionIcon = /* @__PURE__ */ jsx(IconForView, {
|
|
1875
|
+
collectionOrView: collection ?? view,
|
|
1876
|
+
size: "small"
|
|
1877
|
+
});
|
|
1878
|
+
const navigate = useNavigate();
|
|
1879
|
+
const context = useRebaseContext();
|
|
1880
|
+
const favourite = (userConfigurationPersistence?.favouritePaths ?? []).includes(slug);
|
|
1881
|
+
const actionsArray = userConfigurationPersistence ? [/* @__PURE__ */ jsx(IconButton, {
|
|
1882
|
+
size: "small",
|
|
1883
|
+
onClick: (e) => {
|
|
1884
|
+
e.preventDefault();
|
|
1885
|
+
e.stopPropagation();
|
|
1886
|
+
if (favourite) userConfigurationPersistence.setFavouritePaths(userConfigurationPersistence.favouritePaths.filter((p) => p !== slug));
|
|
1887
|
+
else userConfigurationPersistence.setFavouritePaths([...userConfigurationPersistence.favouritePaths, slug]);
|
|
1888
|
+
},
|
|
1889
|
+
children: /* @__PURE__ */ jsx(StarIcon, { className: favourite ? "text-secondary" : "text-surface-400 dark:text-surface-500" })
|
|
1890
|
+
}, "favourite")] : [];
|
|
1891
|
+
const pluginCardActions = useSlot("home.collection.actions", {
|
|
1892
|
+
slug,
|
|
1893
|
+
collection,
|
|
1894
|
+
context
|
|
1895
|
+
});
|
|
1896
|
+
const pluginCardInsights = useSlot("home.card.insight", {
|
|
1897
|
+
slug,
|
|
1898
|
+
collection,
|
|
1899
|
+
context
|
|
1900
|
+
});
|
|
1901
|
+
return /* @__PURE__ */ jsx(NavigationCard, {
|
|
1902
|
+
icon: collectionIcon,
|
|
1903
|
+
name,
|
|
1904
|
+
description,
|
|
1905
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [actionsArray, pluginCardActions] }),
|
|
1906
|
+
additionalContent: pluginCardInsights.length > 0 ? /* @__PURE__ */ jsx(Fragment, { children: pluginCardInsights }) : void 0,
|
|
1907
|
+
onClick: () => {
|
|
1908
|
+
onClick?.();
|
|
1909
|
+
navigate(url);
|
|
1910
|
+
if (userConfigurationPersistence) userConfigurationPersistence.setRecentlyVisitedPaths([slug, ...(userConfigurationPersistence.recentlyVisitedPaths ?? []).filter((p) => p !== slug)]);
|
|
1911
|
+
},
|
|
1912
|
+
shrink
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
//#endregion
|
|
1916
|
+
//#region src/components/HomePage/HomePageDnD.tsx
|
|
1917
|
+
var animateLayoutChanges = (args) => defaultAnimateLayoutChanges({
|
|
1918
|
+
...args,
|
|
1919
|
+
wasDragging: true
|
|
1920
|
+
});
|
|
1921
|
+
var dropAnimationConfig = {};
|
|
1922
|
+
var cloneSerializableNavigationEntry = (entry) => {
|
|
1923
|
+
return {
|
|
1924
|
+
id: entry.id,
|
|
1925
|
+
slug: entry.slug,
|
|
1926
|
+
url: entry.url,
|
|
1927
|
+
name: entry.name,
|
|
1928
|
+
type: entry.type,
|
|
1929
|
+
collection: entry.collection ? { ...entry.collection } : void 0,
|
|
1930
|
+
view: entry.view ? { ...entry.view } : void 0,
|
|
1931
|
+
...entry.group && { group: entry.group },
|
|
1932
|
+
...entry.description && { description: entry.description }
|
|
1933
|
+
};
|
|
1934
|
+
};
|
|
1935
|
+
var cloneItemsForDnd = (items) => items.map((g) => ({
|
|
1936
|
+
name: g.name,
|
|
1937
|
+
entries: g.entries.map(cloneSerializableNavigationEntry)
|
|
1938
|
+
}));
|
|
1939
|
+
function SortableNavigationCard({ entry, onClick }) {
|
|
1940
|
+
const { setNodeRef, listeners, attributes, transform, transition, isDragging } = useSortable({
|
|
1941
|
+
id: entry.url,
|
|
1942
|
+
animateLayoutChanges
|
|
1943
|
+
});
|
|
1944
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1945
|
+
ref: setNodeRef,
|
|
1946
|
+
style: {
|
|
1947
|
+
transform: transform ? CSS.Transform.toString(transform) : void 0,
|
|
1948
|
+
transition,
|
|
1949
|
+
opacity: isDragging ? 0 : 1
|
|
1950
|
+
},
|
|
1951
|
+
...attributes,
|
|
1952
|
+
...listeners,
|
|
1953
|
+
children: /* @__PURE__ */ jsx(NavigationCardBinding, {
|
|
1954
|
+
...entry,
|
|
1955
|
+
onClick
|
|
1956
|
+
})
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
function NavigationGroupDroppable({ id, itemIds, children, isPotentialCardDropTarget = false }) {
|
|
1960
|
+
const { setNodeRef } = useDroppable({ id });
|
|
1961
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1962
|
+
ref: setNodeRef,
|
|
1963
|
+
className: cls(isPotentialCardDropTarget ? "p-2 bg-surface-accent-200 dark:bg-surface-accent-800 rounded-lg" : void 0, "transition-all duration-200 ease-in-out"),
|
|
1964
|
+
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
1965
|
+
items: itemIds,
|
|
1966
|
+
strategy: rectSortingStrategy,
|
|
1967
|
+
children
|
|
1968
|
+
})
|
|
1969
|
+
});
|
|
1970
|
+
}
|
|
1971
|
+
function SortableNavigationGroup({ groupName, children, disabled }) {
|
|
1972
|
+
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
1973
|
+
id: groupName,
|
|
1974
|
+
animateLayoutChanges,
|
|
1975
|
+
disabled
|
|
1976
|
+
});
|
|
1977
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1978
|
+
ref: setNodeRef,
|
|
1979
|
+
style: {
|
|
1980
|
+
transform: transform ? CSS.Transform.toString(transform) : void 0,
|
|
1981
|
+
transition,
|
|
1982
|
+
opacity: isDragging ? 0 : 1
|
|
1983
|
+
},
|
|
1984
|
+
...attributes,
|
|
1985
|
+
...listeners,
|
|
1986
|
+
children
|
|
1987
|
+
});
|
|
1988
|
+
}
|
|
1989
|
+
function useHomePageDnd({ items, setItems, disabled, onCardMovedBetweenGroups, onGroupMoved, onNewGroupDrop, onPersist }) {
|
|
1990
|
+
const dndItems = items;
|
|
1991
|
+
const setDndItems = setItems;
|
|
1992
|
+
const [activeId, setActiveId] = useState(null);
|
|
1993
|
+
const [activeIsGroup, setActiveIsGroup] = useState(false);
|
|
1994
|
+
const [currentDraggingGroupId, setCurrentDraggingGroupId] = useState(null);
|
|
1995
|
+
const [dndKitActiveNode, setDndKitActiveNode] = useState(null);
|
|
1996
|
+
const [isDraggingCardOnly, setIsDraggingCardOnly] = useState(false);
|
|
1997
|
+
const [dialogOpenForGroup, setDialogOpenForGroup] = useState(null);
|
|
1998
|
+
const [isHoveringNewGroupDropZone, setIsHoveringNewGroupDropZone] = useState(false);
|
|
1999
|
+
const [pendingNewGroupName, setPendingNewGroupName] = useState(null);
|
|
2000
|
+
const [stateBeforeNewGroup, setStateBeforeNewGroup] = useState(null);
|
|
2001
|
+
const preDragItemsRef = useRef(null);
|
|
2002
|
+
const interimItemsRef = useRef(null);
|
|
2003
|
+
useEffect(() => {
|
|
2004
|
+
interimItemsRef.current = dndItems;
|
|
2005
|
+
}, [dndItems]);
|
|
2006
|
+
const sensors = useSensors(useSensor(MouseSensor, { activationConstraint: { distance: 10 } }), useSensor(TouchSensor, { activationConstraint: {
|
|
2007
|
+
delay: 150,
|
|
2008
|
+
tolerance: 5
|
|
2009
|
+
} }), useSensor(KeyboardSensor));
|
|
2010
|
+
const dndContainers = dndItems.map((g) => g.name);
|
|
2011
|
+
const findDndContainer = useCallback((id) => {
|
|
2012
|
+
if (!id) return void 0;
|
|
2013
|
+
const group = dndItems.find((g) => g.name === id);
|
|
2014
|
+
if (group) return group.name;
|
|
2015
|
+
for (const g of dndItems) if (g.entries.some((e) => e.url === id)) return g.name;
|
|
2016
|
+
}, [dndItems]);
|
|
2017
|
+
const lastOverId = useRef(null);
|
|
2018
|
+
const recentlyMovedToNewContainer = useRef(false);
|
|
2019
|
+
const collisionDetection = useCallback((args) => {
|
|
2020
|
+
if (disabled || !activeId) return [];
|
|
2021
|
+
if (activeIsGroup) {
|
|
2022
|
+
const groups = args.droppableContainers.filter((c) => dndItems.some((g) => g.name === c.id));
|
|
2023
|
+
if (!groups.length) return [];
|
|
2024
|
+
return closestCenter({
|
|
2025
|
+
...args,
|
|
2026
|
+
droppableContainers: groups
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
const pointer = pointerWithin(args);
|
|
2030
|
+
if (pointer.length) {
|
|
2031
|
+
const zone = pointer.find((c) => c.id === "new-group-drop-zone");
|
|
2032
|
+
if (zone) return [zone];
|
|
2033
|
+
const container = pointer.find((c) => dndItems.some((g) => g.name === c.id));
|
|
2034
|
+
if (container) {
|
|
2035
|
+
const itemsIn = dndItems.find((g) => g.name === container.id)?.entries;
|
|
2036
|
+
if (itemsIn?.length) {
|
|
2037
|
+
const closest = closestCorners({
|
|
2038
|
+
...args,
|
|
2039
|
+
droppableContainers: args.droppableContainers.filter((c) => itemsIn.some((e) => e.url === c.id))
|
|
2040
|
+
});
|
|
2041
|
+
if (closest.length) return closest;
|
|
2042
|
+
}
|
|
2043
|
+
return [container];
|
|
2044
|
+
}
|
|
2045
|
+
const first = getFirstCollision(pointer, "id");
|
|
2046
|
+
if (first) return [{ id: first }];
|
|
2047
|
+
}
|
|
2048
|
+
const rects = rectIntersection(args);
|
|
2049
|
+
const zoneRect = rects.find((c) => c.id === "new-group-drop-zone");
|
|
2050
|
+
if (zoneRect) return [zoneRect];
|
|
2051
|
+
let overId = getFirstCollision(rects, "id");
|
|
2052
|
+
if (overId != null) {
|
|
2053
|
+
if (dndItems.some((g) => g.name === overId)) {
|
|
2054
|
+
const itemsIn = dndItems.find((g) => g.name === overId)?.entries;
|
|
2055
|
+
if (itemsIn?.length) {
|
|
2056
|
+
const closestItem = closestCorners({
|
|
2057
|
+
...args,
|
|
2058
|
+
droppableContainers: args.droppableContainers.filter((c) => itemsIn.some((e) => e.url === c.id))
|
|
2059
|
+
})[0]?.id;
|
|
2060
|
+
if (closestItem) overId = closestItem;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
lastOverId.current = overId;
|
|
2064
|
+
return [{ id: overId }];
|
|
2065
|
+
}
|
|
2066
|
+
if (recentlyMovedToNewContainer.current && lastOverId.current && !activeIsGroup) return [{ id: lastOverId.current }];
|
|
2067
|
+
return [];
|
|
2068
|
+
}, [
|
|
2069
|
+
activeId,
|
|
2070
|
+
dndItems,
|
|
2071
|
+
disabled,
|
|
2072
|
+
activeIsGroup
|
|
2073
|
+
]);
|
|
2074
|
+
const handleDragStart = ({ active }) => {
|
|
2075
|
+
setDndKitActiveNode(active);
|
|
2076
|
+
if (disabled) return;
|
|
2077
|
+
preDragItemsRef.current = cloneItemsForDnd(dndItems);
|
|
2078
|
+
const isGroup = dndItems.some((g) => g.name === active.id);
|
|
2079
|
+
if (!active.data.current) active.data.current = {};
|
|
2080
|
+
active.data.current.type = isGroup ? "group" : "item";
|
|
2081
|
+
setActiveId(active.id);
|
|
2082
|
+
setActiveIsGroup(isGroup);
|
|
2083
|
+
setIsDraggingCardOnly(!isGroup);
|
|
2084
|
+
if (isGroup) setCurrentDraggingGroupId(active.id);
|
|
2085
|
+
recentlyMovedToNewContainer.current = false;
|
|
2086
|
+
};
|
|
2087
|
+
const handleDragOver = ({ active, over }) => {
|
|
2088
|
+
if (disabled || !over) return;
|
|
2089
|
+
const activeIdNow = active.id;
|
|
2090
|
+
const overIdNow = over.id;
|
|
2091
|
+
if (activeIdNow === overIdNow) return;
|
|
2092
|
+
if (activeIsGroup) return;
|
|
2093
|
+
const activeCont = findDndContainer(activeIdNow);
|
|
2094
|
+
const overCont = findDndContainer(overIdNow);
|
|
2095
|
+
if (!activeCont) return;
|
|
2096
|
+
if (overCont && activeCont !== overCont) {
|
|
2097
|
+
recentlyMovedToNewContainer.current = true;
|
|
2098
|
+
lastOverId.current = overIdNow;
|
|
2099
|
+
setDndItems((current) => {
|
|
2100
|
+
const newState = cloneItemsForDnd(current);
|
|
2101
|
+
const srcIdx = newState.findIndex((g) => g.name === activeCont);
|
|
2102
|
+
const tgtIdx = newState.findIndex((g) => g.name === overCont);
|
|
2103
|
+
if (srcIdx === -1 || tgtIdx === -1) return current;
|
|
2104
|
+
const src = newState[srcIdx];
|
|
2105
|
+
const tgt = newState[tgtIdx];
|
|
2106
|
+
const idxInSrc = src.entries.findIndex((e) => e.url === activeIdNow);
|
|
2107
|
+
if (idxInSrc === -1) return current;
|
|
2108
|
+
const [moved] = src.entries.splice(idxInSrc, 1);
|
|
2109
|
+
if (overIdNow === overCont) tgt.entries.push(moved);
|
|
2110
|
+
else {
|
|
2111
|
+
const overIdx = tgt.entries.findIndex((e) => e.url === overIdNow);
|
|
2112
|
+
if (overIdx !== -1) tgt.entries.splice(overIdx, 0, moved);
|
|
2113
|
+
else tgt.entries.push(moved);
|
|
2114
|
+
}
|
|
2115
|
+
return newState;
|
|
2116
|
+
});
|
|
2117
|
+
} else if (activeCont === overCont) recentlyMovedToNewContainer.current = false;
|
|
2118
|
+
};
|
|
2119
|
+
const handleDragEnd = ({ active, over }) => {
|
|
2120
|
+
if (disabled || !over) {
|
|
2121
|
+
resetDragState();
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
const activeIdNow = active.id;
|
|
2125
|
+
const overIdNow = over.id;
|
|
2126
|
+
if (activeIsGroup) {
|
|
2127
|
+
if (activeIdNow !== overIdNow && dndItems.some((g) => g.name === overIdNow)) {
|
|
2128
|
+
const from = dndItems.findIndex((g) => g.name === activeIdNow);
|
|
2129
|
+
const to = dndItems.findIndex((g) => g.name === overIdNow);
|
|
2130
|
+
if (from !== -1 && to !== -1) {
|
|
2131
|
+
const newState = arrayMove(dndItems, from, to);
|
|
2132
|
+
setDndItems(newState);
|
|
2133
|
+
onPersist?.(newState);
|
|
2134
|
+
onGroupMoved?.(activeIdNow, from, to);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
} else {
|
|
2138
|
+
const findContainerInState = (id, state) => {
|
|
2139
|
+
const group = state.find((g) => g.name === id);
|
|
2140
|
+
if (group) return group.name;
|
|
2141
|
+
for (const g of state) if (g.entries.some((e) => e.url === id)) return g.name;
|
|
2142
|
+
};
|
|
2143
|
+
const sourceState = preDragItemsRef.current || dndItems;
|
|
2144
|
+
const activeCont = findContainerInState(activeIdNow, sourceState);
|
|
2145
|
+
findDndContainer(overIdNow);
|
|
2146
|
+
if (overIdNow === "new-group-drop-zone") {
|
|
2147
|
+
if (activeCont) {
|
|
2148
|
+
setStateBeforeNewGroup(cloneItemsForDnd(dndItems));
|
|
2149
|
+
const newState = cloneItemsForDnd(dndItems);
|
|
2150
|
+
const srcIdx = newState.findIndex((g) => g.name === activeCont);
|
|
2151
|
+
if (srcIdx !== -1) {
|
|
2152
|
+
const src = newState[srcIdx];
|
|
2153
|
+
const idxInSrc = src.entries.findIndex((e) => e.url === activeIdNow);
|
|
2154
|
+
if (idxInSrc !== -1) {
|
|
2155
|
+
const [dragged] = src.entries.splice(idxInSrc, 1);
|
|
2156
|
+
if (src.entries.length === 0) newState.splice(srcIdx, 1);
|
|
2157
|
+
let tentative = "New Group";
|
|
2158
|
+
let counter = 1;
|
|
2159
|
+
while (newState.some((g) => g.name === tentative)) tentative = `New Group ${counter++}`;
|
|
2160
|
+
newState.push({
|
|
2161
|
+
name: tentative,
|
|
2162
|
+
entries: [dragged]
|
|
2163
|
+
});
|
|
2164
|
+
setDndItems(newState);
|
|
2165
|
+
setPendingNewGroupName(tentative);
|
|
2166
|
+
setDialogOpenForGroup(tentative);
|
|
2167
|
+
onNewGroupDrop?.();
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
} else {
|
|
2172
|
+
const overCont = findDndContainer(overIdNow);
|
|
2173
|
+
if (activeCont === overCont) {
|
|
2174
|
+
const grpIdx = dndItems.findIndex((g) => g.name === activeCont);
|
|
2175
|
+
if (grpIdx !== -1) {
|
|
2176
|
+
const group = dndItems[grpIdx];
|
|
2177
|
+
const oldIdx = group.entries.findIndex((e) => e.url === activeIdNow);
|
|
2178
|
+
let newIdx = group.entries.findIndex((e) => e.url === overIdNow);
|
|
2179
|
+
if (newIdx === -1 && overIdNow === activeCont) newIdx = group.entries.length - 1;
|
|
2180
|
+
if (oldIdx !== -1 && newIdx !== -1 && oldIdx !== newIdx) {
|
|
2181
|
+
const reordered = arrayMove(group.entries, oldIdx, newIdx);
|
|
2182
|
+
const newState = [...dndItems];
|
|
2183
|
+
newState[grpIdx] = {
|
|
2184
|
+
...group,
|
|
2185
|
+
entries: reordered
|
|
2186
|
+
};
|
|
2187
|
+
setDndItems(newState);
|
|
2188
|
+
onPersist?.(newState);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
} else if (overCont && activeCont !== overCont) {
|
|
2192
|
+
const finalState = cloneItemsForDnd(sourceState);
|
|
2193
|
+
const finalOverId = lastOverId.current || overIdNow;
|
|
2194
|
+
const cleanOverCont = findContainerInState(finalOverId, sourceState) || overCont;
|
|
2195
|
+
const srcIdx = finalState.findIndex((g) => g.name === activeCont);
|
|
2196
|
+
const tgtIdx = finalState.findIndex((g) => g.name === cleanOverCont);
|
|
2197
|
+
if (srcIdx !== -1 && tgtIdx !== -1) {
|
|
2198
|
+
const src = finalState[srcIdx];
|
|
2199
|
+
const tgt = finalState[tgtIdx];
|
|
2200
|
+
const idxInSrc = src.entries.findIndex((e) => e.url === activeIdNow);
|
|
2201
|
+
if (idxInSrc !== -1) {
|
|
2202
|
+
const [moved] = src.entries.splice(idxInSrc, 1);
|
|
2203
|
+
if (finalOverId === cleanOverCont) tgt.entries.push(moved);
|
|
2204
|
+
else {
|
|
2205
|
+
const overIdx = tgt.entries.findIndex((e) => e.url === finalOverId);
|
|
2206
|
+
if (overIdx !== -1) tgt.entries.splice(overIdx, 0, moved);
|
|
2207
|
+
else tgt.entries.push(moved);
|
|
2208
|
+
}
|
|
2209
|
+
if (src.entries.length === 0) finalState.splice(srcIdx, 1);
|
|
2210
|
+
setDndItems(finalState);
|
|
2211
|
+
onPersist?.(finalState);
|
|
2212
|
+
onCardMovedBetweenGroups?.(moved);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
} else if (recentlyMovedToNewContainer.current) console.error("Move between containers detected but conditions not met", {
|
|
2216
|
+
activeCont,
|
|
2217
|
+
overCont,
|
|
2218
|
+
activeIdNow,
|
|
2219
|
+
overIdNow
|
|
2220
|
+
});
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
resetDragState();
|
|
2224
|
+
};
|
|
2225
|
+
const resetDragState = () => {
|
|
2226
|
+
setDndKitActiveNode(null);
|
|
2227
|
+
setActiveId(null);
|
|
2228
|
+
setActiveIsGroup(false);
|
|
2229
|
+
setCurrentDraggingGroupId(null);
|
|
2230
|
+
setIsDraggingCardOnly(false);
|
|
2231
|
+
recentlyMovedToNewContainer.current = false;
|
|
2232
|
+
};
|
|
2233
|
+
const handleDragCancel = () => resetDragState();
|
|
2234
|
+
const handleRenameGroup = (oldName, newName) => {
|
|
2235
|
+
setDndItems((current) => {
|
|
2236
|
+
const idx = current.findIndex((g) => g.name === oldName);
|
|
2237
|
+
if (idx === -1) return current;
|
|
2238
|
+
if (current.some((g) => g.name === newName && g.name !== oldName)) return current;
|
|
2239
|
+
const updated = [...current];
|
|
2240
|
+
updated[idx] = {
|
|
2241
|
+
...updated[idx],
|
|
2242
|
+
name: newName
|
|
2243
|
+
};
|
|
2244
|
+
onPersist?.(updated);
|
|
2245
|
+
return updated;
|
|
2246
|
+
});
|
|
2247
|
+
setPendingNewGroupName(null);
|
|
2248
|
+
setStateBeforeNewGroup(null);
|
|
2249
|
+
setDialogOpenForGroup(null);
|
|
2250
|
+
};
|
|
2251
|
+
const handleDialogClose = () => {
|
|
2252
|
+
if (pendingNewGroupName && dialogOpenForGroup === pendingNewGroupName && stateBeforeNewGroup) setDndItems(stateBeforeNewGroup);
|
|
2253
|
+
setPendingNewGroupName(null);
|
|
2254
|
+
setStateBeforeNewGroup(null);
|
|
2255
|
+
setDialogOpenForGroup(null);
|
|
2256
|
+
};
|
|
2257
|
+
return {
|
|
2258
|
+
sensors,
|
|
2259
|
+
collisionDetection,
|
|
2260
|
+
onDragStart: handleDragStart,
|
|
2261
|
+
onDragOver: handleDragOver,
|
|
2262
|
+
onDragEnd: handleDragEnd,
|
|
2263
|
+
onDragCancel: handleDragCancel,
|
|
2264
|
+
dropAnimation: dropAnimationConfig,
|
|
2265
|
+
activeItemForOverlay: disabled || !activeId || activeIsGroup ? null : dndItems.flatMap((g) => g.entries).find((e) => e.url === activeId) || null,
|
|
2266
|
+
activeGroupData: disabled || !activeId || !activeIsGroup ? null : dndItems.find((g) => g.name === activeId) || null,
|
|
2267
|
+
draggingGroupId: currentDraggingGroupId,
|
|
2268
|
+
containers: dndContainers,
|
|
2269
|
+
dndKitActiveNode,
|
|
2270
|
+
isDraggingCardOnly,
|
|
2271
|
+
dialogOpenForGroup,
|
|
2272
|
+
setDialogOpenForGroup,
|
|
2273
|
+
handleRenameGroup,
|
|
2274
|
+
handleDialogClose,
|
|
2275
|
+
isHoveringNewGroupDropZone,
|
|
2276
|
+
setIsHoveringNewGroupDropZone
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
function NewGroupDropZone({ disabled, setIsHovering }) {
|
|
2280
|
+
const { setNodeRef, isOver } = useDroppable({
|
|
2281
|
+
id: "new-group-drop-zone",
|
|
2282
|
+
disabled
|
|
2283
|
+
});
|
|
2284
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
2285
|
+
useDndMonitor({
|
|
2286
|
+
onDragStart({ active }) {
|
|
2287
|
+
if (disabled) return;
|
|
2288
|
+
const tp = active.data.current?.type;
|
|
2289
|
+
setIsVisible(tp === "item");
|
|
2290
|
+
},
|
|
2291
|
+
onDragEnd() {
|
|
2292
|
+
setIsVisible(false);
|
|
2293
|
+
},
|
|
2294
|
+
onDragCancel() {
|
|
2295
|
+
setIsVisible(false);
|
|
2296
|
+
}
|
|
2297
|
+
});
|
|
2298
|
+
useEffect(() => {
|
|
2299
|
+
setIsHovering(isOver && isVisible);
|
|
2300
|
+
}, [
|
|
2301
|
+
isOver,
|
|
2302
|
+
isVisible,
|
|
2303
|
+
setIsHovering
|
|
2304
|
+
]);
|
|
2305
|
+
if (!isVisible || disabled) return null;
|
|
2306
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2307
|
+
ref: setNodeRef,
|
|
2308
|
+
className: cls("fixed right-8 top-1/2 -translate-y-1/2 w-[200px] h-[120px] border border-dashed rounded-lg flex items-center justify-center transition-all", isOver ? "bg-surface-accent-100 dark:bg-surface-accent-800 border-surface-300 dark:border-surface-600" : "bg-surface-50 dark:bg-surface-900 border-surface-200 dark:border-surface-700"),
|
|
2309
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2310
|
+
className: "text-center p-4",
|
|
2311
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
2312
|
+
className: "block font-medium text-sm",
|
|
2313
|
+
children: "Drop here to create a new group"
|
|
2314
|
+
})
|
|
2315
|
+
})
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
//#endregion
|
|
2319
|
+
//#region src/components/HomePage/RenameGroupDialog.tsx
|
|
2320
|
+
function RenameGroupDialog({ open, initialName, existingGroupNames, onClose, onRename }) {
|
|
2321
|
+
const { t } = useTranslation();
|
|
2322
|
+
const [name, setName] = useState(initialName);
|
|
2323
|
+
const [error, setError] = useState(null);
|
|
2324
|
+
const inputRef = useRef(null);
|
|
2325
|
+
useEffect(() => {
|
|
2326
|
+
if (open) {
|
|
2327
|
+
setName(initialName);
|
|
2328
|
+
setError(null);
|
|
2329
|
+
setTimeout(() => {
|
|
2330
|
+
if (inputRef.current) {
|
|
2331
|
+
inputRef.current.focus();
|
|
2332
|
+
inputRef.current.select();
|
|
2333
|
+
}
|
|
2334
|
+
}, 100);
|
|
2335
|
+
}
|
|
2336
|
+
}, [initialName, open]);
|
|
2337
|
+
const handleNameChange = (event) => {
|
|
2338
|
+
const newName = event.target.value;
|
|
2339
|
+
setName(newName);
|
|
2340
|
+
if (!newName.trim()) setError(t("group_name_empty_error"));
|
|
2341
|
+
else if (existingGroupNames.includes(newName.trim())) setError(t("group_name_exists_error"));
|
|
2342
|
+
else setError(null);
|
|
2343
|
+
};
|
|
2344
|
+
const handleSave = () => {
|
|
2345
|
+
const trimmedName = name.trim();
|
|
2346
|
+
if (!trimmedName) {
|
|
2347
|
+
setError(t("group_name_empty_error"));
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
if (existingGroupNames.includes(trimmedName)) {
|
|
2351
|
+
setError(t("group_name_exists_error"));
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
if (!error) {
|
|
2355
|
+
onRename(trimmedName);
|
|
2356
|
+
onClose();
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
const handleKeyDown = (event) => {
|
|
2360
|
+
if (event.key === "Enter") {
|
|
2361
|
+
event.preventDefault();
|
|
2362
|
+
const trimmedName = name.trim();
|
|
2363
|
+
let currentError = null;
|
|
2364
|
+
if (!trimmedName) currentError = t("group_name_empty_error");
|
|
2365
|
+
else if (existingGroupNames.includes(trimmedName)) currentError = t("group_name_exists_error");
|
|
2366
|
+
if (!currentError && trimmedName) handleSave();
|
|
2367
|
+
else if (currentError) setError(currentError);
|
|
2368
|
+
}
|
|
2369
|
+
};
|
|
2370
|
+
if (!open) return null;
|
|
2371
|
+
return /* @__PURE__ */ jsxs(Dialog, {
|
|
2372
|
+
open,
|
|
2373
|
+
children: [
|
|
2374
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: t("rename_group") }),
|
|
2375
|
+
/* @__PURE__ */ jsxs(DialogContent, { children: [/* @__PURE__ */ jsx(TextField, {
|
|
2376
|
+
inputRef,
|
|
2377
|
+
label: t("group_name_label"),
|
|
2378
|
+
value: name,
|
|
2379
|
+
onChange: handleNameChange,
|
|
2380
|
+
onKeyDown: handleKeyDown,
|
|
2381
|
+
error: !!error,
|
|
2382
|
+
"aria-describedby": error ? "group-name-error" : void 0
|
|
2383
|
+
}), error && /* @__PURE__ */ jsx("p", {
|
|
2384
|
+
id: "group-name-error",
|
|
2385
|
+
style: { display: "none" },
|
|
2386
|
+
children: error
|
|
2387
|
+
})] }),
|
|
2388
|
+
/* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
|
|
2389
|
+
onClick: onClose,
|
|
2390
|
+
variant: "text",
|
|
2391
|
+
children: t("cancel")
|
|
2392
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
2393
|
+
onClick: handleSave,
|
|
2394
|
+
disabled: !!error || !name.trim(),
|
|
2395
|
+
children: t("save")
|
|
2396
|
+
})] })
|
|
2397
|
+
]
|
|
2398
|
+
});
|
|
2399
|
+
}
|
|
2400
|
+
//#endregion
|
|
2401
|
+
//#region src/components/HomePage/ContentHomePage.tsx
|
|
2402
|
+
var DEFAULT_GROUP_NAME = "Views";
|
|
2403
|
+
var ADMIN_GROUP_NAME = "Admin";
|
|
2404
|
+
function ContentHomePage({ additionalActions, additionalChildrenStart, additionalChildrenEnd, sections, hiddenGroups }) {
|
|
2405
|
+
const context = useCMSContext();
|
|
2406
|
+
const { navigationStateController } = context;
|
|
2407
|
+
const customizationController = useCustomizationController();
|
|
2408
|
+
const adminModeController = useAdminModeController();
|
|
2409
|
+
const registry = useRebaseRegistry();
|
|
2410
|
+
const { resolvedSlots } = customizationController;
|
|
2411
|
+
const breadcrumbs = useBreadcrumbsController();
|
|
2412
|
+
const { t } = useTranslation();
|
|
2413
|
+
useEffect(() => {
|
|
2414
|
+
breadcrumbs.set({ breadcrumbs: [] });
|
|
2415
|
+
}, [breadcrumbs.set]);
|
|
2416
|
+
const { allowDragAndDrop = false, navigationEntries: unFilteredNavigationEntries = [], groups: unFilteredGroupOrder = [], onNavigationEntriesUpdate = () => {} } = navigationStateController.topLevelNavigation || {};
|
|
2417
|
+
const studioViewSlugs = useMemo(() => {
|
|
2418
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
2419
|
+
(registry.studioConfig?.devViews ?? []).forEach((v) => slugs.add(v.slug));
|
|
2420
|
+
if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
|
|
2421
|
+
return slugs;
|
|
2422
|
+
}, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
|
|
2423
|
+
const rawNavigationEntries = useMemo(() => {
|
|
2424
|
+
if (adminModeController.mode === "studio") return unFilteredNavigationEntries.filter((e) => e.type === "view" && studioViewSlugs.has(e.slug) || e.type === "admin");
|
|
2425
|
+
return unFilteredNavigationEntries.filter((e) => e.type !== "view" || !studioViewSlugs.has(e.slug));
|
|
2426
|
+
}, [
|
|
2427
|
+
unFilteredNavigationEntries,
|
|
2428
|
+
adminModeController.mode,
|
|
2429
|
+
studioViewSlugs
|
|
2430
|
+
]);
|
|
2431
|
+
const groupOrderFromNavController = useMemo(() => {
|
|
2432
|
+
const entryGroups = new Set(rawNavigationEntries.map((e) => e.group).filter(Boolean));
|
|
2433
|
+
return unFilteredGroupOrder.filter((g) => entryGroups.has(g));
|
|
2434
|
+
}, [unFilteredGroupOrder, rawNavigationEntries]);
|
|
2435
|
+
const fuse = useRef(null);
|
|
2436
|
+
const [filteredUrls, setFilteredUrls] = useState(null);
|
|
2437
|
+
const performingSearch = Boolean(filteredUrls);
|
|
2438
|
+
const filteredNavigationEntries = useMemo(() => {
|
|
2439
|
+
return filteredUrls ? rawNavigationEntries.filter((e) => filteredUrls.includes(e.url)) : rawNavigationEntries;
|
|
2440
|
+
}, [filteredUrls, rawNavigationEntries]);
|
|
2441
|
+
useEffect(() => {
|
|
2442
|
+
fuse.current = new Fuse(rawNavigationEntries, { keys: [
|
|
2443
|
+
"name",
|
|
2444
|
+
"description",
|
|
2445
|
+
"group",
|
|
2446
|
+
"path"
|
|
2447
|
+
] });
|
|
2448
|
+
}, [rawNavigationEntries]);
|
|
2449
|
+
const updateSearch = useCallback((v) => {
|
|
2450
|
+
if (!v?.trim()) {
|
|
2451
|
+
setFilteredUrls(null);
|
|
2452
|
+
return;
|
|
2453
|
+
}
|
|
2454
|
+
const results = fuse.current?.search(v.trim());
|
|
2455
|
+
setFilteredUrls(results ? results.map((x) => x.item.url) : []);
|
|
2456
|
+
}, []);
|
|
2457
|
+
const [items, setItems] = useState([]);
|
|
2458
|
+
const [adminGroupData, setAdminGroupData] = useState(null);
|
|
2459
|
+
const isDndDirtyRef = useRef(false);
|
|
2460
|
+
const dndDirtyTimeoutRef = useRef(null);
|
|
2461
|
+
const processedGroups = useMemo(() => {
|
|
2462
|
+
const src = filteredNavigationEntries;
|
|
2463
|
+
const entriesByGroup = {};
|
|
2464
|
+
src.forEach((e) => {
|
|
2465
|
+
const g = e.type === "admin" ? ADMIN_GROUP_NAME : e.group ?? "Views";
|
|
2466
|
+
(entriesByGroup[g] ??= []).push(e);
|
|
2467
|
+
});
|
|
2468
|
+
const hasPluginAdditionalCards = customizationController.resolvedSlots.some((s) => s.slot === "home.cards");
|
|
2469
|
+
let allProcessed;
|
|
2470
|
+
if (performingSearch) allProcessed = [...new Set(src.map((e) => e.group ?? "Views"))].map((name) => ({
|
|
2471
|
+
name,
|
|
2472
|
+
entries: entriesByGroup[name] || []
|
|
2473
|
+
})).filter((g) => g.entries.length);
|
|
2474
|
+
else {
|
|
2475
|
+
allProcessed = groupOrderFromNavController.map((g) => ({
|
|
2476
|
+
name: g,
|
|
2477
|
+
entries: entriesByGroup[g] || []
|
|
2478
|
+
}));
|
|
2479
|
+
Object.keys(entriesByGroup).forEach((g) => {
|
|
2480
|
+
if (!groupOrderFromNavController.includes(g)) allProcessed.push({
|
|
2481
|
+
name: g,
|
|
2482
|
+
entries: entriesByGroup[g]
|
|
2483
|
+
});
|
|
2484
|
+
});
|
|
2485
|
+
if (hasPluginAdditionalCards && !allProcessed.some((g) => g.name === "Views")) allProcessed.push({
|
|
2486
|
+
name: DEFAULT_GROUP_NAME,
|
|
2487
|
+
entries: []
|
|
2488
|
+
});
|
|
2489
|
+
allProcessed = allProcessed.filter((g) => g.entries.length || g.name === "Views" && hasPluginAdditionalCards);
|
|
2490
|
+
}
|
|
2491
|
+
return {
|
|
2492
|
+
adminGroupData: allProcessed.find((g) => g.name === "Admin") || null,
|
|
2493
|
+
items: allProcessed.filter((g) => g.name !== "Admin" && !hiddenGroups?.includes(g.name))
|
|
2494
|
+
};
|
|
2495
|
+
}, [
|
|
2496
|
+
filteredNavigationEntries,
|
|
2497
|
+
performingSearch,
|
|
2498
|
+
groupOrderFromNavController,
|
|
2499
|
+
customizationController.resolvedSlots,
|
|
2500
|
+
hiddenGroups
|
|
2501
|
+
]);
|
|
2502
|
+
useEffect(() => {
|
|
2503
|
+
if (isDndDirtyRef.current) return;
|
|
2504
|
+
setAdminGroupData(processedGroups.adminGroupData);
|
|
2505
|
+
setItems(processedGroups.items);
|
|
2506
|
+
}, [processedGroups]);
|
|
2507
|
+
const updateItems = (updater) => {
|
|
2508
|
+
setItems(updater);
|
|
2509
|
+
};
|
|
2510
|
+
const persistNavigationGroups = (latest) => {
|
|
2511
|
+
isDndDirtyRef.current = true;
|
|
2512
|
+
if (dndDirtyTimeoutRef.current) clearTimeout(dndDirtyTimeoutRef.current);
|
|
2513
|
+
dndDirtyTimeoutRef.current = setTimeout(() => {
|
|
2514
|
+
isDndDirtyRef.current = false;
|
|
2515
|
+
}, 1e3);
|
|
2516
|
+
const draggable = latest.map((g) => ({
|
|
2517
|
+
name: g.name,
|
|
2518
|
+
entries: g.entries.map((e) => e.slug)
|
|
2519
|
+
}));
|
|
2520
|
+
onNavigationEntriesUpdate(adminGroupData ? [...draggable, {
|
|
2521
|
+
name: adminGroupData.name,
|
|
2522
|
+
entries: adminGroupData.entries.map((e) => e.slug)
|
|
2523
|
+
}] : draggable);
|
|
2524
|
+
};
|
|
2525
|
+
const { isGroupCollapsed, toggleGroupCollapsed } = useCollapsedGroups(useMemo(() => [...items.map((item) => item.name), ...adminGroupData ? [adminGroupData.name] : []], [items, adminGroupData]), "home", useMemo(() => buildCollapsedDefaults(registry.cmsConfig?.navigationGroupMappings, "home"), [registry.cmsConfig?.navigationGroupMappings]));
|
|
2526
|
+
const { sensors, collisionDetection, onDragStart, onDragOver, onDragEnd, dropAnimation, activeItemForOverlay, activeGroupData, draggingGroupId, containers, dndKitActiveNode, onDragCancel, isDraggingCardOnly, dialogOpenForGroup, setDialogOpenForGroup, handleRenameGroup, handleDialogClose, isHoveringNewGroupDropZone, setIsHoveringNewGroupDropZone } = useHomePageDnd({
|
|
2527
|
+
items,
|
|
2528
|
+
setItems: updateItems,
|
|
2529
|
+
disabled: !allowDragAndDrop || performingSearch,
|
|
2530
|
+
onPersist: persistNavigationGroups,
|
|
2531
|
+
onGroupMoved: (g) => context.analyticsController?.onAnalyticsEvent?.("home_move_group", { name: g }),
|
|
2532
|
+
onCardMovedBetweenGroups: (card) => context.analyticsController?.onAnalyticsEvent?.("home_move_card", { id: card.id }),
|
|
2533
|
+
onNewGroupDrop: () => context.analyticsController?.onAnalyticsEvent?.("home_drop_new_group")
|
|
2534
|
+
});
|
|
2535
|
+
const { containerRef, direction } = useRestoreScroll();
|
|
2536
|
+
const dndDisabled = !allowDragAndDrop || performingSearch;
|
|
2537
|
+
const dndModifiers = dndKitActiveNode?.data.current?.type === "group" ? [restrictToVerticalAxis, restrictToWindowEdges] : [restrictToWindowEdges];
|
|
2538
|
+
const sectionProps = { context };
|
|
2539
|
+
const additionalPluginChildrenStart = useSlot("home.children.start", sectionProps);
|
|
2540
|
+
const additionalPluginChildrenEnd = useSlot("home.children.end", sectionProps);
|
|
2541
|
+
const additionalPluginActions = useSlot("home.actions", sectionProps);
|
|
2542
|
+
const homeCardContributions = useMemo(() => {
|
|
2543
|
+
return resolvedSlots.filter((s) => s.slot === "home.cards").sort((a, b) => (a.order ?? 50) - (b.order ?? 50));
|
|
2544
|
+
}, [resolvedSlots]);
|
|
2545
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2546
|
+
ref: containerRef,
|
|
2547
|
+
className: "py-2 overflow-auto h-full w-full bg-surface-50 dark:bg-surface-800",
|
|
2548
|
+
children: [/* @__PURE__ */ jsxs(Container, {
|
|
2549
|
+
maxWidth: "6xl",
|
|
2550
|
+
children: [
|
|
2551
|
+
/* @__PURE__ */ jsx("div", {
|
|
2552
|
+
className: "mb-4 flex flex-col gap-2",
|
|
2553
|
+
children: /* @__PURE__ */ jsx(SchemaDriftBanner, {})
|
|
2554
|
+
}),
|
|
2555
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2556
|
+
className: "w-full sticky py-4 transition-all duration-400 ease-in-out top-0 z-10 flex flex-row gap-4",
|
|
2557
|
+
style: { top: direction === "down" ? -84 : 0 },
|
|
2558
|
+
children: [
|
|
2559
|
+
/* @__PURE__ */ jsx(SearchBar, {
|
|
2560
|
+
onTextSearch: updateSearch,
|
|
2561
|
+
placeholder: t("search_collections"),
|
|
2562
|
+
autoFocus: true,
|
|
2563
|
+
size: "small",
|
|
2564
|
+
innerClassName: "w-full",
|
|
2565
|
+
className: "w-full grow"
|
|
2566
|
+
}),
|
|
2567
|
+
additionalActions,
|
|
2568
|
+
additionalPluginActions
|
|
2569
|
+
]
|
|
2570
|
+
}),
|
|
2571
|
+
/* @__PURE__ */ jsx(FavouritesView, { hidden: performingSearch }),
|
|
2572
|
+
additionalChildrenStart,
|
|
2573
|
+
additionalPluginChildrenStart,
|
|
2574
|
+
/* @__PURE__ */ jsxs(DndContext, {
|
|
2575
|
+
sensors,
|
|
2576
|
+
collisionDetection,
|
|
2577
|
+
measuring: { droppable: {
|
|
2578
|
+
strategy: MeasuringStrategy.Always,
|
|
2579
|
+
frequency: 500
|
|
2580
|
+
} },
|
|
2581
|
+
onDragStart,
|
|
2582
|
+
onDragOver,
|
|
2583
|
+
onDragEnd,
|
|
2584
|
+
onDragCancel,
|
|
2585
|
+
modifiers: dndModifiers,
|
|
2586
|
+
children: [
|
|
2587
|
+
/* @__PURE__ */ jsx(SortableContext, {
|
|
2588
|
+
items: containers,
|
|
2589
|
+
strategy: verticalListSortingStrategy,
|
|
2590
|
+
children: items.map((groupData, groupIndex) => {
|
|
2591
|
+
const groupKey = groupData.name;
|
|
2592
|
+
const entriesInGroup = groupData.entries;
|
|
2593
|
+
const homeCardsProps = {
|
|
2594
|
+
group: groupKey === "Views" ? void 0 : groupKey,
|
|
2595
|
+
context
|
|
2596
|
+
};
|
|
2597
|
+
const additionalCards = homeCardContributions.map((s, i) => /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(s.Component, {
|
|
2598
|
+
...homeCardsProps,
|
|
2599
|
+
...s.props ?? {}
|
|
2600
|
+
}) }, `home_cards_${groupKey}_${i}`));
|
|
2601
|
+
if (entriesInGroup.length === 0 && (additionalCards.length === 0 || performingSearch)) return null;
|
|
2602
|
+
return /* @__PURE__ */ jsx(SortableNavigationGroup, {
|
|
2603
|
+
groupName: groupKey,
|
|
2604
|
+
disabled: dndDisabled,
|
|
2605
|
+
children: /* @__PURE__ */ jsx(NavigationGroup, {
|
|
2606
|
+
group: groupKey === "Views" ? void 0 : groupKey,
|
|
2607
|
+
minimised: draggingGroupId === groupKey && !isDraggingCardOnly,
|
|
2608
|
+
isPotentialCardDropTarget: isDraggingCardOnly,
|
|
2609
|
+
dndDisabled,
|
|
2610
|
+
onEditGroup: () => {
|
|
2611
|
+
if (dndDisabled) return;
|
|
2612
|
+
setDialogOpenForGroup(groupKey);
|
|
2613
|
+
},
|
|
2614
|
+
collapsed: isGroupCollapsed(groupKey),
|
|
2615
|
+
onToggleCollapsed: () => toggleGroupCollapsed(groupKey),
|
|
2616
|
+
children: /* @__PURE__ */ jsx(NavigationGroupDroppable, {
|
|
2617
|
+
id: groupKey,
|
|
2618
|
+
itemIds: entriesInGroup.map((e) => e.url),
|
|
2619
|
+
isPotentialCardDropTarget: isDraggingCardOnly,
|
|
2620
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2621
|
+
className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 ",
|
|
2622
|
+
children: [entriesInGroup.map((entry) => /* @__PURE__ */ jsx(SortableNavigationCard, {
|
|
2623
|
+
entry,
|
|
2624
|
+
onClick: () => {
|
|
2625
|
+
let event = "unmapped_event";
|
|
2626
|
+
if (entry.type === "collection") event = "home_navigate_to_collection";
|
|
2627
|
+
else if (entry.type === "view") event = "home_navigate_to_view";
|
|
2628
|
+
else if (entry.type === "admin") event = "home_navigate_to_admin_view";
|
|
2629
|
+
context.analyticsController?.onAnalyticsEvent?.(event, { path: entry.slug });
|
|
2630
|
+
}
|
|
2631
|
+
}, entry.url)), !performingSearch && groupKey.toLowerCase() !== "Admin".toLowerCase() && additionalCards]
|
|
2632
|
+
})
|
|
2633
|
+
})
|
|
2634
|
+
})
|
|
2635
|
+
}, `group-${groupIndex}`);
|
|
2636
|
+
})
|
|
2637
|
+
}, containers.join(",")),
|
|
2638
|
+
/* @__PURE__ */ jsx(NewGroupDropZone, {
|
|
2639
|
+
disabled: dndDisabled,
|
|
2640
|
+
setIsHovering: setIsHoveringNewGroupDropZone
|
|
2641
|
+
}),
|
|
2642
|
+
/* @__PURE__ */ jsx(DragOverlay, {
|
|
2643
|
+
adjustScale: false,
|
|
2644
|
+
dropAnimation,
|
|
2645
|
+
children: activeGroupData && draggingGroupId === activeGroupData.name ? /* @__PURE__ */ jsx("div", {
|
|
2646
|
+
className: "rounded-lg bg-transparent",
|
|
2647
|
+
style: {
|
|
2648
|
+
padding: 0,
|
|
2649
|
+
margin: 0
|
|
2650
|
+
},
|
|
2651
|
+
children: /* @__PURE__ */ jsx(NavigationGroup, {
|
|
2652
|
+
group: activeGroupData.name === "Views" ? void 0 : activeGroupData.name,
|
|
2653
|
+
isPreview: false,
|
|
2654
|
+
minimised: true
|
|
2655
|
+
})
|
|
2656
|
+
}) : activeItemForOverlay ? /* @__PURE__ */ jsx(NavigationCardBinding, {
|
|
2657
|
+
...activeItemForOverlay,
|
|
2658
|
+
shrink: isHoveringNewGroupDropZone
|
|
2659
|
+
}) : null
|
|
2660
|
+
})
|
|
2661
|
+
]
|
|
2662
|
+
}),
|
|
2663
|
+
!performingSearch && adminGroupData && /* @__PURE__ */ jsx(NavigationGroup, {
|
|
2664
|
+
group: adminGroupData.name,
|
|
2665
|
+
collapsed: isGroupCollapsed(adminGroupData.name),
|
|
2666
|
+
onToggleCollapsed: () => toggleGroupCollapsed(adminGroupData.name),
|
|
2667
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2668
|
+
className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 ",
|
|
2669
|
+
children: adminGroupData.entries.map((entry) => /* @__PURE__ */ jsx(NavigationCardBinding, {
|
|
2670
|
+
...entry,
|
|
2671
|
+
onClick: () => {
|
|
2672
|
+
let event = "unmapped_event";
|
|
2673
|
+
if (entry.type === "collection") event = "home_navigate_to_collection";
|
|
2674
|
+
else if (entry.type === "view") event = "home_navigate_to_view";
|
|
2675
|
+
else if (entry.type === "admin") event = "home_navigate_to_admin_view";
|
|
2676
|
+
context.analyticsController?.onAnalyticsEvent?.(event, { path: entry.slug });
|
|
2677
|
+
}
|
|
2678
|
+
}, entry.url))
|
|
2679
|
+
})
|
|
2680
|
+
}),
|
|
2681
|
+
sections && sections.map((section) => /* @__PURE__ */ jsx(NavigationGroup, {
|
|
2682
|
+
group: section.title,
|
|
2683
|
+
children: section.children
|
|
2684
|
+
}, section.key)),
|
|
2685
|
+
additionalPluginChildrenEnd,
|
|
2686
|
+
additionalChildrenEnd
|
|
2687
|
+
]
|
|
2688
|
+
}), dialogOpenForGroup && /* @__PURE__ */ jsx(RenameGroupDialog, {
|
|
2689
|
+
open: true,
|
|
2690
|
+
initialName: dialogOpenForGroup,
|
|
2691
|
+
existingGroupNames: items.map((g) => g.name).filter((n) => n !== dialogOpenForGroup),
|
|
2692
|
+
onClose: handleDialogClose,
|
|
2693
|
+
onRename: (newName) => {
|
|
2694
|
+
handleRenameGroup(dialogOpenForGroup, newName);
|
|
2695
|
+
}
|
|
2696
|
+
})]
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
//#endregion
|
|
2700
|
+
//#region src/components/CollectionEditorDialogs.tsx
|
|
2701
|
+
var CollectionEditorDialog = lazy(() => import("./CollectionEditorDialog-D7odTn0N.js").then((n) => n.r).then((m) => ({ default: m.CollectionEditorDialog })));
|
|
2702
|
+
var PropertyFormDialog = lazy(() => import("./PropertyEditView-i51tFOfM.js").then((n) => n.t).then((m) => ({ default: m.PropertyFormDialog })));
|
|
2703
|
+
/**
|
|
2704
|
+
* Renders the CollectionEditorDialog and PropertyFormDialog inside
|
|
2705
|
+
* the RebaseShell tree where CMS-internal contexts
|
|
2706
|
+
* (CollectionRegistryContext, NavigationStateContext, UrlContext)
|
|
2707
|
+
* are available.
|
|
2708
|
+
*
|
|
2709
|
+
* Both dialog components are loaded dynamically via React.lazy so that
|
|
2710
|
+
* production CMS bundles that never open the collection editor don't pay
|
|
2711
|
+
* the download/parse cost up-front.
|
|
2712
|
+
*
|
|
2713
|
+
* The dialog state (open/close, props) is managed by ConfigControllerProvider
|
|
2714
|
+
* (a root-scope plugin provider that wraps the shell) and exposed via
|
|
2715
|
+
* CollectionEditorDialogsContext.
|
|
2716
|
+
*/
|
|
2717
|
+
function CollectionEditorDialogs() {
|
|
2718
|
+
const state = useCollectionEditorDialogsState();
|
|
2719
|
+
if (!state) return null;
|
|
2720
|
+
const { collectionDialogProps, propertyDialogProps } = state;
|
|
2721
|
+
if (!(collectionDialogProps?.open || propertyDialogProps?.open)) return null;
|
|
2722
|
+
return /* @__PURE__ */ jsxs(Suspense, {
|
|
2723
|
+
fallback: null,
|
|
2724
|
+
children: [collectionDialogProps?.open && /* @__PURE__ */ jsx(CollectionEditorDialog, {
|
|
2725
|
+
open: false,
|
|
2726
|
+
isNewCollection: false,
|
|
2727
|
+
configController: void 0,
|
|
2728
|
+
handleClose: () => {},
|
|
2729
|
+
...collectionDialogProps
|
|
2730
|
+
}), propertyDialogProps?.open && /* @__PURE__ */ jsx(PropertyFormDialog, {
|
|
2731
|
+
open: false,
|
|
2732
|
+
existingProperty: false,
|
|
2733
|
+
autoOpenTypeSelect: false,
|
|
2734
|
+
inArray: false,
|
|
2735
|
+
allowDataInference: false,
|
|
2736
|
+
propertyConfigs: {},
|
|
2737
|
+
...propertyDialogProps
|
|
2738
|
+
})]
|
|
2739
|
+
});
|
|
2740
|
+
}
|
|
2741
|
+
//#endregion
|
|
2742
|
+
//#region src/components/RebaseRouteDefs.tsx
|
|
2743
|
+
function SettingsView() {
|
|
2744
|
+
const { t } = useTranslation();
|
|
2745
|
+
const breadcrumbs = useBreadcrumbsController();
|
|
2746
|
+
useEffect(() => {
|
|
2747
|
+
breadcrumbs.set({ breadcrumbs: [{
|
|
2748
|
+
title: t("account_settings"),
|
|
2749
|
+
url: "/settings"
|
|
2750
|
+
}] });
|
|
2751
|
+
}, []);
|
|
2752
|
+
return /* @__PURE__ */ jsx(UserSettingsView, {});
|
|
2753
|
+
}
|
|
2754
|
+
/**
|
|
2755
|
+
* Route definitions for the CMS.
|
|
2756
|
+
*
|
|
2757
|
+
* Defines all standard routes: home, studio home, collection view,
|
|
2758
|
+
* settings, debug, custom views, and a catch-all 404.
|
|
2759
|
+
*
|
|
2760
|
+
* **Independently usable**: Use this when you want Rebase routes
|
|
2761
|
+
* inside your own layout/navigation setup.
|
|
2762
|
+
*
|
|
2763
|
+
* @example
|
|
2764
|
+
* ```tsx
|
|
2765
|
+
* <RebaseNavigation>
|
|
2766
|
+
* <RebaseRouteDefs layout={<MyCustomLayout />}>
|
|
2767
|
+
* <Route path="/my-custom" element={<MyView />} />
|
|
2768
|
+
* </RebaseRouteDefs>
|
|
2769
|
+
* </RebaseNavigation>
|
|
2770
|
+
* ```
|
|
2771
|
+
*/
|
|
2772
|
+
function RebaseRouteDefs({ children, layout }) {
|
|
2773
|
+
const registry = useRebaseRegistry();
|
|
2774
|
+
const navigationStateController = useNavigationStateController();
|
|
2775
|
+
const ResolvedHomePage = useComponentOverride("HomePage", ContentHomePage);
|
|
2776
|
+
const cmsHomePage = registry.cmsConfig?.homePage ?? /* @__PURE__ */ jsx(Suspense, {
|
|
2777
|
+
fallback: /* @__PURE__ */ jsx(CircularProgressCenter, {}),
|
|
2778
|
+
children: /* @__PURE__ */ jsx(ResolvedHomePage, {})
|
|
2779
|
+
});
|
|
2780
|
+
const studioHomePage = registry.studioConfig?.homePage;
|
|
2781
|
+
const combinedViews = useMemo(() => [...navigationStateController.views ?? [], ...navigationStateController.adminViews ?? []], [navigationStateController.views, navigationStateController.adminViews]);
|
|
2782
|
+
const routeContents = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2783
|
+
/* @__PURE__ */ jsx(Route, {
|
|
2784
|
+
path: "/",
|
|
2785
|
+
element: cmsHomePage
|
|
2786
|
+
}),
|
|
2787
|
+
registry.studioConfig && /* @__PURE__ */ jsx(Route, {
|
|
2788
|
+
path: "/s",
|
|
2789
|
+
element: studioHomePage
|
|
2790
|
+
}),
|
|
2791
|
+
/* @__PURE__ */ jsx(Route, {
|
|
2792
|
+
path: "/c/*",
|
|
2793
|
+
element: /* @__PURE__ */ jsx(RebaseRoute, {})
|
|
2794
|
+
}),
|
|
2795
|
+
/* @__PURE__ */ jsx(Route, {
|
|
2796
|
+
path: "/settings",
|
|
2797
|
+
element: /* @__PURE__ */ jsx(SettingsView, {})
|
|
2798
|
+
}),
|
|
2799
|
+
/* @__PURE__ */ jsx(Route, {
|
|
2800
|
+
path: "/debug/ui",
|
|
2801
|
+
element: /* @__PURE__ */ jsx(UIReferenceView, {})
|
|
2802
|
+
}),
|
|
2803
|
+
combinedViews.map((view) => {
|
|
2804
|
+
return (Array.isArray(view.slug) ? view.slug : [view.slug]).flatMap((slug) => {
|
|
2805
|
+
const routes = [/* @__PURE__ */ jsx(Route, {
|
|
2806
|
+
path: slug,
|
|
2807
|
+
element: /* @__PURE__ */ jsx(CustomViewRoute, { view })
|
|
2808
|
+
}, slug)];
|
|
2809
|
+
if (view.nestedRoutes) routes.push(/* @__PURE__ */ jsx(Route, {
|
|
2810
|
+
path: slug + "/*",
|
|
2811
|
+
element: /* @__PURE__ */ jsx(CustomViewRoute, { view })
|
|
2812
|
+
}, slug + "/*"));
|
|
2813
|
+
return routes;
|
|
2814
|
+
});
|
|
2815
|
+
}),
|
|
2816
|
+
children,
|
|
2817
|
+
/* @__PURE__ */ jsx(Route, {
|
|
2818
|
+
path: "*",
|
|
2819
|
+
element: navigationStateController.loading ? /* @__PURE__ */ jsx(CircularProgressCenter, {}) : /* @__PURE__ */ jsx(NotFoundPage, {})
|
|
2820
|
+
})
|
|
2821
|
+
] });
|
|
2822
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(RebaseRoutes, { children: layout ? /* @__PURE__ */ jsx(Route, {
|
|
2823
|
+
element: layout,
|
|
2824
|
+
children: routeContents
|
|
2825
|
+
}) : /* @__PURE__ */ jsx(Route, {
|
|
2826
|
+
element: /* @__PURE__ */ jsx(Fragment, { children: routeContents }),
|
|
2827
|
+
children: routeContents
|
|
2828
|
+
}) }), /* @__PURE__ */ jsx(CollectionEditorDialogs, {})] });
|
|
2829
|
+
}
|
|
2830
|
+
//#endregion
|
|
2831
|
+
//#region src/components/RebaseShell.tsx
|
|
2832
|
+
/**
|
|
2833
|
+
* Convenience component that composes all four CMS layers:
|
|
2834
|
+
*
|
|
2835
|
+
* ```
|
|
2836
|
+
* <RebaseAuthGate>
|
|
2837
|
+
* <RebaseNavigation>
|
|
2838
|
+
* <RebaseRouteDefs layout={<RebaseLayout>}>
|
|
2839
|
+
* {children}
|
|
2840
|
+
* </RebaseRouteDefs>
|
|
2841
|
+
* </RebaseNavigation>
|
|
2842
|
+
* </RebaseAuthGate>
|
|
2843
|
+
* ```
|
|
2844
|
+
*
|
|
2845
|
+
* Each layer is independently usable — see their individual docs.
|
|
2846
|
+
* RebaseShell is sugar that composes them all with sensible defaults.
|
|
2847
|
+
*/
|
|
2848
|
+
function RebaseShell(props) {
|
|
2849
|
+
const { title = "Rebase", appBar, drawer, autoOpenDrawer = false, children } = props;
|
|
2850
|
+
const registry = useRebaseRegistry();
|
|
2851
|
+
return /* @__PURE__ */ jsx(RebaseAuthGate, { children: /* @__PURE__ */ jsx(RebaseNavigation, { children: /* @__PURE__ */ jsx(ErrorBoundary, {
|
|
2852
|
+
fullPage: true,
|
|
2853
|
+
children: /* @__PURE__ */ jsx(RebaseRouteDefs, {
|
|
2854
|
+
layout: /* @__PURE__ */ jsx(RebaseLayout, {
|
|
2855
|
+
title,
|
|
2856
|
+
appBar,
|
|
2857
|
+
drawer,
|
|
2858
|
+
autoOpenDrawer,
|
|
2859
|
+
devViews: useMemo(() => {
|
|
2860
|
+
return registry.studioConfig?.devViews ?? [];
|
|
2861
|
+
}, [registry.studioConfig?.devViews])
|
|
2862
|
+
}),
|
|
2863
|
+
children
|
|
2864
|
+
})
|
|
2865
|
+
}) }) });
|
|
2866
|
+
}
|
|
2867
|
+
//#endregion
|
|
2868
|
+
//#region src/components/CollectionPanel.tsx
|
|
2869
|
+
/**
|
|
2870
|
+
* A high-level, reusable wrapper for embedding a Rebase collection view
|
|
2871
|
+
* inside custom pages (dashboards, home pages, entity detail views, etc.).
|
|
2872
|
+
*
|
|
2873
|
+
* Usage:
|
|
2874
|
+
* ```tsx
|
|
2875
|
+
* import { CollectionPanel } from "@rebasepro/admin";
|
|
2876
|
+
*
|
|
2877
|
+
* function MyDashboard() {
|
|
2878
|
+
* return (
|
|
2879
|
+
* <div>
|
|
2880
|
+
* <CollectionPanel path="tasks" title="Pending Tasks" />
|
|
2881
|
+
* <CollectionPanel
|
|
2882
|
+
* path="clients"
|
|
2883
|
+
* viewMode="table"
|
|
2884
|
+
* collectionOverrides={{
|
|
2885
|
+
* defaultFilter: { status: ["!=", "completed"] }
|
|
2886
|
+
* }}
|
|
2887
|
+
* />
|
|
2888
|
+
* </div>
|
|
2889
|
+
* );
|
|
2890
|
+
* }
|
|
2891
|
+
* ```
|
|
2892
|
+
*
|
|
2893
|
+
* @group Components
|
|
2894
|
+
*/
|
|
2895
|
+
function CollectionPanelInner({ mergedCollection, path, title, updateUrl, className }) {
|
|
2896
|
+
const ResolvedCollectionView = useComponentOverride("Collection.View", CollectionViewBinding);
|
|
2897
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2898
|
+
className,
|
|
2899
|
+
children: [title !== false && /* @__PURE__ */ jsx(Typography, {
|
|
2900
|
+
variant: "subtitle2",
|
|
2901
|
+
className: "font-bold mb-2 text-surface-700 dark:text-surface-300",
|
|
2902
|
+
children: title ?? mergedCollection.name
|
|
2903
|
+
}), /* @__PURE__ */ jsx(ResolvedCollectionView, {
|
|
2904
|
+
...mergedCollection,
|
|
2905
|
+
path,
|
|
2906
|
+
updateUrl
|
|
2907
|
+
})]
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2910
|
+
function CollectionPanel(props) {
|
|
2911
|
+
const { path, viewMode, sort, limit, openEntityMode, className, collectionOverrides } = props;
|
|
2912
|
+
const registeredCollection = useCollectionRegistryController().getCollection(path);
|
|
2913
|
+
const mergedCollection = useMemo(() => {
|
|
2914
|
+
if (!registeredCollection) return void 0;
|
|
2915
|
+
const propOverrides = {};
|
|
2916
|
+
if (viewMode) propOverrides.defaultViewMode = viewMode;
|
|
2917
|
+
if (sort) propOverrides.sort = sort;
|
|
2918
|
+
if (limit) propOverrides.pagination = limit;
|
|
2919
|
+
if (openEntityMode) propOverrides.openEntityMode = openEntityMode;
|
|
2920
|
+
return {
|
|
2921
|
+
...registeredCollection,
|
|
2922
|
+
...collectionOverrides ?? {},
|
|
2923
|
+
...propOverrides
|
|
2924
|
+
};
|
|
2925
|
+
}, [
|
|
2926
|
+
registeredCollection,
|
|
2927
|
+
collectionOverrides,
|
|
2928
|
+
viewMode,
|
|
2929
|
+
sort,
|
|
2930
|
+
limit,
|
|
2931
|
+
openEntityMode
|
|
2932
|
+
]);
|
|
2933
|
+
if (!mergedCollection) return /* @__PURE__ */ jsx("div", {
|
|
2934
|
+
className,
|
|
2935
|
+
children: /* @__PURE__ */ jsxs(Typography, {
|
|
2936
|
+
variant: "body2",
|
|
2937
|
+
color: "secondary",
|
|
2938
|
+
children: [
|
|
2939
|
+
"Collection \"",
|
|
2940
|
+
path,
|
|
2941
|
+
"\" not found."
|
|
2942
|
+
]
|
|
2943
|
+
})
|
|
2944
|
+
});
|
|
2945
|
+
return /* @__PURE__ */ jsx(CollectionScopeProvider, {
|
|
2946
|
+
collection: mergedCollection,
|
|
2947
|
+
children: /* @__PURE__ */ jsx(CollectionPanelInner, {
|
|
2948
|
+
...props,
|
|
2949
|
+
mergedCollection
|
|
2950
|
+
})
|
|
2951
|
+
});
|
|
2952
|
+
}
|
|
2953
|
+
//#endregion
|
|
2954
|
+
//#region src/util/propertyConfigMapper.ts
|
|
2955
|
+
/**
|
|
2956
|
+
* Map Rebase property types to headless-supported types.
|
|
2957
|
+
* `vector` and `binary` are not directly renderable, so we fall back to `string`.
|
|
2958
|
+
*/
|
|
2959
|
+
var TYPE_MAP = {
|
|
2960
|
+
string: "string",
|
|
2961
|
+
number: "number",
|
|
2962
|
+
boolean: "boolean",
|
|
2963
|
+
date: "date",
|
|
2964
|
+
geopoint: "geopoint",
|
|
2965
|
+
reference: "reference",
|
|
2966
|
+
relation: "relation",
|
|
2967
|
+
array: "array",
|
|
2968
|
+
map: "map",
|
|
2969
|
+
vector: "string",
|
|
2970
|
+
binary: "string"
|
|
2971
|
+
};
|
|
2972
|
+
/**
|
|
2973
|
+
* Convert a single Property to a CollectionPropertyConfig.
|
|
2974
|
+
*/
|
|
2975
|
+
function mapPropertyToConfig(property) {
|
|
2976
|
+
const base = {
|
|
2977
|
+
type: TYPE_MAP[property.type] ?? "string",
|
|
2978
|
+
name: property.name,
|
|
2979
|
+
description: property.description,
|
|
2980
|
+
columnWidth: property.ui?.columnWidth,
|
|
2981
|
+
hideFromCollection: property.ui?.hideFromCollection
|
|
2982
|
+
};
|
|
2983
|
+
if (property.type === "string") {
|
|
2984
|
+
if (property.enum) base.enum = mapEnumValues(property.enum);
|
|
2985
|
+
if (property.ui?.multiline) base.multiline = true;
|
|
2986
|
+
if (property.ui?.previewAsTag) base.previewAsTag = true;
|
|
2987
|
+
if (property.ui?.url) base.url = property.ui.url;
|
|
2988
|
+
if (property.ui?.markdown) base.markdown = true;
|
|
2989
|
+
if (property.storage) base.storage = true;
|
|
2990
|
+
if (property.email) base.email = true;
|
|
2991
|
+
}
|
|
2992
|
+
if (property.type === "number" && property.enum) base.enum = mapEnumValues(property.enum);
|
|
2993
|
+
if (property.type === "array" && property.of && !Array.isArray(property.of)) base.of = mapPropertyToConfig(property.of);
|
|
2994
|
+
if (property.type === "map" && property.properties) {
|
|
2995
|
+
base.properties = mapPropertiesToConfigs(property.properties);
|
|
2996
|
+
if (property.propertiesOrder) base.propertiesOrder = property.propertiesOrder;
|
|
2997
|
+
}
|
|
2998
|
+
if (property.type === "date" && property.mode) base.mode = property.mode;
|
|
2999
|
+
if (property.ui?.Preview) {}
|
|
3000
|
+
return base;
|
|
3001
|
+
}
|
|
3002
|
+
/**
|
|
3003
|
+
* Convert a Properties record to a CollectionPropertyConfig record.
|
|
3004
|
+
*/
|
|
3005
|
+
function mapPropertiesToConfigs(properties) {
|
|
3006
|
+
const result = {};
|
|
3007
|
+
for (const [key, property] of Object.entries(properties)) result[key] = mapPropertyToConfig(property);
|
|
3008
|
+
return result;
|
|
3009
|
+
}
|
|
3010
|
+
/**
|
|
3011
|
+
* Convert Rebase EnumValues to CollectionEnumValueConfig format.
|
|
3012
|
+
* Handles both array form (EnumValueConfig[]) and record form.
|
|
3013
|
+
*/
|
|
3014
|
+
function mapEnumValues(enumValues) {
|
|
3015
|
+
if (!enumValues) return void 0;
|
|
3016
|
+
if (Array.isArray(enumValues)) {
|
|
3017
|
+
const result = {};
|
|
3018
|
+
for (const item of enumValues) result[String(item.id)] = normalizeEnumValueConfig(item);
|
|
3019
|
+
return result;
|
|
3020
|
+
}
|
|
3021
|
+
const result = {};
|
|
3022
|
+
for (const [key, value] of Object.entries(enumValues)) result[key] = typeof value === "string" ? value : normalizeEnumValueConfig(value);
|
|
3023
|
+
return result;
|
|
3024
|
+
}
|
|
3025
|
+
function normalizeEnumValueConfig(config) {
|
|
3026
|
+
return {
|
|
3027
|
+
label: config.label,
|
|
3028
|
+
color: typeof config.color === "string" ? config.color : void 0,
|
|
3029
|
+
disabled: config.disabled
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
//#endregion
|
|
3033
|
+
//#region src/util/dataControllerAdapter.ts
|
|
3034
|
+
/**
|
|
3035
|
+
* Adapts an `EntityTableController<M>` (from @rebasepro/types) to a
|
|
3036
|
+
* `CollectionDataController<T>` (from @rebasepro/ui), bridging the
|
|
3037
|
+
* entity-aware data layer to the headless collection view.
|
|
3038
|
+
*/
|
|
3039
|
+
/**
|
|
3040
|
+
* Unwrap `Entity<M>` objects to flat rows for the headless view.
|
|
3041
|
+
* The entity system uses `{ id, path, values: M }`; `values` is already the
|
|
3042
|
+
* flat row `M` carrying its real primary key, so unwrapping is just `.values` —
|
|
3043
|
+
* no `id`/`path` merge (those are view-model metadata, not row columns).
|
|
3044
|
+
*/
|
|
3045
|
+
function flattenEntities(entities) {
|
|
3046
|
+
return entities.map((entity) => entity.values);
|
|
3047
|
+
}
|
|
3048
|
+
/**
|
|
3049
|
+
* React hook that adapts a EntityTableController to a CollectionDataController.
|
|
3050
|
+
*
|
|
3051
|
+
* Usage:
|
|
3052
|
+
* ```tsx
|
|
3053
|
+
* const tableController = useDataTableController({ path, collection });
|
|
3054
|
+
* const dataController = useCollectionDataController(tableController);
|
|
3055
|
+
*
|
|
3056
|
+
* <CollectionView dataController={dataController} ... />
|
|
3057
|
+
* ```
|
|
3058
|
+
*/
|
|
3059
|
+
function useCollectionDataController(tableController) {
|
|
3060
|
+
const flatData = useMemo(() => flattenEntities(tableController.data), [tableController.data]);
|
|
3061
|
+
return useMemo(() => ({
|
|
3062
|
+
data: flatData,
|
|
3063
|
+
loading: tableController.dataLoading,
|
|
3064
|
+
noMoreToLoad: tableController.noMoreToLoad,
|
|
3065
|
+
error: tableController.dataLoadingError,
|
|
3066
|
+
filterValues: tableController.filterValues,
|
|
3067
|
+
setFilterValues: tableController.setFilterValues,
|
|
3068
|
+
sortBy: tableController.sortBy,
|
|
3069
|
+
setSortBy: tableController.setSortBy,
|
|
3070
|
+
searchString: tableController.searchString,
|
|
3071
|
+
setSearchString: tableController.setSearchString,
|
|
3072
|
+
clearFilter: tableController.clearFilter,
|
|
3073
|
+
initialScroll: tableController.initialScroll,
|
|
3074
|
+
onScroll: tableController.onScroll,
|
|
3075
|
+
paginationEnabled: tableController.paginationEnabled,
|
|
3076
|
+
pageSize: tableController.pageSize,
|
|
3077
|
+
itemCount: tableController.itemCount,
|
|
3078
|
+
setItemCount: tableController.setItemCount
|
|
3079
|
+
}), [flatData, tableController]);
|
|
3080
|
+
}
|
|
3081
|
+
/**
|
|
3082
|
+
* Non-hook version for cases where you already have the data
|
|
3083
|
+
* and just need to build a static controller.
|
|
3084
|
+
*/
|
|
3085
|
+
function createStaticDataController(data, options) {
|
|
3086
|
+
return {
|
|
3087
|
+
data,
|
|
3088
|
+
loading: options?.loading ?? false,
|
|
3089
|
+
noMoreToLoad: true,
|
|
3090
|
+
error: options?.error
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
3093
|
+
//#endregion
|
|
3094
|
+
//#region src/collection_editor/useJsonCollectionsConfigController.tsx
|
|
3095
|
+
/**
|
|
3096
|
+
* A `CollectionsConfigController` implementation that persists collection
|
|
3097
|
+
* configurations as JSON via a pluggable `JsonCollectionStore` adapter.
|
|
3098
|
+
*
|
|
3099
|
+
* Use this when you want to store collection schemas in a database, API,
|
|
3100
|
+
* localStorage, or any other JSON-compatible backend — as opposed to the
|
|
3101
|
+
* default code-based persistence.
|
|
3102
|
+
*
|
|
3103
|
+
* @example
|
|
3104
|
+
* ```tsx
|
|
3105
|
+
* const store: JsonCollectionStore = {
|
|
3106
|
+
* load: async () => { const res = await fetch('/api/schemas'); return res.json(); },
|
|
3107
|
+
* save: async (slug, data) => { await fetch(`/api/schemas/${slug}`, { method: 'PUT', body: JSON.stringify(data) }); },
|
|
3108
|
+
* delete: async (slug) => { await fetch(`/api/schemas/${slug}`, { method: 'DELETE' }); },
|
|
3109
|
+
* };
|
|
3110
|
+
*
|
|
3111
|
+
* const configController = useJsonCollectionsConfigController({ store });
|
|
3112
|
+
* ```
|
|
3113
|
+
*/
|
|
3114
|
+
function useJsonCollectionsConfigController({ store, readOnly = false, autoLoad = true }) {
|
|
3115
|
+
const [collections, setCollections] = useState([]);
|
|
3116
|
+
const [loading, setLoading] = useState(autoLoad);
|
|
3117
|
+
const [navigationEntries, setNavigationEntries] = useState([]);
|
|
3118
|
+
const storeRef = useRef(store);
|
|
3119
|
+
storeRef.current = store;
|
|
3120
|
+
useEffect(() => {
|
|
3121
|
+
if (!autoLoad) return;
|
|
3122
|
+
let cancelled = false;
|
|
3123
|
+
(async () => {
|
|
3124
|
+
try {
|
|
3125
|
+
const [serialized, navEntries] = await Promise.all([storeRef.current.load(), storeRef.current.loadNavigationEntries?.() ?? Promise.resolve([])]);
|
|
3126
|
+
if (cancelled) return;
|
|
3127
|
+
setCollections(serialized.map(fromSerializableCollectionConfig));
|
|
3128
|
+
setNavigationEntries(navEntries);
|
|
3129
|
+
} catch (e) {
|
|
3130
|
+
console.error("useJsonCollectionsConfigController: failed to load collections", e);
|
|
3131
|
+
} finally {
|
|
3132
|
+
if (!cancelled) setLoading(false);
|
|
3133
|
+
}
|
|
3134
|
+
})();
|
|
3135
|
+
return () => {
|
|
3136
|
+
cancelled = true;
|
|
3137
|
+
};
|
|
3138
|
+
}, [autoLoad]);
|
|
3139
|
+
/**
|
|
3140
|
+
* Update local state and persist a collection to the store.
|
|
3141
|
+
*/
|
|
3142
|
+
const persistCollection = useCallback(async (collection) => {
|
|
3143
|
+
const serializable = toSerializableCollectionConfig(collection);
|
|
3144
|
+
await storeRef.current.save(collection.slug, serializable);
|
|
3145
|
+
}, []);
|
|
3146
|
+
/**
|
|
3147
|
+
* Find collection by id (slug).
|
|
3148
|
+
*/
|
|
3149
|
+
const getCollection = useCallback((id) => {
|
|
3150
|
+
const found = collections.find((c) => c.slug === id);
|
|
3151
|
+
if (found) return found;
|
|
3152
|
+
throw new Error(`Collection "${id}" not found`);
|
|
3153
|
+
}, [collections]);
|
|
3154
|
+
const saveCollection = useCallback(async ({ id, collectionData }) => {
|
|
3155
|
+
const collection = collectionData;
|
|
3156
|
+
await persistCollection(collection);
|
|
3157
|
+
setCollections((prev) => {
|
|
3158
|
+
const idx = prev.findIndex((c) => c.slug === id);
|
|
3159
|
+
if (idx >= 0) {
|
|
3160
|
+
const next = [...prev];
|
|
3161
|
+
next[idx] = collection;
|
|
3162
|
+
return next;
|
|
3163
|
+
}
|
|
3164
|
+
return [...prev, collection];
|
|
3165
|
+
});
|
|
3166
|
+
}, [persistCollection]);
|
|
3167
|
+
const updateCollection = useCallback(async ({ id, collectionData, previousId }) => {
|
|
3168
|
+
setCollections((prev) => {
|
|
3169
|
+
const lookupId = previousId ?? id;
|
|
3170
|
+
const idx = prev.findIndex((c) => c.slug === lookupId);
|
|
3171
|
+
if (idx < 0) return prev;
|
|
3172
|
+
const merged = {
|
|
3173
|
+
...prev[idx],
|
|
3174
|
+
...collectionData
|
|
3175
|
+
};
|
|
3176
|
+
const next = [...prev];
|
|
3177
|
+
next[idx] = merged;
|
|
3178
|
+
persistCollection(merged).catch((e) => console.error("useJsonCollectionsConfigController: failed to update collection", e));
|
|
3179
|
+
if (previousId && previousId !== id) storeRef.current.delete(previousId).catch((e) => console.error("useJsonCollectionsConfigController: failed to delete old slug", e));
|
|
3180
|
+
return next;
|
|
3181
|
+
});
|
|
3182
|
+
}, [persistCollection]);
|
|
3183
|
+
const deleteCollection = useCallback(async ({ id }) => {
|
|
3184
|
+
await storeRef.current.delete(id);
|
|
3185
|
+
setCollections((prev) => prev.filter((c) => c.slug !== id));
|
|
3186
|
+
}, []);
|
|
3187
|
+
const saveProperty = useCallback(async ({ path, propertyKey, property, newPropertiesOrder }) => {
|
|
3188
|
+
setCollections((prev) => {
|
|
3189
|
+
const idx = prev.findIndex((c) => c.slug === path);
|
|
3190
|
+
if (idx < 0) return prev;
|
|
3191
|
+
const collection = { ...prev[idx] };
|
|
3192
|
+
collection.properties = {
|
|
3193
|
+
...collection.properties,
|
|
3194
|
+
[propertyKey]: property
|
|
3195
|
+
};
|
|
3196
|
+
if (newPropertiesOrder) collection.propertiesOrder = newPropertiesOrder;
|
|
3197
|
+
const next = [...prev];
|
|
3198
|
+
next[idx] = collection;
|
|
3199
|
+
persistCollection(next[idx]).catch((e) => console.error("useJsonCollectionsConfigController: failed to save property", e));
|
|
3200
|
+
return next;
|
|
3201
|
+
});
|
|
3202
|
+
}, [persistCollection]);
|
|
3203
|
+
const deleteProperty = useCallback(async ({ path, propertyKey, newPropertiesOrder }) => {
|
|
3204
|
+
setCollections((prev) => {
|
|
3205
|
+
const idx = prev.findIndex((c) => c.slug === path);
|
|
3206
|
+
if (idx < 0) return prev;
|
|
3207
|
+
const collection = { ...prev[idx] };
|
|
3208
|
+
const { [propertyKey]: _removed, ...remaining } = collection.properties;
|
|
3209
|
+
collection.properties = remaining;
|
|
3210
|
+
if (newPropertiesOrder) collection.propertiesOrder = newPropertiesOrder;
|
|
3211
|
+
const next = [...prev];
|
|
3212
|
+
next[idx] = collection;
|
|
3213
|
+
persistCollection(next[idx]).catch((e) => console.error("useJsonCollectionsConfigController: failed to delete property", e));
|
|
3214
|
+
return next;
|
|
3215
|
+
});
|
|
3216
|
+
}, [persistCollection]);
|
|
3217
|
+
const updatePropertiesOrder = useCallback(async ({ collection, newPropertiesOrder }) => {
|
|
3218
|
+
const updated = {
|
|
3219
|
+
...collection,
|
|
3220
|
+
propertiesOrder: newPropertiesOrder
|
|
3221
|
+
};
|
|
3222
|
+
setCollections((prev) => {
|
|
3223
|
+
const idx = prev.findIndex((c) => c.slug === collection.slug);
|
|
3224
|
+
if (idx < 0) return prev;
|
|
3225
|
+
const next = [...prev];
|
|
3226
|
+
next[idx] = updated;
|
|
3227
|
+
return next;
|
|
3228
|
+
});
|
|
3229
|
+
await persistCollection(updated);
|
|
3230
|
+
}, [persistCollection]);
|
|
3231
|
+
const updateKanbanColumnsOrder = useCallback(async ({ collection, kanbanColumnProperty, newColumnsOrder }) => {}, []);
|
|
3232
|
+
const saveNavigationEntriesHandler = useCallback(async (entries) => {
|
|
3233
|
+
setNavigationEntries(entries);
|
|
3234
|
+
if (storeRef.current.saveNavigationEntries) await storeRef.current.saveNavigationEntries(entries);
|
|
3235
|
+
}, []);
|
|
3236
|
+
return useMemo(() => ({
|
|
3237
|
+
loading,
|
|
3238
|
+
readOnly,
|
|
3239
|
+
readOnlyReason: readOnly ? "Configuration is read-only." : void 0,
|
|
3240
|
+
collections,
|
|
3241
|
+
getCollection,
|
|
3242
|
+
saveCollection,
|
|
3243
|
+
updateCollection,
|
|
3244
|
+
deleteCollection,
|
|
3245
|
+
saveProperty,
|
|
3246
|
+
deleteProperty,
|
|
3247
|
+
updatePropertiesOrder,
|
|
3248
|
+
updateKanbanColumnsOrder,
|
|
3249
|
+
navigationEntries,
|
|
3250
|
+
saveNavigationEntries: saveNavigationEntriesHandler
|
|
3251
|
+
}), [
|
|
3252
|
+
loading,
|
|
3253
|
+
readOnly,
|
|
3254
|
+
collections,
|
|
3255
|
+
getCollection,
|
|
3256
|
+
saveCollection,
|
|
3257
|
+
updateCollection,
|
|
3258
|
+
deleteCollection,
|
|
3259
|
+
saveProperty,
|
|
3260
|
+
deleteProperty,
|
|
3261
|
+
updatePropertiesOrder,
|
|
3262
|
+
updateKanbanColumnsOrder,
|
|
3263
|
+
navigationEntries,
|
|
3264
|
+
saveNavigationEntriesHandler
|
|
3265
|
+
]);
|
|
3266
|
+
}
|
|
3267
|
+
//#endregion
|
|
3268
|
+
//#region src/collection_editor/ui/MissingReferenceWidget.tsx
|
|
3269
|
+
function MissingReferenceWidget({ path: pathProp }) {
|
|
3270
|
+
const registry = useCollectionRegistryController();
|
|
3271
|
+
const path = getLastSegment$1(pathProp);
|
|
3272
|
+
const parentCollectionSlugs = registry.getParentCollectionSlugs(pathProp);
|
|
3273
|
+
const parentEntityIds = registry.getParentEntityIds(pathProp);
|
|
3274
|
+
const collectionEditor = useCollectionEditorController();
|
|
3275
|
+
const { t } = useTranslation();
|
|
3276
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3277
|
+
className: "p-1 flex flex-col items-center",
|
|
3278
|
+
children: [/* @__PURE__ */ jsx(ErrorView, { error: t("studio_missing_reference_error", { path }) }), /* @__PURE__ */ jsx(Button, {
|
|
3279
|
+
className: "mx-2",
|
|
3280
|
+
size: "small",
|
|
3281
|
+
onClick: () => {
|
|
3282
|
+
collectionEditor.createCollection({
|
|
3283
|
+
initialValues: {
|
|
3284
|
+
path,
|
|
3285
|
+
name: prettifyIdentifier(path)
|
|
3286
|
+
},
|
|
3287
|
+
parentCollectionSlugs,
|
|
3288
|
+
parentEntityIds,
|
|
3289
|
+
redirect: false,
|
|
3290
|
+
sourceClick: "missing_reference"
|
|
3291
|
+
});
|
|
3292
|
+
},
|
|
3293
|
+
children: t("studio_missing_reference_create")
|
|
3294
|
+
})]
|
|
3295
|
+
});
|
|
3296
|
+
}
|
|
3297
|
+
//#endregion
|
|
3298
|
+
export { AdminModeSyncer, AppBar, ArrayContainer, ArrayCustomShapedFieldBinding, ArrayEnumPreview, ArrayOfMapsPreview, ArrayOfReferencesFieldBinding, ArrayOfReferencesPreview, ArrayOfStorageComponentsPreview, ArrayOfStringsPreview, ArrayOneOfPreview, ArrayPropertyEnumPreview, ArrayPropertyPreview, BasicExportAction, BlockFieldBinding, BooleanPreview, CollectionCardViewBinding, CollectionGenerationApiError, CollectionPanel, CollectionRegistryContext, CollectionRowActions, CollectionTableBinding, CollectionViewActions, CollectionViewBinding, CreationResultDialog, CustomViewRoute, DEFAULT_COLLECTION_GENERATION_ENDPOINT, DEFAULT_FIELD_CONFIGS, DataNewPropertiesMapping, DatePreview, DateTimeFieldBinding, DefaultDrawer, Drawer, DrawerFooterActions, EmptyValue, EntityCardBinding, EntityForm, EntityFormBinding, EntityPreviewBinding, EntityViewBinding, EnumValuesChip, ExportCollectionAction, FieldCaption, FieldHelperText, FormEntry, FormLayout, ImagePreview, ImportCollectionAction, ImportDataPreview, ImportFileUpload, ImportNewPropertyFieldPreview, ImportSaveInProgress, KeyValueFieldBinding, KeyValuePreview, LabelWithIcon, LabelWithIconAndTooltip, MapFieldBinding, MapPropertyPreview, MarkdownEditorFieldBinding, MissingReferenceWidget, MultiSelectFieldBinding, NavigationStateContext, NumberPropertyPreview, PropertyConfigBadge, PropertyFieldBinding, PropertyIdCopyTooltip, PropertyPreview, PropertySelectEntry, ReadOnlyFieldBinding, RebaseAdmin, RebaseAuthGate, RebaseLayout, RebaseNavigation, RebaseRoute, RebaseRouteDefs, RebaseShell, ReferenceFieldBinding, ReferencePreview, ReferenceWidget, RelationPreview, RepeatFieldBinding, Scaffold, SearchIconsView, SelectFieldBinding, SelectableTable, SelectableTableContext, SelectionTableBinding, SideDialogs, SidePanelControllerContext, SidePanelProvider, SkeletonPropertyComponent, StorageThumbnail, StorageThumbnailInternal, StorageUploadFieldBinding, StringPropertyPreview, SwitchFieldBinding, TextFieldBinding, UrlComponentPreview, UrlContext, UserPreview, VectorFieldBinding, VirtualTableInput, addInitialSlash, buildCollectionGenerationCallback, buildSidePanelsFromUrl, convertDataToEntity, convertFileToJson, copyEntityAction, createStaticDataController, deleteEntityAction, downloadBlob, downloadDataAsCsv, downloadEntitiesExport, editEntityAction, extractTouchedValues, flattenEntry, fromSerializableCollectionConfig, fromSerializableProperties, fromSerializableProperty, getBracketNotation, getChanges, getCollectionBySlugWithin, getCollectionPathsCombinations, getDefaultFieldConfig, getDefaultFieldId, getDefaultPropertiesOrder, getEntityCSVExportableData, getEntityJsonExportableData, getEntityPreviewKeys, getEntityTitlePropertyKey, getEntityViewWidth, getFieldConfig, getFieldId, getFullId, getFullIdPath, getIconForProperty, getIconForWidget, getInferenceType, getInitialEntityValues, getLastSegment, getPropertiesWithPropertiesOrder, getPropertyInPath, getResolvedPropertyInPath, idToPropertiesPath, isReferenceProperty, isRelationProperty, mapPropertiesToConfigs, mapPropertyToConfig, mergeEntityActions, namespaceToPropertiesOrderPath, namespaceToPropertiesPath, processValueMapping, removeEmptyContainers, removeInitialAndTrailingSlashes, removeInitialSlash, removeTrailingSlash, renderSkeletonCaptionText, renderSkeletonIcon, renderSkeletonImageThumbnail, renderSkeletonText, resetPasswordAction, resolveCollectionPathIds, resolveEntityAction, resolveEntityView, resolveNavigationFrom, sanitizeUrl, toSerializableCollectionConfig, toSerializableProperties, toSerializableProperty, unflattenObject, useApp, useBreadcrumbsController, useBuildCollectionRegistryController, useBuildNavigationStateController, useBuildSidePanel, useBuildUrlController, useCMSContext, useClearRestoreValue, useCollectionDataController, useCollectionEditorController, useCollectionRegistryController, useCollectionsConfigController, useHistory, useImportConfig, useJsonCollectionsConfigController, useLocalCollectionsConfigController, useNavigationStateController, useResolvedCollections, useResolvedNavigationFrom, useResolvedViews, useSelectionController, useSelectionDialog, useSideDialogsController, useSidePanel, useTopLevelNavigation, useUrlController, validateCollectionJson, zodToFormErrors };
|
|
3299
|
+
|
|
3300
|
+
//# sourceMappingURL=index.js.map
|