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
224 changes: 137 additions & 87 deletions lib/features/main_screen/workspace_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:flutter/material.dart' as material
Widget,
Column;
import 'package:querya_desktop/core/extensions/extension_driver_catalog.dart';
import 'package:querya_desktop/core/motion/querya_fade_slide.dart';
import 'package:querya_desktop/core/motion/querya_switching_body.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/shared/widgets/widgets.dart';
Expand Down Expand Up @@ -175,115 +176,141 @@ class _WorkspacePanelState extends State<WorkspacePanel> {
switch (activeConn.type) {
case 'postgresql':
final pg = widget.selectedPostgresObject;
driverWorkspace = pg == null
? PostgresWorkspaceHome(
key: ValueKey('pg_home_${activeConn.id}'),
connectionRow: activeConn,
postgresSqlEditorContext: widget.postgresSqlEditorContext,
postgresSqlEditorContextToken:
widget.postgresSqlEditorContextToken,
sqlTabRequestToken: widget.postgresSqlTabRequestToken,
isReadOnly: widget.isReadOnly,
)
: buildPostgresObjectWorkspace(
connection: activeConn,
pg: pg,
);
driverWorkspace = _homeObjectMorph(
showingObject: pg != null,
home: PostgresWorkspaceHome(
key: ValueKey('pg_home_${activeConn.id}'),
connectionRow: activeConn,
postgresSqlEditorContext: widget.postgresSqlEditorContext,
postgresSqlEditorContextToken:
widget.postgresSqlEditorContextToken,
sqlTabRequestToken: widget.postgresSqlTabRequestToken,
isReadOnly: widget.isReadOnly,
),
object: pg == null
? null
: buildPostgresObjectWorkspace(
connection: activeConn,
pg: pg,
),
);
break;
case 'mysql':
final my = widget.selectedMysqlObject;
if (my == null) {
driverWorkspace = MysqlWorkspaceHome(
material.Widget? mysqlObject;
if (my != null) {
if (my.kind == MysqlObjectKind.procedure ||
my.kind == MysqlObjectKind.function) {
mysqlObject = MysqlRoutineView(
key: ValueKey(
'mysql_${activeConn.id}_${my.database}_${my.name}_${my.kind}',
),
connectionRow: activeConn,
database: my.database,
routineName: my.name,
isFunction: my.kind == MysqlObjectKind.function,
);
} else {
mysqlObject = MysqlTableView(
key: ValueKey(
'mysql_${activeConn.id}_${my.database}_${my.name}_${my.kind}',
),
connectionRow: activeConn,
database: my.database,
tableName: my.name,
isView: my.kind == MysqlObjectKind.view,
);
}
}
driverWorkspace = _homeObjectMorph(
showingObject: my != null,
home: MysqlWorkspaceHome(
key: ValueKey('mysql_home_${activeConn.id}'),
connectionRow: activeConn,
sqlTabRequestToken: widget.mysqlSqlTabRequestToken,
isReadOnly: widget.isReadOnly,
);
} else if (my.kind == MysqlObjectKind.procedure ||
my.kind == MysqlObjectKind.function) {
driverWorkspace = MysqlRoutineView(
key: ValueKey(
'mysql_${activeConn.id}_${my.database}_${my.name}_${my.kind}',
),
connectionRow: activeConn,
database: my.database,
routineName: my.name,
isFunction: my.kind == MysqlObjectKind.function,
);
} else {
driverWorkspace = MysqlTableView(
key: ValueKey(
'mysql_${activeConn.id}_${my.database}_${my.name}_${my.kind}',
),
connectionRow: activeConn,
database: my.database,
tableName: my.name,
isView: my.kind == MysqlObjectKind.view,
);
}
),
object: mysqlObject,
);
break;
case 'mongodb':
final mongoDb = widget.selectedMongoDb;
driverWorkspace = mongoDb != null
? MongoExplorerView(
key: ValueKey('mongo_${activeConn.id}_db_$mongoDb'),
connectionRow: activeConn,
database: mongoDb,
)
: MongoStatsView(
key: ValueKey(activeConn.id),
connectionRow: activeConn,
);
driverWorkspace = _homeObjectMorph(
showingObject: mongoDb != null,
home: MongoStatsView(
key: ValueKey('mongo_stats_${activeConn.id}'),
connectionRow: activeConn,
),
object: mongoDb == null
? null
: MongoExplorerView(
key: ValueKey('mongo_${activeConn.id}_db_$mongoDb'),
connectionRow: activeConn,
database: mongoDb,
),
);
break;
case 'redis':
final redisDb = widget.selectedRedisDb;
driverWorkspace = redisDb != null
? RedisExplorerView(
key: ValueKey('redis_${activeConn.id}_db_$redisDb'),
connectionRow: activeConn,
database: redisDb,
)
: RedisView(
key: ValueKey(activeConn.id),
connectionRow: activeConn,
);
driverWorkspace = _homeObjectMorph(
showingObject: redisDb != null,
home: RedisView(
key: ValueKey('redis_stats_${activeConn.id}'),
connectionRow: activeConn,
),
object: redisDb == null
? null
: RedisExplorerView(
key: ValueKey('redis_${activeConn.id}_db_$redisDb'),
connectionRow: activeConn,
database: redisDb,
),
);
break;
case 'sqlite':
final sq = widget.selectedSqliteObject;
driverWorkspace = sq == null
? SqliteWorkspaceHome(
key: ValueKey('sqlite_home_${activeConn.id}'),
connectionRow: activeConn,
sqlTabRequestToken: widget.sqliteSqlTabRequestToken,
isReadOnly: widget.isReadOnly,
)
: SqliteTableView(
key: ValueKey(
'sqlite_${activeConn.id}_${sq.name}_${sq.kind}',
driverWorkspace = _homeObjectMorph(
showingObject: sq != null,
home: SqliteWorkspaceHome(
key: ValueKey('sqlite_home_${activeConn.id}'),
connectionRow: activeConn,
sqlTabRequestToken: widget.sqliteSqlTabRequestToken,
isReadOnly: widget.isReadOnly,
),
object: sq == null
? null
: SqliteTableView(
key: ValueKey(
'sqlite_${activeConn.id}_${sq.name}_${sq.kind}',
),
connectionRow: activeConn,
tableName: sq.name,
isView: sq.kind == SqliteObjectKind.view,
),
connectionRow: activeConn,
tableName: sq.name,
isView: sq.kind == SqliteObjectKind.view,
);
);
break;
default:
if (ExtensionDriverCatalog.isExtensionDriverConnection(activeConn)) {
final obj = widget.selectedExtensionObject;
driverWorkspace = obj == null
? ExtensionWorkspaceHome(
key: ValueKey('ext_home_${activeConn.id}'),
connectionRow: activeConn,
sqlTabRequestToken: widget.extensionSqlTabRequestToken,
isReadOnly: widget.isReadOnly,
)
: ExtensionTableView(
key: ValueKey(
'ext_table_${activeConn.id}_${obj.database}_${obj.name}',
driverWorkspace = _homeObjectMorph(
showingObject: obj != null,
home: ExtensionWorkspaceHome(
key: ValueKey('ext_home_${activeConn.id}'),
connectionRow: activeConn,
sqlTabRequestToken: widget.extensionSqlTabRequestToken,
isReadOnly: widget.isReadOnly,
),
object: obj == null
? null
: ExtensionTableView(
key: ValueKey(
'ext_table_${activeConn.id}_${obj.database}_${obj.name}',
),
connectionRow: activeConn,
database: obj.database,
tableName: obj.name,
),
connectionRow: activeConn,
database: obj.database,
tableName: obj.name,
);
);
}
break;
}
Expand Down Expand Up @@ -314,4 +341,27 @@ class _WorkspacePanelState extends State<WorkspacePanel> {
),
);
}

/// Home (stats / SQL) stays keep-alive; object/explorer morphs in on top.
///
/// Object→object switches use [QueryaFadeSlide] so table/DB changes do not
/// hard-cut. Does not animate virtualized result rows inside those views.
material.Widget _homeObjectMorph({
required bool showingObject,
required material.Widget home,
required material.Widget? object,
}) {
return QueryaSwitchingBody(
index: showingObject ? 1 : 0,
children: [
home,
QueryaFadeSlide(
child: object ??
const material.SizedBox.expand(
key: ValueKey('workspace_object_placeholder'),
),
),
],
);
}
}
82 changes: 77 additions & 5 deletions test/features/main_screen/workspace_panel_layout_test.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'package:flutter/material.dart' as material;
import 'package:flutter_test/flutter_test.dart';
import 'package:querya_desktop/core/motion/querya_fade_slide.dart';
import 'package:querya_desktop/core/motion/querya_switching_body.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/features/main_screen/workspace_panel.dart';
import 'package:querya_desktop/features/redis/redis_explorer_view.dart';
import 'package:querya_desktop/features/redis/redis_view.dart';

import '../../support/layout_overflow.dart';
import '../../support/querya_theme_test_shell.dart';
Expand All @@ -18,6 +21,15 @@ void main() {
createdAt: '0',
);

const redisConnection = ConnectionRow(
id: 42,
type: 'redis',
name: 'redis-test',
host: '127.0.0.1',
port: 6379,
createdAt: '0',
);

group('WorkspacePanel layout (no connection)', () {
final sizes = <String, material.Size>{
'narrow_tall': const material.Size(320, 720),
Expand Down Expand Up @@ -75,7 +87,8 @@ void main() {
),
);

expect(find.byKey(const material.Key('workspace_run_button')), findsNothing);
expect(
find.byKey(const material.Key('workspace_run_button')), findsNothing);
expect(find.text('Execute/Refresh (F5)'), findsNothing);
});

Expand All @@ -93,7 +106,8 @@ void main() {
),
);

expect(find.byKey(const material.Key('workspace_run_button')), findsNothing);
expect(
find.byKey(const material.Key('workspace_run_button')), findsNothing);
expect(find.text('Query History'), findsNothing);
expect(find.textContaining('Coming in a future release'), findsNothing);
});
Expand All @@ -109,7 +123,7 @@ void main() {
),
),
);
expect(find.byType(QueryaSwitchingBody), findsOneWidget);
expect(find.byType(QueryaSwitchingBody), findsWidgets);

await pumpWidgetWithSurfaceSize(
tester,
Expand All @@ -123,7 +137,7 @@ void main() {
),
);
await tester.pump();
expect(find.byType(QueryaSwitchingBody), findsOneWidget);
expect(find.byType(QueryaSwitchingBody), findsWidgets);
expect(find.text('Unsupported connection type'), findsOneWidget);

// Back to empty — keep-alive stack stays mounted.
Expand All @@ -137,7 +151,65 @@ void main() {
),
);
await tester.pumpAndSettle();
expect(find.byType(QueryaSwitchingBody), findsOneWidget);
expect(find.byType(QueryaSwitchingBody), findsWidgets);
});
});

group('WorkspacePanel home↔object morph', () {
testWidgets('Redis stats↔explorer uses SwitchingBody + FadeSlide',
(tester) async {
await pumpWidgetWithSurfaceSize(
tester,
const material.Size(800, 600),
queryaThemeTestShell(
child: const material.SizedBox.expand(
child: WorkspacePanel(activeConnection: redisConnection),
),
),
);
await tester.pump();

// Outer empty↔connected + inner home↔object (+ hero FadeSlide keep-alive).
expect(find.byType(QueryaSwitchingBody), findsNWidgets(2));
expect(find.byType(QueryaFadeSlide), findsWidgets);
expect(find.byType(RedisView), findsOneWidget);

await pumpWidgetWithSurfaceSize(
tester,
const material.Size(800, 600),
queryaThemeTestShell(
child: const material.SizedBox.expand(
child: WorkspacePanel(
activeConnection: redisConnection,
selectedRedisDb: 0,
),
),
),
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));

expect(find.byType(QueryaSwitchingBody), findsNWidgets(2));
expect(find.byType(QueryaFadeSlide), findsWidgets);
expect(find.byType(RedisExplorerView), findsOneWidget);
// Home stays keep-alive under SwitchingBody.
expect(find.byType(RedisView), findsOneWidget);

await pumpWidgetWithSurfaceSize(
tester,
const material.Size(800, 600),
queryaThemeTestShell(
child: const material.SizedBox.expand(
child: WorkspacePanel(activeConnection: redisConnection),
),
),
);
// Avoid pumpAndSettle — Redis stats polling keeps a ticker alive.
await tester.pump();
await tester.pump(const Duration(milliseconds: 250));

expect(find.byType(RedisView), findsOneWidget);
expect(find.byType(QueryaSwitchingBody), findsNWidgets(2));
});
});
}
Loading