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
332 changes: 170 additions & 162 deletions lib/features/workspace/generic_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,15 @@ class GenericSqlWorkspaceState extends material.State<GenericSqlWorkspace> {
onClose: _sessions.length > 1
? (index) => unawaited(closeTab(index))
: null,
trailing: widget.showDiagram
? QueryaActionButton(
key: const material.ValueKey('open_diagram_tab'),
label: 'Diagram',
icon: material.Icons.account_tree_outlined,
tooltip: 'Schema diagram',
onPressed: openDiagramTab,
)
: null,
),
material.Expanded(
child: material.IndexedStack(
Expand Down Expand Up @@ -1185,8 +1194,6 @@ class GenericSqlWorkspaceState extends material.State<GenericSqlWorkspace> {
onOpenTable: _openTableFromDiagram,
);
}
final theme = Theme.of(context);
final accent = context.workbench.accent;

return VerticalSplitPane(
key: material.ValueKey(session.id),
Expand All @@ -1198,156 +1205,7 @@ class GenericSqlWorkspaceState extends material.State<GenericSqlWorkspace> {
material.Container(
padding: const material.EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: SqlEditorChrome.sqlToolbarDecoration(context),
child: material.Column(
crossAxisAlignment: material.CrossAxisAlignment.stretch,
mainAxisSize: material.MainAxisSize.min,
children: [
material.Wrap(
spacing: 8,
runSpacing: 8,
crossAxisAlignment: material.WrapCrossAlignment.center,
children: [
if (widget.headerBadge != null)
widget.headerBadge!
else
const Text('Query').semiBold().small(),
if (effectiveDatabase.isNotEmpty)
Text('DB: $effectiveDatabase').muted().small(),
if (widget.delegate.supportsTransactions) _txBadge(),
QueryaActionButton(
label: 'History',
icon: material.Icons.history_rounded,
size: ButtonSize.small,
onPressed: widget.connectionRow.id != null && !session.running
? () => _openHistory(session)
: null,
),
material.Row(
mainAxisSize: material.MainAxisSize.min,
children: [
QueryaActionButton(
key: const material.ValueKey('run_script'),
label: 'Execute (F5)',
icon: material.Icons.play_arrow_rounded,
loading: session.running,
onPressed: () => execute(session),
),
const material.SizedBox(width: 4),
QueryaActionMenu<bool>(
key: const material.ValueKey('run_menu'),
items: [
QueryaActionMenuItem(
value: true,
label: 'Run statement (${_modLabel}Enter)',
icon: material.Icons.short_text_rounded,
),
const QueryaActionMenuItem(
value: false,
label: 'Run script (F5)',
icon: material.Icons.list_alt_rounded,
),
],
onSelected: (atCursor) {
if (!session.running) {
unawaited(execute(session, atCursor));
}
},
child: const material.Icon(
material.Icons.expand_more_rounded,
size: 16,
),
),
],
),
if (widget.delegate.supportsExplain)
QueryaActionButton(
key: const material.ValueKey('explain_query'),
label: 'Explain',
icon: material.Icons.account_tree_outlined,
tooltip: 'Show the query plan',
onPressed: session.running ? null : () => explain(session),
),
if (session.running && widget.delegate.supportsCancel)
QueryaActionButton(
key: const material.ValueKey('cancel_query'),
label: 'Cancel',
icon: material.Icons.stop_rounded,
isDestructive: true,
tooltip: 'Interrupt the running query',
onPressed: () => cancelRunning(session),
),
if (widget.showDiagram)
QueryaActionButton(
key: const material.ValueKey('open_diagram_tab'),
label: 'Diagram',
onPressed: openDiagramTab,
),
if (widget.extraToolbarTrailing?.call(context, session) case final extra?)
extra,
],
),
if (widget.supportsAutocommit || widget.supportsStmtTimeout || widget.delegate.supportsTransactions) ...[
const Gap(8),
material.Wrap(
spacing: 8,
runSpacing: 8,
crossAxisAlignment: material.WrapCrossAlignment.center,
children: [
// Autocommit, statement timeout, Begin and Preferences live
// in one Session menu.
material.IgnorePointer(
ignoring: session.running,
child: material.Opacity(
opacity: session.running ? 0.5 : 1,
child: QueryaActionMenu<_SessionChoice>(
key: const material.ValueKey('session_menu'),
items: _sessionMenuItems(),
onSelected: (choice) => _onSessionChoice(choice),
child: material.Padding(
padding: const material.EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
child: material.Row(
mainAxisSize: material.MainAxisSize.min,
children: [
material.Icon(material.Icons.tune_rounded,
size: 16, color: accent),
const material.SizedBox(width: 6),
Text(_autocommit
? 'Session · auto-commit'
: 'Session · manual'),
const material.SizedBox(width: 4),
material.Icon(
material.Icons.expand_more_rounded,
size: 16,
color: accent),
],
),
),
),
),
),
// Commit and Rollback only while a transaction is open; Begin
// otherwise.
if (widget.delegate.supportsTransactions)
if (_txOpen == true) ...[
QueryaActionButton(
label: 'Commit',
onPressed: session.running ? null : () => runTxCommand('COMMIT'),
),
QueryaActionButton(
label: 'Rollback',
onPressed: session.running ? null : () => runTxCommand('ROLLBACK'),
),
] else
QueryaActionButton(
label: 'Begin',
onPressed: session.running ? null : () => runTxCommand('BEGIN'),
),
],
),
],
],
),
child: _buildToolbarRow(context, session),
),
const Divider(height: 1),
Expanded(
Expand All @@ -1361,16 +1219,6 @@ class GenericSqlWorkspaceState extends material.State<GenericSqlWorkspace> {
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(),
),
const Divider(height: 1),
Expanded(
child: ResultsTab(
columns: session.columns,
Expand Down Expand Up @@ -1398,6 +1246,166 @@ class GenericSqlWorkspaceState extends material.State<GenericSqlWorkspace> {
);
}

/// The toolbar in one row: Run and Explain, the editor tools, then the
/// session state. On a narrow window it scrolls sideways instead of wrapping.
material.Widget _buildToolbarRow(
material.BuildContext context,
SqlQueryTabSession session,
) {
final accent = context.workbench.accent;
final historyEnabled = widget.connectionRow.id != null && !session.running;
return material.SingleChildScrollView(
scrollDirection: material.Axis.horizontal,
child: material.Row(
children: [
if (widget.headerBadge != null) ...[
widget.headerBadge!,
const material.SizedBox(width: 12),
],
QueryaActionButton(
key: const material.ValueKey('run_script'),
label: 'Execute (F5)',
icon: material.Icons.play_arrow_rounded,
loading: session.running,
onPressed: () => execute(session),
),
const material.SizedBox(width: 4),
QueryaActionMenu<bool>(
key: const material.ValueKey('run_menu'),
items: [
QueryaActionMenuItem(
value: true,
label: 'Run statement (${_modLabel}Enter)',
icon: material.Icons.short_text_rounded,
),
const QueryaActionMenuItem(
value: false,
label: 'Run script (F5)',
icon: material.Icons.list_alt_rounded,
),
],
onSelected: (atCursor) {
if (!session.running) {
unawaited(execute(session, atCursor));
}
},
child: const material.Icon(
material.Icons.expand_more_rounded,
size: 16,
),
),
if (widget.delegate.supportsExplain) ...[
const material.SizedBox(width: 8),
QueryaActionButton(
key: const material.ValueKey('explain_query'),
label: 'Explain',
icon: material.Icons.account_tree_outlined,
tooltip: 'Show the query plan',
onPressed: session.running ? null : () => explain(session),
),
],
if (session.running && widget.delegate.supportsCancel) ...[
const material.SizedBox(width: 8),
QueryaActionButton(
key: const material.ValueKey('cancel_query'),
label: 'Cancel',
icon: material.Icons.stop_rounded,
isDestructive: true,
tooltip: 'Interrupt the running query',
onPressed: () => cancelRunning(session),
),
],
const material.SizedBox(width: 16),
QueryaIconButton(
key: const material.ValueKey('history_button'),
icon: const material.Icon(material.Icons.history_rounded),
tooltip: 'History (${_modLabel}H)',
onPressed: historyEnabled ? () => _openHistory(session) : null,
),
QueryaIconButton(
key: const material.ValueKey('format_button'),
icon: const material.Icon(material.Icons.auto_fix_high_rounded),
tooltip: 'Format (Shift+Alt+F)',
onPressed: session.running ? null : () => session.formatSql(),
),
QueryaIconButton(
key: const material.ValueKey('open_sql_button'),
icon: const material.Icon(material.Icons.folder_open_outlined),
tooltip: 'Open .sql file',
onPressed: () => unawaited(openSqlFile()),
),
QueryaIconButton(
key: const material.ValueKey('save_sql_button'),
icon: const material.Icon(material.Icons.save_outlined),
tooltip: 'Save .sql file',
onPressed: () => unawaited(saveSqlFile()),
),
const material.SizedBox(width: 16),
if (effectiveDatabase.isNotEmpty) ...[
QueryaBadge.status(
effectiveDatabase,
status: QueryaBadgeStatus.neutral,
),
const material.SizedBox(width: 8),
],
if (widget.delegate.supportsTransactions) ...[
_txBadge(),
if (_txOpen == true) ...[
const material.SizedBox(width: 8),
QueryaActionButton(
label: 'Commit',
onPressed:
session.running ? null : () => runTxCommand('COMMIT'),
),
const material.SizedBox(width: 4),
QueryaActionButton(
label: 'Rollback',
onPressed:
session.running ? null : () => runTxCommand('ROLLBACK'),
),
],
const material.SizedBox(width: 8),
],
// Autocommit, statement timeout, Begin and Preferences live in one
// Session menu.
material.IgnorePointer(
ignoring: session.running,
child: material.Opacity(
opacity: session.running ? 0.5 : 1,
child: QueryaActionMenu<_SessionChoice>(
key: const material.ValueKey('session_menu'),
items: _sessionMenuItems(),
onSelected: (choice) => _onSessionChoice(choice),
child: material.Padding(
padding: const material.EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
child: material.Row(
mainAxisSize: material.MainAxisSize.min,
children: [
material.Icon(material.Icons.tune_rounded,
size: 16, color: accent),
const material.SizedBox(width: 6),
Text(_autocommit
? 'Session · auto-commit'
: 'Session · manual'),
const material.SizedBox(width: 4),
material.Icon(material.Icons.expand_more_rounded,
size: 16, color: accent),
],
),
),
),
),
),
if (widget.extraToolbarTrailing?.call(context, session) case final extra?) ...[
const material.SizedBox(width: 8),
extra,
],
],
),
);
}

/// Transaction state as a badge: an open transaction is a warning, because
/// work can be lost, and looks different from no transaction.
material.Widget _txBadge() {
Expand Down
8 changes: 8 additions & 0 deletions lib/features/workspace/sql_query_tab_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class SqlQueryTabBar extends material.StatelessWidget {
required this.onSelect,
required this.onAdd,
this.onClose,
this.trailing,
});

final List<SqlQueryTabSession> sessions;
Expand All @@ -19,6 +20,9 @@ class SqlQueryTabBar extends material.StatelessWidget {
final material.VoidCallback onAdd;
final material.ValueChanged<int>? onClose;

/// Shown at the right end of the strip (the schema diagram button).
final material.Widget? trailing;

@override
material.Widget build(material.BuildContext context) {
final theme = Theme.of(context);
Expand All @@ -45,6 +49,10 @@ class SqlQueryTabBar extends material.StatelessWidget {
canClose: sessions.length > 1 ? (_) => true : (_) => false,
),
),
if (trailing != null) ...[
const material.SizedBox(width: 8),
trailing!,
],
],
),
);
Expand Down
Loading
Loading