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
12 changes: 3 additions & 9 deletions lib/features/workspace/sql_query_tab_bar.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart' as material;
import 'package:querya_desktop/features/workspace/sql_editor_chrome.dart';
import 'package:querya_desktop/features/workspace/sql_query_tab_session.dart';
import 'package:querya_desktop/shared/widgets/widgets.dart';
import 'package:querya_desktop/core/ui/querya_control_tokens.dart';
Expand Down Expand Up @@ -26,18 +27,11 @@ class SqlQueryTabBar extends material.StatelessWidget {

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

// The same strip as the toolbar under it: workbench surface, hairline.
return material.Container(
height: 40,
padding: const material.EdgeInsets.symmetric(horizontal: 8),
decoration: material.BoxDecoration(
color: cs.card,
border: material.Border(
bottom: material.BorderSide(color: cs.border),
),
),
decoration: SqlEditorChrome.sqlToolbarDecoration(context),
child: material.Row(
children: [
material.Expanded(
Expand Down
44 changes: 29 additions & 15 deletions lib/features/workspace/sql_workspace_toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ class SqlWorkspaceToolbar extends StatelessWidget {
final VoidCallback onToggleAutocommit;
final void Function(int? seconds) onTimeoutChanged;

static const double compactWidthBreakpoint = 900;
/// Below this width only the run button keeps its label; the rest are
/// icons. Below [iconOnlyWidth] the run button is an icon too.
static const double labelsWidth = 900;
static const double iconOnlyWidth = 620;
static String get _modLabel => Platform.isMacOS ? '⌘' : 'Ctrl+';

List<QueryaActionMenuItem<SqlWorkspaceSessionChoice>> _sessionMenuItems() => [
Expand Down Expand Up @@ -125,27 +128,33 @@ class SqlWorkspaceToolbar extends StatelessWidget {
}
}

material.Widget _txBadge() {
final open = txOpen;
// An unknown state (before the first run) reads as the commit mode, not
// as a dash.
if (open == true) {
/// What is not the default: an open transaction or manual commit. Auto-commit
/// is the default and is named by the Session menu, so it has no badge of its
/// own next to it (#1558).
material.Widget? _txBadge() {
if (txOpen == true) {
return const QueryaBadge.status(
'Transaction open',
status: QueryaBadgeStatus.warning,
);
}
return QueryaBadge.status(
autocommit ? 'Auto-commit' : 'Manual commit',
status: autocommit ? QueryaBadgeStatus.neutral : QueryaBadgeStatus.info,
);
if (!autocommit) {
return const QueryaBadge.status(
'Manual commit',
status: QueryaBadgeStatus.info,
);
}
return null;
}

@override
material.Widget build(material.BuildContext context) {
return material.LayoutBuilder(
builder: (context, constraints) {
final compact = constraints.maxWidth < compactWidthBreakpoint;
// Controls give up their labels as the row narrows, the run button
// last, so the row stays on one line without hiding what it does.
final compact = constraints.maxWidth < labelsWidth;
final runCompact = constraints.maxWidth < iconOnlyWidth;
// One size for the whole row: the run split button, the buttons, the
// icon buttons and the session menu are all `md` (#1439).
return QueryaControlScope(
Expand All @@ -163,7 +172,7 @@ class SqlWorkspaceToolbar extends StatelessWidget {
menuKey: const material.ValueKey('run_menu'),
label: 'Execute (F5)',
icon: material.Icons.play_arrow_rounded,
compact: compact,
compact: runCompact,
loading: session.running,
onPressed: () => onExecute(false),
menuTooltip: 'Run options',
Expand All @@ -187,10 +196,11 @@ class SqlWorkspaceToolbar extends StatelessWidget {
),
if (delegate.supportsExplain) ...[
const material.SizedBox(width: 8),
QueryaButton.secondary(
// A magnifier over lines: the plan. The diagram keeps the tree.
QueryaButton.ghost(
key: const material.ValueKey('explain_query'),
label: 'Explain',
icon: material.Icons.account_tree_outlined,
icon: material.Icons.manage_search_rounded,
compact: compact,
tooltip: 'Show the query plan',
onPressed: session.running ? null : onExplain,
Expand Down Expand Up @@ -242,7 +252,7 @@ class SqlWorkspaceToolbar extends StatelessWidget {
const material.SizedBox(width: 8),
],
if (delegate.supportsTransactions) ...[
_txBadge(),
if (_txBadge() case final badge?) badge,
if (txOpen == true) ...[
const material.SizedBox(width: 8),
QueryaButton.secondary(
Expand All @@ -262,10 +272,14 @@ class SqlWorkspaceToolbar extends StatelessWidget {
],
QueryaMenuButton<SqlWorkspaceSessionChoice>(
key: const material.ValueKey('session_menu'),
variant: QueryaButtonVariant.ghost,
items: _sessionMenuItems(),
onSelected: (choice) => _onSessionChoice(context, choice),
enabled: !session.running,
icon: material.Icons.tune_rounded,
tooltip: autocommit
? 'Session: auto-commit'
: 'Session: manual commit',
label: compact
? null
: (autocommit ? 'Session · auto-commit' : 'Session · manual'),
Expand Down
12 changes: 8 additions & 4 deletions test/e2e/02_sql_workspaces/e2e_transaction_guard_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,23 @@ void main() {
final delegate = _TxDelegate();
await ws.pump(tester, delegate);
await E2eSqlWorkspace.settle(tester);
// Unknown until the first command refreshes the state.
expect(find.text('Auto-commit'), findsOneWidget);
// Unknown until the first command refreshes the state: the default,
// which has no badge; the Session menu names it.
expect(find.text('Transaction open'), findsNothing);
expect(find.byTooltip('Session: auto-commit'), findsOneWidget);

await beginFromSessionMenu(tester);
expect(find.text('Transaction open'), findsOneWidget);
expect(find.text('OK: BEGIN'), findsWidgets);

await tapAndSettle(tester, 'Rollback');
expect(find.text('Auto-commit'), findsOneWidget);
expect(find.text('Transaction open'), findsNothing);
expect(find.byTooltip('Session: auto-commit'), findsOneWidget);

await beginFromSessionMenu(tester);
await tapAndSettle(tester, 'Commit');
expect(find.text('Auto-commit'), findsOneWidget);
expect(find.text('Transaction open'), findsNothing);
expect(find.byTooltip('Session: auto-commit'), findsOneWidget);

expect(delegate.commands, ['BEGIN', 'ROLLBACK', 'BEGIN', 'COMMIT']);
});
Expand Down
16 changes: 13 additions & 3 deletions test/features/workspace/generic_sql_workspace_shortcuts_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -367,20 +367,30 @@ void main() {
expect(tester.getCenter(session).dy, closeTo(tester.getCenter(run).dy, 1));
});

testWidgets('below the breakpoint the labels hide and the icons stay',
testWidgets('the row gives up its labels one control at a time (#1558)',
timeout: _timeout, (tester) async {
await pumpWorkspace(tester, FakeSqlExecutionDelegate());
expect(find.text('Execute (F5)'), findsOneWidget);
expect(find.text('Explain'), findsOneWidget);
expect(find.text('Session · auto-commit'), findsOneWidget);

// Narrower: Explain and Session become icons, the run button keeps its
// label.
await tester.binding.setSurfaceSize(const material.Size(800, 700));
await tester.pumpAndSettle();
expect(find.text('Execute (F5)'), findsOneWidget);
expect(find.text('Explain'), findsNothing);
expect(find.text('Session · auto-commit'), findsNothing);
expect(find.byKey(const material.ValueKey('explain_query')), findsOneWidget);
expect(find.byTooltip('Show the query plan'), findsOneWidget);
expect(find.byTooltip('Session: auto-commit'), findsOneWidget);

// Narrowest: the run button is an icon too, and every control stays.
await tester.binding.setSurfaceSize(const material.Size(560, 700));
await tester.pumpAndSettle();
expect(find.text('Execute (F5)'), findsNothing);
expect(find.text('Explain'), findsNothing);
expect(find.byKey(const material.ValueKey('run_script')), findsOneWidget);
expect(find.byKey(const material.ValueKey('explain_query')), findsOneWidget);
expect(find.byTooltip('Show the query plan'), findsOneWidget);
expect(find.byTooltip('Execute (F5)'), findsOneWidget);
});

Expand Down
51 changes: 51 additions & 0 deletions test/guards/sql_workspace_chrome_guards_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'dart:io';

import 'package:flutter_test/flutter_test.dart';

/// The bars around the SQL editor are one visual system (#1558): they take
/// their colours from the workbench tokens, and no two controls of the row
/// share an icon.
void main() {
const chrome = [
'lib/features/workspace/sql_query_tab_bar.dart',
'lib/features/workspace/sql_workspace_toolbar.dart',
'lib/features/workspace/sql_editor_chrome.dart',
];

String code(String path) => File(path)
.readAsLinesSync()
.where((l) => !l.trimLeft().startsWith('//'))
.join('\n');

test('the SQL pane chrome uses workbench tokens, not the shadcn scheme', () {
for (final path in chrome) {
final source = code(path);
expect(source.contains('colorScheme'), isFalse,
reason: '$path reads Theme.colorScheme: use context.workbench.');
expect(RegExp(r'Color\(0x').hasMatch(source), isFalse,
reason: '$path has a raw colour: use a theme token.');
}
});

test('Explain and the Diagram button do not share an icon', () {
final toolbar = code('lib/features/workspace/sql_workspace_toolbar.dart');
final workspace = code('lib/features/workspace/generic_sql_workspace.dart');
final explain = RegExp(
r"ValueKey\('explain_query'\)[\s\S]*?icon: material\.Icons\.(\w+)",
).firstMatch(toolbar)?.group(1);
final diagram = RegExp(
r"ValueKey\('open_diagram_tab'\)[\s\S]*?icon: material\.Icons\.(\w+)",
).firstMatch(workspace)?.group(1);

expect(explain, isNotNull);
expect(diagram, isNotNull);
expect(explain, isNot(diagram));
});

test('the tab strip and the toolbar share one surface decoration', () {
expect(code('lib/features/workspace/sql_query_tab_bar.dart'),
contains('SqlEditorChrome.sqlToolbarDecoration'));
expect(code('lib/features/workspace/generic_sql_workspace.dart'),
contains('SqlEditorChrome.sqlToolbarDecoration'));
});
}
Loading