From 4ac928a2590cb99cd95b3784ef7cfe42630b8965 Mon Sep 17 00:00:00 2001 From: Parker Lougheed Date: Mon, 12 Feb 2024 16:54:27 -0600 Subject: [PATCH] Minor cleanup to new UI --- pkgs/sketch_pad/lib/console.dart | 19 ++++--- pkgs/sketch_pad/lib/editor/editor.dart | 2 +- pkgs/sketch_pad/lib/execution/execution.dart | 7 ++- pkgs/sketch_pad/lib/execution/frame.dart | 47 +++++++-------- pkgs/sketch_pad/lib/flutter_samples.dart | 15 ++--- pkgs/sketch_pad/lib/gists.dart | 27 ++++----- pkgs/sketch_pad/lib/keys.dart | 24 ++++---- pkgs/sketch_pad/lib/main.dart | 60 +++++++++++--------- pkgs/sketch_pad/lib/model.dart | 23 ++++---- pkgs/sketch_pad/lib/utils.dart | 10 ---- pkgs/sketch_pad/lib/versions.dart | 3 +- pkgs/sketch_pad/lib/widgets.dart | 13 +++++ 12 files changed, 131 insertions(+), 119 deletions(-) diff --git a/pkgs/sketch_pad/lib/console.dart b/pkgs/sketch_pad/lib/console.dart index 3ac727f65..d3142fa27 100644 --- a/pkgs/sketch_pad/lib/console.dart +++ b/pkgs/sketch_pad/lib/console.dart @@ -50,8 +50,11 @@ class _ConsoleWidgetState extends State { color: theme.scaffoldBackgroundColor, border: widget.showDivider ? Border( - top: Divider.createBorderSide(context, - width: 8.0, color: theme.colorScheme.surface)) + top: Divider.createBorderSide( + context, + width: 8.0, + color: theme.colorScheme.surface, + )) : null, ), padding: const EdgeInsets.all(denseSpacing), @@ -99,11 +102,13 @@ class _ConsoleWidgetState extends State { void _scrollToEnd() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - scrollController?.animateTo( - scrollController!.position.maxScrollExtent, - duration: animationDelay, - curve: animationCurve, - ); + if (scrollController case final scrollController?) { + scrollController.animateTo( + scrollController.position.maxScrollExtent, + duration: animationDelay, + curve: animationCurve, + ); + } }); } } diff --git a/pkgs/sketch_pad/lib/editor/editor.dart b/pkgs/sketch_pad/lib/editor/editor.dart index e061ea23f..19e75b521 100644 --- a/pkgs/sketch_pad/lib/editor/editor.dart +++ b/pkgs/sketch_pad/lib/editor/editor.dart @@ -37,7 +37,7 @@ web.Element _codeMirrorFactory(int viewId) { codeMirrorInstance = CodeMirror( div, - { + { 'lineNumbers': true, 'lineWrapping': true, 'mode': 'dart', diff --git a/pkgs/sketch_pad/lib/execution/execution.dart b/pkgs/sketch_pad/lib/execution/execution.dart index be53d8434..ee889c353 100644 --- a/pkgs/sketch_pad/lib/execution/execution.dart +++ b/pkgs/sketch_pad/lib/execution/execution.dart @@ -15,7 +15,7 @@ import 'frame.dart'; const String _viewType = 'dartpad-execution'; bool _viewFactoryInitialized = false; -ExecutionService? executionServiceInstance; +ExecutionService? _executionServiceInstance; final Key _elementViewKey = UniqueKey(); @@ -37,7 +37,7 @@ web.Element _iFrameFactory(int viewId) { ..style.width = '100%' ..style.height = '100%'; - executionServiceInstance = ExecutionServiceImpl(frame); + _executionServiceInstance = ExecutionServiceImpl(frame); return frame; } @@ -74,7 +74,8 @@ class _ExecutionWidgetState extends State { key: _elementViewKey, viewType: _viewType, onPlatformViewCreated: (int id) { - widget.appServices.registerExecutionService(executionServiceInstance); + widget.appServices + .registerExecutionService(_executionServiceInstance); }, ), ); diff --git a/pkgs/sketch_pad/lib/execution/frame.dart b/pkgs/sketch_pad/lib/execution/frame.dart index 70037564b..22a36d37d 100644 --- a/pkgs/sketch_pad/lib/execution/frame.dart +++ b/pkgs/sketch_pad/lib/execution/frame.dart @@ -143,14 +143,14 @@ require(["dartpad_main", "dart_sdk"], function(dartpad_main, dart_sdk) { /// Destroy and reload the iframe. Future _reset() { - if (_frame.parentElement != null) { + if (_frame.parentElement case final parentElement?) { _readyCompleter = Completer(); final clone = _frame.clone(false) as web.HTMLIFrameElement; clone.src = _frameSrc; - _frame.parentElement!.appendChild(clone); - _frame.parentElement!.removeChild(_frame); + parentElement.appendChild(clone); + parentElement.removeChild(_frame); _frame = clone; } @@ -162,29 +162,30 @@ require(["dartpad_main", "dart_sdk"], function(dartpad_main, dart_sdk) { void _initListener() { web.window.addEventListener( - 'message', - (web.Event event) { - if (event is web.MessageEvent) { - final data = event.data.dartify() as Map; - if (data['sender'] != 'frame') { - return; - } - final type = data['type'] as String?; - - if (type == 'stderr') { - // Ignore any exceptions before the iframe has completed - // initialization. - if (_readyCompleter.isCompleted) { - _stdoutController.add(data['message'] as String); - } - } else if (type == 'ready' && !_readyCompleter.isCompleted) { - _readyCompleter.complete(); - } else if (data['message'] != null) { + 'message', + (web.Event event) { + if (event is web.MessageEvent) { + final data = event.data.dartify(); + if (data is! Map || data['sender'] != 'frame') { + return; + } + final type = data['type'] as String?; + + if (type == 'stderr') { + // Ignore any exceptions before the iframe has completed + // initialization. + if (_readyCompleter.isCompleted) { _stdoutController.add(data['message'] as String); } + } else if (type == 'ready' && !_readyCompleter.isCompleted) { + _readyCompleter.complete(); + } else if (data['message'] != null) { + _stdoutController.add(data['message'] as String); } - }.toJS, - false.toJS); + } + }.toJS, + false.toJS, + ); } } diff --git a/pkgs/sketch_pad/lib/flutter_samples.dart b/pkgs/sketch_pad/lib/flutter_samples.dart index 42209c7d3..c031ced71 100644 --- a/pkgs/sketch_pad/lib/flutter_samples.dart +++ b/pkgs/sketch_pad/lib/flutter_samples.dart @@ -5,20 +5,21 @@ import 'package:http/http.dart' as http; class FlutterSampleLoader { - final http.Client client = http.Client(); + final http.Client _client = http.Client(); Future loadFlutterSample({ required String sampleId, String? channel, }) async { // There are only two hosted versions of the docs: master/main and stable. - final sampleUrl = switch (channel) { - 'master' => 'https://main-api.flutter.dev/snippets/$sampleId.dart', - 'main' => 'https://main-api.flutter.dev/snippets/$sampleId.dart', - _ => 'https://api.flutter.dev/snippets/$sampleId.dart', + final sampleUrlAuthority = switch (channel) { + 'master' || 'main' => 'main-api.flutter.dev', + _ => 'api.flutter.dev', }; - final response = await client.get(Uri.parse(sampleUrl)); + final sampleUrl = Uri.https(sampleUrlAuthority, '/snippets/$sampleId.dart'); + + final response = await _client.get(sampleUrl); if (response.statusCode != 200) { throw Exception('Unable to load sample ' @@ -29,6 +30,6 @@ class FlutterSampleLoader { } void dispose() { - client.close(); + _client.close(); } } diff --git a/pkgs/sketch_pad/lib/gists.dart b/pkgs/sketch_pad/lib/gists.dart index cece7ecb0..0975973ed 100644 --- a/pkgs/sketch_pad/lib/gists.dart +++ b/pkgs/sketch_pad/lib/gists.dart @@ -8,22 +8,22 @@ import 'package:collection/collection.dart'; import 'package:http/http.dart' as http; class GistLoader { - final http.Client client = http.Client(); + final http.Client _client = http.Client(); Future load(String gistId) async { final response = - await client.get(Uri.parse('https://api.github.com/gists/$gistId')); + await _client.get(Uri.https('api.github.com', '/gists/$gistId')); if (response.statusCode != 200) { throw Exception('Unable to load gist ' '(${response.statusCode} ${response.reasonPhrase}})'); } - return Gist.fromJson(jsonDecode(response.body) as Map); + return Gist.fromJson(jsonDecode(response.body) as Map); } void dispose() { - client.close(); + _client.close(); } } @@ -40,7 +40,7 @@ class Gist { required this.files, }); - factory Gist.fromJson(Map json) { + factory Gist.fromJson(Map json) { /* { "id": "d3bd83918d21b6d5f778bdc69c3d36d6", "description": "Fibonacci", @@ -65,25 +65,22 @@ class Gist { } }, } */ - final owner = json['owner'] as Map; - final files = json['files'] as Map; + final owner = json['owner'] as Map; + final files = json['files'] as Map; return Gist( id: json['id'] as String, description: json['description'] as String?, owner: owner['login'] as String?, files: files.values - .cast>() + .cast>() .map(GistFile.fromJson) - .toList(), + .toList(growable: false), ); } - String? get mainDartSource { - return files - .firstWhereOrNull((file) => file.fileName == 'main.dart') - ?.content; - } + String? get mainDartSource => + files.firstWhereOrNull((file) => file.fileName == 'main.dart')?.content; } class GistFile { @@ -99,7 +96,7 @@ class GistFile { required this.content, }); - factory GistFile.fromJson(Map json) { + factory GistFile.fromJson(Map json) { return GistFile( fileName: json['filename'] as String, truncated: json['truncated'] as bool, diff --git a/pkgs/sketch_pad/lib/keys.dart b/pkgs/sketch_pad/lib/keys.dart index 1b4239552..4f77ce125 100644 --- a/pkgs/sketch_pad/lib/keys.dart +++ b/pkgs/sketch_pad/lib/keys.dart @@ -38,14 +38,13 @@ final ShortcutActivator quickFixKeyActivator = SingleActivator( control: _nonMac, ); -// map of key activator names - -final List<(String, ShortcutActivator)> keyBindings = [ - ('Code completion', codeCompletionKeyActivator), - ('Find', findKeyActivator), - ('Find next', findNextKeyActivator), - ('Quick fixes', quickFixKeyActivator), - ('Reload', reloadKeyActivator), +/// Key binding names and activators. +final List<({String keyName, ShortcutActivator activator})> keyBindings = [ + (keyName: 'Code completion', activator: codeCompletionKeyActivator), + (keyName: 'Find', activator: findKeyActivator), + (keyName: 'Find next', activator: findNextKeyActivator), + (keyName: 'Quick fixes', activator: quickFixKeyActivator), + (keyName: 'Reload', activator: reloadKeyActivator), ]; extension SingleActivatorExtension on SingleActivator { @@ -60,10 +59,11 @@ extension SingleActivatorExtension on SingleActivator { return Container( decoration: BoxDecoration( - border: Border.fromBorderSide( - Divider.createBorderSide(context, width: 1.0, color: subtleColor), - ), - borderRadius: const BorderRadius.all(Radius.circular(4))), + border: Border.fromBorderSide( + Divider.createBorderSide(context, width: 1.0, color: subtleColor), + ), + borderRadius: const BorderRadius.all(Radius.circular(4)), + ), padding: const EdgeInsets.symmetric( vertical: 2, horizontal: 6, diff --git a/pkgs/sketch_pad/lib/main.dart b/pkgs/sketch_pad/lib/main.dart index 938fe3b32..3b2f8be6b 100644 --- a/pkgs/sketch_pad/lib/main.dart +++ b/pkgs/sketch_pad/lib/main.dart @@ -280,7 +280,7 @@ class _DartPadMainPageState extends State { height: toolbarItemHeight, child: Row( children: [ - dartLogo(width: 32), + const Logo(width: 32), const SizedBox(width: denseSpacing), Text(appName, style: TextStyle( @@ -308,7 +308,7 @@ class _DartPadMainPageState extends State { TextButton( onPressed: () { url_launcher.launchUrl( - Uri.parse('https://docs.flutter.dev/get-started/install'), + Uri.https('docs.flutter.dev', '/get-started/install'), ); }, child: const Row( @@ -606,8 +606,8 @@ class StatusLineWidget extends StatelessWidget { const SizedBox(width: defaultSpacing), TextButton( onPressed: () { - const url = 'https://dart.dev/tools/dartpad/privacy'; - url_launcher.launchUrl(Uri.parse(url)); + url_launcher + .launchUrl(Uri.https('dart.dev', '/tools/dartpad/privacy')); }, child: const Row( children: [ @@ -620,8 +620,8 @@ class StatusLineWidget extends StatelessWidget { const SizedBox(width: defaultSpacing), TextButton( onPressed: () { - const url = 'https://github.com/dart-lang/dart-pad/issues'; - url_launcher.launchUrl(Uri.parse(url)); + url_launcher.launchUrl( + Uri.https('github.com', '/dart-lang/dart-pad/issues')); }, child: const Row( children: [ @@ -662,9 +662,9 @@ class SectionWidget extends StatelessWidget { children: [ Row( children: [ - if (title != null) Text(title!, style: subtleText), + if (title case final title?) Text(title, style: subtleText), const Expanded(child: SizedBox(width: defaultSpacing)), - if (actions != null) actions!, + if (actions case final actions?) actions, ], ), const Divider(), @@ -683,13 +683,17 @@ class SectionWidget extends StatelessWidget { class NewSnippetWidget extends StatelessWidget { final AppServices appServices; - static final _menuItems = [ + static const _menuItems = [ ( label: 'Dart snippet', - icon: dartLogo(), + icon: Logo(), kind: 'dart', ), - (label: 'Flutter snippet', icon: flutterLogo(), kind: 'flutter'), + ( + label: 'Flutter snippet', + icon: Logo(flutter: true), + kind: 'flutter', + ), ]; const NewSnippetWidget({ @@ -713,7 +717,7 @@ class NewSnippetWidget extends StatelessWidget { child: MenuItemButton( leadingIcon: item.icon, child: Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 32, 0), + padding: const EdgeInsets.only(right: 32), child: Text(item.label), ), onPressed: () => appServices.resetTo(type: item.kind), @@ -742,10 +746,9 @@ class ListSamplesWidget extends StatelessWidget { } List _buildMenuItems(BuildContext context) { - final categories = Samples.categories.keys; - final menuItems = [ - for (final category in categories) ...[ + for (final MapEntry(key: category, value: samples) + in Samples.categories.entries) ...[ MenuItemButton( onPressed: null, child: Text( @@ -753,9 +756,10 @@ class ListSamplesWidget extends StatelessWidget { style: Theme.of(context).textTheme.bodyLarge, ), ), - for (final sample in Samples.categories[category]!) + for (final sample in samples) MenuItemButton( - leadingIcon: sample.isDart ? dartLogo() : flutterLogo(), + leadingIcon: + sample.isDart ? const Logo() : const Logo(flutter: true), onPressed: () => GoRouter.of(context).replaceQueryParam('sample', sample.id), child: Padding( @@ -763,10 +767,12 @@ class ListSamplesWidget extends StatelessWidget { child: Text(sample.name), ), ), - ] + ], ]; - return menuItems.map((e) => PointerInterceptor(child: e)).toList(); + return menuItems + .map((e) => PointerInterceptor(child: e)) + .toList(growable: false); } } @@ -795,7 +801,7 @@ class SelectChannelWidget extends StatelessWidget { (int index) => MenuItemButton( onPressed: () => _onTap(context, channels[index]), child: Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 32, 0), + padding: const EdgeInsets.only(right: 32), child: Text('${channels[index].displayName} channel'), ), ), @@ -807,7 +813,7 @@ class SelectChannelWidget extends StatelessWidget { void _onTap(BuildContext context, Channel channel) async { final appServices = Provider.of(context, listen: false); - // update the url + // Update the `channel` query parameter. GoRouter.of(context).replaceQueryParam('channel', channel.name); final version = await appServices.setChannel(channel); @@ -857,7 +863,7 @@ class OverflowMenu extends StatelessWidget { trailingIcon: const Icon(Icons.launch), onPressed: () => _onSelected(context, item.uri), child: Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 32, 0), + padding: const EdgeInsets.only(right: 32), child: Text(item.label), ), ), @@ -872,7 +878,7 @@ class OverflowMenu extends StatelessWidget { } class KeyBindingsTable extends StatelessWidget { - final List<(String, ShortcutActivator)> bindings; + final List<({String keyName, ShortcutActivator activator})> bindings; const KeyBindingsTable({ required this.bindings, @@ -886,7 +892,7 @@ class KeyBindingsTable extends StatelessWidget { children: [ const Divider(), Expanded( - child: VTable<(String, ShortcutActivator)>( + child: VTable<({String keyName, ShortcutActivator activator})>( showToolbar: false, showHeaders: false, startsSorted: true, @@ -896,7 +902,7 @@ class KeyBindingsTable extends StatelessWidget { label: 'Command', width: 100, grow: 0.5, - transformFunction: (binding) => binding.$1, + transformFunction: (binding) => binding.keyName, ), VTableColumn( label: 'Keyboard shortcut', @@ -904,10 +910,10 @@ class KeyBindingsTable extends StatelessWidget { grow: 0.5, alignment: Alignment.centerRight, transformFunction: (binding) => - (binding.$2 as SingleActivator).describe, + (binding.activator as SingleActivator).describe, styleFunction: (binding) => subtleText, renderFunction: (context, binding, _) { - return (binding.$2 as SingleActivator) + return (binding.activator as SingleActivator) .renderToWidget(context); }, ), diff --git a/pkgs/sketch_pad/lib/model.dart b/pkgs/sketch_pad/lib/model.dart index 3dd6c5b9f..50281302a 100644 --- a/pkgs/sketch_pad/lib/model.dart +++ b/pkgs/sketch_pad/lib/model.dart @@ -81,16 +81,13 @@ class AppModel { } void _recalcLayout() { - final hasConsoleText = consoleOutputController.text.isNotEmpty; - final isFlutter = _appIsFlutter; - - if (isFlutter == null) { - _layoutMode.value = LayoutMode.both; - } else if (!isFlutter) { - _layoutMode.value = LayoutMode.justConsole; - } else { - _layoutMode.value = hasConsoleText ? LayoutMode.both : LayoutMode.justDom; - } + _layoutMode.value = switch (_appIsFlutter) { + null => LayoutMode.both, + false => LayoutMode.justConsole, + true => consoleOutputController.text.isNotEmpty + ? LayoutMode.both + : LayoutMode.justDom, + }; } } @@ -396,9 +393,9 @@ enum Channel { static const defaultChannel = Channel.stable; - static List get valuesWithoutLocalhost { - return values.whereNot((channel) => channel == localhost).toList(); - } + static List get valuesWithoutLocalhost => values + .whereNot((channel) => channel == localhost) + .toList(growable: false); static Channel? forName(String name) { name = name.trim().toLowerCase(); diff --git a/pkgs/sketch_pad/lib/utils.dart b/pkgs/sketch_pad/lib/utils.dart index b65924d5c..d9b07ac0c 100644 --- a/pkgs/sketch_pad/lib/utils.dart +++ b/pkgs/sketch_pad/lib/utils.dart @@ -28,16 +28,6 @@ void unimplemented(BuildContext context, String message) { String generateSnippetName() => fluttering_phrases.generate(); -Image dartLogo({double? width}) { - return Image.asset('assets/dart_logo_128.png', - width: width ?? defaultIconSize); -} - -Image flutterLogo({double? width}) { - return Image.asset('assets/flutter_logo_192.png', - width: width ?? defaultIconSize); -} - RelativeRect calculatePopupMenuPosition( BuildContext context, { bool growUpwards = false, diff --git a/pkgs/sketch_pad/lib/versions.dart b/pkgs/sketch_pad/lib/versions.dart index a9a02af62..0433b4858 100644 --- a/pkgs/sketch_pad/lib/versions.dart +++ b/pkgs/sketch_pad/lib/versions.dart @@ -18,7 +18,8 @@ class VersionTable extends StatelessWidget { @override Widget build(BuildContext context) { - final packages = version.packages.where((p) => p.supported).toList(); + final packages = + version.packages.where((p) => p.supported).toList(growable: false); var versionText = 'Based on Dart SDK ${version.dartVersion} ' 'and Flutter SDK ${version.flutterVersion}'; diff --git a/pkgs/sketch_pad/lib/widgets.dart b/pkgs/sketch_pad/lib/widgets.dart index 87821a3bb..7ed625926 100644 --- a/pkgs/sketch_pad/lib/widgets.dart +++ b/pkgs/sketch_pad/lib/widgets.dart @@ -248,3 +248,16 @@ class GoldenRatioCenter extends StatelessWidget { ); } } + +final class Logo extends StatelessWidget { + final String _assetPath; + final double width; + + const Logo({super.key, bool flutter = false, this.width = defaultIconSize}) + : _assetPath = flutter + ? 'assets/flutter_logo_192.png' + : 'assets/dart_logo_128.png'; + + @override + Widget build(BuildContext context) => Image.asset(_assetPath, width: width); +}