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 2021/10/25 18:51:14 UTC

[beam] branch master updated: Merge pull request #15783 from [BEAM-13048] [Playground] Add shortcuts

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 b52932c  Merge pull request #15783 from [BEAM-13048] [Playground] Add shortcuts
b52932c is described below

commit b52932c34cb5862068336711b4297a08bb9e47ef
Author: Aydar Farrakhov <st...@gmail.com>
AuthorDate: Mon Oct 25 21:50:20 2021 +0300

    Merge pull request #15783 from [BEAM-13048] [Playground] Add shortcuts
    
    * [BEAM-13048]: add keyboard shortcuts to playground
    
    * [BEAM-13048]: update shortcuts
    
    * [BEAM-13048]: fix lint errors
---
 playground/frontend/lib/config/theme.dart          |  12 +++
 playground/frontend/lib/constants/sizes.dart       |   1 +
 .../modules/shortcuts/components/shortcut_row.dart |  63 +++++++++++++
 .../shortcuts/components/shortcuts_manager.dart}   |  43 +++++----
 .../shortcuts/components/shortcuts_modal.dart      |  85 +++++++++++++++++
 .../shortcuts/constants/global_shortcuts.dart      | 103 +++++++++++++++++++++
 .../shortcuts/models/shortcut.dart}                |  33 +++----
 .../pages/playground/components/more_actions.dart  |  14 ++-
 .../lib/pages/playground/playground_page.dart      |   6 +-
 .../pages/playground/states/playground_state.dart  |   5 +
 playground/frontend/lib/playground_app.dart        |  17 ++--
 11 files changed, 334 insertions(+), 48 deletions(-)

diff --git a/playground/frontend/lib/config/theme.dart b/playground/frontend/lib/config/theme.dart
index 9baeb58..a76336b 100644
--- a/playground/frontend/lib/config/theme.dart
+++ b/playground/frontend/lib/config/theme.dart
@@ -101,6 +101,16 @@ TabBarTheme createTabBarTheme(Color textColor, Color indicatorColor) {
   );
 }
 
+DialogTheme createDialogTheme(Color textColor) {
+  return DialogTheme(
+    titleTextStyle: TextStyle(
+      color: textColor,
+      fontSize: 32.0,
+      fontWeight: kBoldWeight,
+    ),
+  );
+}
+
 final kLightTheme = ThemeData(
   brightness: Brightness.light,
   primaryColor: kLightPrimary,
@@ -111,6 +121,7 @@ final kLightTheme = ThemeData(
   textButtonTheme: createTextButtonTheme(kLightText),
   elevatedButtonTheme: createElevatedButtonTheme(kLightPrimary),
   tabBarTheme: createTabBarTheme(kLightText, kLightPrimary),
+  dialogTheme: createDialogTheme(kLightText),
 );
 
 final kDarkTheme = ThemeData(
@@ -123,6 +134,7 @@ final kDarkTheme = ThemeData(
   textButtonTheme: createTextButtonTheme(kDarkText),
   elevatedButtonTheme: createElevatedButtonTheme(kDarkPrimary),
   tabBarTheme: createTabBarTheme(kDarkText, kDarkPrimary),
+  dialogTheme: createDialogTheme(kDarkText),
 );
 
 class ThemeColors {
diff --git a/playground/frontend/lib/constants/sizes.dart b/playground/frontend/lib/constants/sizes.dart
index 66b8e16..a67eb78 100644
--- a/playground/frontend/lib/constants/sizes.dart
+++ b/playground/frontend/lib/constants/sizes.dart
@@ -30,6 +30,7 @@ const kIconButtonSplashRadius = 24.0;
 const kFooterHeight = 32.0;
 
 // border radius
+const double kSmBorderRadius = 4.0;
 const double kBorderRadius = 8.0;
 
 // elevation
diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcut_row.dart b/playground/frontend/lib/modules/shortcuts/components/shortcut_row.dart
new file mode 100644
index 0000000..27ddbe6
--- /dev/null
+++ b/playground/frontend/lib/modules/shortcuts/components/shortcut_row.dart
@@ -0,0 +1,63 @@
+/*
+ * 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/material.dart';
+import 'package:flutter/services.dart';
+import 'package:playground/constants/sizes.dart';
+import 'package:playground/modules/shortcuts/models/shortcut.dart';
+
+const kMetaKeyName = 'CMD/CTRL';
+const kShortcutKeyJoinSymbol = ' + ';
+
+class ShortcutRow extends StatelessWidget {
+  final Shortcut shortcut;
+
+  const ShortcutRow({Key? key, required this.shortcut}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final primaryColor = Theme.of(context).primaryColor;
+    // wrap with row to shrink container to child size
+    return Row(
+      children: [
+        Container(
+          decoration: BoxDecoration(
+            border: Border.all(
+              color: primaryColor,
+            ),
+            borderRadius: BorderRadius.circular(kSmBorderRadius),
+          ),
+          padding: const EdgeInsets.all(kMdSpacing),
+          child: Text(
+            shortcut.shortcuts.keys
+                .map((e) => getKeyDisplayName(e))
+                .join(kShortcutKeyJoinSymbol),
+            style: TextStyle(color: primaryColor),
+          ),
+        ),
+      ],
+    );
+  }
+
+  String getKeyDisplayName(LogicalKeyboardKey e) {
+    if (e.keyId == LogicalKeyboardKey.meta.keyId) {
+      return kMetaKeyName;
+    }
+    return e.keyLabel;
+  }
+}
diff --git a/playground/frontend/lib/playground_app.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_manager.dart
similarity index 53%
copy from playground/frontend/lib/playground_app.dart
copy to playground/frontend/lib/modules/shortcuts/components/shortcuts_manager.dart
index 8ce11d1..32d3ce2 100644
--- a/playground/frontend/lib/playground_app.dart
+++ b/playground/frontend/lib/modules/shortcuts/components/shortcuts_manager.dart
@@ -17,28 +17,35 @@
  */
 
 import 'package:flutter/material.dart';
-import 'package:playground/config/theme.dart';
-import 'package:playground/pages/playground/playground_page.dart';
-import 'package:provider/provider.dart';
+import 'package:playground/modules/shortcuts/models/shortcut.dart';
 
-class PlaygroundApp extends StatelessWidget {
-  const PlaygroundApp({Key? key}) : super(key: key);
+class ShortcutsManager extends StatelessWidget {
+  final Widget child;
+  final List<Shortcut> shortcuts;
+
+  const ShortcutsManager({
+    Key? key,
+    required this.child,
+    required this.shortcuts,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return ChangeNotifierProvider(
-      create: (context) => ThemeProvider(),
-      builder: (context, _) {
-        final themeProvider = Provider.of<ThemeProvider>(context);
-        return MaterialApp(
-          title: 'Apache Beam Playground',
-          themeMode: themeProvider.themeMode,
-          theme: kLightTheme,
-          darkTheme: kDarkTheme,
-          home: const PlaygroundPage(),
-          debugShowCheckedModeBanner: false,
-        );
-      },
+    return FocusableActionDetector(
+      autofocus: true,
+      shortcuts: _shortcutsMap,
+      actions: _getActions(context),
+      child: child,
     );
   }
+
+  Map<LogicalKeySet, Intent> get _shortcutsMap => {
+        for (var shortcut in shortcuts)
+          shortcut.shortcuts: shortcut.actionIntent
+      };
+
+  Map<Type, Action<Intent>> _getActions(BuildContext context) => {
+        for (var shortcut in shortcuts)
+          shortcut.actionIntent.runtimeType: shortcut.createAction(context)
+      };
 }
diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart
new file mode 100644
index 0000000..d2d048e
--- /dev/null
+++ b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart
@@ -0,0 +1,85 @@
+/*
+ * 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/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';
+
+const kCloseText = 'CLOSE';
+const kButtonBorderRadius = 24.0;
+const kButtonWidth = 120.0;
+const kButtonHeight = 40.0;
+const kDialogPadding = 40.0;
+
+class ShortcutsModal extends StatelessWidget {
+  const ShortcutsModal({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return AlertDialog(
+      title: const Text('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: kLgSpacing,
+        children: [
+          ...globalShortcuts.map(
+            (shortcut) => Row(
+              crossAxisAlignment: CrossAxisAlignment.center,
+              children: [
+                Expanded(child: ShortcutRow(shortcut: shortcut)),
+                Expanded(
+                  flex: 3,
+                  child: Text(
+                    shortcut.name,
+                    style: const TextStyle(fontWeight: kBoldWeight),
+                  ),
+                ),
+              ],
+            ),
+          )
+        ],
+      ),
+      actions: [
+        ElevatedButton(
+          child: const Text(kCloseText),
+          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(),
+        ),
+      ],
+    );
+  }
+}
diff --git a/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart b/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart
new file mode 100644
index 0000000..38e35de
--- /dev/null
+++ b/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart
@@ -0,0 +1,103 @@
+/*
+ * 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/material.dart';
+import 'package:flutter/services.dart';
+import 'package:playground/modules/shortcuts/models/shortcut.dart';
+import 'package:playground/pages/playground/states/playground_state.dart';
+import 'package:provider/provider.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+const kRunText = 'Run';
+const kResetText = 'Reset';
+const kClearOutputText = 'Clear Output';
+const kNewExampleText = 'New Example';
+
+class ResetIntent extends Intent {
+  const ResetIntent();
+}
+
+class RunIntent extends Intent {
+  const RunIntent();
+}
+
+class ClearOutputIntent extends Intent {
+  const ClearOutputIntent();
+}
+
+class NewExampleIntent extends Intent {
+  const NewExampleIntent();
+}
+
+final List<Shortcut> globalShortcuts = [
+  Shortcut(
+    shortcuts: LogicalKeySet(
+      LogicalKeyboardKey.meta,
+      LogicalKeyboardKey.enter,
+    ),
+    actionIntent: const RunIntent(),
+    name: kRunText,
+    createAction: (BuildContext context) => CallbackAction(
+      onInvoke: (_) => Provider.of<PlaygroundState>(
+        context,
+        listen: false,
+      ).runCode(),
+    ),
+  ),
+  Shortcut(
+    shortcuts: LogicalKeySet(
+      LogicalKeyboardKey.meta,
+      LogicalKeyboardKey.shift,
+      LogicalKeyboardKey.keyR,
+    ),
+    actionIntent: const ResetIntent(),
+    name: kResetText,
+    createAction: (BuildContext context) => CallbackAction(
+      onInvoke: (_) => Provider.of<PlaygroundState>(
+        context,
+        listen: false,
+      ).reset(),
+    ),
+  ),
+  Shortcut(
+    shortcuts: LogicalKeySet(
+      LogicalKeyboardKey.meta,
+      LogicalKeyboardKey.shift,
+      LogicalKeyboardKey.keyC,
+    ),
+    actionIntent: const ClearOutputIntent(),
+    name: kClearOutputText,
+    createAction: (BuildContext context) => CallbackAction(
+      onInvoke: (_) => Provider.of<PlaygroundState>(
+        context,
+        listen: false,
+      ).clearOutput(),
+    ),
+  ),
+  Shortcut(
+    shortcuts: LogicalKeySet(
+      LogicalKeyboardKey.meta,
+      LogicalKeyboardKey.keyM,
+    ),
+    actionIntent: const NewExampleIntent(),
+    name: kNewExampleText,
+    createAction: (_) => CallbackAction(
+      onInvoke: (_) => launch('/'),
+    ),
+  ),
+];
diff --git a/playground/frontend/lib/constants/sizes.dart b/playground/frontend/lib/modules/shortcuts/models/shortcut.dart
similarity index 62%
copy from playground/frontend/lib/constants/sizes.dart
copy to playground/frontend/lib/modules/shortcuts/models/shortcut.dart
index 66b8e16..15301a8 100644
--- a/playground/frontend/lib/constants/sizes.dart
+++ b/playground/frontend/lib/modules/shortcuts/models/shortcut.dart
@@ -16,25 +16,18 @@
  * limitations under the License.
  */
 
-// spacings
-const double kZeroSpacing = 0.0;
-const double kSmSpacing = 4.0;
-const double kMdSpacing = 8.0;
-const double kLgSpacing = 16.0;
+import 'package:flutter/material.dart';
 
-// sizes
-const kHeaderButtonHeight = 46.0;
-const kRunButtonWidth = 150.0;
-const kRunButtonHeight = 40.0;
-const kIconButtonSplashRadius = 24.0;
-const kFooterHeight = 32.0;
+class Shortcut {
+  final String name;
+  final LogicalKeySet shortcuts;
+  final Intent actionIntent;
+  final Function createAction;
 
-// border radius
-const double kBorderRadius = 8.0;
-
-// elevation
-const int kElevation = 1;
-
-// icon sizes
-const double kIconSizeSm = 16.0;
-const double kIconSizeMd = 24.0;
+  Shortcut({
+    required this.name,
+    required this.shortcuts,
+    required this.actionIntent,
+    required this.createAction,
+  });
+}
diff --git a/playground/frontend/lib/pages/playground/components/more_actions.dart b/playground/frontend/lib/pages/playground/components/more_actions.dart
index cd590ca..23ee0bf 100644
--- a/playground/frontend/lib/pages/playground/components/more_actions.dart
+++ b/playground/frontend/lib/pages/playground/components/more_actions.dart
@@ -21,6 +21,7 @@ import 'package:flutter_svg/flutter_svg.dart';
 
 import 'package:playground/config/theme.dart';
 import 'package:playground/constants/assets.dart';
+import 'package:playground/modules/shortcuts/components/shortcuts_modal.dart';
 import 'package:url_launcher/url_launcher.dart';
 
 enum HeaderAction {
@@ -50,10 +51,15 @@ const kBeamWebsiteLink = "https://beam.apache.org/";
 
 const kAboutBeamText = "About Apache Beam";
 
-class MoreActions extends StatelessWidget {
+class MoreActions extends StatefulWidget {
   const MoreActions({Key? key}) : super(key: key);
 
   @override
+  State<MoreActions> createState() => _MoreActionsState();
+}
+
+class _MoreActionsState extends State<MoreActions> {
+  @override
   Widget build(BuildContext context) {
     return Padding(
       padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
@@ -69,6 +75,12 @@ class MoreActions extends StatelessWidget {
             child: ListTile(
               leading: SvgPicture.asset(kShortcutsIconAsset),
               title: const Text(kShortcutsText),
+              onTap: () => {
+                showDialog<void>(
+                  context: context,
+                  builder: (BuildContext context) => const ShortcutsModal(),
+                )
+              },
             ),
           ),
           PopupMenuItem<HeaderAction>(
diff --git a/playground/frontend/lib/pages/playground/playground_page.dart b/playground/frontend/lib/pages/playground/playground_page.dart
index df2fbc5..1c3cf38 100644
--- a/playground/frontend/lib/pages/playground/playground_page.dart
+++ b/playground/frontend/lib/pages/playground/playground_page.dart
@@ -19,9 +19,10 @@
 import 'package:flutter/material.dart';
 import 'package:playground/components/toggle_theme_button/toggle_theme_button.dart';
 import 'package:playground/constants/sizes.dart';
+import 'package:playground/modules/shortcuts/components/shortcuts_manager.dart';
+import 'package:playground/modules/shortcuts/constants/global_shortcuts.dart';
 import 'package:playground/pages/playground/components/playground_page_body.dart';
 import 'package:playground/pages/playground/components/playground_page_footer.dart';
-import 'package:playground/pages/playground/components/playground_page_providers.dart';
 import 'package:playground/modules/actions/components/new_example_action.dart';
 import 'package:playground/modules/actions/components/reset_action.dart';
 import 'package:playground/pages/playground/components/more_actions.dart';
@@ -35,7 +36,8 @@ class PlaygroundPage extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return PlaygroundPageProviders(
+    return ShortcutsManager(
+      shortcuts: globalShortcuts,
       child: Scaffold(
         appBar: AppBar(
           title: Consumer<PlaygroundState>(
diff --git a/playground/frontend/lib/pages/playground/states/playground_state.dart b/playground/frontend/lib/pages/playground/states/playground_state.dart
index 15f0d3a..8eb7cda 100644
--- a/playground/frontend/lib/pages/playground/states/playground_state.dart
+++ b/playground/frontend/lib/pages/playground/states/playground_state.dart
@@ -66,6 +66,11 @@ class PlaygroundState with ChangeNotifier {
     _source = source;
   }
 
+  clearOutput() {
+    _result = null;
+    notifyListeners();
+  }
+
   reset() {
     _sdk = SDK.java;
     _source = _selectedExample?.sources[_sdk] ?? "";
diff --git a/playground/frontend/lib/playground_app.dart b/playground/frontend/lib/playground_app.dart
index 8ce11d1..54955d0 100644
--- a/playground/frontend/lib/playground_app.dart
+++ b/playground/frontend/lib/playground_app.dart
@@ -18,6 +18,7 @@
 
 import 'package:flutter/material.dart';
 import 'package:playground/config/theme.dart';
+import 'package:playground/pages/playground/components/playground_page_providers.dart';
 import 'package:playground/pages/playground/playground_page.dart';
 import 'package:provider/provider.dart';
 
@@ -30,13 +31,15 @@ class PlaygroundApp extends StatelessWidget {
       create: (context) => ThemeProvider(),
       builder: (context, _) {
         final themeProvider = Provider.of<ThemeProvider>(context);
-        return MaterialApp(
-          title: 'Apache Beam Playground',
-          themeMode: themeProvider.themeMode,
-          theme: kLightTheme,
-          darkTheme: kDarkTheme,
-          home: const PlaygroundPage(),
-          debugShowCheckedModeBanner: false,
+        return PlaygroundPageProviders(
+          child: MaterialApp(
+            title: 'Apache Beam Playground',
+            themeMode: themeProvider.themeMode,
+            theme: kLightTheme,
+            darkTheme: kDarkTheme,
+            home: const PlaygroundPage(),
+            debugShowCheckedModeBanner: false,
+          ),
         );
       },
     );