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 e3e30a7 Merge pull request #16857 from [BEAM-13662] [Playground] Support mutlifile examples e3e30a7 is described below commit e3e30a74c3254fbc37ba6a4fbbeb872415eaa122 Author: Aydar Farrakhov <stranni...@gmail.com> AuthorDate: Thu Feb 24 08:25:02 2022 +0300 Merge pull request #16857 from [BEAM-13662] [Playground] Support mutlifile examples * [BEAM-13771][Playground] Add multifile value to the response to the frontend * [BEAM-13771][Playground] Regenerate files * [BEAM-13771][Playground] Regenerate files * [BEAM-13662] playground - multifile example support * [BEAM-13662] playground - support multifile * [BEAM-13662] playground - redesign multifile examples * [BEAM-13662] playground - support multifile examples * [BEAM-13662] add licence * [BEAM-13662] refactor popover list for better readability * [BEAM-13662] refactor popover list for better readability Co-authored-by: AydarZaynutdinov <aydar.zaynutdi...@akvelon.com> Co-authored-by: Ilya <ilya.kozy...@akvelon.com> --- playground/frontend/assets/multifile.svg | 22 ++++ playground/frontend/lib/config.g.dart | 12 +-- playground/frontend/lib/constants/assets.dart | 1 + playground/frontend/lib/constants/links.dart | 1 + playground/frontend/lib/constants/sizes.dart | 1 + playground/frontend/lib/l10n/app_en.arb | 18 +++- .../lib/modules/editor/components/run_button.dart | 11 +- .../description_popover/description_popover.dart | 39 +++++-- .../description_popover_button.dart | 14 ++- .../example_list/example_item_actions.dart | 66 ++++++++++++ .../example_list/expansion_panel_item.dart | 9 +- .../multifile_popover.dart} | 26 +++-- .../multifile_popover_button.dart} | 36 ++++--- .../components/outside_click_handler.dart} | 37 +++++-- .../lib/modules/examples/example_selector.dart | 120 +++++++++++++-------- .../lib/modules/examples/models/example_model.dart | 4 + .../examples/models/popover_state.dart} | 22 ++-- .../example_client/grpc_example_client.dart | 2 + .../components/editor_textarea_wrapper.dart | 13 ++- 19 files changed, 341 insertions(+), 113 deletions(-) diff --git a/playground/frontend/assets/multifile.svg b/playground/frontend/assets/multifile.svg new file mode 100644 index 0000000..2a27ab2 --- /dev/null +++ b/playground/frontend/assets/multifile.svg @@ -0,0 +1,22 @@ +<!-- + 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. +--> + +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M10.75 4.36639C10.75 3.67604 11.3096 3.11639 12 3.11639H20C20.6904 3.11639 21.25 3.67604 21.25 4.36639V12.3664C21.25 13.0567 20.6904 13.6164 20 13.6164H19.5V15.1164H20C21.5188 15.1164 22.75 13.8852 22.75 12.3664V4.36639C22.75 2.84761 21.5188 1.61639 20 1.61639H12C10.4812 1.61639 9.25 2.84761 9.25 4.36639H10.75ZM6.75 8.36639C6.75 7.67604 7.30964 7.11639 8 7.11639H16C16.6904 7.11639 17.25 7.67604 17.25 8.36639V16.3664C17.25 17.0568 16.6904 1 [...] +</svg> diff --git a/playground/frontend/lib/config.g.dart b/playground/frontend/lib/config.g.dart index e189b32..c81baa5 100644 --- a/playground/frontend/lib/config.g.dart +++ b/playground/frontend/lib/config.g.dart @@ -16,14 +16,14 @@ * limitations under the License. */ -const String kApiClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com'; const String kAnalyticsUA = 'UA-73650088-1'; +const String kApiClientURL = + 'https://backend-router-beta-dot-apache-beam-testing.appspot.com'; const String kApiJavaClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com/java/'; + 'https://backend-java-beta-dot-apache-beam-testing.appspot.com'; const String kApiGoClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com/go/'; + 'https://backend-go-beta-dot-apache-beam-testing.appspot.com'; const String kApiPythonClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com/python/'; + 'https://backend-python-beta-dot-apache-beam-testing.appspot.com'; const String kApiScioClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com/scio/'; + 'https://backend-scio-beta-dot-apache-beam-testing.appspot.com'; diff --git a/playground/frontend/lib/constants/assets.dart b/playground/frontend/lib/constants/assets.dart index deaad15..796a97d 100644 --- a/playground/frontend/lib/constants/assets.dart +++ b/playground/frontend/lib/constants/assets.dart @@ -33,6 +33,7 @@ const kCopyIconAsset = 'copy.svg'; const kLinkIconAsset = 'link.svg'; const kDragHorizontalIconAsset = 'drag_horizontal.svg'; const kDragVerticalIconAsset = 'drag_vertical.svg'; +const kMultifileIconAsset = 'multifile.svg'; // notifications icons const kErrorNotificationIconAsset = 'error_notification.svg'; diff --git a/playground/frontend/lib/constants/links.dart b/playground/frontend/lib/constants/links.dart index e3b34eb..688a7ad 100644 --- a/playground/frontend/lib/constants/links.dart +++ b/playground/frontend/lib/constants/links.dart @@ -24,3 +24,4 @@ const kApacheBeamGithubLink = 'https://github.com/apache/beam'; const kBeamWebsiteLink = 'https://beam.apache.org/'; const kScioGithubLink = 'https://github.com/spotify/scio'; const kAboutBeamLink = 'https://beam.apache.org/get-started/beam-overview'; +const kAddExampleLink = 'https://beam.apache.org/get-started/try-beam-playground/#how-to-add-new-examples'; diff --git a/playground/frontend/lib/constants/sizes.dart b/playground/frontend/lib/constants/sizes.dart index 3962a19..74f61ae 100644 --- a/playground/frontend/lib/constants/sizes.dart +++ b/playground/frontend/lib/constants/sizes.dart @@ -52,6 +52,7 @@ const double kCursorSize = 1.0; // container size const double kContainerHeight = 40.0; +const double kCaptionFontSize = 10.0; const double kCodeFontSize = 14.0; const double kLabelFontSize = 16.0; const double kHintFontSize = 16.0; diff --git a/playground/frontend/lib/l10n/app_en.arb b/playground/frontend/lib/l10n/app_en.arb index 8b85370..2b2b470 100644 --- a/playground/frontend/lib/l10n/app_en.arb +++ b/playground/frontend/lib/l10n/app_en.arb @@ -184,7 +184,23 @@ "description": "Text value label" }, "pipelineOptionsError": "Please check the format (example: --key1 value1 --key2 value2), only alphanumeric and \",*,/,-,:,;,',. symbols are allowed", - "@value": { + "@pipelineOptionsError": { "description": "Pipeline options parse error" + }, + "viewOnGithub": "View on GitHub", + "@viewOnGithub": { + "description": "View on Github button" + }, + "addExample": "Add your own example", + "@addExample": { + "description": "Add example link text" + }, + "multifile": "Multifile", + "@multifile": { + "description": "Multifile example" + }, + "multifileWarning": "Multifile not supported yet for running in playground. Open it on github.", + "@multifileWarning": { + "description": "Multifile not supported text" } } \ No newline at end of file diff --git a/playground/frontend/lib/modules/editor/components/run_button.dart b/playground/frontend/lib/modules/editor/components/run_button.dart index 6aecc1f..d31e36d 100644 --- a/playground/frontend/lib/modules/editor/components/run_button.dart +++ b/playground/frontend/lib/modules/editor/components/run_button.dart @@ -32,12 +32,14 @@ class RunButton extends StatelessWidget { final bool isRunning; final VoidCallback runCode; final VoidCallback cancelRun; + final bool disabled; const RunButton({ Key? key, required this.isRunning, required this.runCode, required this.cancelRun, + this.disabled = false, }) : super(key: key); @override @@ -71,9 +73,16 @@ class RunButton extends StatelessWidget { } return Text(buttonText); }), - onPressed: !isRunning ? runCode : cancelRun, + onPressed: onPressHandler(), ), ), ); } + + onPressHandler() { + if (disabled) { + return null; + } + return !isRunning ? runCode : cancelRun; + } } diff --git a/playground/frontend/lib/modules/examples/components/description_popover/description_popover.dart b/playground/frontend/lib/modules/examples/components/description_popover/description_popover.dart index 6a37c8f..5de8144 100644 --- a/playground/frontend/lib/modules/examples/components/description_popover/description_popover.dart +++ b/playground/frontend/lib/modules/examples/components/description_popover/description_popover.dart @@ -17,9 +17,13 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:playground/constants/assets.dart'; import 'package:playground/constants/font_weight.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:url_launcher/url_launcher.dart'; const kDescriptionWidth = 300.0; @@ -30,26 +34,43 @@ class DescriptionPopover extends StatelessWidget { @override Widget build(BuildContext context) { + final hasLink = example.link?.isNotEmpty ?? false; return SizedBox( width: kDescriptionWidth, child: Card( child: Padding( padding: const EdgeInsets.all(kLgSpacing), child: Wrap( - runSpacing: kSmSpacing, + runSpacing: kMdSpacing, children: [ - Text( - example.name, - style: const TextStyle( - fontSize: kTitleFontSize, - fontWeight: kBoldWeight, - ), - ), - Text(example.description), + title, + description, + if (hasLink) getViewOnGithub(context), ], ), ), ), ); } + + Widget get title => Text( + example.name, + style: const TextStyle( + fontSize: kTitleFontSize, + fontWeight: kBoldWeight, + ), + ); + + Widget get description => Text(example.description); + + Widget getViewOnGithub(BuildContext context) { + AppLocalizations appLocale = AppLocalizations.of(context)!; + return TextButton.icon( + icon: SvgPicture.asset(kGithubIconAsset), + onPressed: () { + launch(example.link ?? ''); + }, + label: Text(appLocale.viewOnGithub), + ); + } } diff --git a/playground/frontend/lib/modules/examples/components/description_popover/description_popover_button.dart b/playground/frontend/lib/modules/examples/components/description_popover/description_popover_button.dart index d0eb6f7..d2e60a9 100644 --- a/playground/frontend/lib/modules/examples/components/description_popover/description_popover_button.dart +++ b/playground/frontend/lib/modules/examples/components/description_popover/description_popover_button.dart @@ -28,6 +28,8 @@ class DescriptionPopoverButton extends StatelessWidget { final ExampleModel example; final Alignment followerAnchor; final Alignment targetAnchor; + final void Function()? onOpen; + final void Function()? onClose; const DescriptionPopoverButton({ Key? key, @@ -35,6 +37,8 @@ class DescriptionPopoverButton extends StatelessWidget { required this.example, required this.followerAnchor, required this.targetAnchor, + this.onOpen, + this.onClose, }) : super(key: key); @override @@ -62,12 +66,15 @@ class DescriptionPopoverButton extends StatelessWidget { ExampleModel example, Alignment followerAnchor, Alignment targetAnchor, - ) { + ) async { // close previous description dialog Navigator.of(context, rootNavigator: true).popUntil((route) { return route.isFirst; }); - showAlignedDialog( + if (onOpen != null) { + onOpen!(); + } + await showAlignedDialog( context: context, builder: (dialogContext) => DescriptionPopover( example: example, @@ -76,5 +83,8 @@ class DescriptionPopoverButton extends StatelessWidget { targetAnchor: targetAnchor, barrierColor: Colors.transparent, ); + if (onClose != null) { + onClose!(); + } } } diff --git a/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart b/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart new file mode 100644 index 0000000..9cae77f --- /dev/null +++ b/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart @@ -0,0 +1,66 @@ +/* + * 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/modules/examples/components/description_popover/description_popover_button.dart'; +import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:playground/modules/examples/models/popover_state.dart'; +import 'package:provider/provider.dart'; + +import '../multifile_popover/multifile_popover_button.dart'; + +class ExampleItemActions extends StatelessWidget { + final ExampleModel example; + final BuildContext parentContext; + + const ExampleItemActions( + {Key? key, required this.parentContext, required this.example}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + if (example.isMultiFile) multifilePopover, + descriptionPopover, + ], + ); + } + + Widget get multifilePopover => MultifilePopoverButton( + parentContext: parentContext, + example: example, + followerAnchor: Alignment.topLeft, + targetAnchor: Alignment.topRight, + onOpen: () => _setPopoverOpen(parentContext, true), + onClose: () => _setPopoverOpen(parentContext, false), + ); + + Widget get descriptionPopover => DescriptionPopoverButton( + parentContext: parentContext, + example: example, + followerAnchor: Alignment.topLeft, + targetAnchor: Alignment.topRight, + onOpen: () => _setPopoverOpen(parentContext, true), + onClose: () => _setPopoverOpen(parentContext, false), + ); + + void _setPopoverOpen(BuildContext context, bool isOpen) { + Provider.of<PopoverState>(context, listen: false).setOpen(isOpen); + } +} diff --git a/playground/frontend/lib/modules/examples/components/example_list/expansion_panel_item.dart b/playground/frontend/lib/modules/examples/components/example_list/expansion_panel_item.dart index 74a6e23..7f29c44 100644 --- a/playground/frontend/lib/modules/examples/components/example_list/expansion_panel_item.dart +++ b/playground/frontend/lib/modules/examples/components/example_list/expansion_panel_item.dart @@ -19,7 +19,7 @@ import 'package:flutter/material.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/analytics/analytics_service.dart'; -import 'package:playground/modules/examples/components/description_popover/description_popover_button.dart'; +import 'package:playground/modules/examples/components/example_list/example_item_actions.dart'; import 'package:playground/modules/examples/models/example_model.dart'; import 'package:playground/pages/playground/states/examples_state.dart'; import 'package:playground/pages/playground/states/playground_state.dart'; @@ -72,12 +72,7 @@ class ExpansionPanelItem extends StatelessWidget { ? const TextStyle(fontWeight: FontWeight.bold) : const TextStyle(), ), - DescriptionPopoverButton( - parentContext: context, - example: example, - followerAnchor: Alignment.topLeft, - targetAnchor: Alignment.topRight, - ), + ExampleItemActions(parentContext: context, example: example), ], ), ), diff --git a/playground/frontend/lib/modules/examples/components/description_popover/description_popover.dart b/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover.dart similarity index 64% copy from playground/frontend/lib/modules/examples/components/description_popover/description_popover.dart copy to playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover.dart index 6a37c8f..00db2d7 100644 --- a/playground/frontend/lib/modules/examples/components/description_popover/description_popover.dart +++ b/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover.dart @@ -17,35 +17,47 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:playground/constants/assets.dart'; import 'package:playground/constants/font_weight.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:url_launcher/url_launcher.dart'; -const kDescriptionWidth = 300.0; +const kMultifileWidth = 300.0; -class DescriptionPopover extends StatelessWidget { +class MultifilePopover extends StatelessWidget { final ExampleModel example; - const DescriptionPopover({Key? key, required this.example}) : super(key: key); + const MultifilePopover({Key? key, required this.example}) : super(key: key); @override Widget build(BuildContext context) { + AppLocalizations appLocale = AppLocalizations.of(context)!; return SizedBox( - width: kDescriptionWidth, + width: kMultifileWidth, child: Card( child: Padding( padding: const EdgeInsets.all(kLgSpacing), child: Wrap( - runSpacing: kSmSpacing, + runSpacing: kMdSpacing, children: [ Text( - example.name, + appLocale.multifile, style: const TextStyle( fontSize: kTitleFontSize, fontWeight: kBoldWeight, ), ), - Text(example.description), + Text(appLocale.multifileWarning), + TextButton.icon( + icon: SvgPicture.asset(kGithubIconAsset), + onPressed: () { + launch(example.link ?? ''); + }, + label: Text(appLocale.viewOnGithub), + ), ], ), ), diff --git a/playground/frontend/lib/modules/examples/components/description_popover/description_popover_button.dart b/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover_button.dart similarity index 73% copy from playground/frontend/lib/modules/examples/components/description_popover/description_popover_button.dart copy to playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover_button.dart index d0eb6f7..149194d 100644 --- a/playground/frontend/lib/modules/examples/components/description_popover/description_popover_button.dart +++ b/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover_button.dart @@ -18,23 +18,28 @@ import 'package:aligned_dialog/aligned_dialog.dart'; import 'package:flutter/material.dart'; -import 'package:playground/config/theme.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:playground/constants/assets.dart'; import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/examples/components/description_popover/description_popover.dart'; +import 'package:playground/modules/examples/components/multifile_popover/multifile_popover.dart'; import 'package:playground/modules/examples/models/example_model.dart'; -class DescriptionPopoverButton extends StatelessWidget { +class MultifilePopoverButton extends StatelessWidget { final BuildContext? parentContext; final ExampleModel example; final Alignment followerAnchor; final Alignment targetAnchor; + final void Function()? onOpen; + final void Function()? onClose; - const DescriptionPopoverButton({ + const MultifilePopoverButton({ Key? key, this.parentContext, required this.example, required this.followerAnchor, required this.targetAnchor, + this.onOpen, + this.onClose, }) : super(key: key); @override @@ -42,12 +47,9 @@ class DescriptionPopoverButton extends StatelessWidget { return IconButton( iconSize: kIconSizeMd, splashRadius: kIconButtonSplashRadius, - icon: Icon( - Icons.info_outline_rounded, - color: ThemeColors.of(context).grey1Color, - ), + icon: SvgPicture.asset(kMultifileIconAsset), onPressed: () { - _showDescriptionPopover( + _showMultifilePopover( parentContext ?? context, example, followerAnchor, @@ -57,24 +59,30 @@ class DescriptionPopoverButton extends StatelessWidget { ); } - void _showDescriptionPopover( + void _showMultifilePopover( BuildContext context, ExampleModel example, Alignment followerAnchor, Alignment targetAnchor, - ) { - // close previous description dialog + ) async { + // close previous dialogs Navigator.of(context, rootNavigator: true).popUntil((route) { return route.isFirst; }); - showAlignedDialog( + if (onOpen != null) { + onOpen!(); + } + await showAlignedDialog( context: context, - builder: (dialogContext) => DescriptionPopover( + builder: (dialogContext) => MultifilePopover( example: example, ), followerAnchor: followerAnchor, targetAnchor: targetAnchor, barrierColor: Colors.transparent, ); + if (onClose != null) { + onClose!(); + } } } diff --git a/playground/frontend/lib/config.g.dart b/playground/frontend/lib/modules/examples/components/outside_click_handler.dart similarity index 52% copy from playground/frontend/lib/config.g.dart copy to playground/frontend/lib/modules/examples/components/outside_click_handler.dart index e189b32..16db300 100644 --- a/playground/frontend/lib/config.g.dart +++ b/playground/frontend/lib/modules/examples/components/outside_click_handler.dart @@ -16,14 +16,29 @@ * limitations under the License. */ -const String kApiClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com'; -const String kAnalyticsUA = 'UA-73650088-1'; -const String kApiJavaClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com/java/'; -const String kApiGoClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com/go/'; -const String kApiPythonClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com/python/'; -const String kApiScioClientURL = - 'https://backend-dot-datatokenization.uc.r.appspot.com/scio/'; +import 'package:flutter/material.dart'; +import 'package:playground/modules/examples/models/popover_state.dart'; +import 'package:provider/provider.dart'; + +class OutsideClickHandler extends StatelessWidget { + final void Function() onTap; + + const OutsideClickHandler({Key? key, required this.onTap}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Consumer<PopoverState>(builder: (context, state, child) { + if (state.isOpen) { + return Container(); + } + return GestureDetector( + onTap: onTap, + child: Container( + color: Colors.transparent, + height: double.infinity, + width: double.infinity, + ), + ); + }); + } +} diff --git a/playground/frontend/lib/modules/examples/example_selector.dart b/playground/frontend/lib/modules/examples/example_selector.dart index 1994a65..8824d6e 100644 --- a/playground/frontend/lib/modules/examples/example_selector.dart +++ b/playground/frontend/lib/modules/examples/example_selector.dart @@ -17,21 +17,26 @@ */ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:playground/components/loading_indicator/loading_indicator.dart'; import 'package:playground/config/theme.dart'; +import 'package:playground/constants/links.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/examples/components/examples_components.dart'; +import 'package:playground/modules/examples/components/outside_click_handler.dart'; +import 'package:playground/modules/examples/models/popover_state.dart'; import 'package:playground/modules/examples/models/selector_size_model.dart'; import 'package:playground/pages/playground/states/example_selector_state.dart'; import 'package:playground/pages/playground/states/examples_state.dart'; import 'package:playground/pages/playground/states/playground_state.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; const int kAnimationDurationInMilliseconds = 80; const Offset kAnimationBeginOffset = Offset(0.0, -0.02); const Offset kAnimationEndOffset = Offset(0.0, 0.0); const double kAdditionalDyAlignment = 50.0; -const double kLgContainerHeight = 444.0; +const double kLgContainerHeight = 490.0; const double kLgContainerWidth = 400.0; class ExampleSelector extends StatefulWidget { @@ -121,60 +126,63 @@ class _ExampleSelectorState extends State<ExampleSelector> return OverlayEntry( builder: (context) { - return Consumer2<ExampleState, PlaygroundState>( - builder: (context, exampleState, playgroundState, child) => Stack( - children: [ - GestureDetector( - onTap: () { - closeDropdown(exampleState); - // handle description dialogs - Navigator.of(context, rootNavigator: true).popUntil((route) { - return route.isFirst; - }); - }, - child: Container( - color: Colors.transparent, - height: double.infinity, - width: double.infinity, - ), - ), - ChangeNotifierProvider( - create: (context) => ExampleSelectorState( - exampleState, - playgroundState, - exampleState.getCategories(playgroundState.sdk)!, - ), - builder: (context, _) => Positioned( - left: posModel.xAlignment, - top: posModel.yAlignment + kAdditionalDyAlignment, - child: SlideTransition( - position: offsetAnimation, - child: Material( - elevation: kElevation.toDouble(), - child: Container( - height: kLgContainerHeight, - width: kLgContainerWidth, - decoration: BoxDecoration( - color: Theme.of(context).backgroundColor, - borderRadius: BorderRadius.circular(kMdBorderRadius), + return ChangeNotifierProvider<PopoverState>( + create: (context) => PopoverState(false), + builder: (context, state) { + return Consumer2<ExampleState, PlaygroundState>( + builder: (context, exampleState, playgroundState, child) => Stack( + children: [ + OutsideClickHandler( + onTap: () { + closeDropdown(exampleState); + // handle description dialogs + Navigator.of(context, rootNavigator: true).popUntil((route) { + return route.isFirst; + }); + }, + ), + ChangeNotifierProvider( + create: (context) => ExampleSelectorState( + exampleState, + playgroundState, + exampleState.getCategories(playgroundState.sdk)!, + ), + builder: (context, _) => Positioned( + left: posModel.xAlignment, + top: posModel.yAlignment + kAdditionalDyAlignment, + child: SlideTransition( + position: offsetAnimation, + child: Material( + elevation: kElevation.toDouble(), + child: Container( + height: kLgContainerHeight, + width: kLgContainerWidth, + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + borderRadius: BorderRadius.circular(kMdBorderRadius), + ), + child: exampleState.sdkCategories == null || + playgroundState.selectedExample == null + ? const LoadingIndicator(size: kContainerHeight) + : _buildDropdownContent(context, playgroundState), + ), ), - child: exampleState.sdkCategories == null || - playgroundState.selectedExample == null - ? const LoadingIndicator(size: kContainerHeight) - : _buildDropdownContent(playgroundState), ), ), ), - ), + ], ), - ], - ), + ); + } ); }, ); } - Widget _buildDropdownContent(PlaygroundState playgroundState) { + Widget _buildDropdownContent( + BuildContext context, + PlaygroundState playgroundState, + ) { return Column( children: [ SearchField(controller: textController), @@ -185,6 +193,28 @@ class _ExampleSelectorState extends State<ExampleSelector> animationController: animationController, dropdown: examplesDropdown, ), + Divider( + height: kDividerHeight, + color: ThemeColors.of(context).greyColor, + indent: kLgSpacing, + endIndent: kLgSpacing, + ), + SizedBox( + width: double.infinity, + child: TextButton( + child: Padding( + padding: const EdgeInsets.all(kXlSpacing), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + AppLocalizations.of(context)!.addExample, + style: TextStyle(color: ThemeColors.of(context).primary), + ), + ), + ), + onPressed: () => launch(kAddExampleLink), + ), + ) ], ); } diff --git a/playground/frontend/lib/modules/examples/models/example_model.dart b/playground/frontend/lib/modules/examples/models/example_model.dart index ff95440..e6f850a 100644 --- a/playground/frontend/lib/modules/examples/models/example_model.dart +++ b/playground/frontend/lib/modules/examples/models/example_model.dart @@ -43,6 +43,8 @@ class ExampleModel with Comparable<ExampleModel> { final String name; final String path; final String description; + bool isMultiFile; + String? link; String? source; String? outputs; String? logs; @@ -54,6 +56,8 @@ class ExampleModel with Comparable<ExampleModel> { required this.path, required this.description, required this.type, + this.isMultiFile = false, + this.link, this.source, this.outputs, this.logs, diff --git a/playground/frontend/lib/constants/links.dart b/playground/frontend/lib/modules/examples/models/popover_state.dart similarity index 61% copy from playground/frontend/lib/constants/links.dart copy to playground/frontend/lib/modules/examples/models/popover_state.dart index e3b34eb..af51052 100644 --- a/playground/frontend/lib/constants/links.dart +++ b/playground/frontend/lib/modules/examples/models/popover_state.dart @@ -16,11 +16,17 @@ * limitations under the License. */ -const kReportIssueLink = 'https://issues.apache.org/jira/projects/BEAM/issues/'; -const kBeamPrivacyPolicyLink = 'https://beam.apache.org/privacy_policy/'; -const kBeamPlaygroundGithubLink = - 'https://github.com/apache/beam/tree/master/playground'; -const kApacheBeamGithubLink = 'https://github.com/apache/beam'; -const kBeamWebsiteLink = 'https://beam.apache.org/'; -const kScioGithubLink = 'https://github.com/spotify/scio'; -const kAboutBeamLink = 'https://beam.apache.org/get-started/beam-overview'; +import 'package:flutter/cupertino.dart'; + +class PopoverState extends ChangeNotifier { + bool _isOpen; + + bool get isOpen => _isOpen; + + PopoverState(this._isOpen); + + setOpen(bool open) { + _isOpen = open; + notifyListeners(); + } +} diff --git a/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart b/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart index 2b91d91..672b2e5 100644 --- a/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart +++ b/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart @@ -224,6 +224,8 @@ class GrpcExampleClient implements ExampleClient { type: _exampleTypeFromString(example.type), path: example.cloudPath, pipelineOptions: example.pipelineOptions, + isMultiFile: example.multifile, + link: example.link, ); } } diff --git a/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart b/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart index 52ab98f..4f88f3d 100644 --- a/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart +++ b/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart @@ -23,6 +23,7 @@ import 'package:playground/modules/analytics/analytics_service.dart'; import 'package:playground/modules/editor/components/editor_textarea.dart'; import 'package:playground/modules/editor/components/run_button.dart'; import 'package:playground/modules/examples/components/description_popover/description_popover_button.dart'; +import 'package:playground/modules/examples/components/multifile_popover/multifile_popover_button.dart'; import 'package:playground/modules/examples/models/example_model.dart'; import 'package:playground/modules/notifications/components/notification.dart'; import 'package:playground/modules/sdk/models/sdk.dart'; @@ -53,7 +54,7 @@ class CodeTextAreaWrapper extends StatelessWidget { children: [ Positioned.fill( child: EditorTextArea( - enabled: true, + enabled: !(state.selectedExample?.isMultiFile ?? false), example: state.selectedExample, sdk: state.sdk, onSourceChange: state.setSource, @@ -66,13 +67,21 @@ class CodeTextAreaWrapper extends StatelessWidget { height: kButtonHeight, child: Row( children: [ - if (state.selectedExample != null) + if (state.selectedExample != null) ...[ + if (state.selectedExample?.isMultiFile ?? false) + MultifilePopoverButton( + example: state.selectedExample!, + followerAnchor: Alignment.topRight, + targetAnchor: Alignment.bottomRight, + ), DescriptionPopoverButton( example: state.selectedExample!, followerAnchor: Alignment.topRight, targetAnchor: Alignment.bottomRight, ), + ], RunButton( + disabled: state.selectedExample?.isMultiFile ?? false, isRunning: state.isCodeRunning, cancelRun: () { state.cancelRun().catchError(