diff --git a/CHANGELOG.md b/CHANGELOG.md index 32625cb83..7612e948f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Switcher, menus, and alerts now use each database's own container name: Dataset for BigQuery, Keyspace for Cassandra and ScyllaDB. (#509) +- Filter panel header "Unset" renamed to "Clear": it now keeps filter rows in place and only removes the applied state, returning the table to unfiltered results. Use "Remove All Filters" in the filter options menu to discard all filter rows at once. +- Per-row Apply and Applied buttons removed from the filter panel; "Apply Only This Filter" is now in each row's right-click context menu. +- A tri-state checkbox in the filter panel header toggles all filter rows enabled or disabled at once. ### Fixed diff --git a/TablePro/Views/Filter/FilterPanelView.swift b/TablePro/Views/Filter/FilterPanelView.swift index 7c413aa74..f594f20e3 100644 --- a/TablePro/Views/Filter/FilterPanelView.swift +++ b/TablePro/Views/Filter/FilterPanelView.swift @@ -67,8 +67,43 @@ struct FilterPanelView: View { .onPreferenceChange(FilterRowsHeightKey.self) { filterRowsHeight = $0 } } + private var allFiltersCheckboxImage: String { + switch allFiltersEnabledState { + case true: return "checkmark.square.fill" + case false: return "square" + case .none: return "minus.square.fill" + } + } + + private var allFiltersEnabledState: Bool? { + guard !filterState.filters.isEmpty else { return false } + let enabledCount = filterState.filters.count { $0.isEnabled } + if enabledCount == filterState.filters.count { return true } + if enabledCount == 0 { return false } + return nil + } + + private func toggleAllFiltersEnabled() { + let allEnabled = filterState.filters.allSatisfy { $0.isEnabled } + let newState = !allEnabled + for filter in filterState.filters { + var updated = filter + updated.isEnabled = newState + coordinator.updateFilter(updated) + } + } + private var filterHeader: some View { HStack(spacing: 8) { + if !filterState.filters.isEmpty { + Button(action: toggleAllFiltersEnabled) { + Image(systemName: allFiltersCheckboxImage) + .foregroundStyle(.primary) + } + .buttonStyle(.plain) + .help(String(localized: "Enable or disable all filters")) + } + Text("Filters") .font(.callout.weight(.medium)) @@ -88,15 +123,15 @@ struct FilterPanelView: View { filterOptionsMenu - Button("Unset") { - coordinator.clearFilterState() + Button("Clear") { + coordinator.clearAppliedFilters() onUnset() coordinator.focusActiveGrid() } .buttonStyle(.bordered) .controlSize(.small) .disabled(!filterState.hasAppliedFilters) - .help(String(localized: "Remove all filters and reload")) + .help(String(localized: "Clear applied filters without removing filter rows")) Button("Apply") { applyAllValidFilters() @@ -172,6 +207,17 @@ struct FilterPanelView: View { Divider() + Button(role: .destructive) { + coordinator.clearFilterState() + onUnset() + coordinator.focusActiveGrid() + } label: { + Label(String(localized: "Remove All Filters"), systemImage: "xmark.circle") + } + .disabled(filterState.filters.isEmpty) + + Divider() + Button { showSettingsPopover.toggle() } label: { @@ -198,7 +244,6 @@ struct FilterPanelView: View { completions: completionItems(), enumValuesByColumn: enumValuesByColumn, rawSQLCompletionProvider: rawSQLCompletionProvider, - isApplied: filterState.commit == .solo(filter.id), onAdd: { coordinator.addFilter(columns: columns, primaryKeyColumn: primaryKeyColumn) focusedFilterId = filterState.filters.last?.id diff --git a/TablePro/Views/Filter/FilterRowView.swift b/TablePro/Views/Filter/FilterRowView.swift index c9ebcc9a9..b0491e613 100644 --- a/TablePro/Views/Filter/FilterRowView.swift +++ b/TablePro/Views/Filter/FilterRowView.swift @@ -11,7 +11,6 @@ struct FilterRowView: View { let completions: [String] var enumValuesByColumn: [String: [String]] = [:] var rawSQLCompletionProvider: RawSQLFilterCompletionProvider? - let isApplied: Bool let onAdd: () -> Void let onDuplicate: () -> Void let onRemove: () -> Void @@ -160,28 +159,8 @@ struct FilterRowView: View { } } - @ViewBuilder - private var soloApplyButton: some View { - if isApplied { - Button(String(localized: "Applied"), action: onApply) - .buttonStyle(.borderedProminent) - } else { - Button(String(localized: "Apply"), action: onApply) - .buttonStyle(.bordered) - } - } - private var rowButtons: some View { HStack(spacing: 4) { - soloApplyButton - .controlSize(.small) - .disabled(!filter.isValid) - .accessibilityLabel(String(localized: "Apply only this filter")) - .accessibilityValue(isApplied ? String(localized: "Applied") : "") - .help(isApplied - ? String(localized: "Filtering by only this row") - : String(localized: "Filter by only this row")) - Button(action: onAdd) { Image(systemName: "plus") .frame(width: rowButtonGlyphSize, height: rowButtonGlyphSize) @@ -204,6 +183,15 @@ struct FilterRowView: View { @ViewBuilder private var rowContextMenu: some View { + Button { + onApply() + } label: { + Label(String(localized: "Apply Only This Filter"), systemImage: "checkmark.circle") + } + .disabled(!filter.isValid) + + Divider() + Button { onAdd() } label: {