Skip to content

Commit 2f20aad

Browse files
authored
refactor(datagrid): de-duplicate pagination helpers and guard All rows on unknown totals (#1364) (#1411)
1 parent 6d095b4 commit 2f20aad

4 files changed

Lines changed: 85 additions & 72 deletions

File tree

TablePro/Core/Coordinators/PaginationCoordinator.swift

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -53,32 +53,43 @@ final class PaginationCoordinator {
5353
let total = tab.pagination.totalRowCount, total > 0 else { return }
5454

5555
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
56+
confirmLargeFetch(
57+
messageText: String(localized: "Show All Rows"),
58+
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+
confirmTitle: String(localized: "Show All")
63+
) { [weak self] in
6764
guard let self,
6865
let tabIndex = parent.tabManager.tabs.firstIndex(where: { $0.id == tabId }) else { return }
6966
paginateAfterConfirmation(tabIndex: tabIndex) { pagination in
7067
pagination.updatePageSize(max(total, 1))
7168
pagination.goToFirstPage()
7269
}
7370
}
71+
}
72+
73+
private func confirmLargeFetch(
74+
messageText: String,
75+
informativeText: String,
76+
confirmTitle: String,
77+
onConfirm: @escaping () -> Void
78+
) {
79+
let alert = NSAlert()
80+
alert.messageText = messageText
81+
alert.informativeText = informativeText
82+
alert.alertStyle = .warning
83+
alert.addButton(withTitle: confirmTitle)
84+
alert.addButton(withTitle: String(localized: "Cancel"))
7485

7586
if let window = parent.contentWindow ?? NSApp.keyWindow {
7687
alert.beginSheetModal(for: window) { response in
7788
guard response == .alertFirstButtonReturn else { return }
78-
apply()
89+
onConfirm()
7990
}
8091
} else if alert.runModal() == .alertFirstButtonReturn {
81-
apply()
92+
onConfirm()
8293
}
8394
}
8495

@@ -159,22 +170,12 @@ final class PaginationCoordinator {
159170
message = String(localized: "This will fetch all remaining rows. Large result sets use significant memory. Continue?")
160171
}
161172

162-
let alert = NSAlert()
163-
alert.messageText = String(localized: "Fetch All Rows")
164-
alert.informativeText = message
165-
alert.alertStyle = .warning
166-
alert.addButton(withTitle: String(localized: "Fetch All"))
167-
alert.addButton(withTitle: String(localized: "Cancel"))
168-
169-
let window = parent.contentWindow ?? NSApp.keyWindow
170-
if let window {
171-
alert.beginSheetModal(for: window) { [weak self] response in
172-
guard let self, response == .alertFirstButtonReturn else { return }
173-
performFetchAll(tabId: tab.id, baseQuery: baseQuery)
174-
}
175-
} else {
176-
let response = alert.runModal()
177-
guard response == .alertFirstButtonReturn else { return }
173+
confirmLargeFetch(
174+
messageText: String(localized: "Fetch All Rows"),
175+
informativeText: message,
176+
confirmTitle: String(localized: "Fetch All")
177+
) { [weak self] in
178+
guard let self else { return }
178179
performFetchAll(tabId: tab.id, baseQuery: baseQuery)
179180
}
180181
}

TablePro/Models/Query/QueryTabState.swift

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -171,43 +171,37 @@ struct PaginationState: Equatable {
171171

172172
// MARK: - Navigation Methods
173173

174-
/// Navigate to next page
174+
private mutating func setPage(_ page: Int) {
175+
currentPage = page
176+
currentOffset = (page - 1) * pageSize
177+
}
178+
175179
mutating func goToNextPage() {
176180
guard hasNextPage else { return }
177-
currentPage += 1
178-
currentOffset = (currentPage - 1) * pageSize
181+
setPage(currentPage + 1)
179182
}
180183

181184
mutating func goToNextPage(loadedRowCount: Int) {
182185
guard canGoToNextPage(loadedRowCount: loadedRowCount) else { return }
183-
currentPage += 1
184-
currentOffset = (currentPage - 1) * pageSize
186+
setPage(currentPage + 1)
185187
}
186188

187-
/// Navigate to previous page
188189
mutating func goToPreviousPage() {
189190
guard hasPreviousPage else { return }
190-
currentPage -= 1
191-
currentOffset = (currentPage - 1) * pageSize
191+
setPage(currentPage - 1)
192192
}
193193

194-
/// Navigate to first page
195194
mutating func goToFirstPage() {
196-
currentPage = 1
197-
currentOffset = 0
195+
setPage(1)
198196
}
199197

200-
/// Navigate to last page
201198
mutating func goToLastPage() {
202-
currentPage = totalPages
203-
currentOffset = (totalPages - 1) * pageSize
199+
setPage(totalPages)
204200
}
205201

206-
/// Navigate to specific page
207202
mutating func goToPage(_ page: Int) {
208203
guard page > 0 && page <= totalPages else { return }
209-
currentPage = page
210-
currentOffset = (page - 1) * pageSize
204+
setPage(page)
211205
}
212206

213207
/// Reset pagination to first page

TablePro/Views/Components/PaginationControlsView.swift

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ struct PaginationControlsView: View {
4949
Divider()
5050

5151
Button(String(localized: "All rows…")) { onShowAll() }
52+
.disabled(!pagination.isLastPageKnown)
5253
Button(String(localized: "Custom…")) {
5354
customText = "\(pagination.pageSize)"
5455
showCustomPopover = true
@@ -167,39 +168,54 @@ struct PaginationControlsView: View {
167168
// MARK: - Popovers
168169

169170
private var jumpPopover: some View {
170-
VStack(alignment: .leading, spacing: 8) {
171-
Text("Go to page")
172-
.font(.caption)
173-
.foregroundStyle(.secondary)
174-
HStack {
175-
TextField("", text: $jumpText)
176-
.frame(width: 70)
177-
.focused($isJumpFocused)
178-
.onSubmit(submitJump)
179-
Button("Go", action: submitJump)
180-
.keyboardShortcut(.defaultAction)
181-
}
182-
}
183-
.padding(12)
184-
.onAppear { isJumpFocused = true }
171+
submitPopover(
172+
caption: "Go to page",
173+
text: $jumpText,
174+
fieldWidth: 70,
175+
isFocused: $isJumpFocused,
176+
fieldAccessibilityLabel: String(localized: "Page number"),
177+
buttonTitle: "Go",
178+
action: submitJump
179+
)
185180
}
186181

187182
private var customPageSizePopover: some View {
183+
submitPopover(
184+
caption: "Rows per page",
185+
text: $customText,
186+
fieldWidth: 80,
187+
isFocused: $isCustomFocused,
188+
fieldAccessibilityLabel: String(localized: "Rows per page"),
189+
buttonTitle: "Apply",
190+
action: submitCustom
191+
)
192+
}
193+
194+
private func submitPopover(
195+
caption: LocalizedStringKey,
196+
text: Binding<String>,
197+
fieldWidth: CGFloat,
198+
isFocused: FocusState<Bool>.Binding,
199+
fieldAccessibilityLabel: String,
200+
buttonTitle: LocalizedStringKey,
201+
action: @escaping () -> Void
202+
) -> some View {
188203
VStack(alignment: .leading, spacing: 8) {
189-
Text("Rows per page")
204+
Text(caption)
190205
.font(.caption)
191206
.foregroundStyle(.secondary)
192207
HStack {
193-
TextField("", text: $customText)
194-
.frame(width: 80)
195-
.focused($isCustomFocused)
196-
.onSubmit(submitCustom)
197-
Button("Apply", action: submitCustom)
208+
TextField("", text: text)
209+
.frame(width: fieldWidth)
210+
.focused(isFocused)
211+
.onSubmit(action)
212+
.accessibilityLabel(fieldAccessibilityLabel)
213+
Button(buttonTitle, action: action)
198214
.keyboardShortcut(.defaultAction)
199215
}
200216
}
201217
.padding(12)
202-
.onAppear { isCustomFocused = true }
218+
.onAppear { isFocused.wrappedValue = true }
203219
}
204220

205221
// MARK: - Actions

TablePro/Views/Main/Child/MainStatusBarView.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ struct MainStatusBarView: View {
185185
.help(String(localized: "Toggle Filters (⇧⌘F)"))
186186
}
187187

188-
// Pagination controls for table tabs
189188
if snapshot.tabType == .table, snapshot.hasTableName, showsPaginationControls {
190189
PaginationControlsView(
191190
pagination: snapshot.pagination,
@@ -210,12 +209,15 @@ struct MainStatusBarView: View {
210209
}
211210

212211
private var showsPaginationControls: Bool {
212+
if let total = snapshot.pagination.totalRowCount, total > 0 { return true }
213+
return isPagedWithUnknownTotal
214+
}
215+
216+
private var isPagedWithUnknownTotal: Bool {
213217
let pagination = snapshot.pagination
214-
if let total = pagination.totalRowCount, total > 0 { return true }
215218
return pagination.currentPage > 1 || snapshot.rowCount >= pagination.pageSize
216219
}
217220

218-
/// Generate row info text based on selection and pagination state
219221
private var rowInfoText: String {
220222
let loadedCount = snapshot.rowCount
221223
let selectedCount = selectedRowIndices.count
@@ -236,7 +238,7 @@ struct MainStatusBarView: View {
236238
let prefix = pagination.isApproximateRowCount ? "~" : ""
237239

238240
return String(format: String(localized: "%d-%d of %@%@ rows"), pagination.rangeStart, pagination.rangeEnd, prefix, formattedTotal)
239-
} else if snapshot.tabType == .table, pagination.currentPage > 1 || loadedCount >= pagination.pageSize {
241+
} else if snapshot.tabType == .table, isPagedWithUnknownTotal {
240242
let rangeEnd = pagination.currentOffset + loadedCount
241243
return String(format: String(localized: "%d-%d of ? rows"), pagination.rangeStart, rangeEnd)
242244
} else if loadedCount > 0 {

0 commit comments

Comments
 (0)