Skip to content

Commit 6d095b4

Browse files
authored
feat(datagrid): add page-size menu, first/last buttons, and jump-to-page to the pagination bar (#1364) (#1408)
1 parent f7cab94 commit 6d095b4

13 files changed

Lines changed: 463 additions & 188 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Cloudflare Tunnel: connect to a database behind Cloudflare Access by letting TablePro start and stop `cloudflared access tcp` for you, the same way it manages SSH tunnels. Configure it per connection with browser sign-in or a service token. Needs cloudflared installed (`brew install cloudflared`). (#1285)
1313
- Fill Column: right-click a column header and choose Fill Column to set one value across all loaded rows. The change is staged like a normal edit, so you review it and Save before it applies, and one undo reverts the whole fill. Not available on primary key columns. (#1304)
1414
- AWS IAM authentication for PostgreSQL and MySQL connections to RDS and Aurora. Pick AWS IAM in the connection's Authentication field and use an access key, a named AWS profile, or SSO. TablePro generates a fresh login token on every connect and reconnect, so you never paste an expiring token, and SSL is required automatically. (#1291)
15+
- Pagination bar for table tabs with a rows-per-page menu (5, 10, 20, 100, 500, 1,000, All rows, or a custom size) and First, Previous, Next, and Last page buttons. (#1364)
16+
- Click the page indicator in the pagination bar to jump to a specific page. (#1364)
17+
- Pagination now appears for filtered tables whose total row count is unknown, so you can page through them instead of seeing only the first page. (#1364)
18+
- First Page and Last Page keyboard actions, unbound by default and assignable in Settings > Keyboard. (#1364)
1519

1620
### Fixed
1721

TablePro/Core/Coordinators/PaginationCoordinator.swift

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ final class PaginationCoordinator {
2121
// MARK: - Pagination
2222

2323
func goToNextPage() {
24-
paginateIfPossible(where: \.hasNextPage) { $0.goToNextPage() }
24+
guard let (tab, tabIndex) = parent.tabManager.selectedTabAndIndex else { return }
25+
let loadedRowCount = parent.tabSessionRegistry.tableRows(for: tab.id).rows.count
26+
guard tab.pagination.canGoToNextPage(loadedRowCount: loadedRowCount) else { return }
27+
paginateAfterConfirmation(tabIndex: tabIndex) { $0.goToNextPage(loadedRowCount: loadedRowCount) }
2528
}
2629

2730
func goToPreviousPage() {
@@ -33,21 +36,50 @@ final class PaginationCoordinator {
3336
}
3437

3538
func goToLastPage() {
36-
paginateIfPossible(where: { $0.currentPage != $0.totalPages }) { $0.goToLastPage() }
39+
paginateIfPossible(where: { $0.isLastPageKnown && $0.currentPage != $0.totalPages }) { $0.goToLastPage() }
40+
}
41+
42+
func goToPage(_ page: Int) {
43+
paginateIfPossible(where: { $0.isLastPageKnown && page > 0 && page <= $0.totalPages }) { $0.goToPage(page) }
3744
}
3845

3946
func updatePageSize(_ newSize: Int) {
4047
guard newSize > 0 else { return }
4148
paginateIfPossible { $0.updatePageSize(newSize) }
4249
}
4350

44-
func updateOffset(_ newOffset: Int) {
45-
guard newOffset >= 0 else { return }
46-
paginateIfPossible { $0.updateOffset(newOffset) }
47-
}
51+
func showAllRows() {
52+
guard let (tab, _) = parent.tabManager.selectedTabAndIndex,
53+
let total = tab.pagination.totalRowCount, total > 0 else { return }
4854

49-
func applyPaginationSettings() {
50-
reloadCurrentPage()
55+
let tabId = tab.id
56+
let alert = NSAlert()
57+
alert.messageText = String(localized: "Show All Rows")
58+
alert.informativeText = String(
59+
format: String(localized: "This will load all %@ rows on a single page. Large result sets use significant memory. Continue?"),
60+
total.formatted()
61+
)
62+
alert.alertStyle = .warning
63+
alert.addButton(withTitle: String(localized: "Show All"))
64+
alert.addButton(withTitle: String(localized: "Cancel"))
65+
66+
let apply: () -> Void = { [weak self] in
67+
guard let self,
68+
let tabIndex = parent.tabManager.tabs.firstIndex(where: { $0.id == tabId }) else { return }
69+
paginateAfterConfirmation(tabIndex: tabIndex) { pagination in
70+
pagination.updatePageSize(max(total, 1))
71+
pagination.goToFirstPage()
72+
}
73+
}
74+
75+
if let window = parent.contentWindow ?? NSApp.keyWindow {
76+
alert.beginSheetModal(for: window) { response in
77+
guard response == .alertFirstButtonReturn else { return }
78+
apply()
79+
}
80+
} else if alert.runModal() == .alertFirstButtonReturn {
81+
apply()
82+
}
5183
}
5284

5385
private func paginateIfPossible(

TablePro/Models/Query/QueryTabState.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ struct PaginationState: Equatable {
142142
currentPage < totalPages
143143
}
144144

145+
var isLastPageKnown: Bool {
146+
totalRowCount != nil
147+
}
148+
149+
func canGoToNextPage(loadedRowCount: Int) -> Bool {
150+
if hasNextPage { return true }
151+
return totalRowCount == nil && loadedRowCount >= pageSize
152+
}
153+
145154
/// Whether there is a previous page available
146155
var hasPreviousPage: Bool {
147156
currentPage > 1
@@ -169,6 +178,12 @@ struct PaginationState: Equatable {
169178
currentOffset = (currentPage - 1) * pageSize
170179
}
171180

181+
mutating func goToNextPage(loadedRowCount: Int) {
182+
guard canGoToNextPage(loadedRowCount: loadedRowCount) else { return }
183+
currentPage += 1
184+
currentOffset = (currentPage - 1) * pageSize
185+
}
186+
172187
/// Navigate to previous page
173188
mutating func goToPreviousPage() {
174189
guard hasPreviousPage else { return }

TablePro/Models/UI/KeyboardShortcutModels.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {
5858
// Navigation
5959
case previousPage
6060
case nextPage
61+
case firstPage
62+
case lastPage
6163

6264
// Edit
6365
case undo
@@ -103,7 +105,7 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {
103105
.saveChanges, .saveAs, .previewSQL, .closeTab, .refresh,
104106
.executeQuery, .executeAllStatements, .cancelQuery, .explainQuery, .formatQuery,
105107
.export, .importData, .quickSwitcher,
106-
.previousPage, .nextPage, .saveAsFavorite:
108+
.previousPage, .nextPage, .firstPage, .lastPage, .saveAsFavorite:
107109
return .file
108110
case .undo, .redo, .cut, .copy, .copyRowsExplicit, .copyWithHeaders, .copyAsJson, .paste,
109111
.delete, .selectAll, .clearSelection, .addRow,
@@ -150,6 +152,8 @@ enum ShortcutAction: String, Codable, CaseIterable, Identifiable {
150152
case .quickSwitcher: return String(localized: "Quick Switcher")
151153
case .previousPage: return String(localized: "Previous Page")
152154
case .nextPage: return String(localized: "Next Page")
155+
case .firstPage: return String(localized: "First Page")
156+
case .lastPage: return String(localized: "Last Page")
153157
case .undo: return String(localized: "Undo")
154158
case .redo: return String(localized: "Redo")
155159
case .cut: return String(localized: "Cut")

0 commit comments

Comments
 (0)