@rebasepro/admin 0.6.1 → 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/dist/{CollectionEditorDialog-HNXGxXdO.js → CollectionEditorDialog-iGgS4rLX.js} +233 -152
- package/dist/CollectionEditorDialog-iGgS4rLX.js.map +1 -0
- package/dist/{PropertyEditView-C2wp8kQ9.js → PropertyEditView-C9yhLyja.js} +129 -15
- package/dist/PropertyEditView-C9yhLyja.js.map +1 -0
- package/dist/{CollectionsStudioView-B1mN33GB.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/form/EntityForm.d.ts +10 -9
- package/dist/form/EntityFormBinding.d.ts +27 -0
- package/dist/form/form_utils.d.ts +8 -0
- package/dist/form/index.d.ts +5 -1
- package/dist/{history-BO6VT85a.js → history-ChHgyyvM.js} +2 -2
- package/dist/{history-BO6VT85a.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 +2 -0
- package/dist/index.js +2 -2
- package/dist/routes/CustomViewRoute.d.ts +2 -1
- package/dist/{src-BYniefVu.js → src-DOX2nOyh.js} +1586 -1283
- 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/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/DefaultDrawer.tsx +13 -3
- package/src/components/DrawerNavigationGroup.tsx +2 -2
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +6 -6
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +12 -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/RebaseCMS.tsx +3 -2
- package/src/components/RebaseNavigation.tsx +71 -7
- package/src/components/SelectableTable/SelectableTable.tsx +2 -2
- package/src/components/SideDialogs.tsx +0 -7
- package/src/components/common/default_entity_actions.tsx +9 -9
- package/src/contexts/BreacrumbsContext.tsx +15 -6
- 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/ReadOnlyFieldBinding.tsx +7 -2
- 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 +5 -0
- 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-HNXGxXdO.js.map +0 -1
- package/dist/CollectionsStudioView-B1mN33GB.js.map +0 -1
- package/dist/PropertyEditView-C2wp8kQ9.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-BYniefVu.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
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { EntityCollection } from "@rebasepro/types";
|
|
2
|
-
import React, { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
3
3
|
import { CollectionSize, Entity, EntityTableController, SelectionController } from "@rebasepro/types";
|
|
4
4
|
import { EntityCard } from "./EntityCard";
|
|
5
5
|
import {
|
|
6
6
|
cls,
|
|
7
7
|
CircularProgress,
|
|
8
|
-
Typography
|
|
8
|
+
Typography,
|
|
9
|
+
CardView
|
|
9
10
|
} from "@rebasepro/ui";
|
|
10
11
|
import { useComponentOverride } from "@rebasepro/core";
|
|
11
12
|
|
|
@@ -92,8 +93,6 @@ export function EntityCollectionCardView<M extends Record<string, unknown> = Rec
|
|
|
92
93
|
}: EntityCollectionCardViewProps<M>) {
|
|
93
94
|
|
|
94
95
|
const ResolvedEntityCard = useComponentOverride("Collection.Card", EntityCard) as typeof EntityCard;
|
|
95
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
96
|
-
const hasRestoredScroll = useRef(false);
|
|
97
96
|
|
|
98
97
|
const {
|
|
99
98
|
data,
|
|
@@ -106,116 +105,6 @@ export function EntityCollectionCardView<M extends Record<string, unknown> = Rec
|
|
|
106
105
|
paginationEnabled
|
|
107
106
|
} = tableController;
|
|
108
107
|
|
|
109
|
-
// Track if we're currently loading to prevent multiple simultaneous load requests
|
|
110
|
-
const isLoadingMore = useRef(false);
|
|
111
|
-
|
|
112
|
-
// Keep mutable refs for values used in the scroll listener callback
|
|
113
|
-
// to avoid re-attaching the listener every time pagination state changes.
|
|
114
|
-
const paginationStateRef = useRef({ paginationEnabled,
|
|
115
|
-
noMoreToLoad,
|
|
116
|
-
dataLoading,
|
|
117
|
-
itemCount,
|
|
118
|
-
pageSize });
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
paginationStateRef.current = { paginationEnabled,
|
|
121
|
-
noMoreToLoad,
|
|
122
|
-
dataLoading,
|
|
123
|
-
itemCount,
|
|
124
|
-
pageSize };
|
|
125
|
-
}, [paginationEnabled, noMoreToLoad, dataLoading, itemCount, pageSize]);
|
|
126
|
-
|
|
127
|
-
// Reset loading flag when new data arrives (separate effect, like list view)
|
|
128
|
-
useEffect(() => {
|
|
129
|
-
if (!dataLoading) isLoadingMore.current = false;
|
|
130
|
-
}, [dataLoading]);
|
|
131
|
-
|
|
132
|
-
// Infinite scroll and resize observer
|
|
133
|
-
useEffect(() => {
|
|
134
|
-
const el = containerRef.current;
|
|
135
|
-
if (!el) return;
|
|
136
|
-
const scrollEl = getScrollParent(el);
|
|
137
|
-
if (!scrollEl) return;
|
|
138
|
-
|
|
139
|
-
let rafId: number | null = null;
|
|
140
|
-
|
|
141
|
-
const update = () => {
|
|
142
|
-
rafId = null;
|
|
143
|
-
|
|
144
|
-
// Infinite scroll: trigger load-more when near the bottom
|
|
145
|
-
const { paginationEnabled: pe, noMoreToLoad: nm, itemCount: ic, pageSize: ps } = paginationStateRef.current;
|
|
146
|
-
if (
|
|
147
|
-
pe &&
|
|
148
|
-
!nm &&
|
|
149
|
-
!isLoadingMore.current &&
|
|
150
|
-
scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < 400
|
|
151
|
-
) {
|
|
152
|
-
isLoadingMore.current = true;
|
|
153
|
-
setItemCount?.((ic ?? ps) + ps);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const onScrollEvent = () => {
|
|
158
|
-
if (rafId === null) rafId = requestAnimationFrame(update);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
scrollEl.addEventListener("scroll", onScrollEvent, { passive: true });
|
|
162
|
-
const ro = new ResizeObserver(() => update());
|
|
163
|
-
ro.observe(scrollEl);
|
|
164
|
-
update(); // initial measurement
|
|
165
|
-
|
|
166
|
-
return () => {
|
|
167
|
-
scrollEl.removeEventListener("scroll", onScrollEvent);
|
|
168
|
-
ro.disconnect();
|
|
169
|
-
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
170
|
-
};
|
|
171
|
-
}, [setItemCount]);
|
|
172
|
-
|
|
173
|
-
// Scroll restoration — deferred to after layout paint
|
|
174
|
-
useEffect(() => {
|
|
175
|
-
if (!containerRef.current || !initialScroll || hasRestoredScroll.current || data.length === 0) return;
|
|
176
|
-
|
|
177
|
-
const scrollEl = getScrollParent(containerRef.current);
|
|
178
|
-
if (!scrollEl) return;
|
|
179
|
-
|
|
180
|
-
let attempts = 0;
|
|
181
|
-
const maxAttempts = 5;
|
|
182
|
-
|
|
183
|
-
const tryRestore = () => {
|
|
184
|
-
if (scrollEl.scrollHeight >= initialScroll || attempts >= maxAttempts) {
|
|
185
|
-
scrollEl.scrollTop = initialScroll;
|
|
186
|
-
hasRestoredScroll.current = true;
|
|
187
|
-
} else {
|
|
188
|
-
attempts++;
|
|
189
|
-
requestAnimationFrame(tryRestore);
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
requestAnimationFrame(tryRestore);
|
|
194
|
-
}, [initialScroll, data.length]);
|
|
195
|
-
|
|
196
|
-
// Scroll tracking: call onScroll when user scrolls
|
|
197
|
-
const lastScrollOffset = useRef(0);
|
|
198
|
-
useEffect(() => {
|
|
199
|
-
const el = containerRef.current;
|
|
200
|
-
if (!el || !onScroll) return;
|
|
201
|
-
const scrollEl = getScrollParent(el);
|
|
202
|
-
if (!scrollEl) return;
|
|
203
|
-
|
|
204
|
-
const handleScroll = () => {
|
|
205
|
-
const currentOffset = scrollEl.scrollTop;
|
|
206
|
-
const direction = currentOffset > lastScrollOffset.current ? "forward" : "backward";
|
|
207
|
-
lastScrollOffset.current = currentOffset;
|
|
208
|
-
onScroll({
|
|
209
|
-
scrollDirection: direction,
|
|
210
|
-
scrollOffset: currentOffset,
|
|
211
|
-
scrollUpdateWasRequested: false
|
|
212
|
-
});
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
scrollEl.addEventListener("scroll", handleScroll, { passive: true });
|
|
216
|
-
return () => scrollEl.removeEventListener("scroll", handleScroll);
|
|
217
|
-
}, [onScroll]);
|
|
218
|
-
|
|
219
108
|
const handleEntityClick = useCallback((entity: Entity<M>) => {
|
|
220
109
|
onEntityClick?.(entity);
|
|
221
110
|
}, [onEntityClick]);
|
|
@@ -224,84 +113,45 @@ pageSize };
|
|
|
224
113
|
selectionController?.toggleEntitySelection(entity, selected);
|
|
225
114
|
}, [selectionController]);
|
|
226
115
|
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
}, [selectionController]);
|
|
230
|
-
|
|
231
|
-
const isEntityHighlighted = useCallback((entity: Entity<M>) => {
|
|
232
|
-
return highlightedEntities?.some(e => e.id === entity.id && e.path === entity.path) ?? false;
|
|
233
|
-
}, [highlightedEntities]);
|
|
116
|
+
const selectedIds = useMemo(() => new Set(selectionController?.selectedEntities.map(e => e.id)), [selectionController?.selectedEntities]);
|
|
117
|
+
const highlightedIds = useMemo(() => new Set(highlightedEntities?.map(e => e.id)), [highlightedEntities]);
|
|
234
118
|
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const isInitialLoading = dataLoading && data.length === 0 && !dataLoadingError;
|
|
239
|
-
// Empty state
|
|
240
|
-
const isEmpty = !dataLoading && data.length === 0 && !dataLoadingError;
|
|
119
|
+
const handleRowSelectionChange = useCallback((entity: Entity<M>, selected: boolean) => {
|
|
120
|
+
handleSelectionChange(entity, selected);
|
|
121
|
+
}, [handleSelectionChange]);
|
|
241
122
|
|
|
242
123
|
return (
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
{
|
|
248
|
-
{
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
<ResolvedEntityCard
|
|
276
|
-
key={`${entity.path}_${entity.id}`}
|
|
277
|
-
entity={entity}
|
|
278
|
-
collection={collection}
|
|
279
|
-
onClick={handleEntityClick}
|
|
280
|
-
selected={isEntitySelected(entity)}
|
|
281
|
-
highlighted={isEntityHighlighted(entity)}
|
|
282
|
-
onSelectionChange={handleSelectionChange}
|
|
283
|
-
selectionEnabled={selectionEnabled}
|
|
284
|
-
size={size}
|
|
285
|
-
/>
|
|
286
|
-
))}
|
|
287
|
-
</div>
|
|
288
|
-
|
|
289
|
-
{/* Load more trigger / Loading indicator */}
|
|
290
|
-
<div
|
|
291
|
-
className="flex items-center justify-center py-8"
|
|
292
|
-
>
|
|
293
|
-
{dataLoading && (
|
|
294
|
-
<CircularProgress size="small"/>
|
|
295
|
-
)}
|
|
296
|
-
{!dataLoading && noMoreToLoad && data.length > 0 && (
|
|
297
|
-
<Typography variant="caption" color="secondary">
|
|
298
|
-
All {data.length} entries loaded
|
|
299
|
-
</Typography>
|
|
300
|
-
)}
|
|
301
|
-
</div>
|
|
302
|
-
</div>
|
|
303
|
-
</>
|
|
304
|
-
)}
|
|
305
|
-
</div>
|
|
124
|
+
<CardView<Entity<M>>
|
|
125
|
+
data={data}
|
|
126
|
+
dataLoading={dataLoading}
|
|
127
|
+
noMoreToLoad={noMoreToLoad}
|
|
128
|
+
dataLoadingError={dataLoadingError}
|
|
129
|
+
itemCount={itemCount}
|
|
130
|
+
setItemCount={setItemCount}
|
|
131
|
+
pageSize={pageSize}
|
|
132
|
+
paginationEnabled={paginationEnabled}
|
|
133
|
+
onItemClick={handleEntityClick}
|
|
134
|
+
selectedIds={selectedIds}
|
|
135
|
+
highlightedIds={highlightedIds}
|
|
136
|
+
selectionEnabled={selectionEnabled}
|
|
137
|
+
onSelectionChange={handleRowSelectionChange}
|
|
138
|
+
onScroll={onScroll}
|
|
139
|
+
initialScroll={initialScroll}
|
|
140
|
+
size={size}
|
|
141
|
+
emptyComponent={emptyComponent}
|
|
142
|
+
renderCard={useCallback((entity, { selected, highlighted, onClick }) => (
|
|
143
|
+
<ResolvedEntityCard
|
|
144
|
+
key={`${entity.path}_${entity.id}`}
|
|
145
|
+
entity={entity}
|
|
146
|
+
collection={collection}
|
|
147
|
+
onClick={onClick as any}
|
|
148
|
+
selected={selected}
|
|
149
|
+
highlighted={highlighted}
|
|
150
|
+
onSelectionChange={handleRowSelectionChange}
|
|
151
|
+
selectionEnabled={selectionEnabled}
|
|
152
|
+
size={size}
|
|
153
|
+
/>
|
|
154
|
+
), [collection, selectionEnabled, size, handleRowSelectionChange, ResolvedEntityCard])}
|
|
155
|
+
/>
|
|
306
156
|
);
|
|
307
157
|
}
|
|
@@ -10,7 +10,8 @@ import {
|
|
|
10
10
|
defaultBorderMixin,
|
|
11
11
|
IconButton,
|
|
12
12
|
Tooltip,
|
|
13
|
-
Typography
|
|
13
|
+
Typography,
|
|
14
|
+
ListView
|
|
14
15
|
} from "@rebasepro/ui";
|
|
15
16
|
import { PropertyPreview } from "../../preview";
|
|
16
17
|
import {
|
|
@@ -510,192 +511,62 @@ customEntityActions });
|
|
|
510
511
|
|
|
511
512
|
const rowClasses = getRowClasses(size);
|
|
512
513
|
|
|
513
|
-
|
|
514
|
-
const
|
|
515
|
-
// Empty state
|
|
516
|
-
const isEmpty = !dataLoading && data.length === 0 && !dataLoadingError;
|
|
517
|
-
|
|
518
|
-
// ── Virtualization: scroll-parent windowing ──
|
|
519
|
-
const estimatedRowHeight = getEstimatedRowHeight(size);
|
|
520
|
-
const [effectiveScrollTop, setEffectiveScrollTop] = useState(0);
|
|
521
|
-
const [viewportHeight, setViewportHeight] = useState(800);
|
|
522
|
-
|
|
523
|
-
// Keep mutable refs for values used in the scroll handler to avoid
|
|
524
|
-
// re-attaching the listener every time pagination state changes.
|
|
525
|
-
const paginationStateRef = useRef({ paginationEnabled,
|
|
526
|
-
noMoreToLoad,
|
|
527
|
-
itemCount,
|
|
528
|
-
pageSize });
|
|
529
|
-
useEffect(() => {
|
|
530
|
-
paginationStateRef.current = { paginationEnabled,
|
|
531
|
-
noMoreToLoad,
|
|
532
|
-
itemCount,
|
|
533
|
-
pageSize };
|
|
534
|
-
}, [paginationEnabled, noMoreToLoad, itemCount, pageSize]);
|
|
535
|
-
|
|
536
|
-
useEffect(() => {
|
|
537
|
-
const el = containerRef.current;
|
|
538
|
-
if (!el) return;
|
|
539
|
-
const scrollEl = getScrollParent(el);
|
|
540
|
-
if (!scrollEl) return;
|
|
541
|
-
|
|
542
|
-
let rafId: number | null = null;
|
|
543
|
-
|
|
544
|
-
const update = () => {
|
|
545
|
-
rafId = null;
|
|
546
|
-
const scrollRect = scrollEl.getBoundingClientRect();
|
|
547
|
-
const listRect = el.getBoundingClientRect();
|
|
548
|
-
|
|
549
|
-
// How much of the list has scrolled past the viewport top
|
|
550
|
-
const listTopRelative = listRect.top - scrollRect.top;
|
|
551
|
-
setEffectiveScrollTop(Math.max(0, -listTopRelative));
|
|
552
|
-
setViewportHeight(scrollRect.height);
|
|
553
|
-
|
|
554
|
-
// Infinite scroll: trigger load-more when near the bottom
|
|
555
|
-
const { paginationEnabled: pe, noMoreToLoad: nm, itemCount: ic, pageSize: ps } = paginationStateRef.current;
|
|
556
|
-
if (
|
|
557
|
-
pe &&
|
|
558
|
-
!nm &&
|
|
559
|
-
!isLoadingMore.current &&
|
|
560
|
-
scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < LOAD_MORE_THRESHOLD
|
|
561
|
-
) {
|
|
562
|
-
isLoadingMore.current = true;
|
|
563
|
-
setItemCount?.((ic ?? ps) + ps);
|
|
564
|
-
}
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
const onScroll = () => {
|
|
568
|
-
if (rafId === null) rafId = requestAnimationFrame(update);
|
|
569
|
-
};
|
|
514
|
+
const selectedIds = useMemo(() => new Set(selectionController?.selectedEntities.map(e => e.id)), [selectionController?.selectedEntities]);
|
|
515
|
+
const highlightedIds = useMemo(() => new Set(highlightedEntities?.map(e => e.id)), [highlightedEntities]);
|
|
570
516
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
update(); // initial measurement
|
|
575
|
-
|
|
576
|
-
return () => {
|
|
577
|
-
scrollEl.removeEventListener("scroll", onScroll);
|
|
578
|
-
ro.disconnect();
|
|
579
|
-
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
580
|
-
};
|
|
581
|
-
}, [setItemCount]); // stable deps only — mutable state via refs
|
|
582
|
-
|
|
583
|
-
// Compute the visible window of rows
|
|
584
|
-
const totalHeight = data.length * estimatedRowHeight;
|
|
585
|
-
const startIndex = Math.max(0, Math.floor(effectiveScrollTop / estimatedRowHeight) - OVERSCAN_COUNT);
|
|
586
|
-
const endIndex = Math.min(
|
|
587
|
-
data.length,
|
|
588
|
-
Math.ceil((effectiveScrollTop + viewportHeight) / estimatedRowHeight) + OVERSCAN_COUNT
|
|
589
|
-
);
|
|
590
|
-
const visibleData = data.slice(startIndex, endIndex);
|
|
591
|
-
const offsetY = startIndex * estimatedRowHeight;
|
|
592
|
-
|
|
593
|
-
// Footer height for loading/end indicators
|
|
594
|
-
const footerHeight = dataLoading ? 48 : (!dataLoading && noMoreToLoad && data.length > 0) ? 32 : 0;
|
|
517
|
+
const handleRowSelectionChange = useCallback((entity: Entity<M>, selected: boolean) => {
|
|
518
|
+
handleSelectionChange(entity, selected);
|
|
519
|
+
}, [handleSelectionChange]);
|
|
595
520
|
|
|
596
521
|
return (
|
|
597
|
-
<
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
{
|
|
605
|
-
{
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
!isLast && "border-b",
|
|
642
|
-
!isLast && defaultBorderMixin
|
|
643
|
-
)}
|
|
644
|
-
>
|
|
645
|
-
<ListRow
|
|
646
|
-
entity={entity}
|
|
647
|
-
collection={resolvedCollection}
|
|
648
|
-
onClick={handleEntityClick}
|
|
649
|
-
selected={isEntitySelected(entity)}
|
|
650
|
-
highlighted={isEntityHighlighted(entity)}
|
|
651
|
-
onSelectionChange={handleSelectionChange}
|
|
652
|
-
selectionEnabled={selectionEnabled}
|
|
653
|
-
columns={visibleColumns}
|
|
654
|
-
slotKeys={slotKeys}
|
|
655
|
-
rowClasses={rowClasses}
|
|
656
|
-
showImage={showImage}
|
|
657
|
-
size={size}
|
|
658
|
-
isLast={isLast}
|
|
659
|
-
isActive={selectedEntityId !== undefined && entity.id === selectedEntityId}
|
|
660
|
-
listViewActions={getListViewActions(entity)}
|
|
661
|
-
context={context}
|
|
662
|
-
path={path}
|
|
663
|
-
selectionController={selectionController}
|
|
664
|
-
openEntityMode={openEntityMode}
|
|
665
|
-
/>
|
|
666
|
-
</div>
|
|
667
|
-
);
|
|
668
|
-
})}
|
|
522
|
+
<ListView<Entity<M>>
|
|
523
|
+
data={data}
|
|
524
|
+
dataLoading={dataLoading}
|
|
525
|
+
noMoreToLoad={noMoreToLoad}
|
|
526
|
+
dataLoadingError={dataLoadingError}
|
|
527
|
+
itemCount={itemCount}
|
|
528
|
+
setItemCount={setItemCount}
|
|
529
|
+
pageSize={pageSize}
|
|
530
|
+
paginationEnabled={paginationEnabled}
|
|
531
|
+
onItemClick={handleEntityClick}
|
|
532
|
+
selectedIds={selectedIds}
|
|
533
|
+
highlightedIds={highlightedIds}
|
|
534
|
+
selectionEnabled={selectionEnabled}
|
|
535
|
+
emptyComponent={emptyComponent}
|
|
536
|
+
size={size}
|
|
537
|
+
selectedEntityId={selectedEntityId}
|
|
538
|
+
renderRow={useCallback(({ item: entity, style, className, selected, highlighted, isLast, onClick, onSelectionChange }) => {
|
|
539
|
+
return (
|
|
540
|
+
<div
|
|
541
|
+
key={entity.id}
|
|
542
|
+
style={style}
|
|
543
|
+
className={className}
|
|
544
|
+
>
|
|
545
|
+
<ListRow
|
|
546
|
+
entity={entity}
|
|
547
|
+
collection={resolvedCollection}
|
|
548
|
+
onClick={handleEntityClick}
|
|
549
|
+
selected={selected}
|
|
550
|
+
highlighted={highlighted}
|
|
551
|
+
onSelectionChange={handleRowSelectionChange}
|
|
552
|
+
selectionEnabled={selectionEnabled}
|
|
553
|
+
columns={visibleColumns}
|
|
554
|
+
slotKeys={slotKeys}
|
|
555
|
+
rowClasses={rowClasses}
|
|
556
|
+
showImage={showImage}
|
|
557
|
+
size={size}
|
|
558
|
+
isLast={isLast}
|
|
559
|
+
isActive={selectedEntityId !== undefined && entity.id === selectedEntityId}
|
|
560
|
+
listViewActions={getListViewActions(entity)}
|
|
561
|
+
context={context}
|
|
562
|
+
path={path}
|
|
563
|
+
selectionController={selectionController}
|
|
564
|
+
openEntityMode={openEntityMode}
|
|
565
|
+
/>
|
|
669
566
|
</div>
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
<div
|
|
674
|
-
className="flex items-center justify-center py-3"
|
|
675
|
-
style={{ position: "absolute",
|
|
676
|
-
top: totalHeight,
|
|
677
|
-
left: 0,
|
|
678
|
-
right: 0 }}
|
|
679
|
-
>
|
|
680
|
-
<CircularProgress size="small"/>
|
|
681
|
-
</div>
|
|
682
|
-
)}
|
|
683
|
-
{!dataLoading && noMoreToLoad && data.length > 0 && (
|
|
684
|
-
<div
|
|
685
|
-
className="flex items-center justify-center py-2 dark:bg-surface-900"
|
|
686
|
-
style={{ position: "absolute",
|
|
687
|
-
top: totalHeight,
|
|
688
|
-
left: 0,
|
|
689
|
-
right: 0 }}
|
|
690
|
-
>
|
|
691
|
-
<Typography variant="caption" color="secondary">
|
|
692
|
-
All {data.length} entries loaded
|
|
693
|
-
</Typography>
|
|
694
|
-
</div>
|
|
695
|
-
)}
|
|
696
|
-
</div>
|
|
697
|
-
)}
|
|
698
|
-
</div>
|
|
567
|
+
);
|
|
568
|
+
}, [resolvedCollection, selectionEnabled, visibleColumns, slotKeys, rowClasses, showImage, size, selectedEntityId, getListViewActions, context, path, selectionController, openEntityMode, handleRowSelectionChange, handleEntityClick])}
|
|
569
|
+
/>
|
|
699
570
|
);
|
|
700
571
|
}
|
|
701
572
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useCallback, useMemo } from "react";
|
|
2
2
|
import { setIn } from "@rebasepro/formex";
|
|
3
|
-
import { EntityCollection, SaveEntityProps, RebaseData, RebaseContext, AnalyticsController } from "@rebasepro/types";
|
|
3
|
+
import { EntityCollection, SaveEntityProps, RebaseData, RebaseContext, AnalyticsController, Entity } from "@rebasepro/types";
|
|
4
4
|
import { saveEntityWithCallbacks } from "@rebasepro/core";
|
|
5
|
-
import { BoardItem } from "
|
|
5
|
+
import { BoardItem } from "@rebasepro/ui";
|
|
6
6
|
import { BoardDataController } from "../useBoardDataController";
|
|
7
7
|
import { generateKeyBetween } from "fractional-indexing";
|
|
8
8
|
|
|
@@ -33,10 +33,10 @@ export function useKanbanDragAndDrop<M extends Record<string, unknown>>({
|
|
|
33
33
|
// to compute a single new sort key for the moved item.
|
|
34
34
|
// Only one DB write per drag — the moved item gets its new key.
|
|
35
35
|
const handleItemsReorder = useCallback(async (
|
|
36
|
-
items: BoardItem<M
|
|
36
|
+
items: BoardItem<Entity<M>>[],
|
|
37
37
|
moveInfo?: { itemId: string; sourceColumn: string; targetColumn: string; }
|
|
38
38
|
) => {
|
|
39
|
-
const entity = items.find(item => item.id === moveInfo?.itemId)?.
|
|
39
|
+
const entity = items.find(item => item.id === moveInfo?.itemId)?.data;
|
|
40
40
|
if (!entity || !moveInfo) return;
|
|
41
41
|
|
|
42
42
|
analyticsController.onAnalyticsEvent?.("kanban_card_moved", {
|
|
@@ -64,10 +64,10 @@ export function useKanbanDragAndDrop<M extends Record<string, unknown>>({
|
|
|
64
64
|
|
|
65
65
|
// Get the order keys of the neighbours
|
|
66
66
|
const prevKey = movedIndex > 0
|
|
67
|
-
? (targetColumnItems[movedIndex - 1].
|
|
67
|
+
? (targetColumnItems[movedIndex - 1].data.values?.[orderProperty] as string | null) ?? null
|
|
68
68
|
: null;
|
|
69
69
|
const nextKey = movedIndex < targetColumnItems.length - 1
|
|
70
|
-
? (targetColumnItems[movedIndex + 1].
|
|
70
|
+
? (targetColumnItems[movedIndex + 1].data.values?.[orderProperty] as string | null) ?? null
|
|
71
71
|
: null;
|
|
72
72
|
|
|
73
73
|
try {
|
|
@@ -39,8 +39,9 @@ import {
|
|
|
39
39
|
getIcon
|
|
40
40
|
} from "@rebasepro/core";
|
|
41
41
|
import { getEntityFromMemoryCache } from "@rebasepro/core";
|
|
42
|
-
import {
|
|
43
|
-
import type {
|
|
42
|
+
import { EntityFormBinding } from "../form";
|
|
43
|
+
import type { EntityFormBindingProps } from "../form";
|
|
44
|
+
import type { OnUpdateParams } from "../types/components/EntityFormProps";
|
|
44
45
|
import { EntityEditViewFormActions } from "./EntityEditViewFormActions";
|
|
45
46
|
import { EntityJsonPreview } from "../components/EntityJsonPreview";
|
|
46
47
|
// Lazy-load history view — only loaded when user clicks the HistoryIcon tab
|
|
@@ -85,7 +86,7 @@ export interface EntityEditViewProps<M extends Record<string, unknown> = Record<
|
|
|
85
86
|
navigateBack?: () => void;
|
|
86
87
|
layout?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
87
88
|
barActions?: (params: BarActionsParams) => React.ReactNode;
|
|
88
|
-
formProps?: Partial<
|
|
89
|
+
formProps?: Partial<EntityFormBindingProps<M>>,
|
|
89
90
|
/**
|
|
90
91
|
* Pre-populate the form with these values when creating a new entity.
|
|
91
92
|
* Only applied when the form is in "new" mode (no entityId).
|
|
@@ -197,7 +198,7 @@ export function EntityEditViewInner<M extends Record<string, unknown>>({
|
|
|
197
198
|
}) {
|
|
198
199
|
|
|
199
200
|
const ResolvedFormActions = useComponentOverride("Entity.FormActions", EntityEditViewFormActions);
|
|
200
|
-
const ResolvedEntityForm = useComponentOverride("Entity.Form",
|
|
201
|
+
const ResolvedEntityForm = useComponentOverride("Entity.Form", EntityFormBinding) as typeof EntityFormBinding;
|
|
201
202
|
const ResolvedCollectionView = useComponentOverride("Collection.View", EntityCollectionView);
|
|
202
203
|
|
|
203
204
|
const context = useRebaseContext();
|