diff --git a/lib/core/actions/querya_core_commands.dart b/lib/core/actions/querya_core_commands.dart index 0762f3e2..a110e1b8 100644 --- a/lib/core/actions/querya_core_commands.dart +++ b/lib/core/actions/querya_core_commands.dart @@ -6,6 +6,7 @@ import 'package:querya_desktop/core/actions/data_grid_command_bridge.dart'; import 'package:querya_desktop/core/actions/querya_command.dart'; import 'package:querya_desktop/core/actions/querya_command_host.dart'; import 'package:querya_desktop/core/actions/sql_editor_command_bridge.dart'; +import 'package:querya_desktop/core/actions/table_view_command_bridge.dart'; import 'package:querya_desktop/core/theme/theme_controller.dart'; import 'package:querya_desktop/features/connections/connection_creation_flow.dart'; import 'package:querya_desktop/features/connections/team_profile_actions.dart'; @@ -21,6 +22,7 @@ import 'package:querya_desktop/shared/services/data_export_service.dart'; List queryaCoreCommands() { final sql = SqlEditorCommandBridge.instance; final grid = DataGridCommandBridge.instance; + final tables = TableViewCommandBridge.instance; return [ // -- Workspace ---------------------------------------------------------- QueryaCommand( @@ -160,6 +162,26 @@ List queryaCoreCommands() { QueryaCommandHost.maybeOf(context)?.onToggleReadOnly?.call(), ), + // -- Table -------------------------------------------------------------- + QueryaCommand( + id: 'querya.table.showRelations', + title: 'Table: Show relations', + category: 'Table', + icon: Icons.account_tree_outlined, + aliases: const ['erd', 'diagram', 'foreign keys', 'references'], + isEnabled: (_) => tables.isActive, + execute: (_) => tables.invokeSelectView(1), + ), + QueryaCommand( + id: 'querya.table.showData', + title: 'Table: Show data', + category: 'Table', + icon: Icons.table_rows_outlined, + aliases: const ['grid', 'rows'], + isEnabled: (_) => tables.isActive, + execute: (_) => tables.invokeSelectView(0), + ), + // -- SQL ---------------------------------------------------------------- QueryaCommand( id: 'querya.sql.execute', diff --git a/lib/core/actions/table_view_command_bridge.dart b/lib/core/actions/table_view_command_bridge.dart new file mode 100644 index 00000000..ddf71232 --- /dev/null +++ b/lib/core/actions/table_view_command_bridge.dart @@ -0,0 +1,39 @@ +import 'package:flutter/foundation.dart'; + +/// Lets the Command Palette switch the table browser on screen between Data +/// and Relations. Only a table that offers the switch registers. +class TableViewCommandBridge { + TableViewCommandBridge._(); + + static final TableViewCommandBridge instance = TableViewCommandBridge._(); + + Object? _owner; + void Function(int index)? _onSelectView; + + bool get isActive => _onSelectView != null; + + void register({ + required Object owner, + required void Function(int index) onSelectView, + }) { + _owner = owner; + _onSelectView = onSelectView; + } + + /// Clears the registration only while [owner] still holds it, so a table + /// that closes does not drop the one that registered after it. + void unregister({required Object owner}) { + if (!identical(_owner, owner)) return; + _owner = null; + _onSelectView = null; + } + + /// 0 shows the grid (Data), 1 the neighbourhood (Relations). + void invokeSelectView(int index) => _onSelectView?.call(index); + + @visibleForTesting + void resetForTest() { + _owner = null; + _onSelectView = null; + } +} diff --git a/lib/features/workspace/generic_table_view.dart b/lib/features/workspace/generic_table_view.dart index 2efaa746..774c006b 100644 --- a/lib/features/workspace/generic_table_view.dart +++ b/lib/features/workspace/generic_table_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart' as material; import 'package:flutter/services.dart' show LogicalKeyboardKey; import 'package:querya_desktop/core/storage/local_db.dart' show MutationAuditSource; import 'package:querya_desktop/core/storage/mutation_audit_recorder.dart'; +import 'package:querya_desktop/core/actions/table_view_command_bridge.dart'; import 'package:querya_desktop/core/database/table_mutation_engine.dart'; import 'package:querya_desktop/core/database/table_schema_meta.dart'; import 'package:querya_desktop/features/erd/erd_source.dart'; @@ -216,6 +217,18 @@ class GenericTableViewState extends material.State { void initState() { super.initState(); _loadPage(refreshCount: true); + _syncRelationsCommand(); + } + + /// The Command Palette can switch Data / Relations only while this table + /// offers the switch. + void _syncRelationsCommand() { + final bridge = TableViewCommandBridge.instance; + if (showsRelations) { + bridge.register(owner: this, onSelectView: selectView); + } else { + bridge.unregister(owner: this); + } } @override @@ -234,10 +247,12 @@ class GenericTableViewState extends material.State { } else if (oldWidget.isReadOnly != widget.isReadOnly) { _syncStagingToReadOnly(); } + _syncRelationsCommand(); } @override void dispose() { + TableViewCommandBridge.instance.unregister(owner: this); _resetStaging(); widget.delegate.cancel(interruptIfBusy: true); widget.delegate.dispose(); diff --git a/test/features/workspace/generic_table_view_relations_test.dart b/test/features/workspace/generic_table_view_relations_test.dart index 8ca1de5f..25a342bf 100644 --- a/test/features/workspace/generic_table_view_relations_test.dart +++ b/test/features/workspace/generic_table_view_relations_test.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart' as material; import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/actions/table_view_command_bridge.dart'; import 'package:querya_desktop/features/erd/erd_view.dart'; import 'package:querya_desktop/features/workspace/results_tab.dart'; @@ -7,6 +8,8 @@ import '../../support/fake_table_data_delegate.dart'; import '../../support/generic_table_view_harness.dart'; void main() { + setUp(() => TableViewCommandBridge.instance.resetForTest()); + testWidgets('a table offers the Data | Relations switch', (t) async { await pumpGenericTableView(t, FakeTableDataDelegate()); expect(find.text('Relations'), findsOneWidget); @@ -46,4 +49,25 @@ void main() { expect(find.byType(ErdView, skipOffstage: false), findsOneWidget); expect(find.byType(ResultsTab, skipOffstage: false), findsOneWidget); }); + + testWidgets('the palette switches a table between Data and Relations', + (t) async { + final bridge = TableViewCommandBridge.instance; + await pumpGenericTableView(t, FakeTableDataDelegate()); + expect(bridge.isActive, isTrue); + + bridge.invokeSelectView(1); + await t.pump(); + await t.pump(); + expect(find.byType(ErdView), findsOneWidget); + + bridge.invokeSelectView(0); + await t.pump(); + expect(find.text('Relations'), findsOneWidget); + }); + + testWidgets('a view leaves nothing for the palette to switch', (t) async { + await pumpGenericTableView(t, FakeTableDataDelegate(), isView: true); + expect(TableViewCommandBridge.instance.isActive, isFalse); + }); }