@rebasepro/admin 0.6.1 → 0.8.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 (187) hide show
  1. package/dist/{CollectionEditorDialog-HNXGxXdO.js → CollectionEditorDialog-BO_aQ-Z4.js} +236 -155
  2. package/dist/CollectionEditorDialog-BO_aQ-Z4.js.map +1 -0
  3. package/dist/{PropertyEditView-C2wp8kQ9.js → PropertyEditView-DKzOpMjR.js} +190 -50
  4. package/dist/PropertyEditView-DKzOpMjR.js.map +1 -0
  5. package/dist/{CollectionsStudioView-B1mN33GB.js → RouterCollectionsStudioView-BQTIExjA.js} +61 -39
  6. package/dist/RouterCollectionsStudioView-BQTIExjA.js.map +1 -0
  7. package/dist/collection_editor/_cms_internals.d.ts +1 -1
  8. package/dist/collection_editor/api/generateCollectionApi.d.ts +3 -2
  9. package/dist/collection_editor/extensibility_types.d.ts +135 -0
  10. package/dist/collection_editor/index.d.ts +6 -0
  11. package/dist/collection_editor/serializable_types.d.ts +370 -0
  12. package/dist/collection_editor/serializable_utils.d.ts +50 -0
  13. package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +3 -3
  14. package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +12 -1
  15. package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +13 -1
  16. package/dist/collection_editor/ui/collection_editor/CollectionsStudioView.d.ts +24 -2
  17. package/dist/collection_editor/ui/collection_editor/DisplaySettingsForm.d.ts +2 -1
  18. package/dist/collection_editor/ui/collection_editor/GeneralSettingsForm.d.ts +4 -1
  19. package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +12 -1
  20. package/dist/collection_editor/ui/collection_editor/RouterCollectionStudioView.d.ts +15 -0
  21. package/dist/collection_editor/ui/collection_editor/RouterCollectionsStudioView.d.ts +14 -0
  22. package/dist/collection_editor/useJsonCollectionsConfigController.d.ts +37 -0
  23. package/dist/collection_editor/useSafeSnackbarController.d.ts +2 -0
  24. package/dist/collection_editor_ui.d.ts +4 -0
  25. package/dist/collection_editor_ui.js +33 -4
  26. package/dist/collection_editor_ui.js.map +1 -0
  27. package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
  28. package/dist/components/EntityCollectionTable/index.d.ts +1 -1
  29. package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +3 -3
  30. package/dist/components/EntityCollectionView/hooks/useKanbanDragAndDrop.d.ts +3 -3
  31. package/dist/components/EntityEditView.d.ts +3 -2
  32. package/dist/components/RebaseCMS.d.ts +1 -1
  33. package/dist/components/SelectableTable/SelectionStore.d.ts +12 -21
  34. package/dist/components/field_configs.d.ts +1 -1
  35. package/dist/editor/editor.d.ts +0 -6
  36. package/dist/editor.js +1 -3
  37. package/dist/editor.js.map +1 -1
  38. package/dist/form/EntityForm.d.ts +10 -9
  39. package/dist/form/EntityFormBinding.d.ts +27 -0
  40. package/dist/form/components/StorageUploadProgress.d.ts +6 -1
  41. package/dist/form/form_utils.d.ts +8 -0
  42. package/dist/form/index.d.ts +5 -2
  43. package/dist/{history-BO6VT85a.js → history-B3cqBzEV.js} +2 -2
  44. package/dist/{history-BO6VT85a.js.map → history-B3cqBzEV.js.map} +1 -1
  45. package/dist/hooks/navigation/contexts/CollectionRegistryContext.d.ts +1 -1
  46. package/dist/hooks/navigation/useAsyncResolver.d.ts +26 -0
  47. package/dist/hooks/navigation/useBuildCollectionRegistryController.d.ts +2 -0
  48. package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
  49. package/dist/hooks/navigation/utils.d.ts +1 -1
  50. package/dist/index.d.ts +3 -1
  51. package/dist/index.js +2 -2
  52. package/dist/preview/components/StorageThumbnail.d.ts +3 -1
  53. package/dist/routes/CustomViewRoute.d.ts +2 -1
  54. package/dist/{src-BYniefVu.js → src-cxV1ODRG.js} +1731 -1710
  55. package/dist/src-cxV1ODRG.js.map +1 -0
  56. package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
  57. package/dist/types/components/EntityFormProps.d.ts +59 -0
  58. package/dist/types/components/PropertyPreviewProps.d.ts +2 -1
  59. package/dist/types/fields.d.ts +10 -7
  60. package/dist/util/dataControllerAdapter.d.ts +30 -0
  61. package/dist/util/navigation_utils.d.ts +1 -1
  62. package/dist/util/propertyConfigMapper.d.ts +17 -0
  63. package/package.json +8 -8
  64. package/src/collection_editor/_cms_internals.ts +1 -1
  65. package/src/collection_editor/api/generateCollectionApi.ts +3 -2
  66. package/src/collection_editor/extensibility_types.ts +168 -0
  67. package/src/collection_editor/index.ts +46 -0
  68. package/src/collection_editor/pgColumnToProperty.ts +2 -2
  69. package/src/collection_editor/serializable_types.ts +474 -0
  70. package/src/collection_editor/serializable_utils.ts +576 -0
  71. package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +6 -6
  72. package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +170 -89
  73. package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +95 -78
  74. package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +23 -20
  75. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +79 -27
  76. package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +9 -7
  77. package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +22 -5
  78. package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +3 -3
  79. package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +154 -18
  80. package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
  81. package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -0
  82. package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +44 -0
  83. package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +41 -0
  84. package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +4 -3
  85. package/src/collection_editor/ui/collection_editor/properties/StoragePropertyField.tsx +35 -1
  86. package/src/collection_editor/ui/collection_editor/templates/blog_template.ts +1 -1
  87. package/src/collection_editor/ui/collection_editor/utils/supported_fields.tsx +0 -1
  88. package/src/collection_editor/ui/collection_editor/utils/update_property_for_widget.ts +7 -29
  89. package/src/collection_editor/useJsonCollectionsConfigController.tsx +286 -0
  90. package/src/collection_editor/useSafeSnackbarController.ts +9 -0
  91. package/src/collection_editor_ui.ts +6 -0
  92. package/src/components/DefaultDrawer.tsx +14 -4
  93. package/src/components/DrawerNavigationGroup.tsx +2 -2
  94. package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +11 -9
  95. package/src/components/EntityCollectionTable/fields/TableMultipleRelationField.tsx +2 -1
  96. package/src/components/EntityCollectionTable/fields/TableRelationField.tsx +2 -1
  97. package/src/components/EntityCollectionTable/fields/TableStorageUpload.tsx +23 -20
  98. package/src/components/EntityCollectionTable/index.tsx +1 -1
  99. package/src/components/EntityCollectionTable/table_bindings.tsx +24 -44
  100. package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
  101. package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +13 -12
  102. package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +40 -190
  103. package/src/components/EntityCollectionView/EntityCollectionListView.tsx +66 -184
  104. package/src/components/EntityCollectionView/EntityCollectionView.tsx +8 -25
  105. package/src/components/EntityCollectionView/hooks/useCollectionInlineEditor.ts +1 -1
  106. package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +6 -6
  107. package/src/components/EntityCollectionView/useBoardDataController.tsx +12 -40
  108. package/src/components/EntityCollectionView/useEntityPreviewSlots.ts +3 -3
  109. package/src/components/EntityDetailView.tsx +2 -2
  110. package/src/components/EntityEditView.tsx +7 -6
  111. package/src/components/EntityEditViewFormActions.tsx +35 -29
  112. package/src/components/HomePage/ContentHomePage.tsx +14 -5
  113. package/src/components/RebaseCMS.tsx +3 -3
  114. package/src/components/RebaseNavigation.tsx +74 -9
  115. package/src/components/RelationSelector.tsx +2 -2
  116. package/src/components/SelectableTable/SelectableTable.tsx +13 -5
  117. package/src/components/SelectableTable/SelectionStore.ts +18 -48
  118. package/src/components/SideDialogs.tsx +0 -7
  119. package/src/components/common/default_entity_actions.tsx +22 -11
  120. package/src/components/field_configs.tsx +0 -14
  121. package/src/contexts/BreacrumbsContext.tsx +15 -6
  122. package/src/editor/editor.tsx +4 -7
  123. package/src/editor/selectors/ai-selector.tsx +0 -1
  124. package/src/form/EntityForm.tsx +191 -428
  125. package/src/form/EntityFormActions.tsx +35 -49
  126. package/src/form/EntityFormBinding.tsx +323 -0
  127. package/src/form/components/FormLayout.tsx +1 -1
  128. package/src/form/components/StorageUploadProgress.tsx +9 -2
  129. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +12 -1
  130. package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +4 -4
  131. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
  132. package/src/form/field_bindings/RelationFieldBinding.tsx +5 -7
  133. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +19 -15
  134. package/src/form/form_utils.ts +163 -0
  135. package/src/form/index.tsx +14 -6
  136. package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -2
  137. package/src/hooks/navigation/contexts/NavigationStateContext.tsx +2 -2
  138. package/src/hooks/navigation/contexts/UrlContext.tsx +1 -2
  139. package/src/hooks/navigation/useAsyncResolver.ts +100 -0
  140. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -8
  141. package/src/hooks/navigation/useBuildNavigationStateController.tsx +12 -8
  142. package/src/hooks/navigation/useNavigationResolution.ts +33 -0
  143. package/src/hooks/navigation/useResolvedCollections.ts +29 -71
  144. package/src/hooks/navigation/useResolvedViews.tsx +40 -70
  145. package/src/hooks/navigation/useTopLevelNavigation.ts +20 -16
  146. package/src/hooks/navigation/utils.ts +7 -4
  147. package/src/index.ts +6 -1
  148. package/src/preview/PropertyPreview.tsx +1 -16
  149. package/src/preview/components/StorageThumbnail.tsx +24 -7
  150. package/src/routes/CustomViewRoute.tsx +14 -6
  151. package/src/types/components/EntityFormActionsProps.tsx +1 -1
  152. package/src/types/components/EntityFormProps.tsx +68 -0
  153. package/src/types/components/PropertyPreviewProps.tsx +1 -1
  154. package/src/types/fields.tsx +9 -8
  155. package/src/util/dataControllerAdapter.ts +93 -0
  156. package/src/util/navigation_utils.ts +1 -1
  157. package/src/util/previews.ts +20 -0
  158. package/src/util/propertyConfigMapper.ts +134 -0
  159. package/src/util/property_utils.tsx +1 -2
  160. package/dist/CollectionEditorDialog-HNXGxXdO.js.map +0 -1
  161. package/dist/CollectionsStudioView-B1mN33GB.js.map +0 -1
  162. package/dist/PropertyEditView-C2wp8kQ9.js.map +0 -1
  163. package/dist/components/EntityCollectionTable/fields/VirtualTableDateField.d.ts +0 -12
  164. package/dist/components/EntityCollectionTable/fields/VirtualTableInput.d.ts +0 -10
  165. package/dist/components/EntityCollectionTable/fields/VirtualTableNumberInput.d.ts +0 -9
  166. package/dist/components/EntityCollectionTable/fields/VirtualTableSwitch.d.ts +0 -8
  167. package/dist/components/EntityCollectionView/Board.d.ts +0 -3
  168. package/dist/components/EntityCollectionView/BoardColumn.d.ts +0 -42
  169. package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +0 -9
  170. package/dist/components/EntityCollectionView/BoardSortableList.d.ts +0 -14
  171. package/dist/components/EntityCollectionView/board_types.d.ts +0 -105
  172. package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +0 -11
  173. package/dist/hooks/navigation/useNavigationRegistry.d.ts +0 -12
  174. package/dist/hooks/navigation/useNavigationURLs.d.ts +0 -11
  175. package/dist/src-BYniefVu.js.map +0 -1
  176. package/src/components/EntityCollectionTable/fields/VirtualTableDateField.tsx +0 -39
  177. package/src/components/EntityCollectionTable/fields/VirtualTableInput.tsx +0 -90
  178. package/src/components/EntityCollectionTable/fields/VirtualTableNumberInput.tsx +0 -83
  179. package/src/components/EntityCollectionTable/fields/VirtualTableSwitch.tsx +0 -32
  180. package/src/components/EntityCollectionView/Board.tsx +0 -475
  181. package/src/components/EntityCollectionView/BoardColumn.tsx +0 -165
  182. package/src/components/EntityCollectionView/BoardColumnTitle.tsx +0 -46
  183. package/src/components/EntityCollectionView/BoardSortableList.tsx +0 -177
  184. package/src/components/EntityCollectionView/board_types.ts +0 -113
  185. package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +0 -141
  186. package/src/hooks/navigation/useNavigationRegistry.ts +0 -164
  187. package/src/hooks/navigation/useNavigationURLs.ts +0 -56
@@ -14,10 +14,8 @@ export function updatePropertyFromWidget(propertyData: any,
14
14
  type: "string",
15
15
  propertyConfig: "text_field",
16
16
  storage: undefined,
17
- multiline: undefined,
18
- markdown: undefined,
17
+ ui: { multiline: undefined, markdown: undefined, url: undefined },
19
18
  email: undefined,
20
- url: undefined,
21
19
  enum: undefined,
22
20
  userSelect: undefined
23
21
  } as StringProperty
@@ -29,10 +27,8 @@ export function updatePropertyFromWidget(propertyData: any,
29
27
  type: "string",
30
28
  propertyConfig: "user_select",
31
29
  storage: undefined,
32
- multiline: undefined,
33
- markdown: undefined,
30
+ ui: { multiline: undefined, markdown: undefined, url: undefined },
34
31
  email: undefined,
35
- url: undefined,
36
32
  enum: undefined,
37
33
  userSelect: true
38
34
  } as StringProperty
@@ -43,11 +39,9 @@ export function updatePropertyFromWidget(propertyData: any,
43
39
  {
44
40
  type: "string",
45
41
  propertyConfig: "multiline",
46
- multiline: true,
42
+ ui: { multiline: true, markdown: undefined, url: undefined },
47
43
  storage: undefined,
48
- markdown: undefined,
49
44
  email: undefined,
50
- url: undefined,
51
45
  enum: undefined,
52
46
  userSelect: undefined
53
47
  } as StringProperty
@@ -59,10 +53,8 @@ export function updatePropertyFromWidget(propertyData: any,
59
53
  type: "string",
60
54
  propertyConfig: "markdown",
61
55
  storage: undefined,
62
- multiline: undefined,
63
- markdown: true,
56
+ ui: { multiline: undefined, markdown: true, url: undefined },
64
57
  email: undefined,
65
- url: undefined,
66
58
  userSelect: undefined
67
59
  } as StringProperty
68
60
  );
@@ -73,10 +65,8 @@ export function updatePropertyFromWidget(propertyData: any,
73
65
  type: "string",
74
66
  propertyConfig: "url",
75
67
  storage: undefined,
76
- multiline: undefined,
77
- markdown: undefined,
68
+ ui: { multiline: undefined, markdown: undefined, url: true },
78
69
  email: undefined,
79
- url: true,
80
70
  enum: undefined,
81
71
  userSelect: undefined
82
72
  } as StringProperty
@@ -88,10 +78,8 @@ export function updatePropertyFromWidget(propertyData: any,
88
78
  type: "string",
89
79
  propertyConfig: "email",
90
80
  storage: undefined,
91
- multiline: undefined,
92
- markdown: undefined,
81
+ ui: { multiline: undefined, markdown: undefined, url: undefined },
93
82
  email: true,
94
- url: undefined,
95
83
  enum: undefined,
96
84
  userSelect: undefined
97
85
  } as StringProperty
@@ -103,10 +91,8 @@ export function updatePropertyFromWidget(propertyData: any,
103
91
  type: "string",
104
92
  propertyConfig: "select",
105
93
  storage: undefined,
106
- multiline: undefined,
107
- markdown: undefined,
94
+ ui: { multiline: undefined, markdown: undefined, url: undefined },
108
95
  email: undefined,
109
- url: undefined,
110
96
  enum: propertyData.enum ?? [],
111
97
  userSelect: undefined
112
98
  } as StringProperty
@@ -206,14 +192,6 @@ export function updatePropertyFromWidget(propertyData: any,
206
192
  propertyConfig: "reference"
207
193
  } as Property
208
194
  );
209
- } else if (selectedWidgetId === "reference_as_string") {
210
- updatedProperty = mergeDeep(
211
- propertyData,
212
- {
213
- type: "string",
214
- propertyConfig: "reference_as_string"
215
- } as Property
216
- );
217
195
  } else if (selectedWidgetId === "multi_references") {
218
196
  updatedProperty = mergeDeep(
219
197
  propertyData,
@@ -0,0 +1,286 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import type { EngineProperties, EntityCollection, NavigationGroupMapping, Property } from "@rebasepro/types";
3
+ import type {
4
+ CollectionsConfigController,
5
+ SaveCollectionParams,
6
+ UpdateCollectionParams,
7
+ DeleteCollectionParams,
8
+ SavePropertyParams,
9
+ DeletePropertyParams,
10
+ UpdatePropertiesOrderParams,
11
+ UpdateKanbanColumnsOrderParams,
12
+ } from "./types/config_controller";
13
+ import type { JsonCollectionStore, SerializableCollection } from "./serializable_types";
14
+ import { toSerializableCollection, toSerializableProperty, fromSerializableCollection } from "./serializable_utils";
15
+
16
+ export interface UseJsonCollectionsConfigControllerOptions {
17
+ /**
18
+ * The store adapter for persisting/loading collections.
19
+ */
20
+ store: JsonCollectionStore;
21
+
22
+ /**
23
+ * If true, the configuration cannot be modified.
24
+ */
25
+ readOnly?: boolean;
26
+
27
+ /**
28
+ * If true, collections will be loaded from the store on mount.
29
+ * Defaults to true.
30
+ */
31
+ autoLoad?: boolean;
32
+ }
33
+
34
+ /**
35
+ * A `CollectionsConfigController` implementation that persists collection
36
+ * configurations as JSON via a pluggable `JsonCollectionStore` adapter.
37
+ *
38
+ * Use this when you want to store collection schemas in a database, API,
39
+ * localStorage, or any other JSON-compatible backend — as opposed to the
40
+ * default code-based persistence.
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * const store: JsonCollectionStore = {
45
+ * load: async () => { const res = await fetch('/api/schemas'); return res.json(); },
46
+ * save: async (slug, data) => { await fetch(`/api/schemas/${slug}`, { method: 'PUT', body: JSON.stringify(data) }); },
47
+ * delete: async (slug) => { await fetch(`/api/schemas/${slug}`, { method: 'DELETE' }); },
48
+ * };
49
+ *
50
+ * const configController = useJsonCollectionsConfigController({ store });
51
+ * ```
52
+ */
53
+ export function useJsonCollectionsConfigController({
54
+ store,
55
+ readOnly = false,
56
+ autoLoad = true,
57
+ }: UseJsonCollectionsConfigControllerOptions): CollectionsConfigController {
58
+ const [collections, setCollections] = useState<EntityCollection[]>([]);
59
+ const [loading, setLoading] = useState(autoLoad);
60
+ const [navigationEntries, setNavigationEntries] = useState<NavigationGroupMapping[]>([]);
61
+
62
+ // Keep store ref stable to avoid stale closures in callbacks
63
+ const storeRef = useRef(store);
64
+ storeRef.current = store;
65
+
66
+ // ── Load on mount ─────────────────────────────────────────────────
67
+ useEffect(() => {
68
+ if (!autoLoad) return;
69
+ let cancelled = false;
70
+
71
+ (async () => {
72
+ try {
73
+ const [serialized, navEntries] = await Promise.all([
74
+ storeRef.current.load(),
75
+ storeRef.current.loadNavigationEntries?.() ?? Promise.resolve([]),
76
+ ]);
77
+ if (cancelled) return;
78
+ setCollections(serialized.map(fromSerializableCollection));
79
+ setNavigationEntries(navEntries);
80
+ } catch (e) {
81
+ console.error("useJsonCollectionsConfigController: failed to load collections", e);
82
+ } finally {
83
+ if (!cancelled) setLoading(false);
84
+ }
85
+ })();
86
+
87
+ return () => { cancelled = true; };
88
+ }, [autoLoad]);
89
+
90
+ // ── Helpers ────────────────────────────────────────────────────────
91
+
92
+ /**
93
+ * Update local state and persist a collection to the store.
94
+ */
95
+ const persistCollection = useCallback(async (collection: EntityCollection) => {
96
+ const serializable = toSerializableCollection(collection);
97
+ await storeRef.current.save(collection.slug, serializable);
98
+ }, []);
99
+
100
+ /**
101
+ * Find collection by id (slug).
102
+ */
103
+ const getCollection = useCallback((id: string): EntityCollection => {
104
+ const found = collections.find(c => c.slug === id);
105
+ if (found) return found;
106
+ throw new Error(`Collection "${id}" not found`);
107
+ }, [collections]);
108
+
109
+ // ── CRUD operations ───────────────────────────────────────────────
110
+
111
+ const saveCollection = useCallback(async <M extends Record<string, unknown>>(
112
+ { id, collectionData }: SaveCollectionParams<M>
113
+ ) => {
114
+ const collection = collectionData as EntityCollection;
115
+ await persistCollection(collection);
116
+ setCollections(prev => {
117
+ const idx = prev.findIndex(c => c.slug === id);
118
+ if (idx >= 0) {
119
+ const next = [...prev];
120
+ next[idx] = collection;
121
+ return next;
122
+ }
123
+ return [...prev, collection];
124
+ });
125
+ }, [persistCollection]);
126
+
127
+ const updateCollection = useCallback(async <M extends Record<string, unknown>>(
128
+ { id, collectionData, previousId }: UpdateCollectionParams<M>
129
+ ) => {
130
+ setCollections(prev => {
131
+ const lookupId = previousId ?? id;
132
+ const idx = prev.findIndex(c => c.slug === lookupId);
133
+ if (idx < 0) return prev;
134
+
135
+ const merged = { ...prev[idx], ...collectionData } as EntityCollection;
136
+ const next = [...prev];
137
+ next[idx] = merged;
138
+
139
+ // Persist async — fire-and-forget with error logging
140
+ persistCollection(merged).catch(e =>
141
+ console.error("useJsonCollectionsConfigController: failed to update collection", e)
142
+ );
143
+
144
+ // If slug changed, clean up the old entry in the store
145
+ if (previousId && previousId !== id) {
146
+ storeRef.current.delete(previousId).catch(e =>
147
+ console.error("useJsonCollectionsConfigController: failed to delete old slug", e)
148
+ );
149
+ }
150
+
151
+ return next;
152
+ });
153
+ }, [persistCollection]);
154
+
155
+ const deleteCollection = useCallback(async ({ id }: DeleteCollectionParams) => {
156
+ await storeRef.current.delete(id);
157
+ setCollections(prev => prev.filter(c => c.slug !== id));
158
+ }, []);
159
+
160
+ const saveProperty = useCallback(async ({
161
+ path,
162
+ propertyKey,
163
+ property,
164
+ newPropertiesOrder,
165
+ }: SavePropertyParams) => {
166
+ setCollections(prev => {
167
+ const idx = prev.findIndex(c => c.slug === path);
168
+ if (idx < 0) return prev;
169
+
170
+ const collection = { ...prev[idx] };
171
+ collection.properties = {
172
+ ...collection.properties,
173
+ [propertyKey]: property,
174
+ } as EngineProperties;
175
+ if (newPropertiesOrder) {
176
+ collection.propertiesOrder = newPropertiesOrder as typeof collection.propertiesOrder;
177
+ }
178
+
179
+ const next = [...prev];
180
+ next[idx] = collection as EntityCollection;
181
+
182
+ persistCollection(next[idx]).catch(e =>
183
+ console.error("useJsonCollectionsConfigController: failed to save property", e)
184
+ );
185
+
186
+ return next;
187
+ });
188
+ }, [persistCollection]);
189
+
190
+ const deleteProperty = useCallback(async ({
191
+ path,
192
+ propertyKey,
193
+ newPropertiesOrder,
194
+ }: DeletePropertyParams) => {
195
+ setCollections(prev => {
196
+ const idx = prev.findIndex(c => c.slug === path);
197
+ if (idx < 0) return prev;
198
+
199
+ const collection = { ...prev[idx] };
200
+ const { [propertyKey]: _removed, ...remaining } = collection.properties;
201
+ collection.properties = remaining;
202
+ if (newPropertiesOrder) {
203
+ collection.propertiesOrder = newPropertiesOrder as typeof collection.propertiesOrder;
204
+ }
205
+
206
+ const next = [...prev];
207
+ next[idx] = collection as EntityCollection;
208
+
209
+ persistCollection(next[idx]).catch(e =>
210
+ console.error("useJsonCollectionsConfigController: failed to delete property", e)
211
+ );
212
+
213
+ return next;
214
+ });
215
+ }, [persistCollection]);
216
+
217
+ const updatePropertiesOrder = useCallback(async ({
218
+ collection,
219
+ newPropertiesOrder,
220
+ }: UpdatePropertiesOrderParams) => {
221
+ const updated = {
222
+ ...collection,
223
+ propertiesOrder: newPropertiesOrder,
224
+ } as EntityCollection;
225
+
226
+ setCollections(prev => {
227
+ const idx = prev.findIndex(c => c.slug === collection.slug);
228
+ if (idx < 0) return prev;
229
+ const next = [...prev];
230
+ next[idx] = updated;
231
+ return next;
232
+ });
233
+
234
+ await persistCollection(updated);
235
+ }, [persistCollection]);
236
+
237
+ const updateKanbanColumnsOrder = useCallback(async ({
238
+ collection,
239
+ kanbanColumnProperty,
240
+ newColumnsOrder,
241
+ }: UpdateKanbanColumnsOrderParams) => {
242
+ // Kanban column order is stored in the enum order of the column property.
243
+ // For JSON persistence, we store it as-is — the view layer reads enum order.
244
+ // This is a no-op for now; the kanban order is determined by the enum
245
+ // definition on the property itself.
246
+ }, []);
247
+
248
+ const saveNavigationEntriesHandler = useCallback(async (entries: NavigationGroupMapping[]) => {
249
+ setNavigationEntries(entries);
250
+ if (storeRef.current.saveNavigationEntries) {
251
+ await storeRef.current.saveNavigationEntries(entries);
252
+ }
253
+ }, []);
254
+
255
+ // ── Return controller ─────────────────────────────────────────────
256
+ return useMemo<CollectionsConfigController>(() => ({
257
+ loading,
258
+ readOnly,
259
+ readOnlyReason: readOnly ? "Configuration is read-only." : undefined,
260
+ collections,
261
+ getCollection,
262
+ saveCollection,
263
+ updateCollection,
264
+ deleteCollection,
265
+ saveProperty,
266
+ deleteProperty,
267
+ updatePropertiesOrder,
268
+ updateKanbanColumnsOrder,
269
+ navigationEntries,
270
+ saveNavigationEntries: saveNavigationEntriesHandler,
271
+ }), [
272
+ loading,
273
+ readOnly,
274
+ collections,
275
+ getCollection,
276
+ saveCollection,
277
+ updateCollection,
278
+ deleteCollection,
279
+ saveProperty,
280
+ deleteProperty,
281
+ updatePropertiesOrder,
282
+ updateKanbanColumnsOrder,
283
+ navigationEntries,
284
+ saveNavigationEntriesHandler,
285
+ ]);
286
+ }
@@ -0,0 +1,9 @@
1
+ import { useSnackbarController } from "@rebasepro/core";
2
+
3
+ export function useSafeSnackbarController(): ReturnType<typeof useSnackbarController> | undefined {
4
+ try {
5
+ return useSnackbarController();
6
+ } catch {
7
+ return undefined;
8
+ }
9
+ }
@@ -13,3 +13,9 @@ export type { CollectionStudioViewProps } from "./collection_editor/ui/collectio
13
13
 
14
14
  export { CollectionsStudioView } from "./collection_editor/ui/collection_editor/CollectionsStudioView";
15
15
  export type { CollectionsStudioViewProps } from "./collection_editor/ui/collection_editor/CollectionsStudioView";
16
+
17
+ export { RouterCollectionStudioView } from "./collection_editor/ui/collection_editor/RouterCollectionStudioView";
18
+ export type { RouterCollectionStudioViewProps } from "./collection_editor/ui/collection_editor/RouterCollectionStudioView";
19
+
20
+ export { RouterCollectionsStudioView } from "./collection_editor/ui/collection_editor/RouterCollectionsStudioView";
21
+ export type { RouterCollectionsStudioViewProps } from "./collection_editor/ui/collection_editor/RouterCollectionsStudioView";
@@ -165,11 +165,21 @@ function CMSNavigationContent() {
165
165
 
166
166
  const allNavigationEntries = navigationState.topLevelNavigation?.navigationEntries ?? [];
167
167
 
168
- // Studio mode shows view-type entries (devViews) + admin entries (Users/Roles).
169
- // Content mode shows collections and custom entries + admin entries (Users/Roles), but not studio views.
168
+ // Build a set of Studio-only dev view slugs so we can distinguish them from
169
+ // CMS custom views added via <RebaseCMS views={...}> or plugins.
170
+ const studioViewSlugs = useMemo(() => {
171
+ const slugs = new Set<string>();
172
+ (registry.studioConfig?.devViews ?? []).forEach(v => slugs.add(v.slug));
173
+ // The schema view is also a Studio dev view (auto-injected when collectionEditor is enabled)
174
+ if (registry.studioConfig && registry.cmsConfig?.collectionEditor) slugs.add("schema");
175
+ return slugs;
176
+ }, [registry.studioConfig, registry.cmsConfig?.collectionEditor]);
177
+
178
+ // Studio mode shows Studio dev views + admin entries (Users/Roles).
179
+ // Content mode shows collections, CMS custom views, and admin entries — but not Studio dev views.
170
180
  const filteredEntries = adminModeController.mode === "studio"
171
- ? allNavigationEntries.filter(e => e.type === "view" || e.type === "admin")
172
- : allNavigationEntries.filter(e => e.type !== "view");
181
+ ? allNavigationEntries.filter(e => (e.type === "view" && studioViewSlugs.has(e.slug)) || e.type === "admin")
182
+ : allNavigationEntries.filter(e => e.type !== "view" || !studioViewSlugs.has(e.slug));
173
183
 
174
184
  // Derive groups from the filtered entries, preserving the order from topLevelNavigation.groups
175
185
  const entryGroups = new Set(filteredEntries.map(e => e.group).filter(Boolean));
@@ -1,7 +1,7 @@
1
1
  import type { NavigationEntry } from "@rebasepro/types";
2
2
  import React from "react";
3
3
  import { ChevronDownIcon, cls, iconSize, Typography } from "@rebasepro/ui";
4
- ;
4
+
5
5
  import { IconForView } from "@rebasepro/core";
6
6
  import { DrawerNavigationItem } from "./DrawerNavigationItem";
7
7
  import { useTranslation, useComponentOverride } from "@rebasepro/core";
@@ -108,7 +108,7 @@ export function DrawerNavigationGroup({
108
108
  "transition-all duration-200 ease-in-out",
109
109
  "overflow-hidden",
110
110
  !hideHeader && "dark:bg-transparent",
111
- !hideHeader ? (drawerOpen ? "rounded-lg" : "rounded-lg") : "rounded-lg",
111
+ "rounded-lg",
112
112
  (!hideHeader && collapsed) ? "max-h-0 opacity-0" : "max-h-[2000px] opacity-100"
113
113
  )}
114
114
  >
@@ -42,7 +42,7 @@ import { getValueInPath } from "@rebasepro/utils";
42
42
  * @see VirtualTable
43
43
  * @group Components
44
44
  */
45
- export const EntityCollectionTable = function EntityCollectionTable<M extends Record<string, unknown> = any, USER extends User = any>
45
+ export const EntityCollectionTable = function EntityCollectionTable<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User>
46
46
  ({
47
47
  className,
48
48
  style,
@@ -113,7 +113,7 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
113
113
  const additionalFieldsMap: Record<string, AdditionalFieldDelegate<M, USER>> = useMemo(() => {
114
114
  return (additionalFields
115
115
  ? additionalFields
116
- .map((aC) => ({ [aC.key]: aC as AdditionalFieldDelegate<M, any> }))
116
+ .map((aC) => ({ [aC.key]: aC as AdditionalFieldDelegate<M, USER> }))
117
117
  .reduce((a, b) => ({ ...a,
118
118
  ...b }), {})
119
119
  : {}) as Record<string, AdditionalFieldDelegate<M, USER>>;
@@ -132,9 +132,10 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
132
132
  isDragging,
133
133
  isDraggable,
134
134
  frozen
135
- }: CellRendererParams<any>) => {
135
+ }: CellRendererParams<Entity<M>>) => {
136
136
 
137
- const entity: Entity<M> = rowData;
137
+ const entity: Entity<M> | undefined = rowData;
138
+ if (!entity) return null;
138
139
 
139
140
  const propertyKey = column.key;
140
141
 
@@ -192,9 +193,10 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
192
193
  isDragging,
193
194
  isDraggable,
194
195
  frozen
195
- }: CellRendererParams<any>) => {
196
+ }: CellRendererParams<Entity<M>>) => {
196
197
 
197
- const entity: Entity<M> = rowData;
198
+ const entity: Entity<M> | undefined = rowData;
199
+ if (!entity) return null;
198
200
 
199
201
  const additionalField = additionalFieldsMap[column.key as string];
200
202
  const value = additionalField.dependencies
@@ -293,7 +295,7 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
293
295
  : collectionColumns) as VirtualTableColumn[]
294
296
  ], [idColumn, displayedColumnIds, collectionColumns]);
295
297
 
296
- const cellRenderer = useCallback((props: CellRendererParams<any>) => {
298
+ const cellRenderer = useCallback((props: CellRendererParams<Entity<M>>) => {
297
299
  const column = props.column;
298
300
  const columns = props.columns;
299
301
  const columnKey = column.key;
@@ -302,13 +304,13 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
302
304
  if (props.columnIndex === 0) {
303
305
  if (tableRowActionsBuilder)
304
306
  return tableRowActionsBuilder({
305
- entity: props.rowData,
307
+ entity: props.rowData!,
306
308
  size,
307
309
  width: column.width,
308
310
  frozen: column.frozen
309
311
  });
310
312
  else
311
- return <EntityCollectionRowActions entity={props.rowData}
313
+ return <EntityCollectionRowActions entity={props.rowData!}
312
314
  width={column.width}
313
315
  frozen={column.frozen}
314
316
  isSelected={false}
@@ -1,5 +1,6 @@
1
1
  import { useEntitySelectionDialog } from "../../../hooks/useEntitySelectionDialog";
2
2
  import type { EntityCollection } from "@rebasepro/types";
3
+ import { getCollectionDataPath } from "@rebasepro/types";
3
4
  import React, { useCallback } from "react";
4
5
  import { deepEqual as equal } from "fast-equals";
5
6
 
@@ -58,7 +59,7 @@ export const TableMultipleRelationFieldInternal = React.memo(
58
59
 
59
60
  const relationDialogController = useEntitySelectionDialog({
60
61
  multiselect: true,
61
- path: collection.slug,
62
+ path: getCollectionDataPath(collection),
62
63
  collection,
63
64
  onMultipleEntitiesSelected,
64
65
  selectedEntityIds,
@@ -1,5 +1,6 @@
1
1
  import { useEntitySelectionDialog } from "../../../hooks/useEntitySelectionDialog";
2
2
  import type { EntityCollection } from "@rebasepro/types";
3
+ import { getCollectionDataPath } from "@rebasepro/types";
3
4
  import React, { useCallback } from "react";
4
5
  import { deepEqual as equal } from "fast-equals"
5
6
 
@@ -90,7 +91,7 @@ export const TableRelationFieldInternal = React.memo(
90
91
 
91
92
  const relationDialogController = useEntitySelectionDialog({
92
93
  multiselect,
93
- path: collection.slug,
94
+ path: getCollectionDataPath(collection),
94
95
  collection,
95
96
  onMultipleEntitiesSelected,
96
97
  onSingleEntitySelected,
@@ -8,7 +8,7 @@ import { useDropzone } from "react-dropzone";
8
8
  import { PropertyPreview } from "../../../preview";
9
9
  import { ErrorBoundary } from "@rebasepro/ui";
10
10
  import { cls, IconButton, PencilIcon, Typography } from "@rebasepro/ui";
11
- import { useSnackbarController, useStorageSource, useTranslation, StorageFieldItem, useStorageUploadController } from "@rebasepro/core";
11
+ import { useSnackbarController, useStorageSource, useTranslation, StorageFieldItem, useStorageUploadController, StorageSourceContext } from "@rebasepro/core";
12
12
  import { getThumbnailMeasure } from "../../../preview/util";
13
13
  import { StorageUploadProgress } from "../../../form/components/StorageUploadProgress";
14
14
  import { EntityTableCellActions } from "../internal/EntityTableCellActions";
@@ -64,7 +64,8 @@ export function TableStorageUpload(props: {
64
64
  storage,
65
65
  onFileUploadComplete,
66
66
  storagePathBuilder,
67
- multipleFilesSupported
67
+ multipleFilesSupported,
68
+ resolvedStorageSource
68
69
  } = useStorageUploadController({
69
70
  entityValues: entity.values,
70
71
  entityId: entity.id,
@@ -79,24 +80,26 @@ export function TableStorageUpload(props: {
79
80
 
80
81
  return (
81
82
 
82
- <StorageUpload
83
- internalValue={internalValue}
84
- setInternalValue={setInternalValue}
85
- name={propertyKey}
86
- disabled={disabled}
87
- autoFocus={false}
88
- openPopup={openPopup}
89
- error={error}
90
- selected={selected}
91
- property={property}
92
- onChange={updateValue}
93
- entity={entity}
94
- storagePathBuilder={storagePathBuilder}
95
- storage={storage}
96
- multipleFilesSupported={multipleFilesSupported}
97
- onFilesAdded={onFilesAdded}
98
- onFileUploadComplete={onFileUploadComplete}
99
- previewSize={previewSize}/>
83
+ <StorageSourceContext.Provider value={resolvedStorageSource}>
84
+ <StorageUpload
85
+ internalValue={internalValue}
86
+ setInternalValue={setInternalValue}
87
+ name={propertyKey}
88
+ disabled={disabled}
89
+ autoFocus={false}
90
+ openPopup={openPopup}
91
+ error={error}
92
+ selected={selected}
93
+ property={property}
94
+ onChange={updateValue}
95
+ entity={entity}
96
+ storagePathBuilder={storagePathBuilder}
97
+ storage={storage}
98
+ multipleFilesSupported={multipleFilesSupported}
99
+ onFilesAdded={onFilesAdded}
100
+ onFileUploadComplete={onFileUploadComplete}
101
+ previewSize={previewSize}/>
102
+ </StorageSourceContext.Provider>
100
103
 
101
104
  );
102
105
  }
@@ -11,4 +11,4 @@ export * from "./EntityCollectionRowActions";
11
11
 
12
12
  export * from "./column_utils";
13
13
 
14
- export { VirtualTableInput } from "./fields/VirtualTableInput";
14
+ export { VirtualTableInput } from "@rebasepro/ui";