@rebasepro/admin 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/README.md +135 -0
  2. package/dist/CollectionEditorDialog-jA-PLh6N.js +3549 -0
  3. package/dist/CollectionEditorDialog-jA-PLh6N.js.map +1 -0
  4. package/dist/CollectionsStudioView-UKouBPOm.js +170 -0
  5. package/dist/CollectionsStudioView-UKouBPOm.js.map +1 -0
  6. package/dist/PropertyEditView-DEUddmg_.js +3660 -0
  7. package/dist/PropertyEditView-DEUddmg_.js.map +1 -0
  8. package/dist/collection_editor/ConfigControllerProvider.d.ts +2 -2
  9. package/dist/collection_editor/ui/AddKanbanColumnAction.d.ts +2 -1
  10. package/dist/collection_editor/ui/CollectionViewHeaderAction.d.ts +2 -1
  11. package/dist/collection_editor/ui/EditorCollectionAction.d.ts +1 -1
  12. package/dist/collection_editor/ui/EditorCollectionActionStart.d.ts +1 -1
  13. package/dist/collection_editor/ui/EditorEntityAction.d.ts +1 -1
  14. package/dist/collection_editor/ui/HomePageEditorCollectionAction.d.ts +1 -1
  15. package/dist/collection_editor/ui/KanbanSetupAction.d.ts +2 -1
  16. package/dist/collection_editor/ui/MissingReferenceWidget.d.ts +1 -1
  17. package/dist/collection_editor/ui/NewCollectionButton.d.ts +1 -1
  18. package/dist/collection_editor/ui/NewCollectionCard.d.ts +1 -1
  19. package/dist/collection_editor/ui/PropertyAddColumnComponent.d.ts +1 -1
  20. package/dist/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.d.ts +1 -1
  21. package/dist/collection_editor/ui/collection_editor/AIModifiedPathsContext.d.ts +1 -1
  22. package/dist/collection_editor/ui/collection_editor/CollectionDetailsForm.d.ts +2 -1
  23. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +2 -2
  24. package/dist/collection_editor/ui/collection_editor/CollectionEditorWelcomeView.d.ts +2 -2
  25. package/dist/collection_editor/ui/collection_editor/CollectionJsonImportDialog.d.ts +2 -1
  26. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
  27. package/dist/collection_editor/ui/collection_editor/CollectionRLSTab.d.ts +2 -1
  28. package/dist/collection_editor/ui/collection_editor/CollectionRelationsTab.d.ts +2 -1
  29. package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +2 -1
  30. package/dist/collection_editor/ui/collection_editor/CollectionYupValidation.d.ts +1 -9
  31. package/dist/collection_editor/ui/collection_editor/CollectionsStudioView.d.ts +2 -1
  32. package/dist/collection_editor/ui/collection_editor/DisplaySettingsForm.d.ts +2 -1
  33. package/dist/collection_editor/ui/collection_editor/EntityActionsEditTab.d.ts +2 -1
  34. package/dist/collection_editor/ui/collection_editor/EntityActionsSelectDialog.d.ts +2 -1
  35. package/dist/collection_editor/ui/collection_editor/EntityCustomViewsSelectDialog.d.ts +2 -1
  36. package/dist/collection_editor/ui/collection_editor/EnumForm.d.ts +2 -1
  37. package/dist/collection_editor/ui/collection_editor/GeneralSettingsForm.d.ts +2 -1
  38. package/dist/collection_editor/ui/collection_editor/GetCodeDialog.d.ts +2 -1
  39. package/dist/collection_editor/ui/collection_editor/KanbanConfigSection.d.ts +2 -1
  40. package/dist/collection_editor/ui/collection_editor/LayoutModeSwitch.d.ts +1 -1
  41. package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +4 -3
  42. package/dist/collection_editor/ui/collection_editor/PropertyFieldPreview.d.ts +2 -2
  43. package/dist/collection_editor/ui/collection_editor/PropertyTree.d.ts +2 -2
  44. package/dist/collection_editor/ui/collection_editor/SubcollectionsEditTab.d.ts +2 -1
  45. package/dist/collection_editor/ui/collection_editor/SwitchControl.d.ts +2 -1
  46. package/dist/collection_editor/ui/collection_editor/ViewModeSwitch.d.ts +1 -1
  47. package/dist/collection_editor/ui/collection_editor/import/CollectionEditorImportDataPreview.d.ts +1 -1
  48. package/dist/collection_editor/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +3 -2
  49. package/dist/collection_editor/ui/collection_editor/properties/BlockPropertyField.d.ts +2 -1
  50. package/dist/collection_editor/ui/collection_editor/properties/BooleanPropertyField.d.ts +2 -1
  51. package/dist/collection_editor/ui/collection_editor/properties/DateTimePropertyField.d.ts +2 -1
  52. package/dist/collection_editor/ui/collection_editor/properties/EnumPropertyField.d.ts +2 -1
  53. package/dist/collection_editor/ui/collection_editor/properties/KeyValuePropertyField.d.ts +2 -1
  54. package/dist/collection_editor/ui/collection_editor/properties/MapPropertyField.d.ts +2 -1
  55. package/dist/collection_editor/ui/collection_editor/properties/MarkdownPropertyField.d.ts +2 -1
  56. package/dist/collection_editor/ui/collection_editor/properties/NumberPropertyField.d.ts +2 -1
  57. package/dist/collection_editor/ui/collection_editor/properties/ReferencePropertyField.d.ts +3 -2
  58. package/dist/collection_editor/ui/collection_editor/properties/RelationPropertyField.d.ts +2 -1
  59. package/dist/collection_editor/ui/collection_editor/properties/RepeatPropertyField.d.ts +2 -1
  60. package/dist/collection_editor/ui/collection_editor/properties/StoragePropertyField.d.ts +2 -1
  61. package/dist/collection_editor/ui/collection_editor/properties/StringPropertyField.d.ts +2 -1
  62. package/dist/collection_editor/ui/collection_editor/properties/UrlPropertyField.d.ts +2 -1
  63. package/dist/collection_editor/ui/collection_editor/properties/VectorPropertyField.d.ts +2 -1
  64. package/dist/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.d.ts +2 -1
  65. package/dist/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.d.ts +2 -1
  66. package/dist/collection_editor/ui/collection_editor/properties/conditions/ConditionsPanel.d.ts +1 -1
  67. package/dist/collection_editor/ui/collection_editor/properties/conditions/EnumConditionsEditor.d.ts +2 -1
  68. package/dist/collection_editor/ui/collection_editor/properties/validation/ArrayPropertyValidation.d.ts +2 -1
  69. package/dist/collection_editor/ui/collection_editor/properties/validation/GeneralPropertyValidation.d.ts +2 -1
  70. package/dist/collection_editor/ui/collection_editor/properties/validation/NumberPropertyValidation.d.ts +2 -1
  71. package/dist/collection_editor/ui/collection_editor/properties/validation/StringPropertyValidation.d.ts +2 -1
  72. package/dist/collection_editor/ui/collection_editor/properties/validation/ValidationPanel.d.ts +1 -1
  73. package/dist/collection_editor/ui/collection_editor/templates/blog_template.d.ts +1 -1
  74. package/dist/collection_editor/ui/collection_editor/templates/pages_template.d.ts +1 -1
  75. package/dist/collection_editor/ui/collection_editor/templates/products_template.d.ts +1 -1
  76. package/dist/collection_editor/ui/collection_editor/templates/users_template.d.ts +1 -1
  77. package/dist/collection_editor_ui.js +4 -11
  78. package/dist/components/ArrayContainer.d.ts +3 -3
  79. package/dist/components/ClearFilterSortButton.d.ts +1 -1
  80. package/dist/components/CollectionEditorDialogs.d.ts +2 -1
  81. package/dist/components/CollectionPanel.d.ts +2 -27
  82. package/dist/components/DefaultAppBar.d.ts +1 -1
  83. package/dist/components/DefaultDrawer.d.ts +4 -4
  84. package/dist/components/DeleteEntityDialog.d.ts +2 -1
  85. package/dist/components/DrawerNavigationGroup.d.ts +1 -1
  86. package/dist/components/DrawerNavigationItem.d.ts +1 -1
  87. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +1 -1
  88. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -1
  89. package/dist/components/EntityCollectionTable/fields/TableMultipleRelationField.d.ts +3 -3
  90. package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +3 -3
  91. package/dist/components/EntityCollectionTable/fields/TableRelationField.d.ts +3 -3
  92. package/dist/components/EntityCollectionTable/fields/TableRelationSelectorField.d.ts +2 -1
  93. package/dist/components/EntityCollectionTable/fields/TableStorageUpload.d.ts +2 -2
  94. package/dist/components/EntityCollectionTable/fields/VirtualTableDateField.d.ts +1 -1
  95. package/dist/components/EntityCollectionTable/fields/VirtualTableInput.d.ts +2 -1
  96. package/dist/components/EntityCollectionTable/fields/VirtualTableNumberInput.d.ts +2 -1
  97. package/dist/components/EntityCollectionTable/fields/VirtualTableSelect.d.ts +1 -1
  98. package/dist/components/EntityCollectionTable/fields/VirtualTableSwitch.d.ts +2 -1
  99. package/dist/components/EntityCollectionTable/fields/VirtualTableUserSelect.d.ts +1 -1
  100. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -1
  101. package/dist/components/EntityCollectionTable/internal/EntityTableCellActions.d.ts +1 -1
  102. package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +4 -3
  103. package/dist/components/EntityCollectionView/Board.d.ts +2 -1
  104. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +1 -1
  105. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +1 -1
  106. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +1 -1
  107. package/dist/components/EntityCollectionView/EntityCard.d.ts +2 -1
  108. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +1 -1
  109. package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +1 -1
  110. package/dist/components/EntityCollectionView/EntityCollectionListView.d.ts +1 -1
  111. package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +0 -24
  112. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +1 -1
  113. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +2 -1
  114. package/dist/components/EntityCollectionView/FilterPresetsButton.d.ts +2 -1
  115. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +2 -1
  116. package/dist/components/EntityCollectionView/SplitListView.d.ts +1 -1
  117. package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +2 -1
  118. package/dist/components/EntityDetailView.d.ts +1 -1
  119. package/dist/components/EntityEditView.d.ts +2 -2
  120. package/dist/components/EntityEditViewFormActions.d.ts +2 -1
  121. package/dist/components/EntityJsonPreview.d.ts +2 -1
  122. package/dist/components/EntityPreview.d.ts +3 -3
  123. package/dist/components/EntitySidePanel.d.ts +2 -1
  124. package/dist/components/EntityView.d.ts +2 -1
  125. package/dist/components/FieldCaption.d.ts +1 -1
  126. package/dist/components/HomePage/ContentHomePage.d.ts +1 -1
  127. package/dist/components/HomePage/FavouritesView.d.ts +1 -1
  128. package/dist/components/HomePage/HomePageDnD.d.ts +4 -4
  129. package/dist/components/HomePage/NavigationCard.d.ts +1 -1
  130. package/dist/components/HomePage/NavigationCardBinding.d.ts +2 -1
  131. package/dist/components/HomePage/NavigationGroup.d.ts +2 -2
  132. package/dist/components/HomePage/RenameGroupDialog.d.ts +2 -1
  133. package/dist/components/HomePage/SmallNavigationCard.d.ts +1 -1
  134. package/dist/components/PropertyCollectionView.d.ts +2 -1
  135. package/dist/components/PropertyIdCopyTooltip.d.ts +2 -2
  136. package/dist/components/RebaseAuthGate.d.ts +1 -1
  137. package/dist/components/RebaseLayout.d.ts +1 -1
  138. package/dist/components/RebaseNavigation.d.ts +1 -1
  139. package/dist/components/RebaseRouteDefs.d.ts +1 -1
  140. package/dist/components/RebaseShell.d.ts +1 -1
  141. package/dist/components/ReferenceTable/EntitySelectionTable.d.ts +1 -1
  142. package/dist/components/ReferenceWidget.d.ts +2 -1
  143. package/dist/components/SearchIconsView.d.ts +2 -1
  144. package/dist/components/SelectableTable/SelectableTable.d.ts +2 -2
  145. package/dist/components/SelectableTable/filters/BooleanFilterField.d.ts +2 -1
  146. package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
  147. package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +2 -1
  148. package/dist/components/SelectableTable/filters/RelationFilterField.d.ts +2 -1
  149. package/dist/components/SelectableTable/filters/StringNumberFilterField.d.ts +2 -1
  150. package/dist/components/SideDialogs.d.ts +1 -1
  151. package/dist/components/SideEntityProvider.d.ts +1 -1
  152. package/dist/components/admin/CreationResultDialog.d.ts +2 -1
  153. package/dist/components/app/AppBar.d.ts +1 -1
  154. package/dist/components/app/Drawer.d.ts +1 -1
  155. package/dist/components/common/default_entity_actions.d.ts +2 -1
  156. package/dist/components/history/EntityHistoryEntry.d.ts +1 -1
  157. package/dist/components/history/EntityHistoryView.d.ts +1 -1
  158. package/dist/components/history/LastEditedByFormAction.d.ts +2 -1
  159. package/dist/components/history/LastEditedByIndicator.d.ts +2 -1
  160. package/dist/components/history/UserChip.d.ts +1 -1
  161. package/dist/data_export/export/BasicExportAction.d.ts +2 -1
  162. package/dist/data_export/export/ExportCollectionAction.d.ts +1 -1
  163. package/dist/data_import/components/DataNewPropertiesMapping.d.ts +1 -1
  164. package/dist/data_import/components/ImportFileUpload.d.ts +1 -1
  165. package/dist/data_import/components/ImportNewPropertyFieldPreview.d.ts +2 -1
  166. package/dist/data_import/components/ImportSaveInProgress.d.ts +1 -1
  167. package/dist/data_import/import/ImportCollectionAction.d.ts +4 -3
  168. package/dist/editor/components/SlashCommandMenu.d.ts +2 -1
  169. package/dist/editor/editor.d.ts +1 -1
  170. package/dist/editor/selectors/color-selector.d.ts +1 -1
  171. package/dist/editor/selectors/link-selector.d.ts +1 -1
  172. package/dist/editor/selectors/node-selector.d.ts +1 -1
  173. package/dist/editor/selectors/text-buttons.d.ts +1 -1
  174. package/dist/editor.js +2621 -3574
  175. package/dist/editor.js.map +1 -1
  176. package/dist/form/EntityForm.d.ts +2 -1
  177. package/dist/form/EntityFormActions.d.ts +2 -1
  178. package/dist/form/components/FieldHelperText.d.ts +1 -1
  179. package/dist/form/components/FormEntry.d.ts +1 -1
  180. package/dist/form/components/FormLayout.d.ts +1 -1
  181. package/dist/form/components/LabelWithIconAndTooltip.d.ts +1 -1
  182. package/dist/form/components/LocalChangesMenu.d.ts +2 -1
  183. package/dist/form/components/StorageItemPreview.d.ts +2 -1
  184. package/dist/form/components/StorageUploadProgress.d.ts +2 -1
  185. package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +2 -1
  186. package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +2 -1
  187. package/dist/form/field_bindings/BlockFieldBinding.d.ts +2 -1
  188. package/dist/form/field_bindings/DateTimeFieldBinding.d.ts +2 -1
  189. package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +2 -1
  190. package/dist/form/field_bindings/MapFieldBinding.d.ts +2 -1
  191. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +2 -1
  192. package/dist/form/field_bindings/MultiSelectFieldBinding.d.ts +2 -1
  193. package/dist/form/field_bindings/MultipleRelationFieldBinding.d.ts +2 -1
  194. package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +2 -1
  195. package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +2 -1
  196. package/dist/form/field_bindings/ReferenceFieldBinding.d.ts +2 -1
  197. package/dist/form/field_bindings/RelationFieldBinding.d.ts +2 -1
  198. package/dist/form/field_bindings/RepeatFieldBinding.d.ts +2 -1
  199. package/dist/form/field_bindings/SelectFieldBinding.d.ts +2 -1
  200. package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +3 -2
  201. package/dist/form/field_bindings/SwitchFieldBinding.d.ts +2 -1
  202. package/dist/form/field_bindings/TextFieldBinding.d.ts +2 -1
  203. package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +2 -1
  204. package/dist/form/field_bindings/VectorFieldBinding.d.ts +2 -1
  205. package/dist/history-BL49TIDb.js +311 -0
  206. package/dist/history-BL49TIDb.js.map +1 -0
  207. package/dist/hooks/navigation/useBuildNavigationStateController.d.ts +0 -2
  208. package/dist/hooks/navigation/useNavigationResolution.d.ts +5 -6
  209. package/dist/hooks/navigation/useResolvedCollections.d.ts +0 -2
  210. package/dist/hooks/useBreadcrumbsController.d.ts +2 -32
  211. package/dist/index.js +2 -3393
  212. package/dist/markdown-CklalUUk.js +526 -0
  213. package/dist/markdown-CklalUUk.js.map +1 -0
  214. package/dist/preview/components/ArrayEnumPreview.d.ts +2 -1
  215. package/dist/preview/components/EmptyValue.d.ts +2 -1
  216. package/dist/preview/components/EnumValuesChip.d.ts +1 -1
  217. package/dist/preview/components/ImagePreview.d.ts +2 -1
  218. package/dist/preview/components/ReferencePreview.d.ts +2 -1
  219. package/dist/preview/components/RelationPreview.d.ts +2 -1
  220. package/dist/preview/components/StorageThumbnail.d.ts +1 -1
  221. package/dist/preview/components/UserPreview.d.ts +6 -3
  222. package/dist/preview/property_previews/ArrayOfMapsPreview.d.ts +2 -1
  223. package/dist/preview/property_previews/ArrayOfReferencesPreview.d.ts +1 -1
  224. package/dist/preview/property_previews/ArrayOfRelationsPreview.d.ts +1 -1
  225. package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +2 -1
  226. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +2 -1
  227. package/dist/preview/property_previews/ArrayOneOfPreview.d.ts +2 -1
  228. package/dist/preview/property_previews/ArrayPropertyEnumPreview.d.ts +1 -1
  229. package/dist/preview/property_previews/ArrayPropertyPreview.d.ts +2 -1
  230. package/dist/preview/property_previews/MapPropertyPreview.d.ts +3 -2
  231. package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +5 -4
  232. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  233. package/dist/routes/RebaseRoute.d.ts +1 -1
  234. package/dist/src-BJ8fkM8y.js +24010 -0
  235. package/dist/src-BJ8fkM8y.js.map +1 -0
  236. package/dist/types/components/PropertyPreviewProps.d.ts +4 -0
  237. package/dist/types/components/formex.d.ts +1 -40
  238. package/dist/types/fields.d.ts +9 -1
  239. package/dist/util/navigation_utils.d.ts +2 -1
  240. package/dist/util/previews.d.ts +5 -0
  241. package/dist/util/property_utils.d.ts +1 -1
  242. package/package.json +29 -27
  243. package/src/collection_editor/ConfigControllerProvider.tsx +10 -7
  244. package/src/collection_editor/ui/CollectionViewHeaderAction.tsx +2 -1
  245. package/src/collection_editor/ui/EditorCollectionAction.tsx +2 -1
  246. package/src/collection_editor/ui/EditorCollectionActionStart.tsx +2 -1
  247. package/src/collection_editor/ui/EditorEntityAction.tsx +2 -1
  248. package/src/collection_editor/ui/KanbanSetupAction.tsx +2 -1
  249. package/src/collection_editor/ui/MissingReferenceWidget.tsx +2 -1
  250. package/src/collection_editor/ui/PropertyAddColumnComponent.tsx +2 -1
  251. package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +2 -3
  252. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +0 -1
  253. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +8 -4
  254. package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +2 -2
  255. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +2 -1
  256. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +2 -1
  257. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +2 -2
  258. package/src/collection_editor/ui/collection_editor/properties/RelationPropertyField.tsx +5 -2
  259. package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +2 -1
  260. package/src/collection_editor/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +1 -1
  261. package/src/collection_editor/ui/collection_editor/templates/blog_template.ts +1 -1
  262. package/src/collection_editor/ui/collection_editor/templates/pages_template.ts +1 -1
  263. package/src/collection_editor/ui/collection_editor/templates/products_template.ts +1 -1
  264. package/src/collection_editor/ui/collection_editor/templates/users_template.ts +1 -1
  265. package/src/collection_editor/useLocalCollectionsConfigController.tsx +7 -2
  266. package/src/collection_editor/validateCollectionJson.ts +2 -2
  267. package/src/components/CollectionPanel.tsx +51 -25
  268. package/src/components/DefaultAppBar.tsx +1 -2
  269. package/src/components/DefaultDrawer.tsx +3 -2
  270. package/src/components/DrawerNavigationGroup.tsx +3 -2
  271. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +30 -9
  272. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +1 -1
  273. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +1 -0
  274. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +5 -2
  275. package/src/components/EntityCollectionView/Board.tsx +10 -8
  276. package/src/components/EntityCollectionView/BoardSortableList.tsx +2 -2
  277. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +4 -2
  278. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +14 -4
  279. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +48 -26
  280. package/src/components/EntityCollectionView/EntityCollectionView.tsx +146 -73
  281. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +2 -1
  282. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +3 -2
  283. package/src/components/EntityCollectionView/FilterPresetsButton.tsx +4 -2
  284. package/src/components/EntityCollectionView/ViewModeToggle.tsx +0 -1
  285. package/src/components/EntityCollectionView/useBoardDataController.tsx +17 -15
  286. package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +32 -23
  287. package/src/components/EntityDetailView.tsx +72 -39
  288. package/src/components/EntityEditView.tsx +79 -47
  289. package/src/components/EntitySidePanel.tsx +9 -3
  290. package/src/components/HomePage/ContentHomePage.tsx +3 -3
  291. package/src/components/HomePage/NavigationCard.tsx +5 -5
  292. package/src/components/HomePage/NavigationCardBinding.tsx +1 -1
  293. package/src/components/RebaseLayout.tsx +5 -2
  294. package/src/components/RebaseNavigation.tsx +1 -4
  295. package/src/components/RebaseRouteDefs.tsx +5 -3
  296. package/src/components/SearchIconsView.tsx +1 -1
  297. package/src/components/SelectableTable/SelectableTable.tsx +2 -3
  298. package/src/components/SelectableTable/SelectionStore.ts +9 -3
  299. package/src/components/UserSelector.tsx +92 -2
  300. package/src/components/common/default_entity_actions.tsx +23 -6
  301. package/src/components/field_configs.tsx +6 -3
  302. package/src/editor/editor.tsx +1 -1
  303. package/src/editor/extensions/Image/index.ts +2 -2
  304. package/src/editor/extensions/Image.ts +2 -2
  305. package/src/editor/extensions/clipboard.ts +2 -2
  306. package/src/editor/markdown-it-plugins.d.ts +24 -0
  307. package/src/editor/markdown.ts +2 -3
  308. package/src/form/EntityForm.tsx +21 -15
  309. package/src/form/PropertyFieldBinding.tsx +1 -1
  310. package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
  311. package/src/form/field_bindings/MapFieldBinding.tsx +5 -47
  312. package/src/form/field_bindings/VectorFieldBinding.tsx +3 -3
  313. package/src/form/validation.ts +17 -15
  314. package/src/hooks/navigation/useBuildNavigationStateController.tsx +3 -6
  315. package/src/hooks/navigation/useNavigationResolution.ts +143 -19
  316. package/src/hooks/navigation/useResolvedCollections.ts +4 -6
  317. package/src/hooks/useBreadcrumbsController.tsx +2 -34
  318. package/src/hooks/useBuildSideEntityController.tsx +25 -3
  319. package/src/preview/PropertyPreview.tsx +6 -2
  320. package/src/preview/components/ReferencePreview.tsx +64 -13
  321. package/src/preview/components/RelationPreview.tsx +101 -13
  322. package/src/preview/components/UserPreview.tsx +5 -14
  323. package/src/preview/property_previews/ArrayOfMapsPreview.tsx +1 -1
  324. package/src/routes/RebaseRoute.tsx +5 -4
  325. package/src/types/components/PropertyPreviewProps.tsx +5 -0
  326. package/src/types/components/formex.tsx +1 -45
  327. package/src/types/fields.tsx +10 -1
  328. package/src/util/navigation_utils.ts +14 -4
  329. package/src/util/previews.ts +101 -4
  330. package/dist/CollectionEditorDialog-Bo86h-d-.js +0 -4574
  331. package/dist/CollectionEditorDialog-Bo86h-d-.js.map +0 -1
  332. package/dist/CollectionsStudioView-bsg4AT_Z.js +0 -217
  333. package/dist/CollectionsStudioView-bsg4AT_Z.js.map +0 -1
  334. package/dist/ExportCollectionAction-Cf-Id5uO.js +0 -446
  335. package/dist/ExportCollectionAction-Cf-Id5uO.js.map +0 -1
  336. package/dist/ImportCollectionAction-LN5sreK9.js +0 -685
  337. package/dist/ImportCollectionAction-LN5sreK9.js.map +0 -1
  338. package/dist/PropertyEditView-jCWCxmCz.js +0 -8197
  339. package/dist/PropertyEditView-jCWCxmCz.js.map +0 -1
  340. package/dist/collection_editor_ui.js.map +0 -1
  341. package/dist/index-BrqSvMG0.js +0 -8
  342. package/dist/index-BrqSvMG0.js.map +0 -1
  343. package/dist/index-Cb0bPnzZ.js +0 -12
  344. package/dist/index-Cb0bPnzZ.js.map +0 -1
  345. package/dist/index-z3H8iqsj.js +0 -462
  346. package/dist/index-z3H8iqsj.js.map +0 -1
  347. package/dist/index.js.map +0 -1
  348. package/dist/markdown-DD2JDU1X.js +0 -618
  349. package/dist/markdown-DD2JDU1X.js.map +0 -1
  350. package/dist/useEntityHistory-UVsSclfZ.js +0 -123
  351. package/dist/useEntityHistory-UVsSclfZ.js.map +0 -1
  352. package/dist/util-CvNp6A-r.js +0 -30505
  353. package/dist/util-CvNp6A-r.js.map +0 -1
@@ -0,0 +1,3549 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { Ct as getFieldConfig, Dn as useNavigationStateController, Fn as useSelectionController, In as EntityCollectionTable, Tn as useUrlController, Xt as SearchIconsView, Zt as FieldCaption, ct as ImportNewPropertyFieldPreview, d as useCollectionsConfigController, gt as ImportFileUpload, ht as ImportSaveInProgress, i as namespaceToPropertiesOrderPath, kn as useCollectionRegistryController, lt as DataNewPropertiesMapping, mt as useImportConfig, n as getFullIdPath, pt as getInferenceType, r as idToPropertiesPath, s as CollectionGenerationApiError, st as useCollectionEditorController, t as getFullId, u as validateCollectionJson, ut as convertDataToEntity, wt as getFieldId, yt as PropertyConfigBadge } from "./src-BJ8fkM8y.js";
3
+ import { a as updatePropertyFromWidget, c as useAIModifiedPaths, i as supportedFields, n as PropertyForm, o as PropertyTree, r as PropertyFormDialog, s as AIModifiedPathsProvider } from "./PropertyEditView-DEUddmg_.js";
4
+ import * as React$1 from "react";
5
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+ import { Alert, AppWindow, ArrowLeftIcon, BooleanSwitchWithLabel, Button, Card, CheckIcon, Chip, CircularProgress, CircularProgressCenter, CodeIcon, ColumnsIcon, Container, CopyIcon, DebouncedTextField, Dialog, DialogActions, DialogContent, DialogTitle, ErrorBoundary, FileIcon, FileSearchIcon, FileTextIcon, IconButton, KanbanIcon, KeyIcon, LayoutGridIcon, ListIcon, LoadingButton, Menu, MultiSelect, MultiSelectItem, PanelLeftIcon, Paper, PlusIcon, Select, SelectItem, ShoppingCartIcon, SquareIcon, Tab, TableIcon, Tabs, TextField, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, UserIcon, XIcon, cls, coolIconKeys, defaultBorderMixin, iconSize } from "@rebasepro/ui";
7
+ import { getSubcollections, getTableName, isPropertyBuilder, removeInitialAndTrailingSlashes } from "@rebasepro/common";
8
+ import { AIIcon, ConfirmationDialog, ErrorView, IconForView, UnsavedChangesDialog, useAuthController, useCustomizationController, useLargeLayout, useRebaseContext, useSnackbarController, useUnsavedChangesDialog } from "@rebasepro/core";
9
+ import { Field, Formex, clone, getIn, useCreateFormex, useFormex } from "@rebasepro/formex";
10
+ import { z } from "zod";
11
+ import { camelCase, isEmptyObject, mergeDeep, prettifyIdentifier, randomString, removeUndefined, singular, slugify, toSnakeCase, unslugify } from "@rebasepro/utils";
12
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
+ import { getDataSourceCapabilities } from "@rebasepro/types";
14
+ import { Highlight, themes } from "prism-react-renderer";
15
+ import { buildEntityPropertiesFromData, buildPropertyFromData } from "@rebasepro/schema-inference";
16
+ import JSON5 from "json5";
17
+ //#region src/collection_editor/ui/collection_editor/CollectionYupValidation.tsx
18
+ var CollectionEditorSchema = z.object({
19
+ slug: z.string().min(1, "Required"),
20
+ name: z.string().min(1, "Required"),
21
+ table: z.string().min(1, "Required")
22
+ });
23
+ //#endregion
24
+ //#region src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx
25
+ function GeneralSettingsForm({ isNewCollection, existingPaths, existingIds, parentCollection }) {
26
+ const { values, setFieldValue, handleChange, touched, errors, setFieldTouched, submitCount } = useFormex();
27
+ const [iconDialogOpen, setIconDialogOpen] = useState(false);
28
+ useAuthController();
29
+ useCustomizationController();
30
+ const configController = useCollectionsConfigController();
31
+ const updateDatabaseId = (databaseId) => {
32
+ setFieldValue("databaseId", databaseId ?? void 0);
33
+ };
34
+ const updateName = (name) => {
35
+ setFieldValue("name", name);
36
+ if (!getIn(touched, "table") && isNewCollection && name) setFieldValue("table", toSnakeCase(name));
37
+ if (!getIn(touched, "slug") && isNewCollection && name) setFieldValue("slug", toSnakeCase(name));
38
+ if (!getIn(touched, "singularName") && isNewCollection && name) setFieldValue("singularName", singular(name));
39
+ };
40
+ const collectionIcon = /* @__PURE__ */ jsx(IconForView, { collectionOrView: values });
41
+ const isSubcollection = !!parentCollection;
42
+ const showErrors = submitCount > 0;
43
+ return /* @__PURE__ */ jsx("div", {
44
+ className: "overflow-auto my-auto",
45
+ children: /* @__PURE__ */ jsxs(Container, {
46
+ maxWidth: "4xl",
47
+ className: "flex flex-col gap-4 p-8 m-auto",
48
+ children: [
49
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
50
+ className: "flex flex-row gap-2 items-center",
51
+ children: [
52
+ /* @__PURE__ */ jsx(Typography, {
53
+ variant: !isNewCollection ? "h5" : "h4",
54
+ className: "flex-grow",
55
+ children: isNewCollection ? "New collection" : `${values?.name} collection`
56
+ }),
57
+ /* @__PURE__ */ jsx(DefaultDatabaseField, {
58
+ databaseId: values.databaseId,
59
+ disabled: configController?.readOnly,
60
+ onDatabaseIdUpdate: updateDatabaseId
61
+ }),
62
+ /* @__PURE__ */ jsx(Tooltip, {
63
+ title: "Change icon",
64
+ asChild: true,
65
+ children: /* @__PURE__ */ jsx(IconButton, {
66
+ shape: "square",
67
+ disabled: configController?.readOnly,
68
+ onClick: () => setIconDialogOpen(true),
69
+ children: collectionIcon
70
+ })
71
+ })
72
+ ]
73
+ }), parentCollection && /* @__PURE__ */ jsx(Chip, {
74
+ colorScheme: "teal",
75
+ children: /* @__PURE__ */ jsxs(Typography, {
76
+ variant: "caption",
77
+ children: ["This is a subcollection of ", /* @__PURE__ */ jsx("b", { children: parentCollection.name })]
78
+ })
79
+ })] }),
80
+ /* @__PURE__ */ jsxs("fieldset", {
81
+ disabled: configController?.readOnly,
82
+ className: "contents",
83
+ children: [/* @__PURE__ */ jsxs("div", {
84
+ className: "grid grid-cols-12 gap-4",
85
+ children: [
86
+ /* @__PURE__ */ jsxs("div", {
87
+ className: "col-span-12",
88
+ children: [/* @__PURE__ */ jsx(TextField, {
89
+ value: values.name ?? "",
90
+ onChange: (e) => updateName(e.target.value),
91
+ label: "Name",
92
+ autoFocus: true,
93
+ required: true,
94
+ error: showErrors && Boolean(errors.name)
95
+ }), /* @__PURE__ */ jsx(FieldCaption, {
96
+ error: touched.name && Boolean(errors.name),
97
+ children: touched.name && Boolean(errors.name) ? errors.name : "Name of this collection, usually a plural name (e.g. Products)"
98
+ })]
99
+ }),
100
+ /* @__PURE__ */ jsxs("div", {
101
+ className: cls("col-span-12"),
102
+ children: [/* @__PURE__ */ jsx(Field, {
103
+ name: "table",
104
+ as: DebouncedTextField,
105
+ label: "Table name",
106
+ required: true,
107
+ error: showErrors && Boolean(errors.table)
108
+ }), /* @__PURE__ */ jsx(FieldCaption, {
109
+ error: touched.table && Boolean(errors.table),
110
+ children: touched.table && Boolean(errors.table) ? errors.table : isSubcollection ? "Relative path to the parent (no need to include the parent path)" : "PostgreSQL table name for this collection"
111
+ })]
112
+ }),
113
+ /* @__PURE__ */ jsxs("div", {
114
+ className: "col-span-12",
115
+ children: [/* @__PURE__ */ jsx(TextField, {
116
+ error: showErrors && Boolean(errors.singularName),
117
+ name: "singularName",
118
+ "aria-describedby": "singularName-helper",
119
+ onChange: (e) => {
120
+ setFieldTouched("singularName", true);
121
+ return handleChange(e);
122
+ },
123
+ value: values.singularName ?? "",
124
+ label: "Singular name"
125
+ }), /* @__PURE__ */ jsx(FieldCaption, {
126
+ error: showErrors && Boolean(errors.singularName),
127
+ children: showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"
128
+ })]
129
+ }),
130
+ /* @__PURE__ */ jsxs("div", {
131
+ className: "col-span-12",
132
+ children: [/* @__PURE__ */ jsx(TextField, {
133
+ error: showErrors && Boolean(errors.description),
134
+ name: "description",
135
+ value: values.description ?? "",
136
+ onChange: handleChange,
137
+ multiline: true,
138
+ minRows: 2,
139
+ "aria-describedby": "description-helper-text",
140
+ label: "Description"
141
+ }), /* @__PURE__ */ jsx(FieldCaption, {
142
+ error: showErrors && Boolean(errors.description),
143
+ children: showErrors && Boolean(errors.description) ? errors.description : "Description of the collection, you can use markdown"
144
+ })]
145
+ }),
146
+ /* @__PURE__ */ jsxs("div", {
147
+ className: "col-span-12",
148
+ children: [/* @__PURE__ */ jsx(DebouncedTextField, {
149
+ name: "slug",
150
+ value: values.slug ?? "",
151
+ onChange: (e) => setFieldValue("slug", e.target.value),
152
+ disabled: !isNewCollection,
153
+ label: "Collection ID",
154
+ error: showErrors && Boolean(errors.slug)
155
+ }), /* @__PURE__ */ jsx(FieldCaption, {
156
+ error: touched.slug && Boolean(errors.slug),
157
+ children: touched.slug && Boolean(errors.slug) ? errors.slug : "This ID identifies this collection. Typically the same as the path."
158
+ })]
159
+ })
160
+ ]
161
+ }), null]
162
+ }),
163
+ /* @__PURE__ */ jsx("div", { style: { height: "52px" } }),
164
+ /* @__PURE__ */ jsx(Dialog, {
165
+ open: iconDialogOpen,
166
+ onOpenChange: setIconDialogOpen,
167
+ maxWidth: "xl",
168
+ fullWidth: true,
169
+ children: /* @__PURE__ */ jsx("div", {
170
+ className: "p-4 overflow-auto min-h-[200px]",
171
+ children: /* @__PURE__ */ jsx(SearchIconsView, {
172
+ selectedIcon: typeof values.icon === "string" ? values.icon : void 0,
173
+ onIconSelected: (icon) => {
174
+ setIconDialogOpen(false);
175
+ setFieldValue("icon", icon);
176
+ }
177
+ })
178
+ })
179
+ })
180
+ ]
181
+ })
182
+ });
183
+ }
184
+ function DefaultDatabaseField({ databaseId, disabled = false, onDatabaseIdUpdate }) {
185
+ return /* @__PURE__ */ jsx(Tooltip, {
186
+ title: "Database ID",
187
+ side: "top",
188
+ align: "start",
189
+ children: /* @__PURE__ */ jsx(TextField, {
190
+ size: "small",
191
+ invisible: true,
192
+ inputClassName: "text-end",
193
+ disabled,
194
+ value: databaseId ?? "",
195
+ onChange: (e) => onDatabaseIdUpdate(e.target.value),
196
+ placeholder: "(default)"
197
+ })
198
+ });
199
+ }
200
+ //#endregion
201
+ //#region src/collection_editor/ui/collection_editor/LayoutModeSwitch.tsx
202
+ function LayoutModeSwitch({ value, onChange, className }) {
203
+ return /* @__PURE__ */ jsxs("div", {
204
+ className: cls(className),
205
+ children: [
206
+ /* @__PURE__ */ jsx(Typography, {
207
+ variant: "label",
208
+ color: "secondary",
209
+ className: "ml-3.5",
210
+ children: "Document view"
211
+ }),
212
+ /* @__PURE__ */ jsx("div", {
213
+ className: "my-2",
214
+ children: /* @__PURE__ */ jsx(ToggleButtonGroup, {
215
+ value,
216
+ onValueChange: onChange,
217
+ options: [
218
+ {
219
+ value: "side_panel",
220
+ label: "Side panel",
221
+ icon: /* @__PURE__ */ jsx(ColumnsIcon, {})
222
+ },
223
+ {
224
+ value: "full_screen",
225
+ label: "Full screen",
226
+ icon: /* @__PURE__ */ jsx(SquareIcon, {})
227
+ },
228
+ {
229
+ value: "split",
230
+ label: "Split view",
231
+ icon: /* @__PURE__ */ jsx(PanelLeftIcon, {})
232
+ },
233
+ {
234
+ value: "dialog",
235
+ label: "Centered dialog",
236
+ icon: /* @__PURE__ */ jsx(AppWindow, {})
237
+ }
238
+ ]
239
+ })
240
+ }),
241
+ /* @__PURE__ */ jsx(Typography, {
242
+ variant: "caption",
243
+ color: "secondary",
244
+ className: "ml-3.5",
245
+ children: "Should documents be opened full screen, inline side dialog, or centered dialog"
246
+ })
247
+ ]
248
+ });
249
+ }
250
+ //#endregion
251
+ //#region src/collection_editor/ui/collection_editor/ViewModeSwitch.tsx
252
+ function ViewModeSwitch({ value, onChange, className }) {
253
+ return /* @__PURE__ */ jsxs("div", {
254
+ className: cls(className),
255
+ children: [
256
+ /* @__PURE__ */ jsx(Typography, {
257
+ variant: "label",
258
+ color: "secondary",
259
+ className: "ml-3.5",
260
+ children: "Default collection view"
261
+ }),
262
+ /* @__PURE__ */ jsx("div", {
263
+ className: "my-2",
264
+ children: /* @__PURE__ */ jsx(ToggleButtonGroup, {
265
+ value,
266
+ onValueChange: onChange,
267
+ options: [
268
+ {
269
+ value: "list",
270
+ label: "List",
271
+ icon: /* @__PURE__ */ jsx(ListIcon, {})
272
+ },
273
+ {
274
+ value: "table",
275
+ label: "Table",
276
+ icon: /* @__PURE__ */ jsx(TableIcon, {})
277
+ },
278
+ {
279
+ value: "cards",
280
+ label: "Cards",
281
+ icon: /* @__PURE__ */ jsx(LayoutGridIcon, {})
282
+ },
283
+ {
284
+ value: "kanban",
285
+ label: "Kanban",
286
+ icon: /* @__PURE__ */ jsx(KanbanIcon, {})
287
+ }
288
+ ]
289
+ })
290
+ }),
291
+ /* @__PURE__ */ jsx(Typography, {
292
+ variant: "caption",
293
+ color: "secondary",
294
+ className: "ml-3.5",
295
+ children: "Choose how entities should be displayed by default"
296
+ })
297
+ ]
298
+ });
299
+ }
300
+ //#endregion
301
+ //#region src/collection_editor/ui/collection_editor/KanbanConfigSection.tsx
302
+ function KanbanConfigSection({ className, forceExpanded }) {
303
+ useAuthController();
304
+ const customizationController = useCustomizationController();
305
+ const { values, setFieldValue } = useFormex();
306
+ const panelRef = useRef(null);
307
+ const [columnPropertyDialogOpen, setColumnPropertyDialogOpen] = useState(false);
308
+ const enumStringProperties = useMemo(() => {
309
+ const result = [];
310
+ if (!values.properties) return result;
311
+ Object.entries(values.properties).forEach(([key, prop]) => {
312
+ if (prop && "type" in prop && prop.type === "string" && prop.enum) result.push({
313
+ key,
314
+ label: prop.name || key,
315
+ property: prop
316
+ });
317
+ });
318
+ return result;
319
+ }, [values.properties]);
320
+ const kanbanConfig = values.kanban;
321
+ const columnPropertyMissing = Boolean(kanbanConfig?.columnProperty) && !enumStringProperties.some((p) => p.key === kanbanConfig?.columnProperty);
322
+ React.useEffect(() => {
323
+ if (forceExpanded && panelRef.current) setTimeout(() => {
324
+ panelRef.current?.scrollIntoView({
325
+ behavior: "smooth",
326
+ block: "center"
327
+ });
328
+ }, 100);
329
+ }, [forceExpanded]);
330
+ const showCreateButton = !kanbanConfig?.columnProperty || columnPropertyMissing;
331
+ const dialogPropertyKey = columnPropertyMissing && kanbanConfig?.columnProperty ? kanbanConfig.columnProperty : "status";
332
+ const dialogPropertyName = columnPropertyMissing && kanbanConfig?.columnProperty ? unslugify(kanbanConfig.columnProperty) : "Status";
333
+ return /* @__PURE__ */ jsxs("div", {
334
+ className,
335
+ ref: panelRef,
336
+ children: [
337
+ /* @__PURE__ */ jsx(Select, {
338
+ name: "kanban.columnProperty",
339
+ label: "Kanban Column Property",
340
+ fullWidth: true,
341
+ position: "item-aligned",
342
+ disabled: enumStringProperties.length === 0,
343
+ error: columnPropertyMissing,
344
+ value: kanbanConfig?.columnProperty ?? "",
345
+ onValueChange: (v) => {
346
+ if (v) setFieldValue("kanban", {
347
+ ...kanbanConfig,
348
+ columnProperty: v
349
+ });
350
+ else setFieldValue("kanban", void 0);
351
+ },
352
+ renderValue: (value) => {
353
+ if (columnPropertyMissing) return /* @__PURE__ */ jsxs("span", {
354
+ className: "text-red-500",
355
+ children: [value, " (not found)"]
356
+ });
357
+ const prop = enumStringProperties.find((p) => p.key === value);
358
+ if (!prop) return "Select a property";
359
+ return /* @__PURE__ */ jsxs("div", {
360
+ className: "flex items-center gap-2",
361
+ children: [/* @__PURE__ */ jsx(PropertyConfigBadge, { propertyConfig: getFieldConfig(prop.property, customizationController.propertyConfigs) }), /* @__PURE__ */ jsx("span", { children: prop.label })]
362
+ });
363
+ },
364
+ endAdornment: kanbanConfig?.columnProperty ? /* @__PURE__ */ jsx(IconButton, {
365
+ size: "small",
366
+ onClick: (e) => {
367
+ e.stopPropagation();
368
+ setFieldValue("kanban", void 0);
369
+ },
370
+ children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.smallest })
371
+ }) : void 0,
372
+ children: enumStringProperties.map((prop) => {
373
+ const fieldConfig = getFieldConfig(prop.property, customizationController.propertyConfigs);
374
+ return /* @__PURE__ */ jsx(SelectItem, {
375
+ value: prop.key,
376
+ children: /* @__PURE__ */ jsxs("div", {
377
+ className: "flex items-center gap-3",
378
+ children: [/* @__PURE__ */ jsx(PropertyConfigBadge, { propertyConfig: fieldConfig }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: prop.label }), /* @__PURE__ */ jsx(Typography, {
379
+ variant: "caption",
380
+ color: "secondary",
381
+ children: fieldConfig?.name || "Enum"
382
+ })] })]
383
+ })
384
+ }, prop.key);
385
+ })
386
+ }, `column-select-${enumStringProperties.length}`),
387
+ /* @__PURE__ */ jsx(FieldCaption, {
388
+ error: columnPropertyMissing,
389
+ children: columnPropertyMissing ? `Property "${kanbanConfig?.columnProperty}" does not exist or is not an enum string property. Please select a valid property or clear the selection.` : enumStringProperties.length === 0 ? "No enum string properties found. Add a string property with enum to use Kanban view." : "Select a string property with enum values to group entities into columns"
390
+ }),
391
+ showCreateButton && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Button, {
392
+ variant: "text",
393
+ size: "small",
394
+ className: "ml-3.5 mt-2",
395
+ onClick: () => setColumnPropertyDialogOpen(true),
396
+ children: [
397
+ "+ Create \"",
398
+ dialogPropertyKey,
399
+ "\" property"
400
+ ]
401
+ }), /* @__PURE__ */ jsx(PropertyFormDialog, {
402
+ open: columnPropertyDialogOpen,
403
+ onCancel: () => setColumnPropertyDialogOpen(false),
404
+ property: {
405
+ type: "string",
406
+ name: dialogPropertyName,
407
+ enum: [
408
+ {
409
+ id: "todo",
410
+ label: "To Do"
411
+ },
412
+ {
413
+ id: "in_progress",
414
+ label: "In Progress"
415
+ },
416
+ {
417
+ id: "done",
418
+ label: "Done"
419
+ }
420
+ ]
421
+ },
422
+ propertyKey: dialogPropertyKey,
423
+ existingProperty: false,
424
+ autoOpenTypeSelect: false,
425
+ autoUpdateId: false,
426
+ inArray: false,
427
+ allowDataInference: false,
428
+ propertyConfigs: customizationController.propertyConfigs,
429
+ existingPropertyKeys: Object.keys(values.properties ?? {}),
430
+ onPropertyChanged: ({ id, property }) => {
431
+ const newProperties = {
432
+ ...values.properties,
433
+ [id]: property
434
+ };
435
+ const newPropertiesOrder = [...values.propertiesOrder ?? Object.keys(values.properties ?? {}), id];
436
+ setFieldValue("properties", newProperties);
437
+ setFieldValue("propertiesOrder", newPropertiesOrder);
438
+ setFieldValue("kanban", {
439
+ ...kanbanConfig,
440
+ columnProperty: id
441
+ });
442
+ setColumnPropertyDialogOpen(false);
443
+ }
444
+ })] })
445
+ ]
446
+ });
447
+ }
448
+ //#endregion
449
+ //#region src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx
450
+ function DisplaySettingsForm({ expandKanban }) {
451
+ const { values, setFieldValue, handleChange, submitCount } = useFormex();
452
+ const [orderPropertyDialogOpen, setOrderPropertyDialogOpen] = useState(false);
453
+ useAuthController();
454
+ const customizationController = useCustomizationController();
455
+ const configController = useCollectionsConfigController();
456
+ const textProperties = useMemo(() => {
457
+ const result = [];
458
+ if (!values.properties) return result;
459
+ Object.entries(values.properties).forEach(([key, prop]) => {
460
+ if (prop && "type" in prop && prop.type === "string") result.push({
461
+ key,
462
+ label: prop.name || key,
463
+ property: prop
464
+ });
465
+ });
466
+ return result;
467
+ }, [values.properties]);
468
+ return /* @__PURE__ */ jsx("div", {
469
+ className: "overflow-auto my-auto",
470
+ children: /* @__PURE__ */ jsxs(Container, {
471
+ maxWidth: "4xl",
472
+ className: "flex flex-col gap-4 p-8 m-auto",
473
+ children: [
474
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Typography, {
475
+ variant: "h5",
476
+ className: "flex-grow",
477
+ children: "Display settings"
478
+ }) }),
479
+ /* @__PURE__ */ jsx("fieldset", {
480
+ disabled: configController?.readOnly,
481
+ className: "contents",
482
+ children: /* @__PURE__ */ jsxs("div", {
483
+ className: "grid grid-cols-12 gap-4",
484
+ children: [
485
+ /* @__PURE__ */ jsx(LayoutModeSwitch, {
486
+ className: "col-span-12",
487
+ value: values.openEntityMode ?? "side_panel",
488
+ onChange: (value) => setFieldValue("openEntityMode", value)
489
+ }),
490
+ /* @__PURE__ */ jsx(ViewModeSwitch, {
491
+ className: "col-span-12",
492
+ value: values.defaultViewMode ?? "table",
493
+ onChange: (value) => setFieldValue("defaultViewMode", value)
494
+ }),
495
+ /* @__PURE__ */ jsx(KanbanConfigSection, {
496
+ className: "col-span-12",
497
+ forceExpanded: expandKanban
498
+ }),
499
+ /* @__PURE__ */ jsxs("div", {
500
+ className: "col-span-12 mt-4",
501
+ children: [(() => {
502
+ const orderPropertyMissing = Boolean(values.orderProperty) && !textProperties.some((p) => p.key === values.orderProperty);
503
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Select, {
504
+ name: "orderProperty",
505
+ label: "Order Property",
506
+ fullWidth: true,
507
+ position: "item-aligned",
508
+ disabled: textProperties.length === 0,
509
+ error: orderPropertyMissing,
510
+ value: values.orderProperty ?? "",
511
+ onValueChange: (v) => {
512
+ setFieldValue("orderProperty", v || void 0);
513
+ },
514
+ renderValue: (value) => {
515
+ if (orderPropertyMissing) return /* @__PURE__ */ jsxs("span", {
516
+ className: "text-red-500",
517
+ children: [value, " (not found)"]
518
+ });
519
+ const prop = textProperties.find((p) => p.key === value);
520
+ if (!prop) return "Select a property";
521
+ return /* @__PURE__ */ jsxs("div", {
522
+ className: "flex items-center gap-2",
523
+ children: [/* @__PURE__ */ jsx(PropertyConfigBadge, { propertyConfig: getFieldConfig(prop.property, customizationController.propertyConfigs) }), /* @__PURE__ */ jsx("span", { children: prop.label })]
524
+ });
525
+ },
526
+ endAdornment: values.orderProperty ? /* @__PURE__ */ jsx(IconButton, {
527
+ size: "small",
528
+ onClick: (e) => {
529
+ e.stopPropagation();
530
+ setFieldValue("orderProperty", void 0);
531
+ },
532
+ children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.smallest })
533
+ }) : void 0,
534
+ children: textProperties.map((prop) => {
535
+ const fieldConfig = getFieldConfig(prop.property, customizationController.propertyConfigs);
536
+ return /* @__PURE__ */ jsx(SelectItem, {
537
+ value: prop.key,
538
+ children: /* @__PURE__ */ jsxs("div", {
539
+ className: "flex items-center gap-3",
540
+ children: [/* @__PURE__ */ jsx(PropertyConfigBadge, { propertyConfig: fieldConfig }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: prop.label }), /* @__PURE__ */ jsx(Typography, {
541
+ variant: "caption",
542
+ color: "secondary",
543
+ children: fieldConfig?.name || "Text"
544
+ })] })]
545
+ })
546
+ }, prop.key);
547
+ })
548
+ }, `order-select-${textProperties.length}`), /* @__PURE__ */ jsx(FieldCaption, {
549
+ error: orderPropertyMissing,
550
+ children: orderPropertyMissing ? `Property "${values.orderProperty}" does not exist or is not a text property. Please select a valid property or clear the selection.` : textProperties.length === 0 ? "No text properties found. Add a text property to enable ordering." : "Select a text property to persist the order of items"
551
+ })] });
552
+ })(), (() => {
553
+ const orderPropertyMissing = Boolean(values.orderProperty) && !textProperties.some((p) => p.key === values.orderProperty);
554
+ const showCreateButton = !values.orderProperty || orderPropertyMissing;
555
+ const dialogPropertyKey = orderPropertyMissing && values.orderProperty ? values.orderProperty : "__order";
556
+ const dialogPropertyName = orderPropertyMissing && values.orderProperty ? unslugify(values.orderProperty) : "Order";
557
+ if (!showCreateButton) return null;
558
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Button, {
559
+ variant: "text",
560
+ size: "small",
561
+ className: "ml-3.5 mt-2",
562
+ onClick: () => setOrderPropertyDialogOpen(true),
563
+ children: [
564
+ "+ Create \"",
565
+ dialogPropertyKey,
566
+ "\" property"
567
+ ]
568
+ }), /* @__PURE__ */ jsx(PropertyFormDialog, {
569
+ open: orderPropertyDialogOpen,
570
+ onCancel: () => setOrderPropertyDialogOpen(false),
571
+ property: {
572
+ type: "string",
573
+ name: dialogPropertyName,
574
+ ui: {
575
+ disabled: true,
576
+ hideFromCollection: true
577
+ }
578
+ },
579
+ propertyKey: dialogPropertyKey,
580
+ existingProperty: false,
581
+ autoOpenTypeSelect: false,
582
+ autoUpdateId: false,
583
+ inArray: false,
584
+ allowDataInference: false,
585
+ propertyConfigs: customizationController.propertyConfigs,
586
+ existingPropertyKeys: Object.keys(values.properties ?? {}),
587
+ onPropertyChanged: ({ id, property }) => {
588
+ const newProperties = {
589
+ ...values.properties,
590
+ [id]: property
591
+ };
592
+ const newPropertiesOrder = [...values.propertiesOrder ?? Object.keys(values.properties ?? {}), id];
593
+ setFieldValue("properties", newProperties);
594
+ setFieldValue("propertiesOrder", newPropertiesOrder);
595
+ setFieldValue("orderProperty", id);
596
+ setOrderPropertyDialogOpen(false);
597
+ }
598
+ })] });
599
+ })()]
600
+ }),
601
+ /* @__PURE__ */ jsx("div", {
602
+ className: "col-span-12",
603
+ children: /* @__PURE__ */ jsx(Select, {
604
+ name: "defaultSize",
605
+ fullWidth: true,
606
+ label: "Default row size",
607
+ position: "item-aligned",
608
+ onChange: handleChange,
609
+ value: values.defaultSize ?? "",
610
+ renderValue: (value) => value.toUpperCase(),
611
+ children: [
612
+ "xs",
613
+ "s",
614
+ "m",
615
+ "l",
616
+ "xl"
617
+ ].map((value) => /* @__PURE__ */ jsx(SelectItem, {
618
+ value,
619
+ children: value.toUpperCase()
620
+ }, `size-select-${value}`))
621
+ })
622
+ }),
623
+ /* @__PURE__ */ jsxs("div", {
624
+ className: "col-span-12",
625
+ children: [/* @__PURE__ */ jsx(TextField, {
626
+ name: "sideDialogWidth",
627
+ type: "number",
628
+ "aria-describedby": "sideDialogWidth-helper",
629
+ onChange: (e) => {
630
+ const value = e.target.value;
631
+ if (!value) setFieldValue("sideDialogWidth", null);
632
+ else if (!isNaN(Number(value))) setFieldValue("sideDialogWidth", Number(value));
633
+ },
634
+ endAdornment: /* @__PURE__ */ jsx(IconButton, {
635
+ size: "small",
636
+ onClick: () => {
637
+ setFieldValue("sideDialogWidth", null);
638
+ },
639
+ disabled: !values.sideDialogWidth,
640
+ children: /* @__PURE__ */ jsx(XIcon, { size: iconSize.small })
641
+ }),
642
+ value: values.sideDialogWidth ?? "",
643
+ label: "Side dialog width"
644
+ }), /* @__PURE__ */ jsx(FieldCaption, { children: "Optionally define the width (in pixels) of entities side dialog. Default is 768px" })]
645
+ }),
646
+ /* @__PURE__ */ jsxs("div", {
647
+ className: "col-span-12",
648
+ children: [/* @__PURE__ */ jsx(BooleanSwitchWithLabel, {
649
+ position: "start",
650
+ label: values.inlineEditing === void 0 || values.inlineEditing ? "Data can be edited directly in the table view" : "Data can be edited only in the form view",
651
+ onValueChange: (v) => setFieldValue("inlineEditing", v),
652
+ value: values.inlineEditing === void 0 ? true : values.inlineEditing
653
+ }), /* @__PURE__ */ jsx(FieldCaption, { children: "Allow editing data directly in the table view, without opening the form view." })]
654
+ }),
655
+ /* @__PURE__ */ jsxs("div", {
656
+ className: "col-span-12",
657
+ children: [/* @__PURE__ */ jsx(BooleanSwitchWithLabel, {
658
+ position: "start",
659
+ label: values.includeJsonView === void 0 || values.includeJsonView ? "Include JSON view" : "Do not include JSON view",
660
+ onValueChange: (v) => setFieldValue("includeJsonView", v),
661
+ value: values.includeJsonView === void 0 ? true : values.includeJsonView
662
+ }), /* @__PURE__ */ jsx(FieldCaption, { children: "Allows removing the form view JSON tab if you think it is confusing for your users." })]
663
+ })
664
+ ]
665
+ })
666
+ }),
667
+ /* @__PURE__ */ jsx("div", { style: { height: "52px" } })
668
+ ]
669
+ })
670
+ });
671
+ }
672
+ //#endregion
673
+ //#region src/collection_editor/ui/collection_editor/GetCodeDialog.tsx
674
+ function GetCodeDialog({ collection, onOpenChange, open }) {
675
+ const snackbarController = useSnackbarController();
676
+ const code = collection ? "import { EntityCollection } from \"@rebasepro/core\";\n\nconst " + (collection?.name ? camelCase(collection.name) : "my") + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode({ ...collection }), null, " ") : "No collection selected";
677
+ return /* @__PURE__ */ jsxs(Dialog, {
678
+ open,
679
+ onOpenChange,
680
+ maxWidth: "4xl",
681
+ children: [
682
+ /* @__PURE__ */ jsxs(DialogTitle, {
683
+ variant: "h6",
684
+ children: ["Code for ", collection.name]
685
+ }),
686
+ /* @__PURE__ */ jsxs(DialogContent, { children: [/* @__PURE__ */ jsxs(Typography, {
687
+ variant: "body2",
688
+ className: "my-4 mb-8",
689
+ children: [
690
+ "If you want to customise the collection in code, you can add this collection code to your CMS app configuration. More info in the ",
691
+ /* @__PURE__ */ jsx("a", {
692
+ rel: "noopener noreferrer",
693
+ href: "https://rebase.pro/docs/cloud/quickstart",
694
+ children: "docs"
695
+ }),
696
+ "."
697
+ ]
698
+ }), /* @__PURE__ */ jsx(Highlight, {
699
+ theme: themes.vsDark,
700
+ code,
701
+ language: "typescript",
702
+ children: ({ className, style, tokens, getLineProps, getTokenProps }) => /* @__PURE__ */ jsx("pre", {
703
+ style,
704
+ className: "p-4 rounded-xs text-sm",
705
+ children: tokens.map((line, i) => /* @__PURE__ */ jsx("div", {
706
+ ...getLineProps({ line }),
707
+ children: line.map((token, key) => /* @__PURE__ */ jsx("span", { ...getTokenProps({ token }) }, key))
708
+ }, i))
709
+ })
710
+ })] }),
711
+ /* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsxs(Button, {
712
+ variant: "text",
713
+ size: "small",
714
+ onClick: (e) => {
715
+ e.stopPropagation();
716
+ e.preventDefault();
717
+ snackbarController.open({
718
+ type: "success",
719
+ message: "Copied"
720
+ });
721
+ return navigator.clipboard.writeText(code);
722
+ },
723
+ children: [/* @__PURE__ */ jsx(CopyIcon, {}), "CopyIcon to clipboard"]
724
+ }), /* @__PURE__ */ jsx(Button, {
725
+ onClick: () => onOpenChange(false),
726
+ children: "Close"
727
+ })] })
728
+ ]
729
+ });
730
+ }
731
+ function collectionToCode(collection) {
732
+ const propertyCleanup = (value) => {
733
+ if (value === void 0 || value === null) return value;
734
+ const valueCopy = clone(value);
735
+ if (typeof valueCopy === "function") return valueCopy;
736
+ if (Array.isArray(valueCopy)) return valueCopy.map((v) => propertyCleanup(v));
737
+ if (typeof valueCopy === "object") {
738
+ if (valueCopy === null) return valueCopy;
739
+ const obj = valueCopy;
740
+ Object.keys(obj).forEach((key) => {
741
+ if (!isEmptyObject(obj)) {
742
+ const childRes = propertyCleanup(obj[key]);
743
+ if (childRes !== null && childRes !== void 0 && childRes !== false && !isEmptyObject(childRes)) obj[key] = childRes;
744
+ else delete obj[key];
745
+ }
746
+ });
747
+ delete obj.resolved;
748
+ delete obj.propertiesOrder;
749
+ delete obj.propertyConfig;
750
+ }
751
+ return valueCopy;
752
+ };
753
+ return {
754
+ id: collection.slug,
755
+ name: collection.name,
756
+ singularName: collection.singularName,
757
+ path: collection.slug,
758
+ description: collection.description,
759
+ icon: collection.icon,
760
+ defaultFilter: collection.defaultFilter,
761
+ sort: collection.sort,
762
+ properties: Object.entries({ ...collection.properties ?? {} }).map(([key, value]) => ({ [key]: propertyCleanup(value) })).reduce((a, b) => ({
763
+ ...a,
764
+ ...b
765
+ }), {})
766
+ };
767
+ }
768
+ //#endregion
769
+ //#region src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx
770
+ function CollectionPropertiesEditorForm({ showErrors, isNewCollection, propertyErrorsRef, onPropertyError, setDirty, extraIcon, getUser, getData, doCollectionInference, propertyConfigs }) {
771
+ const { values, setFieldValue, setFieldError, setFieldTouched, errors, dirty } = useFormex();
772
+ const snackbarController = useSnackbarController();
773
+ const configController = useCollectionsConfigController();
774
+ const largeLayout = useLargeLayout();
775
+ const asDialog = !largeLayout;
776
+ const [selectedPropertyIndex, setSelectedPropertyIndex] = useState();
777
+ const [selectedPropertyKey, setSelectedPropertyKey] = useState();
778
+ const [selectedPropertyNamespace, setSelectedPropertyNamespace] = useState();
779
+ const selectedPropertyFullId = selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : void 0;
780
+ const selectedProperty = selectedPropertyFullId ? getIn(values.properties, selectedPropertyFullId.replaceAll(".", ".properties.")) : void 0;
781
+ const [codeDialogOpen, setCodeDialogOpen] = useState(false);
782
+ const [inferringProperties, setInferringProperties] = useState(false);
783
+ const [newPropertyDialogOpen, setNewPropertyDialogOpen] = useState(false);
784
+ const [inferredPropertyKeys, setInferredPropertyKeys] = useState([]);
785
+ const currentPropertiesOrderRef = React.useRef(values.propertiesOrder ? { "": values.propertiesOrder } : {});
786
+ useEffect(() => {
787
+ if (setDirty) setDirty(dirty);
788
+ }, [dirty]);
789
+ const inferPropertiesFromData = doCollectionInference ? () => {
790
+ const inferenceFn = doCollectionInference;
791
+ if (!inferenceFn || configController?.readOnly) return;
792
+ setInferringProperties(true);
793
+ console.debug("CollectionEditor: inferring properties from data", values);
794
+ const promise = inferenceFn(values);
795
+ if (!promise) {
796
+ setInferringProperties(false);
797
+ return;
798
+ }
799
+ promise.then((newCollection) => {
800
+ if (!newCollection) {
801
+ snackbarController.open({
802
+ type: "error",
803
+ message: "Could not infer properties from data"
804
+ });
805
+ return;
806
+ }
807
+ const findNewPropertyKeys = (existingProps, newProps, namespace) => {
808
+ if (!newProps) return [];
809
+ const keys = [];
810
+ for (const key of Object.keys(newProps)) {
811
+ const fullKey = namespace ? `${namespace}.${key}` : key;
812
+ const existingProp = existingProps?.[key];
813
+ const newProp = newProps[key];
814
+ if (!existingProp) keys.push(fullKey);
815
+ else if (typeof newProp === "object" && "type" in newProp && newProp.type === "map" && newProp.properties) {
816
+ const existingMapProps = typeof existingProp === "object" && "type" in existingProp && existingProp.type === "map" ? existingProp.properties : void 0;
817
+ keys.push(...findNewPropertyKeys(existingMapProps, newProp.properties, fullKey));
818
+ }
819
+ }
820
+ return keys;
821
+ };
822
+ const addNewPropertiesOnly = (existingProps, newProps) => {
823
+ if (!newProps) return existingProps ?? {};
824
+ if (!existingProps) return newProps;
825
+ const result = { ...existingProps };
826
+ for (const key of Object.keys(newProps)) {
827
+ const existingProp = existingProps[key];
828
+ const newProp = newProps[key];
829
+ if (!existingProp) result[key] = newProp;
830
+ else if (typeof existingProp === "object" && "type" in existingProp && existingProp.type === "map" && typeof newProp === "object" && "type" in newProp && newProp.type === "map" && newProp.properties) {
831
+ const existingMapProps = existingProp.properties;
832
+ if (existingMapProps) result[key] = {
833
+ ...existingProp,
834
+ properties: addNewPropertiesOnly(existingMapProps, newProp.properties)
835
+ };
836
+ }
837
+ }
838
+ return result;
839
+ };
840
+ const updatedProperties = addNewPropertiesOnly(values.properties ?? {}, newCollection.properties);
841
+ const allNewPropertyKeys = findNewPropertyKeys(values.properties, newCollection.properties);
842
+ const newTopLevelPropertyKeys = (newCollection.properties ? Object.keys(newCollection.properties) : []).filter((propertyKey) => !values.properties[propertyKey]);
843
+ if (allNewPropertyKeys.length === 0) {
844
+ snackbarController.open({
845
+ type: "info",
846
+ message: "No new properties found in existing data"
847
+ });
848
+ return;
849
+ }
850
+ const allExistingKeys = values.propertiesOrder ?? Object.keys(values.properties ?? {});
851
+ const updatedPropertiesOrder = [...newTopLevelPropertyKeys, ...allExistingKeys.filter((key) => !newTopLevelPropertyKeys.includes(key))];
852
+ setFieldValue("properties", updatedProperties, false);
853
+ updatePropertiesOrder(updatedPropertiesOrder);
854
+ setInferredPropertyKeys(allNewPropertyKeys);
855
+ snackbarController.open({
856
+ type: "success",
857
+ message: `Added ${allNewPropertyKeys.length} new ${allNewPropertyKeys.length === 1 ? "property" : "properties"}`
858
+ });
859
+ }).finally(() => {
860
+ setInferringProperties(false);
861
+ });
862
+ } : void 0;
863
+ const getCurrentPropertiesOrder = (namespace) => {
864
+ if (!namespace) return currentPropertiesOrderRef.current[""] ?? getIn(values, namespaceToPropertiesOrderPath());
865
+ return currentPropertiesOrderRef.current[namespace] ?? getIn(values, namespaceToPropertiesOrderPath(namespace));
866
+ };
867
+ const updatePropertiesOrder = (newPropertiesOrder, namespace) => {
868
+ setFieldValue(namespaceToPropertiesOrderPath(namespace), newPropertiesOrder, false);
869
+ currentPropertiesOrderRef.current[namespace ?? ""] = newPropertiesOrder;
870
+ };
871
+ const deleteProperty = (propertyKey, namespace) => {
872
+ if (configController?.readOnly) return;
873
+ const fullId = propertyKey ? getFullId(propertyKey, namespace) : void 0;
874
+ if (!fullId) throw Error("collection editor miss config");
875
+ setFieldValue(idToPropertiesPath(fullId), void 0, false);
876
+ const currentPropertiesOrder = getCurrentPropertiesOrder(namespace);
877
+ if (currentPropertiesOrder) updatePropertiesOrder(currentPropertiesOrder.filter((p) => p !== propertyKey), namespace);
878
+ setNewPropertyDialogOpen(false);
879
+ setSelectedPropertyIndex(void 0);
880
+ setSelectedPropertyKey(void 0);
881
+ setSelectedPropertyNamespace(void 0);
882
+ };
883
+ const onPropertyMove = (propertiesOrder, namespace) => {
884
+ if (configController?.readOnly) return;
885
+ setFieldValue(namespaceToPropertiesOrderPath(namespace), propertiesOrder, false);
886
+ };
887
+ const onPropertyCreated = ({ id, property }) => {
888
+ if (configController?.readOnly) return;
889
+ if (!id) throw Error("Need to include an ID when creating a new property");
890
+ setFieldValue("properties", {
891
+ ...values.properties ?? {},
892
+ [id]: property
893
+ }, false);
894
+ const newPropertiesOrder = [...values.propertiesOrder ?? Object.keys(values.properties), id];
895
+ updatePropertiesOrder(newPropertiesOrder);
896
+ setNewPropertyDialogOpen(false);
897
+ if (largeLayout) {
898
+ setSelectedPropertyIndex(newPropertiesOrder.indexOf(id));
899
+ setSelectedPropertyKey(id);
900
+ }
901
+ setSelectedPropertyNamespace(void 0);
902
+ };
903
+ const onPropertyChanged = ({ id, property, previousId, namespace }) => {
904
+ const fullId = id ? getFullId(id, namespace) : void 0;
905
+ const propertyPath = fullId ? idToPropertiesPath(fullId) : void 0;
906
+ if (previousId && previousId !== id) {
907
+ const previousPropertyPath = idToPropertiesPath(getFullId(previousId, namespace));
908
+ const newPropertiesOrder = getCurrentPropertiesOrder(namespace).map((p) => p === previousId ? id : p).filter((p) => p !== void 0);
909
+ updatePropertiesOrder(newPropertiesOrder, namespace);
910
+ if (id) {
911
+ setSelectedPropertyIndex(newPropertiesOrder.indexOf(id));
912
+ setSelectedPropertyKey(id);
913
+ }
914
+ setFieldValue(previousPropertyPath, void 0, false);
915
+ setFieldTouched(previousPropertyPath, false, false);
916
+ }
917
+ if (propertyPath) {
918
+ setFieldValue(propertyPath, property, false);
919
+ setFieldTouched(propertyPath, true, false);
920
+ }
921
+ };
922
+ const onPropertyErrorInternal = (id, namespace, error) => {
923
+ const propertyPath = id ? getFullId(id, namespace) : void 0;
924
+ console.debug("onPropertyErrorInternal", {
925
+ id,
926
+ namespace,
927
+ error,
928
+ propertyPath
929
+ });
930
+ if (propertyPath) {
931
+ const hasError = error && Object.keys(error).length > 0;
932
+ onPropertyError(id, namespace, hasError ? error : void 0);
933
+ setFieldError(idToPropertiesPath(propertyPath), hasError ? "Property error" : void 0);
934
+ }
935
+ };
936
+ const closePropertyDialog = () => {
937
+ setSelectedPropertyIndex(void 0);
938
+ setSelectedPropertyKey(void 0);
939
+ };
940
+ const initialErrors = selectedPropertyKey && propertyErrorsRef?.current?.properties ? propertyErrorsRef.current.properties[selectedPropertyKey] : void 0;
941
+ const emptyCollection = (values?.propertiesOrder === void 0 || values.propertiesOrder.length === 0) && (!values?.properties || Object.keys(values.properties).length === 0);
942
+ const usedPropertiesOrder = values.propertiesOrder ? values.propertiesOrder : Object.keys(values.properties);
943
+ const owner = useMemo(() => values.ownerId && getUser ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
944
+ const generationCounter = useAIModifiedPaths()?.generationCounter ?? 0;
945
+ const onPropertyClick = (propertyKey, namespace) => {
946
+ console.debug("CollectionEditor: onPropertyClick", {
947
+ propertyKey,
948
+ namespace
949
+ });
950
+ setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
951
+ setSelectedPropertyKey(propertyKey);
952
+ setSelectedPropertyNamespace(namespace);
953
+ };
954
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
955
+ /* @__PURE__ */ jsxs("div", {
956
+ className: "grid grid-cols-12 gap-2 h-full bg-white dark:bg-surface-950",
957
+ children: [
958
+ /* @__PURE__ */ jsxs("div", {
959
+ className: cls("bg-surface-50 dark:bg-surface-900", "p-4 md:p-8", "col-span-12 lg:col-span-5 h-full overflow-auto", !asDialog && "border-r " + defaultBorderMixin),
960
+ children: [
961
+ /* @__PURE__ */ jsxs("div", {
962
+ className: "flex my-2",
963
+ children: [
964
+ /* @__PURE__ */ jsxs("div", {
965
+ className: "grow mb-4",
966
+ children: [/* @__PURE__ */ jsx(Field, {
967
+ name: "name",
968
+ as: DebouncedTextField,
969
+ invisible: true,
970
+ className: "-ml-1",
971
+ inputClassName: "text-2xl font-headers",
972
+ placeholder: "Collection name",
973
+ size: "small",
974
+ required: true,
975
+ error: Boolean(errors?.name)
976
+ }), owner && /* @__PURE__ */ jsxs(Typography, {
977
+ variant: "body2",
978
+ className: "ml-2",
979
+ color: "secondary",
980
+ children: ["Created by ", owner.displayName]
981
+ })]
982
+ }),
983
+ extraIcon && /* @__PURE__ */ jsx("div", {
984
+ className: "ml-4",
985
+ children: extraIcon
986
+ }),
987
+ /* @__PURE__ */ jsxs("div", {
988
+ className: "ml-4 flex flex-row gap-2 items-center flex-shrink-0",
989
+ children: [inferPropertiesFromData && /* @__PURE__ */ jsx(Tooltip, {
990
+ title: "Add new properties based on data",
991
+ asChild: true,
992
+ children: /* @__PURE__ */ jsx(IconButton, {
993
+ variant: "filled",
994
+ disabled: inferringProperties,
995
+ onClick: inferPropertiesFromData,
996
+ children: inferringProperties ? /* @__PURE__ */ jsx(CircularProgress, { size: "small" }) : /* @__PURE__ */ jsx(FileSearchIcon, {})
997
+ })
998
+ }), /* @__PURE__ */ jsx(Tooltip, {
999
+ title: "Add new property",
1000
+ asChild: true,
1001
+ children: /* @__PURE__ */ jsx(Button, {
1002
+ disabled: configController?.readOnly,
1003
+ onClick: () => setNewPropertyDialogOpen(true),
1004
+ children: /* @__PURE__ */ jsx(PlusIcon, {})
1005
+ })
1006
+ })]
1007
+ })
1008
+ ]
1009
+ }),
1010
+ /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(PropertyTree, {
1011
+ className: "mt-2",
1012
+ inferredPropertyKeys,
1013
+ selectedPropertyKey: selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : void 0,
1014
+ properties: values.properties,
1015
+ additionalFields: values.additionalFields,
1016
+ propertiesOrder: usedPropertiesOrder,
1017
+ onPropertyClick,
1018
+ onPropertyMove,
1019
+ onPropertyRemove: (isNewCollection || inferredPropertyKeys && inferredPropertyKeys.length > 0) && !configController?.readOnly ? deleteProperty : void 0,
1020
+ errors
1021
+ }) }),
1022
+ /* @__PURE__ */ jsx(Button, {
1023
+ className: "mt-4 xl:mt-8 w-full",
1024
+ variant: "filled",
1025
+ color: "neutral",
1026
+ disabled: configController?.readOnly,
1027
+ onClick: () => setNewPropertyDialogOpen(true),
1028
+ startIcon: /* @__PURE__ */ jsx(PlusIcon, {}),
1029
+ children: "Add new property"
1030
+ })
1031
+ ]
1032
+ }),
1033
+ !asDialog && /* @__PURE__ */ jsx("div", {
1034
+ className: "col-span-12 lg:col-span-7 p-4 md:py-8 md:px-4 h-full overflow-auto",
1035
+ children: /* @__PURE__ */ jsxs("div", {
1036
+ className: "sticky top-8 min-h-full w-full flex flex-col justify-center",
1037
+ children: [
1038
+ selectedPropertyFullId && !!selectedProperty && !isPropertyBuilder(selectedProperty) && /* @__PURE__ */ jsx(PropertyForm, {
1039
+ inArray: false,
1040
+ existingProperty: !isNewCollection,
1041
+ autoUpdateId: false,
1042
+ allowDataInference: !isNewCollection,
1043
+ autoOpenTypeSelect: false,
1044
+ propertyKey: selectedPropertyKey,
1045
+ propertyNamespace: selectedPropertyNamespace,
1046
+ property: selectedProperty,
1047
+ onPropertyChanged,
1048
+ onDelete: deleteProperty,
1049
+ onError: onPropertyErrorInternal,
1050
+ forceShowErrors: showErrors,
1051
+ initialErrors,
1052
+ getData,
1053
+ propertyConfigs
1054
+ }, `edit_view_${selectedPropertyIndex}_${generationCounter}`),
1055
+ !selectedProperty && /* @__PURE__ */ jsxs("div", {
1056
+ className: "w-full flex flex-col items-center justify-center h-full gap-4",
1057
+ children: [/* @__PURE__ */ jsx(Typography, {
1058
+ variant: "label",
1059
+ className: "",
1060
+ children: emptyCollection ? "Now you can add your first property" : "Select a property to edit it"
1061
+ }), /* @__PURE__ */ jsxs(Button, {
1062
+ disabled: configController?.readOnly,
1063
+ onClick: () => setNewPropertyDialogOpen(true),
1064
+ children: [/* @__PURE__ */ jsx(PlusIcon, {}), "Add new property"]
1065
+ })]
1066
+ }),
1067
+ !!selectedProperty && isPropertyBuilder(selectedProperty) && /* @__PURE__ */ jsx(Typography, {
1068
+ variant: "label",
1069
+ className: "flex items-center justify-center",
1070
+ children: "This property is defined as a property builder in code"
1071
+ })
1072
+ ]
1073
+ })
1074
+ }),
1075
+ asDialog && /* @__PURE__ */ jsx(PropertyFormDialog, {
1076
+ inArray: false,
1077
+ open: selectedPropertyIndex !== void 0,
1078
+ autoUpdateId: !selectedProperty,
1079
+ allowDataInference: !isNewCollection,
1080
+ existingProperty: true,
1081
+ autoOpenTypeSelect: false,
1082
+ propertyKey: selectedPropertyKey,
1083
+ propertyNamespace: selectedPropertyNamespace,
1084
+ property: selectedProperty,
1085
+ onPropertyChanged,
1086
+ onDelete: deleteProperty,
1087
+ onError: onPropertyErrorInternal,
1088
+ forceShowErrors: showErrors,
1089
+ initialErrors,
1090
+ getData,
1091
+ propertyConfigs,
1092
+ onCancel: closePropertyDialog,
1093
+ onOkClicked: asDialog ? closePropertyDialog : void 0
1094
+ }, `edit_view_${selectedPropertyIndex}_${generationCounter}`)
1095
+ ]
1096
+ }),
1097
+ /* @__PURE__ */ jsx(PropertyFormDialog, {
1098
+ inArray: false,
1099
+ existingProperty: false,
1100
+ autoOpenTypeSelect: true,
1101
+ autoUpdateId: true,
1102
+ forceShowErrors: showErrors,
1103
+ open: newPropertyDialogOpen,
1104
+ onCancel: () => setNewPropertyDialogOpen(false),
1105
+ onPropertyChanged: onPropertyCreated,
1106
+ getData,
1107
+ allowDataInference: !isNewCollection,
1108
+ propertyConfigs,
1109
+ existingPropertyKeys: values.propertiesOrder
1110
+ }),
1111
+ /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(GetCodeDialog, {
1112
+ collection: values,
1113
+ open: codeDialogOpen,
1114
+ onOpenChange: setCodeDialogOpen
1115
+ }) })
1116
+ ] });
1117
+ }
1118
+ //#endregion
1119
+ //#region src/collection_editor/ui/collection_editor/CollectionJsonImportDialog.tsx
1120
+ var EXAMPLE_JSON = `{
1121
+ "id": "products",
1122
+ "name": "Products",
1123
+ "path": "products",
1124
+ "icon": "shopping_cart",
1125
+ "properties": {
1126
+ "name": {
1127
+ "type": "string",
1128
+ "name": "Name",
1129
+ "validation": { "required": true }
1130
+ },
1131
+ "price": {
1132
+ "type": "number",
1133
+ "name": "Price"
1134
+ },
1135
+ "available": {
1136
+ "type": "boolean",
1137
+ "name": "Available"
1138
+ }
1139
+ }
1140
+ }`;
1141
+ function CollectionJsonImportDialog({ open, onClose, onImport }) {
1142
+ const [jsonValue, setJsonValue] = useState("");
1143
+ const [errors, setErrors] = useState([]);
1144
+ const [touched, setTouched] = useState(false);
1145
+ const handleJsonChange = useCallback((e) => {
1146
+ const value = e.target.value;
1147
+ setJsonValue(value);
1148
+ setTouched(true);
1149
+ if (!value.trim()) {
1150
+ setErrors([]);
1151
+ return;
1152
+ }
1153
+ setErrors(validateCollectionJson(value).errors);
1154
+ }, []);
1155
+ const handleImport = useCallback(() => {
1156
+ const result = validateCollectionJson(jsonValue);
1157
+ if (result.valid && result.collection) {
1158
+ onImport(result.collection);
1159
+ setJsonValue("");
1160
+ setErrors([]);
1161
+ setTouched(false);
1162
+ onClose();
1163
+ }
1164
+ }, [
1165
+ jsonValue,
1166
+ onImport,
1167
+ onClose
1168
+ ]);
1169
+ const handleClose = useCallback(() => {
1170
+ setJsonValue("");
1171
+ setErrors([]);
1172
+ setTouched(false);
1173
+ onClose();
1174
+ }, [onClose]);
1175
+ const isValid = touched && jsonValue.trim() && errors.length === 0;
1176
+ return /* @__PURE__ */ jsxs(Dialog, {
1177
+ open,
1178
+ onOpenChange: (open) => !open && handleClose(),
1179
+ maxWidth: "2xl",
1180
+ children: [
1181
+ /* @__PURE__ */ jsxs(DialogTitle, {
1182
+ className: "flex items-center gap-2",
1183
+ children: [/* @__PURE__ */ jsx(CodeIcon, { size: iconSize.smallest }), "Import Collection from JSON"]
1184
+ }),
1185
+ /* @__PURE__ */ jsxs(DialogContent, {
1186
+ className: "flex flex-col gap-4",
1187
+ children: [
1188
+ /* @__PURE__ */ jsxs(Typography, {
1189
+ variant: "body2",
1190
+ color: "secondary",
1191
+ children: [
1192
+ "Paste a JSON object representing your collection configuration. The JSON must include ",
1193
+ /* @__PURE__ */ jsx("code", {
1194
+ className: "bg-surface-200 dark:bg-surface-700 px-1 rounded",
1195
+ children: "id"
1196
+ }),
1197
+ ",",
1198
+ /* @__PURE__ */ jsx("code", {
1199
+ className: "bg-surface-200 dark:bg-surface-700 px-1 rounded",
1200
+ children: "name"
1201
+ }),
1202
+ ",",
1203
+ /* @__PURE__ */ jsx("code", {
1204
+ className: "bg-surface-200 dark:bg-surface-700 px-1 rounded",
1205
+ children: "path"
1206
+ }),
1207
+ ", and",
1208
+ /* @__PURE__ */ jsx("code", {
1209
+ className: "bg-surface-200 dark:bg-surface-700 px-1 rounded",
1210
+ children: "properties"
1211
+ }),
1212
+ "."
1213
+ ]
1214
+ }),
1215
+ /* @__PURE__ */ jsx(TextField, {
1216
+ multiline: true,
1217
+ minRows: 12,
1218
+ value: jsonValue,
1219
+ onChange: handleJsonChange,
1220
+ placeholder: EXAMPLE_JSON,
1221
+ error: errors.length > 0 && touched,
1222
+ className: "w-full",
1223
+ inputClassName: "font-mono text-sm resize-none overflow-y-auto h-[300px]"
1224
+ }),
1225
+ errors.length > 0 && touched && /* @__PURE__ */ jsxs(Alert, {
1226
+ color: "error",
1227
+ children: [/* @__PURE__ */ jsx(Typography, {
1228
+ variant: "body2",
1229
+ className: "font-medium mb-2",
1230
+ children: "Validation errors:"
1231
+ }), /* @__PURE__ */ jsx("ul", {
1232
+ className: "list-disc list-inside space-y-1",
1233
+ children: errors.map((error, index) => /* @__PURE__ */ jsx("li", {
1234
+ className: "text-sm",
1235
+ children: error.path ? /* @__PURE__ */ jsxs(Fragment, { children: [
1236
+ /* @__PURE__ */ jsx("code", {
1237
+ className: "bg-red-100 dark:bg-red-900/40 px-1 rounded",
1238
+ children: error.path
1239
+ }),
1240
+ ": ",
1241
+ error.message
1242
+ ] }) : error.message
1243
+ }, index))
1244
+ })]
1245
+ }),
1246
+ isValid && /* @__PURE__ */ jsx(Alert, {
1247
+ color: "success",
1248
+ children: /* @__PURE__ */ jsx(Typography, {
1249
+ variant: "body2",
1250
+ children: "✓ JSON is valid and ready to import"
1251
+ })
1252
+ })
1253
+ ]
1254
+ }),
1255
+ /* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
1256
+ variant: "text",
1257
+ onClick: handleClose,
1258
+ children: "Cancel"
1259
+ }), /* @__PURE__ */ jsx(Button, {
1260
+ variant: "filled",
1261
+ color: "primary",
1262
+ onClick: handleImport,
1263
+ disabled: !isValid,
1264
+ children: "Import Collection"
1265
+ })] })
1266
+ ]
1267
+ });
1268
+ }
1269
+ //#endregion
1270
+ //#region src/collection_editor/ui/collection_editor/templates/products_template.ts
1271
+ var productsCollectionTemplate = {
1272
+ slug: "products",
1273
+ table: "products",
1274
+ name: "Products",
1275
+ singularName: "Product",
1276
+ icon: "ShoppingCart",
1277
+ description: "List of the products currently sold in your shop",
1278
+ properties: {
1279
+ name: {
1280
+ type: "string",
1281
+ name: "Name",
1282
+ description: "Name of this product",
1283
+ validation: { required: true }
1284
+ },
1285
+ brand: {
1286
+ type: "string",
1287
+ name: "Brand",
1288
+ validation: { required: true }
1289
+ },
1290
+ description: {
1291
+ type: "string",
1292
+ name: "Description",
1293
+ description: "Description of this product, supports markdown",
1294
+ markdown: true
1295
+ },
1296
+ main_image: {
1297
+ type: "string",
1298
+ name: "Image",
1299
+ storage: {
1300
+ storagePath: "images",
1301
+ acceptedFiles: ["image/*"]
1302
+ },
1303
+ description: "Upload field for images"
1304
+ },
1305
+ available: {
1306
+ type: "boolean",
1307
+ name: "Available",
1308
+ ui: { columnWidth: 100 },
1309
+ description: "Is this product available in the website"
1310
+ },
1311
+ price: {
1312
+ type: "number",
1313
+ name: "Price",
1314
+ validation: {
1315
+ requiredMessage: "You must set a positive price",
1316
+ min: 0
1317
+ }
1318
+ },
1319
+ images: {
1320
+ type: "array",
1321
+ name: "Images",
1322
+ ui: { hideFromCollection: true },
1323
+ of: {
1324
+ type: "string",
1325
+ storage: {
1326
+ storagePath: "images",
1327
+ acceptedFiles: ["image/*"]
1328
+ }
1329
+ }
1330
+ },
1331
+ related_products: {
1332
+ type: "array",
1333
+ name: "Related products",
1334
+ description: "Products related to this one",
1335
+ of: {
1336
+ type: "reference",
1337
+ path: "products"
1338
+ }
1339
+ },
1340
+ metadata: {
1341
+ name: "Metadata",
1342
+ description: "This is an example of a map property",
1343
+ type: "map",
1344
+ keyValue: true
1345
+ },
1346
+ added_on: {
1347
+ type: "date",
1348
+ name: "Added on",
1349
+ autoValue: "on_create"
1350
+ }
1351
+ }
1352
+ };
1353
+ //#endregion
1354
+ //#region src/collection_editor/ui/collection_editor/templates/blog_template.ts
1355
+ var blogCollectionTemplate = {
1356
+ slug: "blog",
1357
+ table: "blog",
1358
+ name: "Blog",
1359
+ singularName: "Blog entry",
1360
+ icon: "FileText",
1361
+ description: "A collection of blog entries",
1362
+ defaultSize: "l",
1363
+ properties: {
1364
+ name: {
1365
+ name: "Name",
1366
+ validation: { required: true },
1367
+ type: "string"
1368
+ },
1369
+ header_image: {
1370
+ name: "Header image",
1371
+ type: "string",
1372
+ storage: {
1373
+ storagePath: "images",
1374
+ acceptedFiles: ["image/*"],
1375
+ metadata: { cacheControl: "max-age=1000000" }
1376
+ }
1377
+ },
1378
+ content: {
1379
+ name: "Content",
1380
+ description: "Content blocks for the blog entry",
1381
+ validation: { required: true },
1382
+ type: "array",
1383
+ oneOf: {
1384
+ typeField: "type",
1385
+ valueField: "value",
1386
+ properties: {
1387
+ text: {
1388
+ type: "string",
1389
+ name: "Text",
1390
+ markdown: true
1391
+ },
1392
+ quote: {
1393
+ type: "string",
1394
+ name: "Quote",
1395
+ multiline: true
1396
+ },
1397
+ images: {
1398
+ name: "Images",
1399
+ type: "array",
1400
+ of: {
1401
+ type: "string",
1402
+ storage: {
1403
+ storagePath: "images",
1404
+ acceptedFiles: ["image/*"],
1405
+ metadata: { cacheControl: "max-age=1000000" }
1406
+ }
1407
+ },
1408
+ description: "This fields allows uploading multiple images at once and reordering"
1409
+ },
1410
+ products: {
1411
+ name: "Products",
1412
+ type: "array",
1413
+ of: {
1414
+ type: "reference",
1415
+ path: "products",
1416
+ previewProperties: ["name", "main_image"]
1417
+ }
1418
+ }
1419
+ },
1420
+ propertiesOrder: [
1421
+ "text",
1422
+ "quote",
1423
+ "images",
1424
+ "products"
1425
+ ]
1426
+ }
1427
+ },
1428
+ created_on: {
1429
+ name: "Created on",
1430
+ type: "date",
1431
+ autoValue: "on_create"
1432
+ },
1433
+ status: {
1434
+ name: "Status",
1435
+ validation: { required: true },
1436
+ type: "string",
1437
+ enum: {
1438
+ published: {
1439
+ id: "published",
1440
+ label: "Published"
1441
+ },
1442
+ draft: "Draft"
1443
+ },
1444
+ defaultValue: "draft"
1445
+ },
1446
+ publish_date: {
1447
+ name: "Publish date",
1448
+ type: "date",
1449
+ clearable: true
1450
+ },
1451
+ reviewed: {
1452
+ name: "Reviewed",
1453
+ type: "boolean"
1454
+ },
1455
+ tags: {
1456
+ name: "Tags",
1457
+ description: "Example of generic array",
1458
+ type: "array",
1459
+ of: {
1460
+ type: "string",
1461
+ previewAsTag: true
1462
+ }
1463
+ }
1464
+ }
1465
+ };
1466
+ //#endregion
1467
+ //#region src/collection_editor/ui/collection_editor/templates/users_template.ts
1468
+ var usersCollectionTemplate = {
1469
+ slug: "users",
1470
+ table: "users",
1471
+ name: "Users",
1472
+ singularName: "User",
1473
+ description: "Registered users in the app/web",
1474
+ icon: "User",
1475
+ properties: {
1476
+ displayName: {
1477
+ name: "Display name",
1478
+ type: "string"
1479
+ },
1480
+ email: {
1481
+ name: "Email",
1482
+ type: "string",
1483
+ email: true
1484
+ },
1485
+ emailVerified: {
1486
+ name: "Email verified",
1487
+ type: "boolean"
1488
+ },
1489
+ phone: {
1490
+ name: "Phone",
1491
+ type: "string"
1492
+ },
1493
+ favourite_products: {
1494
+ name: "Favourite products",
1495
+ type: "array",
1496
+ of: {
1497
+ type: "reference",
1498
+ path: "products"
1499
+ }
1500
+ },
1501
+ photoURL: {
1502
+ name: "Photo URL",
1503
+ type: "string",
1504
+ url: "image"
1505
+ }
1506
+ }
1507
+ };
1508
+ //#endregion
1509
+ //#region src/collection_editor/ui/collection_editor/templates/pages_template.ts
1510
+ var pagesCollectionTemplate = {
1511
+ slug: "pages",
1512
+ table: "pages",
1513
+ name: "Pages",
1514
+ singularName: "Page",
1515
+ icon: "File",
1516
+ description: "List of website pages that can be edited here",
1517
+ properties: {
1518
+ title: {
1519
+ type: "string",
1520
+ name: "Page Title",
1521
+ validation: { required: true }
1522
+ },
1523
+ slug: {
1524
+ type: "string",
1525
+ name: "URL Slug",
1526
+ validation: {
1527
+ required: true,
1528
+ unique: true,
1529
+ matches: "^[a-z0-9]+(?:-[a-z0-9]+)*$",
1530
+ matchesMessage: "Must be lowercase, alphanumeric, and hyphenated"
1531
+ }
1532
+ },
1533
+ hero_section: {
1534
+ type: "map",
1535
+ name: "Hero Section",
1536
+ properties: {
1537
+ headline: {
1538
+ type: "string",
1539
+ name: "Headline",
1540
+ validation: { required: true }
1541
+ },
1542
+ subhead: {
1543
+ type: "string",
1544
+ name: "Subheadline"
1545
+ },
1546
+ background_image: {
1547
+ type: "string",
1548
+ name: "Background Image",
1549
+ storage: {
1550
+ storagePath: "page_hero/images",
1551
+ acceptedFiles: ["image/*"]
1552
+ }
1553
+ },
1554
+ call_to_action: {
1555
+ type: "string",
1556
+ name: "Call to Action"
1557
+ },
1558
+ call_to_action_link: {
1559
+ type: "string",
1560
+ name: "CTA Link",
1561
+ url: true
1562
+ }
1563
+ }
1564
+ },
1565
+ content: {
1566
+ type: "array",
1567
+ name: "Content",
1568
+ oneOf: { properties: {
1569
+ section: {
1570
+ type: "map",
1571
+ name: "Section",
1572
+ properties: {
1573
+ title: {
1574
+ type: "string",
1575
+ name: "Section Title",
1576
+ validation: { required: true }
1577
+ },
1578
+ content: {
1579
+ type: "string",
1580
+ name: "Section Content",
1581
+ markdown: true
1582
+ },
1583
+ image: {
1584
+ type: "string",
1585
+ name: "Section Image",
1586
+ storage: {
1587
+ storagePath: "page_sections/images",
1588
+ acceptedFiles: ["image/*"]
1589
+ }
1590
+ },
1591
+ link: {
1592
+ type: "string",
1593
+ name: "Section Link",
1594
+ url: true
1595
+ }
1596
+ }
1597
+ },
1598
+ image: {
1599
+ type: "string",
1600
+ name: "Image",
1601
+ storage: {
1602
+ storagePath: "page_sections/images",
1603
+ acceptedFiles: ["image/*"]
1604
+ }
1605
+ },
1606
+ slider: {
1607
+ type: "array",
1608
+ name: "Slider",
1609
+ of: {
1610
+ type: "map",
1611
+ properties: {
1612
+ title: {
1613
+ type: "string",
1614
+ name: "Title",
1615
+ validation: { required: true }
1616
+ },
1617
+ image: {
1618
+ type: "string",
1619
+ storage: {
1620
+ storagePath: "page_sections/images",
1621
+ acceptedFiles: ["image/*"]
1622
+ }
1623
+ }
1624
+ }
1625
+ }
1626
+ }
1627
+ } }
1628
+ },
1629
+ sidebar: {
1630
+ type: "map",
1631
+ name: "Sidebar",
1632
+ properties: {
1633
+ title: {
1634
+ type: "string",
1635
+ name: "Sidebar Title",
1636
+ validation: { required: false }
1637
+ },
1638
+ content: {
1639
+ type: "string",
1640
+ name: "Sidebar Content",
1641
+ markdown: true
1642
+ }
1643
+ }
1644
+ },
1645
+ seo_metadata: {
1646
+ type: "map",
1647
+ name: "SEO Metadata",
1648
+ properties: {
1649
+ meta_title: {
1650
+ type: "string",
1651
+ name: "Meta Title"
1652
+ },
1653
+ meta_description: {
1654
+ type: "string",
1655
+ name: "Meta Description"
1656
+ },
1657
+ focus_keywords: {
1658
+ type: "array",
1659
+ name: "Focus Keywords",
1660
+ of: { type: "string" }
1661
+ }
1662
+ }
1663
+ },
1664
+ footer_override: {
1665
+ type: "string",
1666
+ name: "Footer Override",
1667
+ markdown: true
1668
+ },
1669
+ publish_date: {
1670
+ type: "date",
1671
+ name: "Publish Date",
1672
+ validation: { required: true }
1673
+ },
1674
+ last_updated: {
1675
+ type: "date",
1676
+ name: "Last Updated",
1677
+ autoValue: "on_update"
1678
+ },
1679
+ is_published: {
1680
+ type: "boolean",
1681
+ name: "Is Published",
1682
+ ui: { columnWidth: 100 },
1683
+ description: "Should this page be live on the site?"
1684
+ }
1685
+ }
1686
+ };
1687
+ //#endregion
1688
+ //#region src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx
1689
+ function AICollectionGeneratorPopover({ existingCollection, onGenerated, generateCollection, trigger, size = "small", showLabel = true, onAnalyticsEvent }) {
1690
+ const [menuOpen, setMenuOpen] = useState(false);
1691
+ const [prompt, setPrompt] = useState("");
1692
+ const [loading, setLoading] = useState(false);
1693
+ const [error, setError] = useState(null);
1694
+ const collectionRegistry = useCollectionRegistryController();
1695
+ const configController = useCollectionsConfigController();
1696
+ const snackbarController = useSnackbarController();
1697
+ const existingCollections = collectionRegistry.collections ?? [];
1698
+ const handleGenerate = async () => {
1699
+ if (!prompt.trim()) return;
1700
+ setLoading(true);
1701
+ setError(null);
1702
+ const mode = existingCollection ? "modify" : "create";
1703
+ onAnalyticsEvent?.("ai_collection_generate_start", { mode });
1704
+ try {
1705
+ const collectionsContext = existingCollections.map((c) => ({
1706
+ slug: c.slug,
1707
+ name: c.name,
1708
+ properties: c.properties,
1709
+ propertiesOrder: c.propertiesOrder
1710
+ }));
1711
+ const result = await generateCollection({
1712
+ prompt: prompt.trim(),
1713
+ existingCollections: collectionsContext.slice(0, 30),
1714
+ ...existingCollection && { existingCollection: {
1715
+ slug: existingCollection.slug,
1716
+ name: existingCollection.name,
1717
+ properties: existingCollection.properties,
1718
+ propertiesOrder: existingCollection.propertiesOrder
1719
+ } }
1720
+ });
1721
+ onGenerated(result.collection, result.operations);
1722
+ setMenuOpen(false);
1723
+ setPrompt("");
1724
+ onAnalyticsEvent?.("ai_collection_generate_success", {
1725
+ mode,
1726
+ operationsCount: result.operations?.length
1727
+ });
1728
+ snackbarController.open({
1729
+ type: "success",
1730
+ message: existingCollection ? "Collection updated with AI suggestions" : "Collection generated successfully"
1731
+ });
1732
+ } catch (e) {
1733
+ console.error("Error generating collection:", e);
1734
+ const errorMessage = e instanceof CollectionGenerationApiError ? e.message : "Failed to generate collection. Please try again.";
1735
+ setError(errorMessage);
1736
+ onAnalyticsEvent?.("ai_collection_generate_error", {
1737
+ mode,
1738
+ error: errorMessage
1739
+ });
1740
+ snackbarController.open({
1741
+ type: "error",
1742
+ message: errorMessage
1743
+ });
1744
+ } finally {
1745
+ setLoading(false);
1746
+ }
1747
+ };
1748
+ const handleKeyDown = (e) => {
1749
+ if (e.key === "Enter" && !e.shiftKey) {
1750
+ e.preventDefault();
1751
+ handleGenerate();
1752
+ }
1753
+ };
1754
+ const tooltipTitle = configController?.readOnly ? configController.readOnlyReason || "Collection generation is disabled" : void 0;
1755
+ const defaultTrigger = showLabel ? /* @__PURE__ */ jsx(Tooltip, {
1756
+ title: tooltipTitle,
1757
+ asChild: true,
1758
+ children: /* @__PURE__ */ jsx(Button, {
1759
+ variant: "text",
1760
+ size,
1761
+ disabled: loading || configController?.readOnly,
1762
+ startIcon: loading ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : /* @__PURE__ */ jsx(AIIcon, { size: "small" }),
1763
+ children: "AI Assist"
1764
+ })
1765
+ }) : /* @__PURE__ */ jsx(Tooltip, {
1766
+ title: tooltipTitle,
1767
+ asChild: true,
1768
+ children: /* @__PURE__ */ jsx(IconButton, {
1769
+ size,
1770
+ disabled: loading || configController?.readOnly,
1771
+ "aria-label": "AI Assist",
1772
+ children: loading ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : /* @__PURE__ */ jsx(AIIcon, { size: "small" })
1773
+ })
1774
+ });
1775
+ return /* @__PURE__ */ jsx(Menu, {
1776
+ open: menuOpen,
1777
+ onOpenChange: (open) => {
1778
+ setMenuOpen(open);
1779
+ if (!open) setError(null);
1780
+ },
1781
+ trigger: trigger ?? defaultTrigger,
1782
+ children: /* @__PURE__ */ jsxs("div", {
1783
+ className: "p-4 flex flex-col gap-3 min-w-[360px] max-w-[480px]",
1784
+ children: [
1785
+ /* @__PURE__ */ jsxs("div", {
1786
+ className: "flex items-center gap-2",
1787
+ children: [/* @__PURE__ */ jsx(AIIcon, { size: "small" }), /* @__PURE__ */ jsx(Typography, {
1788
+ variant: "subtitle2",
1789
+ children: existingCollection ? "Modify Collection with AI" : "Generate Collection with AI"
1790
+ })]
1791
+ }),
1792
+ /* @__PURE__ */ jsx(Typography, {
1793
+ variant: "caption",
1794
+ color: "secondary",
1795
+ children: existingCollection ? "Describe the changes you want to make to this collection." : "Describe the collection you want to create."
1796
+ }),
1797
+ /* @__PURE__ */ jsx(TextField, {
1798
+ size: "small",
1799
+ multiline: true,
1800
+ autoFocus: true,
1801
+ className: "w-full text-text-primary dark:text-text-primary-dark",
1802
+ value: prompt,
1803
+ onChange: (e) => setPrompt(e.target.value),
1804
+ onKeyDown: handleKeyDown,
1805
+ placeholder: existingCollection ? "e.g., Add a thumbnail image field with storage, make price required..." : "e.g., Create a products collection with name, price, description, and category...",
1806
+ disabled: loading
1807
+ }),
1808
+ error && /* @__PURE__ */ jsx(Typography, {
1809
+ variant: "caption",
1810
+ className: "text-red-500",
1811
+ children: error
1812
+ }),
1813
+ /* @__PURE__ */ jsxs("div", {
1814
+ className: "flex justify-end gap-2",
1815
+ children: [/* @__PURE__ */ jsx(Button, {
1816
+ variant: "text",
1817
+ size: "small",
1818
+ onClick: () => setMenuOpen(false),
1819
+ disabled: loading,
1820
+ children: "Cancel"
1821
+ }), /* @__PURE__ */ jsx(Button, {
1822
+ variant: "filled",
1823
+ size: "small",
1824
+ onClick: handleGenerate,
1825
+ disabled: !prompt.trim() || loading,
1826
+ startIcon: loading ? /* @__PURE__ */ jsx(CircularProgress, { size: "smallest" }) : void 0,
1827
+ children: loading ? "Generating..." : "Generate"
1828
+ })]
1829
+ })
1830
+ ]
1831
+ })
1832
+ });
1833
+ }
1834
+ //#endregion
1835
+ //#region src/collection_editor/ui/collection_editor/CollectionEditorWelcomeView.tsx
1836
+ function CollectionEditorWelcomeView({ path, parentCollection, onContinue, existingCollectionPaths, generateCollection, onAnalyticsEvent, unmappedTables, onImportFromTable }) {
1837
+ const { pathSuggestions } = useCollectionEditorController();
1838
+ const filteredSuggestions = (pathSuggestions ?? []).filter((s) => !(existingCollectionPaths ?? []).find((c) => c.trim().toLowerCase() === s.trim().toLowerCase()));
1839
+ const { values, setFieldValue, setValues, submitCount } = useFormex();
1840
+ const [jsonImportOpen, setJsonImportOpen] = useState(false);
1841
+ const [importingTable, setImportingTable] = useState(null);
1842
+ const filteredUnmappedTables = (unmappedTables ?? []).filter((t) => !(existingCollectionPaths ?? []).find((c) => c.trim().toLowerCase() === t.trim().toLowerCase()));
1843
+ return /* @__PURE__ */ jsx("div", {
1844
+ className: "overflow-auto my-auto",
1845
+ children: /* @__PURE__ */ jsxs(Container, {
1846
+ maxWidth: "4xl",
1847
+ className: "flex flex-col gap-4 p-8 m-auto",
1848
+ children: [
1849
+ /* @__PURE__ */ jsx("div", {
1850
+ className: "flex flex-row py-2 pt-3 items-center",
1851
+ children: /* @__PURE__ */ jsx(Typography, {
1852
+ variant: "h4",
1853
+ className: "grow",
1854
+ children: "New collection"
1855
+ })
1856
+ }),
1857
+ parentCollection && /* @__PURE__ */ jsx(Chip, {
1858
+ colorScheme: "teal",
1859
+ children: /* @__PURE__ */ jsxs(Typography, {
1860
+ variant: "caption",
1861
+ children: ["This is a subcollection of ", /* @__PURE__ */ jsx("b", { children: parentCollection.name })]
1862
+ })
1863
+ }),
1864
+ filteredUnmappedTables.length > 0 && /* @__PURE__ */ jsxs("div", {
1865
+ className: "my-2",
1866
+ children: [/* @__PURE__ */ jsx(Typography, {
1867
+ variant: "caption",
1868
+ color: "secondary",
1869
+ children: "● Import from an existing database table:"
1870
+ }), /* @__PURE__ */ jsx("div", {
1871
+ className: "flex flex-wrap gap-x-2 gap-y-1 items-center my-2 min-h-7",
1872
+ children: filteredUnmappedTables.map((tableName) => /* @__PURE__ */ jsxs(Chip, {
1873
+ colorScheme: "purple",
1874
+ onClick: () => {
1875
+ if (onImportFromTable) {
1876
+ setImportingTable(tableName);
1877
+ onImportFromTable(tableName);
1878
+ } else {
1879
+ setFieldValue("name", prettifyIdentifier(tableName));
1880
+ setFieldValue("id", tableName);
1881
+ setFieldValue("path", tableName);
1882
+ onContinue();
1883
+ }
1884
+ },
1885
+ size: "small",
1886
+ children: [/* @__PURE__ */ jsx(TableIcon, { size: iconSize.smallest }), importingTable === tableName ? "Loading..." : tableName]
1887
+ }, tableName))
1888
+ })]
1889
+ }),
1890
+ (filteredSuggestions ?? []).length > 0 && /* @__PURE__ */ jsxs("div", {
1891
+ className: "my-2",
1892
+ children: [/* @__PURE__ */ jsx(Typography, {
1893
+ variant: "caption",
1894
+ color: "secondary",
1895
+ children: "● Use one of the existing paths in your database:"
1896
+ }), /* @__PURE__ */ jsx("div", {
1897
+ className: "flex flex-wrap gap-x-2 gap-y-1 items-center my-2 min-h-7",
1898
+ children: filteredSuggestions?.map((suggestion, index) => /* @__PURE__ */ jsx(Chip, {
1899
+ colorScheme: "cyan",
1900
+ onClick: () => {
1901
+ setFieldValue("name", prettifyIdentifier(suggestion));
1902
+ setFieldValue("id", suggestion);
1903
+ setFieldValue("path", suggestion);
1904
+ setFieldValue("properties", void 0);
1905
+ onContinue();
1906
+ },
1907
+ size: "small",
1908
+ children: suggestion
1909
+ }, suggestion))
1910
+ })]
1911
+ }),
1912
+ /* @__PURE__ */ jsxs("div", {
1913
+ className: "flex flex-row gap-8",
1914
+ children: [generateCollection && /* @__PURE__ */ jsxs("div", {
1915
+ className: "my-2",
1916
+ children: [/* @__PURE__ */ jsx(Typography, {
1917
+ variant: "caption",
1918
+ color: "secondary",
1919
+ className: "mb-2",
1920
+ children: "● Describe your collection to AI:"
1921
+ }), /* @__PURE__ */ jsx(AICollectionGeneratorPopover, {
1922
+ onGenerated: (generatedCollection) => {
1923
+ setValues(generatedCollection);
1924
+ onContinue();
1925
+ },
1926
+ generateCollection,
1927
+ onAnalyticsEvent,
1928
+ trigger: /* @__PURE__ */ jsx(Button, {
1929
+ variant: "filled",
1930
+ color: "neutral",
1931
+ startIcon: /* @__PURE__ */ jsx(AIIcon, { size: "small" }),
1932
+ children: "Generate with AI"
1933
+ })
1934
+ })]
1935
+ }), /* @__PURE__ */ jsxs("div", {
1936
+ className: "my-2",
1937
+ children: [
1938
+ /* @__PURE__ */ jsx(Typography, {
1939
+ variant: "caption",
1940
+ color: "secondary",
1941
+ className: "mb-2",
1942
+ children: "● Create from JSON configuration:"
1943
+ }),
1944
+ /* @__PURE__ */ jsx(Button, {
1945
+ variant: "filled",
1946
+ color: "neutral",
1947
+ onClick: () => setJsonImportOpen(true),
1948
+ startIcon: /* @__PURE__ */ jsx(CodeIcon, { size: iconSize.small }),
1949
+ children: "Paste JSON Configuration"
1950
+ }),
1951
+ /* @__PURE__ */ jsx(CollectionJsonImportDialog, {
1952
+ open: jsonImportOpen,
1953
+ onClose: () => setJsonImportOpen(false),
1954
+ onImport: (collection) => {
1955
+ setValues(collection);
1956
+ onContinue();
1957
+ }
1958
+ })
1959
+ ]
1960
+ })]
1961
+ }),
1962
+ !parentCollection && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
1963
+ variant: "caption",
1964
+ color: "secondary",
1965
+ className: "mb-2",
1966
+ children: "● Create a collection from a file (csv, json, xls, xslx...)"
1967
+ }), /* @__PURE__ */ jsx(ImportFileUpload, { onDataAdded: (data, propertiesOrder) => onContinue(data, propertiesOrder) })] }),
1968
+ /* @__PURE__ */ jsxs("div", {
1969
+ className: "my-2",
1970
+ children: [/* @__PURE__ */ jsx(Typography, {
1971
+ variant: "caption",
1972
+ color: "secondary",
1973
+ children: "● Select a template:"
1974
+ }), /* @__PURE__ */ jsxs("div", {
1975
+ className: "flex gap-2",
1976
+ children: [
1977
+ /* @__PURE__ */ jsx(TemplateButton, {
1978
+ title: "Products",
1979
+ subtitle: "A collection of products with images, prices and stock",
1980
+ icon: /* @__PURE__ */ jsx(ShoppingCartIcon, { size: iconSize.small }),
1981
+ onClick: () => {
1982
+ setValues(productsCollectionTemplate);
1983
+ onContinue();
1984
+ }
1985
+ }),
1986
+ /* @__PURE__ */ jsx(TemplateButton, {
1987
+ title: "Users",
1988
+ subtitle: "A collection of users with emails, names and roles",
1989
+ icon: /* @__PURE__ */ jsx(UserIcon, { size: iconSize.small }),
1990
+ onClick: () => {
1991
+ setValues(usersCollectionTemplate);
1992
+ onContinue();
1993
+ }
1994
+ }),
1995
+ /* @__PURE__ */ jsx(TemplateButton, {
1996
+ title: "Blog posts",
1997
+ subtitle: "A collection of blog posts with images, authors and complex content",
1998
+ icon: /* @__PURE__ */ jsx(FileTextIcon, { size: iconSize.small }),
1999
+ onClick: () => {
2000
+ setValues(blogCollectionTemplate);
2001
+ onContinue();
2002
+ }
2003
+ }),
2004
+ /* @__PURE__ */ jsx(TemplateButton, {
2005
+ title: "Pages",
2006
+ subtitle: "A collection of pages with images, authors and complex content",
2007
+ icon: /* @__PURE__ */ jsx(FileIcon, { size: iconSize.small }),
2008
+ onClick: () => {
2009
+ setValues(pagesCollectionTemplate);
2010
+ onContinue();
2011
+ }
2012
+ })
2013
+ ]
2014
+ })]
2015
+ })
2016
+ ]
2017
+ })
2018
+ });
2019
+ }
2020
+ function TemplateButton({ title, subtitle, icon, onClick }) {
2021
+ return /* @__PURE__ */ jsx(Tooltip, {
2022
+ title: subtitle,
2023
+ asChild: true,
2024
+ children: /* @__PURE__ */ jsxs(Card, {
2025
+ onClick,
2026
+ className: cls("my-2 rounded-md border px-4 py-3 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center", "text-text-secondary dark:text-text-secondary-dark", "hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary", "border-surface-400 dark:border-surface-600 "),
2027
+ children: [icon, /* @__PURE__ */ jsx("div", {
2028
+ className: "flex flex-col items-start",
2029
+ children: /* @__PURE__ */ jsx(Typography, {
2030
+ variant: "subtitle2",
2031
+ children: title
2032
+ })
2033
+ })]
2034
+ })
2035
+ });
2036
+ }
2037
+ //#endregion
2038
+ //#region src/collection_editor/ui/collection_editor/import/CollectionEditorImportMapping.tsx
2039
+ function CollectionEditorImportMapping({ importConfig, propertyConfigs }) {
2040
+ const { setFieldValue, setFieldTouched, values } = useFormex();
2041
+ const [selectedProperty, setSelectedProperty] = useState(void 0);
2042
+ const currentPropertiesOrderRef = React.useRef(values.propertiesOrder ? { "": values.propertiesOrder } : {});
2043
+ const propertyKey = selectedProperty ? selectedProperty.id : void 0;
2044
+ const property = selectedProperty || void 0;
2045
+ const onPropertyChanged = ({ id, property, previousId, namespace }) => {
2046
+ const fullId = id ? getFullId(id, namespace) : void 0;
2047
+ const propertyPath = fullId ? idToPropertiesPath(fullId) : void 0;
2048
+ const getCurrentPropertiesOrder = (namespace) => {
2049
+ if (!namespace) return currentPropertiesOrderRef.current[""];
2050
+ return currentPropertiesOrderRef.current[namespace] ?? getIn(values, namespaceToPropertiesOrderPath(namespace));
2051
+ };
2052
+ const updatePropertiesOrder = (newPropertiesOrder, namespace) => {
2053
+ setFieldValue(namespaceToPropertiesOrderPath(namespace), newPropertiesOrder, false);
2054
+ currentPropertiesOrderRef.current[namespace ?? ""] = newPropertiesOrder;
2055
+ };
2056
+ if (previousId && previousId !== id) {
2057
+ const previousPropertyPath = idToPropertiesPath(getFullId(previousId, namespace));
2058
+ updatePropertiesOrder(getCurrentPropertiesOrder(namespace).map((p) => p === previousId ? id : p).filter((p) => p !== void 0), namespace);
2059
+ const newHeadersMapping = { ...importConfig.headersMapping };
2060
+ Object.keys(newHeadersMapping).forEach((key) => {
2061
+ if (newHeadersMapping[key] === previousId) newHeadersMapping[key] = id ?? "";
2062
+ });
2063
+ importConfig.setHeadersMapping(newHeadersMapping);
2064
+ setFieldValue(previousPropertyPath, void 0, false);
2065
+ setFieldTouched(previousPropertyPath, false, false);
2066
+ }
2067
+ if (propertyPath) {
2068
+ setFieldValue(propertyPath, property, false);
2069
+ setFieldTouched(propertyPath, true, false);
2070
+ }
2071
+ };
2072
+ const onPropertyTypeChanged = async ({ id, importKey, property, namespace }) => {
2073
+ const fullId = id ? getFullId(id, namespace) : void 0;
2074
+ const propertyPath = fullId ? idToPropertiesPath(fullId) : void 0;
2075
+ const inferredNewProperty = { ...buildPropertyFromData(importConfig.importData.map((d) => getIn(d, importKey)), property, getInferenceType) };
2076
+ if (propertyPath) {
2077
+ if (inferredNewProperty) setFieldValue(propertyPath, inferredNewProperty, false);
2078
+ else setFieldValue(propertyPath, property, false);
2079
+ setFieldTouched(propertyPath, true, false);
2080
+ }
2081
+ };
2082
+ return /* @__PURE__ */ jsxs("div", {
2083
+ className: "overflow-auto my-auto",
2084
+ children: [
2085
+ /* @__PURE__ */ jsxs(Container, {
2086
+ maxWidth: "6xl",
2087
+ className: "flex flex-col gap-4 p-8 m-auto",
2088
+ children: [/* @__PURE__ */ jsx(Typography, {
2089
+ variant: "h6",
2090
+ className: "my-4 ml-3.5",
2091
+ children: "Data property mapping"
2092
+ }), /* @__PURE__ */ jsx(DataNewPropertiesMapping, {
2093
+ importConfig,
2094
+ destinationProperties: values.properties,
2095
+ buildPropertyView: ({ property, propertyKey, importKey, isIdColumn }) => {
2096
+ if (isIdColumn) return /* @__PURE__ */ jsx(Typography, { children: " This column will be used as ID" });
2097
+ return /* @__PURE__ */ jsx(ImportNewPropertyFieldPreview, {
2098
+ property,
2099
+ propertyKey,
2100
+ onPropertyNameChanged: (propertyKey, value) => setFieldValue(`properties.${propertyKey}.name`, value, false),
2101
+ onEditClick: () => {
2102
+ if (!propertyKey || !property) return;
2103
+ setSelectedProperty({
2104
+ ...property,
2105
+ id: propertyKey
2106
+ });
2107
+ },
2108
+ propertyTypeView: /* @__PURE__ */ jsx(PropertySelect, {
2109
+ property,
2110
+ disabled: false,
2111
+ onPropertyChanged: (props) => onPropertyTypeChanged({
2112
+ ...props,
2113
+ importKey
2114
+ }),
2115
+ propertyKey,
2116
+ propertyConfigs
2117
+ })
2118
+ });
2119
+ }
2120
+ })]
2121
+ }),
2122
+ /* @__PURE__ */ jsx(PropertyFormDialog, {
2123
+ open: selectedProperty !== void 0,
2124
+ propertyKey,
2125
+ property,
2126
+ inArray: false,
2127
+ autoUpdateId: false,
2128
+ onPropertyChanged,
2129
+ allowDataInference: false,
2130
+ onOkClicked: () => {
2131
+ setSelectedProperty(void 0);
2132
+ },
2133
+ onCancel: () => {
2134
+ setSelectedProperty(void 0);
2135
+ },
2136
+ autoOpenTypeSelect: false,
2137
+ existingProperty: false,
2138
+ propertyConfigs
2139
+ }),
2140
+ /* @__PURE__ */ jsx("div", { style: { height: "52px" } })
2141
+ ]
2142
+ });
2143
+ }
2144
+ function PropertySelect({ property, onPropertyChanged, propertyKey, propertyConfigs, disabled }) {
2145
+ const fieldId = property ? getFieldId(property) : null;
2146
+ const widget = property ? getFieldConfig(property, propertyConfigs) : null;
2147
+ const [selectOpen, setSelectOpen] = useState(false);
2148
+ return /* @__PURE__ */ jsx(Tooltip, {
2149
+ title: property && widget ? `${widget?.name} - ${property.type}` : void 0,
2150
+ open: selectOpen ? false : void 0,
2151
+ children: /* @__PURE__ */ jsx(Select, {
2152
+ open: selectOpen,
2153
+ onOpenChange: setSelectOpen,
2154
+ invisible: true,
2155
+ className: "w-full",
2156
+ disabled,
2157
+ error: !widget,
2158
+ fullWidth: true,
2159
+ value: fieldId ?? "",
2160
+ placeholder: "Select a property widget",
2161
+ position: "item-aligned",
2162
+ renderValue: (value) => {
2163
+ if (!widget) return null;
2164
+ return /* @__PURE__ */ jsx(PropertyConfigBadge, { propertyConfig: widget });
2165
+ },
2166
+ onValueChange: (newSelectedWidgetId) => {
2167
+ const newProperty = updatePropertyFromWidget(property, newSelectedWidgetId, propertyConfigs);
2168
+ if (!propertyKey) return;
2169
+ onPropertyChanged({
2170
+ id: propertyKey,
2171
+ property: newProperty,
2172
+ previousId: propertyKey,
2173
+ namespace: void 0
2174
+ });
2175
+ },
2176
+ children: Object.entries(supportedFields).map(([key, widget]) => {
2177
+ return /* @__PURE__ */ jsx(ImportPropertySelectItem, {
2178
+ value: key,
2179
+ optionDisabled: false,
2180
+ propertyConfig: widget,
2181
+ existing: false
2182
+ }, key);
2183
+ })
2184
+ })
2185
+ });
2186
+ }
2187
+ function ImportPropertySelectItem({ value, optionDisabled, propertyConfig, existing }) {
2188
+ return /* @__PURE__ */ jsx(SelectItem, {
2189
+ value,
2190
+ disabled: optionDisabled,
2191
+ className: "flex flex-row items-center",
2192
+ children: /* @__PURE__ */ jsxs("div", {
2193
+ className: cls("flex flex-row items-center text-base min-h-[48px]", optionDisabled ? "w-full" : ""),
2194
+ children: [/* @__PURE__ */ jsx("div", {
2195
+ className: "mr-8",
2196
+ children: /* @__PURE__ */ jsx(PropertyConfigBadge, { propertyConfig })
2197
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", { children: propertyConfig.name }), /* @__PURE__ */ jsx(Typography, {
2198
+ variant: "caption",
2199
+ color: "disabled",
2200
+ className: "max-w-sm",
2201
+ children: existing && optionDisabled ? "You can only switch to widgets that use the same data type" : propertyConfig.description
2202
+ })] })]
2203
+ })
2204
+ });
2205
+ }
2206
+ //#endregion
2207
+ //#region src/collection_editor/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx
2208
+ function CollectionEditorImportDataPreview({ importConfig, properties, propertiesOrder }) {
2209
+ const authController = useAuthController();
2210
+ const registry = useCollectionRegistryController();
2211
+ const [loading, setLoading] = useState(false);
2212
+ async function loadEntities() {
2213
+ const mappedData = importConfig.importData.map((d) => convertDataToEntity(authController, registry, d, importConfig.idColumn, importConfig.headersMapping, properties, "TEMP_PATH", importConfig.defaultValues));
2214
+ importConfig.setEntities(mappedData);
2215
+ }
2216
+ useEffect(() => {
2217
+ loadEntities().finally(() => setLoading(false));
2218
+ }, []);
2219
+ const selectionController = useSelectionController();
2220
+ if (loading) return /* @__PURE__ */ jsx(CircularProgressCenter, {});
2221
+ return /* @__PURE__ */ jsx(EntityCollectionTable, {
2222
+ title: /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography, {
2223
+ variant: "subtitle2",
2224
+ children: "Imported data preview"
2225
+ }), /* @__PURE__ */ jsx(Typography, {
2226
+ variant: "caption",
2227
+ children: "Entities with the same id will be overwritten"
2228
+ })] }),
2229
+ tableController: {
2230
+ data: importConfig.entities,
2231
+ dataLoading: false,
2232
+ noMoreToLoad: false
2233
+ },
2234
+ endAdornment: /* @__PURE__ */ jsx("div", { className: "h-12" }),
2235
+ filterable: false,
2236
+ sortable: false,
2237
+ selectionController,
2238
+ displayedColumnIds: propertiesOrder.map((p) => ({
2239
+ key: p,
2240
+ disabled: false
2241
+ })),
2242
+ openEntityMode: "side_panel",
2243
+ properties,
2244
+ enablePopupIcon: false
2245
+ });
2246
+ }
2247
+ //#endregion
2248
+ //#region src/collection_editor/ui/collection_editor/import/clean_import_data.ts
2249
+ function cleanPropertiesFromImport(properties, parentSlug = "") {
2250
+ const result = Object.keys(properties).reduce((acc, key) => {
2251
+ const property = properties[key];
2252
+ const slug = slugify(key);
2253
+ const fullSlug = parentSlug ? `${parentSlug}.${slug}` : slug;
2254
+ if (property.type === "map" && property.properties) {
2255
+ const slugifiedResult = cleanPropertiesFromImport(property.properties, fullSlug);
2256
+ return {
2257
+ headersMapping: {
2258
+ ...acc.headersMapping,
2259
+ [key]: fullSlug
2260
+ },
2261
+ properties: {
2262
+ ...acc.properties,
2263
+ [slug]: {
2264
+ ...property,
2265
+ properties: slugifiedResult.properties,
2266
+ propertiesOrder: Object.keys(slugifiedResult.properties)
2267
+ }
2268
+ }
2269
+ };
2270
+ }
2271
+ const updatedProperties = {
2272
+ ...acc.properties,
2273
+ [slug]: property
2274
+ };
2275
+ return {
2276
+ headersMapping: {
2277
+ ...acc.headersMapping,
2278
+ [key]: fullSlug
2279
+ },
2280
+ properties: updatedProperties
2281
+ };
2282
+ }, {
2283
+ headersMapping: {},
2284
+ properties: {}
2285
+ });
2286
+ const firstKey = Object.keys(result.headersMapping)?.[0];
2287
+ let idColumn;
2288
+ if (firstKey?.includes("id") || firstKey?.includes("key")) idColumn = firstKey;
2289
+ return {
2290
+ ...result,
2291
+ idColumn
2292
+ };
2293
+ }
2294
+ //#endregion
2295
+ //#region src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx
2296
+ /**
2297
+ * Validates and double-quotes a SQL identifier to prevent injection.
2298
+ * Only allows safe Postgres identifiers (letters, digits, underscores).
2299
+ * Throws if the identifier contains unsafe characters.
2300
+ */
2301
+ function sanitizeSqlIdentifier(name) {
2302
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) throw new Error(`Invalid SQL identifier: "${name}". Only letters, digits, and underscores are allowed.`);
2303
+ return `"${name}"`;
2304
+ }
2305
+ function CollectionRLSTab() {
2306
+ const { values, setFieldValue } = useFormex();
2307
+ const [editingPolicy, setEditingPolicy] = useState(null);
2308
+ const rules = values.securityRules || [];
2309
+ const { databaseAdmin } = useRebaseContext();
2310
+ const [dbPolicies, setDbPolicies] = useState([]);
2311
+ const [isLoadingDb, setIsLoadingDb] = useState(false);
2312
+ useEffect(() => {
2313
+ const fetchLivePolicies = async () => {
2314
+ const tableName = values.id || values.table || values.alias;
2315
+ if (!tableName || !databaseAdmin?.executeSql) return;
2316
+ setIsLoadingDb(true);
2317
+ try {
2318
+ const sql = `
2319
+ SELECT policyname, permissive, roles, cmd, qual, with_check
2320
+ FROM pg_policies
2321
+ WHERE tablename = '${sanitizeSqlIdentifier(tableName).slice(1, -1)}' AND schemaname NOT IN ('information_schema', 'pg_catalog');
2322
+ `;
2323
+ const result = await databaseAdmin.executeSql(sql);
2324
+ const extractRows = (res) => {
2325
+ if (res && typeof res === "object" && "rows" in res && Array.isArray(res.rows)) return res.rows;
2326
+ if (Array.isArray(res)) return res;
2327
+ return [];
2328
+ };
2329
+ setDbPolicies(extractRows(result).map((p) => {
2330
+ let parsedRoles = [];
2331
+ const r = p.roles || p.ROLES;
2332
+ if (Array.isArray(r)) parsedRoles = r;
2333
+ else if (typeof r === "string") parsedRoles = r.replace(/^{|}$/g, "").split(",").map((s) => s.trim());
2334
+ return {
2335
+ policyname: p.policyname || p.POLICYNAME || "",
2336
+ tablename: tableName,
2337
+ permissive: p.permissive || p.PERMISSIVE || "PERMISSIVE",
2338
+ roles: parsedRoles,
2339
+ cmd: p.cmd || p.CMD || "ALL",
2340
+ qual: p.qual || p.QUAL || null,
2341
+ with_check: p.with_check || p.WITH_CHECK || null
2342
+ };
2343
+ }));
2344
+ } catch (e) {
2345
+ console.error("Failed to fetch DB policies", e);
2346
+ } finally {
2347
+ setIsLoadingDb(false);
2348
+ }
2349
+ };
2350
+ fetchLivePolicies();
2351
+ }, [
2352
+ databaseAdmin,
2353
+ values.id,
2354
+ values.table,
2355
+ values.alias
2356
+ ]);
2357
+ const unmappedPolicies = dbPolicies.filter((dp) => !rules.some((r) => r.name === dp.policyname));
2358
+ const handleSave = async (newPolicy) => {
2359
+ const rule = {
2360
+ name: newPolicy.policyname ?? "",
2361
+ operation: newPolicy.cmd?.toLowerCase(),
2362
+ mode: newPolicy.permissive?.toLowerCase(),
2363
+ using: newPolicy.qual || void 0,
2364
+ withCheck: newPolicy.with_check || void 0,
2365
+ roles: newPolicy.roles
2366
+ };
2367
+ let newRules;
2368
+ if (editingPolicy === "new") newRules = [...rules, rule];
2369
+ else newRules = rules.map((r) => r.name === editingPolicy.policyname ? rule : r);
2370
+ setFieldValue("securityRules", newRules);
2371
+ setEditingPolicy(null);
2372
+ };
2373
+ return /* @__PURE__ */ jsx("div", {
2374
+ className: "overflow-auto my-auto",
2375
+ children: /* @__PURE__ */ jsxs(Container, {
2376
+ maxWidth: "4xl",
2377
+ className: "flex flex-col gap-4 p-8 m-auto",
2378
+ children: [/* @__PURE__ */ jsxs("div", {
2379
+ className: "w-full flex flex-col",
2380
+ children: [
2381
+ /* @__PURE__ */ jsxs("div", {
2382
+ className: "flex items-center justify-between mb-8",
2383
+ children: [/* @__PURE__ */ jsx(Typography, {
2384
+ variant: "h5",
2385
+ children: "Row Level Security"
2386
+ }), /* @__PURE__ */ jsx(Button, {
2387
+ variant: "filled",
2388
+ color: "neutral",
2389
+ onClick: () => setEditingPolicy("new"),
2390
+ children: "CREATE POLICY"
2391
+ })]
2392
+ }),
2393
+ rules.length === 0 ? /* @__PURE__ */ jsx("div", {
2394
+ className: "flex-grow flex items-center justify-center text-text-disabled py-12",
2395
+ children: /* @__PURE__ */ jsx(Typography, {
2396
+ variant: "body2",
2397
+ children: "No RLS policies defined for this collection."
2398
+ })
2399
+ }) : /* @__PURE__ */ jsx("div", {
2400
+ className: "flex flex-col gap-3",
2401
+ children: rules.map((rule) => /* @__PURE__ */ jsxs(Paper, {
2402
+ className: "p-4 border border-transparent hover:border-surface-200 dark:hover:border-surface-700 rounded-lg flex flex-col sm:flex-row sm:items-center justify-between gap-4 transition-colors bg-white dark:bg-surface-800 shadow-sm",
2403
+ children: [/* @__PURE__ */ jsxs("div", {
2404
+ className: "flex flex-col gap-1.5 min-w-0",
2405
+ children: [/* @__PURE__ */ jsxs("div", {
2406
+ className: "flex items-center gap-2",
2407
+ children: [/* @__PURE__ */ jsx(KeyIcon, {
2408
+ size: iconSize.smallest,
2409
+ className: "text-text-disabled dark:text-text-disabled-dark shrink-0"
2410
+ }), /* @__PURE__ */ jsx(Typography, {
2411
+ variant: "subtitle2",
2412
+ className: "truncate",
2413
+ children: rule.name
2414
+ })]
2415
+ }), /* @__PURE__ */ jsxs("div", {
2416
+ className: "flex gap-2 text-xs pl-6 overflow-x-auto hide-scrollbar",
2417
+ children: [/* @__PURE__ */ jsxs(Chip, {
2418
+ size: "small",
2419
+ className: "bg-surface-100 dark:bg-surface-900 text-text-secondary border-none",
2420
+ children: ["Action: ", rule.operation || "ALL"]
2421
+ }), /* @__PURE__ */ jsxs(Chip, {
2422
+ size: "small",
2423
+ className: "bg-surface-100 dark:bg-surface-900 text-text-secondary border-none",
2424
+ children: ["Roles: ", Array.isArray(rule.roles) ? rule.roles.join(", ") : rule.roles]
2425
+ })]
2426
+ })]
2427
+ }), /* @__PURE__ */ jsxs("div", {
2428
+ className: "flex items-center gap-1 sm:gap-2 shrink-0",
2429
+ children: [/* @__PURE__ */ jsx(Button, {
2430
+ size: "small",
2431
+ variant: "text",
2432
+ onClick: () => setEditingPolicy({
2433
+ policyname: rule.name,
2434
+ tablename: values.id || values.table || values.alias || "your_table",
2435
+ permissive: (rule.mode || "permissive").toUpperCase(),
2436
+ cmd: (rule.operation || "ALL").toUpperCase(),
2437
+ roles: rule.roles || ["public"],
2438
+ qual: rule.using || null,
2439
+ with_check: rule.withCheck || null
2440
+ }),
2441
+ children: "EDIT"
2442
+ }), /* @__PURE__ */ jsx(IconButton, {
2443
+ size: "small",
2444
+ onClick: () => {
2445
+ setFieldValue("securityRules", rules.filter((r) => r.name !== rule.name));
2446
+ },
2447
+ children: /* @__PURE__ */ jsx(Trash2Icon, {
2448
+ size: iconSize.smallest,
2449
+ className: "text-text-secondary dark:text-text-secondary-dark hover:text-red-500 dark:hover:text-red-500 transition-colors"
2450
+ })
2451
+ })]
2452
+ })]
2453
+ }, rule.name))
2454
+ }),
2455
+ isLoadingDb && unmappedPolicies.length === 0 && /* @__PURE__ */ jsx("div", {
2456
+ className: "flex justify-center mt-8",
2457
+ children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" })
2458
+ }),
2459
+ !isLoadingDb && unmappedPolicies.length > 0 && /* @__PURE__ */ jsxs("div", {
2460
+ className: "mt-12 flex flex-col gap-4",
2461
+ children: [
2462
+ /* @__PURE__ */ jsx(Typography, {
2463
+ variant: "h6",
2464
+ className: "text-text-secondary",
2465
+ children: "Unmapped Database Policies"
2466
+ }),
2467
+ /* @__PURE__ */ jsx(Typography, {
2468
+ variant: "body2",
2469
+ className: "text-text-secondary opacity-80 -mt-2",
2470
+ children: "These policies exist in your Postgres database but are not mapped to this collection's codebase configuration."
2471
+ }),
2472
+ /* @__PURE__ */ jsx("div", {
2473
+ className: "flex flex-col gap-3",
2474
+ children: unmappedPolicies.map((dp) => /* @__PURE__ */ jsxs(Paper, {
2475
+ className: "p-4 border border-orange-200 dark:border-orange-900/50 bg-orange-50/50 dark:bg-orange-900/10 rounded-lg flex flex-col sm:flex-row sm:items-center justify-between gap-4 transition-colors",
2476
+ children: [/* @__PURE__ */ jsxs("div", {
2477
+ className: "flex flex-col gap-1.5 min-w-0",
2478
+ children: [/* @__PURE__ */ jsxs("div", {
2479
+ className: "flex items-center gap-2",
2480
+ children: [
2481
+ /* @__PURE__ */ jsx(KeyIcon, {
2482
+ size: iconSize.smallest,
2483
+ className: "text-orange-500 shrink-0"
2484
+ }),
2485
+ /* @__PURE__ */ jsx(Typography, {
2486
+ variant: "subtitle2",
2487
+ className: "truncate",
2488
+ children: dp.policyname
2489
+ }),
2490
+ /* @__PURE__ */ jsx(Tooltip, {
2491
+ title: "This policy is live in the database but missing from your codebase schema.",
2492
+ children: /* @__PURE__ */ jsx("div", {
2493
+ className: "px-1.5 py-0.5 rounded text-[10px] uppercase bg-orange-500/10 text-orange-600 border border-orange-500/20 shrink-0",
2494
+ children: "DB Only"
2495
+ })
2496
+ })
2497
+ ]
2498
+ }), /* @__PURE__ */ jsxs("div", {
2499
+ className: "flex gap-2 text-xs pl-6 overflow-x-auto hide-scrollbar",
2500
+ children: [/* @__PURE__ */ jsxs(Chip, {
2501
+ size: "small",
2502
+ className: "bg-white dark:bg-surface-900 text-text-secondary border-none",
2503
+ children: ["Action: ", dp.cmd || "ALL"]
2504
+ }), /* @__PURE__ */ jsxs(Chip, {
2505
+ size: "small",
2506
+ className: "bg-white dark:bg-surface-900 text-text-secondary border-none",
2507
+ children: ["Roles: ", Array.isArray(dp.roles) ? dp.roles.join(", ") : dp.roles]
2508
+ })]
2509
+ })]
2510
+ }), /* @__PURE__ */ jsx("div", {
2511
+ className: "flex items-center gap-1 sm:gap-2 shrink-0",
2512
+ children: /* @__PURE__ */ jsx(Button, {
2513
+ size: "small",
2514
+ variant: "outlined",
2515
+ color: "primary",
2516
+ onClick: () => {
2517
+ const rule = {
2518
+ name: dp.policyname,
2519
+ operation: dp.cmd?.toLowerCase(),
2520
+ mode: dp.permissive?.toLowerCase(),
2521
+ using: dp.qual || void 0,
2522
+ withCheck: dp.with_check || void 0,
2523
+ roles: dp.roles
2524
+ };
2525
+ setFieldValue("securityRules", [...rules, rule]);
2526
+ },
2527
+ children: "Import to codebase"
2528
+ })
2529
+ })]
2530
+ }, dp.policyname))
2531
+ })
2532
+ ]
2533
+ })
2534
+ ]
2535
+ }), /* @__PURE__ */ jsx(Dialog, {
2536
+ open: !!editingPolicy,
2537
+ onOpenChange: (open) => !open && setEditingPolicy(null),
2538
+ maxWidth: "4xl",
2539
+ children: editingPolicy && /* @__PURE__ */ jsx(InlinePolicyEditor, {
2540
+ policy: editingPolicy === "new" ? void 0 : editingPolicy,
2541
+ table: values.id || values.table || values.alias || "your_table",
2542
+ onSave: handleSave,
2543
+ onCancel: () => setEditingPolicy(null)
2544
+ })
2545
+ })]
2546
+ })
2547
+ });
2548
+ }
2549
+ var COMMAND_OPTIONS = [
2550
+ "ALL",
2551
+ "SELECT",
2552
+ "INSERT",
2553
+ "UPDATE",
2554
+ "DELETE"
2555
+ ];
2556
+ var ROLE_OPTIONS = [
2557
+ "public",
2558
+ "authenticated",
2559
+ "anon",
2560
+ "admin"
2561
+ ];
2562
+ function InlinePolicyEditor({ policy, table, onSave, onCancel }) {
2563
+ const [name, setName] = useState(policy?.policyname || "");
2564
+ const [behavior, setBehavior] = useState(policy?.permissive || "PERMISSIVE");
2565
+ const [command, setCommand] = useState(policy?.cmd || "ALL");
2566
+ const [roles, setRoles] = useState(policy?.roles ? Array.isArray(policy.roles) ? policy.roles : [policy.roles] : ["public"]);
2567
+ const [usingExpr, setUsingExpr] = useState(policy?.qual || "");
2568
+ const [checkExpr, setCheckExpr] = useState(policy?.with_check || "");
2569
+ const showCheck = command === "ALL" || command === "INSERT" || command === "UPDATE";
2570
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2571
+ /* @__PURE__ */ jsxs(DialogTitle, {
2572
+ variant: "h6",
2573
+ children: [policy ? "Edit Policy" : "Create Policy", /* @__PURE__ */ jsxs("div", {
2574
+ className: "text-sm font-normal text-text-secondary dark:text-text-secondary-dark tracking-wide mt-1",
2575
+ children: ["Define RLS rules for ", /* @__PURE__ */ jsxs("span", {
2576
+ className: "font-mono text-primary bg-primary-bg dark:bg-primary-bg-dark px-1 py-0.5 rounded",
2577
+ children: ["public.", table]
2578
+ })]
2579
+ })]
2580
+ }),
2581
+ /* @__PURE__ */ jsx(DialogContent, {
2582
+ className: "p-4 md:p-6 border-t dark:border-surface-700 bg-surface-50 dark:bg-surface-800",
2583
+ includeMargin: false,
2584
+ children: /* @__PURE__ */ jsxs(Paper, {
2585
+ className: cls("p-4 md:p-6 flex flex-col gap-6 bg-white dark:bg-surface-800 border-none sm:border-solid rounded-none sm:rounded-xl", defaultBorderMixin),
2586
+ children: [
2587
+ /* @__PURE__ */ jsxs("div", {
2588
+ className: "grid grid-cols-1 md:grid-cols-2 gap-4",
2589
+ children: [/* @__PURE__ */ jsxs("div", {
2590
+ className: "flex flex-col gap-1.5",
2591
+ children: [/* @__PURE__ */ jsx(Typography, {
2592
+ variant: "caption",
2593
+ className: "uppercase tracking-wider text-text-secondary",
2594
+ children: "Policy Name"
2595
+ }), /* @__PURE__ */ jsx(TextField, {
2596
+ value: name,
2597
+ onChange: (e) => setName(e.target.value),
2598
+ placeholder: "e.g. allow_read_all"
2599
+ })]
2600
+ }), /* @__PURE__ */ jsxs("div", {
2601
+ className: "flex flex-col gap-1.5",
2602
+ children: [/* @__PURE__ */ jsx(Typography, {
2603
+ variant: "caption",
2604
+ className: "uppercase tracking-wider text-text-secondary",
2605
+ children: "Behavior"
2606
+ }), /* @__PURE__ */ jsxs(Select, {
2607
+ value: behavior,
2608
+ onValueChange: (val) => setBehavior(val),
2609
+ position: "item-aligned",
2610
+ children: [/* @__PURE__ */ jsx(SelectItem, {
2611
+ value: "PERMISSIVE",
2612
+ children: "Permissive"
2613
+ }), /* @__PURE__ */ jsx(SelectItem, {
2614
+ value: "RESTRICTIVE",
2615
+ children: "Restrictive"
2616
+ })]
2617
+ })]
2618
+ })]
2619
+ }),
2620
+ /* @__PURE__ */ jsxs("div", {
2621
+ className: "flex flex-col gap-1.5",
2622
+ children: [/* @__PURE__ */ jsx(Typography, {
2623
+ variant: "caption",
2624
+ className: "uppercase tracking-wider text-text-secondary",
2625
+ children: "Command"
2626
+ }), /* @__PURE__ */ jsx("div", {
2627
+ className: "flex flex-wrap gap-1.5",
2628
+ children: COMMAND_OPTIONS.map((cmd) => /* @__PURE__ */ jsx(Button, {
2629
+ size: "small",
2630
+ variant: command === cmd ? "filled" : "text",
2631
+ color: "neutral",
2632
+ onClick: () => setCommand(cmd),
2633
+ children: cmd
2634
+ }, cmd))
2635
+ })]
2636
+ }),
2637
+ /* @__PURE__ */ jsxs("div", {
2638
+ className: "flex flex-col gap-1.5",
2639
+ children: [/* @__PURE__ */ jsx(Typography, {
2640
+ variant: "caption",
2641
+ className: "uppercase tracking-wider text-text-secondary",
2642
+ children: "Target Roles"
2643
+ }), /* @__PURE__ */ jsx(MultiSelect, {
2644
+ size: "small",
2645
+ value: roles,
2646
+ onValueChange: setRoles,
2647
+ placeholder: "Select roles",
2648
+ children: ROLE_OPTIONS.map((r) => /* @__PURE__ */ jsx(MultiSelectItem, {
2649
+ value: r,
2650
+ children: r
2651
+ }, r))
2652
+ })]
2653
+ }),
2654
+ command !== "INSERT" && /* @__PURE__ */ jsxs("div", {
2655
+ className: "flex flex-col gap-1.5",
2656
+ children: [/* @__PURE__ */ jsx(Typography, {
2657
+ variant: "caption",
2658
+ className: "uppercase tracking-wider text-text-secondary",
2659
+ children: "USING expression"
2660
+ }), /* @__PURE__ */ jsx(TextField, {
2661
+ value: usingExpr,
2662
+ onChange: (e) => setUsingExpr(e.target.value),
2663
+ placeholder: "e.g. auth.uid() = user_id"
2664
+ })]
2665
+ }),
2666
+ showCheck && /* @__PURE__ */ jsxs("div", {
2667
+ className: "flex flex-col gap-1.5",
2668
+ children: [/* @__PURE__ */ jsx(Typography, {
2669
+ variant: "caption",
2670
+ className: "uppercase tracking-wider text-text-secondary",
2671
+ children: "WITH CHECK expression"
2672
+ }), /* @__PURE__ */ jsx(TextField, {
2673
+ value: checkExpr,
2674
+ onChange: (e) => setCheckExpr(e.target.value),
2675
+ placeholder: "e.g. auth.uid() = user_id"
2676
+ })]
2677
+ })
2678
+ ]
2679
+ })
2680
+ }),
2681
+ /* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
2682
+ size: "small",
2683
+ variant: "text",
2684
+ color: "neutral",
2685
+ onClick: onCancel,
2686
+ children: "Cancel"
2687
+ }), /* @__PURE__ */ jsx(Button, {
2688
+ size: "small",
2689
+ variant: "filled",
2690
+ color: "primary",
2691
+ disabled: !name,
2692
+ onClick: () => onSave({
2693
+ policyname: name,
2694
+ permissive: behavior,
2695
+ cmd: command,
2696
+ roles,
2697
+ qual: usingExpr,
2698
+ with_check: showCheck ? checkExpr : null
2699
+ }),
2700
+ children: "Save"
2701
+ })] })
2702
+ ] });
2703
+ }
2704
+ //#endregion
2705
+ //#region src/collection_editor/pgColumnToProperty.ts
2706
+ /**
2707
+ * Maps a PostgreSQL column data type to a Rebase property type.
2708
+ */
2709
+ function pgTypeToRebaseProperty(column) {
2710
+ const { column_name, data_type, udt_name, is_nullable, column_default, enum_values } = column;
2711
+ const required = is_nullable === "NO";
2712
+ const prettifiedName = prettifyIdentifier(column_name);
2713
+ const isAutoId = column_default != null && (column_default.includes("nextval") || column_default.includes("gen_random_uuid") || column_default.includes("uuid_generate") || column_default.includes("identity"));
2714
+ if (data_type === "USER-DEFINED" && enum_values && enum_values.length > 0) return {
2715
+ type: "string",
2716
+ name: prettifiedName,
2717
+ enum: enum_values.map((v) => ({
2718
+ id: v,
2719
+ label: prettifyIdentifier(v)
2720
+ })),
2721
+ validation: required ? { required: true } : void 0
2722
+ };
2723
+ const dt = data_type.toLowerCase();
2724
+ switch (dt) {
2725
+ case "character varying":
2726
+ case "varchar":
2727
+ case "text":
2728
+ case "char":
2729
+ case "character":
2730
+ case "citext": {
2731
+ let colType = "varchar";
2732
+ if (dt === "text" || dt === "citext") colType = "text";
2733
+ if (dt === "char" || dt === "character") colType = "char";
2734
+ const prop = {
2735
+ type: "string",
2736
+ name: prettifiedName,
2737
+ columnType: colType,
2738
+ validation: required ? { required: true } : void 0
2739
+ };
2740
+ if (isAutoId) prop.isId = "manual";
2741
+ return prop;
2742
+ }
2743
+ case "uuid": {
2744
+ const prop = {
2745
+ type: "string",
2746
+ name: prettifiedName,
2747
+ validation: required ? { required: true } : void 0
2748
+ };
2749
+ if (isAutoId) prop.isId = "uuid";
2750
+ return prop;
2751
+ }
2752
+ case "integer":
2753
+ case "bigint":
2754
+ case "smallint": {
2755
+ const prop = {
2756
+ type: "number",
2757
+ name: prettifiedName,
2758
+ columnType: dt === "bigint" ? "bigint" : "integer",
2759
+ validation: {
2760
+ ...required ? { required: true } : {},
2761
+ integer: true
2762
+ }
2763
+ };
2764
+ if (isAutoId) prop.isId = "increment";
2765
+ return prop;
2766
+ }
2767
+ case "serial":
2768
+ case "bigserial":
2769
+ case "smallserial": return {
2770
+ type: "number",
2771
+ name: prettifiedName,
2772
+ columnType: dt === "bigserial" ? "bigserial" : "serial",
2773
+ isId: "increment",
2774
+ validation: {
2775
+ ...required ? { required: true } : {},
2776
+ integer: true
2777
+ }
2778
+ };
2779
+ case "numeric":
2780
+ case "decimal":
2781
+ case "real":
2782
+ case "double precision": {
2783
+ let colType = "numeric";
2784
+ if (dt === "real") colType = "real";
2785
+ if (dt === "double precision") colType = "double precision";
2786
+ return {
2787
+ type: "number",
2788
+ name: prettifiedName,
2789
+ columnType: colType,
2790
+ validation: required ? { required: true } : void 0
2791
+ };
2792
+ }
2793
+ case "boolean": return {
2794
+ type: "boolean",
2795
+ name: prettifiedName,
2796
+ validation: required ? { required: true } : void 0
2797
+ };
2798
+ case "timestamp with time zone":
2799
+ case "timestamp without time zone":
2800
+ case "timestamp":
2801
+ case "timestamptz":
2802
+ case "date":
2803
+ case "time with time zone":
2804
+ case "time without time zone":
2805
+ case "time": {
2806
+ let colType = "timestamp";
2807
+ if (dt.startsWith("date")) colType = "date";
2808
+ if (dt.startsWith("time ") || dt === "time") colType = "time";
2809
+ return {
2810
+ type: "date",
2811
+ name: prettifiedName,
2812
+ columnType: colType,
2813
+ validation: required ? { required: true } : void 0
2814
+ };
2815
+ }
2816
+ case "jsonb":
2817
+ case "json": return {
2818
+ type: "map",
2819
+ name: prettifiedName,
2820
+ columnType: dt === "jsonb" ? "jsonb" : "json",
2821
+ keyValue: true,
2822
+ properties: {}
2823
+ };
2824
+ case "array":
2825
+ case "ARRAY": {
2826
+ let innerType = "string";
2827
+ let colType = void 0;
2828
+ if (udt_name === "_text" || udt_name === "_varchar") {
2829
+ innerType = "string";
2830
+ colType = "text[]";
2831
+ } else if (udt_name === "_int4" || udt_name === "_int2" || udt_name === "_int8") {
2832
+ innerType = "number";
2833
+ colType = "integer[]";
2834
+ } else if (udt_name === "_bool") {
2835
+ innerType = "boolean";
2836
+ colType = "boolean[]";
2837
+ } else if (udt_name === "_numeric") {
2838
+ innerType = "number";
2839
+ colType = "numeric[]";
2840
+ }
2841
+ return {
2842
+ type: "array",
2843
+ name: prettifiedName,
2844
+ columnType: colType,
2845
+ of: { type: innerType }
2846
+ };
2847
+ }
2848
+ default: return {
2849
+ type: "string",
2850
+ name: prettifiedName,
2851
+ validation: required ? { required: true } : void 0
2852
+ };
2853
+ }
2854
+ }
2855
+ /**
2856
+ * Builds a partial EntityCollection from PostgreSQL table metadata.
2857
+ * This is used when creating a new collection from an existing database table.
2858
+ */
2859
+ function buildCollectionFromTableMetadata(tableName, metadata) {
2860
+ const properties = {};
2861
+ const propertiesOrder = [];
2862
+ const relations = [];
2863
+ const securityRules = [];
2864
+ for (const column of metadata.columns) {
2865
+ const property = pgTypeToRebaseProperty(column);
2866
+ if (property) {
2867
+ const propRecord = property;
2868
+ Object.keys(propRecord).forEach((key) => propRecord[key] === void 0 && delete propRecord[key]);
2869
+ properties[column.column_name] = property;
2870
+ propertiesOrder.push(column.column_name);
2871
+ }
2872
+ }
2873
+ if (metadata.foreignKeys) for (const fk of metadata.foreignKeys) {
2874
+ const relName = fk.column_name.endsWith("_id") ? fk.column_name.substring(0, fk.column_name.length - 3) : fk.column_name;
2875
+ relations.push({
2876
+ id: fk.column_name,
2877
+ relationName: relName,
2878
+ target: fk.foreign_table_name,
2879
+ cardinality: "one",
2880
+ direction: "owning",
2881
+ localKey: fk.column_name
2882
+ });
2883
+ }
2884
+ if (metadata.junctions) for (const junction of metadata.junctions) {
2885
+ const relName = junction.target_table_name;
2886
+ relations.push({
2887
+ id: junction.target_table_name + "_relation",
2888
+ relationName: relName,
2889
+ target: junction.target_table_name,
2890
+ cardinality: "many",
2891
+ direction: "owning",
2892
+ through: {
2893
+ table: junction.junction_table_name,
2894
+ sourceColumn: junction.source_column_name,
2895
+ targetColumn: junction.target_column_name
2896
+ }
2897
+ });
2898
+ }
2899
+ if (metadata.policies) for (const policy of metadata.policies) {
2900
+ let operations = [];
2901
+ switch (policy.cmd) {
2902
+ case "ALL":
2903
+ operations = [
2904
+ "read",
2905
+ "create",
2906
+ "update",
2907
+ "delete"
2908
+ ];
2909
+ break;
2910
+ case "SELECT":
2911
+ operations = ["read"];
2912
+ break;
2913
+ case "INSERT":
2914
+ operations = ["create"];
2915
+ break;
2916
+ case "UPDATE":
2917
+ operations = ["update"];
2918
+ break;
2919
+ case "DELETE":
2920
+ operations = ["delete"];
2921
+ break;
2922
+ }
2923
+ securityRules.push({
2924
+ name: policy.policy_name,
2925
+ operations,
2926
+ roles: policy.roles ?? [],
2927
+ qual: policy.qual,
2928
+ with_check: policy.with_check
2929
+ });
2930
+ }
2931
+ return {
2932
+ name: prettifyIdentifier(tableName),
2933
+ slug: tableName,
2934
+ table: tableName,
2935
+ properties,
2936
+ propertiesOrder,
2937
+ ...relations.length > 0 ? { relations } : {},
2938
+ ...securityRules.length > 0 ? { securityRules } : {}
2939
+ };
2940
+ }
2941
+ //#endregion
2942
+ //#region src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx
2943
+ var CollectionEditorDialog_exports = /* @__PURE__ */ __exportAll({
2944
+ CollectionEditor: () => CollectionEditor,
2945
+ CollectionEditorDialog: () => CollectionEditorDialog
2946
+ });
2947
+ function CollectionEditorDialog(props) {
2948
+ const open = props.open;
2949
+ const [formDirty, setFormDirty] = React$1.useState(false);
2950
+ const { dialogProps, triggerDialog } = useUnsavedChangesDialog(open && formDirty, () => props.handleClose(void 0));
2951
+ const handleCancel = () => {
2952
+ if (!formDirty) props.handleClose(void 0);
2953
+ else triggerDialog();
2954
+ };
2955
+ useEffect(() => {
2956
+ if (!open) setFormDirty(false);
2957
+ }, [open]);
2958
+ return /* @__PURE__ */ jsxs(Dialog, {
2959
+ open,
2960
+ fullWidth: true,
2961
+ fullHeight: true,
2962
+ scrollable: false,
2963
+ maxWidth: "7xl",
2964
+ onOpenChange: (open) => !open ? handleCancel() : void 0,
2965
+ children: [/* @__PURE__ */ jsx(DialogTitle, {
2966
+ hidden: true,
2967
+ children: "Collection editor"
2968
+ }), /* @__PURE__ */ jsxs(AIModifiedPathsProvider, { children: [open && /* @__PURE__ */ jsx(CollectionEditor, {
2969
+ ...props,
2970
+ handleCancel,
2971
+ setFormDirty
2972
+ }), /* @__PURE__ */ jsx(UnsavedChangesDialog, { ...dialogProps })] })]
2973
+ });
2974
+ }
2975
+ function CollectionEditor(props) {
2976
+ const { propertyConfigs } = useCustomizationController();
2977
+ const navigationState = useNavigationStateController();
2978
+ const collectionRegistry = useCollectionRegistryController();
2979
+ const authController = useAuthController();
2980
+ const { topLevelNavigation } = navigationState;
2981
+ const { collections } = collectionRegistry;
2982
+ const initialValuesProp = props.initialValues;
2983
+ const copyFromProp = props.copyFrom;
2984
+ const includeTemplates = !copyFromProp && !initialValuesProp?.slug && (props.parentCollectionSlugs ?? []).length === 0;
2985
+ const collectionsInThisLevel = (props.parentCollection ? getSubcollections(props.parentCollection) : collections) ?? [];
2986
+ const existingPaths = collectionsInThisLevel.map((col) => getTableName(col)?.trim().toLowerCase()).filter(Boolean);
2987
+ const existingIds = collectionsInThisLevel.map((col) => col.slug?.trim().toLowerCase()).filter(Boolean);
2988
+ const [collection, setCollection] = React$1.useState();
2989
+ const [initialLoadingCompleted, setInitialLoadingCompleted] = React$1.useState(false);
2990
+ useEffect(() => {
2991
+ try {
2992
+ if (collectionRegistry.initialised) {
2993
+ if (props.editedCollectionId) {
2994
+ const collectionPath = [...props.parentCollectionSlugs ?? [], props.editedCollectionId].reduce((acc, segment, i) => i === 0 ? segment : `${acc}/fake_id/${segment}`, "");
2995
+ setCollection(collectionRegistry.getRawCollection(collectionPath));
2996
+ } else setCollection(void 0);
2997
+ setInitialLoadingCompleted(true);
2998
+ }
2999
+ } catch (e) {
3000
+ console.error(e);
3001
+ }
3002
+ }, [
3003
+ props.editedCollectionId,
3004
+ props.parentCollectionSlugs,
3005
+ props.parentEntityIds,
3006
+ collectionRegistry.initialised,
3007
+ collectionRegistry.getRawCollection
3008
+ ]);
3009
+ const initialCollection = collection ? {
3010
+ ...collection,
3011
+ slug: collection.slug ?? randomString(16)
3012
+ } : void 0;
3013
+ const initialValues = initialCollection ? applyPropertyConfigs(initialCollection, propertyConfigs) : copyFromProp ? (() => {
3014
+ const { subcollections: _sub, ...rest } = copyFromProp;
3015
+ return {
3016
+ ...rest,
3017
+ name: "",
3018
+ ownerId: authController.user?.uid ?? ""
3019
+ };
3020
+ })() : {
3021
+ slug: initialValuesProp?.slug ?? randomString(16),
3022
+ table: initialValuesProp?.slug ?? "",
3023
+ name: initialValuesProp?.name ?? "",
3024
+ properties: {},
3025
+ propertiesOrder: [],
3026
+ icon: coolIconKeys[Math.floor(Math.random() * coolIconKeys.length)],
3027
+ ownerId: authController.user?.uid ?? ""
3028
+ };
3029
+ if (!initialLoadingCompleted) return /* @__PURE__ */ jsx(CircularProgressCenter, {});
3030
+ if (!props.isNewCollection && (!collectionRegistry.initialised || !initialLoadingCompleted)) return /* @__PURE__ */ jsx(CircularProgressCenter, {});
3031
+ return /* @__PURE__ */ jsx(CollectionEditorInternal, {
3032
+ ...props,
3033
+ initialValues,
3034
+ existingPaths,
3035
+ existingIds,
3036
+ includeTemplates,
3037
+ collection,
3038
+ setCollection,
3039
+ propertyConfigs
3040
+ });
3041
+ }
3042
+ function CollectionEditorInternal({ isNewCollection, configController, editedCollectionId, parentCollectionSlugs, parentEntityIds, path, collectionInference, handleClose, extraView, handleCancel, setFormDirty, getUser, parentCollection, getData, existingPaths, existingIds, includeTemplates, collection, setCollection, initialValues, propertyConfigs, existingEntities, initialView: initialViewProp, expandKanban, generateCollection, onAnalyticsEvent, fullScreen, unmappedTables, onFetchTableMetadata }) {
3043
+ const importConfig = useImportConfig();
3044
+ const urlController = useUrlController();
3045
+ const collectionRegistry = useCollectionRegistryController();
3046
+ const snackbarController = useSnackbarController();
3047
+ const propertyErrorsRef = useRef({});
3048
+ const [currentView, setCurrentView] = useState(isNewCollection ? includeTemplates ? "welcome" : "general" : initialViewProp ?? "properties");
3049
+ const [error, setError] = React$1.useState();
3050
+ const saveCollection = (updatedCollection) => {
3051
+ const id = updatedCollection.slug;
3052
+ return configController.saveCollection({
3053
+ id,
3054
+ collectionData: updatedCollection,
3055
+ previousId: editedCollectionId,
3056
+ parentCollectionSlugs
3057
+ }).then(() => {
3058
+ setError(void 0);
3059
+ return true;
3060
+ }).catch((e) => {
3061
+ setError(e);
3062
+ console.error(e);
3063
+ snackbarController.open({
3064
+ type: "error",
3065
+ message: "Error persisting collection: " + (e.message ?? "Details in the console")
3066
+ });
3067
+ return false;
3068
+ });
3069
+ };
3070
+ const setNextMode = () => {
3071
+ if (currentView === "general") if (importConfig.inUse) setCurrentView("import_data_saving");
3072
+ else if (extraView) setCurrentView("extra_view");
3073
+ else setCurrentView("properties");
3074
+ else if (currentView === "welcome") setCurrentView("general");
3075
+ else if (currentView === "import_data_mapping") setCurrentView("import_data_preview");
3076
+ else if (currentView === "import_data_preview") setCurrentView("general");
3077
+ else if (currentView === "extra_view") setCurrentView("properties");
3078
+ else setCurrentView("general");
3079
+ };
3080
+ const doCollectionInference = collectionInference ? (collection) => {
3081
+ if (!collectionInference) return void 0;
3082
+ return collectionInference?.(collection.slug, parentPaths ?? [], collection.databaseId);
3083
+ } : void 0;
3084
+ const inferCollectionFromData = async (newCollection) => {
3085
+ try {
3086
+ if (!doCollectionInference) {
3087
+ setCollection(newCollection);
3088
+ return Promise.resolve(newCollection);
3089
+ }
3090
+ setCurrentView("loading");
3091
+ const inferredCollection = await doCollectionInference?.(newCollection);
3092
+ if (!inferredCollection) {
3093
+ setCollection(newCollection);
3094
+ return Promise.resolve(newCollection);
3095
+ }
3096
+ const values = { ...newCollection ?? {} };
3097
+ if (Object.keys(inferredCollection.properties ?? {}).length > 0) {
3098
+ values.properties = inferredCollection.properties;
3099
+ values.propertiesOrder = inferredCollection.propertiesOrder;
3100
+ }
3101
+ if (!values.propertiesOrder) {
3102
+ values.propertiesOrder = Object.keys(values.properties);
3103
+ return values;
3104
+ }
3105
+ setCollection(values);
3106
+ console.debug("Inferred collection", {
3107
+ newCollection: newCollection ?? {},
3108
+ values
3109
+ });
3110
+ return values;
3111
+ } catch (e) {
3112
+ console.error(e);
3113
+ snackbarController.open({
3114
+ type: "error",
3115
+ message: "Error inferring collection: " + (e instanceof Error ? e.message : "Details in the console")
3116
+ });
3117
+ return newCollection;
3118
+ }
3119
+ };
3120
+ const onSubmit = async (newCollectionState, formexController) => {
3121
+ console.debug("Submitting collection", newCollectionState);
3122
+ try {
3123
+ if (!isNewCollection) {
3124
+ if (await saveCollection(newCollectionState)) {
3125
+ aiModifiedPaths?.clearAllPaths();
3126
+ formexController.resetForm({ values: newCollectionState });
3127
+ handleClose(newCollectionState);
3128
+ }
3129
+ return;
3130
+ }
3131
+ if (currentView === "welcome") {
3132
+ setNextMode();
3133
+ formexController.resetForm({ values: newCollectionState });
3134
+ } else if (currentView === "general") if (extraView || importConfig.inUse) {
3135
+ formexController.resetForm({ values: newCollectionState });
3136
+ setNextMode();
3137
+ } else if (isNewCollection) {
3138
+ const values = await inferCollectionFromData(newCollectionState);
3139
+ formexController.resetForm({
3140
+ values: values ?? newCollectionState,
3141
+ touched: {
3142
+ path: true,
3143
+ name: true
3144
+ }
3145
+ });
3146
+ setNextMode();
3147
+ } else {
3148
+ formexController.resetForm({ values: newCollectionState });
3149
+ setNextMode();
3150
+ }
3151
+ else if (currentView === "extra_view") {
3152
+ setNextMode();
3153
+ formexController.resetForm({ values: newCollectionState });
3154
+ } else if (currentView === "import_data_mapping") setNextMode();
3155
+ else if (currentView === "import_data_preview") setNextMode();
3156
+ else if (currentView === "properties") {
3157
+ if (await saveCollection(newCollectionState)) {
3158
+ formexController.resetForm({ values: initialValues });
3159
+ setNextMode();
3160
+ handleClose(newCollectionState);
3161
+ }
3162
+ } else {
3163
+ setNextMode();
3164
+ formexController.resetForm({ values: newCollectionState });
3165
+ }
3166
+ } catch (e) {
3167
+ snackbarController.open({
3168
+ type: "error",
3169
+ message: "Error persisting collection: " + (e instanceof Error ? e.message : "Details in the console")
3170
+ });
3171
+ console.error(e);
3172
+ formexController.resetForm({ values: newCollectionState });
3173
+ }
3174
+ };
3175
+ const validation = (col) => {
3176
+ let errors = {};
3177
+ const schema = (currentView === "properties" || currentView === "general") && CollectionEditorSchema;
3178
+ if (schema) {
3179
+ const result = schema.safeParse(col);
3180
+ if (!result.success) result.error.issues.forEach((issue) => {
3181
+ const path = issue.path.join(".");
3182
+ if (path) errors[path] = issue.message;
3183
+ });
3184
+ }
3185
+ if (currentView === "properties") errors = {
3186
+ ...errors,
3187
+ ...propertyErrorsRef.current
3188
+ };
3189
+ if (currentView === "general") {
3190
+ const pathError = validatePath(getTableName(col), isNewCollection, existingPaths, col.slug);
3191
+ if (pathError) errors.slug = pathError;
3192
+ const idError = validateId(col.slug, isNewCollection, existingPaths, existingIds);
3193
+ if (idError) errors.id = idError;
3194
+ }
3195
+ if (Object.keys(errors).length > 0) console.error("Formex validation blocked save:", errors, "Current view:", currentView);
3196
+ return errors;
3197
+ };
3198
+ const formController = useCreateFormex({
3199
+ initialValues,
3200
+ onSubmit,
3201
+ validation,
3202
+ debugId: "COLLECTION_EDITOR"
3203
+ });
3204
+ const { values, setFieldValue, isSubmitting, dirty, submitCount } = formController;
3205
+ const usedPath = getTableName(values);
3206
+ const pathError = validatePath(usedPath, isNewCollection, existingPaths, values.slug);
3207
+ const parentPaths = !pathError && parentCollectionSlugs ? collectionRegistry.convertIdsToPaths(parentCollectionSlugs) : void 0;
3208
+ const updatedFullPath = parentPaths && parentPaths.length > 0 ? [...parentPaths, usedPath].join("/fake_id/") : path?.includes("/") ? path.split("/").slice(0, -1).join("/") + "/" + usedPath : usedPath;
3209
+ const resolvedPath = !pathError ? urlController.resolveDatabasePathsFrom(updatedFullPath) : void 0;
3210
+ const getDataWithPath = resolvedPath && getData ? async () => {
3211
+ const data = await getData(resolvedPath, parentPaths ?? []);
3212
+ if (existingEntities) {
3213
+ const existingData = existingEntities.map((e) => e.values);
3214
+ data.push(...existingData);
3215
+ }
3216
+ return data;
3217
+ } : void 0;
3218
+ useEffect(() => {
3219
+ setFormDirty(dirty);
3220
+ }, [dirty]);
3221
+ function onImportDataSet(data, propertiesOrder) {
3222
+ importConfig.setInUse(true);
3223
+ buildEntityPropertiesFromData(data, getInferenceType).then((properties) => {
3224
+ const res = cleanPropertiesFromImport(properties);
3225
+ importConfig.setIdColumn(res.idColumn);
3226
+ importConfig.setImportData(data);
3227
+ importConfig.setHeadersMapping(res.headersMapping);
3228
+ const filteredHeadingsOrder = (propertiesOrder ?? []).filter((key) => res.headersMapping[key]) ?? Object.keys(res.properties);
3229
+ importConfig.setHeadingsOrder(filteredHeadingsOrder);
3230
+ importConfig.setOriginProperties(res.properties);
3231
+ const mappedHeadings = (propertiesOrder ?? []).map((key) => res.headersMapping[key]).filter(Boolean) ?? Object.keys(res.properties);
3232
+ setFieldValue("properties", res.properties);
3233
+ setFieldValue("propertiesOrder", mappedHeadings);
3234
+ });
3235
+ }
3236
+ const validValues = Boolean(values.name) && Boolean(values.slug);
3237
+ const onImportMappingComplete = () => {
3238
+ const updatedProperties = { ...values.properties };
3239
+ if (importConfig.idColumn) delete updatedProperties[importConfig.idColumn];
3240
+ setFieldValue("properties", updatedProperties);
3241
+ setNextMode();
3242
+ };
3243
+ const [deleteRequested, setDeleteRequested] = useState(false);
3244
+ const deleteCollection = () => {
3245
+ if (!collection) return;
3246
+ configController?.deleteCollection({ id: collection.slug }).then(() => {
3247
+ setDeleteRequested(false);
3248
+ handleCancel();
3249
+ snackbarController.open({
3250
+ message: "Collection deleted",
3251
+ type: "success"
3252
+ });
3253
+ });
3254
+ };
3255
+ const onWelcomeScreenContinue = (importData, propertiesOrder) => {
3256
+ if (importData) {
3257
+ onImportDataSet(importData, propertiesOrder);
3258
+ setCurrentView("import_data_mapping");
3259
+ } else setCurrentView("general");
3260
+ };
3261
+ const aiModifiedPaths = useAIModifiedPaths();
3262
+ const handleAIGenerated = (generatedCollection, operations) => {
3263
+ formController.setValues(generatedCollection);
3264
+ if (operations && aiModifiedPaths) aiModifiedPaths.addModifiedPaths(operations);
3265
+ };
3266
+ return /* @__PURE__ */ jsxs("div", {
3267
+ className: "h-full w-full flex flex-col bg-white dark:bg-surface-950",
3268
+ children: [/* @__PURE__ */ jsx(Formex, {
3269
+ value: formController,
3270
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [!isNewCollection && /* @__PURE__ */ jsxs("div", {
3271
+ className: cls("px-4 py-2 w-full flex shrink-0 items-center justify-between gap-4 bg-surface-50 dark:bg-surface-950 border-b", defaultBorderMixin),
3272
+ children: [/* @__PURE__ */ jsx("div", {
3273
+ className: "flex flex-1 items-center justify-end gap-4 min-w-0",
3274
+ children: /* @__PURE__ */ jsxs(Tabs, {
3275
+ value: currentView,
3276
+ className: "bg-transparent !w-fit max-w-full",
3277
+ onValueChange: (v) => setCurrentView(v),
3278
+ children: [
3279
+ /* @__PURE__ */ jsx(Tab, {
3280
+ value: "general",
3281
+ children: "General"
3282
+ }),
3283
+ /* @__PURE__ */ jsx(Tab, {
3284
+ value: "display",
3285
+ children: "Display"
3286
+ }),
3287
+ /* @__PURE__ */ jsx(Tab, {
3288
+ value: "properties",
3289
+ children: "Properties"
3290
+ }),
3291
+ getDataSourceCapabilities(values.driver).supportsRLS && /* @__PURE__ */ jsx(Tab, {
3292
+ value: "rls",
3293
+ children: "RLS"
3294
+ })
3295
+ ]
3296
+ })
3297
+ }), /* @__PURE__ */ jsxs("div", {
3298
+ className: "flex items-center gap-4",
3299
+ children: [generateCollection && /* @__PURE__ */ jsx(AICollectionGeneratorPopover, {
3300
+ existingCollection: values,
3301
+ onGenerated: handleAIGenerated,
3302
+ generateCollection,
3303
+ onAnalyticsEvent
3304
+ }), fullScreen && !isNewCollection && /* @__PURE__ */ jsx("div", {
3305
+ className: "flex items-center gap-2",
3306
+ children: /* @__PURE__ */ jsx(LoadingButton, {
3307
+ variant: "filled",
3308
+ color: "primary",
3309
+ type: "submit",
3310
+ onClick: () => formController.handleSubmit(),
3311
+ disabled: !dirty || isSubmitting || configController?.readOnly,
3312
+ loading: isSubmitting,
3313
+ children: configController?.readOnly ? "Update (Read-only)" : "Update"
3314
+ })
3315
+ })]
3316
+ })]
3317
+ }), /* @__PURE__ */ jsxs("form", {
3318
+ noValidate: true,
3319
+ onSubmit: formController.handleSubmit,
3320
+ className: "flex-grow flex flex-col min-h-0 relative",
3321
+ children: [/* @__PURE__ */ jsxs("div", {
3322
+ className: "flex-grow flex flex-col min-h-0 overflow-y-auto no-scrollbar relative w-full h-full",
3323
+ children: [
3324
+ currentView === "loading" && /* @__PURE__ */ jsx(CircularProgressCenter, {}),
3325
+ currentView === "extra_view" && usedPath && extraView?.View && /* @__PURE__ */ jsx(extraView.View, { path: usedPath }),
3326
+ currentView === "welcome" && /* @__PURE__ */ jsx(CollectionEditorWelcomeView, {
3327
+ path: usedPath,
3328
+ onContinue: onWelcomeScreenContinue,
3329
+ existingCollectionPaths: existingPaths,
3330
+ parentCollection,
3331
+ generateCollection,
3332
+ onAnalyticsEvent,
3333
+ unmappedTables,
3334
+ onImportFromTable: onFetchTableMetadata ? async (tableName) => {
3335
+ try {
3336
+ const columns = await onFetchTableMetadata(tableName);
3337
+ if (!columns) return;
3338
+ const collectionData = buildCollectionFromTableMetadata(tableName, columns);
3339
+ formController.setValues({
3340
+ ...formController.values,
3341
+ ...collectionData
3342
+ });
3343
+ onWelcomeScreenContinue();
3344
+ } catch (e) {
3345
+ console.error("Error importing table:", e);
3346
+ snackbarController.open({
3347
+ type: "error",
3348
+ message: "Error importing table: " + (e instanceof Error ? e.message : "Unknown error")
3349
+ });
3350
+ }
3351
+ } : void 0
3352
+ }),
3353
+ currentView === "import_data_mapping" && importConfig && /* @__PURE__ */ jsx(CollectionEditorImportMapping, {
3354
+ importConfig,
3355
+ propertyConfigs
3356
+ }),
3357
+ currentView === "import_data_preview" && importConfig && /* @__PURE__ */ jsx(CollectionEditorImportDataPreview, {
3358
+ importConfig,
3359
+ properties: values.properties,
3360
+ propertiesOrder: values.propertiesOrder
3361
+ }),
3362
+ currentView === "import_data_saving" && importConfig && /* @__PURE__ */ jsx(ImportSaveInProgress, {
3363
+ importConfig,
3364
+ collection: values,
3365
+ path: usedPath,
3366
+ onImportSuccess: async (importedCollection) => {
3367
+ snackbarController.open({
3368
+ type: "info",
3369
+ message: "Data imported successfully"
3370
+ });
3371
+ await saveCollection(values);
3372
+ handleClose(importedCollection);
3373
+ }
3374
+ }),
3375
+ currentView === "general" && /* @__PURE__ */ jsx(GeneralSettingsForm, {
3376
+ existingPaths,
3377
+ existingIds,
3378
+ parentCollection,
3379
+ isNewCollection
3380
+ }),
3381
+ currentView === "display" && /* @__PURE__ */ jsx(DisplaySettingsForm, { expandKanban }),
3382
+ currentView === "rls" && getDataSourceCapabilities(values.driver).supportsRLS && /* @__PURE__ */ jsx(CollectionRLSTab, {}),
3383
+ currentView === "properties" && /* @__PURE__ */ jsx(CollectionPropertiesEditorForm, {
3384
+ showErrors: submitCount > 0,
3385
+ isNewCollection,
3386
+ onPropertyError: (propertyKey, namespace, error) => {
3387
+ propertyErrorsRef.current = removeUndefined({
3388
+ ...propertyErrorsRef.current,
3389
+ [getFullIdPath(propertyKey, namespace)]: removeUndefined(error, true)
3390
+ }, true);
3391
+ formController.validate();
3392
+ },
3393
+ getUser,
3394
+ getData: getDataWithPath,
3395
+ doCollectionInference,
3396
+ propertyConfigs,
3397
+ extraIcon: extraView?.icon && /* @__PURE__ */ jsx(IconButton, {
3398
+ color: "primary",
3399
+ onClick: () => setCurrentView("extra_view"),
3400
+ children: extraView.icon
3401
+ })
3402
+ })
3403
+ ]
3404
+ }), (!fullScreen || isNewCollection || !!error) && /* @__PURE__ */ jsxs("div", {
3405
+ className: "shrink-0 w-full p-4 sm:px-6 sm:py-4 border-t border-surface-200 dark:border-surface-900 flex items-center justify-between gap-4 bg-white dark:bg-surface-950",
3406
+ children: [
3407
+ error && /* @__PURE__ */ jsx(ErrorView, { error }),
3408
+ isNewCollection && includeTemplates && currentView === "import_data_mapping" && /* @__PURE__ */ jsx(Button, {
3409
+ variant: "text",
3410
+ type: "button",
3411
+ onClick: () => {
3412
+ importConfig.setInUse(false);
3413
+ return setCurrentView("welcome");
3414
+ },
3415
+ children: "Back"
3416
+ }),
3417
+ isNewCollection && includeTemplates && currentView === "import_data_preview" && /* @__PURE__ */ jsx(Button, {
3418
+ variant: "text",
3419
+ type: "button",
3420
+ onClick: () => {
3421
+ setCurrentView("import_data_mapping");
3422
+ },
3423
+ children: "Back"
3424
+ }),
3425
+ isNewCollection && includeTemplates && currentView === "general" && /* @__PURE__ */ jsx(Button, {
3426
+ variant: "text",
3427
+ type: "button",
3428
+ onClick: () => setCurrentView("welcome"),
3429
+ children: "Back"
3430
+ }),
3431
+ isNewCollection && currentView === "properties" && /* @__PURE__ */ jsxs(Button, {
3432
+ variant: "text",
3433
+ type: "button",
3434
+ color: "neutral",
3435
+ onClick: () => setCurrentView("general"),
3436
+ children: [/* @__PURE__ */ jsx(ArrowLeftIcon, {}), "Back"]
3437
+ }),
3438
+ (!fullScreen || isNewCollection) && /* @__PURE__ */ jsx(Button, {
3439
+ variant: "text",
3440
+ color: "neutral",
3441
+ onClick: () => {
3442
+ handleCancel();
3443
+ },
3444
+ children: "Cancel"
3445
+ }),
3446
+ currentView === "welcome" && /* @__PURE__ */ jsx(Button, {
3447
+ variant: "text",
3448
+ onClick: () => onWelcomeScreenContinue(),
3449
+ children: "Continue from scratch"
3450
+ }),
3451
+ isNewCollection && currentView === "import_data_mapping" && /* @__PURE__ */ jsx(Button, {
3452
+ variant: "filled",
3453
+ color: "primary",
3454
+ onClick: onImportMappingComplete,
3455
+ children: "Next"
3456
+ }),
3457
+ isNewCollection && currentView === "import_data_preview" && /* @__PURE__ */ jsx(Button, {
3458
+ variant: "filled",
3459
+ color: "primary",
3460
+ onClick: () => {
3461
+ setNextMode();
3462
+ },
3463
+ children: "Next"
3464
+ }),
3465
+ isNewCollection && (currentView === "general" || currentView === "properties") && /* @__PURE__ */ jsxs(LoadingButton, {
3466
+ variant: "filled",
3467
+ color: "primary",
3468
+ type: "submit",
3469
+ loading: isSubmitting,
3470
+ disabled: isSubmitting || currentView === "general" && !validValues || configController?.readOnly,
3471
+ startIcon: currentView === "properties" ? /* @__PURE__ */ jsx(CheckIcon, {}) : void 0,
3472
+ children: [currentView === "general" && "Next", currentView === "properties" && "Create collection"]
3473
+ }),
3474
+ !isNewCollection && !fullScreen && /* @__PURE__ */ jsx(LoadingButton, {
3475
+ variant: "filled",
3476
+ color: "primary",
3477
+ type: "submit",
3478
+ disabled: isSubmitting || configController?.readOnly,
3479
+ loading: isSubmitting,
3480
+ children: configController?.readOnly ? "Update collection (Read-only)" : "Update collection"
3481
+ })
3482
+ ]
3483
+ })]
3484
+ })] })
3485
+ }), /* @__PURE__ */ jsx(ConfirmationDialog, {
3486
+ open: deleteRequested,
3487
+ onAccept: deleteCollection,
3488
+ onCancel: () => setDeleteRequested(false),
3489
+ title: /* @__PURE__ */ jsx(Fragment, { children: "Delete the stored config?" }),
3490
+ body: /* @__PURE__ */ jsxs(Fragment, { children: [
3491
+ " This will ",
3492
+ /* @__PURE__ */ jsx("b", { children: "not delete any data" }),
3493
+ ", only the stored config, and reset to the code state."
3494
+ ] })
3495
+ })]
3496
+ });
3497
+ }
3498
+ function applyPropertyConfigs(collection, propertyConfigs) {
3499
+ const { properties, ...rest } = collection;
3500
+ const propertiesResult = {};
3501
+ if (properties) Object.keys(properties).forEach((key) => {
3502
+ const prop = properties[key];
3503
+ if (prop == null) return;
3504
+ propertiesResult[key] = applyPropertiesConfig(prop, propertyConfigs);
3505
+ });
3506
+ return {
3507
+ ...rest,
3508
+ properties: propertiesResult
3509
+ };
3510
+ }
3511
+ function applyPropertiesConfig(property, propertyConfigs) {
3512
+ let internalProperty = property;
3513
+ if (propertyConfigs && internalProperty && typeof internalProperty === "object" && internalProperty.propertyConfig) {
3514
+ const propertyConfig = propertyConfigs[internalProperty.propertyConfig];
3515
+ if (propertyConfig && isPropertyBuilder(propertyConfig.property)) internalProperty = propertyConfig.property;
3516
+ else {
3517
+ if (propertyConfig) internalProperty = mergeDeep(propertyConfig.property, internalProperty);
3518
+ if (!isPropertyBuilder(internalProperty) && internalProperty.type === "map" && internalProperty.properties) {
3519
+ const properties = {};
3520
+ Object.keys(internalProperty.properties).forEach((key) => {
3521
+ properties[key] = applyPropertiesConfig(internalProperty.properties[key], propertyConfigs);
3522
+ });
3523
+ internalProperty = {
3524
+ ...internalProperty,
3525
+ properties
3526
+ };
3527
+ }
3528
+ }
3529
+ }
3530
+ return internalProperty;
3531
+ }
3532
+ var validatePath = (value, isNewCollection, existingPaths, idValue) => {
3533
+ let error;
3534
+ if (!value) error = "You must specify a path in the database for this collection";
3535
+ if (isNewCollection && existingPaths?.includes(value.trim().toLowerCase()) && !idValue) error = "There is already a collection with the specified path. If you want to have multiple collections referring to the same database path, make sure the have different ids";
3536
+ if (removeInitialAndTrailingSlashes(value).split("/").length % 2 === 0) error = `Collection paths must have an odd number of segments: ${value}`;
3537
+ return error;
3538
+ };
3539
+ var validateId = (value, isNewCollection, existingPaths, existingIds) => {
3540
+ if (!value) return void 0;
3541
+ let error;
3542
+ if (isNewCollection && existingPaths?.includes(value.trim().toLowerCase())) error = "There is already a collection that uses this value as a path";
3543
+ if (isNewCollection && existingIds?.includes(value.trim().toLowerCase())) error = "There is already a collection which uses this id";
3544
+ return error;
3545
+ };
3546
+ //#endregion
3547
+ export { CollectionEditorDialog as n, CollectionEditorDialog_exports as r, CollectionEditor as t };
3548
+
3549
+ //# sourceMappingURL=CollectionEditorDialog-jA-PLh6N.js.map