Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- `SettablePlugin` protocol in TableProPluginKit SDK: unified settings pattern for all plugins with automatic persistence via `loadSettings()`/`saveSettings()`, replacing duplicated boilerplate across export/import/driver plugins
- Plugin UI/capability metadata: each driver plugin now self-declares brand color, connection mode, supported features, column types, URL schemes, and grouping strategy via the `DriverPlugin` protocol
- Driver plugin settings view support: `DriverPlugin.settingsView()` allows plugins to provide custom settings UI in the Installed Plugins panel
- Dynamic connection fields: connection form Advanced tab now renders fields from `DriverPlugin.additionalConnectionFields` instead of hardcoded per-database sections, with support for text, secure, and dropdown field types
Expand Down
16 changes: 8 additions & 8 deletions Plugins/CSVExportPlugin/CSVExportOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ struct CSVExportOptionsView: View {
var body: some View {
VStack(alignment: .leading, spacing: 10) {
VStack(alignment: .leading, spacing: 8) {
Toggle("Convert NULL to EMPTY", isOn: $plugin.options.convertNullToEmpty)
Toggle("Convert NULL to EMPTY", isOn: $plugin.settings.convertNullToEmpty)
.toggleStyle(.checkbox)

Toggle("Convert line break to space", isOn: $plugin.options.convertLineBreakToSpace)
Toggle("Convert line break to space", isOn: $plugin.settings.convertLineBreakToSpace)
.toggleStyle(.checkbox)

Toggle("Put field names in the first row", isOn: $plugin.options.includeFieldNames)
Toggle("Put field names in the first row", isOn: $plugin.settings.includeFieldNames)
.toggleStyle(.checkbox)

Toggle("Sanitize formula-like values", isOn: $plugin.options.sanitizeFormulas)
Toggle("Sanitize formula-like values", isOn: $plugin.settings.sanitizeFormulas)
.toggleStyle(.checkbox)
.help("Prevent CSV formula injection by prefixing values starting with =, +, -, @ with a single quote")
}
Expand All @@ -30,7 +30,7 @@ struct CSVExportOptionsView: View {

VStack(alignment: .leading, spacing: 10) {
optionRow(String(localized: "Delimiter", bundle: .main)) {
Picker("", selection: $plugin.options.delimiter) {
Picker("", selection: $plugin.settings.delimiter) {
ForEach(CSVDelimiter.allCases) { delimiter in
Text(delimiter.displayName).tag(delimiter)
}
Expand All @@ -41,7 +41,7 @@ struct CSVExportOptionsView: View {
}

optionRow(String(localized: "Quote", bundle: .main)) {
Picker("", selection: $plugin.options.quoteHandling) {
Picker("", selection: $plugin.settings.quoteHandling) {
ForEach(CSVQuoteHandling.allCases) { handling in
Text(handling.rawValue).tag(handling)
}
Expand All @@ -52,7 +52,7 @@ struct CSVExportOptionsView: View {
}

optionRow(String(localized: "Line break", bundle: .main)) {
Picker("", selection: $plugin.options.lineBreak) {
Picker("", selection: $plugin.settings.lineBreak) {
ForEach(CSVLineBreak.allCases) { lineBreak in
Text(lineBreak.rawValue).tag(lineBreak)
}
Expand All @@ -63,7 +63,7 @@ struct CSVExportOptionsView: View {
}

optionRow(String(localized: "Decimal", bundle: .main)) {
Picker("", selection: $plugin.options.decimalFormat) {
Picker("", selection: $plugin.settings.decimalFormat) {
ForEach(CSVDecimalFormat.allCases) { format in
Text(format.rawValue).tag(format)
}
Expand Down
26 changes: 11 additions & 15 deletions Plugins/CSVExportPlugin/CSVExportPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import SwiftUI
import TableProPluginKit

@Observable
final class CSVExportPlugin: ExportFormatPlugin {
final class CSVExportPlugin: ExportFormatPlugin, SettablePlugin {
static let pluginName = "CSV Export"
static let pluginVersion = "1.0.0"
static let pluginDescription = "Export data to CSV format"
Expand All @@ -20,19 +20,16 @@ final class CSVExportPlugin: ExportFormatPlugin {
// swiftlint:disable:next force_try
static let decimalFormatRegex = try! NSRegularExpression(pattern: #"^[+-]?\d+\.\d+$"#)

private let storage = PluginSettingsStorage(pluginId: "csv")
typealias Settings = CSVExportOptions
static let settingsStorageId = "csv"

var options = CSVExportOptions() {
didSet { storage.save(options) }
var settings = CSVExportOptions() {
didSet { saveSettings() }
}

required init() {
if let saved = storage.load(CSVExportOptions.self) {
options = saved
}
}
required init() { loadSettings() }

func optionsView() -> AnyView? {
func settingsView() -> AnyView? {
AnyView(CSVExportOptionsView(plugin: self))
}

Expand All @@ -45,7 +42,7 @@ final class CSVExportPlugin: ExportFormatPlugin {
let fileHandle = try PluginExportUtilities.createFileHandle(at: destination)
defer { try? fileHandle.close() }

let lineBreak = options.lineBreak.value
let lineBreak = settings.lineBreak.value

for (index, table) in tables.enumerated() {
try progress.checkCancellation()
Expand Down Expand Up @@ -73,15 +70,15 @@ final class CSVExportPlugin: ExportFormatPlugin {

if result.rows.isEmpty { break }

var batchOptions = options
var batchSettings = settings
if !isFirstBatch {
batchOptions.includeFieldNames = false
batchSettings.includeFieldNames = false
}

try writeCSVContent(
columns: result.columns,
rows: result.rows,
options: batchOptions,
options: batchSettings,
to: fileHandle,
progress: progress
)
Expand Down Expand Up @@ -180,5 +177,4 @@ final class CSVExportPlugin: ExportFormatPlugin {
return processed
}
}

}
6 changes: 3 additions & 3 deletions Plugins/JSONExportPlugin/JSONExportOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ struct JSONExportOptionsView: View {

var body: some View {
VStack(alignment: .leading, spacing: 8) {
Toggle("Pretty print (formatted output)", isOn: $plugin.options.prettyPrint)
Toggle("Pretty print (formatted output)", isOn: $plugin.settings.prettyPrint)
.toggleStyle(.checkbox)

Toggle("Include NULL values", isOn: $plugin.options.includeNullValues)
Toggle("Include NULL values", isOn: $plugin.settings.includeNullValues)
.toggleStyle(.checkbox)

Toggle("Preserve all values as strings", isOn: $plugin.options.preserveAllAsStrings)
Toggle("Preserve all values as strings", isOn: $plugin.settings.preserveAllAsStrings)
.toggleStyle(.checkbox)
.help("Keep leading zeros in ZIP codes, phone numbers, and IDs by outputting all values as strings")
}
Expand Down
24 changes: 10 additions & 14 deletions Plugins/JSONExportPlugin/JSONExportPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import SwiftUI
import TableProPluginKit

@Observable
final class JSONExportPlugin: ExportFormatPlugin {
final class JSONExportPlugin: ExportFormatPlugin, SettablePlugin {
static let pluginName = "JSON Export"
static let pluginVersion = "1.0.0"
static let pluginDescription = "Export data to JSON format"
Expand All @@ -17,19 +17,16 @@ final class JSONExportPlugin: ExportFormatPlugin {
static let defaultFileExtension = "json"
static let iconName = "curlybraces"

private let storage = PluginSettingsStorage(pluginId: "json")
typealias Settings = JSONExportOptions
static let settingsStorageId = "json"

var options = JSONExportOptions() {
didSet { storage.save(options) }
var settings = JSONExportOptions() {
didSet { saveSettings() }
}

required init() {
if let saved = storage.load(JSONExportOptions.self) {
options = saved
}
}
required init() { loadSettings() }

func optionsView() -> AnyView? {
func settingsView() -> AnyView? {
AnyView(JSONExportOptionsView(plugin: self))
}

Expand All @@ -42,7 +39,7 @@ final class JSONExportPlugin: ExportFormatPlugin {
let fileHandle = try PluginExportUtilities.createFileHandle(at: destination)
defer { try? fileHandle.close() }

let prettyPrint = options.prettyPrint
let prettyPrint = settings.prettyPrint
let indent = prettyPrint ? " " : ""
let newline = prettyPrint ? "\n" : ""

Expand Down Expand Up @@ -95,7 +92,7 @@ final class JSONExportPlugin: ExportFormatPlugin {
for (colIndex, column) in columns.enumerated() {
if colIndex < row.count {
let value = row[colIndex]
if options.includeNullValues || value != nil {
if settings.includeNullValues || value != nil {
if !isFirstField {
rowString += ", "
}
Expand All @@ -104,7 +101,7 @@ final class JSONExportPlugin: ExportFormatPlugin {
let escapedKey = PluginExportUtilities.escapeJSONString(column)
let jsonValue = formatJSONValue(
value,
preserveAsString: options.preserveAllAsStrings
preserveAsString: settings.preserveAllAsStrings
)
rowString += "\"\(escapedKey)\": \(jsonValue)"
}
Expand Down Expand Up @@ -167,5 +164,4 @@ final class JSONExportPlugin: ExportFormatPlugin {

return "\"\(PluginExportUtilities.escapeJSONString(val))\""
}

}
2 changes: 1 addition & 1 deletion Plugins/MQLExportPlugin/MQLExportOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct MQLExportOptionsView: View {

Spacer()

Picker("", selection: $plugin.options.batchSize) {
Picker("", selection: $plugin.settings.batchSize) {
ForEach(Self.batchSizeOptions, id: \.self) { size in
Text("\(size)")
.tag(size)
Expand Down
20 changes: 8 additions & 12 deletions Plugins/MQLExportPlugin/MQLExportPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import SwiftUI
import TableProPluginKit

@Observable
final class MQLExportPlugin: ExportFormatPlugin {
final class MQLExportPlugin: ExportFormatPlugin, SettablePlugin {
static let pluginName = "MQL Export"
static let pluginVersion = "1.0.0"
static let pluginDescription = "Export data to MongoDB Query Language format"
Expand All @@ -24,17 +24,14 @@ final class MQLExportPlugin: ExportFormatPlugin {
PluginExportOptionColumn(id: "data", label: "Data", width: 44)
]

private let storage = PluginSettingsStorage(pluginId: "mql")
typealias Settings = MQLExportOptions
static let settingsStorageId = "mql"

var options = MQLExportOptions() {
didSet { storage.save(options) }
var settings = MQLExportOptions() {
didSet { saveSettings() }
}

required init() {
if let saved = storage.load(MQLExportOptions.self) {
options = saved
}
}
required init() { loadSettings() }

func defaultTableOptionValues() -> [Bool] {
[true, true, true]
Expand All @@ -44,7 +41,7 @@ final class MQLExportPlugin: ExportFormatPlugin {
optionValues.contains(true)
}

func optionsView() -> AnyView? {
func settingsView() -> AnyView? {
AnyView(MQLExportOptionsView(plugin: self))
}

Expand All @@ -67,7 +64,7 @@ final class MQLExportPlugin: ExportFormatPlugin {
}
try fileHandle.write(contentsOf: "\n".toUTF8Data())

let batchSize = options.batchSize
let batchSize = settings.batchSize

for (index, table) in tables.enumerated() {
try progress.checkCancellation()
Expand Down Expand Up @@ -219,5 +216,4 @@ final class MQLExportPlugin: ExportFormatPlugin {
try fileHandle.write(contentsOf: "\(indexContent)\n".toUTF8Data())
}
}

}
4 changes: 2 additions & 2 deletions Plugins/SQLExportPlugin/SQLExportOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct SQLExportOptionsView: View {

Spacer()

Picker("", selection: $plugin.options.batchSize) {
Picker("", selection: $plugin.settings.batchSize) {
ForEach(Self.batchSizeOptions, id: \.self) { size in
Text(size == 1 ? String(localized: "1 (no batching)", bundle: .main) : "\(size)")
.tag(size)
Expand All @@ -38,7 +38,7 @@ struct SQLExportOptionsView: View {
}
.help("Higher values create fewer INSERT statements, resulting in smaller files and faster imports")

Toggle("Compress the file using Gzip", isOn: $plugin.options.compressWithGzip)
Toggle("Compress the file using Gzip", isOn: $plugin.settings.compressWithGzip)
.toggleStyle(.checkbox)
.font(.system(size: 13))
}
Expand Down
25 changes: 11 additions & 14 deletions Plugins/SQLExportPlugin/SQLExportPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SwiftUI
import TableProPluginKit

@Observable
final class SQLExportPlugin: ExportFormatPlugin {
final class SQLExportPlugin: ExportFormatPlugin, SettablePlugin {
static let pluginName = "SQL Export"
static let pluginVersion = "1.0.0"
static let pluginDescription = "Export data to SQL format"
Expand All @@ -25,21 +25,18 @@ final class SQLExportPlugin: ExportFormatPlugin {
PluginExportOptionColumn(id: "data", label: "Data", width: 44)
]

private let storage = PluginSettingsStorage(pluginId: "sql")
typealias Settings = SQLExportOptions
static let settingsStorageId = "sql"

var options = SQLExportOptions() {
didSet { storage.save(options) }
var settings = SQLExportOptions() {
didSet { saveSettings() }
}

var ddlFailures: [String] = []

private static let logger = Logger(subsystem: "com.TablePro", category: "SQLExportPlugin")

required init() {
if let saved = storage.load(SQLExportOptions.self) {
options = saved
}
}
required init() { loadSettings() }

func defaultTableOptionValues() -> [Bool] {
[true, true, true]
Expand All @@ -50,7 +47,7 @@ final class SQLExportPlugin: ExportFormatPlugin {
}

var currentFileExtension: String {
options.compressWithGzip ? "sql.gz" : "sql"
settings.compressWithGzip ? "sql.gz" : "sql"
}

var warnings: [String] {
Expand All @@ -59,7 +56,7 @@ final class SQLExportPlugin: ExportFormatPlugin {
return ["Could not fetch table structure for: \(failedTables)"]
}

func optionsView() -> AnyView? {
func settingsView() -> AnyView? {
AnyView(SQLExportOptionsView(plugin: self))
}

Expand All @@ -75,7 +72,7 @@ final class SQLExportPlugin: ExportFormatPlugin {
let targetURL: URL
let tempFileURL: URL?

if options.compressWithGzip {
if settings.compressWithGzip {
let tempURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString + ".sql")
tempFileURL = tempURL
Expand Down Expand Up @@ -171,7 +168,7 @@ final class SQLExportPlugin: ExportFormatPlugin {
}

if includeData {
let batchSize = options.batchSize
let batchSize = settings.batchSize
var offset = 0
var wroteAnyRows = false

Expand Down Expand Up @@ -218,7 +215,7 @@ final class SQLExportPlugin: ExportFormatPlugin {
}

// Handle gzip compression
if options.compressWithGzip, let tempURL = tempFileURL {
if settings.compressWithGzip, let tempURL = tempFileURL {
progress.setStatus("Compressing...")

do {
Expand Down
4 changes: 2 additions & 2 deletions Plugins/SQLImportPlugin/SQLImportOptionsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ struct SQLImportOptionsView: View {

var body: some View {
VStack(alignment: .leading, spacing: 12) {
Toggle("Wrap in transaction (BEGIN/COMMIT)", isOn: Bindable(plugin).options.wrapInTransaction)
Toggle("Wrap in transaction (BEGIN/COMMIT)", isOn: Bindable(plugin).settings.wrapInTransaction)
.font(.system(size: 13))
.help(
"Execute all statements in a single transaction. If any statement fails, all changes are rolled back."
)

Toggle("Disable foreign key checks", isOn: Bindable(plugin).options.disableForeignKeyChecks)
Toggle("Disable foreign key checks", isOn: Bindable(plugin).settings.disableForeignKeyChecks)
.font(.system(size: 13))
.help(
"Temporarily disable foreign key constraints during import. Useful for importing data with circular dependencies."
Expand Down
Loading
Loading