@rebasepro/admin 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/{CollectionEditorDialog-jA-PLh6N.js → CollectionEditorDialog-iGgS4rLX.js} +233 -152
- package/dist/CollectionEditorDialog-iGgS4rLX.js.map +1 -0
- package/dist/{PropertyEditView-DEUddmg_.js → PropertyEditView-C9yhLyja.js} +129 -15
- package/dist/PropertyEditView-C9yhLyja.js.map +1 -0
- package/dist/{CollectionsStudioView-UKouBPOm.js → RouterCollectionsStudioView-Dqx27GD8.js} +61 -39
- package/dist/RouterCollectionsStudioView-Dqx27GD8.js.map +1 -0
- package/dist/collection_editor/extensibility_types.d.ts +135 -0
- package/dist/collection_editor/index.d.ts +6 -0
- package/dist/collection_editor/serializable_types.d.ts +374 -0
- package/dist/collection_editor/serializable_utils.d.ts +50 -0
- package/dist/collection_editor/ui/collection_editor/CollectionEditorDialog.d.ts +2 -1
- package/dist/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +12 -1
- package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +13 -1
- package/dist/collection_editor/ui/collection_editor/CollectionsStudioView.d.ts +24 -2
- package/dist/collection_editor/ui/collection_editor/DisplaySettingsForm.d.ts +2 -1
- package/dist/collection_editor/ui/collection_editor/GeneralSettingsForm.d.ts +4 -1
- package/dist/collection_editor/ui/collection_editor/PropertyEditView.d.ts +12 -1
- package/dist/collection_editor/ui/collection_editor/RouterCollectionStudioView.d.ts +15 -0
- package/dist/collection_editor/ui/collection_editor/RouterCollectionsStudioView.d.ts +14 -0
- package/dist/collection_editor/useJsonCollectionsConfigController.d.ts +37 -0
- package/dist/collection_editor/useSafeSnackbarController.d.ts +2 -0
- package/dist/collection_editor_ui.d.ts +4 -0
- package/dist/collection_editor_ui.js +33 -4
- package/dist/collection_editor_ui.js.map +1 -0
- package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +3 -3
- package/dist/components/EntityCollectionView/hooks/useKanbanDragAndDrop.d.ts +3 -3
- package/dist/components/EntityEditView.d.ts +3 -2
- package/dist/components/RebaseCMS.d.ts +1 -1
- package/dist/editor/editor.d.ts +10 -4
- package/dist/editor.js +6 -4
- package/dist/editor.js.map +1 -1
- package/dist/form/EntityForm.d.ts +10 -9
- package/dist/form/EntityFormBinding.d.ts +27 -0
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +2 -2
- package/dist/form/form_utils.d.ts +8 -0
- package/dist/form/index.d.ts +5 -1
- package/dist/{history-BL49TIDb.js → history-ChHgyyvM.js} +2 -2
- package/dist/{history-BL49TIDb.js.map → history-ChHgyyvM.js.map} +1 -1
- package/dist/hooks/navigation/contexts/CollectionRegistryContext.d.ts +1 -1
- package/dist/hooks/navigation/useAsyncResolver.d.ts +26 -0
- package/dist/hooks/navigation/useBuildCollectionRegistryController.d.ts +2 -0
- package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
- package/dist/hooks/navigation/utils.d.ts +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -2
- package/dist/routes/CustomViewRoute.d.ts +2 -1
- package/dist/{src-BJ8fkM8y.js → src-DOX2nOyh.js} +1634 -1330
- package/dist/src-DOX2nOyh.js.map +1 -0
- package/dist/types/components/EntityFormActionsProps.d.ts +1 -1
- package/dist/types/components/EntityFormProps.d.ts +59 -0
- package/dist/types/fields.d.ts +6 -1
- package/dist/util/dataControllerAdapter.d.ts +30 -0
- package/dist/util/navigation_utils.d.ts +1 -1
- package/dist/util/propertyConfigMapper.d.ts +17 -0
- package/package.json +18 -18
- package/src/collection_editor/extensibility_types.ts +168 -0
- package/src/collection_editor/index.ts +46 -0
- package/src/collection_editor/serializable_types.ts +487 -0
- package/src/collection_editor/serializable_utils.ts +588 -0
- package/src/collection_editor/ui/AddKanbanColumnAction.tsx +2 -0
- package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +4 -4
- package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +134 -70
- package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +95 -78
- package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +23 -20
- package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +79 -27
- package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +9 -7
- package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +22 -5
- package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +3 -3
- package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +155 -10
- package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +6 -6
- package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +1 -0
- package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +44 -0
- package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +41 -0
- package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +4 -3
- package/src/collection_editor/useJsonCollectionsConfigController.tsx +286 -0
- package/src/collection_editor/useSafeSnackbarController.ts +9 -0
- package/src/collection_editor_ui.ts +6 -0
- package/src/components/ArrayContainer.tsx +5 -5
- package/src/components/DefaultDrawer.tsx +13 -3
- package/src/components/DrawerNavigationGroup.tsx +2 -2
- package/src/components/DrawerNavigationItem.tsx +4 -4
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +14 -11
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +40 -190
- package/src/components/EntityCollectionView/EntityCollectionListView.tsx +54 -183
- package/src/components/EntityCollectionView/hooks/useKanbanDragAndDrop.ts +6 -6
- package/src/components/EntityEditView.tsx +5 -4
- package/src/components/EntityEditViewFormActions.tsx +32 -26
- package/src/components/HomePage/ContentHomePage.tsx +13 -4
- package/src/components/HomePage/NavigationCard.tsx +8 -14
- package/src/components/HomePage/NavigationGroup.tsx +1 -1
- package/src/components/HomePage/SmallNavigationCard.tsx +1 -5
- package/src/components/RebaseCMS.tsx +3 -2
- package/src/components/RebaseNavigation.tsx +71 -7
- package/src/components/SelectableTable/SelectableTable.tsx +2 -2
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +28 -21
- package/src/components/SideDialogs.tsx +0 -7
- package/src/components/common/default_entity_actions.tsx +18 -10
- package/src/contexts/BreacrumbsContext.tsx +15 -6
- package/src/editor/editor.tsx +14 -8
- package/src/editor/selectors/ai-selector.tsx +0 -1
- package/src/form/EntityForm.tsx +191 -428
- package/src/form/EntityFormActions.tsx +33 -47
- package/src/form/EntityFormBinding.tsx +323 -0
- package/src/form/components/FormLayout.tsx +1 -1
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +4 -4
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -2
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +1 -1
- package/src/form/form_utils.ts +163 -0
- package/src/form/index.tsx +14 -5
- package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +2 -2
- package/src/hooks/navigation/contexts/NavigationStateContext.tsx +2 -2
- package/src/hooks/navigation/contexts/UrlContext.tsx +1 -2
- package/src/hooks/navigation/useAsyncResolver.ts +100 -0
- package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +15 -8
- package/src/hooks/navigation/useBuildNavigationStateController.tsx +12 -8
- package/src/hooks/navigation/useNavigationResolution.ts +33 -0
- package/src/hooks/navigation/useResolvedCollections.ts +29 -71
- package/src/hooks/navigation/useResolvedViews.tsx +40 -70
- package/src/hooks/navigation/useTopLevelNavigation.ts +20 -16
- package/src/hooks/navigation/utils.ts +7 -4
- package/src/index.ts +7 -2
- package/src/routes/CustomViewRoute.tsx +14 -6
- package/src/types/components/EntityFormActionsProps.tsx +1 -1
- package/src/types/components/EntityFormProps.tsx +68 -0
- package/src/types/fields.tsx +7 -1
- package/src/util/dataControllerAdapter.ts +93 -0
- package/src/util/navigation_utils.ts +1 -1
- package/src/util/propertyConfigMapper.ts +134 -0
- package/LICENSE +0 -21
- package/dist/CollectionEditorDialog-jA-PLh6N.js.map +0 -1
- package/dist/CollectionsStudioView-UKouBPOm.js.map +0 -1
- package/dist/PropertyEditView-DEUddmg_.js.map +0 -1
- package/dist/components/EntityCollectionView/Board.d.ts +0 -3
- package/dist/components/EntityCollectionView/BoardColumn.d.ts +0 -42
- package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +0 -9
- package/dist/components/EntityCollectionView/BoardSortableList.d.ts +0 -14
- package/dist/components/EntityCollectionView/board_types.d.ts +0 -105
- package/dist/hooks/navigation/useNavigationRegistry.d.ts +0 -12
- package/dist/hooks/navigation/useNavigationURLs.d.ts +0 -11
- package/dist/src-BJ8fkM8y.js.map +0 -1
- package/src/components/EntityCollectionView/Board.tsx +0 -475
- package/src/components/EntityCollectionView/BoardColumn.tsx +0 -165
- package/src/components/EntityCollectionView/BoardColumnTitle.tsx +0 -46
- package/src/components/EntityCollectionView/BoardSortableList.tsx +0 -177
- package/src/components/EntityCollectionView/board_types.ts +0 -113
- package/src/hooks/navigation/useNavigationRegistry.ts +0 -164
- package/src/hooks/navigation/useNavigationURLs.ts +0 -56
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import type { 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
|
+
};
|
|
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
|
+
}
|
|
@@ -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";
|
|
@@ -402,13 +402,13 @@ export function ArrayContainer<T>({
|
|
|
402
402
|
const newIds = [...internalIds];
|
|
403
403
|
newIds.splice(index, 1);
|
|
404
404
|
setInternalIds(newIds);
|
|
405
|
-
onValueChange(value.filter((_, i) => i !== index));
|
|
405
|
+
onValueChange((value ?? []).filter((_, i) => i !== index));
|
|
406
406
|
};
|
|
407
407
|
|
|
408
408
|
const copy = (index: number) => {
|
|
409
409
|
if ((value ?? []).length >= max) return;
|
|
410
410
|
const id = getRandomId();
|
|
411
|
-
const copyingItem = value[index];
|
|
411
|
+
const copyingItem = (value ?? [])[index];
|
|
412
412
|
const newIds: number[] = [
|
|
413
413
|
...internalIds.slice(0, index + 1),
|
|
414
414
|
id,
|
|
@@ -416,7 +416,7 @@ export function ArrayContainer<T>({
|
|
|
416
416
|
];
|
|
417
417
|
if (onInternalIdAdded) onInternalIdAdded(id);
|
|
418
418
|
setInternalIds(newIds);
|
|
419
|
-
onValueChange([...value.slice(0, index + 1), copyingItem, ...value.slice(index + 1)]);
|
|
419
|
+
onValueChange([...(value ?? []).slice(0, index + 1), copyingItem, ...(value ?? []).slice(index + 1)]);
|
|
420
420
|
};
|
|
421
421
|
|
|
422
422
|
const addInIndex = (index: number) => {
|
|
@@ -429,7 +429,7 @@ export function ArrayContainer<T>({
|
|
|
429
429
|
];
|
|
430
430
|
if (onInternalIdAdded) onInternalIdAdded(id);
|
|
431
431
|
setInternalIds(newIds);
|
|
432
|
-
onValueChange([...value.slice(0, index), newDefaultEntry, ...value.slice(index)]);
|
|
432
|
+
onValueChange([...(value ?? []).slice(0, index), newDefaultEntry, ...(value ?? []).slice(index)]);
|
|
433
433
|
};
|
|
434
434
|
|
|
435
435
|
const onDragEnd = (event: DragEndEvent) => {
|
|
@@ -444,7 +444,7 @@ export function ArrayContainer<T>({
|
|
|
444
444
|
if (oldIndex === -1 || newIndex === -1) return;
|
|
445
445
|
const newIds = arrayMove(internalIds, oldIndex, newIndex);
|
|
446
446
|
setInternalIds(newIds);
|
|
447
|
-
onValueChange(arrayMove(value, oldIndex, newIndex));
|
|
447
|
+
onValueChange(arrayMove(value ?? [], oldIndex, newIndex));
|
|
448
448
|
};
|
|
449
449
|
|
|
450
450
|
return sortable ? (
|
|
@@ -165,11 +165,21 @@ function CMSNavigationContent() {
|
|
|
165
165
|
|
|
166
166
|
const allNavigationEntries = navigationState.topLevelNavigation?.navigationEntries ?? [];
|
|
167
167
|
|
|
168
|
-
//
|
|
169
|
-
//
|
|
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
181
|
? allNavigationEntries.filter(e => e.type === "view" || e.type === "admin")
|
|
172
|
-
: allNavigationEntries.filter(e => e.type !== "view");
|
|
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
|
-
|
|
111
|
+
"rounded-lg",
|
|
112
112
|
(!hideHeader && collapsed) ? "max-h-0 opacity-0" : "max-h-[2000px] opacity-100"
|
|
113
113
|
)}
|
|
114
114
|
>
|
|
@@ -22,7 +22,7 @@ export function DrawerNavigationItem({
|
|
|
22
22
|
}) {
|
|
23
23
|
|
|
24
24
|
const iconWrap = <div
|
|
25
|
-
className={"shrink-0 flex items-center justify-center w-[44px] h-[30px] text-surface-500 dark:text-text-secondary-dark [&>svg]:size-4"}>
|
|
25
|
+
className={"shrink-0 flex items-center justify-center w-[44px] h-[30px] text-surface-500 dark:text-text-secondary-dark [&>svg]:size-4 group-hover/nav:text-primary transition-colors duration-150"}>
|
|
26
26
|
{icon}
|
|
27
27
|
</div>;
|
|
28
28
|
|
|
@@ -33,12 +33,12 @@ export function DrawerNavigationItem({
|
|
|
33
33
|
width: "100%",
|
|
34
34
|
transition: drawerOpen ? "width 150ms ease-in" : undefined
|
|
35
35
|
}}
|
|
36
|
-
className={({ isActive }: { isActive: boolean }) => cls("rounded-
|
|
37
|
-
"hover:bg-
|
|
36
|
+
className={({ isActive }: { isActive: boolean }) => cls("rounded-lg truncate group/nav",
|
|
37
|
+
"hover:bg-primary/5 dark:hover:bg-primary/5 text-surface-700 dark:text-surface-300 hover:text-surface-900 dark:hover:text-white",
|
|
38
38
|
"flex flex-row items-center",
|
|
39
39
|
drawerOpen ? "pr-4 h-[30px]" : "h-[30px]",
|
|
40
40
|
"font-medium text-[13px]",
|
|
41
|
-
isActive ? "bg-
|
|
41
|
+
isActive ? "bg-primary/8 dark:bg-primary/10 text-primary dark:text-primary [&_div]:text-primary" : ""
|
|
42
42
|
)}
|
|
43
43
|
to={url}
|
|
44
44
|
>
|
|
@@ -5,10 +5,10 @@ import { Checkbox, Chip, cls, defaultBorderMixin, Markdown } from "@rebasepro/ui
|
|
|
5
5
|
import { PropertyPreview } from "../../preview";
|
|
6
6
|
import { useAuthController, useCustomizationController } from "@rebasepro/core";
|
|
7
7
|
import { IconForView } from "@rebasepro/core";
|
|
8
|
-
import { BoardItemViewProps } from "
|
|
8
|
+
import { BoardItemViewProps } from "@rebasepro/ui";
|
|
9
9
|
import { useCollectionSlotKeys, resolveEntitySlots } from "./useEntityPreviewSlots";
|
|
10
10
|
|
|
11
|
-
export type EntityBoardCardProps<M extends Record<string, unknown> = Record<string, unknown>> = BoardItemViewProps<M
|
|
11
|
+
export type EntityBoardCardProps<M extends Record<string, unknown> = Record<string, unknown>> = BoardItemViewProps<Entity<M>> & {
|
|
12
12
|
collection: EntityCollection<M>;
|
|
13
13
|
onClick?: (entity: Entity<M>) => void;
|
|
14
14
|
selected?: boolean;
|
|
@@ -35,7 +35,7 @@ function EntityBoardCardInner<M extends Record<string, unknown> = Record<string,
|
|
|
35
35
|
onSelectionChange,
|
|
36
36
|
selectionEnabled = false
|
|
37
37
|
}: EntityBoardCardProps<M>) {
|
|
38
|
-
const entity = item.
|
|
38
|
+
const entity = item.data;
|
|
39
39
|
const authController = useAuthController();
|
|
40
40
|
const customizationController = useCustomizationController();
|
|
41
41
|
|
|
@@ -247,14 +247,14 @@ export function createEntityBoardCardComponent<M extends Record<string, unknown>
|
|
|
247
247
|
onSelectionChange?: (entity: Entity<M>, selected: boolean) => void;
|
|
248
248
|
selectionEnabled?: boolean;
|
|
249
249
|
}
|
|
250
|
-
): React.ComponentType<BoardItemViewProps<M
|
|
251
|
-
return function EntityBoardCardWrapper(props: BoardItemViewProps<M
|
|
250
|
+
): React.ComponentType<BoardItemViewProps<Entity<M>>> {
|
|
251
|
+
return function EntityBoardCardWrapper(props: BoardItemViewProps<Entity<M>>) {
|
|
252
252
|
return (
|
|
253
253
|
<EntityBoardCard
|
|
254
254
|
{...props}
|
|
255
255
|
collection={collection}
|
|
256
256
|
onClick={options.onClick}
|
|
257
|
-
selected={options.isEntitySelected?.(props.item.
|
|
257
|
+
selected={options.isEntitySelected?.(props.item.data)}
|
|
258
258
|
onSelectionChange={options.onSelectionChange}
|
|
259
259
|
selectionEnabled={options.selectionEnabled}
|
|
260
260
|
/>
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
|
|
2
1
|
import type { EntityCollection } from "@rebasepro/types";
|
|
3
2
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
3
|
import { Entity, EntityTableController, EnumValueConfig, SaveEntityProps, SelectionController } from "@rebasepro/types";
|
|
5
|
-
import { Board } from "./Board";
|
|
6
|
-
import { BoardItem, BoardItemViewProps, ColumnLoadingState } from "./board_types";
|
|
7
4
|
import { EntityBoardCard } from "./EntityBoardCard";
|
|
8
5
|
import {
|
|
9
6
|
Button,
|
|
@@ -13,12 +10,17 @@ import {
|
|
|
13
10
|
Dialog,
|
|
14
11
|
DialogActions,
|
|
15
12
|
DialogContent,
|
|
13
|
+
DialogTitle,
|
|
16
14
|
getColorSchemeForSeed,
|
|
17
15
|
IconButton,
|
|
18
16
|
iconSize,
|
|
19
17
|
RefreshCwIcon,
|
|
20
18
|
Tooltip,
|
|
21
|
-
Typography
|
|
19
|
+
Typography,
|
|
20
|
+
KanbanView,
|
|
21
|
+
BoardItem,
|
|
22
|
+
BoardItemViewProps,
|
|
23
|
+
ColumnLoadingState
|
|
22
24
|
} from "@rebasepro/ui";
|
|
23
25
|
import { resolveEnumValues } from "@rebasepro/common";
|
|
24
26
|
import { getPropertyInPath } from "../../util/property_utils";
|
|
@@ -317,10 +319,10 @@ parentEntityIds,
|
|
|
317
319
|
}, [columns, boardDataController.columnData]);
|
|
318
320
|
|
|
319
321
|
// Convert entities to board items per column (data already sorted by orderProperty from controller)
|
|
320
|
-
const boardItems: BoardItem<M
|
|
322
|
+
const boardItems: BoardItem<Entity<M>>[] = useMemo(() => {
|
|
321
323
|
return allEntities.map((entity: Entity<M>) => ({
|
|
322
324
|
id: String(entity.id),
|
|
323
|
-
entity
|
|
325
|
+
data: entity
|
|
324
326
|
}));
|
|
325
327
|
}, [allEntities]);
|
|
326
328
|
|
|
@@ -340,11 +342,11 @@ parentEntityIds,
|
|
|
340
342
|
}, [columns, boardDataController.columnData]);
|
|
341
343
|
|
|
342
344
|
// Use the lookup map to assign columns - ensures items stay in the column they were fetched for
|
|
343
|
-
const assignColumn = useCallback((item: BoardItem<M
|
|
345
|
+
const assignColumn = useCallback((item: BoardItem<Entity<M>>): string => {
|
|
344
346
|
const column = entityColumnMap[item.id];
|
|
345
347
|
if (column) return column;
|
|
346
348
|
// Fallback: read from entity values (for newly created items or edge cases)
|
|
347
|
-
const value = item.
|
|
349
|
+
const value = item.data.values?.[columnProperty];
|
|
348
350
|
if (value && columns.includes(String(value))) return String(value);
|
|
349
351
|
return columns[0] || "";
|
|
350
352
|
}, [entityColumnMap, columnProperty, columns]);
|
|
@@ -461,12 +463,12 @@ parentEntityIds,
|
|
|
461
463
|
// When ItemComponent identity changes, React.memo'd SortableItem remounts
|
|
462
464
|
// the card → DOM is destroyed/recreated → CSS :hover state is lost → flicker.
|
|
463
465
|
const ItemComponent = useMemo(() => {
|
|
464
|
-
const Comp = (props: BoardItemViewProps<M
|
|
466
|
+
const Comp = (props: BoardItemViewProps<Entity<M>>) => (
|
|
465
467
|
<EntityBoardCard
|
|
466
468
|
{...props}
|
|
467
469
|
collection={collectionRef.current as EntityCollection<M>}
|
|
468
470
|
onClick={stableOnClick}
|
|
469
|
-
selected={isEntitySelectedRef.current(props.item.
|
|
471
|
+
selected={isEntitySelectedRef.current(props.item.data)}
|
|
470
472
|
onSelectionChange={stableOnSelectionChange}
|
|
471
473
|
selectionEnabled={selectionEnabledRef.current}
|
|
472
474
|
/>
|
|
@@ -591,7 +593,7 @@ parentEntityIds,
|
|
|
591
593
|
|
|
592
594
|
{/* Main board */}
|
|
593
595
|
<div className="flex-1 overflow-auto no-scrollbar">
|
|
594
|
-
<
|
|
596
|
+
<KanbanView
|
|
595
597
|
data={boardItems}
|
|
596
598
|
columns={columns}
|
|
597
599
|
columnLabels={columnLabels}
|
|
@@ -626,6 +628,7 @@ parentEntityIds,
|
|
|
626
628
|
|
|
627
629
|
{/* Backfill dialog */}
|
|
628
630
|
<Dialog open={showBackfillDialog} onOpenChange={setShowBackfillDialog}>
|
|
631
|
+
<DialogTitle hidden>{t("initialize_kanban_order")}</DialogTitle>
|
|
629
632
|
<DialogContent>
|
|
630
633
|
<Typography variant="h6" className="mb-4">{t("initialize_kanban_order")}</Typography>
|
|
631
634
|
<Typography variant="body2">
|