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 lib/core/storage/local_db.dart
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ class LocalDb {
Future<void> close() async {
await _db?.close();
_db = null;
_cachedDbPath = null;
}
}

Expand Down
86 changes: 86 additions & 0 deletions lib/features/connections/connections_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ import 'package:querya_desktop/core/motion/querya_motion.dart';
import 'package:querya_desktop/core/motion/querya_motion_context.dart';
import 'package:querya_desktop/features/connections/connection_creation_flow.dart';
import 'package:querya_desktop/shared/widgets/widgets.dart';
import 'package:querya_desktop/core/database/redis_service.dart';
import 'package:querya_desktop/app/app_shutdown.dart';

import 'package:querya_desktop/features/mongodb/mongo_database_dialog.dart';
import 'package:querya_desktop/features/postgresql/postgres_object_kind.dart';
Expand Down Expand Up @@ -219,6 +221,7 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
List<ConnectionRow> _connections = [];
Map<String, int> _folderIdByName = {};
final Set<String> _expandedFolders = {};
final Set<int> _expandedConnections = {};

/// Ignores stale [setState] when multiple [_loadData] runs overlap (e.g. tests).
int _loadDataGeneration = 0;
Expand Down Expand Up @@ -312,6 +315,68 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
await _loadData();
}

void connect(int connectionId) {
setState(() {
_expandedConnections.add(connectionId);
});
}

@visibleForTesting
bool isConnectionExpanded(int id) => _expandedConnections.contains(id);

Future<void> disconnect(ConnectionRow conn) async {
final id = conn.id!;
setState(() {
_expandedConnections.remove(id);
});
if (conn.type == 'postgresql') {
PostgresService.instance.interrupt(conn, database: conn.databaseName ?? 'postgres', mode: PgSessionMode.readOnly);
PostgresService.instance.interrupt(conn, database: conn.databaseName ?? 'postgres', mode: PgSessionMode.readWrite);
} else if (conn.type == 'mysql') {
MysqlService.instance.interrupt(conn, database: conn.databaseName ?? '', mode: MysqlSessionMode.readOnly);
MysqlService.instance.interrupt(conn, database: conn.databaseName ?? '', mode: MysqlSessionMode.readWrite);
} else if (conn.type == 'sqlite') {
SqliteService.instance.interrupt(conn, mode: SqliteSessionMode.readOnly);
SqliteService.instance.interrupt(conn, mode: SqliteSessionMode.readWrite);
} else if (conn.type == 'redis') {
final redisConn = RedisService.instance.getConnection(id);
if (redisConn != null) {
await RedisService.instance.disconnect(redisConn);
}
} else if (conn.type == 'mongodb') {
await MongoService.instance.disconnectByConnectionId(id);
}
}

Future<void> disconnectAll() async {
setState(() {
_expandedConnections.clear();
});
await disconnectAllExternalServices();
await SqliteService.instance.disconnectAll();
}

Future<void> disconnectOthers(ConnectionRow keepConn) async {
final keepId = keepConn.id!;
setState(() {
_expandedConnections.clear();
_expandedConnections.add(keepId);
});
for (final conn in _connections) {
if (conn.id == keepId) continue;
await disconnect(conn);
}
}

Future<void> reconnect(ConnectionRow conn) async {
final id = conn.id!;
await disconnect(conn);
await Future<void>.delayed(const Duration(milliseconds: 50));
if (mounted) {
connect(id);
}
}

/// Icon for a connection type (matches New Connection dialog).
material.IconData _iconForType(String type) {
return switch (type) {
Expand All @@ -338,6 +403,17 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
Widget _buildConnectionTile(ConnectionRow conn) {
final isSelected = widget.selectedConnectionId != null &&
widget.selectedConnectionId == conn.id;
final isExpanded = _expandedConnections.contains(conn.id);
void handleExpandedChanged(bool expanded) {
setState(() {
if (expanded) {
_expandedConnections.add(conn.id!);
} else {
_expandedConnections.remove(conn.id!);
}
});
}

if (conn.type == 'postgresql') {
return _PostgresConnectionTile(
connection: conn,
Expand All @@ -348,6 +424,8 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
onTap: () => widget.onConnectionSelected?.call(conn),
onPostgresObjectSelected: widget.onPostgresObjectSelected,
onPostgresOpenSqlWorkspace: widget.onPostgresOpenSqlWorkspace,
isExpanded: isExpanded,
onExpandedChanged: handleExpandedChanged,
);
} else if (conn.type == 'mysql') {
return _MysqlConnectionTile(
Expand All @@ -359,6 +437,8 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
onTap: () => widget.onConnectionSelected?.call(conn),
onMysqlObjectSelected: widget.onMysqlObjectSelected,
onMysqlOpenSqlWorkspace: widget.onMysqlOpenSqlWorkspace,
isExpanded: isExpanded,
onExpandedChanged: handleExpandedChanged,
);
} else if (conn.type == 'redis') {
return _RedisConnectionTile(
Expand All @@ -369,6 +449,8 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
onRemove: () => _removeConnection(conn.id!),
onTap: () => widget.onConnectionSelected?.call(conn),
onDatabaseTap: (db) => widget.onRedisDatabaseSelected?.call(conn, db),
isExpanded: isExpanded,
onExpandedChanged: handleExpandedChanged,
);
} else if (conn.type == 'mongodb') {
return _MongoConnectionTile(
Expand All @@ -379,6 +461,8 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
onRemove: () => _removeConnection(conn.id!),
onTap: () => widget.onConnectionSelected?.call(conn),
onDatabaseTap: (db) => widget.onMongoDBDatabaseSelected?.call(conn, db),
isExpanded: isExpanded,
onExpandedChanged: handleExpandedChanged,
);
} else if (conn.type == 'sqlite') {
return _SqliteConnectionTile(
Expand All @@ -390,6 +474,8 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
onTap: () => widget.onConnectionSelected?.call(conn),
onSqliteObjectSelected: widget.onSqliteObjectSelected,
onSqliteOpenSqlWorkspace: widget.onSqliteOpenSqlWorkspace,
isExpanded: isExpanded,
onExpandedChanged: handleExpandedChanged,
);
}
return _ConnectionTile(
Expand Down
33 changes: 29 additions & 4 deletions lib/features/connections/connections_panel_mongo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class _MongoConnectionTile extends StatefulWidget {
required this.onRemove,
this.onTap,
this.onDatabaseTap,
this.isExpanded = false,
this.onExpandedChanged,
});

final ConnectionRow connection;
Expand All @@ -20,24 +22,47 @@ class _MongoConnectionTile extends StatefulWidget {
final VoidCallback onRemove;
final VoidCallback? onTap;
final void Function(String database)? onDatabaseTap;
final bool isExpanded;
final ValueChanged<bool>? onExpandedChanged;

@override
State<_MongoConnectionTile> createState() => _MongoConnectionTileState();
}

class _MongoConnectionTileState extends State<_MongoConnectionTile> {
bool _expanded = false;
bool get _expanded => widget.isExpanded;
bool _loading = false;
String? _error;
List<String> _databases = [];

void _toggle() {
setState(() => _expanded = !_expanded);
if (_expanded && _databases.isEmpty && !_loading) {
@override
void initState() {
super.initState();
if (widget.isExpanded) {
_loadDatabases();
}
}

@override
void didUpdateWidget(_MongoConnectionTile oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isExpanded && !oldWidget.isExpanded) {
if (_databases.isEmpty && !_loading) {
_loadDatabases();
}
} else if (!widget.isExpanded && oldWidget.isExpanded) {
setState(() {
_databases = [];
_loading = false;
_error = null;
});
}
}

void _toggle() {
widget.onExpandedChanged?.call(!widget.isExpanded);
}

Future<void> _loadDatabases() async {
if (!mounted) return;
setState(() {
Expand Down
33 changes: 29 additions & 4 deletions lib/features/connections/connections_panel_mysql.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class _MysqlConnectionTile extends StatefulWidget {
this.onTap,
this.onMysqlObjectSelected,
this.onMysqlOpenSqlWorkspace,
this.isExpanded = false,
this.onExpandedChanged,
});

final ConnectionRow connection;
Expand All @@ -27,24 +29,47 @@ class _MysqlConnectionTile extends StatefulWidget {
MysqlObjectKind kind,
)? onMysqlObjectSelected;
final void Function(ConnectionRow connection)? onMysqlOpenSqlWorkspace;
final bool isExpanded;
final ValueChanged<bool>? onExpandedChanged;

@override
State<_MysqlConnectionTile> createState() => _MysqlConnectionTileState();
}

class _MysqlConnectionTileState extends State<_MysqlConnectionTile> {
bool _expanded = false;
bool get _expanded => widget.isExpanded;
bool _loading = false;
String? _error;
List<String> _databases = [];

void _toggle() {
setState(() => _expanded = !_expanded);
if (_expanded && _databases.isEmpty && !_loading) {
@override
void initState() {
super.initState();
if (widget.isExpanded) {
_loadDatabases();
}
}

@override
void didUpdateWidget(_MysqlConnectionTile oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isExpanded && !oldWidget.isExpanded) {
if (_databases.isEmpty && !_loading) {
_loadDatabases();
}
} else if (!widget.isExpanded && oldWidget.isExpanded) {
setState(() {
_databases = [];
_loading = false;
_error = null;
});
}
}

void _toggle() {
widget.onExpandedChanged?.call(!widget.isExpanded);
}

Future<void> _loadDatabases() async {
if (!mounted) return;
setState(() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class _PostgresConnectionTile extends StatefulWidget {
this.onTap,
this.onPostgresObjectSelected,
this.onPostgresOpenSqlWorkspace,
this.isExpanded = false,
this.onExpandedChanged,
});

final ConnectionRow connection;
Expand All @@ -28,25 +30,48 @@ class _PostgresConnectionTile extends StatefulWidget {
PostgresObjectKind kind,
)? onPostgresObjectSelected;
final OnPostgresOpenSqlWorkspace? onPostgresOpenSqlWorkspace;
final bool isExpanded;
final ValueChanged<bool>? onExpandedChanged;

@override
State<_PostgresConnectionTile> createState() =>
_PostgresConnectionTileState();
}

class _PostgresConnectionTileState extends State<_PostgresConnectionTile> {
bool _expanded = false;
bool get _expanded => widget.isExpanded;
bool _loading = false;
String? _error;
List<String> _databases = [];

void _toggle() {
setState(() => _expanded = !_expanded);
if (_expanded && _databases.isEmpty && !_loading) {
@override
void initState() {
super.initState();
if (widget.isExpanded) {
_loadDatabases();
}
}

@override
void didUpdateWidget(_PostgresConnectionTile oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isExpanded && !oldWidget.isExpanded) {
if (_databases.isEmpty && !_loading) {
_loadDatabases();
}
} else if (!widget.isExpanded && oldWidget.isExpanded) {
setState(() {
_databases = [];
_loading = false;
_error = null;
});
}
}

void _toggle() {
widget.onExpandedChanged?.call(!widget.isExpanded);
}

Future<void> _loadDatabases() async {
if (!mounted) return;
setState(() {
Expand Down
Loading
Loading