From b8f32901129d9f58ae6f54d66f4606c32c5ca851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Qu=E1=BB=91c=20=C4=90=E1=BA=A1t?= Date: Thu, 23 Apr 2026 13:50:00 +0700 Subject: [PATCH] feat: add SQL operator symbols to filter menu for quick visual recognition --- CHANGELOG.md | 1 + .../Core/Storage/FilterSettingsStorage.swift | 7 +++---- TablePro/Models/Database/TableFilter.swift | 21 +++++++++++++++++++ TablePro/Views/Filter/FilterRowView.swift | 15 +++++++++++-- docs/features/filtering.mdx | 2 +- 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c34701d8a..96b3435d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Filter operator picker shows SQL symbols alongside names for quick visual recognition - SQL autocomplete now suggests column names before a FROM clause is written, using all cached schema columns as fallback - Eager column cache warming after schema load for faster autocomplete - MCP query safety: three-tier classification with server-side confirmation for write and destructive queries diff --git a/TablePro/Core/Storage/FilterSettingsStorage.swift b/TablePro/Core/Storage/FilterSettingsStorage.swift index 64a29e92d..202a025c4 100644 --- a/TablePro/Core/Storage/FilterSettingsStorage.swift +++ b/TablePro/Core/Storage/FilterSettingsStorage.swift @@ -33,10 +33,9 @@ enum FilterDefaultOperator: String, CaseIterable, Identifiable, Codable { var id: String { rawValue } var displayName: String { - switch self { - case .equal: return "Equal (=)" - case .contains: return "Contains" - } + let op = toFilterOperator() + if op.symbol.isEmpty { return op.displayName } + return "\(op.symbol) \(op.displayName)" } func toFilterOperator() -> FilterOperator { diff --git a/TablePro/Models/Database/TableFilter.swift b/TablePro/Models/Database/TableFilter.swift index 6ad740f15..27a6be4c7 100644 --- a/TablePro/Models/Database/TableFilter.swift +++ b/TablePro/Models/Database/TableFilter.swift @@ -68,6 +68,27 @@ enum FilterOperator: String, CaseIterable, Identifiable, Codable { case .regex: return String(localized: "matches regex") } } + + /// SQL operator symbol for visual recognition in menus + var symbol: String { + switch self { + case .equal: return "=" + case .notEqual: return "!=" + case .greaterThan: return ">" + case .greaterOrEqual: return ">=" + case .lessThan: return "<" + case .lessOrEqual: return "<=" + case .contains: return "LIKE %..%" + case .notContains: return "NOT LIKE %..%" + case .startsWith: return "LIKE ..%" + case .endsWith: return "LIKE %.." + case .inList: return "IN (..)" + case .notInList: return "NOT IN (..)" + case .between: return "BETWEEN" + case .regex: return "~" + case .isNull, .isNotNull, .isEmpty, .isNotEmpty: return "" + } + } } /// Represents a single table filter condition diff --git a/TablePro/Views/Filter/FilterRowView.swift b/TablePro/Views/Filter/FilterRowView.swift index 48941e34f..100ceafe9 100644 --- a/TablePro/Views/Filter/FilterRowView.swift +++ b/TablePro/Views/Filter/FilterRowView.swift @@ -71,7 +71,7 @@ struct FilterRowView: View { private var operatorPicker: some View { Picker("", selection: $filter.filterOperator) { ForEach(FilterOperator.allCases) { op in - Text(op.displayName).tag(op) + OperatorMenuLabel(op: op).tag(op) } } .pickerStyle(.menu) @@ -130,7 +130,7 @@ struct FilterRowView: View { Text("—") .font(.callout) .foregroundStyle(.tertiary) - .frame(minWidth: 80, alignment: .leading) + .frame(maxWidth: .infinity, alignment: .leading) } } @@ -182,4 +182,15 @@ struct FilterRowView: View { Label(String(localized: "Remove Filter"), systemImage: "trash") } } + + // MARK: - Operator Menu Label + + private struct OperatorMenuLabel: View { + let op: FilterOperator + + var body: some View { + Text(op.symbol.isEmpty ? op.displayName : "\(op.symbol) \(op.displayName)") + .accessibilityLabel(op.displayName) + } + } } diff --git a/docs/features/filtering.mdx b/docs/features/filtering.mdx index eaab76fbd..b2ebf4313 100644 --- a/docs/features/filtering.mdx +++ b/docs/features/filtering.mdx @@ -13,7 +13,7 @@ Filters are per-tab. Tables with active filters open in a new tab when you click ## Operators -18 operators: `=`, `!=`, contains, not contains, starts with, ends with, `>`, `>=`, `<`, `<=`, IS NULL, IS NOT NULL, IS EMPTY, IS NOT EMPTY, IN, NOT IN, BETWEEN, REGEX. +18 operators with SQL symbols shown inline: `=`, `!=`, `LIKE %..%`, `NOT LIKE %..%`, `LIKE ..%`, `LIKE %..`, `>`, `>=`, `<`, `<=`, IS NULL, IS NOT NULL, IS EMPTY, IS NOT EMPTY, `IN (..)`, `NOT IN (..)`, `BETWEEN`, `~` (regex). BETWEEN shows two value fields. IN/NOT IN takes comma-separated values.