mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-31 14:16:39 +01:00
Fix issue list scroll pagination
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
cc8e31c05a
commit
fce4e9eadc
3 files changed
+82
-8
No files matched your search
@@ -908,6 +908,57 @@ describe("IssuesList", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("waits for the desktop main scroll container before rendering more local rows", async () => {
|
||||
const manyIssues = Array.from({ length: 420 }, (_, index) =>
|
||||
createIssue({
|
||||
id: `issue-${index + 1}`,
|
||||
identifier: `PAP-${index + 1}`,
|
||||
title: `Issue ${index + 1}`,
|
||||
}),
|
||||
);
|
||||
const main = document.createElement("main");
|
||||
main.id = "main-content";
|
||||
main.style.overflowY = "auto";
|
||||
document.body.appendChild(main);
|
||||
main.appendChild(container);
|
||||
Object.defineProperty(main, "clientHeight", { configurable: true, value: 600 });
|
||||
Object.defineProperty(main, "scrollHeight", { configurable: true, value: 2000 });
|
||||
Object.defineProperty(main, "scrollTop", { configurable: true, writable: true, value: 0 });
|
||||
setDocumentScrollMetrics({ innerHeight: 600, scrollY: 0, scrollHeight: 600 });
|
||||
|
||||
const { root } = renderWithQueryClient(
|
||||
<IssuesList
|
||||
issues={manyIssues}
|
||||
agents={[]}
|
||||
projects={[]}
|
||||
viewStateKey="paperclip:test-issues"
|
||||
onUpdateIssue={() => undefined}
|
||||
/>,
|
||||
container,
|
||||
);
|
||||
|
||||
await waitForAssertion(() => {
|
||||
expect(container.querySelectorAll('[data-testid="issue-row"]')).toHaveLength(100);
|
||||
});
|
||||
|
||||
await flush();
|
||||
await flush();
|
||||
expect(container.querySelectorAll('[data-testid="issue-row"]')).toHaveLength(100);
|
||||
|
||||
act(() => {
|
||||
main.scrollTop = 1500;
|
||||
main.dispatchEvent(new Event("scroll"));
|
||||
});
|
||||
|
||||
await waitForAssertion(() => {
|
||||
expect(container.querySelectorAll('[data-testid="issue-row"]').length).toBeGreaterThan(100);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
root.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
it("requests more server issues after scrolling past the rendered rows", async () => {
|
||||
const visibleIssues = Array.from({ length: 100 }, (_, index) =>
|
||||
createIssue({
|
||||
|
||||
@@ -73,6 +73,22 @@ const ISSUE_BOARD_COLUMN_RESULT_LIMIT = 200;
|
||||
const INITIAL_ISSUE_ROW_RENDER_LIMIT = 100;
|
||||
const ISSUE_ROW_RENDER_BATCH_SIZE = 150;
|
||||
const ISSUE_SCROLL_LOAD_THRESHOLD_PX = 320;
|
||||
|
||||
function findIssuesScrollContainer(element: HTMLElement | null): HTMLElement | null {
|
||||
if (!element || typeof window === "undefined") return null;
|
||||
let current = element.parentElement;
|
||||
while (current && current !== document.body && current !== document.documentElement) {
|
||||
const overflowY = window.getComputedStyle(current).overflowY;
|
||||
if (
|
||||
(overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay")
|
||||
&& current.scrollHeight > current.clientHeight
|
||||
) {
|
||||
return current;
|
||||
}
|
||||
current = current.parentElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
const boardIssueStatuses = ISSUE_STATUSES;
|
||||
const issueStatusLabels: Record<IssueStatus, string> = {
|
||||
backlog: "Backlog",
|
||||
@@ -486,6 +502,7 @@ export function IssuesList({
|
||||
onSearchChange,
|
||||
onUpdateIssue,
|
||||
}: IssuesListProps) {
|
||||
const rootRef = useRef<HTMLDivElement | null>(null);
|
||||
const { selectedCompanyId } = useCompany();
|
||||
const { openNewIssue } = useDialogActions();
|
||||
const { data: session } = useQuery({
|
||||
@@ -1016,27 +1033,31 @@ export function IssuesList({
|
||||
useEffect(() => {
|
||||
if (!canLoadMoreIssues) return;
|
||||
let animationFrameId: number | null = null;
|
||||
const scrollContainer = findIssuesScrollContainer(rootRef.current);
|
||||
const scrollTarget: Window | HTMLElement = scrollContainer ?? window;
|
||||
|
||||
const checkScrollPosition = () => {
|
||||
if (animationFrameId !== null) return;
|
||||
animationFrameId = window.requestAnimationFrame(() => {
|
||||
animationFrameId = null;
|
||||
const documentElement = document.documentElement;
|
||||
if (documentElement.scrollHeight === 0) return;
|
||||
const scrollBottom = window.scrollY + window.innerHeight;
|
||||
const threshold = documentElement.scrollHeight - ISSUE_SCROLL_LOAD_THRESHOLD_PX;
|
||||
const scrollHeight = scrollContainer?.scrollHeight ?? document.documentElement.scrollHeight;
|
||||
if (scrollHeight === 0) return;
|
||||
const scrollBottom = scrollContainer
|
||||
? scrollContainer.scrollTop + scrollContainer.clientHeight
|
||||
: window.scrollY + window.innerHeight;
|
||||
const threshold = scrollHeight - ISSUE_SCROLL_LOAD_THRESHOLD_PX;
|
||||
if (scrollBottom >= threshold) {
|
||||
loadMoreIssueRows();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", checkScrollPosition, { passive: true });
|
||||
scrollTarget.addEventListener("scroll", checkScrollPosition, { passive: true });
|
||||
window.addEventListener("resize", checkScrollPosition);
|
||||
checkScrollPosition();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", checkScrollPosition);
|
||||
scrollTarget.removeEventListener("scroll", checkScrollPosition);
|
||||
window.removeEventListener("resize", checkScrollPosition);
|
||||
if (animationFrameId !== null) window.cancelAnimationFrame(animationFrameId);
|
||||
};
|
||||
@@ -1094,7 +1115,7 @@ export function IssuesList({
|
||||
let remainingRowsToRender = viewState.viewMode === "list" ? renderedIssueRowLimit : Number.POSITIVE_INFINITY;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div ref={rootRef} className="space-y-4">
|
||||
{progressSummary ? (
|
||||
<SubIssueProgressSummaryStrip summary={progressSummary} issueLinkState={issueLinkState} />
|
||||
) : null}
|
||||
|
||||
Reference in new issue
Block a user