You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by pa...@apache.org on 2023/03/27 17:11:54 UTC

[beam] branch master updated: Issue25731 beam shortcut key sequence (#25876)

This is an automated email from the ASF dual-hosted git repository.

pabloem pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new cff22144756 Issue25731 beam shortcut key sequence (#25876)
cff22144756 is described below

commit cff221447564614abd6b0439459f60af4633e4b8
Author: Dmitry Repin <mr...@gmail.com>
AuthorDate: Mon Mar 27 21:11:46 2023 +0400

    Issue25731 beam shortcut key sequence (#25876)
    
    * Integration test to load the default example of the default SDK and change the example (#24730) (#24729)
    
    * Fix formatting and README (#24730)
    
    * Support collection v1.17.0 (#24730)
    
    * LoadingIndicator on chaning examples, remove duplicating licenses (#24730)
    
    * Add a missing license header (#24730)
    
    * Integration test for changing SDK and running code (#24779) (#382)
    
    * Integration test for changing SDK and running code (#24779)
    
    * Rename an integration test (#24779)
    
    * Use enum to switch SDK in integration test (#24779)
    
    * Find SDK in a dropdown by key (#24779)
    
    * Add a TODO (#24779)
    
    * Fix exports (#24779)
    
    * Issue24779 integration changing sdk from 24370 (#387)
    
    * Integration test for changing SDK and running code (#24779)
    
    * Rename an integration test (#24779)
    
    * Use enum to switch SDK in integration test (#24779)
    
    * Find SDK in a dropdown by key (#24779)
    
    * Add a TODO (#24779)
    
    * Fix exports (#24779)
    
    * Integration tests miscellaneous UI (#383)
    
    * miscellaneous ui integration tests
    
    * reverted pubspec.lock
    
    * gradle tasks ordered alhpabetically
    
    * integration tests refactoring
    
    * clean code
    
    * integration tests miscellaneous ui fix pr
    
    * rename method
    
    * added layout adaptivity
    
    * A minor cleanup (#24779)
    
    Co-authored-by: Dmitry Repin <mr...@gmail.com>
    
    * integration tests run and editing
    
    * example selector test
    
    * minor fixes
    
    * rat
    
    * fix pr
    
    * minor
    
    * minor
    
    * rat
    
    * integration test finder written
    
    * integration test minor fixes
    
    * minor fixes
    
    * removed comment
    
    * minor fixes
    
    * playground integration tests minor fixes
    
    * integration test pumpAnSettleNoException
    
    * integration test shortcut refactor
    
    * integration test another changing shortcuts running
    
    * upgrade to flutter 3.7.1
    
    * workaround comment
    
    * playground frontend updated major versions
    
    * issues 25329 25331 25336
    
    * 25329 extract connectivity extension to separate file
    
    * Upgrade Flutter to 3.7.3 in integration tests (#24730)
    
    * Fix integration test (#24730)
    
    * fix cors issue and added mouse scroll to tags
    
    * Upgrade Flutter in Dockerfile (#24720)
    
    * sorting moved to model
    
    * sorting moved to model
    
    * sorting moved to model
    
    * bugs fix
    
    * issue 25278
    
    * fix pr
    
    * quites fix in en.yaml
    
    * Fix not loading default example (#25528)
    
    * fix compile error
    
    * Refactor output tabs, test embedded playground (#25136) (#439)
    
    * Refactor output tabs, test embedded playground (#25136)
    
    * Clean up (#25136)
    
    * Change example paths to IDs in integration tests
    
    * issue25731 shortcut keys sequence
    
    * Update playground/frontend/playground_components/test/src/models/shortcut_test.dart
    
    Co-authored-by: alexeyinkin <le...@inkin.ru>
    
    * issue25731 fix shortcut modal width, fix shortcut meta key text
    
    * issue25731 fix shortcut modal window
    
    * issue25731 pr fix
    
    * issue25731 fix pr
    
    * issue25731 remane file
    
    * fix test
    
    ---------
    
    Co-authored-by: Alexey Inkin <al...@akvelon.com>
    Co-authored-by: alexeyinkin <le...@inkin.ru>
---
 .../integration_test/common/common_finders.dart    |  4 +-
 .../miscellaneous_ui/shortcuts_modal_test.dart     |  2 +-
 .../integration_test/standalone_editing_test.dart  |  4 +-
 .../standalone_run_shortcuts_test.dart             |  6 +-
 .../shortcuts/components/shortcuts_dialog.dart     | 77 +++++++++++++++++
 .../shortcuts/components/shortcuts_modal.dart      | 97 ----------------------
 .../shortcuts/constants/global_shortcuts.dart      |  8 +-
 .../widgets/more_actions.dart                      |  8 +-
 .../assets/translations/en.yaml                    |  3 +
 .../lib/playground_components.dart                 |  2 +
 .../lib/src/controllers/playground_controller.dart | 12 +--
 .../lib/src/models/shortcut.dart                   | 18 ++--
 .../lib/src/util/iterable.dart}                    | 21 ++---
 .../src/widgets/{dialog.dart => close_button.dart} | 42 ++++------
 .../lib/src/widgets/dialog.dart                    | 27 ++++--
 .../lib/src/widgets/shortcuts_manager.dart         |  2 +-
 .../test/src/models/shortcut_test.dart             | 86 +++++++++++++++++++
 .../lib/src/examples/python/aggregation_mean.dart  |  2 +-
 .../lib/src/widget_tester.dart                     |  2 +-
 19 files changed, 252 insertions(+), 171 deletions(-)

diff --git a/playground/frontend/integration_test/common/common_finders.dart b/playground/frontend/integration_test/common/common_finders.dart
index d0863dbfcbb..af49b996b54 100644
--- a/playground/frontend/integration_test/common/common_finders.dart
+++ b/playground/frontend/integration_test/common/common_finders.dart
@@ -26,7 +26,7 @@ import 'package:playground/modules/examples/components/description_popover/descr
 import 'package:playground/modules/examples/example_selector.dart';
 import 'package:playground/modules/sdk/components/sdk_selector.dart';
 import 'package:playground/modules/sdk/components/sdk_selector_row.dart';
-import 'package:playground/modules/shortcuts/components/shortcuts_modal.dart';
+import 'package:playground/modules/shortcuts/components/shortcuts_dialog.dart';
 import 'package:playground/pages/standalone_playground/widgets/editor_textarea_wrapper.dart';
 import 'package:playground/pages/standalone_playground/widgets/feedback/feedback_dropdown_content.dart';
 import 'package:playground/pages/standalone_playground/widgets/feedback/playground_feedback.dart';
@@ -117,6 +117,6 @@ extension CommonFindersExtension on CommonFinders {
   }
 
   Finder shortcutsModal() {
-    return byType(ShortcutsModal);
+    return byType(ShortcutsDialogContent);
   }
 }
diff --git a/playground/frontend/integration_test/miscellaneous_ui/shortcuts_modal_test.dart b/playground/frontend/integration_test/miscellaneous_ui/shortcuts_modal_test.dart
index e12752abef3..4b9b1a9fc3b 100644
--- a/playground/frontend/integration_test/miscellaneous_ui/shortcuts_modal_test.dart
+++ b/playground/frontend/integration_test/miscellaneous_ui/shortcuts_modal_test.dart
@@ -38,7 +38,7 @@ Future<void> checkShortcutsModal(WidgetTester wt) async {
 
   expect(find.shortcutsModal(), findsOneWidget);
 
-  await wt.tap(find.text(appLocale.close));
+  await wt.sendKeyEvent(LogicalKeyboardKey.escape);
   await wt.pumpAndSettle();
 
   expect(find.shortcutsModal(), findsNothing);
diff --git a/playground/frontend/integration_test/standalone_editing_test.dart b/playground/frontend/integration_test/standalone_editing_test.dart
index 83a4d433fd3..4b71cac107c 100644
--- a/playground/frontend/integration_test/standalone_editing_test.dart
+++ b/playground/frontend/integration_test/standalone_editing_test.dart
@@ -43,9 +43,7 @@ Future<void> _checkAutocomplete(WidgetTester wt) async {
   await wt.enterCodeFieldText('\n\n\n\n\nsdk');
 
   final playgroundController = wt.findPlaygroundController();
-  await wt.runShortcut(
-    playgroundController.showSuggestionsShortcut.shortcuts,
-  );
+  await wt.runShortcut(playgroundController.showSuggestionsShortcut);
   await wt.pumpAndSettle();
 
   expect(find.text('sdkHttpMetadata'), findsOneWidget);
diff --git a/playground/frontend/integration_test/standalone_run_shortcuts_test.dart b/playground/frontend/integration_test/standalone_run_shortcuts_test.dart
index 188cc0a2d2a..85d71e28221 100644
--- a/playground/frontend/integration_test/standalone_run_shortcuts_test.dart
+++ b/playground/frontend/integration_test/standalone_run_shortcuts_test.dart
@@ -50,7 +50,7 @@ Future<void> _checkResetShortcut(
 
   expect(controller.source, isNot(startSource));
 
-  await wt.runShortcut(controller.resetShortcut.shortcuts);
+  await wt.runShortcut(controller.resetShortcut);
   await wt.pumpAndSettle();
 
   expect(startSource, controller.source);
@@ -61,7 +61,7 @@ Future<void> _checkRunShortcut(
   PlaygroundController controller,
 ) async {
   final output = controller.codeRunner.resultLogOutput;
-  await wt.runShortcut(controller.runShortcut.shortcuts);
+  await wt.runShortcut(controller.runShortcut);
   await wt.pumpAndSettle();
 
   expect(output, isNot(controller.codeRunner.resultLogOutput));
@@ -74,7 +74,7 @@ Future<void> _checkClearOutputShortcut(
   expect(controller.codeRunner.resultLogOutput, isNotEmpty);
   expect(controller.codeRunner.resultLogOutput, isNotNull);
 
-  await wt.runShortcut(kClearOutputShortcut.shortcuts);
+  await wt.runShortcut(kClearOutputShortcut);
   await wt.pumpAndSettle();
 
   expect(controller.codeRunner.resultLogOutput, isEmpty);
diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcuts_dialog.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_dialog.dart
new file mode 100644
index 00000000000..4ebc14acdff
--- /dev/null
+++ b/playground/frontend/lib/modules/shortcuts/components/shortcuts_dialog.dart
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:playground/constants/font_weight.dart';
+import 'package:playground/constants/sizes.dart';
+import 'package:playground/modules/shortcuts/components/shortcut_row.dart';
+import 'package:playground/modules/shortcuts/constants/global_shortcuts.dart';
+import 'package:playground_components/playground_components.dart';
+
+class ShortcutsDialogContent extends StatelessWidget {
+  static const _kModalMaxWidth = 400.0;
+  static const _kShortcutsMaxWidth = 200.0;
+
+  const ShortcutsDialogContent({
+    required this.playgroundController,
+  });
+
+  final PlaygroundController playgroundController;
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      width: _kModalMaxWidth,
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        mainAxisSize: MainAxisSize.min,
+        children: [
+          ...[
+            ...playgroundController.shortcuts,
+            ...globalShortcuts,
+          ]
+              .map(
+                (shortcut) => Row(
+                  crossAxisAlignment: CrossAxisAlignment.center,
+                  children: [
+                    ConstrainedBox(
+                      constraints:
+                          const BoxConstraints(maxWidth: _kShortcutsMaxWidth),
+                      child: ShortcutRow(shortcut: shortcut),
+                    ),
+                    const SizedBox(width: kMdSpacing),
+                    Expanded(
+                      child: Text(
+                        shortcut.actionIntent.slug.tr(),
+                        style: const TextStyle(fontWeight: kBoldWeight),
+                      ),
+                    ),
+                  ],
+                ),
+              )
+              .alternateWith(
+                const SizedBox(
+                  height: kXlSpacing,
+                ),
+              ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart
deleted file mode 100644
index f4332e55ebd..00000000000
--- a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-import 'package:playground/constants/font_weight.dart';
-import 'package:playground/constants/sizes.dart';
-import 'package:playground/modules/shortcuts/components/shortcut_row.dart';
-import 'package:playground/modules/shortcuts/constants/global_shortcuts.dart';
-import 'package:playground_components/playground_components.dart';
-
-const kButtonBorderRadius = 24.0;
-const kButtonWidth = 120.0;
-const kButtonHeight = 40.0;
-const kDialogPadding = 40.0;
-
-class ShortcutsModal extends StatelessWidget {
-  final PlaygroundController playgroundController;
-
-  const ShortcutsModal({
-    required this.playgroundController,
-  });
-
-  @override
-  Widget build(BuildContext context) {
-    AppLocalizations appLocale = AppLocalizations.of(context)!;
-
-    return AlertDialog(
-      title: Text(appLocale.shortcuts),
-      titlePadding: const EdgeInsets.only(
-        top: kDialogPadding,
-        left: kDialogPadding,
-      ),
-      contentPadding: const EdgeInsets.all(kDialogPadding),
-      actionsPadding: const EdgeInsets.only(
-        bottom: kDialogPadding,
-        right: kDialogPadding,
-      ),
-      content: Wrap(
-        crossAxisAlignment: WrapCrossAlignment.start,
-        runSpacing: kXlSpacing,
-        children: [
-          ...[
-            ...playgroundController.shortcuts,
-            ...globalShortcuts,
-          ].map(
-            (shortcut) => Row(
-              crossAxisAlignment: CrossAxisAlignment.center,
-              children: [
-                Expanded(child: ShortcutRow(shortcut: shortcut)),
-                const SizedBox(width: kMdSpacing),
-                Expanded(
-                  flex: 3,
-                  child: Text(
-                    shortcut.actionIntent.slug.tr(),
-                    style: const TextStyle(fontWeight: kBoldWeight),
-                  ),
-                ),
-              ],
-            ),
-          )
-        ],
-      ),
-      actions: [
-        ElevatedButton(
-          style: ButtonStyle(
-            elevation: MaterialStateProperty.all<double>(0.0),
-            fixedSize: MaterialStateProperty.all<Size>(
-              const Size(kButtonWidth, kButtonHeight),
-            ),
-            shape: MaterialStateProperty.all<StadiumBorder>(
-              const StadiumBorder(),
-            ),
-          ),
-          onPressed: () => Navigator.of(context).pop(),
-          child: Text(appLocale.close),
-        ),
-      ],
-    );
-  }
-}
diff --git a/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart b/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart
index 2745c20ebdb..4c762e1c6c3 100644
--- a/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart
+++ b/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart
@@ -31,10 +31,10 @@ class NewExampleIntent extends BeamIntent {
 }
 
 final kClearOutputShortcut = BeamShortcut(
-  shortcuts: LogicalKeySet(
+  keys: [
     LogicalKeyboardKeyExtension.metaOrControl,
     LogicalKeyboardKey.keyB,
-  ),
+  ],
   actionIntent: const ClearOutputIntent(),
   createAction: (BuildContext context) => CallbackAction(
     onInvoke: (_) => Provider.of<PlaygroundController>(
@@ -45,10 +45,10 @@ final kClearOutputShortcut = BeamShortcut(
 );
 
 final kNewExampleShortcut = BeamShortcut(
-  shortcuts: LogicalKeySet(
+  keys: [
     LogicalKeyboardKeyExtension.metaOrControl,
     LogicalKeyboardKey.keyM,
-  ),
+  ],
   actionIntent: const NewExampleIntent(),
   createAction: (_) => CallbackAction(
     onInvoke: (_) => launchUrl(Uri.parse('/')),
diff --git a/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart b/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart
index 3a313ed522f..20be1db15a1 100644
--- a/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart
+++ b/playground/frontend/lib/pages/standalone_playground/widgets/more_actions.dart
@@ -25,7 +25,7 @@ import 'package:url_launcher/url_launcher.dart';
 
 import '../../../constants/links.dart';
 import '../../../modules/analytics/analytics_service.dart';
-import '../../../modules/shortcuts/components/shortcuts_modal.dart';
+import '../../../modules/shortcuts/components/shortcuts_dialog.dart';
 import '../../../src/assets/assets.gen.dart';
 
 enum HeaderAction {
@@ -85,9 +85,11 @@ class _MoreActionsState extends State<MoreActions> {
               title: Text(appLocale.shortcuts),
               onTap: () {
                 AnalyticsService.get(context).trackOpenShortcutsModal();
-                showDialog<void>(
+                BeamDialog.show(
+                  actions: [BeamCloseButton()],
                   context: context,
-                  builder: (BuildContext context) => ShortcutsModal(
+                  title: Text(appLocale.shortcuts),
+                  child: ShortcutsDialogContent(
                     playgroundController: widget.playgroundController,
                   ),
                 );
diff --git a/playground/frontend/playground_components/assets/translations/en.yaml b/playground/frontend/playground_components/assets/translations/en.yaml
index d13565223a3..7822c979686 100644
--- a/playground/frontend/playground_components/assets/translations/en.yaml
+++ b/playground/frontend/playground_components/assets/translations/en.yaml
@@ -43,6 +43,9 @@ widgets:
 
   codeEditor:
     label: 'Code Text Area'
+  
+  closeButton:
+    label: 'Close'
 
   output:
     filter:
diff --git a/playground/frontend/playground_components/lib/playground_components.dart b/playground/frontend/playground_components/lib/playground_components.dart
index 020fdc4b7a6..92d86e4119e 100644
--- a/playground/frontend/playground_components/lib/playground_components.dart
+++ b/playground/frontend/playground_components/lib/playground_components.dart
@@ -59,12 +59,14 @@ export 'src/services/symbols/loaders/yaml.dart';
 export 'src/theme/switch_notifier.dart';
 export 'src/theme/theme.dart';
 export 'src/util/async.dart';
+export 'src/util/iterable.dart';
 export 'src/util/logical_keyboard_key.dart';
 export 'src/util/pipeline_options.dart';
 export 'src/util/string.dart';
 
 export 'src/widgets/bubble.dart';
 export 'src/widgets/clickable.dart';
+export 'src/widgets/close_button.dart';
 export 'src/widgets/complexity.dart';
 export 'src/widgets/dialog.dart';
 export 'src/widgets/divider.dart';
diff --git a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
index f380af4cc02..e3fca592449 100644
--- a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
+++ b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
@@ -312,10 +312,10 @@ class PlaygroundController with ChangeNotifier {
   }
 
   late BeamShortcut runShortcut = BeamShortcut(
-    shortcuts: LogicalKeySet(
+    keys: [
       LogicalKeyboardKeyExtension.metaOrControl,
       LogicalKeyboardKey.enter,
-    ),
+    ],
     actionIntent: const RunIntent(),
     createAction: (BuildContext context) => CallbackAction(
       onInvoke: (_) => codeRunner.runCode(),
@@ -323,11 +323,11 @@ class PlaygroundController with ChangeNotifier {
   );
 
   late BeamShortcut resetShortcut = BeamShortcut(
-    shortcuts: LogicalKeySet(
+    keys: [
       LogicalKeyboardKeyExtension.metaOrControl,
       LogicalKeyboardKey.shift,
       LogicalKeyboardKey.keyE,
-    ),
+    ],
     actionIntent: const ResetIntent(),
     createAction: (BuildContext context) => CallbackAction(
       onInvoke: (_) => reset(),
@@ -335,11 +335,11 @@ class PlaygroundController with ChangeNotifier {
   );
 
   late BeamShortcut showSuggestionsShortcut = BeamShortcut(
-    shortcuts: LogicalKeySet(
+    keys: [
       LogicalKeyboardKeyExtension.metaOrControl,
       LogicalKeyboardKey.shift,
       LogicalKeyboardKey.keyS,
-    ),
+    ],
     actionIntent: const ShowSuggestionsIntent(),
     createAction: (BuildContext context) => CallbackAction(
       onInvoke: (_) => showSuggestions(),
diff --git a/playground/frontend/playground_components/lib/src/models/shortcut.dart b/playground/frontend/playground_components/lib/src/models/shortcut.dart
index ff4ede80f91..1f3a3898b7e 100644
--- a/playground/frontend/playground_components/lib/src/models/shortcut.dart
+++ b/playground/frontend/playground_components/lib/src/models/shortcut.dart
@@ -22,26 +22,32 @@ import 'package:flutter/services.dart';
 import 'intents.dart';
 
 class BeamShortcut {
-  final LogicalKeySet shortcuts;
+  // Keys in the order to be shown or mocked.
+  //
+  // A list is required because a [LogicalKeySet] discards the original order.
+  final List<LogicalKeyboardKey> keys;
+  
+  LogicalKeySet get keySet => LogicalKeySet.fromSet(keys.toSet());
+  
   final BeamIntent actionIntent;
   final CallbackAction Function(BuildContext) createAction;
 
   BeamShortcut({
-    required this.shortcuts,
+    required this.keys,
     required this.actionIntent,
     required this.createAction,
   });
 
-  static const _metaKeyName = 'CMD/CTRL';
+  static const _metaKeyName = 'Command';
   static const _glue = ' + ';
 
   String get title {
-    return shortcuts.keys
-        .map(getKeyDisplayName)
+    return keys
+        .map(_getKeyDisplayName)
         .join(_glue);
   }
 
-  String getKeyDisplayName(LogicalKeyboardKey e) {
+  String _getKeyDisplayName(LogicalKeyboardKey e) {
     if (e.keyId == LogicalKeyboardKey.meta.keyId) {
       return _metaKeyName;
     }
diff --git a/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart b/playground/frontend/playground_components/lib/src/util/iterable.dart
similarity index 67%
copy from playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart
copy to playground/frontend/playground_components/lib/src/util/iterable.dart
index e18ac22f502..3ce5b6f88b4 100644
--- a/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart
+++ b/playground/frontend/playground_components/lib/src/util/iterable.dart
@@ -16,16 +16,11 @@
  * limitations under the License.
  */
 
-import 'package:playground_components/playground_components.dart';
-
-import '../example_descriptor.dart';
-
-const pythonAggregationMean = ExampleDescriptor(
-  //
-  'AggregationMean',
-  dbPath: 'SDK_PYTHON_AggregationMean',
-  path: '/learning/katas/python/Common Transforms/Aggregation/Mean/task.py',
-  sdk: Sdk.python,
-
-  outputContains: ['16 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]'],
-);
+extension IterableExtension on Iterable {
+  Iterable<T> alternateWith<T>(T separator) {
+    return expand<T>((item) sync* {
+      yield separator;
+      yield item as T;
+    }).skip(1);
+  }
+}
diff --git a/playground/frontend/playground_components/lib/src/widgets/dialog.dart b/playground/frontend/playground_components/lib/src/widgets/close_button.dart
similarity index 58%
copy from playground/frontend/playground_components/lib/src/widgets/dialog.dart
copy to playground/frontend/playground_components/lib/src/widgets/close_button.dart
index 1a0a0c8e4d9..868e53fa920 100644
--- a/playground/frontend/playground_components/lib/src/widgets/dialog.dart
+++ b/playground/frontend/playground_components/lib/src/widgets/close_button.dart
@@ -16,36 +16,28 @@
  * limitations under the License.
  */
 
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 
-// TODO(alexeyinkin): In future convert all dialogs to this one.
-class BeamDialog extends StatelessWidget {
-  const BeamDialog({
-    required this.child,
-    this.title,
-  });
+class BeamCloseButton extends StatelessWidget {
+  static const _width = 120.0;
+  static const _height = 40.0;
 
-  final Widget child;
-  final Widget? title;
-
-  static Future<void> show({
-    required Widget child,
-    required BuildContext context,
-    Widget? title,
-  }) async {
-    await showDialog<void>(
-      context: context,
-      builder: (BuildContext context) => BeamDialog(
-        child: AlertDialog(
-          title: title,
-          content: child,
+  @override
+  Widget build(BuildContext context) {
+    return ElevatedButton(
+      style: const ButtonStyle(
+        elevation: MaterialStatePropertyAll<double>(0),
+        fixedSize: MaterialStatePropertyAll<Size>(
+          Size(_width, _height),
+        ),
+        shape: MaterialStatePropertyAll<StadiumBorder>(
+          StadiumBorder(),
         ),
+        padding: MaterialStatePropertyAll(EdgeInsets.only(bottom: 2)),
       ),
+      onPressed: () => Navigator.of(context).pop(),
+      child: Text('widgets.closeButton.label'.tr().toUpperCase()),
     );
   }
-
-  @override
-  Widget build(BuildContext context) {
-    return child;
-  }
 }
diff --git a/playground/frontend/playground_components/lib/src/widgets/dialog.dart b/playground/frontend/playground_components/lib/src/widgets/dialog.dart
index 1a0a0c8e4d9..7e14d481649 100644
--- a/playground/frontend/playground_components/lib/src/widgets/dialog.dart
+++ b/playground/frontend/playground_components/lib/src/widgets/dialog.dart
@@ -20,11 +20,15 @@ import 'package:flutter/material.dart';
 
 // TODO(alexeyinkin): In future convert all dialogs to this one.
 class BeamDialog extends StatelessWidget {
+  static const _padding = 40.0;
+
   const BeamDialog({
     required this.child,
+    this.actions = const [],
     this.title,
   });
 
+  final List<Widget> actions;
   final Widget child;
   final Widget? title;
 
@@ -32,20 +36,33 @@ class BeamDialog extends StatelessWidget {
     required Widget child,
     required BuildContext context,
     Widget? title,
+    List<Widget> actions = const [],
   }) async {
     await showDialog<void>(
       context: context,
       builder: (BuildContext context) => BeamDialog(
-        child: AlertDialog(
-          title: title,
-          content: child,
-        ),
+        actions: actions,
+        title: title,
+        child: child,
       ),
     );
   }
 
   @override
   Widget build(BuildContext context) {
-    return child;
+    return AlertDialog(
+      actions: actions,
+      actionsPadding: const EdgeInsets.only(
+        bottom: _padding,
+        right: _padding,
+      ),
+      content: child,
+      contentPadding: const EdgeInsets.all(_padding),
+      title: title,
+      titlePadding: const EdgeInsets.only(
+        top: _padding,
+        left: _padding,
+      ),
+    );
   }
 }
diff --git a/playground/frontend/playground_components/lib/src/widgets/shortcuts_manager.dart b/playground/frontend/playground_components/lib/src/widgets/shortcuts_manager.dart
index 2006c7662e7..d5184961f3a 100644
--- a/playground/frontend/playground_components/lib/src/widgets/shortcuts_manager.dart
+++ b/playground/frontend/playground_components/lib/src/widgets/shortcuts_manager.dart
@@ -43,7 +43,7 @@ class ShortcutsManager extends StatelessWidget {
 
   Map<LogicalKeySet, Intent> get _shortcutsMap => {
         for (var shortcut in shortcuts)
-          shortcut.shortcuts: shortcut.actionIntent
+          shortcut.keySet: shortcut.actionIntent
       };
 
   Map<Type, Action<Intent>> _getActions(BuildContext context) => {
diff --git a/playground/frontend/playground_components/test/src/models/shortcut_test.dart b/playground/frontend/playground_components/test/src/models/shortcut_test.dart
new file mode 100644
index 00000000000..db982e9bbc1
--- /dev/null
+++ b/playground/frontend/playground_components/test/src/models/shortcut_test.dart
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:playground_components/playground_components.dart';
+
+void main() {
+  group(
+    'BeamShortcut test.',
+    () {
+      test(
+        'Title builds correctly',
+        () {
+          const meta = 'Command';
+          final shortcutsAndTitles = {
+            //
+            _buildShortcut([
+              LogicalKeyboardKey.meta,
+              LogicalKeyboardKey.enter,
+            ]): '$meta + Enter',
+
+            _buildShortcut([
+              LogicalKeyboardKey.enter,
+              LogicalKeyboardKey.meta,
+            ]): 'Enter + $meta',
+
+            _buildShortcut([
+              LogicalKeyboardKey.meta,
+              LogicalKeyboardKey.shift,
+              LogicalKeyboardKey.keyS,
+            ]): '$meta + Shift + S',
+
+            _buildShortcut([
+              LogicalKeyboardKey.meta,
+              LogicalKeyboardKey.alt,
+              LogicalKeyboardKey.shift,
+              LogicalKeyboardKey.keyS,
+            ]): '$meta + Alt + Shift + S',
+
+            _buildShortcut([
+              LogicalKeyboardKey.control,
+              LogicalKeyboardKey.alt,
+              LogicalKeyboardKey.shift,
+              LogicalKeyboardKey.keyS,
+            ]): 'Control + Alt + Shift + S',
+          };
+
+          for (final entry in shortcutsAndTitles.entries) {
+            expect(entry.key.title, entry.value);
+          }
+        },
+      );
+    },
+  );
+}
+
+BeamShortcut _buildShortcut(List<LogicalKeyboardKey> keys) {
+  return BeamShortcut(
+    keys: keys,
+    actionIntent: const BeamIntent(
+      slug: 'slug',
+    ),
+    createAction: (_) => CallbackAction(
+      onInvoke: (_) {
+        return null;
+      },
+    ),
+  );
+}
diff --git a/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart b/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart
index e18ac22f502..7c91e14277f 100644
--- a/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart
+++ b/playground/frontend/playground_components_dev/lib/src/examples/python/aggregation_mean.dart
@@ -27,5 +27,5 @@ const pythonAggregationMean = ExampleDescriptor(
   path: '/learning/katas/python/Common Transforms/Aggregation/Mean/task.py',
   sdk: Sdk.python,
 
-  outputContains: ['16 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]'],
+  outputContains: ['5.5'],
 );
diff --git a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart
index 730d05154f2..112446c9758 100644
--- a/playground/frontend/playground_components_dev/lib/src/widget_tester.dart
+++ b/playground/frontend/playground_components_dev/lib/src/widget_tester.dart
@@ -77,7 +77,7 @@ extension WidgetTesterExtension on WidgetTester {
     return context.read<PlaygroundController>();
   }
 
-  Future<void> runShortcut(LogicalKeySet shortcut) async {
+  Future<void> runShortcut(BeamShortcut shortcut) async {
     final list = shortcut.keys.toList();
     for (final key in list) {
       await sendKeyDownEvent(key);