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
6 changes: 6 additions & 0 deletions lib/core/actions/sql_connection_types.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:querya_desktop/core/storage/local_db.dart';

const kSqlCapableConnectionTypes = {'postgresql', 'mysql', 'sqlite'};

bool isSqlCapableConnection(ConnectionRow? connection) =>
connection != null && kSqlCapableConnectionTypes.contains(connection.type);
95 changes: 95 additions & 0 deletions lib/core/actions/sql_editor_command_bridge.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import 'package:flutter/foundation.dart';

enum SqlEditorPendingAction { none, newQuery, openFile, saveFile }

/// Lets the active SQL workspace handle File menu commands when menu focus is
/// outside the editor subtree (e.g. title bar).
class SqlEditorCommandBridge {
SqlEditorCommandBridge._();

static final SqlEditorCommandBridge instance = SqlEditorCommandBridge._();

int? _ownerConnectionId;
VoidCallback? _onNew;
VoidCallback? _onOpen;
VoidCallback? _onSave;

SqlEditorPendingAction pendingAction = SqlEditorPendingAction.none;

bool get isActive => _onNew != null;

void register({
required int? connectionId,
required VoidCallback onNew,
required VoidCallback onOpen,
required VoidCallback onSave,
}) {
_ownerConnectionId = connectionId;
_onNew = onNew;
_onOpen = onOpen;
_onSave = onSave;
_flushPending();
}

void unregister({required int? connectionId}) {
if (_ownerConnectionId != connectionId) return;
_ownerConnectionId = null;
_onNew = null;
_onOpen = null;
_onSave = null;
}

void queuePending(SqlEditorPendingAction action) {
pendingAction = action;
}

void invokeNew() {
if (_onNew != null) {
_onNew!();
return;
}
pendingAction = SqlEditorPendingAction.newQuery;
}

void invokeOpen() {
if (_onOpen != null) {
_onOpen!();
return;
}
pendingAction = SqlEditorPendingAction.openFile;
}

void invokeSave() {
if (_onSave != null) {
_onSave!();
return;
}
pendingAction = SqlEditorPendingAction.saveFile;
}

void _flushPending() {
final action = pendingAction;
if (action == SqlEditorPendingAction.none) return;
pendingAction = SqlEditorPendingAction.none;

switch (action) {
case SqlEditorPendingAction.none:
break;
case SqlEditorPendingAction.newQuery:
_onNew?.call();
case SqlEditorPendingAction.openFile:
_onOpen?.call();
case SqlEditorPendingAction.saveFile:
_onSave?.call();
}
}

@visibleForTesting
void resetForTest() {
_ownerConnectionId = null;
_onNew = null;
_onOpen = null;
_onSave = null;
pendingAction = SqlEditorPendingAction.none;
}
}
102 changes: 102 additions & 0 deletions lib/core/actions/sql_editor_global_actions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import 'package:flutter/material.dart' as material;
import 'package:flutter/widgets.dart';
import 'package:querya_desktop/core/actions/sql_connection_types.dart';
import 'package:querya_desktop/core/actions/sql_editor_actions.dart';
import 'package:querya_desktop/core/actions/sql_editor_command_bridge.dart';
import 'package:querya_desktop/core/storage/local_db.dart';

/// Fallback [Actions] for File → New/Open/Save when focus is outside a SQL
/// workspace (title bar, table view, MongoDB/Redis, empty workspace).
class SqlEditorGlobalActions extends StatelessWidget {
const SqlEditorGlobalActions({
super.key,
required this.activeConnection,
required this.onOpenSqlWorkspace,
required this.child,
});

final ConnectionRow? activeConnection;
final void Function(ConnectionRow connection) onOpenSqlWorkspace;
final Widget child;

static const _noSqlConnectionMessage =
'Select a PostgreSQL, MySQL, or SQLite connection to edit SQL files.';

@override
Widget build(BuildContext context) {
return Actions(
actions: <Type, Action<Intent>>{
NewSqlIntent: CallbackAction<NewSqlIntent>(
onInvoke: (_) {
_handleNew(context);
return null;
},
),
OpenSqlIntent: CallbackAction<OpenSqlIntent>(
onInvoke: (_) {
_handleOpen(context);
return null;
},
),
SaveSqlIntent: CallbackAction<SaveSqlIntent>(
onInvoke: (_) {
_handleSave(context);
return null;
},
),
},
child: child,
);
}

void _handleNew(material.BuildContext context) {
final bridge = SqlEditorCommandBridge.instance;
if (bridge.isActive) {
bridge.invokeNew();
return;
}
final connection = activeConnection;
if (!isSqlCapableConnection(connection)) {
_showHint(context, _noSqlConnectionMessage);
return;
}
bridge.queuePending(SqlEditorPendingAction.newQuery);
onOpenSqlWorkspace(connection!);
}

void _handleOpen(material.BuildContext context) {
final bridge = SqlEditorCommandBridge.instance;
if (bridge.isActive) {
bridge.invokeOpen();
return;
}
final connection = activeConnection;
if (!isSqlCapableConnection(connection)) {
_showHint(context, _noSqlConnectionMessage);
return;
}
bridge.queuePending(SqlEditorPendingAction.openFile);
onOpenSqlWorkspace(connection!);
}

void _handleSave(material.BuildContext context) {
final bridge = SqlEditorCommandBridge.instance;
if (bridge.isActive) {
bridge.invokeSave();
return;
}
final connection = activeConnection;
if (!isSqlCapableConnection(connection)) {
_showHint(context, _noSqlConnectionMessage);
return;
}
bridge.queuePending(SqlEditorPendingAction.saveFile);
onOpenSqlWorkspace(connection!);
}

static void _showHint(material.BuildContext context, String message) {
material.ScaffoldMessenger.of(context).showSnackBar(
material.SnackBar(content: material.Text(message)),
);
}
}
80 changes: 48 additions & 32 deletions lib/features/main_screen/main_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:flutter/material.dart' as material
BuildContext,
Widget,
RepaintBoundary;
import 'package:querya_desktop/core/actions/sql_editor_global_actions.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/core/theme/querya_theme_scope.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
Expand Down Expand Up @@ -123,6 +124,17 @@ class _MainScreenState extends State<MainScreen> {
_workspace.value = _workspace.value.openSqliteSqlWorkspace(connection);
}

void _openSqlWorkspaceForConnection(ConnectionRow connection) {
switch (connection.type) {
case 'postgresql':
_onPostgresOpenSqlWorkspace(connection);
case 'mysql':
_onMysqlOpenSqlWorkspace(connection);
case 'sqlite':
_onSqliteOpenSqlWorkspace(connection);
}
}

Future<void> _openNewConnectionFromHero() async {
final row = await promptCreateConnection(context);
if (!mounted || row == null) return;
Expand Down Expand Up @@ -152,17 +164,20 @@ class _MainScreenState extends State<MainScreen> {
@override
material.Widget build(material.BuildContext context) {
final wb = context.workbench;
return material.Scaffold(
backgroundColor: wb.canvas,
body: WindowBorder(
color: wb.borderSubtle.withValues(alpha: 0.35),
width: 1,
child: Column(
children: [
ValueListenableBuilder<MainScreenWorkspaceState>(
valueListenable: _workspace,
builder: (context, workspace, _) {
return QueryaWindowTitleBar(
return ValueListenableBuilder<MainScreenWorkspaceState>(
valueListenable: _workspace,
builder: (context, workspace, _) {
return SqlEditorGlobalActions(
activeConnection: workspace.activeConnection,
onOpenSqlWorkspace: _openSqlWorkspaceForConnection,
child: material.Scaffold(
backgroundColor: wb.canvas,
body: WindowBorder(
color: wb.borderSubtle.withValues(alpha: 0.35),
width: 1,
child: Column(
children: [
QueryaWindowTitleBar(
onNewDatabaseConnection: _onNewDatabaseConnectionFromMenu,
onNewDatabaseConnectionFromUrl: _onNewDatabaseConnectionFromUrl,
activeConnection: workspace.activeConnection,
Expand Down Expand Up @@ -197,29 +212,30 @@ class _MainScreenState extends State<MainScreen> {
_connectionsPanelKey.currentState?.disconnectOthers(active);
}
},
);
},
),
Divider(height: 1, color: wb.borderSubtle.withValues(alpha: 0.22)),
Expanded(
child: _MainContentSplit(
connectionsPanelKey: _connectionsPanelKey,
workspace: _workspace,
onConnectionSelected: _onConnectionSelected,
onPostgresObjectSelected: _onPostgresObjectSelected,
onMysqlObjectSelected: _onMysqlObjectSelected,
onSqliteObjectSelected: _onSqliteObjectSelected,
onRedisDatabaseSelected: _onRedisDatabaseSelected,
onMongoDBDatabaseSelected: _onMongoDBDatabaseSelected,
onPostgresOpenSqlWorkspace: _onPostgresOpenSqlWorkspace,
onMysqlOpenSqlWorkspace: _onMysqlOpenSqlWorkspace,
onSqliteOpenSqlWorkspace: _onSqliteOpenSqlWorkspace,
onRequestNewConnection: _openNewConnectionFromHero,
),
Divider(height: 1, color: wb.borderSubtle.withValues(alpha: 0.22)),
Expanded(
child: _MainContentSplit(
connectionsPanelKey: _connectionsPanelKey,
workspace: _workspace,
onConnectionSelected: _onConnectionSelected,
onPostgresObjectSelected: _onPostgresObjectSelected,
onMysqlObjectSelected: _onMysqlObjectSelected,
onSqliteObjectSelected: _onSqliteObjectSelected,
onRedisDatabaseSelected: _onRedisDatabaseSelected,
onMongoDBDatabaseSelected: _onMongoDBDatabaseSelected,
onPostgresOpenSqlWorkspace: _onPostgresOpenSqlWorkspace,
onMysqlOpenSqlWorkspace: _onMysqlOpenSqlWorkspace,
onSqliteOpenSqlWorkspace: _onSqliteOpenSqlWorkspace,
onRequestNewConnection: _openNewConnectionFromHero,
),
),
],
),
),
],
),
),
),
);
},
);
}
}
Expand Down
14 changes: 14 additions & 0 deletions lib/features/mysql/mysql_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter/material.dart' as material;
import 'package:flutter/services.dart' show LogicalKeyboardKey;
import 'package:file_selector/file_selector.dart';
import 'package:querya_desktop/core/actions/sql_editor_actions.dart';
import 'package:querya_desktop/core/actions/sql_editor_command_bridge.dart';
import 'package:querya_desktop/core/database/mysql_service.dart';
import 'package:querya_desktop/core/layout/vertical_split_pane.dart';
import 'package:querya_desktop/core/storage/app_settings.dart';
Expand Down Expand Up @@ -65,9 +66,20 @@ class _MysqlSqlWorkspaceState extends material.State<MysqlSqlWorkspace> {
SqlWorkspaceSettingsRevision.listenable.addListener(_appSettingsListener);
material.WidgetsBinding.instance.addPostFrameCallback((_) {
unawaited(_loadWorkspaceSettings());
_registerSqlEditorCommands();
});
}

void _registerSqlEditorCommands() {
if (!mounted) return;
SqlEditorCommandBridge.instance.register(
connectionId: widget.connectionRow.id,
onNew: () => _sqlController.clear(),
onOpen: () => unawaited(_openSqlFile()),
onSave: () => unawaited(_saveSqlFile()),
);
}

@override
void didUpdateWidget(covariant MysqlSqlWorkspace oldWidget) {
super.didUpdateWidget(oldWidget);
Expand Down Expand Up @@ -120,6 +132,8 @@ class _MysqlSqlWorkspaceState extends material.State<MysqlSqlWorkspace> {

@override
void dispose() {
SqlEditorCommandBridge.instance
.unregister(connectionId: widget.connectionRow.id);
SqlWorkspaceSettingsRevision.listenable
.removeListener(_appSettingsListener);
_topFraction.dispose();
Expand Down
14 changes: 14 additions & 0 deletions lib/features/postgresql/postgres_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/material.dart' as material;
import 'package:flutter/services.dart' show LogicalKeyboardKey;
import 'package:file_selector/file_selector.dart';
import 'package:querya_desktop/core/actions/sql_editor_actions.dart';
import 'package:querya_desktop/core/actions/sql_editor_command_bridge.dart';
import 'package:postgres/postgres.dart' as pg;
import 'package:querya_desktop/core/database/postgres_service.dart';
import 'package:querya_desktop/core/database/postgres_sql.dart';
Expand Down Expand Up @@ -106,9 +107,20 @@ class _PostgresSqlWorkspaceState extends material.State<PostgresSqlWorkspace> {
material.WidgetsBinding.instance.addPostFrameCallback((_) {
_syncPostgresSqlTreeContext();
unawaited(_loadWorkspaceSettings());
_registerSqlEditorCommands();
});
}

void _registerSqlEditorCommands() {
if (!mounted) return;
SqlEditorCommandBridge.instance.register(
connectionId: widget.connectionRow.id,
onNew: () => _sqlController.clear(),
onOpen: () => unawaited(_openSqlFile()),
onSave: () => unawaited(_saveSqlFile()),
);
}

@override
void didUpdateWidget(covariant PostgresSqlWorkspace oldWidget) {
super.didUpdateWidget(oldWidget);
Expand Down Expand Up @@ -257,6 +269,8 @@ class _PostgresSqlWorkspaceState extends material.State<PostgresSqlWorkspace> {

@override
void dispose() {
SqlEditorCommandBridge.instance
.unregister(connectionId: widget.connectionRow.id);
SqlWorkspaceSettingsRevision.listenable
.removeListener(_appSettingsListener);
_topFraction.dispose();
Expand Down
Loading
Loading