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
13 changes: 13 additions & 0 deletions lib/core/actions/sql_editor_actions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:flutter/widgets.dart';

class NewSqlIntent extends Intent {
const NewSqlIntent();
}

class OpenSqlIntent extends Intent {
const OpenSqlIntent();
}

class SaveSqlIntent extends Intent {
const SaveSqlIntent();
}
26 changes: 22 additions & 4 deletions lib/features/main_screen/querya_window_title_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/material.dart' as material
import 'package:querya_desktop/core/theme/querya_theme_scope.dart';
import 'package:querya_desktop/features/connections/driver_manager_dialog.dart';
import 'package:querya_desktop/features/settings/preferences_dialog.dart';
import 'package:querya_desktop/core/actions/sql_editor_actions.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';

/// Custom bitsdojo title bar styled from [QueryaThemeScope] workbench tokens.
Expand Down Expand Up @@ -76,12 +77,29 @@ class QueryaWindowTitleBar extends StatelessWidget {
MenuButton(
subMenu: [
MenuButton(
onPressed: (_) {}, child: const Text('New')),
MenuButton(
onPressed: (_) {},
onPressed: (ctx) {
Actions.maybeInvoke(
FocusManager.instance.primaryFocus?.context ?? ctx,
const NewSqlIntent(),
);
},
child: const Text('New')),
MenuButton(
onPressed: (ctx) {
Actions.maybeInvoke(
FocusManager.instance.primaryFocus?.context ?? ctx,
const OpenSqlIntent(),
);
},
child: const Text('Open...')),
MenuButton(
onPressed: (_) {}, child: const Text('Save')),
onPressed: (ctx) {
Actions.maybeInvoke(
FocusManager.instance.primaryFocus?.context ?? ctx,
const SaveSqlIntent(),
);
},
child: const Text('Save')),
const MenuDivider(),
MenuButton(
onPressed: (_) => appWindow.close(),
Expand Down
186 changes: 123 additions & 63 deletions lib/features/mysql/mysql_sql_workspace.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart';
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/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 @@ -237,78 +240,135 @@ class _MysqlSqlWorkspaceState extends material.State<MysqlSqlWorkspace> {
return v.toInt();
}

Future<void> _openSqlFile() async {
try {
final file = await openFile(
acceptedTypeGroups: const [
XTypeGroup(
label: 'SQL query',
extensions: ['sql'],
),
],
);
if (file == null) return;
final text = await file.readAsString();
if (!mounted) return;
_sqlController.value = material.TextEditingValue(
text: text,
selection: material.TextSelection.collapsed(offset: text.length),
);
} catch (_) {}
}

Future<void> _saveSqlFile() async {
try {
final name = 'query_${DateTime.now().toIso8601String().replaceAll(':', '-')}.sql';
final location = await getSaveLocation(
acceptedTypeGroups: const [
XTypeGroup(label: 'SQL', extensions: ['sql']),
],
suggestedName: name,
);
final path = location?.path;
if (path == null || path.isEmpty) return;
await File(path).writeAsString(_sqlController.text);
} catch (_) {}
}

@override
material.Widget build(material.BuildContext context) {
final theme = Theme.of(context);

return material.CallbackShortcuts(
bindings: {
const material.SingleActivator(LogicalKeyboardKey.f5): () {
if (!_running) {
unawaited(_execute());
}
},
return Actions(
actions: <Type, Action<Intent>>{
NewSqlIntent: CallbackAction<NewSqlIntent>(
onInvoke: (intent) {
_sqlController.clear();
return null;
},
),
OpenSqlIntent: CallbackAction<OpenSqlIntent>(
onInvoke: (intent) {
unawaited(_openSqlFile());
return null;
},
),
SaveSqlIntent: CallbackAction<SaveSqlIntent>(
onInvoke: (intent) {
unawaited(_saveSqlFile());
return null;
},
),
},
child: material.Focus(
autofocus: true,
child: VerticalSplitPane(
fraction: _topFraction,
maxFraction: 0.85,
top: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_MysqlSqlToolbar(
onExecute: _running ? null : _execute,
running: _running,
queryTimeoutSeconds: _queryTimeoutSeconds,
onQueryTimeoutChanged: _onStmtTimeoutChanged,
onOpenPreferences: () => showPreferencesDialog(context),
onOpenHistory: widget.connectionRow.id != null && !_running
? () {
showSqlQueryHistoryDialog(
context: context,
connectionId: widget.connectionRow.id!,
databaseName: widget.connectionRow.databaseName,
sqlController: _sqlController,
);
}
: null,
),
const Divider(height: 1),
Expanded(
child: QueryEditorTab(
controller: _sqlController,
fontSize: _editorFontSize,
child: material.CallbackShortcuts(
bindings: {
const material.SingleActivator(LogicalKeyboardKey.f5): () {
if (!_running) {
unawaited(_execute());
}
},
},
child: material.Focus(
autofocus: true,
child: VerticalSplitPane(
fraction: _topFraction,
maxFraction: 0.85,
top: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_MysqlSqlToolbar(
onExecute: _running ? null : _execute,
running: _running,
queryTimeoutSeconds: _queryTimeoutSeconds,
onQueryTimeoutChanged: _onStmtTimeoutChanged,
onOpenPreferences: () => showPreferencesDialog(context),
onOpenHistory: widget.connectionRow.id != null && !_running
? () {
showSqlQueryHistoryDialog(
context: context,
connectionId: widget.connectionRow.id!,
databaseName: widget.connectionRow.databaseName,
sqlController: _sqlController,
);
}
: null,
),
),
],
),
bottom: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
material.Container(
constraints: const material.BoxConstraints(minHeight: 44),
padding: const material.EdgeInsets.symmetric(
horizontal: 12,
const Divider(height: 1),
Expanded(
child: QueryEditorTab(
controller: _sqlController,
fontSize: _editorFontSize,
),
),
decoration: material.BoxDecoration(
color: theme.colorScheme.muted.withValues(alpha: 0.6),
],
),
bottom: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
material.Container(
constraints: const material.BoxConstraints(minHeight: 44),
padding: const material.EdgeInsets.symmetric(
horizontal: 12,
),
decoration: material.BoxDecoration(
color: theme.colorScheme.muted.withValues(alpha: 0.6),
),
alignment: material.Alignment.centerLeft,
child: const Text('Data Output').semiBold().small(),
),
alignment: material.Alignment.centerLeft,
child: const Text('Data Output').semiBold().small(),
),
const Divider(height: 1),
Expanded(
child: ResultsTab(
columns: _columns,
rows: _rows,
errorMessage: _error,
isLoading: _running,
affectedRows: _affectedRows,
statusLine: _statusLine,
const Divider(height: 1),
Expanded(
child: ResultsTab(
columns: _columns,
rows: _rows,
errorMessage: _error,
isLoading: _running,
affectedRows: _affectedRows,
statusLine: _statusLine,
),
),
),
],
],
),
),
),
),
Expand Down
Loading
Loading