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
74 changes: 70 additions & 4 deletions lib/features/connections/connections_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ import 'package:flutter/material.dart' as material
ListView,
ClampingScrollPhysics,
CallbackShortcuts,
SingleActivator;
SingleActivator,
AnimatedContainer;
import 'package:flutter/services.dart'
show Clipboard, ClipboardData, LogicalKeyboardKey;
import 'package:querya_desktop/core/database/mongodb_service.dart';
Expand Down Expand Up @@ -155,11 +156,57 @@ material.Widget lazyConnectionTreeList({
);
}

/// Inherited scope providing the active connection and object selection down the connections tree.
class _ConnectionsTreeSelectionScope extends InheritedWidget {
const _ConnectionsTreeSelectionScope({
required this.selectedConnectionId,
required this.selectedPostgresObject,
required this.selectedMysqlObject,
required this.selectedSqliteObject,
required this.selectedExtensionObject,
required this.selectedRedisDb,
required this.selectedMongoDb,
required super.child,
});

final int? selectedConnectionId;
final ({String database, String schema, String name, PostgresObjectKind kind})?
selectedPostgresObject;
final ({String database, String name, MysqlObjectKind kind})?
selectedMysqlObject;
final ({String name, SqliteObjectKind kind})? selectedSqliteObject;
final ({String database, String name})? selectedExtensionObject;
final int? selectedRedisDb;
final String? selectedMongoDb;

static _ConnectionsTreeSelectionScope? of(material.BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<_ConnectionsTreeSelectionScope>();
}

@override
bool updateShouldNotify(_ConnectionsTreeSelectionScope oldWidget) {
return selectedConnectionId != oldWidget.selectedConnectionId ||
selectedPostgresObject != oldWidget.selectedPostgresObject ||
selectedMysqlObject != oldWidget.selectedMysqlObject ||
selectedSqliteObject != oldWidget.selectedSqliteObject ||
selectedExtensionObject != oldWidget.selectedExtensionObject ||
selectedRedisDb != oldWidget.selectedRedisDb ||
selectedMongoDb != oldWidget.selectedMongoDb;
}
}

/// Left panel: Browser tree (pgAdmin-style). Uses shadcn layout widgets.
class ConnectionsPanel extends StatefulWidget {
const ConnectionsPanel({
super.key,
this.selectedConnectionId,
this.selectedPostgresObject,
this.selectedMysqlObject,
this.selectedSqliteObject,
this.selectedExtensionObject,
this.selectedRedisDb,
this.selectedMongoDb,
this.onConnectionSelected,
this.onRedisDatabaseSelected,
this.onMongoDBDatabaseSelected,
Expand All @@ -181,6 +228,16 @@ class ConnectionsPanel extends StatefulWidget {
/// Highlights the active connection row in the sidebar (workspace selection).
final int? selectedConnectionId;

/// Currently selected database objects (table, view, routine, db).
final ({String database, String schema, String name, PostgresObjectKind kind})?
selectedPostgresObject;
final ({String database, String name, MysqlObjectKind kind})?
selectedMysqlObject;
final ({String name, SqliteObjectKind kind})? selectedSqliteObject;
final ({String database, String name})? selectedExtensionObject;
final int? selectedRedisDb;
final String? selectedMongoDb;

/// Called when the user taps a connection tile.
final void Function(ConnectionRow connection)? onConnectionSelected;

Expand Down Expand Up @@ -551,7 +608,15 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
final topLevelCount =
_folders.length + rootConnections.length + (showEmptyState ? 1 : 0);

return material.Container(
return _ConnectionsTreeSelectionScope(
selectedConnectionId: widget.selectedConnectionId,
selectedPostgresObject: widget.selectedPostgresObject,
selectedMysqlObject: widget.selectedMysqlObject,
selectedSqliteObject: widget.selectedSqliteObject,
selectedExtensionObject: widget.selectedExtensionObject,
selectedRedisDb: widget.selectedRedisDb,
selectedMongoDb: widget.selectedMongoDb,
child: material.Container(
decoration: material.BoxDecoration(
color: theme.colorScheme.background,
border: material.Border(
Expand Down Expand Up @@ -689,6 +754,7 @@ class ConnectionsPanelState extends State<ConnectionsPanel> {
),
],
),
);
}
),
);
}
}
5 changes: 5 additions & 0 deletions lib/features/connections/connections_panel_mongo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -407,10 +407,15 @@ class _MongoDatabaseNode extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final sel = _ConnectionsTreeSelectionScope.of(context);
final isSelected = sel != null &&
sel.selectedConnectionId == connection.id &&
sel.selectedMongoDb == name;
return material.Padding(
padding: const material.EdgeInsets.only(left: 16, top: 2, bottom: 2),
child: _PgTreeRow(
label: name,
isSelected: isSelected,
icon: QueryaIcons.database,
iconSize: QueryaIconSizes.treeConnection,
iconColor: theme.colorScheme.primary.withValues(alpha: 0.7),
Expand Down
8 changes: 8 additions & 0 deletions lib/features/connections/connections_panel_mysql.dart
Original file line number Diff line number Diff line change
Expand Up @@ -687,11 +687,19 @@ class _MysqlObjectGroupState extends State<_MysqlObjectGroup> {
padding: const material.EdgeInsets.only(left: 26),
itemBuilder: (context, index) {
final item = widget.items[index];
final sel = _ConnectionsTreeSelectionScope.of(context);
final isSelected = sel != null &&
sel.selectedConnectionId == widget.connection.id &&
sel.selectedMysqlObject != null &&
sel.selectedMysqlObject!.database == widget.databaseName &&
sel.selectedMysqlObject!.name == item &&
sel.selectedMysqlObject!.kind == widget.objectKind;
return _PgTreeRow(
key: material.ValueKey(
'mysql-${widget.objectKind.name}-${widget.databaseName}-$item',
),
label: item,
isSelected: isSelected,
icon: widget.itemIcon,
iconSize: QueryaIconSizes.treeLeaf,
iconColor: QueryaTreeTokens.leafIconColor(
Expand Down
104 changes: 70 additions & 34 deletions lib/features/connections/connections_panel_pg_tree.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class _PgTreeRow extends material.StatelessWidget {
const _PgTreeRow({
super.key,
required this.label,
this.isSelected = false,
this.leading,
this.icon,
this.iconSize = QueryaIconSizes.treeGroup,
Expand All @@ -80,6 +81,7 @@ class _PgTreeRow extends material.StatelessWidget {
});

final String label;
final bool isSelected;
final material.Widget? leading;
final material.IconData? icon;
final double iconSize;
Expand Down Expand Up @@ -115,43 +117,67 @@ class _PgTreeRow extends material.StatelessWidget {
const material.SingleActivator(LogicalKeyboardKey.arrowLeft): () =>
onTap!(),
},
child: material.Material(
color: material.Colors.transparent,
child: material.InkWell(
onTap: onTap,
canRequestFocus: onTap != null,
child: material.AnimatedContainer(
duration: context.motionDuration(QueryaMotion.fast),
curve: context.motionCurve(QueryaMotion.standardCurve),
decoration: material.BoxDecoration(
color: isSelected
? primary.withValues(alpha: 0.12)
: material.Colors.transparent,
borderRadius: material.BorderRadius.circular(4),
hoverColor: primary.withValues(alpha: 0.07),
focusColor: primary.withValues(alpha: 0.14),
splashColor: primary.withValues(alpha: 0.10),
highlightColor: primary.withValues(alpha: 0.05),
mouseCursor: onTap != null
? material.SystemMouseCursors.click
: material.SystemMouseCursors.basic,
child: material.Padding(
padding: material.EdgeInsets.symmetric(
horizontal: 4,
vertical: verticalPadding,
),
child: material.Row(
children: [
if (leading != null) ...[
leading!,
const Gap(4),
],
if (icon != null) ...[
material.Icon(
icon,
size: iconSize,
color: iconColor ?? muted,
border: isSelected
? material.Border.all(
color: primary.withValues(alpha: 0.35),
width: 1,
)
: null,
),
child: material.Material(
color: material.Colors.transparent,
child: material.InkWell(
onTap: onTap,
canRequestFocus: onTap != null,
borderRadius: material.BorderRadius.circular(4),
hoverColor: primary.withValues(alpha: 0.07),
focusColor: primary.withValues(alpha: 0.14),
splashColor: primary.withValues(alpha: 0.10),
highlightColor: primary.withValues(alpha: 0.05),
mouseCursor: onTap != null
? material.SystemMouseCursors.click
: material.SystemMouseCursors.basic,
child: material.Padding(
padding: material.EdgeInsets.symmetric(
horizontal: 4,
vertical: verticalPadding,
),
child: material.Row(
children: [
if (leading != null) ...[
leading!,
const Gap(4),
],
if (icon != null) ...[
material.Icon(
icon,
size: iconSize,
color: isSelected ? primary : (iconColor ?? muted),
),
const Gap(6),
],
material.Expanded(
child: _PgTreeRowLabel(
label: label,
textStyle: isSelected
? textStyle.copyWith(
fontWeight: material.FontWeight.w600,
color: theme.colorScheme.foreground,
)
: textStyle,
),
),
const Gap(6),
if (trailing != null) trailing!,
],
material.Expanded(
child: _PgTreeRowLabel(label: label, textStyle: textStyle),
),
if (trailing != null) trailing!,
],
),
),
),
),
Expand Down Expand Up @@ -1119,11 +1145,21 @@ class _PgObjectGroupState extends State<_PgObjectGroup> {
padding: const material.EdgeInsets.only(left: 26),
itemBuilder: (context, index) {
final item = widget.items[index];
final sel = _ConnectionsTreeSelectionScope.of(context);
final isSelected = sel != null &&
sel.selectedConnectionId == widget.connection.id &&
sel.selectedPostgresObject != null &&
sel.selectedPostgresObject!.database ==
widget.databaseName &&
sel.selectedPostgresObject!.schema == widget.schemaName &&
sel.selectedPostgresObject!.name == item &&
sel.selectedPostgresObject!.kind == widget.objectKind;
return _PgTreeRow(
key: material.ValueKey(
'pg-${widget.objectKind.name}-${widget.databaseName}-${widget.schemaName}-$item',
),
label: item,
isSelected: isSelected,
icon: widget.itemIcon,
iconSize: QueryaIconSizes.treeLeaf,
iconColor: QueryaTreeTokens.leafIconColor(
Expand Down
8 changes: 8 additions & 0 deletions lib/features/connections/connections_panel_redis.dart
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ class _RedisDatabasesNode extends material.StatelessWidget {
itemBuilder: (context, index) {
final db = databases[index];
return _RedisDatabaseNode(
connection: connection,
index: db.index,
keys: db.keys,
onTap: () => onDatabaseTap?.call(db.index),
Expand All @@ -361,22 +362,29 @@ class _RedisDatabasesNode extends material.StatelessWidget {

class _RedisDatabaseNode extends StatelessWidget {
const _RedisDatabaseNode({
required this.connection,
required this.index,
required this.keys,
required this.onTap,
});

final ConnectionRow connection;
final int index;
final int keys;
final VoidCallback onTap;

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final sel = _ConnectionsTreeSelectionScope.of(context);
final isSelected = sel != null &&
sel.selectedConnectionId == connection.id &&
sel.selectedRedisDb == index;
return material.Padding(
padding: const material.EdgeInsets.only(left: 16),
child: _PgTreeRow(
label: 'db$index',
isSelected: isSelected,
icon: QueryaIcons.database,
iconSize: QueryaIconSizes.treeConnection,
iconColor: keys > 0
Expand Down
7 changes: 7 additions & 0 deletions lib/features/connections/connections_panel_sqlite.dart
Original file line number Diff line number Diff line change
Expand Up @@ -415,11 +415,18 @@ class _SqliteObjectGroupState extends State<_SqliteObjectGroup> {
padding: const material.EdgeInsets.only(left: 26),
itemBuilder: (context, index) {
final item = widget.items[index];
final sel = _ConnectionsTreeSelectionScope.of(context);
final isSelected = sel != null &&
sel.selectedConnectionId == widget.connection.id &&
sel.selectedSqliteObject != null &&
sel.selectedSqliteObject!.name == item &&
sel.selectedSqliteObject!.kind == widget.objectKind;
return _PgTreeRow(
key: material.ValueKey(
'sqlite-${widget.objectKind.name}-$item',
),
label: item,
isSelected: isSelected,
icon: widget.itemIcon,
iconSize: QueryaIconSizes.treeLeaf,
iconColor: QueryaTreeTokens.leafIconColor(
Expand Down
19 changes: 18 additions & 1 deletion lib/features/extensions/extension_table_toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class ExtensionTableToolbar extends material.StatelessWidget {
this.onCancelQuery,
this.onCopyFormat,
this.onSaveFormat,
this.onNavigateHome,
});

final String title;
Expand All @@ -40,6 +41,7 @@ class ExtensionTableToolbar extends material.StatelessWidget {
final VoidCallback? onCancelQuery;
final material.ValueChanged<DataExportFormat>? onCopyFormat;
final material.ValueChanged<DataExportFormat>? onSaveFormat;
final VoidCallback? onNavigateHome;

@override
material.Widget build(material.BuildContext context) {
Expand All @@ -58,6 +60,21 @@ class ExtensionTableToolbar extends material.StatelessWidget {
),
child: material.Row(
children: [
if (onNavigateHome != null) ...[
material.Tooltip(
message: 'Return to driver overview',
child: OutlineButton(
size: ButtonSize.small,
onPressed: onNavigateHome,
leading: const material.Icon(
material.Icons.dns_outlined,
size: 14,
),
child: const Text('Driver'),
),
),
const Gap(10),
],
material.Icon(tableIcon, size: 18, color: cs.primary),
const Gap(8),
material.Expanded(
Expand Down Expand Up @@ -163,7 +180,7 @@ class ExtensionTableToolbar extends material.StatelessWidget {
material.Icons.chevron_left_rounded,
size: 16,
),
child: const Text('Back'),
child: const Text('Prev'),
),
const Gap(4),
OutlineButton(
Expand Down
Loading
Loading