Skip to content

Commit 245bb36

Browse files
committed
feat(desktop): per-page size selector (50/100/250/500/1000)
Toolbar gets a 'Per page' select. Changing it snaps to the page boundary that contains the current offset so the user doesn't get jumped to row 1.
1 parent 4ab3bb2 commit 245bb36

1 file changed

Lines changed: 36 additions & 12 deletions

File tree

desktop/frontend/src/App.tsx

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ function suggestOutput(input: string, suffix: string, ext?: string) {
8686
return `${dir}${stem}.${suffix}${finalExt}`;
8787
}
8888

89-
const PAGE_SIZE = 100;
89+
const PAGE_SIZE_OPTIONS = [50, 100, 250, 500, 1000];
90+
const DEFAULT_PAGE_SIZE = 100;
9091

9192
// ---------- root -----------------------------------------------------------
9293

@@ -96,6 +97,7 @@ export default function App() {
9697
const [info, setInfo] = useState<main.FileInfo | null>(null);
9798
const [page, setPage] = useState<main.PagePayload | null>(null);
9899
const [offset, setOffset] = useState(0);
100+
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
99101
const [loadingFile, setLoadingFile] = useState(false);
100102
const [loadingPage, setLoadingPage] = useState(false);
101103
const [stats, setStats] = useState<main.StatsPayload | null>(null);
@@ -134,7 +136,7 @@ export default function App() {
134136
const i = await FileInfoCSV(path);
135137
setInfo(i);
136138
setOffset(0);
137-
const p = await ReadPage(path, 0, PAGE_SIZE);
139+
const p = await ReadPage(path, 0, pageSize);
138140
setPage(p);
139141
} finally {
140142
setLoadingFile(false);
@@ -146,18 +148,25 @@ export default function App() {
146148
if (p) await loadFile(p);
147149
}
148150

149-
async function gotoOffset(newOffset: number) {
151+
async function gotoOffset(newOffset: number, size = pageSize) {
150152
if (!info) return;
151153
setLoadingPage(true);
152154
try {
153-
const p = await ReadPage(info.path, Math.max(0, newOffset), PAGE_SIZE);
155+
const p = await ReadPage(info.path, Math.max(0, newOffset), size);
154156
setPage(p);
155157
setOffset(p.offset);
156158
} finally {
157159
setLoadingPage(false);
158160
}
159161
}
160162

163+
async function changePageSize(size: number) {
164+
setPageSize(size);
165+
// Snap to the page boundary that contains the current offset.
166+
const newOffset = Math.floor(offset / size) * size;
167+
await gotoOffset(newOffset, size);
168+
}
169+
161170
async function loadStatsIfNeeded(): Promise<main.StatsPayload | null> {
162171
if (stats) return stats;
163172
if (!info) return null;
@@ -180,9 +189,11 @@ export default function App() {
180189
<Toolbar
181190
info={info}
182191
offset={offset}
192+
pageSize={pageSize}
193+
onPageSize={changePageSize}
183194
page={page}
184-
onPrev={() => gotoOffset(offset - PAGE_SIZE)}
185-
onNext={() => gotoOffset(offset + PAGE_SIZE)}
195+
onPrev={() => gotoOffset(offset - pageSize)}
196+
onNext={() => gotoOffset(offset + pageSize)}
186197
onJump={(o) => gotoOffset(o)}
187198
onAction={setAction}
188199
/>
@@ -288,9 +299,11 @@ function FileInfoBar({ info }: { info: main.FileInfo }) {
288299
);
289300
}
290301

291-
function Toolbar({ info, offset, page, onPrev, onNext, onJump, onAction }: {
302+
function Toolbar({ info, offset, pageSize, onPageSize, page, onPrev, onNext, onJump, onAction }: {
292303
info: main.FileInfo;
293304
offset: number;
305+
pageSize: number;
306+
onPageSize: (n: number) => void;
294307
page: main.PagePayload | null;
295308
onPrev: () => void;
296309
onNext: () => void;
@@ -299,18 +312,18 @@ function Toolbar({ info, offset, page, onPrev, onNext, onJump, onAction }: {
299312
}) {
300313
const total = page?.totalRows ?? info.rows;
301314
const showingFrom = total === 0 ? 0 : offset + 1;
302-
const showingTo = Math.min(offset + PAGE_SIZE, total);
303-
const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
304-
const currentPage = Math.floor(offset / PAGE_SIZE) + 1;
315+
const showingTo = Math.min(offset + pageSize, total);
316+
const totalPages = Math.max(1, Math.ceil(total / pageSize));
317+
const currentPage = Math.floor(offset / pageSize) + 1;
305318
const canPrev = offset > 0;
306-
const canNext = offset + PAGE_SIZE < total;
319+
const canNext = offset + pageSize < total;
307320

308321
const [pageInput, setPageInput] = useState(String(currentPage));
309322
useEffect(() => setPageInput(String(currentPage)), [currentPage]);
310323

311324
function jumpFromInput() {
312325
const n = Math.max(1, Math.min(totalPages, Number(pageInput) || 1));
313-
onJump((n - 1) * PAGE_SIZE);
326+
onJump((n - 1) * pageSize);
314327
}
315328

316329
return (
@@ -339,6 +352,17 @@ function Toolbar({ info, offset, page, onPrev, onNext, onJump, onAction }: {
339352
</span>
340353

341354
<div className="ml-auto flex items-center gap-2">
355+
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
356+
<span>Per page</span>
357+
<Select value={String(pageSize)} onValueChange={(v) => onPageSize(Number(v))}>
358+
<SelectTrigger className="h-7 w-[78px]"><SelectValue /></SelectTrigger>
359+
<SelectContent>
360+
{PAGE_SIZE_OPTIONS.map((n) => (
361+
<SelectItem key={n} value={String(n)}>{n}</SelectItem>
362+
))}
363+
</SelectContent>
364+
</Select>
365+
</div>
342366
<DropdownMenu>
343367
<DropdownMenuTrigger asChild>
344368
<Button size="sm">

0 commit comments

Comments
 (0)