Skip to content

Commit efddc9c

Browse files
authored
feat: multi-select, bulk actions and reorder for welcome window (#449)
* feat: multi-select, bulk actions and reorder for welcome window connection list * fix: context menu correctness for group operations * fix: add plural variations for count-based localized strings * fix: moveGroups bounds check and remove dead hoveredConnectionId state
1 parent 1a933f4 commit efddc9c

6 files changed

Lines changed: 393 additions & 177 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Multi-select connections in Welcome window (Cmd+Click, Shift+Click) with bulk delete (⌘⌫), Move to Group, and multi-connect
13-
- Drag-and-drop connections between groups, reorder within groups, and reorder groups
13+
- Reorder connections within groups and reorder groups in Welcome window
1414
- ClickHouse, MSSQL, Redis, XLSX Export, MQL Export, and SQL Import now ship as built-in plugins
1515
- Large document safety caps for syntax highlighting (skip >5MB, throttle >50KB)
1616
- Lazy-load full values for LONGTEXT/MEDIUMTEXT/CLOB columns in the detail pane sidebar

TablePro.xcodeproj/project.pbxproj

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212
5A862000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
1313
5A862000D00000000 /* SQLiteDriver.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A862000100000000 /* SQLiteDriver.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1414
5A863000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
15+
5A863000D00000000 /* ClickHouseDriver.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A863000100000000 /* ClickHouseDriver.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1516
5A864000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
17+
5A864000D00000000 /* MSSQLDriver.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A864000100000000 /* MSSQLDriver.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1618
5A865000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
1719
5A865000D00000000 /* MySQLDriver.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A865000100000000 /* MySQLDriver.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
1820
5A866000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
1921
5A867000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
22+
5A867000D00000000 /* RedisDriver.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A867000100000000 /* RedisDriver.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
2023
5A868000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
2124
5A868000D00000000 /* PostgreSQLDriver.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A868000100000000 /* PostgreSQLDriver.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
2225
5A869000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
@@ -26,15 +29,12 @@
2629
5A86B000D00000000 /* JSONExport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86B000100000000 /* JSONExport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
2730
5A86C000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
2831
5A86C000D00000000 /* SQLExport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86C000100000000 /* SQLExport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
29-
5A863000D00000000 /* ClickHouseDriver.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A863000100000000 /* ClickHouseDriver.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
30-
5A864000D00000000 /* MSSQLDriver.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A864000100000000 /* MSSQLDriver.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
31-
5A867000D00000000 /* RedisDriver.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A867000100000000 /* RedisDriver.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
32-
5A86D000D00000000 /* XLSXExport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86D000100000000 /* XLSXExport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
33-
5A86E000D00000000 /* MQLExport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86E000100000000 /* MQLExport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
34-
5A86F000D00000000 /* SQLImport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86F000100000000 /* SQLImport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3532
5A86D000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
33+
5A86D000D00000000 /* XLSXExport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86D000100000000 /* XLSXExport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3634
5A86E000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
35+
5A86E000D00000000 /* MQLExport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86E000100000000 /* MQLExport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3736
5A86F000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
37+
5A86F000D00000000 /* SQLImport.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5A86F000100000000 /* SQLImport.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3838
5A87A000A00000000 /* TableProPluginKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A860000100000000 /* TableProPluginKit.framework */; };
3939
5ACE00012F4F000000000004 /* CodeEditSourceEditor in Frameworks */ = {isa = PBXBuildFile; productRef = 5ACE00012F4F000000000002 /* CodeEditSourceEditor */; };
4040
5ACE00012F4F000000000005 /* CodeEditLanguages in Frameworks */ = {isa = PBXBuildFile; productRef = 5ACE00012F4F000000000003 /* CodeEditLanguages */; };
@@ -90,13 +90,27 @@
9090
remoteGlobalIDString = 5A863000000000000;
9191
remoteInfo = ClickHouseDriver;
9292
};
93+
5A864000B00000000 /* PBXContainerItemProxy */ = {
94+
isa = PBXContainerItemProxy;
95+
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
96+
proxyType = 1;
97+
remoteGlobalIDString = 5A864000000000000;
98+
remoteInfo = MSSQLDriver;
99+
};
93100
5A865000B00000000 /* PBXContainerItemProxy */ = {
94101
isa = PBXContainerItemProxy;
95102
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
96103
proxyType = 1;
97104
remoteGlobalIDString = 5A865000000000000;
98105
remoteInfo = MySQLDriver;
99106
};
107+
5A867000B00000000 /* PBXContainerItemProxy */ = {
108+
isa = PBXContainerItemProxy;
109+
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
110+
proxyType = 1;
111+
remoteGlobalIDString = 5A867000000000000;
112+
remoteInfo = RedisDriver;
113+
};
100114
5A868000B00000000 /* PBXContainerItemProxy */ = {
101115
isa = PBXContainerItemProxy;
102116
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
@@ -132,20 +146,6 @@
132146
remoteGlobalIDString = 5A86C000000000000;
133147
remoteInfo = SQLExport;
134148
};
135-
5A864000B00000000 /* PBXContainerItemProxy */ = {
136-
isa = PBXContainerItemProxy;
137-
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
138-
proxyType = 1;
139-
remoteGlobalIDString = 5A864000000000000;
140-
remoteInfo = MSSQLDriver;
141-
};
142-
5A867000B00000000 /* PBXContainerItemProxy */ = {
143-
isa = PBXContainerItemProxy;
144-
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
145-
proxyType = 1;
146-
remoteGlobalIDString = 5A867000000000000;
147-
remoteInfo = RedisDriver;
148-
};
149149
5A86D000B00000000 /* PBXContainerItemProxy */ = {
150150
isa = PBXContainerItemProxy;
151151
containerPortal = 5A1091BF2EF17EDC0055EA7C /* Project object */;
@@ -1793,11 +1793,21 @@
17931793
target = 5A863000000000000 /* ClickHouseDriver */;
17941794
targetProxy = 5A863000B00000000 /* PBXContainerItemProxy */;
17951795
};
1796+
5A864000C00000000 /* PBXTargetDependency */ = {
1797+
isa = PBXTargetDependency;
1798+
target = 5A864000000000000 /* MSSQLDriver */;
1799+
targetProxy = 5A864000B00000000 /* PBXContainerItemProxy */;
1800+
};
17961801
5A865000C00000000 /* PBXTargetDependency */ = {
17971802
isa = PBXTargetDependency;
17981803
target = 5A865000000000000 /* MySQLDriver */;
17991804
targetProxy = 5A865000B00000000 /* PBXContainerItemProxy */;
18001805
};
1806+
5A867000C00000000 /* PBXTargetDependency */ = {
1807+
isa = PBXTargetDependency;
1808+
target = 5A867000000000000 /* RedisDriver */;
1809+
targetProxy = 5A867000B00000000 /* PBXContainerItemProxy */;
1810+
};
18011811
5A868000C00000000 /* PBXTargetDependency */ = {
18021812
isa = PBXTargetDependency;
18031813
target = 5A868000000000000 /* PostgreSQLDriver */;
@@ -1823,16 +1833,6 @@
18231833
target = 5A86C000000000000 /* SQLExport */;
18241834
targetProxy = 5A86C000B00000000 /* PBXContainerItemProxy */;
18251835
};
1826-
5A864000C00000000 /* PBXTargetDependency */ = {
1827-
isa = PBXTargetDependency;
1828-
target = 5A864000000000000 /* MSSQLDriver */;
1829-
targetProxy = 5A864000B00000000 /* PBXContainerItemProxy */;
1830-
};
1831-
5A867000C00000000 /* PBXTargetDependency */ = {
1832-
isa = PBXTargetDependency;
1833-
target = 5A867000000000000 /* RedisDriver */;
1834-
targetProxy = 5A867000B00000000 /* PBXContainerItemProxy */;
1835-
};
18361836
5A86D000C00000000 /* PBXTargetDependency */ = {
18371837
isa = PBXTargetDependency;
18381838
target = 5A86D000000000000 /* XLSXExport */;

TablePro/Core/Storage/ConnectionStorage.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,25 @@ final class ConnectionStorage {
107107
deleteAllPluginSecureFields(for: connection.id, fieldIds: secureFieldIds)
108108
}
109109

110+
/// Batch-delete multiple connections and clean up their Keychain entries
111+
func deleteConnections(_ connectionsToDelete: [DatabaseConnection]) {
112+
for conn in connectionsToDelete {
113+
SyncChangeTracker.shared.markDeleted(.connection, id: conn.id.uuidString)
114+
}
115+
let idsToDelete = Set(connectionsToDelete.map(\.id))
116+
var all = loadConnections()
117+
all.removeAll { idsToDelete.contains($0.id) }
118+
saveConnections(all)
119+
for conn in connectionsToDelete {
120+
deletePassword(for: conn.id)
121+
deleteSSHPassword(for: conn.id)
122+
deleteKeyPassphrase(for: conn.id)
123+
deleteTOTPSecret(for: conn.id)
124+
let fields = Self.secureFieldIds(for: conn.type)
125+
deleteAllPluginSecureFields(for: conn.id, fieldIds: fields)
126+
}
127+
}
128+
110129
/// Duplicate a connection with a new UUID and "(Copy)" suffix
111130
/// Copies all passwords from source connection to the duplicate
112131
func duplicateConnection(_ connection: DatabaseConnection) -> DatabaseConnection {

TablePro/Resources/Localizable.xcstrings

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@
179179
}
180180
},
181181
"(%@)" : {
182+
"extractionState" : "stale",
182183
"localizations" : {
183184
"tr" : {
184185
"stringUnit" : {
@@ -3919,6 +3920,9 @@
39193920
}
39203921
}
39213922
}
3923+
},
3924+
"Are you sure you want to delete %lld connections? This cannot be undone." : {
3925+
39223926
},
39233927
"Are you sure you want to delete this connection? This cannot be undone." : {
39243928
"localizations" : {
@@ -6525,6 +6529,28 @@
65256529
}
65266530
}
65276531
},
6532+
"Connect %lld Connections" : {
6533+
"localizations" : {
6534+
"en" : {
6535+
"variations" : {
6536+
"plural" : {
6537+
"one" : {
6538+
"stringUnit" : {
6539+
"state" : "translated",
6540+
"value" : "Connect %lld Connection"
6541+
}
6542+
},
6543+
"other" : {
6544+
"stringUnit" : {
6545+
"state" : "translated",
6546+
"value" : "Connect %lld Connections"
6547+
}
6548+
}
6549+
}
6550+
}
6551+
}
6552+
}
6553+
},
65286554
"Connect Anyway" : {
65296555
"localizations" : {
65306556
"tr" : {
@@ -9299,6 +9325,28 @@
92999325
}
93009326
}
93019327
},
9328+
"Delete %lld Connections" : {
9329+
"localizations" : {
9330+
"en" : {
9331+
"variations" : {
9332+
"plural" : {
9333+
"one" : {
9334+
"stringUnit" : {
9335+
"state" : "translated",
9336+
"value" : "Delete %lld Connection"
9337+
}
9338+
},
9339+
"other" : {
9340+
"stringUnit" : {
9341+
"state" : "translated",
9342+
"value" : "Delete %lld Connections"
9343+
}
9344+
}
9345+
}
9346+
}
9347+
}
9348+
}
9349+
},
93029350
"Delete Check Constraint" : {
93039351
"extractionState" : "stale",
93049352
"localizations" : {
@@ -12270,6 +12318,9 @@
1227012318
}
1227112319
}
1227212320
}
12321+
},
12322+
"Failed to load full value" : {
12323+
1227312324
},
1227412325
"Failed to load plugin registry" : {
1227512326
"extractionState" : "stale",
@@ -17573,6 +17624,9 @@
1757317624
}
1757417625
}
1757517626
}
17627+
},
17628+
"Move to Group" : {
17629+
1757617630
},
1757717631
"Move Up" : {
1757817632
"extractionState" : "stale",
@@ -18113,6 +18167,9 @@
1811318167
}
1811418168
}
1811518169
}
18170+
},
18171+
"New Group..." : {
18172+
1811618173
},
1811718174
"New Jump Host" : {
1811818175
"localizations" : {
@@ -23568,6 +23625,9 @@
2356823625
}
2356923626
}
2357023627
}
23628+
},
23629+
"Remove from Group" : {
23630+
2357123631
},
2357223632
"Remove license from this machine" : {
2357323633
"localizations" : {

0 commit comments

Comments
 (0)