Skip to content

Commit f93fb09

Browse files
committed
add page navigation
1 parent 6644012 commit f93fb09

File tree

2 files changed

+113
-11
lines changed

2 files changed

+113
-11
lines changed

src/webviews/extension-side/dataframe/dataframeController.ts

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ type SelectPageSizeCommand = {
2424
};
2525

2626
type GoToPageCommand = {
27+
cellId?: string;
28+
cellIndex?: number;
2729
command: 'goToPage';
28-
cellIndex: number;
2930
page: number;
3031
};
3132

@@ -145,14 +146,57 @@ export class DataframeController implements IExtensionSyncActivationService {
145146
});
146147
}
147148

148-
private handleGoToPage(editor: NotebookEditor, message: GoToPageCommand) {
149-
const cell = editor.notebook.cellAt(message.cellIndex);
150-
logger.info(
151-
`[DataframeRenderer] goToPage called for cell ${
152-
message.cellIndex
153-
} (${cell?.document.uri.toString()}), page=${message.page}`
154-
);
155-
// Could store current page in cell metadata if needed
149+
private async handleGoToPage(editor: NotebookEditor, message: GoToPageCommand) {
150+
let cell;
151+
let cellIndex: number;
152+
153+
// Try to find cell by cellId first (more reliable)
154+
if (message.cellId) {
155+
const cells = editor.notebook.getCells();
156+
const foundCell = cells.find((c) => c.metadata.id === message.cellId);
157+
158+
if (foundCell) {
159+
cell = foundCell;
160+
cellIndex = foundCell.index;
161+
logger.info(`[DataframeController] Found cell by cellId ${message.cellId} at index ${cellIndex}`);
162+
} else {
163+
const errorMessage = `Unable to navigate to page: Could not find the cell with ID ${message.cellId}. The cell may have been deleted.`;
164+
logger.error(`[DataframeController] ${errorMessage}`);
165+
await window.showErrorMessage(errorMessage);
166+
throw new Error(errorMessage);
167+
}
168+
} else {
169+
const errorMessage =
170+
'Unable to navigate to page: No cell identifier provided. ' +
171+
'Please re-run the cell to update the output metadata.';
172+
logger.error(`[DataframeController] ${errorMessage}`);
173+
await window.showErrorMessage(errorMessage);
174+
throw new Error(errorMessage);
175+
}
176+
177+
// Update page index in table state within cell metadata
178+
const existingTableState = cell.metadata.deepnote_table_state || {};
179+
const updatedTableState = {
180+
...existingTableState,
181+
pageIndex: message.page
182+
};
183+
184+
const edit = new WorkspaceEdit();
185+
const notebookEdit = NotebookEdit.updateCellMetadata(cellIndex, {
186+
...cell.metadata,
187+
deepnote_table_state: updatedTableState
188+
});
189+
190+
edit.set(editor.notebook.uri, [notebookEdit]);
191+
192+
await workspace.applyEdit(edit);
193+
194+
// Re-execute the cell to apply the new page
195+
logger.info(`[DataframeController] Re-executing cell ${cellIndex} with new page index ${message.page}`);
196+
await commands.executeCommand('notebook.cell.execute', {
197+
ranges: [{ start: cellIndex, end: cellIndex + 1 }],
198+
document: editor.notebook.uri
199+
});
156200
}
157201

158202
private async handleCopyTableData(message: CopyTableDataCommand) {

src/webviews/webview-side/dataframe-renderer/DataframeRenderer.tsx

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import React, { memo, useMemo, useState } from 'react';
22
import { RendererContext } from 'vscode-notebook-renderer';
33

4+
import '../react-common/codicon/codicon.css';
5+
46
export interface DataframeMetadata {
57
table_state_spec?: string;
68
}
@@ -45,13 +47,16 @@ export const DataframeRenderer = memo(function DataframeRenderer({
4547

4648
const tableState = useMemo((): TableState => JSON.parse(metadata?.table_state_spec || '{}'), [metadata]);
4749
const [pageSize, setPageSize] = useState(tableState.pageSize || 10);
50+
const [pageIndex, setPageIndex] = useState(tableState.pageIndex || 0);
4851

4952
console.log({ state: context.getState(), tableState });
5053

5154
const filteredColumns = data.columns.filter((column) => !column.name.startsWith('_deepnote_'));
5255
const numberOfRows = Math.min(data.row_count, data.preview_row_count);
5356
const numberOfColumns = filteredColumns.length;
5457

58+
const totalPages = Math.ceil(data.row_count / pageSize);
59+
5560
const handlePageSizeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
5661
const newPageSize = Number(event.target.value);
5762

@@ -71,6 +76,23 @@ export const DataframeRenderer = memo(function DataframeRenderer({
7176
context.postMessage?.(message);
7277
};
7378

79+
const handlePageChange = (newPageIndex: number) => {
80+
setPageIndex(newPageIndex);
81+
82+
console.log('[DataframeRenderer] handlePageChange called with cellId:', cellId, 'page:', newPageIndex);
83+
84+
const message = {
85+
command: 'goToPage',
86+
cellId,
87+
cellIndex,
88+
page: newPageIndex
89+
};
90+
91+
console.log('[DataframeRenderer] Posting message:', message);
92+
93+
context.postMessage?.(message);
94+
};
95+
7496
return (
7597
<div className="w-full">
7698
<div className="w-full overflow-x-auto">
@@ -103,7 +125,7 @@ export const DataframeRenderer = memo(function DataframeRenderer({
103125
})}
104126
</div>
105127
</div>
106-
<div className="px-[8px] py-[4px] flex justify-between items-center border-l border-r border-b border-[var(--vscode-panel-border)] font-mono">
128+
<div className="px-[8px] py-[12px] flex justify-between items-center border-l border-r border-b border-[var(--vscode-panel-border)] font-mono">
107129
<div className="flex gap-[4px] items-center">
108130
<div>
109131
{numberOfRows} rows, {numberOfColumns} columns
@@ -127,7 +149,43 @@ export const DataframeRenderer = memo(function DataframeRenderer({
127149
</div>
128150
</div>
129151

130-
<div></div>
152+
<div className="flex gap-[12px] items-center">
153+
<button
154+
className={`
155+
border border-[var(--vscode-panel-border)] bg-[var(--vscode-button-secondaryBackground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]
156+
text-[var(--vscode-button-secondaryForeground)]
157+
disabled:opacity-50 disabled:cursor-not-allowed
158+
flex items-center justify-center
159+
h-[20px] w-[20px]
160+
`}
161+
disabled={pageIndex === 0}
162+
title="Previous page"
163+
onClick={() => handlePageChange(pageIndex - 1)}
164+
>
165+
<div className="codicon codicon-chevron-left" style={{ fontSize: 12 }} />
166+
</button>
167+
<span className="">
168+
Page {pageIndex + 1} of {totalPages}
169+
</span>
170+
<button
171+
className={`
172+
border border-[var(--vscode-panel-border)] bg-[var(--vscode-button-secondaryBackground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]
173+
text-[var(--vscode-button-secondaryForeground)]
174+
disabled:opacity-50 disabled:cursor-not-allowed
175+
flex items-center justify-center
176+
h-[20px] w-[20px]
177+
`}
178+
disabled={pageIndex >= totalPages - 1}
179+
title="Next page"
180+
onClick={() => handlePageChange(pageIndex + 1)}
181+
>
182+
<div className="codicon codicon-chevron-right" style={{ fontSize: 12 }} />
183+
</button>
184+
</div>
185+
186+
<div>
187+
{/* Actions */}
188+
</div>
131189
</div>
132190
</div>
133191
);

0 commit comments

Comments
 (0)