@firecms/core 3.0.0 → 3.1.0-canary.02232f4

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 (340) hide show
  1. package/README.md +1 -1
  2. package/dist/components/AIIcon.d.ts +16 -0
  3. package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +7 -1
  4. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  5. package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +14 -0
  6. package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +6 -0
  7. package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -4
  8. package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +6 -0
  9. package/dist/components/EntityCollectionTable/internal/popup_field/useDraggable.d.ts +2 -2
  10. package/dist/components/EntityCollectionView/Board.d.ts +2 -0
  11. package/dist/components/EntityCollectionView/BoardColumn.d.ts +42 -0
  12. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +9 -0
  13. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +14 -0
  14. package/dist/components/EntityCollectionView/CollectionDataErrorBanner.d.ts +4 -0
  15. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +26 -0
  16. package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
  17. package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
  18. package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
  19. package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
  20. package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
  21. package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
  22. package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +44 -0
  23. package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
  24. package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
  25. package/dist/components/ErrorBoundary.d.ts +4 -2
  26. package/dist/components/HomePage/DefaultHomePage.d.ts +0 -1
  27. package/dist/components/LanguageToggle.d.ts +1 -0
  28. package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
  29. package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
  30. package/dist/components/UnsavedChangesDialog.d.ts +1 -0
  31. package/dist/components/VirtualTable/VirtualTable.performance.test.d.ts +1 -0
  32. package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
  33. package/dist/components/VirtualTable/VirtualTableHeader.d.ts +4 -1
  34. package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
  35. package/dist/components/VirtualTable/VirtualTableProps.d.ts +17 -1
  36. package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
  37. package/dist/components/VirtualTable/types.d.ts +3 -0
  38. package/dist/components/index.d.ts +4 -0
  39. package/dist/contexts/index.d.ts +10 -0
  40. package/dist/core/DrawerNavigationGroup.d.ts +45 -0
  41. package/dist/core/index.d.ts +1 -0
  42. package/dist/editor/components/SlashCommandMenu.d.ts +6 -0
  43. package/dist/editor/components/editor-bubble-item.d.ts +8 -0
  44. package/dist/editor/components/editor-bubble.d.ts +8 -0
  45. package/dist/editor/components/image-bubble.d.ts +5 -0
  46. package/dist/editor/components/index.d.ts +16 -0
  47. package/dist/editor/components/table-bubble.d.ts +5 -0
  48. package/dist/editor/editor.d.ts +30 -0
  49. package/dist/editor/extensions/HighlightDecorationExtension.d.ts +24 -0
  50. package/dist/editor/extensions/Image/index.d.ts +6 -0
  51. package/dist/editor/extensions/Image.d.ts +6 -0
  52. package/dist/editor/extensions/TextLoadingDecorationExtension.d.ts +16 -0
  53. package/dist/editor/extensions/clipboard.d.ts +7 -0
  54. package/dist/editor/extensions/custom-keymap.d.ts +1 -0
  55. package/dist/editor/extensions/drag-and-drop.d.ts +9 -0
  56. package/dist/editor/hooks/useProseMirror.d.ts +13 -0
  57. package/dist/editor/hooks/useProseMirrorContext.d.ts +9 -0
  58. package/dist/editor/index.d.ts +2 -0
  59. package/dist/editor/markdown.d.ts +5 -0
  60. package/dist/editor/nodeViews/ImageComponent.d.ts +3 -0
  61. package/dist/editor/nodeViews/ReactNodeView.d.ts +29 -0
  62. package/dist/editor/nodeViews/TaskItemComponent.d.ts +3 -0
  63. package/dist/editor/nodeViews/index.d.ts +6 -0
  64. package/dist/editor/plugins/index.d.ts +2 -0
  65. package/dist/editor/plugins/inputrules.d.ts +6 -0
  66. package/dist/editor/plugins/placeholderPlugin.d.ts +3 -0
  67. package/dist/editor/plugins/slashCommandPlugin.d.ts +12 -0
  68. package/dist/editor/schema.d.ts +2 -0
  69. package/dist/editor/selectors/ai-selector.d.ts +0 -0
  70. package/dist/editor/selectors/color-selector.d.ts +10 -0
  71. package/dist/editor/selectors/link-selector.d.ts +8 -0
  72. package/dist/editor/selectors/node-selector.d.ts +15 -0
  73. package/dist/editor/selectors/text-buttons.d.ts +1 -0
  74. package/dist/editor/types.d.ts +5 -0
  75. package/dist/editor/useProseMirror.d.ts +16 -0
  76. package/dist/editor/utils/prosemirror-utils.d.ts +6 -0
  77. package/dist/editor/utils/remove_classes.d.ts +1 -0
  78. package/dist/editor/utils/useDebouncedCallback.d.ts +1 -0
  79. package/dist/form/components/ErrorFocus.d.ts +1 -1
  80. package/dist/form/components/LocalChangesMenu.d.ts +2 -2
  81. package/dist/form/components/StorageUploadProgress.d.ts +1 -1
  82. package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
  83. package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
  84. package/dist/form/validation.d.ts +3 -2
  85. package/dist/hooks/index.d.ts +1 -0
  86. package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
  87. package/dist/hooks/useBuildNavigationController.d.ts +0 -1
  88. package/dist/hooks/useCollapsedGroups.d.ts +6 -3
  89. package/dist/hooks/useTranslation.d.ts +17 -0
  90. package/dist/i18n/FireCMSi18nProvider.d.ts +33 -0
  91. package/dist/index.d.ts +5 -0
  92. package/dist/index.es.js +31028 -16080
  93. package/dist/index.es.js.map +1 -1
  94. package/dist/index.umd.js +29955 -15028
  95. package/dist/index.umd.js.map +1 -1
  96. package/dist/internal/useRestoreScroll.d.ts +1 -1
  97. package/dist/locales/de.d.ts +2 -0
  98. package/dist/locales/en.d.ts +10 -0
  99. package/dist/locales/es.d.ts +10 -0
  100. package/dist/locales/fr.d.ts +2 -0
  101. package/dist/locales/hi.d.ts +2 -0
  102. package/dist/locales/it.d.ts +2 -0
  103. package/dist/locales/pt.d.ts +7 -0
  104. package/dist/preview/PropertyPreviewProps.d.ts +5 -0
  105. package/dist/preview/components/DatePreview.d.ts +13 -3
  106. package/dist/preview/components/ImagePreview.d.ts +5 -1
  107. package/dist/preview/components/StorageThumbnail.d.ts +2 -1
  108. package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
  109. package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
  110. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  111. package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
  112. package/dist/types/analytics.d.ts +1 -1
  113. package/dist/types/collections.d.ts +88 -2
  114. package/dist/types/customization_controller.d.ts +2 -1
  115. package/dist/types/datasource.d.ts +0 -1
  116. package/dist/types/entities.d.ts +1 -0
  117. package/dist/types/firecms.d.ts +2 -1
  118. package/dist/types/index.d.ts +1 -0
  119. package/dist/types/navigation.d.ts +2 -2
  120. package/dist/types/plugins.d.ts +69 -1
  121. package/dist/types/properties.d.ts +277 -12
  122. package/dist/types/storage.d.ts +9 -0
  123. package/dist/types/translations.d.ts +669 -0
  124. package/dist/util/__tests__/conditions.test.d.ts +1 -0
  125. package/dist/util/__tests__/objects.test.d.ts +1 -0
  126. package/dist/util/conditions.d.ts +26 -0
  127. package/dist/util/entities.d.ts +2 -3
  128. package/dist/util/index.d.ts +3 -1
  129. package/dist/util/lazy_eager.d.ts +7 -0
  130. package/dist/util/objects.d.ts +1 -0
  131. package/dist/util/property_utils.d.ts +2 -1
  132. package/dist/util/resolutions.d.ts +3 -3
  133. package/dist/util/useStorageUploadController.d.ts +11 -2
  134. package/package.json +52 -12
  135. package/src/app/Scaffold.tsx +20 -19
  136. package/src/components/AIIcon.tsx +41 -0
  137. package/src/components/ArrayContainer.tsx +7 -8
  138. package/src/components/ClearFilterSortButton.tsx +25 -19
  139. package/src/components/ConfirmationDialog.tsx +4 -4
  140. package/src/components/DeleteEntityDialog.tsx +12 -11
  141. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +82 -43
  142. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
  143. package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
  144. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
  145. package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +6 -3
  146. package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +24 -44
  147. package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
  148. package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +1 -1
  149. package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -2
  150. package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +11 -11
  151. package/src/components/EntityCollectionView/Board.tsx +324 -0
  152. package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
  153. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
  154. package/src/components/EntityCollectionView/BoardSortableList.tsx +174 -0
  155. package/src/components/EntityCollectionView/CollectionDataErrorBanner.tsx +43 -0
  156. package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
  157. package/src/components/EntityCollectionView/EntityCard.tsx +235 -0
  158. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +706 -0
  159. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +236 -0
  160. package/src/components/EntityCollectionView/EntityCollectionView.tsx +531 -209
  161. package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +35 -22
  162. package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +86 -15
  163. package/src/components/EntityCollectionView/FiltersDialog.tsx +252 -0
  164. package/src/components/EntityCollectionView/ViewModeToggle.tsx +202 -0
  165. package/src/components/EntityCollectionView/board_types.ts +113 -0
  166. package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
  167. package/src/components/EntityJsonPreview.tsx +2 -1
  168. package/src/components/EntityPreview.tsx +1 -1
  169. package/src/components/EntityView.tsx +3 -2
  170. package/src/components/ErrorBoundary.tsx +27 -15
  171. package/src/components/ErrorTooltip.tsx +2 -1
  172. package/src/components/HomePage/DefaultHomePage.tsx +65 -22
  173. package/src/components/HomePage/HomePageDnD.tsx +59 -42
  174. package/src/components/HomePage/NavigationCard.tsx +20 -18
  175. package/src/components/HomePage/NavigationGroup.tsx +20 -17
  176. package/src/components/HomePage/RenameGroupDialog.tsx +15 -15
  177. package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
  178. package/src/components/LanguageToggle.tsx +66 -0
  179. package/src/components/NotFoundPage.tsx +5 -3
  180. package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +12 -17
  181. package/src/components/ReferenceWidget.tsx +5 -6
  182. package/src/components/SearchIconsView.tsx +3 -1
  183. package/src/components/SelectableTable/SelectableTable.tsx +75 -67
  184. package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
  185. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +50 -40
  186. package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +53 -40
  187. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +60 -58
  188. package/src/components/UnsavedChangesDialog.tsx +6 -6
  189. package/src/components/UserDisplay.tsx +4 -4
  190. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +387 -0
  191. package/src/components/VirtualTable/VirtualTable.tsx +277 -121
  192. package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
  193. package/src/components/VirtualTable/VirtualTableHeader.tsx +76 -64
  194. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +163 -42
  195. package/src/components/VirtualTable/VirtualTableProps.tsx +21 -2
  196. package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
  197. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
  198. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +19 -6
  199. package/src/components/VirtualTable/types.tsx +3 -0
  200. package/src/components/common/default_entity_actions.tsx +4 -0
  201. package/src/components/common/useColumnsIds.tsx +95 -3
  202. package/src/components/common/useDataSourceTableController.tsx +12 -4
  203. package/src/components/index.tsx +5 -0
  204. package/src/contexts/BreacrumbsContext.tsx +15 -8
  205. package/src/contexts/index.ts +10 -0
  206. package/src/core/DefaultAppBar.tsx +49 -32
  207. package/src/core/DefaultDrawer.tsx +49 -57
  208. package/src/core/DrawerNavigationGroup.tsx +120 -0
  209. package/src/core/DrawerNavigationItem.tsx +4 -3
  210. package/src/core/EntityEditView.tsx +94 -50
  211. package/src/core/EntityEditViewFormActions.tsx +24 -17
  212. package/src/core/EntitySidePanel.tsx +34 -30
  213. package/src/core/FireCMS.tsx +33 -6
  214. package/src/core/SideDialogs.tsx +4 -2
  215. package/src/core/field_configs.tsx +18 -11
  216. package/src/core/index.tsx +1 -0
  217. package/src/editor/components/SlashCommandMenu.tsx +516 -0
  218. package/src/editor/components/editor-bubble-item.tsx +32 -0
  219. package/src/editor/components/editor-bubble.tsx +118 -0
  220. package/src/editor/components/image-bubble.tsx +156 -0
  221. package/src/editor/components/index.ts +14 -0
  222. package/src/editor/components/table-bubble.tsx +165 -0
  223. package/src/editor/editor.tsx +455 -0
  224. package/src/editor/extensions/HighlightDecorationExtension.ts +114 -0
  225. package/src/editor/extensions/Image/index.ts +133 -0
  226. package/src/editor/extensions/Image.ts +159 -0
  227. package/src/editor/extensions/TextLoadingDecorationExtension.tsx +107 -0
  228. package/src/editor/extensions/clipboard.ts +72 -0
  229. package/src/editor/extensions/custom-keymap.ts +24 -0
  230. package/src/editor/extensions/drag-and-drop.tsx +480 -0
  231. package/src/editor/hooks/useProseMirror.ts +124 -0
  232. package/src/editor/hooks/useProseMirrorContext.ts +15 -0
  233. package/src/editor/index.ts +2 -0
  234. package/src/editor/markdown.ts +172 -0
  235. package/src/editor/nodeViews/ImageComponent.tsx +20 -0
  236. package/src/editor/nodeViews/ReactNodeView.tsx +89 -0
  237. package/src/editor/nodeViews/TaskItemComponent.tsx +29 -0
  238. package/src/editor/nodeViews/index.ts +35 -0
  239. package/src/editor/plugins/index.ts +58 -0
  240. package/src/editor/plugins/inputrules.ts +82 -0
  241. package/src/editor/plugins/placeholderPlugin.ts +55 -0
  242. package/src/editor/plugins/slashCommandPlugin.ts +61 -0
  243. package/src/editor/schema.ts +240 -0
  244. package/src/editor/selectors/ai-selector.tsx +111 -0
  245. package/src/editor/selectors/color-selector.tsx +200 -0
  246. package/src/editor/selectors/link-selector.tsx +118 -0
  247. package/src/editor/selectors/node-selector.tsx +157 -0
  248. package/src/editor/selectors/text-buttons.tsx +86 -0
  249. package/src/editor/types.ts +6 -0
  250. package/src/editor/useProseMirror.ts +126 -0
  251. package/src/editor/utils/prosemirror-utils.ts +108 -0
  252. package/src/editor/utils/remove_classes.ts +17 -0
  253. package/src/editor/utils/useDebouncedCallback.ts +25 -0
  254. package/src/form/EntityForm.tsx +150 -75
  255. package/src/form/EntityFormActions.tsx +19 -12
  256. package/src/form/PropertyFieldBinding.tsx +68 -51
  257. package/src/form/components/ErrorFocus.tsx +3 -3
  258. package/src/form/components/LocalChangesMenu.tsx +19 -19
  259. package/src/form/components/StorageItemPreview.tsx +5 -3
  260. package/src/form/components/StorageUploadProgress.tsx +22 -6
  261. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +18 -5
  262. package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +22 -10
  263. package/src/form/field_bindings/BlockFieldBinding.tsx +26 -9
  264. package/src/form/field_bindings/DateTimeFieldBinding.tsx +18 -17
  265. package/src/form/field_bindings/KeyValueFieldBinding.tsx +46 -25
  266. package/src/form/field_bindings/MapFieldBinding.tsx +88 -70
  267. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +93 -52
  268. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +15 -1
  269. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +25 -11
  270. package/src/form/field_bindings/ReferenceFieldBinding.tsx +25 -11
  271. package/src/form/field_bindings/RepeatFieldBinding.tsx +21 -6
  272. package/src/form/field_bindings/SelectFieldBinding.tsx +7 -5
  273. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +110 -92
  274. package/src/form/field_bindings/SwitchFieldBinding.tsx +31 -14
  275. package/src/form/field_bindings/TextFieldBinding.tsx +77 -38
  276. package/src/form/field_bindings/UserSelectFieldBinding.tsx +7 -5
  277. package/src/form/validation.ts +245 -160
  278. package/src/hooks/index.tsx +1 -0
  279. package/src/hooks/useBreadcrumbsController.tsx +18 -0
  280. package/src/hooks/useBuildNavigationController.tsx +91 -41
  281. package/src/hooks/useCollapsedGroups.ts +18 -9
  282. package/src/hooks/useTranslation.ts +31 -0
  283. package/src/hooks/useValidateAuthenticator.tsx +1 -1
  284. package/src/i18n/FireCMSi18nProvider.tsx +160 -0
  285. package/src/index.ts +5 -0
  286. package/src/internal/useBuildDataSource.ts +68 -34
  287. package/src/internal/useBuildSideDialogsController.tsx +11 -8
  288. package/src/internal/useBuildSideEntityController.tsx +24 -24
  289. package/src/internal/useRestoreScroll.tsx +26 -14
  290. package/src/locales/de.ts +718 -0
  291. package/src/locales/en.ts +730 -0
  292. package/src/locales/es.ts +730 -0
  293. package/src/locales/fr.ts +718 -0
  294. package/src/locales/hi.ts +718 -0
  295. package/src/locales/it.ts +718 -0
  296. package/src/locales/pt.ts +727 -0
  297. package/src/preview/PropertyPreview.tsx +43 -33
  298. package/src/preview/PropertyPreviewProps.tsx +6 -0
  299. package/src/preview/components/DatePreview.tsx +72 -4
  300. package/src/preview/components/EmptyValue.tsx +1 -1
  301. package/src/preview/components/ImagePreview.tsx +37 -21
  302. package/src/preview/components/ReferencePreview.tsx +8 -2
  303. package/src/preview/components/StorageThumbnail.tsx +16 -12
  304. package/src/preview/components/UrlComponentPreview.tsx +32 -27
  305. package/src/preview/components/UserPreview.tsx +3 -1
  306. package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
  307. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
  308. package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
  309. package/src/preview/property_previews/MapPropertyPreview.tsx +49 -27
  310. package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
  311. package/src/routes/CustomCMSRoute.tsx +1 -0
  312. package/src/routes/FireCMSRoute.tsx +87 -65
  313. package/src/types/analytics.ts +10 -0
  314. package/src/types/collections.ts +97 -3
  315. package/src/types/customization_controller.tsx +2 -1
  316. package/src/types/datasource.ts +54 -56
  317. package/src/types/entities.ts +10 -0
  318. package/src/types/firecms.tsx +2 -1
  319. package/src/types/index.ts +1 -0
  320. package/src/types/navigation.ts +2 -2
  321. package/src/types/plugins.tsx +77 -1
  322. package/src/types/properties.ts +369 -37
  323. package/src/types/storage.ts +11 -1
  324. package/src/types/translations.ts +752 -0
  325. package/src/util/__tests__/conditions.test.ts +506 -0
  326. package/src/util/__tests__/objects.test.ts +196 -0
  327. package/src/util/callbacks.ts +6 -3
  328. package/src/util/collections.ts +51 -6
  329. package/src/util/conditions.ts +339 -0
  330. package/src/util/entities.ts +29 -30
  331. package/src/util/entity_cache.ts +2 -1
  332. package/src/util/index.ts +3 -1
  333. package/src/util/join_collections.ts +10 -8
  334. package/src/util/lazy_eager.tsx +33 -0
  335. package/src/util/objects.ts +46 -13
  336. package/src/util/{references.ts → previews.ts} +16 -2
  337. package/src/util/property_utils.tsx +37 -11
  338. package/src/util/resolutions.ts +62 -58
  339. package/src/util/useStorageUploadController.tsx +34 -30
  340. /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
@@ -14,13 +14,15 @@ import {
14
14
  PartialEntityCollection,
15
15
  PropertyOrBuilder,
16
16
  ResolvedProperty,
17
- SaveEntityProps
17
+ SaveEntityProps,
18
+ ViewMode
18
19
  } from "../../types";
19
20
  import {
20
21
  EntityCollectionRowActions,
21
22
  EntityCollectionTable,
22
23
  useDataSourceTableController
23
24
  } from "../EntityCollectionTable";
25
+ import { CollectionTableToolbar } from "../EntityCollectionTable/internal/CollectionTableToolbar";
24
26
 
25
27
  import {
26
28
  canCreateEntity,
@@ -43,10 +45,16 @@ import {
43
45
  useFireCMSContext,
44
46
  useLargeLayout,
45
47
  useNavigationController,
46
- useSideEntityController
48
+ useSideEntityController,
49
+ useTranslation
47
50
  } from "../../hooks";
51
+ import { useBreadcrumbsController } from "../../hooks/useBreadcrumbsController";
48
52
  import { useUserConfigurationPersistence } from "../../hooks/useUserConfigurationPersistence";
49
53
  import { EntityCollectionViewActions } from "./EntityCollectionViewActions";
54
+ import { EntityCollectionCardView } from "./EntityCollectionCardView";
55
+ import { EntityCollectionBoardView } from "./EntityCollectionBoardView";
56
+ import { CollectionDataErrorBanner } from "./CollectionDataErrorBanner";
57
+ import { ViewModeToggle, KanbanPropertyOption } from "./ViewModeToggle";
50
58
  import {
51
59
  AddIcon,
52
60
  Button,
@@ -142,18 +150,20 @@ export type EntityCollectionViewProps<M extends Record<string, any>> = {
142
150
  */
143
151
  export const EntityCollectionView = React.memo(
144
152
  function EntityCollectionView<M extends Record<string, any>>({
145
- fullPath: fullPathProp,
146
- fullIdPath,
147
- parentCollectionIds,
148
- isSubCollection,
149
- className,
150
- updateUrl,
151
- ...collectionProp
152
- }: EntityCollectionViewProps<M>
153
+ fullPath: fullPathProp,
154
+ fullIdPath,
155
+ parentCollectionIds,
156
+ isSubCollection,
157
+ className,
158
+ updateUrl,
159
+ ...collectionProp
160
+ }: EntityCollectionViewProps<M>
153
161
  ) {
154
162
 
155
163
  const context = useFireCMSContext();
164
+ const { t } = useTranslation();
156
165
  const navigation = useNavigationController();
166
+ const breadcrumbs = useBreadcrumbsController();
157
167
  const fullPath = fullPathProp ?? collectionProp.path;
158
168
  const dataSource = useDataSource(collectionProp);
159
169
  const sideEntityController = useSideEntityController();
@@ -184,8 +194,19 @@ export const EntityCollectionView = React.memo(
184
194
 
185
195
  const [lastDeleteTimestamp, setLastDeleteTimestamp] = React.useState<number>(0);
186
196
 
187
- // number of entities in the collection
188
- const [docsCount, setDocsCount] = useState<number>(0);
197
+ // Track recently deleted entities for optimistic Kanban count updates
198
+ const [deletedEntities, setDeletedEntities] = React.useState<Entity<M>[]>([]);
199
+
200
+ // number of entities in the collection (undefined = loading)
201
+ const [docsCount, setDocsCount] = useState<number | undefined>(undefined);
202
+
203
+ // Optimistic state for column order to prevent UI flickering during persistence
204
+ const [localPropertiesOrder, setLocalPropertiesOrder] = useState<string[] | undefined>(collection.propertiesOrder);
205
+
206
+ // Sync local state with collection's propertiesOrder when it changes from external sources
207
+ useEffect(() => {
208
+ setLocalPropertiesOrder(collection.propertiesOrder);
209
+ }, [collection.propertiesOrder]);
189
210
 
190
211
  const unselectNavigatedEntity = useCallback(() => {
191
212
  const currentSelection = highlightedEntity;
@@ -208,6 +229,83 @@ export const EntityCollectionView = React.memo(
208
229
 
209
230
  const [popOverOpen, setPopOverOpen] = useState(false);
210
231
 
232
+ // View mode priority: URL > saved user config > collection.defaultViewMode
233
+ const defaultViewMode = collection.defaultViewMode ?? "table";
234
+
235
+ // Parse view from URL
236
+ const getViewFromUrl = useCallback((): ViewMode | null => {
237
+ const params = new URLSearchParams(window.location.search);
238
+ const urlView = params.get("__view");
239
+ if (urlView && ["table", "kanban", "cards"].includes(urlView)) {
240
+ return urlView as ViewMode;
241
+ }
242
+ return null;
243
+ }, []);
244
+
245
+ // Get saved view from local persistence
246
+ const getSavedView = useCallback((): ViewMode | null => {
247
+ const saved = userConfigPersistence?.getCollectionConfig<M>(fullPath)?.defaultViewMode;
248
+ return (saved as ViewMode) ?? null;
249
+ }, [userConfigPersistence, fullPath]);
250
+
251
+ const [viewMode, setViewModeState] = useState<ViewMode>(() => {
252
+ // Priority: URL > saved config > collection default
253
+ const urlView = getViewFromUrl();
254
+ if (urlView) return urlView;
255
+ const savedView = getSavedView();
256
+ if (savedView) return savedView;
257
+ return defaultViewMode;
258
+ });
259
+
260
+ // Sync URL with current view on init (if view came from saved config)
261
+ useEffect(() => {
262
+ const urlView = getViewFromUrl();
263
+ if (!urlView && viewMode !== "table") {
264
+ // View came from saved config but URL doesn't have it - update URL without push
265
+ const url = new URL(window.location.href);
266
+ url.searchParams.set("__view", viewMode);
267
+ window.history.replaceState({}, "", url.toString());
268
+ }
269
+ }, []); // Only on mount
270
+
271
+ // Update URL when view mode changes (user action)
272
+ const setViewMode = useCallback((newMode: ViewMode) => {
273
+ setViewModeState(newMode);
274
+
275
+ // Update URL with __view param
276
+ const url = new URL(window.location.href);
277
+ if (newMode === "table") {
278
+ url.searchParams.delete("__view");
279
+ } else {
280
+ url.searchParams.set("__view", newMode);
281
+ }
282
+ window.history.pushState({}, "", url.toString());
283
+ }, []);
284
+
285
+ // Listen for browser back/forward
286
+ useEffect(() => {
287
+ const handlePopState = () => {
288
+ const urlView = getViewFromUrl();
289
+ if (urlView) {
290
+ // URL has explicit view - use it
291
+ setViewModeState(urlView);
292
+ } else {
293
+ // No URL param - fallback to saved config or collection default
294
+ const savedView = getSavedView();
295
+ setViewModeState(savedView ?? defaultViewMode);
296
+ }
297
+ };
298
+
299
+ window.addEventListener("popstate", handlePopState);
300
+ return () => window.removeEventListener("popstate", handlePopState);
301
+ }, [getViewFromUrl, getSavedView, defaultViewMode]);
302
+
303
+ // Card view size state - controls the grid column count
304
+ const [cardSize, setCardSize] = useState<CollectionSize>(collection.defaultSize ?? "m");
305
+
306
+ // Table view size state - controls row height
307
+ const [tableSize, setTableSize] = useState<CollectionSize>(collection.defaultSize ?? "m");
308
+
211
309
  const selectionController = useSelectionController<M>();
212
310
  const usedSelectionController = collection.selectionController ?? selectionController;
213
311
  const {
@@ -284,6 +382,7 @@ export const EntityCollectionView = React.memo(
284
382
  path: fullPath
285
383
  });
286
384
  setSelectedEntities((selectedEntities) => selectedEntities.filter((e) => e.id !== entity.id));
385
+ setDeletedEntities(prev => [...prev, entity]);
287
386
  setLastDeleteTimestamp(Date.now());
288
387
  };
289
388
 
@@ -293,6 +392,7 @@ export const EntityCollectionView = React.memo(
293
392
  });
294
393
  setSelectedEntities([]);
295
394
  setDeleteEntityClicked(undefined);
395
+ setDeletedEntities(prev => [...prev, ...entities]);
296
396
  setLastDeleteTimestamp(Date.now());
297
397
  };
298
398
 
@@ -317,9 +417,9 @@ export const EntityCollectionView = React.memo(
317
417
  }, [userConfigPersistence]);
318
418
 
319
419
  const onColumnResize = useCallback(({
320
- width,
321
- key
322
- }: OnColumnResizeParams) => {
420
+ width,
421
+ key
422
+ }: OnColumnResizeParams) => {
323
423
 
324
424
  const collection = collectionRef.current;
325
425
  // Only for property columns
@@ -328,29 +428,44 @@ export const EntityCollectionView = React.memo(
328
428
  onCollectionModifiedForUser(fullPath, localCollection);
329
429
  }, [onCollectionModifiedForUser, fullPath]);
330
430
 
331
- const onSizeChanged = useCallback((size: CollectionSize) => {
431
+ const onTableSizeChanged = useCallback((size: CollectionSize) => {
432
+ setTableSize(size);
332
433
  if (userConfigPersistence)
333
434
  onCollectionModifiedForUser(fullPath, { defaultSize: size })
334
435
  }, [onCollectionModifiedForUser, fullPath, userConfigPersistence]);
335
436
 
437
+ // View mode change: update URL + save to local persistence
438
+ const onViewModeChange = useCallback((mode: ViewMode) => {
439
+ analyticsController.onAnalyticsEvent?.("view_mode_changed", {
440
+ path: fullPath,
441
+ from: viewMode,
442
+ to: mode
443
+ });
444
+ setViewMode(mode);
445
+ // Save to local persistence for next visit
446
+ if (userConfigPersistence) {
447
+ onCollectionModifiedForUser(fullPath, { defaultViewMode: mode } as PartialEntityCollection<M>);
448
+ }
449
+ }, [setViewMode, userConfigPersistence, onCollectionModifiedForUser, fullPath, analyticsController, viewMode]);
450
+
336
451
  const createEnabled = canCreateEntity(collection, authController, fullPath, null);
337
452
 
338
453
  const uniqueFieldValidator: UniqueFieldValidator = useCallback(
339
454
  ({
340
- name,
341
- value,
342
- property,
343
- entityId
344
- }) => dataSource.checkUniqueField(fullPath, name, value, entityId, collection),
455
+ name,
456
+ value,
457
+ property,
458
+ entityId
459
+ }) => dataSource.checkUniqueField(fullPath, name, value, entityId, collection),
345
460
  [fullPath]);
346
461
 
347
462
  const onValueChange: OnCellValueChange<any, any> = ({
348
- value,
349
- propertyKey,
350
- onValueUpdated,
351
- setError,
352
- data: entity,
353
- }) => {
463
+ value,
464
+ propertyKey,
465
+ onValueUpdated,
466
+ setError,
467
+ data: entity,
468
+ }) => {
354
469
 
355
470
  const updatedValues = setIn({ ...entity.values }, propertyKey, value);
356
471
 
@@ -394,10 +509,88 @@ export const EntityCollectionView = React.memo(
394
509
  authController,
395
510
  }), [collection, fullPath]);
396
511
 
512
+ // Check if Kanban view is possible (collection has at least one string enum property)
513
+ const hasEnumProperty = useMemo(() => {
514
+ const properties = resolvedCollection.properties;
515
+ return Object.values(properties).some((prop: any) =>
516
+ prop && prop.dataType === "string" && prop.enumValues
517
+ );
518
+ }, [resolvedCollection.properties]);
519
+
520
+ // Compute the effective enabled views:
521
+ // - Start from collection.enabledViews (defaults to all three)
522
+ // - Filter out kanban if no enum properties exist
523
+ const enabledViews: ViewMode[] = useMemo(() => {
524
+ const configured = collection.enabledViews ?? ["table", "cards", "kanban"];
525
+ if (!hasEnumProperty) {
526
+ return configured.filter(v => v !== "kanban");
527
+ }
528
+ return configured;
529
+ }, [collection.enabledViews, hasEnumProperty]);
530
+
531
+ // Compute available enum properties for kanban column selection
532
+ const kanbanPropertyOptions: KanbanPropertyOption[] = useMemo(() => {
533
+ const options: KanbanPropertyOption[] = [];
534
+ const properties = resolvedCollection.properties;
535
+
536
+ for (const [key, property] of Object.entries(properties)) {
537
+ const prop = property as any;
538
+ if (prop && prop.dataType === "string" && prop.enumValues) {
539
+ options.push({
540
+ key,
541
+ label: prop.name || key
542
+ });
543
+ }
544
+ }
545
+
546
+ return options;
547
+ }, [resolvedCollection.properties]);
548
+
549
+ // Get saved kanban property from user config
550
+ const getSavedKanbanProperty = useCallback((): string | undefined => {
551
+ const saved = userConfigPersistence?.getCollectionConfig<M>(fullPath);
552
+ return (saved as any)?.kanbanColumnProperty;
553
+ }, [userConfigPersistence, fullPath]);
554
+
555
+ // Selected kanban property state - priority: saved config > collection default > first available
556
+ const [selectedKanbanProperty, setSelectedKanbanProperty] = useState<string>(() => {
557
+ const saved = getSavedKanbanProperty();
558
+ if (saved && kanbanPropertyOptions.some(o => o.key === saved)) return saved;
559
+ if (collection.kanban?.columnProperty) return collection.kanban.columnProperty;
560
+ return kanbanPropertyOptions[0]?.key ?? "";
561
+ });
562
+
563
+ // Update selected property if options change and current selection is no longer valid
564
+ useEffect(() => {
565
+ if (kanbanPropertyOptions.length > 0 && !kanbanPropertyOptions.some(o => o.key === selectedKanbanProperty)) {
566
+ const saved = getSavedKanbanProperty();
567
+ if (saved && kanbanPropertyOptions.some(o => o.key === saved)) {
568
+ setSelectedKanbanProperty(saved);
569
+ } else if (collection.kanban?.columnProperty && kanbanPropertyOptions.some(o => o.key === collection.kanban?.columnProperty)) {
570
+ setSelectedKanbanProperty(collection.kanban.columnProperty);
571
+ } else {
572
+ setSelectedKanbanProperty(kanbanPropertyOptions[0]?.key ?? "");
573
+ }
574
+ }
575
+ }, [kanbanPropertyOptions, selectedKanbanProperty, getSavedKanbanProperty, collection.kanban?.columnProperty]);
576
+
577
+ // Handle kanban property change
578
+ const onKanbanPropertyChange = useCallback((property: string) => {
579
+ analyticsController.onAnalyticsEvent?.("kanban_property_changed", {
580
+ path: fullPath,
581
+ property
582
+ });
583
+ setSelectedKanbanProperty(property);
584
+ // Save to local persistence
585
+ if (userConfigPersistence) {
586
+ onCollectionModifiedForUser(fullPath, { kanbanColumnProperty: property } as any);
587
+ }
588
+ }, [userConfigPersistence, onCollectionModifiedForUser, fullPath, analyticsController]);
589
+
397
590
  const getPropertyFor = useCallback(({
398
- propertyKey,
399
- entity
400
- }: GetPropertyForProps<M>) => {
591
+ propertyKey,
592
+ entity
593
+ }: GetPropertyForProps<M>) => {
401
594
  let propertyOrBuilder: PropertyOrBuilder<any, M> | undefined = getPropertyInPath<M>(collection.properties, propertyKey);
402
595
 
403
596
  // we might not find the property in the collection if combining property builders and map spread
@@ -417,7 +610,18 @@ export const EntityCollectionView = React.memo(
417
610
  });
418
611
  }, [collection.properties, customizationController.propertyConfigs, resolvedCollection.properties]);
419
612
 
420
- const displayedColumnIds = useColumnIds(resolvedCollection, true);
613
+ // Use a collection with local propertiesOrder for optimistic UI updates
614
+ const collectionWithLocalOrder = useMemo(() => {
615
+ if (localPropertiesOrder && localPropertiesOrder !== resolvedCollection.propertiesOrder) {
616
+ return {
617
+ ...resolvedCollection,
618
+ propertiesOrder: localPropertiesOrder
619
+ };
620
+ }
621
+ return resolvedCollection;
622
+ }, [resolvedCollection, localPropertiesOrder]);
623
+
624
+ const displayedColumnIds = useColumnIds(collectionWithLocalOrder, true);
421
625
 
422
626
  const additionalFields = useMemo(() => {
423
627
  const subcollectionColumns: AdditionalFieldDelegate<M, any>[] = collection.subcollections?.map((subcollection) => {
@@ -427,23 +631,22 @@ export const EntityCollectionView = React.memo(
427
631
  width: 200,
428
632
  dependencies: [],
429
633
  Builder: ({ entity }) => (
430
- <Button color={"primary"}
431
- variant={"outlined"}
432
- className={"max-w-full truncate justify-start"}
433
- startIcon={<KeyboardTabIcon size={"small"}/>}
434
- onClick={(event: any) => {
435
- event.stopPropagation();
436
- navigateToEntity({
437
- openEntityMode,
438
- collection,
439
- entityId: entity.id,
440
- selectedTab: subcollection.id ?? subcollection.path,
441
- path: fullPath,
442
- fullIdPath,
443
- navigation,
444
- sideEntityController
445
- })
446
- }}>
634
+ <Button
635
+ className={"max-w-full truncate justify-start"}
636
+ startIcon={<KeyboardTabIcon size={"small"} />}
637
+ onClick={(event: any) => {
638
+ event.stopPropagation();
639
+ navigateToEntity({
640
+ openEntityMode,
641
+ collection,
642
+ entityId: entity.id,
643
+ selectedTab: subcollection.id ?? subcollection.path,
644
+ path: fullPath,
645
+ fullIdPath,
646
+ navigation,
647
+ sideEntityController
648
+ })
649
+ }}>
447
650
  {subcollection.name}
448
651
  </Button>
449
652
  )
@@ -465,7 +668,7 @@ export const EntityCollectionView = React.memo(
465
668
  <ReferencePreview
466
669
  key={reference.path + "/" + reference.id}
467
670
  reference={reference}
468
- size={"small"}/>
671
+ size={"small"} />
469
672
  );
470
673
  })}
471
674
  </div>
@@ -488,18 +691,20 @@ export const EntityCollectionView = React.memo(
488
691
  const largeLayout = useLargeLayout();
489
692
 
490
693
  const getActionsForEntity = ({
491
- entity,
492
- customEntityActions
493
- }: {
694
+ entity,
695
+ customEntityActions
696
+ }: {
494
697
  entity?: Entity<M>,
495
698
  customEntityActions?: EntityAction[]
496
699
  }): EntityAction[] => {
497
700
  const deleteEnabled = entity ? canDeleteEntity(collection, authController, fullPath, entity) : true;
498
- const actions: EntityAction[] = [editEntityAction];
701
+ const actions: EntityAction[] = [
702
+ { ...editEntityAction, name: t("edit") }
703
+ ];
499
704
  if (createEnabled)
500
- actions.push(copyEntityAction);
705
+ actions.push({ ...copyEntityAction, name: t("copy") });
501
706
  if (deleteEnabled)
502
- actions.push(deleteEntityAction);
707
+ actions.push({ ...deleteEntityAction, name: t("delete") });
503
708
  if (customEntityActions)
504
709
  return mergeEntityActions(actions, customEntityActions);
505
710
  return actions;
@@ -514,11 +719,11 @@ export const EntityCollectionView = React.memo(
514
719
  };
515
720
 
516
721
  const tableRowActionsBuilder = useCallback(({
517
- entity,
518
- size,
519
- width,
520
- frozen
521
- }: {
722
+ entity,
723
+ size,
724
+ width,
725
+ frozen
726
+ }: {
522
727
  entity: Entity<any>,
523
728
  size: CollectionSize,
524
729
  width: number,
@@ -558,45 +763,28 @@ export const EntityCollectionView = React.memo(
558
763
 
559
764
  }, [updateLastDeleteTimestamp, usedSelectionController]);
560
765
 
561
- const title = <Popover
562
- open={popOverOpen}
563
- onOpenChange={setPopOverOpen}
564
- enabled={Boolean(collection.description)}
565
- trigger={<div className="flex flex-col items-start">
566
- <Typography
567
- variant={"subtitle1"}
568
- className={`leading-none truncate max-w-[160px] lg:max-w-[240px] ${collection.description ? "cursor-pointer" : "cursor-auto"}`}
569
- onClick={collection.description
570
- ? (e) => {
571
- setPopOverOpen(true);
572
- e.stopPropagation();
573
- }
574
- : undefined}>
575
- {`${collection.name}`}
576
- </Typography>
577
-
578
- <EntitiesCount
579
- fullPath={fullPath}
580
- collection={collection}
581
- filter={tableController.filterValues}
582
- sortBy={tableController.sortBy}
583
- onCountChange={setDocsCount}
584
- />
585
-
586
- </div>}
587
- >
766
+ // Update breadcrumb count when count changes (only if loaded)
767
+ useEffect(() => {
768
+ if (docsCount !== undefined) {
769
+ breadcrumbs.updateCount(fullPath, docsCount);
770
+ }
771
+ }, [docsCount, fullPath, breadcrumbs.updateCount]);
588
772
 
589
- {collection.description && <div className="m-4 text-surface-900 dark:text-white">
590
- <Markdown source={collection.description}/>
591
- </div>}
773
+ // EntitiesCount fetches count and updates breadcrumb - no visual rendering needed here
774
+ const countFetcher = <EntitiesCount
775
+ fullPath={fullPath}
776
+ collection={collection}
777
+ filter={tableController.filterValues}
778
+ sortBy={tableController.sortBy}
779
+ onCountChange={setDocsCount}
780
+ />;
592
781
 
593
- </Popover>;
594
782
 
595
783
  const buildAdditionalHeaderWidget = useCallback(({
596
- property,
597
- propertyKey,
598
- onHover
599
- }: {
784
+ property,
785
+ propertyKey,
786
+ onHover
787
+ }: {
600
788
  property: ResolvedProperty,
601
789
  propertyKey: string,
602
790
  onHover: boolean
@@ -616,7 +804,7 @@ export const EntityCollectionView = React.memo(
616
804
  fullPath={fullPath}
617
805
  collection={collection}
618
806
  tableController={tableController}
619
- parentCollectionIds={parentCollectionIds ?? []}/>;
807
+ parentCollectionIds={parentCollectionIds ?? []} />;
620
808
  })}
621
809
  </>;
622
810
  }, [customizationController.plugins, fullPath, parentCollectionIds]);
@@ -625,9 +813,9 @@ export const EntityCollectionView = React.memo(
625
813
  ? function () {
626
814
  if (typeof AddColumnComponent === "function")
627
815
  return <AddColumnComponent fullPath={fullPath}
628
- parentCollectionIds={parentCollectionIds ?? []}
629
- collection={collection}
630
- tableController={tableController}/>;
816
+ parentCollectionIds={parentCollectionIds ?? []}
817
+ collection={collection}
818
+ tableController={tableController} />;
631
819
  return null;
632
820
  }
633
821
  : undefined;
@@ -643,32 +831,55 @@ export const EntityCollectionView = React.memo(
643
831
  parentCollectionIds
644
832
  });
645
833
 
834
+ // Popover open state managed at parent level to prevent closing when view changes
835
+ const [viewModePopoverOpen, setViewModePopoverOpen] = useState(false);
836
+
837
+ // Create ViewModeToggle once to prevent remounting when view changes
838
+ const viewModeToggleElement = (
839
+ <ViewModeToggle
840
+ viewMode={viewMode}
841
+ onViewModeChange={onViewModeChange}
842
+ enabledViews={enabledViews}
843
+ size={viewMode === "table" ? tableSize : viewMode === "cards" ? cardSize : undefined}
844
+ onSizeChanged={viewMode === "table" ? onTableSizeChanged : viewMode === "cards" ? setCardSize : undefined}
845
+ open={viewModePopoverOpen}
846
+ onOpenChange={setViewModePopoverOpen}
847
+ kanbanPropertyOptions={kanbanPropertyOptions}
848
+ selectedKanbanProperty={selectedKanbanProperty}
849
+ onKanbanPropertyChange={onKanbanPropertyChange}
850
+ />
851
+ );
852
+
853
+ // Compute plugin-provided error view for collection loading errors
854
+ const pluginErrorView = useMemo(() => {
855
+ const error = tableController.dataLoadingError;
856
+ if (!error || !customizationController.plugins) return null;
857
+ for (const plugin of customizationController.plugins) {
858
+ if (plugin.collectionView?.CollectionError) {
859
+ const CollectionError = plugin.collectionView.CollectionError;
860
+ return <CollectionError
861
+ path={fullPath}
862
+ collection={collection}
863
+ parentCollectionIds={parentCollectionIds}
864
+ error={error}
865
+ />;
866
+ }
867
+ }
868
+ return null;
869
+ }, [tableController.dataLoadingError, customizationController.plugins, fullPath, collection, parentCollectionIds]);
870
+
646
871
  return (
647
- <div className={cls("overflow-hidden h-full w-full rounded-md", className)}
648
- ref={containerRef}>
649
- <EntityCollectionTable
650
- key={`collection_table_${fullPath}`}
651
- additionalFields={additionalFields}
652
- tableController={tableController}
653
- enablePopupIcon={true}
654
- displayedColumnIds={displayedColumnIds}
655
- onSizeChanged={onSizeChanged}
656
- onEntityClick={onEntityClick}
657
- onColumnResize={onColumnResize}
658
- onValueChange={onValueChange}
659
- tableRowActionsBuilder={tableRowActionsBuilder}
660
- uniqueFieldValidator={uniqueFieldValidator}
661
- title={title}
662
- selectionController={usedSelectionController}
663
- highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
664
- defaultSize={collection.defaultSize}
665
- properties={resolvedCollection.properties}
666
- getPropertyFor={getPropertyFor}
667
- onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
668
- onScroll={tableController.onScroll}
669
- initialScroll={tableController.initialScroll}
872
+ <div className={cls("overflow-hidden h-full w-full rounded-md flex flex-col", className)}
873
+ ref={containerRef}>
874
+
875
+ {/* Unified toolbar - rendered once, outside view conditionals */}
876
+ {countFetcher}
877
+ <CollectionTableToolbar
878
+ loading={tableController.dataLoading}
879
+ onTextSearch={textSearchEnabled && textSearchInitialised ? tableController.setSearchString : undefined}
880
+ onTextSearchClick={textSearchEnabled && !textSearchInitialised ? onTextSearchClick : undefined}
670
881
  textSearchLoading={textSearchLoading}
671
- textSearchEnabled={textSearchEnabled}
882
+ viewModeToggle={viewModeToggleElement}
672
883
  actionsStart={<EntityCollectionViewStartActions
673
884
  parentCollectionIds={parentCollectionIds ?? []}
674
885
  collection={collection}
@@ -676,7 +887,8 @@ export const EntityCollectionView = React.memo(
676
887
  path={fullPath}
677
888
  relativePath={collection.path}
678
889
  selectionController={usedSelectionController}
679
- collectionEntitiesCount={docsCount}/>}
890
+ collectionEntitiesCount={docsCount}
891
+ resolvedProperties={resolvedCollection.properties} />}
680
892
  actions={<EntityCollectionViewActions
681
893
  parentCollectionIds={parentCollectionIds ?? []}
682
894
  collection={collection}
@@ -689,33 +901,145 @@ export const EntityCollectionView = React.memo(
689
901
  selectionEnabled={selectionEnabled}
690
902
  collectionEntitiesCount={docsCount}
691
903
  />}
692
- emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
693
- ? <div className="flex flex-col items-center justify-center">
694
- <Typography variant={"subtitle2"}>So empty...</Typography>
695
- <Button
696
- color={"primary"}
697
- variant={"outlined"}
698
- onClick={onNewClick}
699
- className="mt-4"
700
- >
701
- <AddIcon/>
702
- Create your first entry
703
- </Button>
704
- </div>
705
- : <Typography variant={"label"}>No results with the applied filter/sort</Typography>
706
- }
707
- hoverRow={hoverRow}
708
- inlineEditing={checkInlineEditing()}
709
- AdditionalHeaderWidget={buildAdditionalHeaderWidget}
710
- AddColumnComponent={addColumnComponentInternal}
711
- getIdColumnWidth={getIdColumnWidth}
712
- additionalIDHeaderWidget={<EntityIdHeaderWidget
713
- path={fullPath}
714
- fullIdPath={fullIdPath ?? fullPath}
715
- collection={collection}/>}
716
- openEntityMode={openEntityMode}
717
904
  />
718
905
 
906
+ {/* View content - only the view-specific content changes */}
907
+ {tableController.dataLoadingError ? (
908
+ pluginErrorView ?? <CollectionDataErrorBanner error={tableController.dataLoadingError} />
909
+ ) : viewMode === "kanban" && enabledViews.includes("kanban") ? (
910
+ <EntityCollectionBoardView
911
+ key={`kanban-view-${fullPath}-${selectedKanbanProperty}`}
912
+ collection={collection}
913
+ tableController={tableController}
914
+ fullPath={fullPath}
915
+ parentCollectionIds={parentCollectionIds}
916
+ columnProperty={selectedKanbanProperty}
917
+ onEntityClick={onEntityClick}
918
+ selectionController={usedSelectionController}
919
+ selectionEnabled={selectionEnabled}
920
+ highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
921
+ deletedEntities={deletedEntities}
922
+ emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
923
+ ? <div className="flex flex-col items-center justify-center">
924
+ <Typography variant={"subtitle2"}>{t("so_empty")}</Typography>
925
+ <Button
926
+ onClick={onNewClick}
927
+ className="mt-4"
928
+ >
929
+ <AddIcon />
930
+ {t("create_your_first_entry")}
931
+ </Button>
932
+ </div>
933
+ : <Typography variant={"label"}>{t("no_results_filter_sort")}</Typography>
934
+ }
935
+ />
936
+ ) : viewMode === "cards" ? (
937
+ <EntityCollectionCardView
938
+ key={`cards-view-${fullPath}`}
939
+ collection={collection}
940
+ tableController={tableController}
941
+ onEntityClick={onEntityClick}
942
+ selectionController={usedSelectionController}
943
+ selectionEnabled={selectionEnabled}
944
+ highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
945
+ onScroll={tableController.onScroll}
946
+ initialScroll={tableController.initialScroll}
947
+ size={cardSize}
948
+ emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
949
+ ? <div className="flex flex-col items-center justify-center">
950
+ <Typography variant={"subtitle2"}>{t("so_empty")}</Typography>
951
+ <Button
952
+ onClick={onNewClick}
953
+ className="mt-4"
954
+ >
955
+ <AddIcon />
956
+ {t("create_your_first_entry")}
957
+ </Button>
958
+ </div>
959
+ : <Typography variant={"label"}>{t("no_results_filter_sort")}</Typography>
960
+ }
961
+ />
962
+ ) : (
963
+ <EntityCollectionTable
964
+ key={`collection_table_${fullPath}`}
965
+ hideToolbar={true}
966
+ additionalFields={additionalFields}
967
+ tableController={tableController}
968
+ enablePopupIcon={true}
969
+ displayedColumnIds={displayedColumnIds}
970
+ onSizeChanged={onTableSizeChanged}
971
+ onEntityClick={onEntityClick}
972
+ onColumnResize={onColumnResize}
973
+ onValueChange={onValueChange}
974
+ tableRowActionsBuilder={tableRowActionsBuilder}
975
+ uniqueFieldValidator={uniqueFieldValidator}
976
+ selectionController={usedSelectionController}
977
+ highlightedEntities={highlightedEntity ? [highlightedEntity] : []}
978
+ defaultSize={tableSize}
979
+ properties={resolvedCollection.properties}
980
+ getPropertyFor={getPropertyFor}
981
+ onTextSearchClick={textSearchInitialised ? undefined : onTextSearchClick}
982
+ onScroll={tableController.onScroll}
983
+ initialScroll={tableController.initialScroll}
984
+ textSearchLoading={textSearchLoading}
985
+ textSearchEnabled={textSearchEnabled}
986
+ emptyComponent={canCreateEntities && tableController.filterValues === undefined && tableController.sortBy === undefined
987
+ ? <div className="flex flex-col items-center justify-center">
988
+ <Typography variant={"subtitle2"}>{t("so_empty")}</Typography>
989
+ <Button
990
+ onClick={onNewClick}
991
+ className="mt-4"
992
+ >
993
+ <AddIcon />
994
+ {t("create_your_first_entry")}
995
+ </Button>
996
+ </div>
997
+ : <Typography variant={"label"}>{t("no_results_filter_sort")}</Typography>
998
+ }
999
+ hoverRow={hoverRow}
1000
+ inlineEditing={checkInlineEditing()}
1001
+ AdditionalHeaderWidget={buildAdditionalHeaderWidget}
1002
+ AddColumnComponent={addColumnComponentInternal}
1003
+ getIdColumnWidth={getIdColumnWidth}
1004
+ additionalIDHeaderWidget={<EntityIdHeaderWidget
1005
+ path={fullPath}
1006
+ fullIdPath={fullIdPath ?? fullPath}
1007
+ collection={collection} />}
1008
+ openEntityMode={openEntityMode}
1009
+ onColumnsOrderChange={(newColumns) => {
1010
+ // Extract property keys from the new column order
1011
+ // Filter to only include actual property columns (not frozen columns, not additional fields, etc.)
1012
+ // Deduplicate to clean up any previously duplicated keys
1013
+ const seenKeys = new Set<string>();
1014
+ const newPropertiesOrder = newColumns
1015
+ .filter(col => !col.frozen && getPropertyInPath(collection.properties, col.key))
1016
+ .map(col => col.key)
1017
+ .filter(key => {
1018
+ if (seenKeys.has(key)) return false;
1019
+ seenKeys.add(key);
1020
+ return true;
1021
+ });
1022
+
1023
+ // Optimistically update local state to prevent UI flickering
1024
+ setLocalPropertiesOrder(newPropertiesOrder);
1025
+
1026
+ // Call each plugin's onColumnsReorder callback
1027
+ if (customizationController?.plugins) {
1028
+ customizationController.plugins
1029
+ .filter(plugin => plugin.collectionView?.onColumnsReorder)
1030
+ .forEach(plugin => {
1031
+ plugin.collectionView!.onColumnsReorder!({
1032
+ fullPath,
1033
+ parentCollectionIds: parentCollectionIds ?? [],
1034
+ collection,
1035
+ newPropertiesOrder
1036
+ });
1037
+ });
1038
+ }
1039
+ }}
1040
+ />
1041
+ )}
1042
+
719
1043
  {popupCell && <PopupFormField
720
1044
  key={`popup_form_${popupCell?.propertyKey}_${popupCell?.entityId}`}
721
1045
  open={Boolean(popupCell)}
@@ -728,7 +1052,7 @@ export const EntityCollectionView = React.memo(
728
1052
  customFieldValidator={uniqueFieldValidator}
729
1053
  path={resolvedFullPath}
730
1054
  onCellValueChange={onValueChange}
731
- container={containerRef.current}/>}
1055
+ container={containerRef.current} />}
732
1056
 
733
1057
  {deleteEntityClicked &&
734
1058
  <DeleteEntityDialog
@@ -739,7 +1063,7 @@ export const EntityCollectionView = React.memo(
739
1063
  open={Boolean(deleteEntityClicked)}
740
1064
  onEntityDelete={internalOnEntityDelete}
741
1065
  onMultipleEntitiesDelete={internalOnMultipleEntitiesDelete}
742
- onClose={() => setDeleteEntityClicked(undefined)}/>}
1066
+ onClose={() => setDeleteEntityClicked(undefined)} />}
743
1067
 
744
1068
  </div>
745
1069
  );
@@ -769,12 +1093,12 @@ export const EntityCollectionView = React.memo(
769
1093
  }) as React.FunctionComponent<EntityCollectionViewProps<any>>
770
1094
 
771
1095
  function EntitiesCount({
772
- fullPath,
773
- collection,
774
- filter,
775
- sortBy,
776
- onCountChange
777
- }: {
1096
+ fullPath,
1097
+ collection,
1098
+ filter,
1099
+ sortBy,
1100
+ onCountChange
1101
+ }: {
778
1102
  fullPath: string,
779
1103
  collection: EntityCollection,
780
1104
  filter?: FilterValues<any>,
@@ -803,9 +1127,9 @@ function EntitiesCount({
803
1127
  }, [fullPath, dataSource.countEntities, resolvedPath, collection, filter, sortByProperty, currentSort]);
804
1128
 
805
1129
  useEffect(() => {
806
- if (onCountChange) {
1130
+ if (onCountChange && count !== undefined) {
807
1131
  setError(undefined);
808
- onCountChange(count ?? 0);
1132
+ onCountChange(count);
809
1133
  }
810
1134
  }, [onCountChange, count]);
811
1135
 
@@ -813,14 +1137,11 @@ function EntitiesCount({
813
1137
  return null;
814
1138
  }
815
1139
 
816
- return <Typography
817
- className="w-full text-ellipsis block overflow-hidden whitespace-nowrap max-w-xs text-left w-fit-content"
818
- variant={"caption"}
819
- color={"secondary"}>
820
- {count !== undefined ? `${count} entities` : <Skeleton className={"w-full max-w-[80px] mt-1"}/>}
821
- </Typography>;
1140
+ // Count is now displayed in the breadcrumb bar, this component only fetches and reports
1141
+ return null;
822
1142
  }
823
1143
 
1144
+
824
1145
  function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityCollection {
825
1146
  if (key.includes(".")) {
826
1147
  const [parentKey, ...childKey] = key.split(".");
@@ -830,10 +1151,10 @@ function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityC
830
1151
  }
831
1152
 
832
1153
  function EntityIdHeaderWidget({
833
- collection,
834
- path,
835
- fullIdPath
836
- }: {
1154
+ collection,
1155
+ path,
1156
+ fullIdPath
1157
+ }: {
837
1158
  collection: EntityCollection,
838
1159
  path: string,
839
1160
  fullIdPath: string
@@ -844,11 +1165,12 @@ function EntityIdHeaderWidget({
844
1165
  const [searchString, setSearchString] = React.useState("");
845
1166
  const [recentIds, setRecentIds] = React.useState<string[]>(getRecentIds(collection.id));
846
1167
  const sideEntityController = useSideEntityController();
1168
+ const { t } = useTranslation();
847
1169
 
848
1170
  const openEntityMode = collection?.openEntityMode ?? DEFAULT_ENTITY_OPEN_MODE;
849
1171
 
850
1172
  return (
851
- <Tooltip title={!openPopup ? "Find by ID" : undefined} asChild={false}>
1173
+ <Tooltip title={!openPopup ? t("find_by_id") : undefined} asChild={false}>
852
1174
  <Popover
853
1175
  open={openPopup}
854
1176
  onOpenChange={setOpenPopup}
@@ -857,65 +1179,65 @@ function EntityIdHeaderWidget({
857
1179
  alignOffset={-117}
858
1180
  trigger={
859
1181
  <IconButton size={"small"}>
860
- <SearchIcon size={"small"}/>
1182
+ <SearchIcon size={"small"} />
861
1183
  </IconButton>
862
1184
  }>
863
1185
  <div
864
1186
  className={cls("my-2 rounded-lg bg-surface-50 dark:bg-surface-950 text-surface-900 dark:text-white")}>
865
1187
  <form noValidate={true}
866
- onSubmit={(e) => {
867
- e.preventDefault();
868
- if (!searchString) return;
869
- setOpenPopup(false);
870
- const entityId = searchString.trim();
871
- setRecentIds(addRecentId(collection.id, entityId));
872
- navigateToEntity({
873
- openEntityMode,
874
- collection,
875
- entityId,
876
- path,
877
- fullIdPath,
878
- sideEntityController,
879
- navigation
880
- })
881
- }}
882
- className={"w-96 max-w-full"}>
1188
+ onSubmit={(e) => {
1189
+ e.preventDefault();
1190
+ if (!searchString) return;
1191
+ setOpenPopup(false);
1192
+ const entityId = searchString.trim();
1193
+ setRecentIds(addRecentId(collection.id, entityId));
1194
+ navigateToEntity({
1195
+ openEntityMode,
1196
+ collection,
1197
+ entityId,
1198
+ path,
1199
+ fullIdPath,
1200
+ sideEntityController,
1201
+ navigation
1202
+ })
1203
+ }}
1204
+ className={"w-96 max-w-full"}>
883
1205
 
884
1206
  <div className="flex p-2 w-full gap-2">
885
1207
  <input
886
1208
  autoFocus={openPopup}
887
- placeholder={"Find entity by ID"}
1209
+ placeholder={t("find_entity_by_id")}
888
1210
  // size={"small"}
889
1211
  onChange={(e) => {
890
1212
  setSearchString(e.target.value);
891
1213
  }}
892
1214
  value={searchString}
893
- className={"rounded-lg bg-white dark:bg-surface-800 flex-grow bg-transparent outline-none p-2 " + focusedDisabled}/>
1215
+ className={"rounded-lg bg-white dark:bg-surface-800 flex-grow bg-transparent outline-none p-2 " + focusedDisabled} />
894
1216
  <Button variant={"text"}
895
- disabled={!(searchString.trim())}
896
- type={"submit"}
897
- ><KeyboardTabIcon/></Button>
1217
+ disabled={!(searchString.trim())}
1218
+ type={"submit"}
1219
+ ><KeyboardTabIcon /></Button>
898
1220
  </div>
899
1221
  </form>
900
1222
  {recentIds && recentIds.length > 0 && <div className="flex flex-col gap-2 p-2">
901
1223
  {recentIds.map(id => (
902
1224
  <ReferencePreview reference={new EntityReference(id, path)}
903
- key={id}
904
- hover={true}
905
- onClick={() => {
906
- setOpenPopup(false);
907
- navigateToEntity({
908
- openEntityMode,
909
- collection,
910
- entityId: id,
911
- path,
912
- fullIdPath,
913
- sideEntityController,
914
- navigation
915
- })
916
- }}
917
- includeEntityLink={false}
918
- size={"small"}/>
1225
+ key={id}
1226
+ hover={true}
1227
+ onClick={() => {
1228
+ setOpenPopup(false);
1229
+ navigateToEntity({
1230
+ openEntityMode,
1231
+ collection,
1232
+ entityId: id,
1233
+ path,
1234
+ fullIdPath,
1235
+ sideEntityController,
1236
+ navigation
1237
+ })
1238
+ }}
1239
+ includeEntityLink={false}
1240
+ size={"small"} />
919
1241
  ))}
920
1242
  </div>}
921
1243
  </div>