feat(frontend): bidirectional lazy load for anchored grid returns
Returning to the grid at a deep position (deep link / hard reload to a file, then back → /files?anchor=<id>) used to load only a tiny forward window at the anchor. Now the grid fills the viewport around the anchor and pages in both directions as the user scrolls. - loadAroundAnchor fetches a window centred on the anchor and pre-fills a few pages each way sequentially, then centres on the anchor once. Doing the initial fill explicitly (rather than via the sentinels) keeps the pages contiguous and leaves the sentinels out of range, so there's no mount-time load storm. - loading starts true when the URL carries an ?anchor, so the child InfiniteScroll sentinels (whose effects run before this page's reset effect on mount) can't fire a stray page-1 loadMore that interleaves with loadAroundAnchor. - loadPrev pages backward (direction=backward) and prepends, then shifts the scroller down by the added height via flushSync (no paint between prepend and correction) so the viewport stays visually fixed. - InfiniteScroll gains an `edge` prop; a top instance (shown only when hasPrev) drives upward loading. Both loaders share the `loading` guard. - Mock: honour direction=backward and emit prev_cursor; the Go backend already supports backward keyset pagination. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -606,12 +606,25 @@ export function mockApiPlugin(): Plugin {
|
||||
return json(res, 200, { items: slice, next_cursor, prev_cursor });
|
||||
}
|
||||
|
||||
const direction = qs.get('direction') ?? 'forward';
|
||||
if (direction === 'backward' && cursor) {
|
||||
// Cursor marks the current top boundary; return the page before it.
|
||||
const end = Number(Buffer.from(cursor, 'base64').toString());
|
||||
const start = Math.max(0, end - limit);
|
||||
const slice = MOCK_FILES.slice(start, end);
|
||||
const prev_cursor = start > 0
|
||||
? Buffer.from(String(start)).toString('base64') : null;
|
||||
const next_cursor = Buffer.from(String(end)).toString('base64');
|
||||
return json(res, 200, { items: slice, next_cursor, prev_cursor });
|
||||
}
|
||||
const offset = cursor ? Number(Buffer.from(cursor, 'base64').toString()) : 0;
|
||||
const slice = MOCK_FILES.slice(offset, offset + limit);
|
||||
const nextOffset = offset + slice.length;
|
||||
const next_cursor = nextOffset < MOCK_FILES.length
|
||||
? Buffer.from(String(nextOffset)).toString('base64') : null;
|
||||
return json(res, 200, { items: slice, next_cursor, prev_cursor: null });
|
||||
const prev_cursor = offset > 0
|
||||
? Buffer.from(String(offset)).toString('base64') : null;
|
||||
return json(res, 200, { items: slice, next_cursor, prev_cursor });
|
||||
}
|
||||
|
||||
// GET /tags/{id}/rules
|
||||
|
||||
Reference in New Issue
Block a user