diff --git a/.github/workflows/build_playground_frontend.yml b/.github/workflows/build_playground_frontend.yml index f71fc314b7a21..a27ce08d07ae4 100644 --- a/.github/workflows/build_playground_frontend.yml +++ b/.github/workflows/build_playground_frontend.yml @@ -37,7 +37,7 @@ jobs: GO_VERSION: 1.18.0 BEAM_VERSION: 2.40.0 TERRAFORM_VERSION: 1.0.9 - FLUTTER_VERSION: 3.0.1-stable + FLUTTER_VERSION: 3.3.2 STAND_SUFFIX: '' GOOGLE_DOMAIN: '-dot-apache-beam-testing.appspot.com' steps: @@ -49,8 +49,8 @@ jobs: java-version: '8' - name: install flutter run: | - wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_$FLUTTER_VERSION.tar.xz &&\ - tar -xf flutter_linux_$FLUTTER_VERSION.tar.xz &&\ + wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_$FLUTTER_VERSION-stable.tar.xz &&\ + tar -xf flutter_linux_$FLUTTER_VERSION-stable.tar.xz &&\ mv flutter /opt/ &&\ ln -s /opt/flutter/bin/flutter /usr/local/bin/flutter &&\ ln -s /opt/flutter/bin/dart /usr/local/bin/dart &&\ diff --git a/.gitignore b/.gitignore index 443ced0aaeecb..ec600f50f8303 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,7 @@ website/www/yarn-error.log **/.flutter-plugins **/.flutter-plugins-dependencies **/generated_plugin_registrant.dart +**/*.mocks.dart # Ignore Beam Playground Terraform **/.terraform diff --git a/learning/tour-of-beam/frontend/assets/png/profile-website.png b/learning/tour-of-beam/frontend/assets/png/profile-website.png new file mode 100644 index 0000000000000..e0b25a42aafb3 Binary files /dev/null and b/learning/tour-of-beam/frontend/assets/png/profile-website.png differ diff --git a/learning/tour-of-beam/frontend/assets/svg/github-logo.svg b/learning/tour-of-beam/frontend/assets/svg/github-logo.svg new file mode 100644 index 0000000000000..495172be937c2 --- /dev/null +++ b/learning/tour-of-beam/frontend/assets/svg/github-logo.svg @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/learning/tour-of-beam/frontend/assets/svg/google-logo.svg b/learning/tour-of-beam/frontend/assets/svg/google-logo.svg new file mode 100644 index 0000000000000..b704ef2cc7c46 --- /dev/null +++ b/learning/tour-of-beam/frontend/assets/svg/google-logo.svg @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/learning/tour-of-beam/frontend/assets/svg/profile-about.svg b/learning/tour-of-beam/frontend/assets/svg/profile-about.svg new file mode 100644 index 0000000000000..9096d879ce6e3 --- /dev/null +++ b/learning/tour-of-beam/frontend/assets/svg/profile-about.svg @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/learning/tour-of-beam/frontend/assets/svg/profile-delete.svg b/learning/tour-of-beam/frontend/assets/svg/profile-delete.svg new file mode 100644 index 0000000000000..2d801aba6e281 --- /dev/null +++ b/learning/tour-of-beam/frontend/assets/svg/profile-delete.svg @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/learning/tour-of-beam/frontend/assets/svg/profile-logout.svg b/learning/tour-of-beam/frontend/assets/svg/profile-logout.svg new file mode 100644 index 0000000000000..9f4b5de09be79 --- /dev/null +++ b/learning/tour-of-beam/frontend/assets/svg/profile-logout.svg @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/learning/tour-of-beam/frontend/assets/svg/unit-progress-0.svg b/learning/tour-of-beam/frontend/assets/svg/unit-progress-0.svg new file mode 100644 index 0000000000000..37d5945870d5f --- /dev/null +++ b/learning/tour-of-beam/frontend/assets/svg/unit-progress-0.svg @@ -0,0 +1,19 @@ + + + + + diff --git a/learning/tour-of-beam/frontend/assets/svg/unit-progress-100.svg b/learning/tour-of-beam/frontend/assets/svg/unit-progress-100.svg new file mode 100644 index 0000000000000..8eb0cf172d208 --- /dev/null +++ b/learning/tour-of-beam/frontend/assets/svg/unit-progress-100.svg @@ -0,0 +1,19 @@ + + + + + diff --git a/learning/tour-of-beam/frontend/assets/svg/welcome-progress-0.svg b/learning/tour-of-beam/frontend/assets/svg/welcome-progress-0.svg index d80426bf7899b..1b8d4d67d5ef6 100644 --- a/learning/tour-of-beam/frontend/assets/svg/welcome-progress-0.svg +++ b/learning/tour-of-beam/frontend/assets/svg/welcome-progress-0.svg @@ -15,5 +15,5 @@ See the License for the specific language governing permissions and limitations under the License. --> - + diff --git a/learning/tour-of-beam/frontend/assets/translations/en.yaml b/learning/tour-of-beam/frontend/assets/translations/en.yaml index b40cbfee09d61..8dff25f4f91e7 100644 --- a/learning/tour-of-beam/frontend/assets/translations/en.yaml +++ b/learning/tour-of-beam/frontend/assets/translations/en.yaml @@ -16,24 +16,33 @@ # under the License. ui: - copyright: '© The Apache Software Foundation' - darkMode: 'Dark Mode' - lightMode: 'Light Mode' - privacyPolicy: 'Privacy Policy' - reportIssue: 'Report Issue in GitHub' - signIn: 'Sign in' - continueGitHub: 'Continue with GitHub' - continueGoogle: 'Continue with Google' + about: About Tour of Beam + builtWith: Built with Apache Beam + continueGitHub: Continue with GitHub + continueGoogle: Continue with Google + copyright: © The Apache Software Foundation + deleteAccount: Delete my account + privacyPolicy: Privacy Policy + reportIssue: Report Issue in GitHub + signIn: Sign in + signOut: Sign out + toWebsite: To Apache Beam website + pages: welcome: - title: 'Welcome to the Tour of Beam!' - ifSaveProgress: 'Your journey is broken down into learning modules. If you would like to save your progress and track completed modules, please' - signIn: ' sign in.' + ifSaveProgress: Your journey is broken down into learning modules. If you would like to save your progress and track completed modules, please selectLanguage: 'Please select the default language (you may change the language at any time):' - startLearning: 'Start learning' + signIn: ' sign in.' + startTour: Start your tour + title: Welcome to the Tour of Beam! + tour: + completeUnit: Complete Unit + summaryTitle: Table of Contents + dialogs: signInIf: If you would like to save your progress and track completed modules + complexity: - basic: 'Basic level' - medium: 'Medium level' - advanced: 'Advanced level' + basic: Basic level + medium: Medium level + advanced: Advanced level diff --git a/learning/tour-of-beam/frontend/integration_test/app_test.dart b/learning/tour-of-beam/frontend/integration_test/app_test.dart index 232234bb05655..e74b4d35624f5 100644 --- a/learning/tour-of-beam/frontend/integration_test/app_test.dart +++ b/learning/tour-of-beam/frontend/integration_test/app_test.dart @@ -19,7 +19,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:tour_of_beam/components/toggle_theme_button.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:tour_of_beam/main.dart' as app; void main() { diff --git a/learning/tour-of-beam/frontend/lib/components/expansion_tile_wrapper.dart b/learning/tour-of-beam/frontend/lib/components/expansion_tile_wrapper.dart new file mode 100644 index 0000000000000..b09e6177ef2f6 --- /dev/null +++ b/learning/tour-of-beam/frontend/lib/components/expansion_tile_wrapper.dart @@ -0,0 +1,42 @@ +/* + * 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_components/playground_components.dart'; + +class ExpansionTileWrapper extends StatelessWidget { + final ExpansionTile expansionTile; + const ExpansionTileWrapper(this.expansionTile); + + @override + Widget build(BuildContext context) { + return Theme( + data: Theme.of(context).copyWith( + hoverColor: BeamColors.transparent, + splashColor: BeamColors.transparent, + highlightColor: BeamColors.transparent, + dividerColor: BeamColors.transparent, + unselectedWidgetColor: Colors.grey, + colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.grey), + visualDensity: const VisualDensity(vertical: -4), + listTileTheme: const ListTileThemeData(dense: true), + ), + child: expansionTile, + ); + } +} diff --git a/learning/tour-of-beam/frontend/lib/components/filler_text.dart b/learning/tour-of-beam/frontend/lib/components/filler_text.dart new file mode 100644 index 0000000000000..ca6099e6d9dec --- /dev/null +++ b/learning/tour-of-beam/frontend/lib/components/filler_text.dart @@ -0,0 +1,29 @@ +/* + * 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'; + +class FillerText extends StatelessWidget { + final int width; + const FillerText({required this.width}); + + @override + Widget build(BuildContext context) { + return Text(''.padRight(width, 'Just a filler text. ')); + } +} diff --git a/learning/tour-of-beam/frontend/lib/components/footer.dart b/learning/tour-of-beam/frontend/lib/components/footer.dart index b42fbca162caa..e801836bb8984 100644 --- a/learning/tour-of-beam/frontend/lib/components/footer.dart +++ b/learning/tour-of-beam/frontend/lib/components/footer.dart @@ -18,10 +18,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:url_launcher/url_launcher.dart'; -import '../config/theme/colors_provider.dart'; -import '../constants/links.dart'; import '../constants/sizes.dart'; class Footer extends StatelessWidget { @@ -31,12 +30,25 @@ class Footer extends StatelessWidget { Widget build(BuildContext context) { return _Body( child: Wrap( - spacing: TobSizes.size16, + alignment: WrapAlignment.spaceBetween, crossAxisAlignment: WrapCrossAlignment.center, children: [ - const _ReportIssueButton(), - const _PrivacyPolicyButton(), - const Text('ui.copyright').tr(), + Wrap( + spacing: BeamSizes.size16, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + const _ReportIssueButton(), + const _PrivacyPolicyButton(), + const Text('ui.copyright').tr(), + ], + ), + // TODO(nausharipov): get version, https://github.com/apache/beam/issues/23038 + Text( + '${'ui.builtWith'.tr()} (TODO: Version)', + style: const TextStyle( + color: BeamColors.grey3, + ), + ), ], ), ); @@ -49,19 +61,22 @@ class _Body extends StatelessWidget { @override Widget build(BuildContext context) { + final themeData = Theme.of(context); + final ext = themeData.extension()!; + return Container( + width: double.infinity, + height: TobSizes.footerHeight, padding: const EdgeInsets.symmetric( - vertical: TobSizes.size4, - horizontal: TobSizes.size16, + vertical: BeamSizes.size4, + horizontal: BeamSizes.size16, ), decoration: BoxDecoration( - color: ThemeColors.of(context).secondaryBackground, + color: ext.secondaryBackgroundColor, border: Border( - top: BorderSide(color: ThemeColors.of(context).divider), + top: BorderSide(color: themeData.dividerColor), ), ), - height: TobSizes.footerHeight, - width: double.infinity, child: child, ); } @@ -75,7 +90,7 @@ class _ReportIssueButton extends StatelessWidget { return TextButton( style: _linkButtonStyle, onPressed: () { - launchUrl(Uri.parse(TobLinks.reportIssue)); + launchUrl(Uri.parse(BeamLinks.reportIssue)); }, child: const Text('ui.reportIssue').tr(), ); @@ -90,7 +105,7 @@ class _PrivacyPolicyButton extends StatelessWidget { return TextButton( style: _linkButtonStyle, onPressed: () { - launchUrl(Uri.parse(TobLinks.privacyPolicy)); + launchUrl(Uri.parse(BeamLinks.privacyPolicy)); }, child: const Text('ui.privacyPolicy').tr(), ); diff --git a/learning/tour-of-beam/frontend/lib/components/sign_in/sign_in_button.dart b/learning/tour-of-beam/frontend/lib/components/login/login_button.dart similarity index 72% rename from learning/tour-of-beam/frontend/lib/components/sign_in/sign_in_button.dart rename to learning/tour-of-beam/frontend/lib/components/login/login_button.dart index b823ea71c1b12..36d96faffe5a8 100644 --- a/learning/tour-of-beam/frontend/lib/components/sign_in/sign_in_button.dart +++ b/learning/tour-of-beam/frontend/lib/components/login/login_button.dart @@ -18,28 +18,24 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:playground_components/dismissible_overlay.dart'; +import 'package:playground_components/playground_components.dart'; -import '../../constants/sizes.dart'; -import 'sign_in_overlay_content.dart'; +import 'login_content.dart'; -class SignInButton extends StatefulWidget { - const SignInButton(); +class LoginButton extends StatelessWidget { + const LoginButton(); - @override - State createState() => _SignInButtonState(); -} - -class _SignInButtonState extends State { @override Widget build(BuildContext context) { return TextButton( - onPressed: _openOverlay, + onPressed: () { + _openOverlay(context); + }, child: const Text('ui.signIn').tr(), ); } - void _openOverlay() { + void _openOverlay(BuildContext context) { OverlayEntry? overlay; overlay = OverlayEntry( builder: (context) => DismissibleOverlay( @@ -47,9 +43,9 @@ class _SignInButtonState extends State { overlay?.remove(); }, child: const Positioned( - right: TobSizes.size10, - top: TobSizes.appBarHeight, - child: SignInOverlayContent(), + right: BeamSizes.size10, + top: BeamSizes.appBarHeight, + child: LoginContent(), ), ), ); diff --git a/learning/tour-of-beam/frontend/lib/components/login/login_content.dart b/learning/tour-of-beam/frontend/lib/components/login/login_content.dart new file mode 100644 index 0000000000000..d4d0d8873cce9 --- /dev/null +++ b/learning/tour-of-beam/frontend/lib/components/login/login_content.dart @@ -0,0 +1,140 @@ +/* + * 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_svg/svg.dart'; +import 'package:playground_components/playground_components.dart'; + +import '../../constants/sizes.dart'; +import '../../generated/assets.gen.dart'; + +class LoginContent extends StatelessWidget { + const LoginContent(); + + @override + Widget build(BuildContext context) { + return _Body( + child: Column( + children: [ + Text( + 'ui.signIn', + style: Theme.of(context).textTheme.titleLarge, + ).tr(), + const SizedBox(height: BeamSizes.size10), + const Text( + 'dialogs.signInIf', + textAlign: TextAlign.center, + ).tr(), + const _Divider(), + const _BrandedLoginButtons(), + ], + ), + ); + } +} + +class _Body extends StatelessWidget { + final Widget child; + const _Body({required this.child}); + + @override + Widget build(BuildContext context) { + return Material( + elevation: BeamSizes.size10, + borderRadius: BorderRadius.circular(10), + child: Container( + width: TobSizes.authOverlayWidth, + padding: const EdgeInsets.all(BeamSizes.size24), + child: child, + ), + ); + } +} + +class _Divider extends StatelessWidget { + const _Divider(); + + @override + Widget build(BuildContext context) { + return Container( + color: BeamColors.grey3, + margin: const EdgeInsets.symmetric(vertical: 20), + width: BeamSizes.size32, + height: BeamSizes.size1, + ); + } +} + +class _BrandedLoginButtons extends StatelessWidget { + const _BrandedLoginButtons(); + + @override + Widget build(BuildContext context) { + final isLightTheme = Theme.of(context).brightness == Brightness.light; + final textStyle = + MaterialStatePropertyAll(Theme.of(context).textTheme.bodyMedium); + const padding = MaterialStatePropertyAll( + EdgeInsets.symmetric( + vertical: BeamSizes.size20, + horizontal: BeamSizes.size24, + ), + ); + const minimumSize = MaterialStatePropertyAll(Size(double.infinity, 0)); + + final darkButtonStyle = ButtonStyle( + backgroundColor: const MaterialStatePropertyAll(BeamColors.darkGrey), + minimumSize: minimumSize, + padding: padding, + textStyle: textStyle, + ); + final githubLightButtonStyle = ButtonStyle( + backgroundColor: const MaterialStatePropertyAll(BeamColors.darkBlue), + minimumSize: minimumSize, + padding: padding, + textStyle: textStyle, + ); + final googleLightButtonStyle = ButtonStyle( + backgroundColor: const MaterialStatePropertyAll(BeamColors.white), + elevation: const MaterialStatePropertyAll(BeamSizes.size4), + foregroundColor: const MaterialStatePropertyAll(BeamColors.black), + minimumSize: minimumSize, + overlayColor: MaterialStatePropertyAll(Theme.of(context).hoverColor), + padding: padding, + textStyle: textStyle, + ); + + return Column( + children: [ + ElevatedButton.icon( + onPressed: () {}, + style: isLightTheme ? githubLightButtonStyle : darkButtonStyle, + icon: SvgPicture.asset(Assets.svg.githubLogo), + label: const Text('ui.continueGitHub').tr(), + ), + const SizedBox(height: BeamSizes.size16), + ElevatedButton.icon( + onPressed: () {}, + style: isLightTheme ? googleLightButtonStyle : darkButtonStyle, + icon: SvgPicture.asset(Assets.svg.googleLogo), + label: const Text('ui.continueGoogle').tr(), + ), + ], + ); + } +} diff --git a/learning/tour-of-beam/frontend/lib/components/logo.dart b/learning/tour-of-beam/frontend/lib/components/logo.dart index e48b3985bf6b4..913678c76bd56 100644 --- a/learning/tour-of-beam/frontend/lib/components/logo.dart +++ b/learning/tour-of-beam/frontend/lib/components/logo.dart @@ -17,9 +17,7 @@ */ import 'package:flutter/material.dart'; - -import '../constants/assets.dart'; -import '../constants/sizes.dart'; +import 'package:playground_components/playground_components.dart'; class Logo extends StatelessWidget { const Logo(); @@ -28,12 +26,9 @@ class Logo extends StatelessWidget { Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, - children: [ - Image.asset( - TobAssets.beamLogo, - height: TobIconSizes.large, - ), - const _Text(), + children: const [ + BeamLogo(), + _Text(), ], ); } diff --git a/learning/tour-of-beam/frontend/lib/components/profile/avatar.dart b/learning/tour-of-beam/frontend/lib/components/profile/avatar.dart new file mode 100644 index 0000000000000..959c83876e3c0 --- /dev/null +++ b/learning/tour-of-beam/frontend/lib/components/profile/avatar.dart @@ -0,0 +1,57 @@ +/* + * 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_components/playground_components.dart'; + +import '../../generated/assets.gen.dart'; +import 'profile_content.dart'; + +class Avatar extends StatelessWidget { + const Avatar(); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + _openOverlay(context); + }, + child: CircleAvatar( + backgroundColor: BeamColors.white, + foregroundImage: AssetImage(Assets.png.laptopLight.path), + ), + ); + } + + void _openOverlay(BuildContext context) { + OverlayEntry? overlay; + overlay = OverlayEntry( + builder: (context) => DismissibleOverlay( + close: () { + overlay?.remove(); + }, + child: const Positioned( + right: BeamSizes.size10, + top: BeamSizes.appBarHeight, + child: ProfileContent(), + ), + ), + ); + Overlay.of(context)?.insert(overlay); + } +} diff --git a/learning/tour-of-beam/frontend/lib/components/profile/profile_content.dart b/learning/tour-of-beam/frontend/lib/components/profile/profile_content.dart new file mode 100644 index 0000000000000..65f039dad326b --- /dev/null +++ b/learning/tour-of-beam/frontend/lib/components/profile/profile_content.dart @@ -0,0 +1,160 @@ +/* + * 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_svg/svg.dart'; +import 'package:playground_components/playground_components.dart'; + +import '../../constants/sizes.dart'; +import '../../generated/assets.gen.dart'; + +class ProfileContent extends StatelessWidget { + const ProfileContent(); + + @override + Widget build(BuildContext context) { + return _Body( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + _Info(), + BeamDivider(), + _Buttons(), + ], + ), + ); + } +} + +class _Body extends StatelessWidget { + final Widget child; + + const _Body({required this.child}); + + @override + Widget build(BuildContext context) { + return Material( + elevation: BeamSizes.size10, + borderRadius: BorderRadius.circular(10), + child: SizedBox( + width: TobSizes.authOverlayWidth, + child: child, + ), + ); + } +} + +class _Info extends StatelessWidget { + const _Info(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(BeamSizes.size16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Name Surname', + style: Theme.of(context).textTheme.titleLarge, + ), + Text( + 'email@mail.com', + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ); + } +} + +class _Buttons extends StatelessWidget { + const _Buttons(); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + _IconLabel( + isSvg: false, + onTap: () {}, + iconPath: Assets.png.profileWebsite.path, + label: 'ui.toWebsite'.tr(), + ), + const BeamDivider(), + _IconLabel( + onTap: () {}, + iconPath: Assets.svg.profileAbout, + label: 'ui.about'.tr(), + ), + const BeamDivider(), + _IconLabel( + onTap: () {}, + iconPath: Assets.svg.profileLogout, + label: 'ui.signOut'.tr(), + ), + const BeamDivider(), + _IconLabel( + onTap: () {}, + iconPath: Assets.svg.profileDelete, + label: 'ui.deleteAccount'.tr(), + ), + ], + ); + } +} + +class _IconLabel extends StatelessWidget { + final String iconPath; + final String label; + final void Function()? onTap; + + // TODO(nausharipov): Auto-determine. + final bool isSvg; + + const _IconLabel({ + required this.iconPath, + required this.label, + required this.onTap, + this.isSvg = true, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(BeamSizes.size12), + child: Row( + children: [ + if (isSvg) + SvgPicture.asset(iconPath) + else + Image.asset( + iconPath, + height: 20, + ), + const SizedBox(width: BeamSizes.size10), + Text(label), + ], + ), + ), + ); + } +} diff --git a/learning/tour-of-beam/frontend/lib/components/scaffold.dart b/learning/tour-of-beam/frontend/lib/components/scaffold.dart new file mode 100644 index 0000000000000..f8352140436ef --- /dev/null +++ b/learning/tour-of-beam/frontend/lib/components/scaffold.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:flutter/material.dart'; +import 'package:playground_components/playground_components.dart'; + +import 'footer.dart'; +import 'login/login_button.dart'; +import 'logo.dart'; +import 'profile/avatar.dart'; +import 'sdk_dropdown.dart'; + +class TobScaffold extends StatelessWidget { + final Widget child; + + const TobScaffold({ + super.key, + required this.child, + }); + + // TODO(nausharipov): get state + static const _isAuthorized = true; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Logo(), + actions: const [ + _ActionVerticalPadding(child: SdkDropdown()), + SizedBox(width: BeamSizes.size12), + _ActionVerticalPadding(child: ToggleThemeButton()), + SizedBox(width: BeamSizes.size6), + _ActionVerticalPadding( + child: _isAuthorized ? Avatar() : LoginButton(), + ), + SizedBox(width: BeamSizes.size16), + ], + ), + body: Column( + children: [ + Expanded(child: child), + const Footer(), + ], + ), + ); + } +} + +class _ActionVerticalPadding extends StatelessWidget { + final Widget child; + + const _ActionVerticalPadding({required this.child}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: BeamSizes.size10), + child: child, + ); + } +} diff --git a/learning/tour-of-beam/frontend/lib/components/sdk_dropdown.dart b/learning/tour-of-beam/frontend/lib/components/sdk_dropdown.dart new file mode 100644 index 0000000000000..47f1a728b8eac --- /dev/null +++ b/learning/tour-of-beam/frontend/lib/components/sdk_dropdown.dart @@ -0,0 +1,64 @@ +/* + * 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_components/playground_components.dart'; + +class SdkDropdown extends StatelessWidget { + const SdkDropdown(); + + @override + Widget build(BuildContext context) { + return _DropdownWrapper( + child: DropdownButton( + value: 'Java', + onChanged: (sdk) { + // TODO(nausharipov): change SDK + }, + items: const ['Java', 'Python', 'Go'] + .map( + (sdk) => DropdownMenuItem( + value: sdk, + child: Text(sdk), + ), + ) + .toList(growable: false), + isDense: true, + alignment: Alignment.center, + focusColor: BeamColors.transparent, + borderRadius: BorderRadius.circular(BeamSizes.size6), + ), + ); + } +} + +class _DropdownWrapper extends StatelessWidget { + final Widget child; + const _DropdownWrapper({required this.child}); + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: Theme.of(context).hoverColor, + borderRadius: BorderRadius.circular(BeamSizes.size6), + ), + child: DropdownButtonHideUnderline(child: child), + ); + } +} diff --git a/learning/tour-of-beam/frontend/lib/components/sign_in/sign_in_overlay_content.dart b/learning/tour-of-beam/frontend/lib/components/sign_in/sign_in_overlay_content.dart deleted file mode 100644 index 80fb00805e25d..0000000000000 --- a/learning/tour-of-beam/frontend/lib/components/sign_in/sign_in_overlay_content.dart +++ /dev/null @@ -1,89 +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 '../../../constants/colors.dart'; -import '../../../constants/sizes.dart'; - -class SignInOverlayContent extends StatelessWidget { - const SignInOverlayContent(); - - @override - Widget build(BuildContext context) { - return _Body( - child: Column( - children: [ - Text( - 'ui.signIn', - style: Theme.of(context).textTheme.titleLarge, - ).tr(), - const SizedBox(height: TobSizes.size10), - const Text( - 'dialogs.signInIf', - textAlign: TextAlign.center, - ).tr(), - const _Divider(), - // TODO(nausharipov): check branded buttons in firebase_auth - ElevatedButton( - onPressed: () {}, - child: const Text('ui.continueGitHub').tr(), - ), - const SizedBox(height: TobSizes.size16), - ElevatedButton( - onPressed: () {}, - child: const Text('ui.continueGoogle').tr(), - ), - ], - ), - ); - } -} - -class _Body extends StatelessWidget { - final Widget child; - const _Body({required this.child}); - - @override - Widget build(BuildContext context) { - return Material( - elevation: TobSizes.size10, - borderRadius: BorderRadius.circular(10), - child: Container( - width: TobSizes.authOverlayWidth, - padding: const EdgeInsets.all(TobSizes.size24), - child: child, - ), - ); - } -} - -class _Divider extends StatelessWidget { - const _Divider(); - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 20), - width: TobSizes.size32, - height: TobSizes.size1, - color: TobColors.grey3, - ); - } -} diff --git a/learning/tour-of-beam/frontend/lib/config/theme/colors_provider.dart b/learning/tour-of-beam/frontend/lib/config/theme/colors_provider.dart deleted file mode 100644 index 3aab5a1703ca2..0000000000000 --- a/learning/tour-of-beam/frontend/lib/config/theme/colors_provider.dart +++ /dev/null @@ -1,86 +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:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import '../../constants/colors.dart'; - -class ThemeColorsProvider extends StatelessWidget { - final ThemeColors data; - final Widget child; - - const ThemeColorsProvider({ - super.key, - required this.data, - required this.child, - }); - - @override - Widget build(BuildContext context) { - return Provider.value( - value: data, - child: child, - ); - } -} - -class ThemeColors { - final Color? _background; - final bool isDark; - - ThemeColors({ - required this.isDark, - Color? background, - }) : _background = background; - - static ThemeColors of(BuildContext context, {bool listen = true}) { - return Provider.of(context, listen: listen); - } - - const ThemeColors.fromBrightness({ - required this.isDark, - }) : _background = null; - - Color get divider => - isDark ? TobDarkThemeColors.grey : TobLightThemeColors.grey; - - Color get primary => - isDark ? TobDarkThemeColors.primary : TobLightThemeColors.primary; - - Color get primaryBackgroundTextColor => TobColors.white; - - Color get lightGreyBackgroundTextColor => TobColors.black; - - Color get secondaryBackground => isDark - ? TobDarkThemeColors.secondaryBackground - : TobLightThemeColors.secondaryBackground; - - Color get background => - _background ?? - (isDark - ? TobDarkThemeColors.primaryBackground - : TobLightThemeColors.primaryBackground); - - Color get textColor => - isDark ? TobDarkThemeColors.text : TobLightThemeColors.text; - - Color get progressBackgroundColor => - // TODO(nausharipov): reuse these colors after discussion with Anna - isDark ? const Color(0xffFFFFFF) : const Color(0xff242639); -} diff --git a/learning/tour-of-beam/frontend/lib/config/theme/theme.dart b/learning/tour-of-beam/frontend/lib/config/theme/theme.dart deleted file mode 100644 index d8cf2c1c20868..0000000000000 --- a/learning/tour-of-beam/frontend/lib/config/theme/theme.dart +++ /dev/null @@ -1,160 +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:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; - -import '../../constants/colors.dart'; -import '../../constants/sizes.dart'; - -final kLightTheme = ThemeData( - brightness: Brightness.light, - primaryColor: TobLightThemeColors.primary, - canvasColor: TobLightThemeColors.primaryBackground, - scaffoldBackgroundColor: TobLightThemeColors.secondaryBackground, - backgroundColor: TobLightThemeColors.primaryBackground, - textTheme: _getTextTheme(TobLightThemeColors.text), - textButtonTheme: _getTextButtonTheme(TobLightThemeColors.text), - outlinedButtonTheme: _getOutlineButtonTheme( - TobLightThemeColors.text, - TobLightThemeColors.primary, - ), - elevatedButtonTheme: _getElevatedButtonTheme(TobLightThemeColors.primary), - appBarTheme: _getAppBarTheme(TobLightThemeColors.secondaryBackground), -); - -final kDarkTheme = ThemeData( - brightness: Brightness.dark, - primaryColor: TobDarkThemeColors.primary, - canvasColor: TobDarkThemeColors.primaryBackground, - scaffoldBackgroundColor: TobDarkThemeColors.secondaryBackground, - backgroundColor: TobDarkThemeColors.primaryBackground, - textTheme: _getTextTheme(TobDarkThemeColors.text), - textButtonTheme: _getTextButtonTheme(TobDarkThemeColors.text), - outlinedButtonTheme: _getOutlineButtonTheme( - TobDarkThemeColors.text, - TobDarkThemeColors.primary, - ), - elevatedButtonTheme: _getElevatedButtonTheme(TobDarkThemeColors.primary), - appBarTheme: _getAppBarTheme(TobDarkThemeColors.secondaryBackground), -); - -TextTheme _getTextTheme(Color textColor) { - return GoogleFonts.sourceSansProTextTheme( - const TextTheme( - displayLarge: _emptyTextStyle, - displayMedium: TextStyle( - fontSize: 48, - fontWeight: FontWeight.w900, - ), - displaySmall: TextStyle( - fontFamily: 'Roboto_regular', - fontSize: 18, - fontWeight: FontWeight.w400, - ), - headlineLarge: _emptyTextStyle, - headlineMedium: _emptyTextStyle, - headlineSmall: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - ), - titleLarge: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w600, - ), - titleMedium: _emptyTextStyle, - titleSmall: _emptyTextStyle, - labelLarge: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), - labelMedium: _emptyTextStyle, - labelSmall: _emptyTextStyle, - bodyLarge: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w400, - ), - bodyMedium: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w400, - ), - bodySmall: _emptyTextStyle, - ).apply( - bodyColor: textColor, - displayColor: textColor, - ), - ); -} - -TextButtonThemeData _getTextButtonTheme(Color textColor) { - return TextButtonThemeData( - style: TextButton.styleFrom( - primary: textColor, - shape: _getButtonBorder(TobBorderRadius.large), - ), - ); -} - -OutlinedButtonThemeData _getOutlineButtonTheme( - Color textColor, - Color outlineColor, -) { - return OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - primary: textColor, - side: BorderSide(color: outlineColor, width: 3), - padding: _buttonPadding, - shape: _getButtonBorder(TobBorderRadius.small), - ), - ); -} - -ElevatedButtonThemeData _getElevatedButtonTheme(Color color) { - return ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - onPrimary: TobColors.white, - primary: color, - padding: _buttonPadding, - elevation: TobSizes.size0, - ), - ); -} - -AppBarTheme _getAppBarTheme(Color backgroundColor) { - return AppBarTheme( - color: backgroundColor, - elevation: TobSizes.size1, - centerTitle: false, - toolbarHeight: TobSizes.appBarHeight, - ); -} - -const EdgeInsets _buttonPadding = EdgeInsets.symmetric( - vertical: TobSizes.size20, - horizontal: TobSizes.size40, -); - -RoundedRectangleBorder _getButtonBorder(double radius) { - return RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(radius), - ), - ); -} - -const TextStyle _emptyTextStyle = TextStyle(); diff --git a/learning/tour-of-beam/frontend/lib/constants/assets.dart b/learning/tour-of-beam/frontend/lib/constants/assets.dart deleted file mode 100644 index 1af152cc402b1..0000000000000 --- a/learning/tour-of-beam/frontend/lib/constants/assets.dart +++ /dev/null @@ -1,34 +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. - */ - -String _getPngPath(String fileName) { - return 'png/$fileName.png'; -} - -String _getSvgPath(String fileName) { - return 'svg/$fileName.svg'; -} - -class TobAssets { - static final beamLogo = _getPngPath('beam-logo'); - static final themeMode = _getSvgPath('theme-mode'); - static final welcomeLaptop = _getPngPath('welcome-laptop'); - static final laptopDark = _getPngPath('laptop-dark'); - static final laptopLight = _getPngPath('laptop-light'); - static final welcomeProgress0 = _getSvgPath('welcome-progress-0'); -} diff --git a/learning/tour-of-beam/frontend/lib/constants/sizes.dart b/learning/tour-of-beam/frontend/lib/constants/sizes.dart index 187a0f60f952d..bb9a665c8a30f 100644 --- a/learning/tour-of-beam/frontend/lib/constants/sizes.dart +++ b/learning/tour-of-beam/frontend/lib/constants/sizes.dart @@ -17,36 +17,8 @@ */ class TobSizes { - static const double size0 = 0; - static const double size1 = 1; - static const double size4 = 4; - static const double size6 = 6; - static const double size8 = 8; - static const double size10 = 10; - static const double size12 = 12; - static const double size16 = 16; - static const double size20 = 20; - static const double size24 = 24; - static const double size32 = 32; - static const double size36 = 36; - static const double size40 = 40; - static const double appBarHeight = 55; - static const double footerHeight = 30; - static const double authOverlayWidth = 300; -} - -class TobBorderRadius { - static const double small = 4; - static const double medium = 6; - static const double large = 8; - static const double xl = 28; -} - -class TobIconSizes { - static const double xs = 8; - static const double small = 16; - static const double medium = 24; - static const double large = 32; + static const double footerHeight = 35; + static const double authOverlayWidth = 260; } class ScreenSizes { diff --git a/learning/tour-of-beam/frontend/lib/main.dart b/learning/tour-of-beam/frontend/lib/main.dart index 4c81d88a4f510..c7eb698c3782c 100644 --- a/learning/tour-of-beam/frontend/lib/main.dart +++ b/learning/tour-of-beam/frontend/lib/main.dart @@ -17,15 +17,15 @@ */ import 'package:easy_localization/easy_localization.dart'; +import 'package:easy_localization_ext/easy_localization_ext.dart'; import 'package:easy_localization_loader/easy_localization_loader.dart'; import 'package:flutter/material.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; import 'package:url_strategy/url_strategy.dart'; -import 'config/theme/switch_notifier.dart'; -import 'config/theme/theme.dart'; import 'locator.dart'; -import 'pages/welcome/screen.dart'; +import 'pages/tour/screen.dart'; void main() async { setPathUrlStrategy(); @@ -39,7 +39,10 @@ void main() async { startLocale: englishLocale, fallbackLocale: englishLocale, path: 'assets/translations', - assetLoader: YamlAssetLoader(), + assetLoader: MultiAssetLoader([ + PlaygroundComponents.translationLoader, + YamlAssetLoader(), + ]), child: const TourOfBeamApp(), ), ); @@ -61,7 +64,7 @@ class TourOfBeamApp extends StatelessWidget { localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, - home: const WelcomeScreen(), + home: const TourScreen(), ); }, ), diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/playground_demo.dart b/learning/tour-of-beam/frontend/lib/pages/tour/playground_demo.dart new file mode 100644 index 0000000000000..384d46b0fc29b --- /dev/null +++ b/learning/tour-of-beam/frontend/lib/pages/tour/playground_demo.dart @@ -0,0 +1,126 @@ +/* + * 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/widgets.dart'; +import 'package:playground_components/playground_components.dart'; + +// This is for demo only. Need a thought-through import in production. + +const String kApiClientURL = + 'https://backend-router-beta-dot-apache-beam-testing.appspot.com'; +const String kApiJavaClientURL = + 'https://backend-java-beta-dot-apache-beam-testing.appspot.com'; +const String kApiGoClientURL = + 'https://backend-go-beta-dot-apache-beam-testing.appspot.com'; +const String kApiPythonClientURL = + 'https://backend-python-beta-dot-apache-beam-testing.appspot.com'; +const String kApiScioClientURL = + 'https://backend-scio-beta-dot-apache-beam-testing.appspot.com'; + +class PlaygroundDemoWidget extends StatefulWidget { + const PlaygroundDemoWidget({super.key}); + + @override + State createState() => _PlaygroundDemoWidgetState(); +} + +class _PlaygroundDemoWidgetState extends State { + late final PlaygroundController playgroundController; + + @override + void initState() { + super.initState(); + + final exampleRepository = ExampleRepository( + client: GrpcExampleClient(url: kApiClientURL), + ); + + final codeRepository = CodeRepository( + client: GrpcCodeClient( + url: kApiClientURL, + runnerUrlsById: { + Sdk.java.id: kApiJavaClientURL, + Sdk.go.id: kApiGoClientURL, + Sdk.python.id: kApiPythonClientURL, + Sdk.scio.id: kApiScioClientURL, + }, + ), + ); + + final exampleCache = ExampleCache( + exampleRepository: exampleRepository, + hasCatalog: true, + ); + + playgroundController = PlaygroundController( + codeRepository: codeRepository, + exampleCache: exampleCache, + examplesLoader: ExamplesLoader(), + ); + + playgroundController.examplesLoader.load( + const ExamplesLoadingDescriptor( + descriptors: [ + CatalogDefaultExampleLoadingDescriptor(sdk: Sdk.java), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: playgroundController, + builder: _buildOnChange, + ); + } + + Widget _buildOnChange(BuildContext context, Widget? child) { + final snippetController = playgroundController.snippetEditingController; + if (snippetController == null) { + return const LoadingIndicator(); + } + + return Stack( + children: [ + SplitView( + direction: Axis.vertical, + first: SnippetEditor( + controller: snippetController, + isEditable: true, + goToContextLine: false, + ), + second: OutputWidget( + playgroundController: playgroundController, + graphDirection: Axis.horizontal, + ), + ), + Positioned( + top: 30, + right: 30, + child: Row( + children: [ + RunOrCancelButton(playgroundController: playgroundController), + ], + ), + ), + ], + ); + } +} diff --git a/learning/tour-of-beam/frontend/lib/pages/tour/screen.dart b/learning/tour-of-beam/frontend/lib/pages/tour/screen.dart new file mode 100644 index 0000000000000..7d708ff250895 --- /dev/null +++ b/learning/tour-of-beam/frontend/lib/pages/tour/screen.dart @@ -0,0 +1,359 @@ +/* + * 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_svg/svg.dart'; +import 'package:playground_components/playground_components.dart'; + +import '../../components/expansion_tile_wrapper.dart'; +import '../../components/filler_text.dart'; +import '../../components/scaffold.dart'; +import '../../constants/sizes.dart'; +import '../../generated/assets.gen.dart'; +import 'playground_demo.dart'; + +class TourScreen extends StatelessWidget { + const TourScreen(); + + @override + Widget build(BuildContext context) { + return TobScaffold( + child: MediaQuery.of(context).size.width > ScreenBreakpoints.twoColumns + ? const _WideTour() + : const _NarrowTour(), + ); + } +} + +class _WideTour extends StatelessWidget { + const _WideTour(); + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + _ContentTree(), + Expanded( + child: SplitView( + direction: Axis.horizontal, + first: _Content(), + second: PlaygroundDemoWidget(), + ), + ), + ], + ); + } +} + +class _NarrowTour extends StatelessWidget { + const _NarrowTour(); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + _ContentTree(), + Expanded(child: _Content()), + ], + ), + DecoratedBox( + decoration: BoxDecoration( + border: Border( + top: BorderSide(color: Theme.of(context).dividerColor), + ), + ), + child: const _NarrowScreenPlayground(), + ), + ], + ), + ); + } +} + +class _ContentTree extends StatelessWidget { + const _ContentTree(); + + @override + Widget build(BuildContext context) { + return Container( + width: 250, + padding: const EdgeInsets.symmetric(horizontal: BeamSizes.size12), + child: SingleChildScrollView( + child: Column( + children: [ + const _ContentTreeTitle(), + ...[ + 'Core Transforms', + 'Common Transforms', + ].map((e) => _Module(module: e)).toList(growable: false), + const SizedBox(height: BeamSizes.size12), + ], + ), + ), + ); + } +} + +class _Module extends StatelessWidget { + final String module; + const _Module({required this.module}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + _ModuleTitle(title: module), + ...['Map', 'Combine'] + .map((group) => _Group(group: group)) + .toList(growable: false), + const BeamDivider( + margin: EdgeInsets.symmetric(vertical: BeamSizes.size10), + ), + ], + ); + } +} + +class _ContentTreeTitle extends StatelessWidget { + const _ContentTreeTitle(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: BeamSizes.size12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'pages.tour.summaryTitle', + style: Theme.of(context).textTheme.headlineLarge, + ).tr(), + ], + ), + ); + } +} + +class _ModuleTitle extends StatelessWidget { + final String title; + const _ModuleTitle({required this.title}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: BeamSizes.size6), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: Theme.of(context).textTheme.headlineMedium, + ), + const Padding( + padding: EdgeInsets.only(right: BeamSizes.size4), + child: ComplexityWidget(complexity: Complexity.basic), + ), + ], + ), + ); + } +} + +class _Group extends StatelessWidget { + final String group; + const _Group({required this.group}); + + @override + Widget build(BuildContext context) { + return ExpansionTileWrapper( + ExpansionTile( + tilePadding: EdgeInsets.zero, + title: _GroupTitle(title: group), + childrenPadding: const EdgeInsets.only( + left: BeamSizes.size24, + top: BeamSizes.size10, + ), + children: const [_Units()], + ), + ); + } +} + +class _Units extends StatelessWidget { + const _Units(); + + @override + Widget build(BuildContext context) { + return Column( + children: ['ParDo one-to-one', 'ParDo one-to-many'] + .map((e) => _Unit(title: e)) + .toList(growable: false), + ); + } +} + +class _Unit extends StatelessWidget { + final String title; + const _Unit({required this.title}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: BeamSizes.size18), + child: Row( + children: [ + _ProgressIndicator( + assetPath: Assets.svg.unitProgress100, + ), + Text(title), + ], + ), + ); + } +} + +class _GroupTitle extends StatelessWidget { + final String title; + const _GroupTitle({required this.title}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + _ProgressIndicator( + assetPath: Assets.svg.unitProgress100, + ), + Text( + title, + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ); + } +} + +class _ProgressIndicator extends StatelessWidget { + final String assetPath; + const _ProgressIndicator({required this.assetPath}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only( + left: BeamSizes.size4, + right: BeamSizes.size8, + ), + child: SvgPicture.asset(assetPath), + ); + } +} + +class _Content extends StatelessWidget { + const _Content(); + + @override + Widget build(BuildContext context) { + final themeData = Theme.of(context); + + return Container( + height: MediaQuery.of(context).size.height - + BeamSizes.appBarHeight - + TobSizes.footerHeight, + decoration: BoxDecoration( + color: themeData.backgroundColor, + border: Border( + left: BorderSide(color: themeData.dividerColor), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: SingleChildScrollView( + controller: ScrollController(), + child: const FillerText(width: 1000), + ), + ), + const _ContentFooter(), + ], + ), + ); + } +} + +class _ContentFooter extends StatelessWidget { + const _ContentFooter(); + + @override + Widget build(BuildContext context) { + final themeData = Theme.of(context); + + return Container( + decoration: BoxDecoration( + border: Border( + top: BorderSide(color: themeData.dividerColor), + ), + color: + themeData.extension()?.secondaryBackgroundColor, + ), + width: double.infinity, + padding: const EdgeInsets.all(BeamSizes.size20), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + child: OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: themeData.primaryColor, + side: BorderSide(color: themeData.primaryColor), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(BeamSizes.size4), + ), + ), + ), + child: const Text( + 'pages.tour.completeUnit', + overflow: TextOverflow.ellipsis, + ).tr(), + onPressed: () { + // TODO(nausharipov): complete unit + }, + ), + ), + ], + ), + ); + } +} + +class _NarrowScreenPlayground extends StatelessWidget { + const _NarrowScreenPlayground(); + + @override + Widget build(BuildContext context) { + // TODO(alexeyinkin): Even this way the narrow layout breaks, https://github.com/apache/beam/issues/23244 + return const Center(child: Text('TODO: Playground for narrow screen')); + } +} diff --git a/learning/tour-of-beam/frontend/lib/pages/welcome/screen.dart b/learning/tour-of-beam/frontend/lib/pages/welcome/screen.dart index 2898e15846538..ae799b7e77c4c 100644 --- a/learning/tour-of-beam/frontend/lib/pages/welcome/screen.dart +++ b/learning/tour-of-beam/frontend/lib/pages/welcome/screen.dart @@ -20,20 +20,19 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:playground_components/playground_components.dart'; -import '../../components/complexity.dart'; -import '../../components/page_container.dart'; -import '../../config/theme/colors_provider.dart'; -import '../../constants/assets.dart'; -import '../../constants/colors.dart'; +import '../../components/filler_text.dart'; +import '../../components/scaffold.dart'; import '../../constants/sizes.dart'; +import '../../generated/assets.gen.dart'; class WelcomeScreen extends StatelessWidget { const WelcomeScreen(); @override Widget build(BuildContext context) { - return PageContainer( + return TobScaffold( child: SingleChildScrollView( child: MediaQuery.of(context).size.width > ScreenBreakpoints.twoColumns ? const _WideWelcome() @@ -48,16 +47,18 @@ class _WideWelcome extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: const [ - Expanded( - child: _SdkSelection(), - ), - Expanded( - child: _TourSummary(), - ), - ], + return IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Expanded( + child: _SdkSelection(), + ), + Expanded( + child: _TourSummary(), + ), + ], + ), ); } } @@ -79,34 +80,35 @@ class _NarrowWelcome extends StatelessWidget { class _SdkSelection extends StatelessWidget { const _SdkSelection(); + static const double _minimalHeight = 900; + @override Widget build(BuildContext context) { return Container( constraints: BoxConstraints( minHeight: MediaQuery.of(context).size.height - - TobSizes.appBarHeight - + BeamSizes.appBarHeight - TobSizes.footerHeight, ), - color: ThemeColors.of(context).background, + color: Theme.of(context).backgroundColor, child: Stack( children: [ Positioned( bottom: 0, left: 0, right: 0, - // TODO(nausharipov): use flutter_gen after merging child: Theme.of(context).brightness == Brightness.dark - ? Image.asset(TobAssets.laptopDark) - : Image.asset(TobAssets.laptopLight), + ? Image.asset(Assets.png.laptopDark.path) + : Image.asset(Assets.png.laptopLight.path), ), - const SizedBox(height: 900), + const SizedBox(height: _minimalHeight), Padding( padding: const EdgeInsets.fromLTRB(50, 60, 50, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ _IntroText(), - SizedBox(height: TobSizes.size32), + SizedBox(height: BeamSizes.size32), _Buttons(), ], ), @@ -124,7 +126,7 @@ class _TourSummary extends StatelessWidget { Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric( - vertical: TobSizes.size20, + vertical: BeamSizes.size20, horizontal: 27, ), child: Column( @@ -135,7 +137,7 @@ class _TourSummary extends StatelessWidget { isLast: module == _modules.last, ), ) - .toList(), + .toList(growable: false), ), ); } @@ -152,6 +154,8 @@ class _TourSummary extends StatelessWidget { class _IntroText extends StatelessWidget { const _IntroText(); + static const double _dividerMaxWidth = 150; + @override Widget build(BuildContext context) { return Column( @@ -163,9 +167,9 @@ class _IntroText extends StatelessWidget { ).tr(), Container( margin: const EdgeInsets.symmetric(vertical: 32), - height: 2, - color: TobColors.grey2, - constraints: const BoxConstraints(maxWidth: 150), + height: BeamSizes.size2, + color: BeamColors.grey2, + constraints: const BoxConstraints(maxWidth: _dividerMaxWidth), ), RichText( text: TextSpan( @@ -179,7 +183,7 @@ class _IntroText extends StatelessWidget { style: Theme.of(context) .textTheme .bodyLarge! - .copyWith(color: ThemeColors.of(context).primary), + .copyWith(color: Theme.of(context).primaryColor), recognizer: TapGestureRecognizer() ..onTap = () { // TODO(nausharipov): sign in @@ -198,7 +202,7 @@ class _Buttons extends StatelessWidget { const _Buttons(); void _onSdkChanged(String value) { - // TODO(nausharipov): select the language + // TODO(nausharipov): change sdk } @override @@ -214,13 +218,13 @@ class _Buttons extends StatelessWidget { onChanged: _onSdkChanged, ), ) - .toList(), + .toList(growable: false), ), ElevatedButton( onPressed: () { // TODO(nausharipov): redirect }, - child: const Text('pages.welcome.startLearning').tr(), + child: const Text('pages.welcome.startTour').tr(), ), ], ); @@ -246,10 +250,10 @@ class _SdkButton extends StatelessWidget { padding: const EdgeInsets.only(right: 15, bottom: 10), child: OutlinedButton( style: OutlinedButton.styleFrom( - backgroundColor: ThemeColors.of(context).background, + backgroundColor: Theme.of(context).backgroundColor, side: groupValue == value ? null - : const BorderSide(color: TobColors.grey1), + : const BorderSide(color: BeamColors.grey1), ), onPressed: () { onChanged(value); @@ -293,13 +297,13 @@ class _ModuleHeader extends StatelessWidget { child: Row( children: [ Padding( - padding: const EdgeInsets.all(TobSizes.size4), + padding: const EdgeInsets.all(BeamSizes.size4), child: SvgPicture.asset( - TobAssets.welcomeProgress0, - color: ThemeColors.of(context).progressBackgroundColor, + Assets.svg.welcomeProgress0, + color: BeamColors.grey4, ), ), - const SizedBox(width: TobSizes.size16), + const SizedBox(width: BeamSizes.size16), Expanded( child: Text( title, @@ -315,7 +319,7 @@ class _ModuleHeader extends StatelessWidget { 'complexity.medium', style: Theme.of(context).textTheme.headlineSmall, ).tr(), - const SizedBox(width: TobSizes.size6), + const SizedBox(width: BeamSizes.size6), const ComplexityWidget(complexity: Complexity.medium), ], ), @@ -332,24 +336,24 @@ class _ModuleBody extends StatelessWidget { @override Widget build(BuildContext context) { + final themeData = Theme.of(context); + return Container( margin: _moduleLeftMargin, decoration: BoxDecoration( border: Border( left: BorderSide( - color: ThemeColors.of(context).divider, + color: themeData.dividerColor, ), ), ), padding: _modulePadding, child: Column( children: [ - const Text( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam velit purus, tincidunt id velit vitae, mattis dictum velit. Nunc sit amet nunc at turpis eleifend commodo ac ut libero. Aenean rutrum rutrum nulla ut efficitur. Vestibulum pulvinar eros dictum lectus volutpat dignissim vitae quis nisi. Maecenas sem erat, elementum in euismod ut, interdum ac massa.', - ), - const SizedBox(height: TobSizes.size16), + const FillerText(width: 20), + const SizedBox(height: BeamSizes.size16), Divider( - color: ThemeColors.of(context).divider, + color: themeData.dividerColor, ), ], ), @@ -365,9 +369,7 @@ class _LastModuleBody extends StatelessWidget { return Container( margin: _moduleLeftMargin, padding: _modulePadding, - child: const Text( - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam velit purus, tincidunt id velit vitae, mattis dictum velit. Nunc sit amet nunc at turpis eleifend commodo ac ut libero. Aenean rutrum rutrum nulla ut efficitur. Vestibulum pulvinar eros dictum lectus volutpat dignissim vitae quis nisi. Maecenas sem erat, elementum in euismod ut, interdum ac massa.', - ), + child: const FillerText(width: 20), ); } } diff --git a/learning/tour-of-beam/frontend/pubspec.lock b/learning/tour-of-beam/frontend/pubspec.lock index b0a898316f4b8..c59bb0affd660 100644 --- a/learning/tour-of-beam/frontend/pubspec.lock +++ b/learning/tour-of-beam/frontend/pubspec.lock @@ -1,13 +1,34 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "46.0.0" + aligned_dialog: + dependency: transitive + description: + name: aligned_dialog + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.6" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "4.6.0" archive: dependency: transitive description: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.1.11" + version: "3.3.0" args: dependency: transitive description: @@ -21,7 +42,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -29,27 +50,99 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" + build_config: + dependency: transitive + description: + name: build_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + build_runner: + dependency: "direct dev" + description: + name: build_runner + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + url: "https://pub.dartlang.org" + source: hosted + version: "7.2.3" + built_collection: + dependency: transitive + description: + name: built_collection + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + url: "https://pub.dartlang.org" + source: hosted + version: "8.4.1" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: + version: "1.2.1" + checked_yaml: dependency: transitive description: - name: charcode + name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.3.1" + version: "2.0.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.0" + code_text_field: + dependency: "direct main" + description: + path: "." + ref: "9e2c9fe52a69481f038f4b6609e8a0a776429437" + resolved-ref: "9e2c9fe52a69481f038f4b6609e8a0a776429437" + url: "https://github.com/BertrandBev/code_field.git" + source: git + version: "1.0.3" collection: dependency: transitive description: @@ -57,13 +150,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + color: + dependency: transitive + description: + name: color + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" csv: dependency: transitive description: @@ -71,6 +178,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "5.0.1" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.3" + dartx: + dependency: transitive + description: + name: dartx + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" easy_localization: dependency: "direct main" description: @@ -78,6 +199,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + easy_localization_ext: + dependency: "direct main" + description: + name: easy_localization_ext + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1" easy_localization_loader: dependency: "direct main" description: @@ -92,13 +220,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.2" + equatable: + dependency: transitive + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: transitive description: @@ -113,6 +248,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.2" + fixnum: + dependency: transitive + description: + name: fixnum + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" flutter: dependency: "direct main" description: flutter @@ -123,6 +265,27 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_gen_core: + dependency: transitive + description: + name: flutter_gen_core + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.0" + flutter_gen_runner: + dependency: "direct dev" + description: + name: flutter_gen_runner + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.0" + flutter_highlight: + dependency: transitive + description: + name: flutter_highlight + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" flutter_localizations: dependency: transitive description: flutter @@ -145,6 +308,13 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -157,6 +327,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "7.2.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" google_fonts: dependency: "direct main" description: @@ -164,6 +341,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + googleapis_auth: + dependency: transitive + description: + name: googleapis_auth + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" + graphs: + dependency: transitive + description: + name: graphs + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + grpc: + dependency: transitive + description: + name: grpc + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + highlight: + dependency: transitive + description: + name: highlight + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" http: dependency: transitive description: @@ -171,6 +376,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.13.4" + http2: + dependency: transitive + description: + name: http2 + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" http_parser: dependency: transitive description: @@ -190,6 +409,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.17.0" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" js: dependency: transitive description: @@ -197,27 +423,55 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.4" + json_annotation: + dependency: transitive + description: + name: json_annotation + url: "https://pub.dartlang.org" + source: hosted + version: "4.6.0" + linked_scroll_controller: + dependency: transitive + description: + name: linked_scroll_controller + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" nested: dependency: transitive description: @@ -225,13 +479,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" path_drawing: dependency: transitive description: @@ -323,6 +584,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.1" process: dependency: transitive description: @@ -330,6 +598,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.2.4" + protobuf: + dependency: transitive + description: + name: protobuf + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" provider: dependency: "direct main" description: @@ -337,6 +612,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.0.3" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" shared_preferences: dependency: "direct main" description: @@ -393,6 +682,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" sky_engine: dependency: transitive description: flutter @@ -404,7 +707,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -419,34 +722,55 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + stream_transform: + dependency: transitive + description: + name: stream_transform + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" sync_http: dependency: transitive description: name: sync_http url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.3.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" + time: + dependency: transitive + description: + name: time + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + timing: + dependency: transitive + description: + name: timing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" total_lints: dependency: "direct dev" description: @@ -460,7 +784,7 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" url_launcher: dependency: "direct main" description: @@ -537,7 +861,21 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "8.2.2" + version: "9.0.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" webdriver: dependency: transitive description: @@ -574,5 +912,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.17.6 <3.0.0" - flutter: ">=3.0.0" + dart: ">=2.18.1 <3.0.0" + flutter: ">=3.3.2" diff --git a/learning/tour-of-beam/frontend/pubspec.yaml b/learning/tour-of-beam/frontend/pubspec.yaml index 220bfd5e5bcef..fb9ee46307d67 100644 --- a/learning/tour-of-beam/frontend/pubspec.yaml +++ b/learning/tour-of-beam/frontend/pubspec.yaml @@ -18,29 +18,35 @@ name: tour_of_beam description: Tour of Beam -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: 'none' version: 0.1.0 environment: - sdk: ">=2.17.6 <3.0.0" - flutter: ">=3.0.0 <4.0.0" + sdk: ">=2.18.1 <3.0.0" + flutter: ">=3.3.2" dependencies: + code_text_field: + git: + url: https://github.com/BertrandBev/code_field.git + ref: 9e2c9fe52a69481f038f4b6609e8a0a776429437 easy_localization: ^3.0.1 + easy_localization_ext: ^0.1.0 easy_localization_loader: ^1.0.0 flutter: { sdk: flutter } flutter_svg: ^1.0.3 get_it: ^7.2.0 google_fonts: ^3.0.1 - playground_components: - path: ../../../playground/frontend/playground_components + playground_components: { path: ../../../playground/frontend/playground_components } provider: ^6.0.3 shared_preferences: ^2.0.15 url_launcher: ^6.1.5 url_strategy: ^0.2.0 dev_dependencies: + build_runner: ^2.2.0 + flutter_gen_runner: ^4.3.0 flutter_test: { sdk: flutter } integration_test: { sdk: flutter } total_lints: ^2.17.0 @@ -51,3 +57,6 @@ flutter: - assets/translations/en.yaml - assets/png/ - assets/svg/ + +flutter_gen: + output: lib/generated/ diff --git a/learning/tour-of-beam/frontend/lib/components/page_container.dart b/learning/tour-of-beam/frontend/test/common/test_screen_wrapper.dart similarity index 61% rename from learning/tour-of-beam/frontend/lib/components/page_container.dart rename to learning/tour-of-beam/frontend/test/common/test_screen_wrapper.dart index 3500e1739273c..8146e336c3dfe 100644 --- a/learning/tour-of-beam/frontend/lib/components/page_container.dart +++ b/learning/tour-of-beam/frontend/test/common/test_screen_wrapper.dart @@ -17,37 +17,23 @@ */ import 'package:flutter/material.dart'; +import 'package:playground_components/playground_components.dart'; +import 'package:provider/provider.dart'; -import '../constants/sizes.dart'; -import 'footer.dart'; -import 'logo.dart'; -import 'sign_in/sign_in_button.dart'; -import 'toggle_theme_button.dart'; - -class PageContainer extends StatelessWidget { +class TestScreenWrapper extends StatelessWidget { final Widget child; - - const PageContainer({ - super.key, - required this.child, - }); + const TestScreenWrapper({required this.child}); @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Logo(), - actions: const [ - ToggleThemeButton(), - SignInButton(), - SizedBox(width: TobSizes.size16), - ], - ), - body: Column( - children: [ - Expanded(child: child), - const Footer(), - ], + return ThemeSwitchNotifierProvider( + child: Consumer( + builder: (context, themeSwitchNotifier, _) { + return MaterialApp( + theme: kLightTheme, + home: child, + ); + }, ), ); } diff --git a/learning/tour-of-beam/frontend/test/overflow_test.dart b/learning/tour-of-beam/frontend/test/overflow_test.dart new file mode 100644 index 0000000000000..0596b63a61c2c --- /dev/null +++ b/learning/tour-of-beam/frontend/test/overflow_test.dart @@ -0,0 +1,34 @@ +/* + * 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_test/flutter_test.dart'; +import 'package:tour_of_beam/pages/tour/screen.dart'; +import 'common/test_screen_wrapper.dart'; + +void main() { + testWidgets('WelcomeScreen overflow', (tester) async { + tester.binding.window.physicalSizeTestValue = const Size(500, 296); + // TODO(nausharipov): fix the failure + await tester.pumpWidget( + const TestScreenWrapper( + child: TourScreen(), + ), + ); + }); +} diff --git a/playground/buf.gen.yaml b/playground/buf.gen.yaml index bd9b9f3d83090..d04b54a6c5dd3 100644 --- a/playground/buf.gen.yaml +++ b/playground/buf.gen.yaml @@ -28,5 +28,5 @@ plugins: - paths=source_relative - require_unimplemented_servers=false - name: dart - out: frontend/lib - opt: grpc \ No newline at end of file + out: frontend/playground_components/lib/src + opt: grpc diff --git a/playground/frontend/Dockerfile b/playground/frontend/Dockerfile index 4265186930754..2b342adfca1cf 100644 --- a/playground/frontend/Dockerfile +++ b/playground/frontend/Dockerfile @@ -17,11 +17,11 @@ ############################################################################### FROM debian:11 as build -ARG FLUTTER_VERSION=3.0.1-stable +ARG FLUTTER_VERSION=3.3.2 RUN apt-get update && apt-get install -y wget xz-utils git -RUN wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_$FLUTTER_VERSION.tar.xz &&\ - tar -xf flutter_linux_$FLUTTER_VERSION.tar.xz &&\ +RUN wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_$FLUTTER_VERSION-stable.tar.xz &&\ + tar -xf flutter_linux_$FLUTTER_VERSION-stable.tar.xz &&\ mv flutter /opt/ &&\ ln -s /opt/flutter/bin/flutter /usr/bin/flutter &&\ ln -s /opt/flutter/bin/dart /usr/bin/dart &&\ @@ -31,9 +31,18 @@ RUN wget https://storage.googleapis.com/flutter_infra_release/releases/stable/li COPY . /app WORKDIR /app -RUN cd /app && flutter build web -v + +RUN cd /app/playground_components &&\ + flutter pub get -v &&\ + flutter pub run build_runner build -v + +RUN cd /app &&\ + flutter pub get -v &&\ + flutter pub run build_runner build -v &&\ + flutter build web -v + FROM nginx:1.21.3 COPY --from=build /app/nginx_default.conf /etc/nginx/conf.d/default.conf COPY --from=build /app/build/web/ /usr/share/nginx/html -RUN cp /usr/share/nginx/html/assets/assets/* /usr/share/nginx/html/assets/ +RUN cp -r /usr/share/nginx/html/assets/assets/* /usr/share/nginx/html/assets/ RUN gzip -kr /usr/share/nginx/html/assets/* diff --git a/playground/frontend/README.md b/playground/frontend/README.md index 92eb33628ab2c..eb015b48b9ea5 100644 --- a/playground/frontend/README.md +++ b/playground/frontend/README.md @@ -25,6 +25,18 @@ Beam Playground is an interactive environment to try out Beam transforms and exa ## Getting Started +Running, debugging, and testing all require this first step that fetches +dependencies and generates code: + +```bash +cd playground_components +flutter pub get +flutter pub run build_runner build +cd .. +flutter pub get +flutter pub run build_runner build +``` + ### Run See [playground/README.md](../README.md) for details on requirements and setup. diff --git a/playground/frontend/assets/theme.svg b/playground/frontend/assets/theme.svg deleted file mode 100644 index 10d8b3d5c9bef..0000000000000 --- a/playground/frontend/assets/theme.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - diff --git a/playground/frontend/assets/translations/en.yaml b/playground/frontend/assets/translations/en.yaml new file mode 100644 index 0000000000000..c7d74f96d44f7 --- /dev/null +++ b/playground/frontend/assets/translations/en.yaml @@ -0,0 +1,21 @@ +# 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. + +intents: + playground: + clearOutput: 'Clear Output' + newExample: 'New Example' diff --git a/playground/frontend/build.gradle b/playground/frontend/build.gradle index fe8aa85bdf10b..5843927fc7774 100644 --- a/playground/frontend/build.gradle +++ b/playground/frontend/build.gradle @@ -40,26 +40,18 @@ dependencies { dockerDependency project(path: playgroundJobServerProject, configuration: "shadow") } -task removeBuild { - group = "verification" - description = "remove build artifacts" - doLast { - exec { - executable("rm") - args("-r", "build") - } - } -} - task analyze { - dependsOn("pubGet") - dependsOn("removeBuild") + dependsOn("playground_components:generate") + dependsOn("generate") + group = "verification" description = "Analyze dart code" + doLast { exec { + // Exact paths instead of '.' so it does not go into playground_components executable("dart") - args("analyze", ".") + args("analyze", "lib", "test") } } } @@ -80,8 +72,9 @@ task format { description = "Idiomatically format Dart source code" doLast { exec { + // Exact paths instead of '.' so it does not go into playground_components executable("dart") - args("format", ".") + args("format", "lib", "test") } } } @@ -98,9 +91,12 @@ task run { } task test { - dependsOn("pubGet") + dependsOn("playground_components:generate") + dependsOn("generate") + group = "verification" description = "flutter test" + doLast { exec { executable("flutter") @@ -110,10 +106,37 @@ task test { } task precommit { - dependsOn(":playground:frontend:removeBuild") - dependsOn(":playground:frontend:pubGet") - dependsOn(":playground:frontend:analyze") - dependsOn(":playground:frontend:test") + dependsOn("playground_components:precommit") + + dependsOn("analyze") + dependsOn("test") +} + +task generate { + dependsOn("flutterClean") + dependsOn("pubGet") + + group = "build" + description = "Generate code" + + doLast { + exec { + executable("flutter") + args("pub", "run", "build_runner", "build", "--delete-conflicting-outputs") + } + } +} + +task flutterClean { + group = "build" + description = "Remove build artifacts" + + doLast { + exec { + executable("flutter") + args("clean") + } + } } task copyDockerfileDependencies(type: Copy) { diff --git a/playground/frontend/lib/components/banner/banner_description.dart b/playground/frontend/lib/components/banner/banner_description.dart index ab9ccfd51793e..34ff3b51cefae 100644 --- a/playground/frontend/lib/components/banner/banner_description.dart +++ b/playground/frontend/lib/components/banner/banner_description.dart @@ -18,7 +18,6 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/constants/font_weight.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/examples/components/description_popover/description_popover.dart'; @@ -61,10 +60,9 @@ class BannerDescription extends StatelessWidget { RichText( text: TextSpan( children: [ - TextSpan( + const TextSpan( text: kBannerDescription1, style: TextStyle( - color: ThemeColors.of(context).textColor, height: kDescriptionLineHeight, ), ), @@ -76,10 +74,9 @@ class BannerDescription extends StatelessWidget { ..onTap = () async { launchUrl(Uri.parse(kBannerUrl)); }), - TextSpan( + const TextSpan( text: kHyperlinkText, style: TextStyle( - color: ThemeColors.of(context).textColor, height: kDescriptionLineHeight, ), ), diff --git a/playground/frontend/lib/components/dropdown_button/dropdown_button.dart b/playground/frontend/lib/components/dropdown_button/dropdown_button.dart index fa89fce72ff04..c2aff6f2f9503 100644 --- a/playground/frontend/lib/components/dropdown_button/dropdown_button.dart +++ b/playground/frontend/lib/components/dropdown_button/dropdown_button.dart @@ -17,9 +17,9 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/utils/dropdown_utils.dart'; +import 'package:playground_components/playground_components.dart'; const int kAnimationDurationInMilliseconds = 80; const Offset kAnimationBeginOffset = Offset(0.0, -0.02); @@ -87,10 +87,12 @@ class _AppDropdownButtonState extends State @override Widget build(BuildContext context) { + final ext = Theme.of(context).extension()!; + return Container( height: kContainerHeight, decoration: BoxDecoration( - color: ThemeColors.of(context).dropdownButton, + color: ext.fieldBackgroundColor, borderRadius: BorderRadius.circular(kSmBorderRadius), ), child: TextButton( @@ -149,7 +151,7 @@ class _AppDropdownButtonState extends State height: widget.height, width: widget.width, decoration: BoxDecoration( - color: ThemeColors.of(context).background, + color: Theme.of(context).backgroundColor, borderRadius: BorderRadius.circular(kMdBorderRadius), ), child: widget.createDropdown(_close), diff --git a/playground/frontend/lib/components/playground_run_or_cancel_button.dart b/playground/frontend/lib/components/playground_run_or_cancel_button.dart new file mode 100644 index 0000000000000..b555a71a882ce --- /dev/null +++ b/playground/frontend/lib/components/playground_run_or_cancel_button.dart @@ -0,0 +1,57 @@ +/* + * 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/widgets.dart'; +import 'package:playground_components/playground_components.dart'; +import 'package:provider/provider.dart'; + +import '../modules/analytics/analytics_service.dart'; +import '../utils/analytics_utils.dart'; + +class PlaygroundRunOrCancelButton extends StatelessWidget { + const PlaygroundRunOrCancelButton(); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, playgroundController, child) { + final analyticsService = AnalyticsService.get(context); + final stopwatch = Stopwatch(); + final exampleName = getAnalyticsExampleName(playgroundController); + + return RunOrCancelButton( + playgroundController: playgroundController, + beforeCancel: () { + final exampleName = getAnalyticsExampleName(playgroundController); + analyticsService.trackClickCancelRunEvent(exampleName); + }, + beforeRun: () { + stopwatch.start(); + analyticsService.trackClickRunEvent(exampleName); + }, + onComplete: () { + analyticsService.trackRunTimeEvent( + exampleName, + stopwatch.elapsedMilliseconds, + ); + }, + ); + } + ); + } +} diff --git a/playground/frontend/lib/components/toggle_theme_button/toggle_theme_button.dart b/playground/frontend/lib/components/toggle_theme_button/toggle_theme_button.dart deleted file mode 100644 index 730b5b240b92e..0000000000000 --- a/playground/frontend/lib/components/toggle_theme_button/toggle_theme_button.dart +++ /dev/null @@ -1,55 +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:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:playground/config/theme.dart'; -import 'package:playground/constants/assets.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/analytics/analytics_service.dart'; -import 'package:provider/provider.dart'; - -class ToggleThemeButton extends StatelessWidget { - const ToggleThemeButton({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final appLocale = AppLocalizations.of(context)!; - - return Consumer(builder: (context, notifier, child) { - final text = notifier.isDarkMode ? appLocale.lightMode : appLocale.darkMode; - - return Padding( - padding: const EdgeInsets.symmetric( - vertical: kSmSpacing, - horizontal: kMdSpacing, - ), - child: TextButton.icon( - icon: SvgPicture.asset(kThemeIconAsset), - label: Text(text), - onPressed: () { - notifier.toggleTheme(); - AnalyticsService.get(context) - .trackClickToggleTheme(!notifier.isDarkMode); - }, - ), - ); - }); - } -} diff --git a/playground/frontend/lib/config/theme.dart b/playground/frontend/lib/config/theme.dart deleted file mode 100644 index 62a30f0082b01..0000000000000 --- a/playground/frontend/lib/config/theme.dart +++ /dev/null @@ -1,305 +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:code_text_field/code_text_field.dart'; -import 'package:flutter/material.dart'; -import 'package:playground/constants/colors.dart'; -import 'package:playground/constants/font_weight.dart'; -import 'package:playground/constants/fonts.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/editor/components/editor_themes.dart'; -import 'package:provider/provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -const kThemeMode = 'theme_mode'; - -class ThemeSwitchNotifier extends ChangeNotifier { - late SharedPreferences _preferences; - ThemeMode themeMode = ThemeMode.light; - - static const _darkThemeColors = ThemeColors.fromBrightness(isDark: true); - static const _lightThemeColors = ThemeColors.fromBrightness(isDark: false); - - ThemeColors get themeColors { - switch (themeMode) { - case ThemeMode.dark: - return _darkThemeColors; - default: - return _lightThemeColors; - } - } - - final _darkCodeTheme = createTheme(_darkThemeColors); - final _lightCodeTheme = createTheme(_lightThemeColors); - - CodeThemeData get codeTheme { - switch (themeMode) { - case ThemeMode.dark: - return _darkCodeTheme; - default: - return _lightCodeTheme; - } - } - - init() { - _setPreferences(); - } - - _setPreferences() async { - _preferences = await SharedPreferences.getInstance(); - themeMode = _preferences.getString(kThemeMode) == ThemeMode.dark.toString() - ? ThemeMode.dark - : ThemeMode.light; - notifyListeners(); - } - - bool get isDarkMode { - return themeMode == ThemeMode.dark; - } - - void toggleTheme() { - themeMode = themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light; - _preferences.setString(kThemeMode, themeMode.toString()); - notifyListeners(); - } -} - -class ThemeSwitchNotifierProvider extends StatelessWidget { - final Widget child; - - const ThemeSwitchNotifierProvider({ - super.key, - required this.child, - }); - - @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (context) => ThemeSwitchNotifier()..init(), - child: Consumer( - builder: (context, themeSwitchNotifier, _) => ThemeColorsProvider( - data: themeSwitchNotifier.themeColors, - child: child, - ), - ), - ); - } -} - -class ThemeColorsProvider extends StatelessWidget { - final ThemeColors data; - final Widget child; - - const ThemeColorsProvider({ - super.key, - required this.data, - required this.child, - }); - - @override - Widget build(BuildContext context) { - return Provider.value( - value: data, - child: child, - ); - } -} - -TextTheme createTextTheme(Color textColor) { - return getBaseFontTheme( - const TextTheme( - headline1: TextStyle(), - headline2: TextStyle(), - headline3: TextStyle(), - headline4: TextStyle(), - headline5: TextStyle(), - headline6: TextStyle(), - subtitle1: TextStyle(), - subtitle2: TextStyle(), - bodyText1: TextStyle(), - bodyText2: TextStyle(), - caption: TextStyle(), - overline: TextStyle(), - button: TextStyle(fontWeight: kBoldWeight), - ).apply( - bodyColor: textColor, - displayColor: textColor, - ), - ); -} - -TextButtonThemeData createTextButtonTheme(Color textColor) { - return TextButtonThemeData( - style: TextButton.styleFrom( - primary: textColor, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(kLgBorderRadius)), - ), - ), - ); -} - -OutlinedButtonThemeData createOutlineButtonTheme(Color textColor) { - return OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - primary: textColor, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(kSmBorderRadius)), - ), - ), - ); -} - -ElevatedButtonThemeData createElevatedButtonTheme(Color primaryColor) { - return ElevatedButtonThemeData( - style: ElevatedButton.styleFrom(primary: primaryColor), - ); -} - -PopupMenuThemeData createPopupMenuTheme() { - return const PopupMenuThemeData( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(kLgBorderRadius), - ), - ), - ); -} - -AppBarTheme createAppBarTheme(Color backgroundColor) { - return AppBarTheme( - color: backgroundColor, - elevation: 1, - centerTitle: false, - ); -} - -TabBarTheme createTabBarTheme(Color textColor, Color indicatorColor) { - const labelStyle = TextStyle(fontWeight: kMediumWeight); - return TabBarTheme( - unselectedLabelColor: textColor, - labelColor: textColor, - labelStyle: labelStyle, - unselectedLabelStyle: labelStyle, - indicator: UnderlineTabIndicator( - borderSide: BorderSide(width: 2.0, 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, - backgroundColor: kLightPrimaryBackground, - appBarTheme: createAppBarTheme(kLightSecondaryBackground), - textTheme: createTextTheme(kLightText), - popupMenuTheme: createPopupMenuTheme(), - textButtonTheme: createTextButtonTheme(kLightText), - outlinedButtonTheme: createOutlineButtonTheme(kLightText), - elevatedButtonTheme: createElevatedButtonTheme(kLightPrimary), - tabBarTheme: createTabBarTheme(kLightText, kLightPrimary), - dialogTheme: createDialogTheme(kLightText), -); - -final kDarkTheme = ThemeData( - brightness: Brightness.dark, - primaryColor: kDarkPrimary, - backgroundColor: kDarkPrimaryBackground, - appBarTheme: createAppBarTheme(kDarkSecondaryBackground), - textTheme: createTextTheme(kDarkText), - popupMenuTheme: createPopupMenuTheme(), - textButtonTheme: createTextButtonTheme(kDarkText), - outlinedButtonTheme: createOutlineButtonTheme(kDarkText), - elevatedButtonTheme: createElevatedButtonTheme(kDarkPrimary), - tabBarTheme: createTabBarTheme(kDarkText, kDarkPrimary), - dialogTheme: createDialogTheme(kDarkText), -); - -class ThemeColors { - final Color? _background; - final Color? _dropdownButton; - - final bool isDark; - - static ThemeColors of(BuildContext context, {bool listen = true}) { - return Provider.of(context, listen: listen); - } - - ThemeColors({ - required this.isDark, - Color? background, - Color? dropdownButtonColor, - }) : _background = background, - _dropdownButton = dropdownButtonColor; - - const ThemeColors.fromBrightness({ - required this.isDark, - }) : _background = null, - _dropdownButton = null; - - ThemeColors copyWith({ - Color? background, - Color? dropdownButton, - }) { - return ThemeColors( - isDark: isDark, - background: background ?? this.background, - dropdownButtonColor: dropdownButton ?? this.dropdownButton, - ); - } - - Color get dropdownButton => - _dropdownButton ?? (isDark ? kDarkGrey : kLightGrey); - - Color get divider => isDark ? kDarkGrey : kLightGrey; - - Color get lightGreyColor => isDark ? kLightGrey1 : kLightGrey; - - Color get primary => isDark ? kLightPrimary : kDarkPrimary; - - Color get primaryBackgroundTextColor => Colors.white; - - Color get lightGreyBackgroundTextColor => Colors.black; - - Color get grey1Color => isDark ? kDarkGrey1 : kLightGrey1; - - Color get secondaryBackground => - isDark ? kDarkSecondaryBackground : kLightSecondaryBackground; - - Color get background => - _background ?? - (isDark ? kDarkPrimaryBackground : kLightPrimaryBackground); - - Color get code1 => isDark ? kDarkCode2 : kLightCode2; - - Color get code2 => isDark ? kDarkCode1 : kLightCode1; - - Color get codeComment => isDark ? kDarkCodeComment : kLightCodeComment; - - Color get textColor => isDark ? kDarkText : kLightText; -} diff --git a/playground/frontend/lib/constants/sizes.dart b/playground/frontend/lib/constants/sizes.dart index 3d895aae285dc..c4515a5e3dc1f 100644 --- a/playground/frontend/lib/constants/sizes.dart +++ b/playground/frontend/lib/constants/sizes.dart @@ -25,23 +25,17 @@ const double kXlSpacing = 16.0; const double kXxlSpacing = 36.0; // sizes -const kHeaderButtonHeight = 46.0; -const kRunButtonWidth = 150.0; const kButtonHeight = 40.0; const kIconButtonSplashRadius = 24.0; -const kFooterHeight = 32.0; // border radius const double kSmBorderRadius = 4.0; const double kMdBorderRadius = 6.0; -const double kLgBorderRadius = 8.0; -const double kXlBorderRadius = 28.0; // elevation const double kElevation = 2; // icon sizes -const double kIconSizeXs = 8.0; const double kIconSizeSm = 16.0; const double kIconSizeMd = 24.0; const double kIconSizeLg = 32.0; @@ -52,16 +46,8 @@ 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; const double kTitleFontSize = 18.0; //divider size const double kDividerHeight = 1.0; -const double kLgDividerHeight = 2.0; - -//loading indicator size -const double kMdLoadingIndicatorSize = 40.0; -const double kLgLoadingIndicatorSize = 50.0; diff --git a/playground/frontend/lib/l10n/app_en.arb b/playground/frontend/lib/l10n/app_en.arb index 48a3a8675b82d..538df437994a9 100644 --- a/playground/frontend/lib/l10n/app_en.arb +++ b/playground/frontend/lib/l10n/app_en.arb @@ -7,14 +7,6 @@ "@darkMode": { "description": "Title for a theme switch" }, - "newExample": "New Example", - "@newExample": { - "description": "Title for the New Example button" - }, - "reset": "Reset", - "@reset": { - "description": "Title for the reset button" - }, "run": "Run", "@run": { "description": "Title for the run button" @@ -143,10 +135,6 @@ "@reportIssue": { "description": "Title for the Report issue in GitHub button" }, - "codeTextArea": "Code Text Area", - "@codeTextArea": { - "description": "Title for the Code text area semantics" - }, "bottom": "Bottom", "@bottom": { "description": "Part of the output placements semantics label" @@ -159,10 +147,6 @@ "@left": { "description": "Part of the output placements semantics label" }, - "clearOutput": "Clear Output", - "@clearOutput": { - "description": "Title for the Clear Output shortcut row" - }, "pipelineOptions": "Pipeline Options", "@pipelineOptions": { "description": "Title for the Pipeline Options" diff --git a/playground/frontend/lib/l10n/l10n.dart b/playground/frontend/lib/l10n/l10n.dart index cd63ec7799093..9f7e197317e44 100644 --- a/playground/frontend/lib/l10n/l10n.dart +++ b/playground/frontend/lib/l10n/l10n.dart @@ -16,10 +16,12 @@ * limitations under the License. */ -import 'package:flutter/material.dart'; +import 'dart:ui'; class L10n { + static const en = Locale('en'); + static const locales = [ - Locale('en'), + en, ]; } diff --git a/playground/frontend/lib/main.dart b/playground/frontend/lib/main.dart index 2e57fc49a1208..0c36af3e7bcf9 100644 --- a/playground/frontend/lib/main.dart +++ b/playground/frontend/lib/main.dart @@ -17,15 +17,34 @@ */ import 'package:akvelon_flutter_issue_106664_workaround/akvelon_flutter_issue_106664_workaround.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:easy_localization_ext/easy_localization_ext.dart'; +import 'package:easy_localization_loader/easy_localization_loader.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl_browser.dart'; -import 'package:playground/configure_nonweb.dart' -if (dart.library.html) 'package:playground/configure_web.dart'; import 'package:playground/playground_app.dart'; +import 'package:playground_components/playground_components.dart'; +import 'package:url_strategy/url_strategy.dart'; -void main() { +import 'l10n/l10n.dart'; + +void main() async { FlutterIssue106664Workaround.instance.apply(); - findSystemLocale(); - configureApp(); - runApp(const PlaygroundApp()); + setPathUrlStrategy(); + await EasyLocalization.ensureInitialized(); + + await findSystemLocale(); + runApp( + EasyLocalization( + supportedLocales: L10n.locales, + startLocale: L10n.en, + fallbackLocale: L10n.en, + path: 'assets/translations', + assetLoader: MultiAssetLoader([ + PlaygroundComponents.translationLoader, + YamlAssetLoader(), + ]), + child: const PlaygroundApp(), + ), + ); } diff --git a/playground/frontend/lib/modules/actions/components/new_example_action.dart b/playground/frontend/lib/modules/actions/components/new_example_action.dart index 332b4beba3971..594492987c112 100644 --- a/playground/frontend/lib/modules/actions/components/new_example_action.dart +++ b/playground/frontend/lib/modules/actions/components/new_example_action.dart @@ -16,13 +16,11 @@ * 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/config/theme.dart'; -import 'package:playground/modules/actions/components/header_icon_button.dart'; import 'package:playground/modules/analytics/analytics_service.dart'; -import 'package:playground/modules/shortcuts/components/shortcut_tooltip.dart'; import 'package:playground/modules/shortcuts/constants/global_shortcuts.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:url_launcher/url_launcher.dart'; class NewExampleAction extends StatelessWidget { @@ -35,9 +33,9 @@ class NewExampleAction extends StatelessWidget { child: HeaderIconButton( icon: Icon( Icons.add_circle_outline, - color: ThemeColors.of(context).grey1Color, + color: Theme.of(context).extension()?.iconColor, ), - label: AppLocalizations.of(context)!.newExample, + label: 'intents.playground.newExample'.tr(), onPressed: () { launchUrl(Uri.parse('/')); AnalyticsService.get(context).trackClickNewExample(); diff --git a/playground/frontend/lib/modules/actions/components/reset_action.dart b/playground/frontend/lib/modules/actions/components/reset_action.dart index 533bdec2d1c40..a0e111996322a 100644 --- a/playground/frontend/lib/modules/actions/components/reset_action.dart +++ b/playground/frontend/lib/modules/actions/components/reset_action.dart @@ -17,33 +17,21 @@ */ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:playground/config/theme.dart'; -import 'package:playground/constants/assets.dart'; -import 'package:playground/modules/actions/components/header_icon_button.dart'; import 'package:playground/modules/analytics/analytics_service.dart'; -import 'package:playground/modules/shortcuts/components/shortcut_tooltip.dart'; -import 'package:playground/modules/shortcuts/constants/global_shortcuts.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:playground_components/playground_components.dart'; +import 'package:provider/provider.dart'; class ResetAction extends StatelessWidget { - final VoidCallback reset; - - const ResetAction({Key? key, required this.reset}) : super(key: key); + const ResetAction(); @override Widget build(BuildContext context) { - return ShortcutTooltip( - shortcut: kResetShortcut, - child: HeaderIconButton( - icon: SvgPicture.asset( - kResetIconAsset, - color: ThemeColors.of(context).grey1Color, - ), - label: AppLocalizations.of(context)!.reset, - onPressed: () { - reset(); - AnalyticsService.get(context).trackReset(); + final analyticsService = AnalyticsService.get(context); + return Consumer( + builder: (context, playgroundController, child) => ResetButton( + playgroundController: playgroundController, + beforeReset: () { + analyticsService.trackReset(); }, ), ); diff --git a/playground/frontend/lib/modules/analytics/analytics_service.dart b/playground/frontend/lib/modules/analytics/analytics_service.dart index 6571a4898d445..0416e7dcc520c 100644 --- a/playground/frontend/lib/modules/analytics/analytics_service.dart +++ b/playground/frontend/lib/modules/analytics/analytics_service.dart @@ -17,8 +17,7 @@ */ import 'package:flutter/widgets.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; abstract class AnalyticsService { @@ -26,8 +25,8 @@ abstract class AnalyticsService { return Provider.of(context, listen: false); } - void trackSelectSdk(SDK? oldSdk, SDK newSdk); - void trackSelectExample(ExampleModel newExample); + void trackSelectSdk(Sdk? oldSdk, Sdk newSdk); + void trackSelectExample(ExampleBase newExample); void trackClickNewExample(); void trackReset(); void trackClickToggleTheme(bool isDark); diff --git a/playground/frontend/lib/modules/analytics/google_analytics_service.dart b/playground/frontend/lib/modules/analytics/google_analytics_service.dart index d9eaf2e6f120f..7b083b3bc25ea 100644 --- a/playground/frontend/lib/modules/analytics/google_analytics_service.dart +++ b/playground/frontend/lib/modules/analytics/google_analytics_service.dart @@ -19,24 +19,23 @@ import 'package:playground/config.g.dart'; import 'package:playground/modules/analytics/analytics_events.dart'; import 'package:playground/modules/analytics/analytics_service.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:usage/usage_html.dart'; class GoogleAnalyticsService implements AnalyticsService { final _analytics = AnalyticsHtml(kAnalyticsUA, 'beam', '1.0'); @override - void trackSelectSdk(SDK? oldSdk, SDK newSdk) { + void trackSelectSdk(Sdk? oldSdk, Sdk newSdk) { safeSendEvent( kSdkCategory, kSelectSdkEvent, - label: '${oldSdk?.displayName}_${newSdk.displayName}', + label: '${oldSdk?.title}_${newSdk.title}', ); } @override - void trackSelectExample(ExampleModel newExample) { + void trackSelectExample(ExampleBase newExample) { safeSendEvent( kExampleCategory, kSelectExampleEvent, diff --git a/playground/frontend/lib/modules/editor/components/editor_themes.dart b/playground/frontend/lib/modules/editor/components/editor_themes.dart deleted file mode 100644 index 42c68905f9628..0000000000000 --- a/playground/frontend/lib/modules/editor/components/editor_themes.dart +++ /dev/null @@ -1,64 +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:code_text_field/code_text_field.dart'; -import 'package:flutter/material.dart'; -import 'package:playground/config/theme.dart'; - -CodeThemeData createTheme(ThemeColors colors) { - return CodeThemeData( - styles: _createThemeStyles(colors), - ); -} - -Map _createThemeStyles(ThemeColors colors) { - return { - 'root': TextStyle( - backgroundColor: colors.background, - color: colors.textColor, - ), - 'comment': TextStyle(color: colors.codeComment), - 'quote': TextStyle(color: colors.code2), - 'variable': TextStyle(color: colors.code2), - 'keyword': TextStyle(color: colors.code2), - 'selector-tag': TextStyle(color: colors.code2), - 'built_in': TextStyle(color: colors.code2), - 'name': TextStyle(color: colors.code2), - 'tag': TextStyle(color: colors.code2), - 'string': TextStyle(color: colors.code1), - 'title': TextStyle(color: colors.code1), - 'section': TextStyle(color: colors.code1), - 'attribute': TextStyle(color: colors.code1), - 'literal': TextStyle(color: colors.code1), - 'template-tag': TextStyle(color: colors.code1), - 'template-variable': TextStyle(color: colors.code1), - 'type': TextStyle(color: colors.code1), - 'addition': TextStyle(color: colors.code1), - 'deletion': TextStyle(color: colors.code2), - 'selector-attr': TextStyle(color: colors.code2), - 'selector-pseudo': TextStyle(color: colors.code2), - 'meta': TextStyle(color: colors.code2), - 'doctag': TextStyle(color: colors.codeComment), - 'attr': TextStyle(color: colors.primary), - 'symbol': TextStyle(color: colors.code2), - 'bullet': TextStyle(color: colors.code2), - 'link': TextStyle(color: colors.code2), - 'emphasis': const TextStyle(fontStyle: FontStyle.italic), - 'strong': const TextStyle(fontWeight: FontWeight.bold), - }; -} diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart index fd1d6c9eedaa8..0b8cd5836b4bf 100644 --- a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart @@ -24,7 +24,7 @@ import 'package:playground/modules/editor/components/pipeline_options_dropdown/p import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_separator.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart'; -import 'package:playground/modules/editor/parsers/run_options_parser.dart'; +import 'package:playground_components/playground_components.dart'; const kOptionsTabIndex = 0; const kRawTabIndex = 1; diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_separator.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_separator.dart index 17070dd84f162..78f8b0d7c34c7 100644 --- a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_separator.dart +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_separator.dart @@ -17,8 +17,8 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/constants/sizes.dart'; +import 'package:playground_components/playground_components.dart'; class PipelineOptionsDropdownSeparator extends StatelessWidget { const PipelineOptionsDropdownSeparator({Key? key}) : super(key: key); @@ -28,7 +28,7 @@ class PipelineOptionsDropdownSeparator extends StatelessWidget { return Container( height: kDividerHeight, decoration: BoxDecoration( - color: ThemeColors.of(context).lightGreyColor, + color: Theme.of(context).extension()?.borderColor, ), ); } diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart index a7f2eabf87e3f..6aa41c0c3cdf9 100644 --- a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart @@ -16,10 +16,9 @@ * limitations under the License. */ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:collection/collection.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/constants/colors.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_option_controller.dart'; @@ -81,7 +80,7 @@ class PipelineOptionsForm extends StatelessWidget { Icons.delete_outlined, color: kLightPrimary, ), - color: ThemeColors.of(context).grey1Color, + color: Theme.of(context).dividerColor, onPressed: () => onDelete(index), ), ), diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart index 2c11873652880..d4202b1256977 100644 --- a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart @@ -17,8 +17,8 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/constants/sizes.dart'; +import 'package:playground_components/playground_components.dart'; class PipelineOptionsTextField extends StatelessWidget { final TextEditingController controller; @@ -32,6 +32,9 @@ class PipelineOptionsTextField extends StatelessWidget { @override Widget build(BuildContext context) { + final themeData = Theme.of(context); + final ext = themeData.extension()!; + return Container( margin: const EdgeInsets.only( top: kMdSpacing, @@ -48,16 +51,15 @@ class PipelineOptionsTextField extends StatelessWidget { controller: controller, decoration: InputDecoration( contentPadding: const EdgeInsets.all(kMdSpacing), - border: _getInputBorder(ThemeColors.of(context).lightGreyColor), - focusedBorder: _getInputBorder(ThemeColors.of(context).primary), + border: _getInputBorder(ext.borderColor), + focusedBorder: _getInputBorder(themeData.primaryColor), ), - cursorColor: ThemeColors.of(context).textColor, ), ), ); } - _getInputBorder(Color color) { + OutlineInputBorder _getInputBorder(Color color) { return OutlineInputBorder( borderSide: BorderSide(color: color), borderRadius: BorderRadius.circular(kMdBorderRadius), diff --git a/playground/frontend/lib/modules/editor/components/share_dropdown/link_text_field.dart b/playground/frontend/lib/modules/editor/components/share_dropdown/link_text_field.dart index 9708a88208a00..67430514e5a39 100644 --- a/playground/frontend/lib/modules/editor/components/share_dropdown/link_text_field.dart +++ b/playground/frontend/lib/modules/editor/components/share_dropdown/link_text_field.dart @@ -18,9 +18,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/constants/font_weight.dart'; import 'package:playground/constants/sizes.dart'; +import 'package:playground_components/playground_components.dart'; const _kTextFieldMaxHeight = 45.0; @@ -45,9 +45,11 @@ class _LinkTextFieldState extends State { @override Widget build(BuildContext context) { + final themeData = Theme.of(context); + return Container( decoration: BoxDecoration( - color: ThemeColors.of(context).dropdownButton, + color: themeData.extension()?.borderColor, borderRadius: BorderRadius.circular(kSmBorderRadius), ), child: Container( @@ -66,7 +68,7 @@ class _LinkTextFieldState extends State { style: TextStyle( fontSize: kLabelFontSize, fontWeight: kNormalWeight, - color: ThemeColors.of(context).primary, + color: themeData.primaryColor, ), ), ), diff --git a/playground/frontend/lib/modules/editor/components/share_dropdown/share_button.dart b/playground/frontend/lib/modules/editor/components/share_dropdown/share_button.dart index 9355934e71865..f0f6026ea6933 100644 --- a/playground/frontend/lib/modules/editor/components/share_dropdown/share_button.dart +++ b/playground/frontend/lib/modules/editor/components/share_dropdown/share_button.dart @@ -19,8 +19,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:playground/components/dropdown_button/dropdown_button.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/modules/editor/components/share_dropdown/share_dropdown_body.dart'; +import 'package:playground_components/playground_components.dart'; const _kShareDropdownHeight = 140.0; const _kShareDropdownWidth = 460.0; @@ -31,22 +31,28 @@ class ShareButton extends StatelessWidget { @override Widget build(BuildContext context) { + final parentThemeData = Theme.of(context); + final ext = parentThemeData.extension()!; final appLocale = AppLocalizations.of(context)!; - final parentThemeData = ThemeColors.of(context); final themeData = parentThemeData.copyWith( - background: parentThemeData.secondaryBackground, - dropdownButton: parentThemeData.primary.withOpacity(_kButtonColorOpacity), + backgroundColor: ext.secondaryBackgroundColor, + extensions: { + ext.copyWith( + fieldBackgroundColor: + parentThemeData.primaryColor.withOpacity(_kButtonColorOpacity), + ), + }, ); - return ThemeColorsProvider( + return Theme( data: themeData, child: AppDropdownButton( buttonText: Text(appLocale.shareMyCode), showArrow: false, leading: Icon( Icons.share_outlined, - color: ThemeColors.of(context).primary, + color: themeData.primaryColor, ), height: _kShareDropdownHeight, width: _kShareDropdownWidth, diff --git a/playground/frontend/lib/modules/editor/components/share_dropdown/share_dropdown_body.dart b/playground/frontend/lib/modules/editor/components/share_dropdown/share_dropdown_body.dart index 68a208373671d..18366335fee79 100644 --- a/playground/frontend/lib/modules/editor/components/share_dropdown/share_dropdown_body.dart +++ b/playground/frontend/lib/modules/editor/components/share_dropdown/share_dropdown_body.dart @@ -19,7 +19,7 @@ import 'package:flutter/material.dart'; import 'package:playground/modules/editor/components/share_dropdown/share_tabs/share_tabs.dart'; import 'package:playground/modules/editor/components/share_dropdown/share_tabs_headers.dart'; -import 'package:playground/modules/output/components/output_header/tab_header.dart'; +import 'package:playground_components/playground_components.dart'; const _kTabsCount = 2; diff --git a/playground/frontend/lib/modules/editor/components/share_dropdown/share_tabs/share_tabs.dart b/playground/frontend/lib/modules/editor/components/share_dropdown/share_tabs/share_tabs.dart index 8c2e72948ae05..45d5b82cd0059 100644 --- a/playground/frontend/lib/modules/editor/components/share_dropdown/share_tabs/share_tabs.dart +++ b/playground/frontend/lib/modules/editor/components/share_dropdown/share_tabs/share_tabs.dart @@ -19,7 +19,7 @@ import 'package:flutter/material.dart'; import 'package:playground/modules/editor/components/share_dropdown/share_tabs/example_share_tabs.dart'; import 'package:playground/modules/editor/components/share_dropdown/share_tabs/snippet_save_and_share_tabs.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; class ShareTabs extends StatelessWidget { @@ -34,17 +34,17 @@ class ShareTabs extends StatelessWidget { Widget build(BuildContext context) { return Container( color: Theme.of(context).backgroundColor, - child: Consumer( - builder: (context, playgroundState, _) { - if (playgroundState.isExampleChanged) { + child: Consumer( + builder: (context, playgroundController, _) { + if (playgroundController.isExampleChanged) { return SnippetSaveAndShareTabs( - playgroundState: playgroundState, + playgroundController: playgroundController, tabController: tabController, ); } return ExampleShareTabs( - examplePath: playgroundState.selectedExample!.path, + examplePath: playgroundController.selectedExample!.path, tabController: tabController, ); }, diff --git a/playground/frontend/lib/modules/editor/components/share_dropdown/share_tabs/snippet_save_and_share_tabs.dart b/playground/frontend/lib/modules/editor/components/share_dropdown/share_tabs/snippet_save_and_share_tabs.dart index ddd1bbb0f5363..050725de8d92b 100644 --- a/playground/frontend/lib/modules/editor/components/share_dropdown/share_tabs/snippet_save_and_share_tabs.dart +++ b/playground/frontend/lib/modules/editor/components/share_dropdown/share_tabs/snippet_save_and_share_tabs.dart @@ -17,28 +17,26 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/components/loading_indicator/loading_indicator.dart'; -import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/editor/components/share_dropdown/share_tabs/example_share_tabs.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; class SnippetSaveAndShareTabs extends StatelessWidget { - final PlaygroundState playgroundState; + final PlaygroundController playgroundController; final TabController tabController; const SnippetSaveAndShareTabs({ super.key, - required this.playgroundState, + required this.playgroundController, required this.tabController, }); @override Widget build(BuildContext context) { return FutureBuilder( - future: playgroundState.getSnippetId(), + future: playgroundController.getSnippetId(), builder: (context, snapshot) { if (!snapshot.hasData) { - return const LoadingIndicator(size: kLgLoadingIndicatorSize); + return const LoadingIndicator(); } return ExampleShareTabs( diff --git a/playground/frontend/lib/modules/editor/components/share_dropdown/share_tabs_headers.dart b/playground/frontend/lib/modules/editor/components/share_dropdown/share_tabs_headers.dart index 4d80b4309bd28..1d528a0b228e8 100644 --- a/playground/frontend/lib/modules/editor/components/share_dropdown/share_tabs_headers.dart +++ b/playground/frontend/lib/modules/editor/components/share_dropdown/share_tabs_headers.dart @@ -17,7 +17,7 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -35,17 +35,19 @@ class ShareTabsHeaders extends StatelessWidget { Widget build(BuildContext context) { final appLocale = AppLocalizations.of(context)!; - return Consumer(builder: (context, state, child) { - return SizedBox( - width: _width, - child: TabBar( - controller: tabController, - tabs: [ - Text(appLocale.link), - Text(appLocale.embed), - ], - ), - ); - }); + return Consumer( + builder: (context, controller, child) { + return SizedBox( + width: _width, + child: TabBar( + controller: tabController, + tabs: [ + Text(appLocale.link), + Text(appLocale.embed), + ], + ), + ); + }, + ); } } diff --git a/playground/frontend/lib/modules/editor/repository/code_repository/code_client/grpc_code_client.dart b/playground/frontend/lib/modules/editor/repository/code_repository/code_client/grpc_code_client.dart deleted file mode 100644 index 6ec86f1826b22..0000000000000 --- a/playground/frontend/lib/modules/editor/repository/code_repository/code_client/grpc_code_client.dart +++ /dev/null @@ -1,234 +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:grpc/grpc_web.dart'; -import 'package:playground/api/iis_workaround_channel.dart'; -import 'package:playground/api/v1/api.pbgrpc.dart' as grpc; -import 'package:playground/config.g.dart'; -import 'package:playground/modules/editor/parsers/run_options_parser.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/check_status_response.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/code_client.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/output_response.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/run_code_response.dart'; -import 'package:playground/modules/editor/repository/code_repository/run_code_error.dart'; -import 'package:playground/modules/editor/repository/code_repository/run_code_request.dart'; -import 'package:playground/modules/editor/repository/code_repository/run_code_result.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; -import 'package:playground/utils/replace_incorrect_symbols.dart'; - -const kGeneralError = 'Failed to execute code'; - -class GrpcCodeClient implements CodeClient { - late final grpc.PlaygroundServiceClient _defaultClient; - - GrpcCodeClient() { - final channel = IisWorkaroundChannel.xhr( - Uri.parse(kApiClientURL), - ); - _defaultClient = grpc.PlaygroundServiceClient(channel); - } - - @override - Future runCode(RunCodeRequestWrapper request) { - return _runSafely(() => _createRunCodeClient(request.sdk) - .runCode(_toGrpcRequest(request)) - .then((response) => RunCodeResponse(response.pipelineUuid))); - } - - @override - Future cancelExecution(String pipelineUuid) { - return _runSafely(() => - _defaultClient.cancel(grpc.CancelRequest(pipelineUuid: pipelineUuid))); - } - - @override - Future checkStatus( - String pipelineUuid, - RunCodeRequestWrapper request, - ) { - return _runSafely(() => _defaultClient - .checkStatus(grpc.CheckStatusRequest(pipelineUuid: pipelineUuid)) - .then( - (response) => CheckStatusResponse(_toClientStatus(response.status)), - )); - } - - @override - Future getCompileOutput( - String pipelineUuid, - RunCodeRequestWrapper request, - ) { - return _runSafely(() => _defaultClient - .getCompileOutput( - grpc.GetCompileOutputRequest(pipelineUuid: pipelineUuid), - ) - .then((response) => _toOutputResponse(response.output))); - } - - @override - Future getRunOutput( - String pipelineUuid, - RunCodeRequestWrapper request, - ) { - return _runSafely(() => _defaultClient - .getRunOutput(grpc.GetRunOutputRequest(pipelineUuid: pipelineUuid)) - .then((response) => _toOutputResponse(response.output)) - .catchError((err) { - print(err); - return _toOutputResponse(''); - })); - } - - @override - Future getLogOutput( - String pipelineUuid, - RunCodeRequestWrapper request, - ) { - return _runSafely(() => _defaultClient - .getLogs(grpc.GetLogsRequest(pipelineUuid: pipelineUuid)) - .then((response) => _toOutputResponse(response.output)) - .catchError((err) { - print(err); - return _toOutputResponse(''); - })); - } - - @override - Future getRunErrorOutput( - String pipelineUuid, - RunCodeRequestWrapper request, - ) { - return _runSafely(() => _defaultClient - .getRunError(grpc.GetRunErrorRequest(pipelineUuid: pipelineUuid)) - .then((response) => _toOutputResponse(response.output))); - } - - @override - Future getValidationErrorOutput( - String pipelineUuid, - RunCodeRequestWrapper request, - ) { - return _runSafely(() => _defaultClient - .getValidationOutput( - grpc.GetValidationOutputRequest(pipelineUuid: pipelineUuid)) - .then((response) => _toOutputResponse(response.output))); - } - - @override - Future getPreparationErrorOutput( - String pipelineUuid, - RunCodeRequestWrapper request, - ) { - return _runSafely(() => _defaultClient - .getPreparationOutput( - grpc.GetPreparationOutputRequest(pipelineUuid: pipelineUuid)) - .then((response) => _toOutputResponse(response.output))); - } - - @override - Future getGraphOutput( - String pipelineUuid, - RunCodeRequestWrapper request, - ) { - return _runSafely(() => _defaultClient - .getGraph(grpc.GetGraphRequest(pipelineUuid: pipelineUuid)) - .then((response) => OutputResponse(response.graph)) - .catchError((err) { - print(err); - return _toOutputResponse(''); - })); - } - - Future _runSafely(Future Function() invoke) async { - try { - return await invoke(); - } on GrpcError catch (error) { - throw RunCodeError(error.message); - } on Exception catch (_) { - throw RunCodeError(null); - } - } - - /// Run Code request should use different urls for each sdk - /// instead of the default one, because we need to code - /// sdk services for it - grpc.PlaygroundServiceClient _createRunCodeClient(SDK? sdk) { - String apiClientURL = kApiClientURL; - if (sdk != null) { - apiClientURL = sdk.getRoute; - } - IisWorkaroundChannel channel = IisWorkaroundChannel.xhr( - Uri.parse(apiClientURL), - ); - return grpc.PlaygroundServiceClient(channel); - } - - grpc.RunCodeRequest _toGrpcRequest(RunCodeRequestWrapper request) { - return grpc.RunCodeRequest() - ..code = request.code - ..sdk = _getGrpcSdk(request.sdk) - ..pipelineOptions = pipelineOptionsToString(request.pipelineOptions); - } - - grpc.Sdk _getGrpcSdk(SDK sdk) { - switch (sdk) { - case SDK.java: - return grpc.Sdk.SDK_JAVA; - case SDK.go: - return grpc.Sdk.SDK_GO; - case SDK.python: - return grpc.Sdk.SDK_PYTHON; - case SDK.scio: - return grpc.Sdk.SDK_SCIO; - } - } - - RunCodeStatus _toClientStatus(grpc.Status status) { - switch (status) { - case grpc.Status.STATUS_UNSPECIFIED: - return RunCodeStatus.unspecified; - case grpc.Status.STATUS_VALIDATING: - case grpc.Status.STATUS_PREPARING: - return RunCodeStatus.preparation; - case grpc.Status.STATUS_COMPILING: - return RunCodeStatus.compiling; - case grpc.Status.STATUS_EXECUTING: - return RunCodeStatus.executing; - case grpc.Status.STATUS_CANCELED: - case grpc.Status.STATUS_FINISHED: - return RunCodeStatus.finished; - case grpc.Status.STATUS_COMPILE_ERROR: - return RunCodeStatus.compileError; - case grpc.Status.STATUS_RUN_TIMEOUT: - return RunCodeStatus.timeout; - case grpc.Status.STATUS_RUN_ERROR: - return RunCodeStatus.runError; - case grpc.Status.STATUS_VALIDATION_ERROR: - return RunCodeStatus.validationError; - case grpc.Status.STATUS_PREPARATION_ERROR: - return RunCodeStatus.preparationError; - case grpc.Status.STATUS_ERROR: - return RunCodeStatus.unknownError; - } - return RunCodeStatus.unspecified; - } - - OutputResponse _toOutputResponse(String response) { - return OutputResponse(replaceIncorrectSymbols(response)); - } -} 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 a438aac5baede..e368973cb64b5 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 @@ -22,13 +22,13 @@ 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:playground_components/playground_components.dart'; import 'package:url_launcher/url_launcher.dart'; const kDescriptionWidth = 300.0; class DescriptionPopover extends StatelessWidget { - final ExampleModel example; + final ExampleBase example; const DescriptionPopover({Key? key, required this.example}) : super(key: key); 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 5e935eb0328d4..8df888ed76fae 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 @@ -19,14 +19,13 @@ import 'package:aligned_dialog/aligned_dialog.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/examples/components/description_popover/description_popover.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:playground_components/playground_components.dart'; class DescriptionPopoverButton extends StatelessWidget { final BuildContext? parentContext; - final ExampleModel example; + final ExampleBase example; final Alignment followerAnchor; final Alignment targetAnchor; final void Function()? onOpen; @@ -52,7 +51,7 @@ class DescriptionPopoverButton extends StatelessWidget { splashRadius: kIconButtonSplashRadius, icon: Icon( Icons.info_outline_rounded, - color: ThemeColors.of(context).grey1Color, + color: Theme.of(context).extension()?.iconColor, ), tooltip: appLocale.exampleDescription, onPressed: () { @@ -69,7 +68,7 @@ class DescriptionPopoverButton extends StatelessWidget { void _showDescriptionPopover( BuildContext context, - ExampleModel example, + ExampleBase example, Alignment followerAnchor, Alignment targetAnchor, ) async { diff --git a/playground/frontend/lib/modules/examples/components/example_list/category_expansion_panel.dart b/playground/frontend/lib/modules/examples/components/example_list/category_expansion_panel.dart index c6dcf47271e61..049db9e5968e8 100644 --- a/playground/frontend/lib/modules/examples/components/example_list/category_expansion_panel.dart +++ b/playground/frontend/lib/modules/examples/components/example_list/category_expansion_panel.dart @@ -22,12 +22,12 @@ import 'package:expansion_widget/expansion_widget.dart'; import 'package:flutter/material.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/examples/components/example_list/expansion_panel_item.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:playground_components/playground_components.dart'; class CategoryExpansionPanel extends StatelessWidget { final String categoryName; final List examples; - final ExampleModel selectedExample; + final ExampleBase selectedExample; final AnimationController animationController; final OverlayEntry? dropdown; 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 index 9cae77f6073b4..584b4c1573163 100644 --- 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 @@ -18,14 +18,13 @@ 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/components/multifile_popover/multifile_popover_button.dart'; import 'package:playground/modules/examples/models/popover_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; -import '../multifile_popover/multifile_popover_button.dart'; - class ExampleItemActions extends StatelessWidget { - final ExampleModel example; + final ExampleBase example; final BuildContext parentContext; const ExampleItemActions( diff --git a/playground/frontend/lib/modules/examples/components/example_list/example_list.dart b/playground/frontend/lib/modules/examples/components/example_list/example_list.dart index 6b175e2692a19..1c9227d2f59e3 100644 --- a/playground/frontend/lib/modules/examples/components/example_list/example_list.dart +++ b/playground/frontend/lib/modules/examples/components/example_list/example_list.dart @@ -18,15 +18,15 @@ import 'package:flutter/material.dart'; import 'package:playground/modules/examples/components/examples_components.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; import 'package:playground/pages/playground/states/example_selector_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; class ExampleList extends StatelessWidget { final ScrollController controller; final AnimationController animationController; final OverlayEntry? dropdown; - final ExampleModel selectedExample; + final ExampleBase selectedExample; const ExampleList({ Key? key, @@ -50,7 +50,7 @@ class ExampleList extends StatelessWidget { itemCount: state.categories.length, itemBuilder: (context, index) => CategoryExpansionPanel( selectedExample: selectedExample, - categoryName: state.categories[index].name, + categoryName: state.categories[index].title, examples: state.categories[index].examples, animationController: animationController, dropdown: dropdown, 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 f212f3c07e4cd..b035e35d2d992 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 @@ -20,14 +20,12 @@ 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/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'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; class ExpansionPanelItem extends StatelessWidget { - final ExampleModel example; - final ExampleModel selectedExample; + final ExampleBase example; + final ExampleBase selectedExample; final AnimationController animationController; final OverlayEntry? dropdown; @@ -41,20 +39,21 @@ class ExpansionPanelItem extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, playgroundState, child) => MouseRegion( + return Consumer( + builder: (context, controller, child) => MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: () async { - if (playgroundState.selectedExample != example) { - _closeDropdown(playgroundState.exampleState); + if (controller.selectedExample != example) { + _closeDropdown(controller.exampleCache); AnalyticsService.get(context).trackSelectExample(example); final exampleWithInfo = - await playgroundState.exampleState.loadExampleInfo(example); + await controller.exampleCache.loadExampleInfo(example); // TODO: setCurrentSdk = false when we do // per-SDK output and run status. // Now using true to reset the output and run status. - playgroundState.setExample(exampleWithInfo, setCurrentSdk: true); + // https://github.com/apache/beam/issues/23248 + controller.setExample(exampleWithInfo, setCurrentSdk: true); } }, child: Container( @@ -83,9 +82,9 @@ class ExpansionPanelItem extends StatelessWidget { ); } - void _closeDropdown(ExampleState exampleState) { + void _closeDropdown(ExampleCache exampleCache) { animationController.reverse(); dropdown?.remove(); - exampleState.changeSelectorVisibility(); + exampleCache.changeSelectorVisibility(); } } diff --git a/playground/frontend/lib/modules/examples/components/filter/category_bubble.dart b/playground/frontend/lib/modules/examples/components/filter/category_bubble.dart index aabe00735ee6b..849f19db877cf 100644 --- a/playground/frontend/lib/modules/examples/components/filter/category_bubble.dart +++ b/playground/frontend/lib/modules/examples/components/filter/category_bubble.dart @@ -17,10 +17,8 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/config/theme.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; import 'package:playground/pages/playground/states/example_selector_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; class CategoryBubble extends StatelessWidget { @@ -35,46 +33,21 @@ class CategoryBubble extends StatelessWidget { @override Widget build(BuildContext context) { - return MouseRegion( - cursor: SystemMouseCursors.click, - child: Padding( - padding: const EdgeInsets.only(right: kMdSpacing), - child: Consumer( - builder: (context, state, child) { - final isSelected = type == state.selectedFilterType; + return Consumer( + builder: (context, state, child) { + final isSelected = type == state.selectedFilterType; - return GestureDetector( - onTap: () { - if (!isSelected) { - state.setSelectedFilterType(type); - state.sortCategories(); - } - }, - child: Container( - height: kContainerHeight, - padding: const EdgeInsets.symmetric(horizontal: kXlSpacing), - decoration: BoxDecoration( - color: isSelected - ? ThemeColors.of(context).primary - : ThemeColors.of(context).lightGreyColor, - borderRadius: BorderRadius.circular(kXlBorderRadius), - ), - child: Center( - child: Text( - name, - style: TextStyle( - color: isSelected - ? ThemeColors.of(context).primaryBackgroundTextColor - : ThemeColors.of(context) - .lightGreyBackgroundTextColor, - ), - ), - ), - ), - ); + return BubbleWidget( + isSelected: isSelected, + title: name, + onTap: () { + if (!isSelected) { + state.setSelectedFilterType(type); + state.sortCategories(); + } }, - ), - ), + ); + }, ); } } diff --git a/playground/frontend/lib/modules/examples/components/filter/type_filter.dart b/playground/frontend/lib/modules/examples/components/filter/type_filter.dart index c56f6fcde4151..a221999f3ce66 100644 --- a/playground/frontend/lib/modules/examples/components/filter/type_filter.dart +++ b/playground/frontend/lib/modules/examples/components/filter/type_filter.dart @@ -20,7 +20,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/examples/components/examples_components.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:playground_components/playground_components.dart'; class TypeFilter extends StatelessWidget { const TypeFilter({Key? key}) : super(key: key); diff --git a/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover.dart b/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover.dart index 6d90bc7bfd56d..c4fb93fa47065 100644 --- a/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover.dart +++ b/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover.dart @@ -22,13 +22,13 @@ 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:playground_components/playground_components.dart'; import 'package:url_launcher/url_launcher.dart'; const kMultifileWidth = 300.0; class MultifilePopover extends StatelessWidget { - final ExampleModel example; + final ExampleBase example; const MultifilePopover({Key? key, required this.example}) : super(key: key); diff --git a/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover_button.dart b/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover_button.dart index 069830c65c763..d530aa645ebe8 100644 --- a/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover_button.dart +++ b/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover_button.dart @@ -23,11 +23,11 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:playground/constants/assets.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/examples/components/multifile_popover/multifile_popover.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; +import 'package:playground_components/playground_components.dart'; class MultifilePopoverButton extends StatelessWidget { final BuildContext? parentContext; - final ExampleModel example; + final ExampleBase example; final Alignment followerAnchor; final Alignment targetAnchor; final void Function()? onOpen; @@ -67,7 +67,7 @@ class MultifilePopoverButton extends StatelessWidget { void _showMultifilePopover( BuildContext context, - ExampleModel example, + ExampleBase example, Alignment followerAnchor, Alignment targetAnchor, ) async { diff --git a/playground/frontend/lib/modules/examples/components/search_field/search_field.dart b/playground/frontend/lib/modules/examples/components/search_field/search_field.dart index 80939ac15554e..2095dfac1d23f 100644 --- a/playground/frontend/lib/modules/examples/components/search_field/search_field.dart +++ b/playground/frontend/lib/modules/examples/components/search_field/search_field.dart @@ -18,9 +18,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/pages/playground/states/example_selector_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; const double kContainerWidth = 376.0; @@ -34,8 +34,11 @@ class SearchField extends StatelessWidget { @override Widget build(BuildContext context) { + final borderColor = + Theme.of(context).extension()!.borderColor; + final OutlineInputBorder border = OutlineInputBorder( - borderSide: BorderSide(color: ThemeColors.of(context).lightGreyColor), + borderSide: BorderSide(color: borderColor), borderRadius: BorderRadius.circular(kMdBorderRadius), ); @@ -61,7 +64,7 @@ class SearchField extends StatelessWidget { ), child: Icon( Icons.search, - color: ThemeColors.of(context).lightGreyColor, + color: borderColor, size: kIconSizeMd, ), ), @@ -72,7 +75,7 @@ class SearchField extends StatelessWidget { hintText: AppLocalizations.of(context)!.search, contentPadding: const EdgeInsets.only(left: kLgSpacing), ), - cursorColor: ThemeColors.of(context).lightGreyColor, + cursorColor: borderColor, cursorWidth: kCursorSize, textAlignVertical: TextAlignVertical.center, onFieldSubmitted: (String filterText) => diff --git a/playground/frontend/lib/modules/examples/example_selector.dart b/playground/frontend/lib/modules/examples/example_selector.dart index f65c45bfacfdf..c08fdb3cea2d1 100644 --- a/playground/frontend/lib/modules/examples/example_selector.dart +++ b/playground/frontend/lib/modules/examples/example_selector.dart @@ -18,18 +18,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:playground/components/horizontal_divider/horizontal_divider.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/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:playground/utils/dropdown_utils.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -90,10 +86,10 @@ class _ExampleSelectorState extends State return Container( height: kContainerHeight, decoration: BoxDecoration( - color: ThemeColors.of(context).dropdownButton, + color: Theme.of(context).dividerColor, borderRadius: BorderRadius.circular(kSmBorderRadius), ), - child: Consumer( + child: Consumer( builder: (context, state, child) => TextButton( key: selectorKey, onPressed: () { @@ -111,7 +107,7 @@ class _ExampleSelectorState extends State alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, children: [ - Consumer( + Consumer( builder: (context, state, child) => Text(state.examplesTitle), ), const Icon(Icons.keyboard_arrow_down), @@ -130,12 +126,12 @@ class _ExampleSelectorState extends State return ChangeNotifierProvider( create: (context) => PopoverState(false), builder: (context, state) { - return Consumer( - builder: (context, playgroundState, child) => Stack( + return Consumer( + builder: (context, playgroundController, child) => Stack( children: [ OutsideClickHandler( onTap: () { - _closeDropdown(playgroundState.exampleState); + _closeDropdown(playgroundController.exampleCache); // handle description dialogs Navigator.of(context, rootNavigator: true) .popUntil((route) { @@ -145,9 +141,9 @@ class _ExampleSelectorState extends State ), ChangeNotifierProvider( create: (context) => ExampleSelectorState( - playgroundState, - playgroundState.exampleState - .getCategories(playgroundState.sdk), + playgroundController, + playgroundController.exampleCache + .getCategories(playgroundController.sdk), ), builder: (context, _) => Positioned( left: dropdownOffset.dx, @@ -166,7 +162,7 @@ class _ExampleSelectorState extends State ), child: _buildDropdownContent( context, - playgroundState, + playgroundController, ), ), ), @@ -184,13 +180,11 @@ class _ExampleSelectorState extends State Widget _buildDropdownContent( BuildContext context, - PlaygroundState playgroundState, + PlaygroundController playgroundController, ) { - if (playgroundState.exampleState.sdkCategories == null || - playgroundState.selectedExample == null) { - return const LoadingIndicator( - size: kMdLoadingIndicatorSize, - ); + if (playgroundController.exampleCache.categoryListsBySdk.isEmpty || + playgroundController.selectedExample == null) { + return const LoadingIndicator(); } return Column( @@ -199,11 +193,11 @@ class _ExampleSelectorState extends State const TypeFilter(), ExampleList( controller: scrollController, - selectedExample: playgroundState.selectedExample!, + selectedExample: playgroundController.selectedExample!, animationController: animationController, dropdown: examplesDropdown, ), - const HorizontalDivider(indent: kLgSpacing), + const BeamDivider(), SizedBox( width: double.infinity, child: TextButton( @@ -213,7 +207,7 @@ class _ExampleSelectorState extends State alignment: Alignment.centerLeft, child: Text( AppLocalizations.of(context)!.addExample, - style: TextStyle(color: ThemeColors.of(context).primary), + style: TextStyle(color: Theme.of(context).primaryColor), ), ), ), @@ -224,9 +218,9 @@ class _ExampleSelectorState extends State ); } - void _closeDropdown(ExampleState exampleState) { + void _closeDropdown(ExampleCache exampleCache) { animationController.reverse(); examplesDropdown?.remove(); - exampleState.changeSelectorVisibility(); + exampleCache.changeSelectorVisibility(); } } diff --git a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/catalog_default_example_loading_descriptor.dart b/playground/frontend/lib/modules/examples/models/example_loading_descriptors/catalog_default_example_loading_descriptor.dart deleted file mode 100644 index 5841e0e294c63..0000000000000 --- a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/catalog_default_example_loading_descriptor.dart +++ /dev/null @@ -1,44 +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:playground/modules/sdk/models/sdk.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_origin.dart'; - -class CatalogDefaultExampleLoadingDescriptor extends ExampleLoadingDescriptor { - final SDK sdk; - - const CatalogDefaultExampleLoadingDescriptor({ - required this.sdk, - }); - - @override - ExampleOrigin get origin => ExampleOrigin.catalogDefault; - - @override - int get hashCode => sdk.hashCode; - - @override - bool operator ==(Object other) { - return other is CatalogDefaultExampleLoadingDescriptor && sdk == other.sdk; - } - - // Only ContentExampleLoadingDescriptor is serialized now. - @override - Map toJson() => throw UnimplementedError(); -} diff --git a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory.dart b/playground/frontend/lib/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory.dart index ca9199f128554..7b3854d6f31f0 100644 --- a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory.dart +++ b/playground/frontend/lib/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory.dart @@ -19,18 +19,11 @@ import 'dart:convert'; import 'package:playground/constants/params.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/catalog_default_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/content_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/empty_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/examples_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/standard_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart'; import 'package:playground/modules/examples/models/example_token_type.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/playground_components.dart'; class ExamplesLoadingDescriptorFactory { - static const _defaultSdk = SDK.java; + static const _defaultSdk = Sdk.java; static ExamplesLoadingDescriptor fromUriParts({ required String path, @@ -61,7 +54,7 @@ class ExamplesLoadingDescriptorFactory { return null; } - final sdk = SDK.tryParse(params[kSdkParam]); + final sdk = Sdk.tryParse(params[kSdkParam]); return ExamplesLoadingDescriptor( descriptors: _parseMultipleInstantExamples(list, sdk), @@ -75,7 +68,7 @@ class ExamplesLoadingDescriptorFactory { static List _parseMultipleInstantExamples( List list, - SDK? sdk, + Sdk? sdk, ) { final result = []; @@ -132,7 +125,7 @@ class ExamplesLoadingDescriptorFactory { return null; } - final sdk = SDK.tryParse(params[kSdkParam]) ?? _defaultSdk; + final sdk = Sdk.tryParse(params[kSdkParam]) ?? _defaultSdk; return ExamplesLoadingDescriptor( descriptors: [ @@ -161,14 +154,14 @@ class ExamplesLoadingDescriptorFactory { return ExamplesLoadingDescriptor( descriptors: [ EmptyExampleLoadingDescriptor( - sdk: SDK.tryParse(params[kSdkParam]) ?? _defaultSdk, + sdk: Sdk.tryParse(params[kSdkParam]) ?? _defaultSdk, ), ], lazyLoadDescriptors: _emptyLazyLoadDescriptors, ); } - static Map> _getLazyLoadDescriptors() { + static Map> _getLazyLoadDescriptors() { if (isEmbedded()) { return _emptyLazyLoadDescriptors; } @@ -176,18 +169,18 @@ class ExamplesLoadingDescriptorFactory { return _defaultLazyLoadDescriptors; } - static Map> + static Map> get _emptyLazyLoadDescriptors { return { - for (final sdk in SDK.values) + for (final sdk in Sdk.known) sdk: [EmptyExampleLoadingDescriptor(sdk: sdk)] }; } - static Map> + static Map> get _defaultLazyLoadDescriptors { return { - for (final sdk in SDK.values) + for (final sdk in Sdk.known) sdk: [CatalogDefaultExampleLoadingDescriptor(sdk: sdk)] }; } diff --git a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/standard_example_loading_descriptor.dart b/playground/frontend/lib/modules/examples/models/example_loading_descriptors/standard_example_loading_descriptor.dart deleted file mode 100644 index a5adbff51ac61..0000000000000 --- a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/standard_example_loading_descriptor.dart +++ /dev/null @@ -1,46 +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:playground/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_origin.dart'; - -class StandardExampleLoadingDescriptor extends ExampleLoadingDescriptor { - final String path; - - const StandardExampleLoadingDescriptor({ - required this.path, - }); - - @override - ExampleOrigin get origin => ExampleOrigin.standard; - - @override - String toString() => '$origin-$path'; - - @override - int get hashCode => path.hashCode; - - @override - bool operator ==(Object other) { - return other is StandardExampleLoadingDescriptor && path == other.path; - } - - // Only ContentExampleLoadingDescriptor is serialized now. - @override - Map toJson() => throw UnimplementedError(); -} diff --git a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart b/playground/frontend/lib/modules/examples/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart deleted file mode 100644 index 26e58a8453fbb..0000000000000 --- a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart +++ /dev/null @@ -1,47 +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:playground/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_origin.dart'; - -class UserSharedExampleLoadingDescriptor extends ExampleLoadingDescriptor { - final String snippetId; - - const UserSharedExampleLoadingDescriptor({ - required this.snippetId, - }); - - @override - ExampleOrigin get origin => ExampleOrigin.userShared; - - @override - String toString() => '$origin-$snippetId'; - - @override - int get hashCode => snippetId.hashCode; - - @override - bool operator ==(Object other) { - return other is UserSharedExampleLoadingDescriptor && - snippetId == other.snippetId; - } - - // Only ContentExampleLoadingDescriptor is serialized now. - @override - Map toJson() => throw UnimplementedError(); -} diff --git a/playground/frontend/lib/modules/examples/models/example_token_type.dart b/playground/frontend/lib/modules/examples/models/example_token_type.dart index 789b1c3a179a3..48c7c58063588 100644 --- a/playground/frontend/lib/modules/examples/models/example_token_type.dart +++ b/playground/frontend/lib/modules/examples/models/example_token_type.dart @@ -16,7 +16,7 @@ * limitations under the License. */ -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/playground_components.dart'; enum ExampleTokenType { standard, @@ -24,7 +24,7 @@ enum ExampleTokenType { ; static ExampleTokenType fromToken(String token) { - final sdk = SDK.tryParseExamplePath(token); + final sdk = Sdk.tryParseExamplePath(token); if (sdk != null) { return standard; } diff --git a/playground/frontend/lib/modules/examples/repositories/example_client/example_client.dart b/playground/frontend/lib/modules/examples/repositories/example_client/example_client.dart deleted file mode 100644 index 039a567c5efa8..0000000000000 --- a/playground/frontend/lib/modules/examples/repositories/example_client/example_client.dart +++ /dev/null @@ -1,66 +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:playground/modules/editor/repository/code_repository/code_client/output_response.dart'; -import 'package:playground/modules/examples/repositories/models/get_snippet_request.dart'; -import 'package:playground/modules/examples/repositories/models/get_snippet_response.dart'; -import 'package:playground/modules/examples/repositories/models/get_example_code_response.dart'; -import 'package:playground/modules/examples/repositories/models/get_example_request.dart'; -import 'package:playground/modules/examples/repositories/models/get_example_response.dart'; -import 'package:playground/modules/examples/repositories/models/get_list_of_examples_request.dart'; -import 'package:playground/modules/examples/repositories/models/get_list_of_examples_response.dart'; -import 'package:playground/modules/examples/repositories/models/save_snippet_request.dart'; -import 'package:playground/modules/examples/repositories/models/save_snippet_response.dart'; - -abstract class ExampleClient { - Future getListOfExamples( - GetListOfExamplesRequestWrapper request, - ); - - Future getExampleSource( - GetExampleRequestWrapper request, - ); - - Future getDefaultExample( - GetExampleRequestWrapper request, - ); - - Future getExample( - GetExampleRequestWrapper request, - ); - - Future getExampleOutput( - GetExampleRequestWrapper request, - ); - - Future getExampleLogs( - GetExampleRequestWrapper request, - ); - - Future getExampleGraph( - GetExampleRequestWrapper request, - ); - - Future getSnippet( - GetSnippetRequestWrapper request, - ); - - Future saveSnippet( - SaveSnippetRequestWrapper request, - ); -} 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 deleted file mode 100644 index f4a11be58aae4..0000000000000 --- a/playground/frontend/lib/modules/examples/repositories/example_client/grpc_example_client.dart +++ /dev/null @@ -1,366 +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:grpc/grpc_web.dart'; -import 'package:playground/api/iis_workaround_channel.dart'; -import 'package:playground/api/v1/api.pbgrpc.dart' as grpc; -import 'package:playground/config.g.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/output_response.dart'; -import 'package:playground/modules/examples/models/category_model.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/modules/examples/repositories/example_client/example_client.dart'; -import 'package:playground/modules/examples/repositories/models/get_example_code_response.dart'; -import 'package:playground/modules/examples/repositories/models/get_example_request.dart'; -import 'package:playground/modules/examples/repositories/models/get_example_response.dart'; -import 'package:playground/modules/examples/repositories/models/get_list_of_examples_request.dart'; -import 'package:playground/modules/examples/repositories/models/get_list_of_examples_response.dart'; -import 'package:playground/modules/examples/repositories/models/get_snippet_request.dart'; -import 'package:playground/modules/examples/repositories/models/get_snippet_response.dart'; -import 'package:playground/modules/examples/repositories/models/save_snippet_request.dart'; -import 'package:playground/modules/examples/repositories/models/save_snippet_response.dart'; -import 'package:playground/modules/examples/repositories/models/shared_file_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; -import 'package:playground/utils/replace_incorrect_symbols.dart'; - -class GrpcExampleClient implements ExampleClient { - late final grpc.PlaygroundServiceClient _defaultClient; - - GrpcExampleClient() { - final channel = IisWorkaroundChannel.xhr( - Uri.parse(kApiClientURL), - ); - _defaultClient = grpc.PlaygroundServiceClient(channel); - } - - @override - Future getListOfExamples( - GetListOfExamplesRequestWrapper request, - ) { - return _runSafely( - () => _defaultClient - .getPrecompiledObjects( - _getListOfExamplesRequestToGrpcRequest(request)) - .then((response) => GetListOfExampleResponse( - _toClientCategories(response.sdkCategories))), - ); - } - - @override - Future getDefaultExample( - GetExampleRequestWrapper request, - ) { - return _runSafely( - () => _defaultClient - .getDefaultPrecompiledObject( - _getDefaultExampleRequestToGrpcRequest(request)) - .then( - (response) => GetExampleResponse( - _toExampleModel( - request.sdk, - response.precompiledObject, - ), - ), - ), - ); - } - - @override - Future getExample( - GetExampleRequestWrapper request, - ) { - return _runSafely( - () => _defaultClient - .getPrecompiledObject( - grpc.GetPrecompiledObjectRequest()..cloudPath = request.path) - .then( - (response) => GetExampleResponse( - _toExampleModel( - request.sdk, - response.precompiledObject, - ), - ), - ), - ); - } - - @override - Future getExampleSource( - GetExampleRequestWrapper request) { - return _runSafely( - () => _defaultClient - .getPrecompiledObjectCode( - _getExampleCodeRequestToGrpcRequest(request)) - .then((response) => - GetExampleCodeResponse(replaceIncorrectSymbols(response.code))), - ); - } - - @override - Future getExampleOutput(GetExampleRequestWrapper request) { - return _runSafely( - () => _defaultClient - .getPrecompiledObjectOutput( - _getExampleOutputRequestToGrpcRequest(request)) - .then((response) => - OutputResponse(replaceIncorrectSymbols(response.output))) - .catchError((err) { - print(err); - return OutputResponse(''); - }), - ); - } - - @override - Future getExampleLogs(GetExampleRequestWrapper request) { - return _runSafely( - () => _defaultClient - .getPrecompiledObjectLogs(_getExampleLogRequestToGrpcRequest(request)) - .then((response) => - OutputResponse(replaceIncorrectSymbols(response.output))) - .catchError((err) { - print(err); - return OutputResponse(''); - }), - ); - } - - @override - Future getExampleGraph(GetExampleRequestWrapper request) { - return _runSafely( - () => _defaultClient - .getPrecompiledObjectGraph( - _getExampleGraphRequestToGrpcRequest(request)) - .then((response) => OutputResponse(response.graph)) - .catchError((err) { - print(err); - return OutputResponse(''); - }), - ); - } - - @override - Future getSnippet( - GetSnippetRequestWrapper request, - ) { - return _runSafely( - () => _defaultClient - .getSnippet(_getSnippetRequestToGrpcRequest(request)) - .then( - (response) => GetSnippetResponse( - files: _convertToSharedFileList(response.files), - sdk: _getAppSdk(response.sdk), - pipelineOptions: response.pipelineOptions, - ), - ), - ); - } - - @override - Future saveSnippet( - SaveSnippetRequestWrapper request, - ) { - return _runSafely( - () => _defaultClient - .saveSnippet(_saveSnippetRequestToGrpcRequest(request)) - .then( - (response) => SaveSnippetResponse( - id: response.id, - ), - ), - ); - } - - Future _runSafely(Future Function() invoke) { - try { - return invoke(); - } on GrpcError catch (error) { - throw Exception(error.message); - } - } - - grpc.GetPrecompiledObjectsRequest _getListOfExamplesRequestToGrpcRequest( - GetListOfExamplesRequestWrapper request, - ) { - return grpc.GetPrecompiledObjectsRequest() - ..category = request.category ?? '' - ..sdk = request.sdk == null - ? grpc.Sdk.SDK_UNSPECIFIED - : _getGrpcSdk(request.sdk!); - } - - grpc.GetDefaultPrecompiledObjectRequest - _getDefaultExampleRequestToGrpcRequest( - GetExampleRequestWrapper request, - ) { - return grpc.GetDefaultPrecompiledObjectRequest() - ..sdk = _getGrpcSdk(request.sdk); - } - - grpc.GetPrecompiledObjectCodeRequest _getExampleCodeRequestToGrpcRequest( - GetExampleRequestWrapper request, - ) { - return grpc.GetPrecompiledObjectCodeRequest()..cloudPath = request.path; - } - - grpc.GetPrecompiledObjectOutputRequest _getExampleOutputRequestToGrpcRequest( - GetExampleRequestWrapper request, - ) { - return grpc.GetPrecompiledObjectOutputRequest()..cloudPath = request.path; - } - - grpc.GetPrecompiledObjectLogsRequest _getExampleLogRequestToGrpcRequest( - GetExampleRequestWrapper request, - ) { - return grpc.GetPrecompiledObjectLogsRequest()..cloudPath = request.path; - } - - grpc.GetPrecompiledObjectGraphRequest _getExampleGraphRequestToGrpcRequest( - GetExampleRequestWrapper request, - ) { - return grpc.GetPrecompiledObjectGraphRequest()..cloudPath = request.path; - } - - grpc.GetSnippetRequest _getSnippetRequestToGrpcRequest( - GetSnippetRequestWrapper request, - ) { - return grpc.GetSnippetRequest()..id = request.id; - } - - grpc.SaveSnippetRequest _saveSnippetRequestToGrpcRequest( - SaveSnippetRequestWrapper request, - ) { - return grpc.SaveSnippetRequest() - ..sdk = _getGrpcSdk(request.sdk) - ..pipelineOptions = request.pipelineOptions - ..files.addAll(_convertToSnippetFileList(request.files)); - } - - grpc.Sdk _getGrpcSdk(SDK sdk) { - switch (sdk) { - case SDK.java: - return grpc.Sdk.SDK_JAVA; - case SDK.go: - return grpc.Sdk.SDK_GO; - case SDK.python: - return grpc.Sdk.SDK_PYTHON; - case SDK.scio: - return grpc.Sdk.SDK_SCIO; - } - } - - SDK _getAppSdk(grpc.Sdk sdk) { - switch (sdk) { - case grpc.Sdk.SDK_JAVA: - return SDK.java; - case grpc.Sdk.SDK_GO: - return SDK.go; - case grpc.Sdk.SDK_PYTHON: - return SDK.python; - case grpc.Sdk.SDK_SCIO: - return SDK.scio; - default: - return SDK.java; - } - } - - ExampleType _exampleTypeFromString(grpc.PrecompiledObjectType type) { - switch (type) { - case grpc.PrecompiledObjectType.PRECOMPILED_OBJECT_TYPE_EXAMPLE: - return ExampleType.example; - case grpc.PrecompiledObjectType.PRECOMPILED_OBJECT_TYPE_KATA: - return ExampleType.kata; - case grpc.PrecompiledObjectType.PRECOMPILED_OBJECT_TYPE_UNIT_TEST: - return ExampleType.test; - case grpc.PrecompiledObjectType.PRECOMPILED_OBJECT_TYPE_UNSPECIFIED: - return ExampleType.all; - default: - return ExampleType.example; - } - } - - Map> _toClientCategories( - List response, - ) { - Map> sdkCategoriesMap = {}; - List>> entries = []; - for (var sdkMap in response) { - SDK sdk = _getAppSdk(sdkMap.sdk); - List categoriesForSdk = []; - for (var category in sdkMap.categories) { - List examples = category.precompiledObjects - .map((example) => _toExampleModel(sdk, example)) - .toList() - ..sort(); - categoriesForSdk.add(CategoryModel( - name: category.categoryName, - examples: examples, - )); - } - entries.add(MapEntry(sdk, categoriesForSdk..sort())); - } - sdkCategoriesMap.addEntries(entries); - return sdkCategoriesMap; - } - - ExampleModel _toExampleModel(SDK sdk, grpc.PrecompiledObject example) { - return ExampleModel( - sdk: sdk, - name: example.name, - description: example.description, - type: _exampleTypeFromString(example.type), - path: example.cloudPath, - contextLine: example.contextLine, - pipelineOptions: example.pipelineOptions, - isMultiFile: example.multifile, - link: example.link, - ); - } - - List _convertToSharedFileList( - List snippetFileList, - ) { - final sharedFilesList = []; - - for (grpc.SnippetFile item in snippetFileList) { - sharedFilesList.add(SharedFile( - code: item.content, - isMain: item.isMain, - name: item.name, - )); - } - - return sharedFilesList; - } - - List _convertToSnippetFileList( - List sharedFilesList, - ) { - final snippetFileList = []; - - for (SharedFile item in sharedFilesList) { - snippetFileList.add( - grpc.SnippetFile() - ..name = item.name - ..isMain = true - ..content = item.code, - ); - } - - return snippetFileList; - } -} diff --git a/playground/frontend/lib/modules/examples/repositories/example_repository.dart b/playground/frontend/lib/modules/examples/repositories/example_repository.dart deleted file mode 100644 index 65c09f885fbab..0000000000000 --- a/playground/frontend/lib/modules/examples/repositories/example_repository.dart +++ /dev/null @@ -1,98 +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:playground/modules/examples/models/category_model.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/modules/examples/repositories/example_client/example_client.dart'; -import 'package:playground/modules/examples/repositories/models/get_snippet_request.dart'; -import 'package:playground/modules/examples/repositories/models/get_snippet_response.dart'; -import 'package:playground/modules/examples/repositories/models/get_example_request.dart'; -import 'package:playground/modules/examples/repositories/models/get_list_of_examples_request.dart'; -import 'package:playground/modules/examples/repositories/models/save_snippet_request.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; - -class ExampleRepository { - late final ExampleClient _client; - - ExampleRepository(ExampleClient client) { - _client = client; - } - - Future>> getListOfExamples( - GetListOfExamplesRequestWrapper request, - ) async { - final result = await _client.getListOfExamples(request); - return result.categories; - } - - Future getDefaultExample( - GetExampleRequestWrapper request, - ) async { - final result = await _client.getDefaultExample(request); - return result.example; - } - - Future getExampleSource( - GetExampleRequestWrapper request, - ) async { - final result = await _client.getExampleSource(request); - return result.code; - } - - Future getExampleOutput( - GetExampleRequestWrapper request, - ) async { - final result = await _client.getExampleOutput(request); - return result.output; - } - - Future getExampleLogs( - GetExampleRequestWrapper request, - ) async { - final result = await _client.getExampleLogs(request); - return result.output; - } - - Future getExampleGraph( - GetExampleRequestWrapper request, - ) async { - final result = await _client.getExampleGraph(request); - return result.output; - } - - Future getExample( - GetExampleRequestWrapper request, - ) async { - final result = await _client.getExample(request); - return result.example; - } - - Future getSnippet( - GetSnippetRequestWrapper request, - ) async { - final result = await _client.getSnippet(request); - return result; - } - - Future saveSnippet( - SaveSnippetRequestWrapper request, - ) async { - final result = await _client.saveSnippet(request); - return result.id; - } -} diff --git a/playground/frontend/lib/modules/examples/repositories/models/get_example_request.dart b/playground/frontend/lib/modules/examples/repositories/models/get_example_request.dart deleted file mode 100644 index da87f09b2881a..0000000000000 --- a/playground/frontend/lib/modules/examples/repositories/models/get_example_request.dart +++ /dev/null @@ -1,37 +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:flutter/cupertino.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; - -class GetExampleRequestWrapper { - final String path; - final SDK sdk; - - GetExampleRequestWrapper(this.path, this.sdk); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is GetExampleRequestWrapper && - path == other.path && - sdk == other.sdk; - - @override - int get hashCode => hashValues(path.hashCode, sdk.hashCode); -} diff --git a/playground/frontend/lib/modules/examples/repositories/models/get_list_of_examples_request.dart b/playground/frontend/lib/modules/examples/repositories/models/get_list_of_examples_request.dart deleted file mode 100644 index 0d39df522d5c0..0000000000000 --- a/playground/frontend/lib/modules/examples/repositories/models/get_list_of_examples_request.dart +++ /dev/null @@ -1,37 +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:flutter/cupertino.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; - -class GetListOfExamplesRequestWrapper { - final SDK? sdk; - final String? category; - - GetListOfExamplesRequestWrapper({required this.sdk, required this.category}); - - @override - bool operator ==(Object other) => - identical(this, other) || - other is GetListOfExamplesRequestWrapper && - category == other.category && - sdk == other.sdk; - - @override - int get hashCode => hashValues(category.hashCode, sdk.hashCode); -} diff --git a/playground/frontend/lib/modules/messages/handlers/messages_handler.dart b/playground/frontend/lib/modules/messages/handlers/messages_handler.dart index 3a4047afbb523..12538d7daecb9 100644 --- a/playground/frontend/lib/modules/messages/handlers/messages_handler.dart +++ b/playground/frontend/lib/modules/messages/handlers/messages_handler.dart @@ -20,16 +20,16 @@ import 'package:playground/modules/messages/handlers/abstract_message_handler.da import 'package:playground/modules/messages/handlers/set_content_message_handler.dart'; import 'package:playground/modules/messages/handlers/set_sdk_message_handler.dart'; import 'package:playground/modules/messages/models/abstract_message.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; class MessagesHandler extends AbstractMessageHandler { final List handlers; MessagesHandler({ - required PlaygroundState playgroundState, + required PlaygroundController playgroundController, }) : handlers = [ - SetContentMessageHandler(playgroundState: playgroundState), - SetSdkMessageHandler(playgroundState: playgroundState), + SetContentMessageHandler(playgroundController: playgroundController), + SetSdkMessageHandler(playgroundController: playgroundController), ]; @override diff --git a/playground/frontend/lib/modules/messages/handlers/set_content_message_handler.dart b/playground/frontend/lib/modules/messages/handlers/set_content_message_handler.dart index 1e16e397f6e0d..c2424b38af649 100644 --- a/playground/frontend/lib/modules/messages/handlers/set_content_message_handler.dart +++ b/playground/frontend/lib/modules/messages/handlers/set_content_message_handler.dart @@ -19,13 +19,13 @@ import 'package:playground/modules/messages/handlers/abstract_message_handler.dart'; import 'package:playground/modules/messages/models/abstract_message.dart'; import 'package:playground/modules/messages/models/set_content_message.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; class SetContentMessageHandler extends AbstractMessageHandler { - final PlaygroundState playgroundState; + final PlaygroundController playgroundController; const SetContentMessageHandler({ - required this.playgroundState, + required this.playgroundController, }); @override @@ -39,6 +39,6 @@ class SetContentMessageHandler extends AbstractMessageHandler { } void _handle(SetContentMessage message) { - playgroundState.examplesLoader.load(message.descriptor); + playgroundController.examplesLoader.load(message.descriptor); } } diff --git a/playground/frontend/lib/modules/messages/handlers/set_sdk_message_handler.dart b/playground/frontend/lib/modules/messages/handlers/set_sdk_message_handler.dart index 9a2595f9215c2..2d066a9e8462a 100644 --- a/playground/frontend/lib/modules/messages/handlers/set_sdk_message_handler.dart +++ b/playground/frontend/lib/modules/messages/handlers/set_sdk_message_handler.dart @@ -19,13 +19,13 @@ import 'package:playground/modules/messages/handlers/abstract_message_handler.dart'; import 'package:playground/modules/messages/models/abstract_message.dart'; import 'package:playground/modules/messages/models/set_sdk_message.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; class SetSdkMessageHandler extends AbstractMessageHandler { - final PlaygroundState playgroundState; + final PlaygroundController playgroundController; const SetSdkMessageHandler({ - required this.playgroundState, + required this.playgroundController, }); @override @@ -39,6 +39,6 @@ class SetSdkMessageHandler extends AbstractMessageHandler { } void _handle(SetSdkMessage message) { - playgroundState.setSdk(message.sdk); + playgroundController.setSdk(message.sdk); } } diff --git a/playground/frontend/lib/modules/messages/models/set_content_message.dart b/playground/frontend/lib/modules/messages/models/set_content_message.dart index 707bf5deb68b3..fd8e1a04ba8f3 100644 --- a/playground/frontend/lib/modules/messages/models/set_content_message.dart +++ b/playground/frontend/lib/modules/messages/models/set_content_message.dart @@ -16,9 +16,9 @@ * limitations under the License. */ -import 'package:playground/modules/examples/models/example_loading_descriptors/examples_loading_descriptor.dart'; import 'package:playground/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory.dart'; import 'package:playground/modules/messages/models/abstract_message.dart'; +import 'package:playground_components/playground_components.dart'; /// A message that sets content for multiple snippets. /// diff --git a/playground/frontend/lib/modules/messages/models/set_sdk_message.dart b/playground/frontend/lib/modules/messages/models/set_sdk_message.dart index ed195fd8013c8..af3841afadfb3 100644 --- a/playground/frontend/lib/modules/messages/models/set_sdk_message.dart +++ b/playground/frontend/lib/modules/messages/models/set_sdk_message.dart @@ -17,13 +17,13 @@ */ import 'package:playground/modules/messages/models/abstract_message.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/playground_components.dart'; /// A message that switches the SDK. /// /// Sent to iframes by Beam documentation HTML when the language is switched. class SetSdkMessage extends AbstractMessage { - final SDK sdk; + final Sdk sdk; static const type = 'SetSdk'; @@ -36,7 +36,7 @@ class SetSdkMessage extends AbstractMessage { return null; } - final sdk = SDK.tryParse(map['sdk']); + final sdk = Sdk.tryParse(map['sdk']); if (sdk == null) { return null; } @@ -63,7 +63,7 @@ class SetSdkMessage extends AbstractMessage { @override Map toJson() { return { - 'sdk': sdk.name, + 'sdk': sdk.id, }; } } diff --git a/playground/frontend/lib/modules/output/components/output_area.dart b/playground/frontend/lib/modules/output/components/output_area.dart deleted file mode 100644 index 1492124ef3564..0000000000000 --- a/playground/frontend/lib/modules/output/components/output_area.dart +++ /dev/null @@ -1,74 +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:flutter/material.dart'; -import 'package:playground/modules/graph/graph_builder/painters/graph_painter.dart'; -import 'package:playground/modules/output/components/graph.dart'; -import 'package:playground/modules/output/components/output_result.dart'; -import 'package:playground/modules/output/models/output_placement.dart'; -import 'package:playground/modules/output/models/output_placement_state.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; -import 'package:provider/provider.dart'; - -class OutputArea extends StatelessWidget { - final TabController tabController; - final bool showGraph; - - const OutputArea({ - Key? key, - required this.tabController, - required this.showGraph, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - color: Theme.of(context).backgroundColor, - child: Consumer2( - builder: (context, playgroundState, placementState, child) { - final sdk = playgroundState.sdk; - - return TabBarView( - controller: tabController, - physics: const NeverScrollableScrollPhysics(), - children: [ - OutputResult( - text: playgroundState.outputResult, - isSelected: tabController.index == 0, - ), - if (showGraph) - sdk == null - ? Container() - : GraphTab( - graph: playgroundState.result?.graph ?? '', - sdk: sdk, - direction: _getGraphDirection(placementState.placement), - ), - ], - ); - }, - ), - ); - } - - GraphDirection _getGraphDirection(OutputPlacement placement) { - return placement == OutputPlacement.bottom - ? GraphDirection.horizontal - : GraphDirection.vertical; - } -} diff --git a/playground/frontend/lib/modules/output/components/output_header/result_filter_bubble.dart b/playground/frontend/lib/modules/output/components/output_header/result_filter_bubble.dart deleted file mode 100644 index d4401e7756131..0000000000000 --- a/playground/frontend/lib/modules/output/components/output_header/result_filter_bubble.dart +++ /dev/null @@ -1,80 +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:flutter/material.dart'; -import 'package:playground/config/theme.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/examples/models/outputs_model.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; -import 'package:provider/provider.dart'; - -class ResultFilterBubble extends StatelessWidget { - final OutputType type; - final String name; - - const ResultFilterBubble({ - Key? key, - required this.type, - required this.name, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return MouseRegion( - cursor: SystemMouseCursors.click, - child: Padding( - padding: const EdgeInsets.only(right: kMdSpacing), - child: Consumer( - builder: (context, state, child) { - final isSelected = type == state.selectedOutputFilterType; - - return GestureDetector( - onTap: () { - if (!isSelected) { - state.setSelectedOutputFilterType(type); - state.filterOutput(type); - } - }, - child: Container( - height: kContainerHeight, - padding: const EdgeInsets.symmetric(horizontal: kXlSpacing), - decoration: BoxDecoration( - color: isSelected - ? ThemeColors.of(context).primary - : ThemeColors.of(context).lightGreyColor, - borderRadius: BorderRadius.circular(kXlBorderRadius), - ), - child: Center( - child: Text( - name, - style: TextStyle( - color: isSelected - ? ThemeColors.of(context).primaryBackgroundTextColor - : ThemeColors.of(context) - .lightGreyBackgroundTextColor, - ), - ), - ), - ), - ); - }, - ), - ), - ); - } -} diff --git a/playground/frontend/lib/modules/output/components/output_header/result_filter_popover.dart b/playground/frontend/lib/modules/output/components/output_header/result_filter_popover.dart deleted file mode 100644 index d93d79e5b1fc0..0000000000000 --- a/playground/frontend/lib/modules/output/components/output_header/result_filter_popover.dart +++ /dev/null @@ -1,75 +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:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/examples/models/outputs_model.dart'; -import 'package:playground/modules/output/components/output_header/result_filter_bubble.dart'; - -const kPopoverWidth = 240.0; -const kPopoverPadding = 50.0; - -class ResultFilterPopover extends StatelessWidget { - const ResultFilterPopover({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - AppLocalizations appLocale = AppLocalizations.of(context)!; - - return Padding( - padding: const EdgeInsets.only(top: kPopoverPadding), - child: SizedBox( - width: kPopoverWidth, - child: Card( - child: Padding( - padding: const EdgeInsets.all(kMdSpacing), - child: Wrap( - runSpacing: kMdSpacing, - children: [ - Text(appLocale.displayAtThisTab), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: kSmSpacing, - vertical: kSmSpacing, - ), - child: Row( - children: [ - ResultFilterBubble( - type: OutputType.all, - name: appLocale.all, - ), - ResultFilterBubble( - type: OutputType.log, - name: appLocale.log, - ), - ResultFilterBubble( - type: OutputType.output, - name: appLocale.output, - ), - ], - ), - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/playground/frontend/lib/modules/output/models/output_placement.dart b/playground/frontend/lib/modules/output/models/output_placement.dart index d15c9d36c3843..642553751ccda 100644 --- a/playground/frontend/lib/modules/output/models/output_placement.dart +++ b/playground/frontend/lib/modules/output/models/output_placement.dart @@ -24,6 +24,13 @@ enum OutputPlacement { right, left, bottom, + ; + + Axis get graphDirection { + return this == OutputPlacement.bottom + ? Axis.horizontal + : Axis.vertical; + } } extension OutputPlacementToIcon on OutputPlacement { diff --git a/playground/frontend/lib/modules/sdk/components/sdk_selector.dart b/playground/frontend/lib/modules/sdk/components/sdk_selector.dart index c8197e8acbc3d..c4c1656a17483 100644 --- a/playground/frontend/lib/modules/sdk/components/sdk_selector.dart +++ b/playground/frontend/lib/modules/sdk/components/sdk_selector.dart @@ -21,8 +21,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:playground/components/dropdown_button/dropdown_button.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/sdk/components/sdk_selector_row.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; const kEmptyExampleName = 'Catalog'; @@ -31,8 +30,8 @@ const double kWidth = 150; const double kHeight = 172; class SDKSelector extends StatelessWidget { - final SDK? value; - final ValueChanged onChanged; + final Sdk? value; + final ValueChanged onChanged; const SDKSelector({ Key? key, @@ -45,35 +44,35 @@ class SDKSelector extends StatelessWidget { final localizations = AppLocalizations.of(context)!; final text = value == null ? localizations.selectSdkPlaceholder - : 'SDK: ${value?.displayName}'; + : 'SDK: ${value?.title}'; return Semantics( container: true, button: true, label: localizations.selectSdkDropdownSemantics, - child: AppDropdownButton( - buttonText: Text(text), - createDropdown: (close) => Column( - children: [ - const SizedBox(height: kMdSpacing), - ...SDK.values.map((SDK value) { - return SizedBox( - width: double.infinity, - child: Consumer( - builder: (context, state, child) => SdkSelectorRow( + child: Consumer( + builder: (context, controller, child) => AppDropdownButton( + buttonText: Text(text), + createDropdown: (close) => Column( + children: [ + const SizedBox(height: kMdSpacing), + ...Sdk.known.map((Sdk value) { + return SizedBox( + width: double.infinity, + child: SdkSelectorRow( sdk: value, onSelect: () { close(); onChanged(value); }, ), - ), - ); - }), - ], + ); + }), + ], + ), + width: kWidth, + height: kHeight, ), - width: kWidth, - height: kHeight, ), ); } diff --git a/playground/frontend/lib/modules/sdk/components/sdk_selector_row.dart b/playground/frontend/lib/modules/sdk/components/sdk_selector_row.dart index d66dda2dee7f7..7993723bf25dc 100644 --- a/playground/frontend/lib/modules/sdk/components/sdk_selector_row.dart +++ b/playground/frontend/lib/modules/sdk/components/sdk_selector_row.dart @@ -19,10 +19,10 @@ import 'package:flutter/material.dart'; import 'package:playground/constants/font_weight.dart'; import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/playground_components.dart'; class SdkSelectorRow extends StatelessWidget { - final SDK sdk; + final Sdk sdk; final VoidCallback onSelect; const SdkSelectorRow({ @@ -48,7 +48,7 @@ class SdkSelectorRow extends StatelessWidget { onPressed: onSelect, child: Padding( padding: const EdgeInsets.all(kLgSpacing), - child: Text(sdk.displayName), + child: Text(sdk.title), ), ); } diff --git a/playground/frontend/lib/modules/sdk/models/sdk.dart b/playground/frontend/lib/modules/sdk/models/sdk.dart deleted file mode 100644 index ef1fd6f57283a..0000000000000 --- a/playground/frontend/lib/modules/sdk/models/sdk.dart +++ /dev/null @@ -1,117 +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:highlight/highlight.dart'; -import 'package:highlight/languages/go.dart'; -import 'package:highlight/languages/java.dart'; -import 'package:highlight/languages/python.dart'; -import 'package:highlight/languages/scala.dart'; -import 'package:playground/config.g.dart'; - -enum SDK { - java, - go, - python, - scio, - ; - - /// A temporary solution while we wait for the backend to add - /// sdk in example responses. - static SDK? tryParseExamplePath(String? path) { - if (path == null) { - return null; - } - - if (path.startsWith('SDK_JAVA')) { - return java; - } - - if (path.startsWith('SDK_GO')) { - return go; - } - - if (path.startsWith('SDK_PYTHON')) { - return python; - } - - if (path.startsWith('SDK_SCIO')) { - return scio; - } - - return null; - } - - static SDK? tryParse(Object? value) { - if (value is! String) { - return null; - } - - try { - return values.byName(value); - } catch (ex) { - return null; - } - } -} - -extension SDKToString on SDK { - String get displayName { - switch (this) { - case SDK.go: - return 'Go'; - case SDK.java: - return 'Java'; - case SDK.python: - return 'Python'; - case SDK.scio: - return 'SCIO'; - } - } -} - -extension SdkToRoute on SDK { - String get getRoute { - switch (this) { - case SDK.java: - return kApiJavaClientURL; - case SDK.go: - return kApiGoClientURL; - case SDK.python: - return kApiPythonClientURL; - case SDK.scio: - return kApiScioClientURL; - default: - return ''; - } - } -} - -extension SdkToHighlightMode on SDK { - Mode get highlightMode { - switch (this) { - case SDK.java: - return java; - case SDK.go: - return go; - case SDK.python: - return python; - case SDK.scio: - return scala; - } - } -} diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcut_row.dart b/playground/frontend/lib/modules/shortcuts/components/shortcut_row.dart index 538c20cdecfa4..27cd343570380 100644 --- a/playground/frontend/lib/modules/shortcuts/components/shortcut_row.dart +++ b/playground/frontend/lib/modules/shortcuts/components/shortcut_row.dart @@ -18,11 +18,10 @@ import 'package:flutter/material.dart'; import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/shortcuts/models/shortcut.dart'; -import 'package:playground/modules/shortcuts/utils/shortcuts_display_name.dart'; +import 'package:playground_components/playground_components.dart'; class ShortcutRow extends StatelessWidget { - final Shortcut shortcut; + final BeamShortcut shortcut; const ShortcutRow({Key? key, required this.shortcut}) : super(key: key); @@ -39,7 +38,7 @@ class ShortcutRow extends StatelessWidget { ), padding: const EdgeInsets.all(kMdSpacing), child: Text( - getShortcutDisplayName(shortcut), + shortcut.title, style: TextStyle(color: primaryColor), ), ), diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcuts_manager.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_manager.dart index 32d3ce26151f6..5a9f1bc184537 100644 --- a/playground/frontend/lib/modules/shortcuts/components/shortcuts_manager.dart +++ b/playground/frontend/lib/modules/shortcuts/components/shortcuts_manager.dart @@ -17,11 +17,11 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/modules/shortcuts/models/shortcut.dart'; +import 'package:playground_components/playground_components.dart'; class ShortcutsManager extends StatelessWidget { final Widget child; - final List shortcuts; + final List shortcuts; const ShortcutsManager({ Key? key, diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart index c92c2748756f7..1f334327f6b05 100644 --- a/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart +++ b/playground/frontend/lib/modules/shortcuts/components/shortcuts_modal.dart @@ -16,12 +16,14 @@ * 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; @@ -29,7 +31,11 @@ const kButtonHeight = 40.0; const kDialogPadding = 40.0; class ShortcutsModal extends StatelessWidget { - const ShortcutsModal({Key? key}) : super(key: key); + final PlaygroundController playgroundController; + + const ShortcutsModal({ + required this.playgroundController, + }); @override Widget build(BuildContext context) { @@ -50,7 +56,10 @@ class ShortcutsModal extends StatelessWidget { crossAxisAlignment: WrapCrossAlignment.start, runSpacing: kXlSpacing, children: [ - ...globalShortcuts.map( + ...[ + ...playgroundController.shortcuts, + ...globalShortcuts, + ].map( (shortcut) => Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -58,7 +67,7 @@ class ShortcutsModal extends StatelessWidget { Expanded( flex: 3, child: Text( - localize(context, shortcut.name), + shortcut.actionIntent.slug.tr(), style: const TextStyle(fontWeight: kBoldWeight), ), ), @@ -84,21 +93,4 @@ class ShortcutsModal extends StatelessWidget { ], ); } - - String localize(BuildContext context, String shortcutName) { - AppLocalizations appLocale = AppLocalizations.of(context)!; - - switch(shortcutName) { - case 'Run': - return appLocale.run; - case 'Reset': - return appLocale.reset; - case 'Clear Output': - return appLocale.clearOutput; - case 'New Example': - return appLocale.newExample; - default: - return shortcutName; - } - } } diff --git a/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart b/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart index bf3b14e7a73b0..03797eaff3a03 100644 --- a/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart +++ b/playground/frontend/lib/modules/shortcuts/constants/global_shortcuts.dart @@ -18,93 +18,44 @@ 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:playground_components/playground_components.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 BeamIntent { + const ClearOutputIntent() : super(slug: 'intents.playground.clearOutput'); } -class ClearOutputIntent extends Intent { - const ClearOutputIntent(); +class NewExampleIntent extends BeamIntent { + const NewExampleIntent() : super(slug: 'intents.playground.newExample'); } -class NewExampleIntent extends Intent { - const NewExampleIntent(); -} - -final kRunShortcut = Shortcut( - shortcuts: LogicalKeySet( - LogicalKeyboardKey.meta, - LogicalKeyboardKey.enter, - ), - actionIntent: const RunIntent(), - name: kRunText, - createAction: (BuildContext context) => CallbackAction( - onInvoke: (_) => Provider.of( - context, - listen: false, - ).runCode(), - ), -); - -final kResetShortcut = Shortcut( - shortcuts: LogicalKeySet( - LogicalKeyboardKey.meta, - LogicalKeyboardKey.shift, - LogicalKeyboardKey.keyE, - ), - actionIntent: const ResetIntent(), - name: kResetText, - createAction: (BuildContext context) => CallbackAction( - onInvoke: (_) => Provider.of( - context, - listen: false, - ).reset(), - ), -); - -final kClearOutputShortcut = Shortcut( +final kClearOutputShortcut = BeamShortcut( shortcuts: LogicalKeySet( LogicalKeyboardKey.meta, LogicalKeyboardKey.keyB, ), actionIntent: const ClearOutputIntent(), - name: kClearOutputText, createAction: (BuildContext context) => CallbackAction( - onInvoke: (_) => Provider.of( + onInvoke: (_) => Provider.of( context, listen: false, ).clearOutput(), ), ); -final kNewExampleShortcut = Shortcut( +final kNewExampleShortcut = BeamShortcut( shortcuts: LogicalKeySet( LogicalKeyboardKey.meta, LogicalKeyboardKey.keyM, ), actionIntent: const NewExampleIntent(), - name: kNewExampleText, createAction: (_) => CallbackAction( onInvoke: (_) => launchUrl(Uri.parse('/')), ), ); -final List globalShortcuts = [ - kRunShortcut, - kResetShortcut, +final List globalShortcuts = [ kClearOutputShortcut, kNewExampleShortcut, ]; diff --git a/playground/frontend/lib/pages/embedded_playground/components/embedded_actions.dart b/playground/frontend/lib/pages/embedded_playground/components/embedded_actions.dart index 5e8e56bf4faa5..10ccf96ba4f84 100644 --- a/playground/frontend/lib/pages/embedded_playground/components/embedded_actions.dart +++ b/playground/frontend/lib/pages/embedded_playground/components/embedded_actions.dart @@ -27,8 +27,8 @@ import 'package:playground/constants/assets.dart'; import 'package:playground/constants/params.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/messages/models/set_content_message.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; import 'package:playground/utils/javascript_post_message.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; const kTryPlaygroundButtonWidth = 200.0; @@ -44,29 +44,29 @@ class EmbeddedActions extends StatelessWidget { child: SizedBox( width: kTryPlaygroundButtonWidth, height: kTryPlaygroundButtonHeight, - child: Consumer( - builder: (context, state, child) => ElevatedButton.icon( + child: Consumer( + builder: (context, controller, child) => ElevatedButton.icon( icon: SvgPicture.asset(kLinkIconAsset), label: Text(AppLocalizations.of(context)!.tryInPlayground), - onPressed: () => _openStandalonePlayground(state), + onPressed: () => _openStandalonePlayground(controller), ), ), ), ); } - void _openStandalonePlayground(PlaygroundState state) { + void _openStandalonePlayground(PlaygroundController controller) { // The empty list forces the parsing of EmptyExampleLoadingDescriptor // and prevents the glimpse of the default catalog example. final window = html.window.open( - '/?$kExamplesParam=[]&$kSdkParam=${state.sdk?.name}', + '/?$kExamplesParam=[]&$kSdkParam=${controller.sdk?.id}', '', ); javaScriptPostMessageRepeated( window, SetContentMessage( - descriptor: state.getLoadingDescriptor(), + descriptor: controller.getLoadingDescriptor(), ), ); } diff --git a/playground/frontend/lib/pages/embedded_playground/components/embedded_appbar_title.dart b/playground/frontend/lib/pages/embedded_playground/components/embedded_appbar_title.dart index fd981f5987513..112bea826ddb4 100644 --- a/playground/frontend/lib/pages/embedded_playground/components/embedded_appbar_title.dart +++ b/playground/frontend/lib/pages/embedded_playground/components/embedded_appbar_title.dart @@ -18,16 +18,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:playground/components/toggle_theme_button/toggle_theme_icon_button.dart'; +import 'package:playground/components/playground_run_or_cancel_button.dart'; import 'package:playground/constants/assets.dart'; import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/analytics/analytics_service.dart'; -import 'package:playground/modules/editor/components/run_button.dart'; -import 'package:playground/modules/notifications/components/notification.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; -import 'package:playground/utils/analytics_utils.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; class EmbeddedAppBarTitle extends StatelessWidget { @@ -35,50 +30,19 @@ class EmbeddedAppBarTitle extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, state, child) => Wrap( + return Consumer( + builder: (context, controller, child) => Wrap( crossAxisAlignment: WrapCrossAlignment.center, spacing: kXlSpacing, children: [ - RunButton( - isRunning: state.isCodeRunning, - cancelRun: () { - final exampleName = getAnalyticsExampleName(state); - final analyticsService = AnalyticsService.get(context); - analyticsService.trackClickCancelRunEvent(exampleName); - - state.cancelRun().catchError( - (_) => NotificationManager.showError( - context, - AppLocalizations.of(context)!.runCode, - AppLocalizations.of(context)!.cancelExecution, - ), - ); - }, - runCode: () { - final stopwatch = Stopwatch()..start(); - final exampleName = getAnalyticsExampleName(state); - final analyticsService = AnalyticsService.get(context); - - state.runCode( - onFinish: () { - analyticsService.trackRunTimeEvent( - exampleName, - stopwatch.elapsedMilliseconds, - ); - }, - ); - analyticsService.trackClickRunEvent(exampleName); - }, - ), + const PlaygroundRunOrCancelButton(), const ToggleThemeIconButton(), IconButton( iconSize: kIconSizeLg, splashRadius: kIconButtonSplashRadius, icon: SvgPicture.asset(kCopyIconAsset), onPressed: () { - final source = - Provider.of(context, listen: false).source; + final source = controller.source; Clipboard.setData(ClipboardData(text: source)); }, ), diff --git a/playground/frontend/lib/pages/embedded_playground/components/embedded_editor.dart b/playground/frontend/lib/pages/embedded_playground/components/embedded_editor.dart index 9728bdb1f4384..94000034f2999 100644 --- a/playground/frontend/lib/pages/embedded_playground/components/embedded_editor.dart +++ b/playground/frontend/lib/pages/embedded_playground/components/embedded_editor.dart @@ -17,10 +17,7 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/components/loading_indicator/loading_indicator.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/editor/components/editor_textarea.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; class EmbeddedEditor extends StatelessWidget { @@ -30,21 +27,17 @@ class EmbeddedEditor extends StatelessWidget { @override Widget build(BuildContext context) { - final state = Provider.of(context); - final controller = state.snippetEditingController; + final controller = Provider.of(context); + final snippetController = controller.snippetEditingController; - if (controller == null) { - return const LoadingIndicator(size: kLgLoadingIndicatorSize); + if (snippetController == null) { + return const LoadingIndicator(); } - return EditorTextArea( - codeController: controller.codeController, - key: ValueKey(state.selectedExample), - enabled: true, - sdk: controller.sdk, - example: state.selectedExample, + return SnippetEditor( + controller: snippetController, isEditable: isEditable, - isEmbedded: true, + goToContextLine: false, ); } } diff --git a/playground/frontend/lib/pages/embedded_playground/embedded_playground_page.dart b/playground/frontend/lib/pages/embedded_playground/embedded_playground_page.dart index 8d4c41454f3cf..396c31a8264d8 100644 --- a/playground/frontend/lib/pages/embedded_playground/embedded_playground_page.dart +++ b/playground/frontend/lib/pages/embedded_playground/embedded_playground_page.dart @@ -17,12 +17,11 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/modules/output/components/output.dart'; import 'package:playground/pages/embedded_playground/components/embedded_actions.dart'; import 'package:playground/pages/embedded_playground/components/embedded_appbar_title.dart'; import 'package:playground/pages/embedded_playground/components/embedded_editor.dart'; import 'package:playground/pages/embedded_playground/components/embedded_split_view.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; const kActionsWidth = 300.0; @@ -38,8 +37,8 @@ class EmbeddedPlaygroundPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, state, child) => Scaffold( + return Consumer( + builder: (context, controller, child) => Scaffold( appBar: AppBar( automaticallyImplyLeading: false, title: const EmbeddedAppBarTitle(), @@ -49,9 +48,9 @@ class EmbeddedPlaygroundPage extends StatelessWidget { first: EmbeddedEditor(isEditable: isEditable), second: Container( color: Theme.of(context).backgroundColor, - child: Output( - isEmbedded: true, - playgroundState: state, + child: OutputWidget( + playgroundController: controller, + graphDirection: Axis.horizontal, ), ), ), diff --git a/playground/frontend/lib/pages/playground/components/close_listener.dart b/playground/frontend/lib/pages/playground/components/close_listener.dart index 682355585b09b..ba2a19a7ae226 100644 --- a/playground/frontend/lib/pages/playground/components/close_listener.dart +++ b/playground/frontend/lib/pages/playground/components/close_listener.dart @@ -17,7 +17,7 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'dart:html' as html; import 'package:provider/provider.dart'; @@ -36,7 +36,7 @@ class _CloseListenerState extends State { void initState() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { html.window.onBeforeUnload.listen((event) async { - Provider.of(context, listen: false).cancelRun(); + Provider.of(context, listen: false).cancelRun(); }); }); super.initState(); 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 be58a603d1813..5d427f41d98fe 100644 --- a/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart +++ b/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart @@ -18,17 +18,12 @@ 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/components/playground_run_or_cancel_button.dart'; import 'package:playground/constants/sizes.dart'; -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/editor/components/share_dropdown/share_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/notifications/components/notification.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; -import 'package:playground/utils/analytics_utils.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; class CodeTextAreaWrapper extends StatelessWidget { @@ -36,17 +31,17 @@ class CodeTextAreaWrapper extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer(builder: (context, state, child) { - if (state.result?.errorMessage?.isNotEmpty ?? false) { + return Consumer(builder: (context, controller, child) { + if (controller.result?.errorMessage?.isNotEmpty ?? false) { WidgetsBinding.instance.addPostFrameCallback((_) { - _handleError(context, state); + _handleError(context, controller); }); } - final controller = state.snippetEditingController; + final snippetController = controller.snippetEditingController; - if (controller == null) { - return const LoadingIndicator(size: kLgLoadingIndicatorSize); + if (snippetController == null) { + return const LoadingIndicator(); } return Column( @@ -55,12 +50,10 @@ class CodeTextAreaWrapper extends StatelessWidget { child: Stack( children: [ Positioned.fill( - child: EditorTextArea( - codeController: controller.codeController, - enabled: !(state.selectedExample?.isMultiFile ?? false), - example: state.selectedExample, - sdk: controller.sdk, + child: SnippetEditor( + controller: snippetController, isEditable: true, + goToContextLine: true, ), ), Positioned( @@ -69,12 +62,12 @@ class CodeTextAreaWrapper extends StatelessWidget { height: kButtonHeight, child: Row( children: [ - if (state.selectedExample != null) ...[ - if (state.selectedExample?.isMultiFile ?? false) + if (controller.selectedExample != null) ...[ + if (controller.selectedExample?.isMultiFile ?? false) Semantics( container: true, child: MultifilePopoverButton( - example: state.selectedExample!, + example: controller.selectedExample!, followerAnchor: Alignment.topRight, targetAnchor: Alignment.bottomRight, ), @@ -82,7 +75,7 @@ class CodeTextAreaWrapper extends StatelessWidget { Semantics( container: true, child: DescriptionPopoverButton( - example: state.selectedExample!, + example: controller.selectedExample!, followerAnchor: Alignment.topRight, targetAnchor: Alignment.bottomRight, ), @@ -95,39 +88,7 @@ class CodeTextAreaWrapper extends StatelessWidget { const SizedBox(width: kLgSpacing), Semantics( container: true, - child: RunButton( - disabled: state.selectedExample?.isMultiFile ?? false, - isRunning: state.isCodeRunning, - cancelRun: () { - final exampleName = getAnalyticsExampleName(state); - AnalyticsService.get(context) - .trackClickCancelRunEvent(exampleName); - state.cancelRun().catchError( - (_) => NotificationManager.showError( - context, - AppLocalizations.of(context)!.runCode, - AppLocalizations.of(context)! - .cancelExecution, - ), - ); - }, - runCode: () { - AnalyticsService analyticsService = - AnalyticsService.get(context); - final stopwatch = Stopwatch()..start(); - final exampleName = getAnalyticsExampleName(state); - - state.runCode( - onFinish: () { - analyticsService.trackRunTimeEvent( - exampleName, - stopwatch.elapsedMilliseconds, - ); - }, - ); - analyticsService.trackClickRunEvent(exampleName); - }, - ), + child: const PlaygroundRunOrCancelButton(), ), ], ), @@ -140,12 +101,12 @@ class CodeTextAreaWrapper extends StatelessWidget { }); } - _handleError(BuildContext context, PlaygroundState state) { + void _handleError(BuildContext context, PlaygroundController controller) { NotificationManager.showError( context, AppLocalizations.of(context)!.runCode, - state.result?.errorMessage ?? '', + controller.result?.errorMessage ?? '', ); - state.resetError(); + controller.resetError(); } } diff --git a/playground/frontend/lib/pages/playground/components/feedback/feedback_dropdown_content.dart b/playground/frontend/lib/pages/playground/components/feedback/feedback_dropdown_content.dart index 38f17396cbffe..278d973d84efb 100644 --- a/playground/frontend/lib/pages/playground/components/feedback/feedback_dropdown_content.dart +++ b/playground/frontend/lib/pages/playground/components/feedback/feedback_dropdown_content.dart @@ -17,13 +17,12 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/components/horizontal_divider/horizontal_divider.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/constants/font_weight.dart'; import 'package:playground/constants/fonts.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/analytics/analytics_service.dart'; import 'package:playground/pages/playground/components/feedback/feedback_dropdown_icon_button.dart'; +import 'package:playground_components/playground_components.dart'; const double kTextFieldWidth = 365.0; const double kTextFieldHeight = 68.0; @@ -46,8 +45,10 @@ class FeedbackDropdownContent extends StatelessWidget { @override Widget build(BuildContext context) { + final borderColor = Theme.of(context).extension()!.borderColor; + final OutlineInputBorder border = OutlineInputBorder( - borderSide: BorderSide(color: ThemeColors.of(context).lightGreyColor), + borderSide: BorderSide(color: borderColor), borderRadius: BorderRadius.circular(kMdBorderRadius), ); @@ -114,7 +115,7 @@ class FeedbackDropdownContent extends StatelessWidget { enabledBorder: border, contentPadding: const EdgeInsets.all(kMdSpacing), ), - cursorColor: ThemeColors.of(context).lightGreyColor, + cursorColor: borderColor, cursorWidth: kCursorSize, onFieldSubmitted: (String filterText) {}, maxLines: 3, @@ -124,7 +125,7 @@ class FeedbackDropdownContent extends StatelessWidget { ], ), ), - const HorizontalDivider(), + const BeamDivider(), Padding( padding: const EdgeInsets.only( top: kXlSpacing, @@ -141,7 +142,7 @@ class FeedbackDropdownContent extends StatelessWidget { color: Theme.of(context).backgroundColor, borderRadius: BorderRadius.circular(kSmBorderRadius), border: Border.all( - color: ThemeColors.of(context).lightGreyColor, + color: borderColor, ), ), child: TextButton( diff --git a/playground/frontend/lib/pages/playground/components/more_actions.dart b/playground/frontend/lib/pages/playground/components/more_actions.dart index 4fe7c9aa121c0..60f4e0ff9b345 100644 --- a/playground/frontend/lib/pages/playground/components/more_actions.dart +++ b/playground/frontend/lib/pages/playground/components/more_actions.dart @@ -19,11 +19,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/constants/assets.dart'; import 'package:playground/constants/links.dart'; import 'package:playground/modules/analytics/analytics_service.dart'; import 'package:playground/modules/shortcuts/components/shortcuts_modal.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:url_launcher/url_launcher.dart'; enum HeaderAction { @@ -36,7 +36,11 @@ enum HeaderAction { } class MoreActions extends StatefulWidget { - const MoreActions({Key? key}) : super(key: key); + final PlaygroundController playgroundController; + + const MoreActions({ + required this.playgroundController, + }); @override State createState() => _MoreActionsState(); @@ -52,7 +56,7 @@ class _MoreActionsState extends State { child: PopupMenuButton( icon: Icon( Icons.more_horiz_outlined, - color: ThemeColors.of(context).grey1Color, + color: Theme.of(context).extension()?.iconColor, ), itemBuilder: (BuildContext context) => >[ PopupMenuItem( @@ -62,10 +66,12 @@ class _MoreActionsState extends State { leading: SvgPicture.asset(kShortcutsIconAsset), title: Text(appLocale.shortcuts), onTap: () { - AnalyticsService.get(context).trackOpenShortcutsModal(); - showDialog( + AnalyticsService.get(context).trackOpenShortcutsModal(); + showDialog( context: context, - builder: (BuildContext context) => const ShortcutsModal(), + builder: (BuildContext context) => ShortcutsModal( + playgroundController: widget.playgroundController, + ), ); }, ), diff --git a/playground/frontend/lib/pages/playground/components/playground_page_body.dart b/playground/frontend/lib/pages/playground/components/playground_page_body.dart index 8a723982ed40c..63d918ae292e3 100644 --- a/playground/frontend/lib/pages/playground/components/playground_page_body.dart +++ b/playground/frontend/lib/pages/playground/components/playground_page_body.dart @@ -17,14 +17,12 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/components/split_view/split_view.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/output/components/output.dart'; +import 'package:playground/modules/output/components/output_header/output_placements.dart'; import 'package:playground/modules/output/models/output_placement.dart'; import 'package:playground/modules/output/models/output_placement_state.dart'; import 'package:playground/pages/playground/components/editor_textarea_wrapper.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; class PlaygroundPageBody extends StatelessWidget { @@ -32,30 +30,34 @@ class PlaygroundPageBody extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer2( + return Consumer2( builder: (context, outputState, playgroundState, child) { - final output = createOutput(playgroundState); + final output = OutputWidget( + graphDirection: outputState.placement.graphDirection, + playgroundController: playgroundState, + trailing: const OutputPlacements(), + ); + switch (outputState.placement) { case OutputPlacement.bottom: return SplitView( - direction: SplitViewDirection.vertical, + direction: Axis.vertical, first: codeTextArea, second: output, - dividerSize: kMdSpacing, ); + case OutputPlacement.left: return SplitView( - direction: SplitViewDirection.horizontal, + direction: Axis.horizontal, first: output, second: codeTextArea, - dividerSize: kMdSpacing, ); + case OutputPlacement.right: return SplitView( - direction: SplitViewDirection.horizontal, + direction: Axis.horizontal, first: codeTextArea, second: output, - dividerSize: kMdSpacing, ); } }); @@ -63,18 +65,13 @@ class PlaygroundPageBody extends StatelessWidget { Widget get codeTextArea => const CodeTextAreaWrapper(); - Widget createOutput(PlaygroundState state) => Output( - isEmbedded: false, - playgroundState: state, - ); - Widget getVerticalSeparator(BuildContext context) => Container( width: kMdSpacing, - color: ThemeColors.of(context).divider, + color: Theme.of(context).dividerColor, ); Widget getHorizontalSeparator(BuildContext context) => Container( height: kMdSpacing, - color: ThemeColors.of(context).divider, + color: Theme.of(context).dividerColor, ); } diff --git a/playground/frontend/lib/pages/playground/components/playground_page_footer.dart b/playground/frontend/lib/pages/playground/components/playground_page_footer.dart index 7e6b6bcf799a9..b97bafad949f2 100644 --- a/playground/frontend/lib/pages/playground/components/playground_page_footer.dart +++ b/playground/frontend/lib/pages/playground/components/playground_page_footer.dart @@ -18,12 +18,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/constants/font_weight.dart'; import 'package:playground/constants/links.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/analytics/analytics_service.dart'; import 'package:playground/pages/playground/components/feedback/playground_feedback.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:url_launcher/url_launcher.dart'; class PlaygroundPageFooter extends StatelessWidget { @@ -34,7 +34,9 @@ class PlaygroundPageFooter extends StatelessWidget { AppLocalizations appLocale = AppLocalizations.of(context)!; return Container( - color: ThemeColors.of(context).secondaryBackground, + color: Theme.of(context) + .extension() + ?.secondaryBackgroundColor, width: double.infinity, child: Padding( padding: const EdgeInsets.symmetric( diff --git a/playground/frontend/lib/pages/playground/components/playground_page_providers.dart b/playground/frontend/lib/pages/playground/components/playground_page_providers.dart index 9b809c9c262c0..a5e89130b0511 100644 --- a/playground/frontend/lib/pages/playground/components/playground_page_providers.dart +++ b/playground/frontend/lib/pages/playground/components/playground_page_providers.dart @@ -17,26 +17,19 @@ */ import 'package:flutter/material.dart'; +import 'package:playground/config.g.dart'; +import 'package:playground/constants/params.dart'; import 'package:playground/modules/analytics/analytics_service.dart'; import 'package:playground/modules/analytics/google_analytics_service.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/grpc_code_client.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_repository.dart'; -import 'package:playground/modules/examples/repositories/example_client/grpc_example_client.dart'; -import 'package:playground/modules/examples/repositories/example_repository.dart'; +import 'package:playground/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory.dart'; import 'package:playground/modules/messages/handlers/messages_debouncer.dart'; import 'package:playground/modules/messages/handlers/messages_handler.dart'; import 'package:playground/modules/messages/listeners/messages_listener.dart'; import 'package:playground/modules/output/models/output_placement_state.dart'; -import 'package:playground/pages/playground/states/example_loaders/examples_loader.dart'; -import 'package:playground/pages/playground/states/examples_state.dart'; import 'package:playground/pages/playground/states/feedback_state.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; -final CodeRepository kCodeRepository = CodeRepository(GrpcCodeClient()); -final ExampleRepository kExampleRepository = - ExampleRepository(GrpcExampleClient()); - class PlaygroundPageProviders extends StatelessWidget { final Widget child; @@ -52,20 +45,47 @@ class PlaygroundPageProviders extends StatelessWidget { Provider( create: (context) => GoogleAnalyticsService(), ), - ChangeNotifierProvider( + ChangeNotifierProvider( create: (context) { - final state = PlaygroundState( + final codeRepository = CodeRepository( + client: GrpcCodeClient( + url: kApiClientURL, + runnerUrlsById: { + Sdk.java.id: kApiJavaClientURL, + Sdk.go.id: kApiGoClientURL, + Sdk.python.id: kApiPythonClientURL, + Sdk.scio.id: kApiScioClientURL, + }, + ), + ); + + final exampleRepository = ExampleRepository( + client: GrpcExampleClient(url: kApiClientURL), + ); + + final exampleCache = ExampleCache( + exampleRepository: exampleRepository, + hasCatalog: !isEmbedded(), + )..init(); + + final controller = PlaygroundController( examplesLoader: ExamplesLoader(), - exampleState: ExampleState(kExampleRepository)..init(), - codeRepository: kCodeRepository, + exampleCache: exampleCache, + codeRepository: codeRepository, + ); + + final descriptor = ExamplesLoadingDescriptorFactory.fromUriParts( + path: Uri.base.path, + params: Uri.base.queryParameters, ); + controller.examplesLoader.load(descriptor); final handler = MessagesDebouncer( - handler: MessagesHandler(playgroundState: state), + handler: MessagesHandler(playgroundController: controller), ); MessagesListener(handler: handler); - return state; + return controller; }, ), ChangeNotifierProvider( diff --git a/playground/frontend/lib/pages/playground/playground_page.dart b/playground/frontend/lib/pages/playground/playground_page.dart index 7f68817f3e4a9..a7a66f02d5ab4 100644 --- a/playground/frontend/lib/pages/playground/playground_page.dart +++ b/playground/frontend/lib/pages/playground/playground_page.dart @@ -18,7 +18,6 @@ import 'package:flutter/material.dart'; import 'package:playground/components/logo/logo_component.dart'; -import 'package:playground/components/toggle_theme_button/toggle_theme_button.dart'; import 'package:playground/constants/sizes.dart'; import 'package:playground/modules/actions/components/new_example_action.dart'; import 'package:playground/modules/actions/components/reset_action.dart'; @@ -33,7 +32,7 @@ import 'package:playground/pages/playground/components/close_listener_nonweb.dar import 'package:playground/pages/playground/components/more_actions.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/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; class PlaygroundPage extends StatelessWidget { @@ -42,56 +41,65 @@ class PlaygroundPage extends StatelessWidget { @override Widget build(BuildContext context) { return CloseListener( - child: ShortcutsManager( - shortcuts: globalShortcuts, - child: Scaffold( - appBar: AppBar( - automaticallyImplyLeading: false, - title: Consumer( - builder: (context, state, child) { - final controller = state.snippetEditingController; + child: Consumer( + builder: (context, controller, child) { + final snippetController = controller.snippetEditingController; - return Wrap( + return ShortcutsManager( + shortcuts: [ + ...controller.shortcuts, + ...globalShortcuts, + ], + child: Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: Wrap( crossAxisAlignment: WrapCrossAlignment.center, spacing: kXlSpacing, children: [ const Logo(), ExampleSelector( changeSelectorVisibility: - state.exampleState.changeSelectorVisibility, - isSelectorOpened: state.exampleState.isSelectorOpened, + controller.exampleCache.changeSelectorVisibility, + isSelectorOpened: + controller.exampleCache.isSelectorOpened, ), SDKSelector( - value: state.sdk, + value: controller.sdk, onChanged: (newSdk) { AnalyticsService.get(context) - .trackSelectSdk(state.sdk, newSdk); - state.setSdk(newSdk); + .trackSelectSdk(controller.sdk, newSdk); + controller.setSdk(newSdk); }, ), - if (controller != null) + if (snippetController != null) PipelineOptionsDropdown( - pipelineOptions: controller.pipelineOptions, - setPipelineOptions: state.setPipelineOptions, + pipelineOptions: snippetController.pipelineOptions, + setPipelineOptions: controller.setPipelineOptions, ), const NewExampleAction(), - ResetAction(reset: state.reset), + const ResetAction(), ], - ); - }, - ), - actions: const [ToggleThemeButton(), MoreActions()], - ), - body: Column( - children: [ - const Expanded(child: PlaygroundPageBody()), - Semantics( - container: true, - child: const PlaygroundPageFooter(), + ), + actions: [ + const ToggleThemeButton(), + MoreActions( + playgroundController: controller, + ), + ], ), - ], - ), - ), + body: Column( + children: [ + const Expanded(child: PlaygroundPageBody()), + Semantics( + container: true, + child: const PlaygroundPageFooter(), + ), + ], + ), + ), + ); + }, ), ); } diff --git a/playground/frontend/lib/pages/playground/states/example_loaders/examples_loader.dart b/playground/frontend/lib/pages/playground/states/example_loaders/examples_loader.dart deleted file mode 100644 index ad4efacd13093..0000000000000 --- a/playground/frontend/lib/pages/playground/states/example_loaders/examples_loader.dart +++ /dev/null @@ -1,110 +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:playground/modules/examples/models/example_loading_descriptors/catalog_default_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/content_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/empty_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/examples_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/standard_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart'; -import 'package:playground/pages/playground/states/example_loaders/catalog_default_example_loader.dart'; -import 'package:playground/pages/playground/states/example_loaders/content_example_loader.dart'; -import 'package:playground/pages/playground/states/example_loaders/empty_example_loader.dart'; -import 'package:playground/pages/playground/states/example_loaders/example_loader.dart'; -import 'package:playground/pages/playground/states/example_loaders/standard_example_loader.dart'; -import 'package:playground/pages/playground/states/example_loaders/user_shared_example_loader.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; - -class ExamplesLoader { - PlaygroundState? _playgroundState; - ExamplesLoadingDescriptor? _descriptor; - - void setPlaygroundState(PlaygroundState value) { - _playgroundState = value; - } - - Future load(ExamplesLoadingDescriptor descriptor) async { - if (_descriptor == descriptor) { - return; - } - - _descriptor = descriptor; - await Future.wait( - descriptor.descriptors.map( - (one) => loadOne(group: descriptor, one: one), - ), - ); - - final sdk = descriptor.initialSdk; - if (sdk != null) { - _playgroundState!.setSdk(sdk); - } - } - - Future loadOne({ - required ExamplesLoadingDescriptor group, - required ExampleLoadingDescriptor one, - }) async { - final example = await _getOneLoader(one).future; - _playgroundState!.setExample( - example, - setCurrentSdk: - example.sdk == group.initialSdk || group.initialSdk == null, - ); - } - - ExampleLoader _getOneLoader(ExampleLoadingDescriptor descriptor) { - final exampleState = _playgroundState!.exampleState; - - if (descriptor is CatalogDefaultExampleLoadingDescriptor) { - return CatalogDefaultExampleLoader( - descriptor: descriptor, - exampleState: exampleState, - ); - } - - if (descriptor is ContentExampleLoadingDescriptor) { - return ContentExampleLoader( - descriptor: descriptor, - ); - } - - if (descriptor is EmptyExampleLoadingDescriptor) { - return EmptyExampleLoader( - descriptor: descriptor, - ); - } - - if (descriptor is StandardExampleLoadingDescriptor) { - return StandardExampleLoader( - descriptor: descriptor, - exampleState: exampleState, - ); - } - - if (descriptor is UserSharedExampleLoadingDescriptor) { - return UserSharedExampleLoader( - descriptor: descriptor, - exampleState: exampleState, - ); - } - - throw Exception('Unknown example loading descriptor: $descriptor'); - } -} diff --git a/playground/frontend/lib/pages/playground/states/example_selector_state.dart b/playground/frontend/lib/pages/playground/states/example_selector_state.dart index 2f6f462f7f013..f62a1773012fc 100644 --- a/playground/frontend/lib/pages/playground/states/example_selector_state.dart +++ b/playground/frontend/lib/pages/playground/states/example_selector_state.dart @@ -17,18 +17,16 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/modules/examples/models/category_model.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; class ExampleSelectorState with ChangeNotifier { - final PlaygroundState _playgroundState; + final PlaygroundController _playgroundController; ExampleType _selectedFilterType; String _filterText; - List categories; + List categories; ExampleSelectorState( - this._playgroundState, + this._playgroundController, this.categories, [ this._selectedFilterType = ExampleType.all, this._filterText = '', @@ -48,26 +46,26 @@ class ExampleSelectorState with ChangeNotifier { notifyListeners(); } - void setCategories(List? categories) { - this.categories = categories ?? []; + void setCategories(List categories) { + this.categories = categories; notifyListeners(); } void sortCategories() { - final categories = _playgroundState.exampleState.getCategories( - _playgroundState.sdk, + final categories = _playgroundController.exampleCache.getCategories( + _playgroundController.sdk, ); final sortedCategories = categories - .map((category) => CategoryModel( - name: category.name, + .map((category) => CategoryWithExamples( + title: category.title, examples: _sortCategoryExamples(category.examples))) .where((category) => category.examples.isNotEmpty) .toList(); setCategories(sortedCategories); } - List _sortCategoryExamples(List examples) { + List _sortCategoryExamples(List examples) { final isAllFilterType = selectedFilterType == ExampleType.all; final isFilterTextEmpty = filterText.isEmpty; if (isAllFilterType && isFilterTextEmpty) { @@ -89,15 +87,15 @@ class ExampleSelectorState with ChangeNotifier { return sortExamplesByName(sorted, filterText); } - List sortExamplesByType( - List examples, + List sortExamplesByType( + List examples, ExampleType type, ) { return examples.where((element) => element.type == type).toList(); } - List sortExamplesByName( - List examples, + List sortExamplesByName( + List examples, String name, ) { return examples diff --git a/playground/frontend/lib/pages/playground/states/examples_state.dart b/playground/frontend/lib/pages/playground/states/examples_state.dart deleted file mode 100644 index 594e74a7a1b02..0000000000000 --- a/playground/frontend/lib/pages/playground/states/examples_state.dart +++ /dev/null @@ -1,218 +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 'dart:async'; - -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:playground/constants/params.dart'; -import 'package:playground/modules/examples/models/category_model.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/modules/examples/repositories/example_repository.dart'; -import 'package:playground/modules/examples/repositories/models/get_snippet_request.dart'; -import 'package:playground/modules/examples/repositories/models/get_snippet_response.dart'; -import 'package:playground/modules/examples/repositories/models/get_example_request.dart'; -import 'package:playground/modules/examples/repositories/models/get_list_of_examples_request.dart'; -import 'package:playground/modules/examples/repositories/models/save_snippet_request.dart'; -import 'package:playground/modules/examples/repositories/models/shared_file_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; - -class ExampleState with ChangeNotifier { - final ExampleRepository _exampleRepository; - Map>? sdkCategories; - Map defaultExamplesMap = {}; - ExampleModel? defaultExample; - bool isSelectorOpened = false; - - final _allExamplesCompleter = Completer(); - - Future get allExamplesFuture => _allExamplesCompleter.future; - - bool get hasExampleCatalog => !isEmbedded(); - - ExampleState(this._exampleRepository); - - Future init() async { - if (hasExampleCatalog) { - await Future.wait([ - _loadCategories(), - loadDefaultExamplesIfNot(), - ]); - } - } - - void setSdkCategories(Map> map) { - sdkCategories = map; - _allExamplesCompleter.complete(); - } - - List getCategories(SDK? sdk) { - return sdkCategories?[sdk] ?? []; - } - - Future getExampleOutput(String id, SDK sdk) async { - return _exampleRepository.getExampleOutput( - GetExampleRequestWrapper(id, sdk), - ); - } - - Future getExampleSource(String id, SDK sdk) async { - return _exampleRepository.getExampleSource( - GetExampleRequestWrapper(id, sdk), - ); - } - - Future getExample(String path, SDK sdk) async { - return _exampleRepository.getExample( - GetExampleRequestWrapper(path, sdk), - ); - } - - Future getExampleLogs(String id, SDK sdk) async { - return _exampleRepository.getExampleLogs( - GetExampleRequestWrapper(id, sdk), - ); - } - - Future getExampleGraph(String id, SDK sdk) async { - return _exampleRepository.getExampleGraph( - GetExampleRequestWrapper(id, sdk), - ); - } - - Future loadSharedExample(String id) async { - GetSnippetResponse result = await _exampleRepository.getSnippet( - GetSnippetRequestWrapper(id: id), - ); - return ExampleModel( - sdk: result.sdk, - name: result.files.first.name, - path: id, - description: '', - type: ExampleType.example, - source: result.files.first.code, - pipelineOptions: result.pipelineOptions, - ); - } - - Future getSnippetId({ - required List files, - required SDK sdk, - required String pipelineOptions, - }) async { - String id = await _exampleRepository.saveSnippet(SaveSnippetRequestWrapper( - files: files, - sdk: sdk, - pipelineOptions: pipelineOptions, - )); - return id; - } - - Future loadExampleInfo(ExampleModel example) async { - if (example.isInfoFetched()) { - return example; - } - - //GRPC GetPrecompiledGraph errors hotfix - if (example.name == 'MinimalWordCount' && - (example.sdk == SDK.go || example.sdk == SDK.scio)) { - final exampleData = await Future.wait([ - getExampleSource(example.path, example.sdk), - getExampleOutput(example.path, example.sdk), - getExampleLogs(example.path, example.sdk), - ]); - example.setSource(exampleData[0]); - example.setOutputs(exampleData[1]); - example.setLogs(exampleData[2]); - return example; - } - - final exampleData = await Future.wait([ - getExampleSource(example.path, example.sdk), - getExampleOutput(example.path, example.sdk), - getExampleLogs(example.path, example.sdk), - getExampleGraph(example.path, example.sdk) - ]); - example.setSource(exampleData[0]); - example.setOutputs(exampleData[1]); - example.setLogs(exampleData[2]); - example.setGraph(exampleData[3]); - return example; - } - - Future _loadCategories() { - return _exampleRepository - .getListOfExamples( - GetListOfExamplesRequestWrapper(sdk: null, category: null), - ) - .then((map) => setSdkCategories(map)); - } - - void changeSelectorVisibility() { - isSelectorOpened = !isSelectorOpened; - notifyListeners(); - } - - Future loadDefaultExamples() async { - if (defaultExamplesMap.isNotEmpty) { - return; - } - - try { - await Future.wait(SDK.values.map(_loadDefaultExample)); - } catch (ex) { - if (defaultExamplesMap.isEmpty) { - rethrow; - } - // As long as any of the examples is loaded, continue. - print(ex); - // TODO: Log. - } - - notifyListeners(); - } - - Future _loadDefaultExample(SDK sdk) async { - final exampleWithoutInfo = await _exampleRepository.getDefaultExample( - // First parameter is an empty string, because we don't need path to get the default example. - GetExampleRequestWrapper('', sdk), - ); - - defaultExamplesMap[sdk] = await loadExampleInfo(exampleWithoutInfo); - } - - Future loadDefaultExamplesIfNot() async { - if (defaultExamplesMap.isNotEmpty) { - return; - } - - await loadDefaultExamples(); - } - - Future getCatalogExampleByPath(String path) async { - await allExamplesFuture; - - final allExamples = sdkCategories?.values - .expand((sdkCategory) => sdkCategory.map((e) => e.examples)) - .expand((element) => element); - - return allExamples?.firstWhereOrNull( - (e) => e.path == path, - ); - } -} diff --git a/playground/frontend/lib/playground_app.dart b/playground/frontend/lib/playground_app.dart index 8c5f2a489e6e5..867bbad7f4b93 100644 --- a/playground/frontend/lib/playground_app.dart +++ b/playground/frontend/lib/playground_app.dart @@ -16,16 +16,16 @@ * limitations under the License. */ -import 'package:code_text_field/code_text_field.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:playground/config/locale.dart'; -import 'package:playground/config/theme.dart'; import 'package:playground/l10n/l10n.dart'; import 'package:playground/pages/playground/components/playground_page_providers.dart'; import 'package:playground/pages/playground/playground_page.dart'; import 'package:playground/pages/routes.dart'; +import 'package:playground_components/playground_components.dart'; import 'package:provider/provider.dart'; class PlaygroundApp extends StatelessWidget { @@ -36,32 +36,30 @@ class PlaygroundApp extends StatelessWidget { return ThemeSwitchNotifierProvider( child: Consumer( builder: (context, themeSwitchNotifier, _) { - return CodeTheme( - data: themeSwitchNotifier.codeTheme, - child: ChangeNotifierProvider( - create: (context) => LocaleProvider(), - builder: (context, state) { - final localeProvider = Provider.of(context); - return PlaygroundPageProviders( - child: MaterialApp( - title: 'Apache Beam Playground', - themeMode: themeSwitchNotifier.themeMode, - theme: kLightTheme, - darkTheme: kDarkTheme, - onGenerateRoute: Routes.generateRoute, - home: const PlaygroundPage(), - debugShowCheckedModeBanner: false, - locale: localeProvider.locale, - supportedLocales: L10n.locales, - localizationsDelegates: const [ - AppLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - ), - ); - }, - ), + return ChangeNotifierProvider( + create: (context) => LocaleProvider(), + builder: (context, state) { + final localeProvider = Provider.of(context); + return PlaygroundPageProviders( + child: MaterialApp( + title: 'Apache Beam Playground', + themeMode: themeSwitchNotifier.themeMode, + theme: kLightTheme, + darkTheme: kDarkTheme, + onGenerateRoute: Routes.generateRoute, + home: const PlaygroundPage(), + debugShowCheckedModeBanner: false, + locale: localeProvider.locale, + supportedLocales: L10n.locales, + localizationsDelegates: [ + ...context.localizationDelegates, + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + ), + ); + }, ); }, ), diff --git a/playground/frontend/lib/utils/analytics_utils.dart b/playground/frontend/lib/utils/analytics_utils.dart index ab77fc23d964c..786b54ff382f1 100644 --- a/playground/frontend/lib/utils/analytics_utils.dart +++ b/playground/frontend/lib/utils/analytics_utils.dart @@ -16,13 +16,12 @@ * limitations under the License. */ -import 'package:playground/modules/sdk/models/sdk.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/playground_components.dart'; -String getAnalyticsExampleName(PlaygroundState state) { - final customCodeName = 'Custom code, sdk ${state.sdk?.displayName}'; - if (state.isExampleChanged) { +String getAnalyticsExampleName(PlaygroundController controller) { + final customCodeName = 'Custom code, sdk ${controller.sdk?.title}'; + if (controller.isExampleChanged) { return customCodeName; } - return state.selectedExample?.path ?? customCodeName; + return controller.selectedExample?.path ?? customCodeName; } diff --git a/playground/frontend/assets/reset.svg b/playground/frontend/playground_components/assets/buttons/reset.svg similarity index 100% rename from playground/frontend/assets/reset.svg rename to playground/frontend/playground_components/assets/buttons/reset.svg diff --git a/learning/tour-of-beam/frontend/assets/svg/theme-mode.svg b/playground/frontend/playground_components/assets/buttons/theme-mode.svg similarity index 100% rename from learning/tour-of-beam/frontend/assets/svg/theme-mode.svg rename to playground/frontend/playground_components/assets/buttons/theme-mode.svg diff --git a/playground/frontend/assets/error_notification.svg b/playground/frontend/playground_components/assets/notification_icons/error.svg similarity index 100% rename from playground/frontend/assets/error_notification.svg rename to playground/frontend/playground_components/assets/notification_icons/error.svg diff --git a/playground/frontend/assets/info_notification.svg b/playground/frontend/playground_components/assets/notification_icons/info.svg similarity index 100% rename from playground/frontend/assets/info_notification.svg rename to playground/frontend/playground_components/assets/notification_icons/info.svg diff --git a/playground/frontend/assets/success_notification.svg b/playground/frontend/playground_components/assets/notification_icons/success.svg similarity index 100% rename from playground/frontend/assets/success_notification.svg rename to playground/frontend/playground_components/assets/notification_icons/success.svg diff --git a/playground/frontend/assets/warning_notification.svg b/playground/frontend/playground_components/assets/notification_icons/warning.svg similarity index 100% rename from playground/frontend/assets/warning_notification.svg rename to playground/frontend/playground_components/assets/notification_icons/warning.svg diff --git a/learning/tour-of-beam/frontend/assets/png/beam-logo.png b/playground/frontend/playground_components/assets/png/beam-logo.png similarity index 100% rename from learning/tour-of-beam/frontend/assets/png/beam-logo.png rename to playground/frontend/playground_components/assets/png/beam-logo.png diff --git a/playground/frontend/playground_components/assets/svg/drag-horizontal.svg b/playground/frontend/playground_components/assets/svg/drag-horizontal.svg new file mode 100644 index 0000000000000..f5e8dcda558a7 --- /dev/null +++ b/playground/frontend/playground_components/assets/svg/drag-horizontal.svg @@ -0,0 +1,23 @@ + + + + + + diff --git a/playground/frontend/playground_components/assets/svg/drag-vertical.svg b/playground/frontend/playground_components/assets/svg/drag-vertical.svg new file mode 100644 index 0000000000000..fea5377776ef4 --- /dev/null +++ b/playground/frontend/playground_components/assets/svg/drag-vertical.svg @@ -0,0 +1,23 @@ + + + + + + diff --git a/playground/frontend/playground_components/assets/translations/en.yaml b/playground/frontend/playground_components/assets/translations/en.yaml new file mode 100644 index 0000000000000..f80677c598a9a --- /dev/null +++ b/playground/frontend/playground_components/assets/translations/en.yaml @@ -0,0 +1,52 @@ +# 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. + +ui: + darkMode: 'Dark Mode' + lightMode: 'Light Mode' + +intents: + playground: + run: 'Run Code' + reset: 'Reset Code' + +widgets: + + codeEditor: + label: 'Code Text Area' + + output: + filter: + all: 'All' + log: 'Log' + output: 'Output' + filterTitle: 'Display at this tab' + graph: 'Graph' + result: 'Result' + + + resetButton: + label: 'Reset' + + runOrCancelButton: + titles: + run: 'Run' + cancel: 'Cancel' + notificationTitles: + run: 'Run Code' + cancelExecution: 'Cancel Execution' + diff --git a/playground/frontend/playground_components/build.gradle.kts b/playground/frontend/playground_components/build.gradle.kts new file mode 100644 index 0000000000000..bacc8daddecfa --- /dev/null +++ b/playground/frontend/playground_components/build.gradle.kts @@ -0,0 +1,90 @@ +/* + * 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. + */ + +description = "Apache Beam :: Playground :: playground_components Flutter Package" + +tasks.register("precommit") { + dependsOn("analyze") + dependsOn("test") +} + +tasks.register("analyze") { + dependsOn("generate") + + group = "verification" + description = "Run dart analyzer" + + doLast { + exec { + executable("dart") + args("analyze", ".") + } + } +} + +tasks.register("test") { + dependsOn("generate") + + group = "verification" + description = "Run tests" + + doLast { + exec { + executable("flutter") + args("test") + } + } +} + +tasks.register("clean") { + group = "build" + description = "Remove build artifacts" + doLast { + exec { + executable("flutter") + args("clean") + } + } +} + +tasks.register("pubGet") { + group = "build" + description = "Install dependencies" + + doLast { + exec { + executable("flutter") + args("pub", "get") + } + } +} + +tasks.register("generate") { + dependsOn("clean") + dependsOn("pubGet") + + group = "build" + description = "Generate code" + + doLast { + exec { + executable("flutter") + args("pub", "run", "build_runner", "build", "--delete-conflicting-outputs") + } + } +} diff --git a/playground/frontend/playground_components/lib/playground_components.dart b/playground/frontend/playground_components/lib/playground_components.dart new file mode 100644 index 0000000000000..a7201940a84af --- /dev/null +++ b/playground/frontend/playground_components/lib/playground_components.dart @@ -0,0 +1,73 @@ +/* + * 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. + */ + +export 'src/cache/example_cache.dart'; + +export 'src/constants/colors.dart'; +export 'src/constants/links.dart'; +export 'src/constants/playground_components.dart'; +export 'src/constants/sizes.dart'; + +export 'src/controllers/example_loaders/examples_loader.dart'; +export 'src/controllers/playground_controller.dart'; + +export 'src/enums/complexity.dart'; + +export 'src/models/category_with_examples.dart'; +export 'src/models/example.dart'; +export 'src/models/example_base.dart'; +export 'src/models/example_loading_descriptors/catalog_default_example_loading_descriptor.dart'; +export 'src/models/example_loading_descriptors/content_example_loading_descriptor.dart'; +export 'src/models/example_loading_descriptors/empty_example_loading_descriptor.dart'; +export 'src/models/example_loading_descriptors/example_loading_descriptor.dart'; +export 'src/models/example_loading_descriptors/examples_loading_descriptor.dart'; +export 'src/models/example_loading_descriptors/standard_example_loading_descriptor.dart'; +export 'src/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart'; +export 'src/models/intents.dart'; +export 'src/models/outputs.dart'; +export 'src/models/sdk.dart'; +export 'src/models/shortcut.dart'; + +export 'src/notifications/notification.dart'; + +export 'src/repositories/code_client/grpc_code_client.dart'; +export 'src/repositories/code_repository.dart'; +export 'src/repositories/example_client/grpc_example_client.dart'; +export 'src/repositories/example_repository.dart'; + +export 'src/theme/switch_notifier.dart'; +export 'src/theme/theme.dart'; + +export 'src/util/pipeline_options.dart'; + +export 'src/widgets/bubble.dart'; +export 'src/widgets/complexity.dart'; +export 'src/widgets/dismissible_overlay.dart'; +export 'src/widgets/divider.dart'; +export 'src/widgets/header_icon_button.dart'; +export 'src/widgets/loading_indicator.dart'; +export 'src/widgets/logo.dart'; +export 'src/widgets/output/output.dart'; +export 'src/widgets/reset_button.dart'; +export 'src/widgets/run_or_cancel_button.dart'; +export 'src/widgets/shortcut_tooltip.dart'; +export 'src/widgets/snippet_editor.dart'; +export 'src/widgets/split_view.dart'; +export 'src/widgets/tab_header.dart'; +export 'src/widgets/toggle_theme_button.dart'; +export 'src/widgets/toggle_theme_icon_button.dart'; diff --git a/playground/frontend/lib/configure_nonweb.dart b/playground/frontend/playground_components/lib/src/api/iis_workaround_channel.dart similarity index 88% rename from playground/frontend/lib/configure_nonweb.dart rename to playground/frontend/playground_components/lib/src/api/iis_workaround_channel.dart index 4568ded62f946..5c41c00d327e4 100644 --- a/playground/frontend/lib/configure_nonweb.dart +++ b/playground/frontend/playground_components/lib/src/api/iis_workaround_channel.dart @@ -16,6 +16,5 @@ * limitations under the License. */ -void configureApp() { - // see https://flutter.dev/docs/development/ui/navigation/url-strategies -} +export 'iis_workaround_channel_non_web.dart' + if (dart.library.html) 'iis_workaround_channel_web.dart'; diff --git a/playground/frontend/playground_components/lib/src/api/iis_workaround_channel_non_web.dart b/playground/frontend/playground_components/lib/src/api/iis_workaround_channel_non_web.dart new file mode 100644 index 0000000000000..39fb9d0ba3885 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/api/iis_workaround_channel_non_web.dart @@ -0,0 +1,30 @@ +/* + * 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:grpc/grpc_connection_interface.dart'; + +class IisWorkaroundChannel extends ClientChannelBase { + final Uri uri; + + IisWorkaroundChannel.xhr(this.uri) : super(); + + @override + ClientConnection createConnection() { + throw UnimplementedError('This only works in web'); + } +} diff --git a/playground/frontend/lib/api/iis_workaround_channel.dart b/playground/frontend/playground_components/lib/src/api/iis_workaround_channel_web.dart similarity index 88% rename from playground/frontend/lib/api/iis_workaround_channel.dart rename to playground/frontend/playground_components/lib/src/api/iis_workaround_channel_web.dart index 2232c59e50346..b666677a643de 100644 --- a/playground/frontend/lib/api/iis_workaround_channel.dart +++ b/playground/frontend/playground_components/lib/src/api/iis_workaround_channel_web.dart @@ -28,12 +28,12 @@ class IisWorkaroundChannel extends ClientChannelBase { @override ClientConnection createConnection() { - return IisClientConnection(uri); + return _IisClientConnection(uri); } } -class IisClientConnection extends XhrClientConnection { - IisClientConnection(Uri uri) : super(uri); +class _IisClientConnection extends XhrClientConnection { + _IisClientConnection(super.uri); @override GrpcTransportStream makeRequest( @@ -43,7 +43,8 @@ class IisClientConnection extends XhrClientConnection { ErrorHandler onError, { CallOptions? callOptions, }) { - var pathWithoutFirstSlash = path.substring(1); + final pathWithoutFirstSlash = path.substring(1); + return super.makeRequest( pathWithoutFirstSlash, timeout, diff --git a/playground/frontend/lib/api/v1/api.pb.dart b/playground/frontend/playground_components/lib/src/api/v1/api.pb.dart similarity index 100% rename from playground/frontend/lib/api/v1/api.pb.dart rename to playground/frontend/playground_components/lib/src/api/v1/api.pb.dart diff --git a/playground/frontend/lib/api/v1/api.pbenum.dart b/playground/frontend/playground_components/lib/src/api/v1/api.pbenum.dart similarity index 100% rename from playground/frontend/lib/api/v1/api.pbenum.dart rename to playground/frontend/playground_components/lib/src/api/v1/api.pbenum.dart diff --git a/playground/frontend/lib/api/v1/api.pbgrpc.dart b/playground/frontend/playground_components/lib/src/api/v1/api.pbgrpc.dart similarity index 100% rename from playground/frontend/lib/api/v1/api.pbgrpc.dart rename to playground/frontend/playground_components/lib/src/api/v1/api.pbgrpc.dart diff --git a/playground/frontend/lib/api/v1/api.pbjson.dart b/playground/frontend/playground_components/lib/src/api/v1/api.pbjson.dart similarity index 100% rename from playground/frontend/lib/api/v1/api.pbjson.dart rename to playground/frontend/playground_components/lib/src/api/v1/api.pbjson.dart diff --git a/playground/frontend/playground_components/lib/src/cache/example_cache.dart b/playground/frontend/playground_components/lib/src/cache/example_cache.dart new file mode 100644 index 0000000000000..04d1425ec80c8 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/cache/example_cache.dart @@ -0,0 +1,238 @@ +/* + * 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 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; + +import '../models/category_with_examples.dart'; +import '../models/example.dart'; +import '../models/example_base.dart'; +import '../models/sdk.dart'; +import '../repositories/example_repository.dart'; +import '../repositories/models/get_default_precompiled_object_request.dart'; +import '../repositories/models/get_precompiled_object_request.dart'; +import '../repositories/models/get_precompiled_objects_request.dart'; +import '../repositories/models/shared_file.dart'; +import '../repositories/models/get_snippet_request.dart'; +import '../repositories/models/save_snippet_request.dart'; + +/// A runtime cache for examples fetched from a repository. +class ExampleCache extends ChangeNotifier { + /// If set, then categories and default examples are enabled. + /// Otherwise examples can only be queried by paths. + final bool hasCatalog; + + final ExampleRepository _exampleRepository; + final categoryListsBySdk = >{}; + + final Map defaultExamplesBySdk = {}; + + // TODO(alexeyinkin): Extract, https://github.com/apache/beam/issues/23249 + bool isSelectorOpened = false; + + final _allExamplesCompleter = Completer(); + + Future get allExamplesFuture => _allExamplesCompleter.future; + + ExampleCache({ + required ExampleRepository exampleRepository, + required this.hasCatalog, + }) : _exampleRepository = exampleRepository; + + Future init() async { + if (hasCatalog) { + await Future.wait([ + _loadCategories(), + loadDefaultExamplesIfNot(), + ]); + } + } + + void setSdkCategories(Map> map) { + categoryListsBySdk.addAll(map); + _allExamplesCompleter.complete(); + } + + List getCategories(Sdk? sdk) { + return categoryListsBySdk[sdk] ?? []; + } + + Future getExampleOutput(String path, Sdk sdk) async { + return _exampleRepository.getExampleOutput( + GetPrecompiledObjectRequest(path: path, sdk: sdk), + ); + } + + Future getExampleSource(String path, Sdk sdk) async { + return _exampleRepository.getExampleSource( + GetPrecompiledObjectRequest(path: path, sdk: sdk), + ); + } + + Future getExample(String path, Sdk sdk) async { + return _exampleRepository.getExample( + GetPrecompiledObjectRequest(path: path, sdk: sdk), + ); + } + + Future getExampleLogs(String path, Sdk sdk) async { + return _exampleRepository.getExampleLogs( + GetPrecompiledObjectRequest(path: path, sdk: sdk), + ); + } + + Future getExampleGraph(String id, Sdk sdk) async { + return _exampleRepository.getExampleGraph( + GetPrecompiledObjectRequest(path: id, sdk: sdk), + ); + } + + Future loadSharedExample(String id) async { + final result = await _exampleRepository.getSnippet( + GetSnippetRequest(id: id), + ); + + return Example( + sdk: result.sdk, + name: result.files.first.name, + path: id, + description: '', + type: ExampleType.example, + source: result.files.first.code, + pipelineOptions: result.pipelineOptions, + ); + } + + Future getSnippetId({ + required List files, + required Sdk sdk, + required String pipelineOptions, + }) async { + final id = await _exampleRepository.saveSnippet( + SaveSnippetRequest( + files: files, + sdk: sdk, + pipelineOptions: pipelineOptions, + ), + ); + return id; + } + + Future loadExampleInfo(ExampleBase example) async { + if (example is Example) { + return example; + } + + //GRPC GetPrecompiledGraph errors hotfix + if (example.name == 'MinimalWordCount' && + (example.sdk == Sdk.go || example.sdk == Sdk.scio)) { + final exampleData = await Future.wait([ + getExampleSource(example.path, example.sdk), + getExampleOutput(example.path, example.sdk), + getExampleLogs(example.path, example.sdk), + ]); + + return Example.fromBase( + example, + source: exampleData[0], + outputs: exampleData[1], + logs: exampleData[2], + ); + } + + final exampleData = await Future.wait([ + getExampleSource(example.path, example.sdk), + getExampleOutput(example.path, example.sdk), + getExampleLogs(example.path, example.sdk), + getExampleGraph(example.path, example.sdk) + ]); + + return Example.fromBase( + example, + source: exampleData[0], + outputs: exampleData[1], + logs: exampleData[2], + graph: exampleData[3], + ); + } + + Future _loadCategories() async { + final result = await _exampleRepository.getListOfExamples( + const GetPrecompiledObjectsRequest( + sdk: null, + category: null, + ), + ); + + setSdkCategories(result); + } + + void changeSelectorVisibility() { + isSelectorOpened = !isSelectorOpened; + notifyListeners(); + } + + Future loadDefaultExamples() async { + if (defaultExamplesBySdk.isNotEmpty) { + return; + } + + try { + await Future.wait(Sdk.known.map(_loadDefaultExample)); + } catch (ex) { + if (defaultExamplesBySdk.isEmpty) { + rethrow; + } + // As long as any of the examples is loaded, continue. + print(ex); + // TODO: Log. + } + + notifyListeners(); + } + + Future _loadDefaultExample(Sdk sdk) async { + final exampleWithoutInfo = await _exampleRepository.getDefaultExample( + GetDefaultPrecompiledObjectRequest(sdk: sdk), + ); + + defaultExamplesBySdk[sdk] = await loadExampleInfo(exampleWithoutInfo); + } + + Future loadDefaultExamplesIfNot() async { + if (defaultExamplesBySdk.isNotEmpty) { + return; + } + + await loadDefaultExamples(); + } + + Future getCatalogExampleByPath(String path) async { + await allExamplesFuture; + + final allExamples = categoryListsBySdk.values + .expand((categories) => categories.map((c) => c.examples)) + .expand((examples) => examples); + + return allExamples.firstWhereOrNull( + (e) => e.path == path, + ); + } +} diff --git a/learning/tour-of-beam/frontend/lib/constants/colors.dart b/playground/frontend/playground_components/lib/src/constants/colors.dart similarity index 52% rename from learning/tour-of-beam/frontend/lib/constants/colors.dart rename to playground/frontend/playground_components/lib/src/constants/colors.dart index b595f8a46a698..43be2453329d8 100644 --- a/learning/tour-of-beam/frontend/lib/constants/colors.dart +++ b/playground/frontend/playground_components/lib/src/constants/colors.dart @@ -18,31 +18,61 @@ import 'package:flutter/material.dart'; -class TobColors { +class BeamColors { + static const transparent = Colors.transparent; static const white = Colors.white; static const black = Colors.black; static const grey1 = Color(0xffDFE1E3); static const grey2 = Color(0xffCBCBCB); static const grey3 = Color(0xffA0A4AB); static const grey4 = Color(0x30808080); + static const darkGrey = Color(0xff2E2E34); + static const darkBlue = Color(0xff242639); static const green = Color(0xff37AC66); static const orange = Color(0xffEEAB00); static const red = Color(0xffE54545); } -class TobLightThemeColors { - static const primaryBackground = Colors.white; - static const secondaryBackground = Color(0xffFEFDFD); +class BeamGraphColors { + static const node = BeamColors.grey3; + static const border = Color(0xFF45454E); + static const edge = BeamLightThemeColors.primary; +} + +class BeamNotificationColors { + static const error = Color(0xFFE54545); + static const info = Color(0xFF3E67F6); + static const success = Color(0xFF37AC66); + static const warning = Color(0xFFEEAB00); +} + +class BeamLightThemeColors { + static const border = Color(0xFFE5E5E5); + static const primaryBackground = BeamColors.white; + static const secondaryBackground = Color(0xffFCFCFC); static const grey = Color(0xffE5E5E5); - static const text = Color(0xff242639); + static const listBackground = Color(0xFFA0A4AB); + static const text = BeamColors.darkBlue; static const primary = Color(0xffE74D1A); + static const icon = Color(0xFFA0A4AB); + + static const code1 = Color(0xFFDA2833); + static const code2 = Color(0xFF5929B4); + static const codeComment = Color(0xFF4C6B60); } -class TobDarkThemeColors { +class BeamDarkThemeColors { + static const border = Color(0xFFA0A4AB); static const primaryBackground = Color(0xff18181B); - static const secondaryBackground = Color(0xff2E2E34); + static const secondaryBackground = BeamColors.darkGrey; static const grey = Color(0xff3F3F46); + static const listBackground = Color(0xFF606772); static const text = Color(0xffFFFFFF); static const primary = Color(0xffF26628); + static const icon = Color(0xFF606772); + + static const code1 = Color(0xFFDA2833); + static const code2 = Color(0xFF5929B4); + static const codeComment = Color(0xFF4C6B60); } diff --git a/learning/tour-of-beam/frontend/lib/constants/links.dart b/playground/frontend/playground_components/lib/src/constants/links.dart similarity index 98% rename from learning/tour-of-beam/frontend/lib/constants/links.dart rename to playground/frontend/playground_components/lib/src/constants/links.dart index 1d15c5c3656f9..d55de92c80283 100644 --- a/learning/tour-of-beam/frontend/lib/constants/links.dart +++ b/playground/frontend/playground_components/lib/src/constants/links.dart @@ -16,7 +16,7 @@ * limitations under the License. */ -class TobLinks { +class BeamLinks { static const reportIssue = 'https://github.com/apache/beam/issues'; static const privacyPolicy = 'https://beam.apache.org/privacy_policy/'; } diff --git a/playground/frontend/lib/modules/examples/models/example_origin.dart b/playground/frontend/playground_components/lib/src/constants/playground_components.dart similarity index 68% rename from playground/frontend/lib/modules/examples/models/example_origin.dart rename to playground/frontend/playground_components/lib/src/constants/playground_components.dart index ee4e0d789007b..89909e112a816 100644 --- a/playground/frontend/lib/modules/examples/models/example_origin.dart +++ b/playground/frontend/playground_components/lib/src/constants/playground_components.dart @@ -16,26 +16,14 @@ * limitations under the License. */ -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:easy_localization_ext/easy_localization_ext.dart'; -enum ExampleOrigin { - empty, - content, - standard, - userShared, - catalogDefault, - ; +class PlaygroundComponents { + static const packageName = 'playground_components'; - static ExampleOrigin fromToken(String? token) { - if (token == null) { - return empty; - } - - final sdk = SDK.tryParseExamplePath(token); - if (sdk != null) { - return standard; - } - - return userShared; - } + // TODO(alexeyinkin): Make const when this is fixed: https://github.com/aissat/easy_localization_loader/issues/39 + static final translationLoader = YamlPackageAssetLoader( + packageName, + path: 'assets/translations', + ); } diff --git a/playground/frontend/playground_components/lib/src/constants/sizes.dart b/playground/frontend/playground_components/lib/src/constants/sizes.dart new file mode 100644 index 0000000000000..916ae6f7243ad --- /dev/null +++ b/playground/frontend/playground_components/lib/src/constants/sizes.dart @@ -0,0 +1,57 @@ +/* + * 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. + */ + +class BeamSizes { + static const double size0 = 0; + static const double size1 = 1; + static const double size2 = 2; + static const double size4 = 4; + static const double size6 = 6; + static const double size8 = 8; + static const double size10 = 10; + static const double size12 = 12; + static const double size16 = 16; + static const double size18 = 18; + static const double size20 = 20; + static const double size24 = 24; + static const double size30 = 30; + static const double size32 = 32; + static const double size36 = 36; + static const double size40 = 40; + static const double size64 = 64; + + static const double appBarHeight = 55; + static const double buttonHeight = 40; + static const double headerButtonHeight = 46; + static const double loadingIndicator = 40; + static const double splitViewSeparator = BeamSizes.size8; +} + +class BeamBorderRadius { + static const double small = BeamSizes.size4; + static const double large = BeamSizes.size8; + static const double infinite = 1000; // TODO: Use StadiumBorder +} + +class BeamIconSizes { + static const double xs = BeamSizes.size8; + static const double small = BeamSizes.size16; + static const double large = BeamSizes.size32; + + static const double largeSplashRadius = 24; +} diff --git a/playground/frontend/lib/pages/playground/states/example_loaders/catalog_default_example_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/catalog_default_example_loader.dart similarity index 65% rename from playground/frontend/lib/pages/playground/states/example_loaders/catalog_default_example_loader.dart rename to playground/frontend/playground_components/lib/src/controllers/example_loaders/catalog_default_example_loader.dart index e07d4cb67d6e3..d07d08970265f 100644 --- a/playground/frontend/lib/pages/playground/states/example_loaders/catalog_default_example_loader.dart +++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/catalog_default_example_loader.dart @@ -16,28 +16,28 @@ * limitations under the License. */ -import 'package:playground/modules/examples/models/example_loading_descriptors/catalog_default_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/pages/playground/states/example_loaders/example_loader.dart'; -import 'package:playground/pages/playground/states/examples_state.dart'; +import '../../cache/example_cache.dart'; +import '../../models/example.dart'; +import '../../models/example_loading_descriptors/catalog_default_example_loading_descriptor.dart'; +import 'example_loader.dart'; class CatalogDefaultExampleLoader extends ExampleLoader { final CatalogDefaultExampleLoadingDescriptor descriptor; - final ExampleState exampleState; + final ExampleCache exampleCache; const CatalogDefaultExampleLoader({ required this.descriptor, - required this.exampleState, + required this.exampleCache, }); @override - Future get future async { - if (!exampleState.hasExampleCatalog) { + Future get future async { + if (!exampleCache.hasCatalog) { throw Exception('Default example requires a catalog in ExampleState'); } - await exampleState.loadDefaultExamplesIfNot(); - final result = exampleState.defaultExamplesMap[descriptor.sdk]; + await exampleCache.loadDefaultExamplesIfNot(); + final result = exampleCache.defaultExamplesBySdk[descriptor.sdk]; if (result == null) { throw Exception('Default example not found for $descriptor'); diff --git a/playground/frontend/lib/pages/playground/states/example_loaders/content_example_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/content_example_loader.dart similarity index 72% rename from playground/frontend/lib/pages/playground/states/example_loaders/content_example_loader.dart rename to playground/frontend/playground_components/lib/src/controllers/example_loaders/content_example_loader.dart index 117bf139f525d..a196d7122a7ed 100644 --- a/playground/frontend/lib/pages/playground/states/example_loaders/content_example_loader.dart +++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/content_example_loader.dart @@ -16,24 +16,29 @@ * limitations under the License. */ -import 'package:playground/modules/examples/models/example_loading_descriptors/content_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/pages/playground/states/example_loaders/example_loader.dart'; +import '../../cache/example_cache.dart'; +import '../../models/example.dart'; +import '../../models/example_base.dart'; +import '../../models/example_loading_descriptors/content_example_loading_descriptor.dart'; +import 'example_loader.dart'; class ContentExampleLoader extends ExampleLoader { final ContentExampleLoadingDescriptor descriptor; const ContentExampleLoader({ required this.descriptor, + // TODO(alexeyinkin): Remove when this lands: https://github.com/dart-lang/language/issues/1813 + required ExampleCache exampleCache, }); @override - Future get future async => ExampleModel( + Future get future async => Example( sdk: descriptor.sdk, name: descriptor.name ?? 'Embedded_Example', path: '', description: '', type: ExampleType.example, source: descriptor.content, + pipelineOptions: '', ); } diff --git a/playground/frontend/lib/pages/playground/states/example_loaders/empty_example_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/empty_example_loader.dart similarity index 70% rename from playground/frontend/lib/pages/playground/states/example_loaders/empty_example_loader.dart rename to playground/frontend/playground_components/lib/src/controllers/example_loaders/empty_example_loader.dart index 3f1acdc87bd29..8f3b3d07e4c5d 100644 --- a/playground/frontend/lib/pages/playground/states/example_loaders/empty_example_loader.dart +++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/empty_example_loader.dart @@ -16,23 +16,29 @@ * limitations under the License. */ -import 'package:playground/modules/examples/models/example_loading_descriptors/empty_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/pages/playground/states/example_loaders/example_loader.dart'; +import '../../cache/example_cache.dart'; +import '../../models/example.dart'; +import '../../models/example_base.dart'; +import '../../models/example_loading_descriptors/empty_example_loading_descriptor.dart'; +import 'example_loader.dart'; class EmptyExampleLoader extends ExampleLoader { final EmptyExampleLoadingDescriptor descriptor; const EmptyExampleLoader({ required this.descriptor, + // TODO(alexeyinkin): Remove when this lands: https://github.com/dart-lang/language/issues/1813 + required ExampleCache exampleCache, }); @override - Future get future async => ExampleModel( + Future get future async => Example( sdk: descriptor.sdk, name: 'Embedded_Example', path: '', description: '', type: ExampleType.example, + source: '', + pipelineOptions: '', ); } diff --git a/playground/frontend/lib/pages/playground/states/example_loaders/example_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/example_loader.dart similarity index 89% rename from playground/frontend/lib/pages/playground/states/example_loaders/example_loader.dart rename to playground/frontend/playground_components/lib/src/controllers/example_loaders/example_loader.dart index 9c6b256683cc2..c90f03745da7a 100644 --- a/playground/frontend/lib/pages/playground/states/example_loaders/example_loader.dart +++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/example_loader.dart @@ -16,10 +16,10 @@ * limitations under the License. */ -import 'package:playground/modules/examples/models/example_model.dart'; +import '../../models/example.dart'; abstract class ExampleLoader { const ExampleLoader(); - Future get future; + Future get future; } diff --git a/playground/frontend/playground_components/lib/src/controllers/example_loaders/example_loader_factory.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/example_loader_factory.dart new file mode 100644 index 0000000000000..9a16d7d4b9902 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/example_loader_factory.dart @@ -0,0 +1,53 @@ +/* + * 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 '../../cache/example_cache.dart'; +import '../../models/example_loading_descriptors/example_loading_descriptor.dart'; +import 'example_loader.dart'; + +typedef ExampleLoaderFactoryFunction + = ExampleLoader Function({ + required D descriptor, + required ExampleCache exampleCache, +}); + +class ExampleLoaderFactory { + final factories = {}; + + void add( + ExampleLoaderFactoryFunction function, + ) { + factories[D] = ({ + required ExampleLoadingDescriptor descriptor, + required ExampleCache exampleCache, + }) { + return function(descriptor: descriptor as D, exampleCache: exampleCache); + }; + } + + ExampleLoader? create({ + required D descriptor, + required ExampleCache exampleCache, + }) { + return factories[descriptor.runtimeType]?.call( + descriptor: descriptor, + exampleCache: exampleCache, + ); + } +} + diff --git a/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart new file mode 100644 index 0000000000000..6d872e6775125 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.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:collection/collection.dart'; + +import '../../models/example_loading_descriptors/example_loading_descriptor.dart'; +import '../../models/example_loading_descriptors/examples_loading_descriptor.dart'; +import '../../models/sdk.dart'; +import '../playground_controller.dart'; +import 'catalog_default_example_loader.dart'; +import 'content_example_loader.dart'; +import 'empty_example_loader.dart'; +import 'example_loader_factory.dart'; +import 'standard_example_loader.dart'; +import 'user_shared_example_loader.dart'; + +class ExamplesLoader { + final defaultFactory = ExampleLoaderFactory(); + PlaygroundController? _playgroundController; + ExamplesLoadingDescriptor? _descriptor; + + ExamplesLoader() { + defaultFactory.add(CatalogDefaultExampleLoader.new); + defaultFactory.add(ContentExampleLoader.new); + defaultFactory.add(EmptyExampleLoader.new); + defaultFactory.add(StandardExampleLoader.new); + defaultFactory.add(UserSharedExampleLoader.new); + } + + void setPlaygroundController(PlaygroundController value) { + _playgroundController = value; + } + + Future load(ExamplesLoadingDescriptor descriptor) async { + if (_descriptor == descriptor) { + return; + } + + _descriptor = descriptor; + await Future.wait( + descriptor.descriptors.map( + (one) => loadOne(group: descriptor, one: one), + ), + ); + + final sdk = descriptor.initialSdk; + if (sdk != null) { + _playgroundController!.setSdk(sdk); + } + } + + Future loadDefaultIfAny(Sdk sdk) async { + final group = _descriptor; + final one = group?.lazyLoadDescriptors[sdk]?.firstOrNull; + + if (group == null || one == null) { + return; + } + + return loadOne( + group: group, + one: one, + ); + } + + Future loadOne({ + required ExamplesLoadingDescriptor group, + required ExampleLoadingDescriptor one, + }) async { + final loader = defaultFactory.create( + descriptor: one, + exampleCache: _playgroundController!.exampleCache, + ); + + if (loader == null) { + // TODO: Log. + print('Cannot create example loader for $one'); + return; + } + + final example = await loader.future; + _playgroundController!.setExample( + example, + setCurrentSdk: + example.sdk == group.initialSdk || group.initialSdk == null, + ); + } +} diff --git a/playground/frontend/lib/pages/playground/states/example_loaders/standard_example_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/standard_example_loader.dart similarity index 61% rename from playground/frontend/lib/pages/playground/states/example_loaders/standard_example_loader.dart rename to playground/frontend/playground_components/lib/src/controllers/example_loaders/standard_example_loader.dart index 667f8f24e1dcc..23c429ebab13f 100644 --- a/playground/frontend/lib/pages/playground/states/example_loaders/standard_example_loader.dart +++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/standard_example_loader.dart @@ -18,11 +18,12 @@ import 'dart:async'; -import 'package:playground/modules/examples/models/example_loading_descriptors/standard_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; -import 'package:playground/pages/playground/states/example_loaders/example_loader.dart'; -import 'package:playground/pages/playground/states/examples_state.dart'; +import '../../cache/example_cache.dart'; +import '../../models/example.dart'; +import '../../models/example_base.dart'; +import '../../models/example_loading_descriptors/standard_example_loading_descriptor.dart'; +import '../../models/sdk.dart'; +import 'example_loader.dart'; /// Loads a given example from the local cache, then adds info from network. /// @@ -30,20 +31,20 @@ import 'package:playground/pages/playground/states/examples_state.dart'; /// its cache. So it only completes if this is successful. class StandardExampleLoader extends ExampleLoader { final StandardExampleLoadingDescriptor descriptor; - final ExampleState exampleState; - final _completer = Completer(); + final ExampleCache exampleCache; + final _completer = Completer(); @override - Future get future => _completer.future; + Future get future => _completer.future; StandardExampleLoader({ required this.descriptor, - required this.exampleState, + required this.exampleCache, }) { _load(); } - void _load() async { + Future _load() async { final example = await _loadExampleWithoutInfo(); if (example == null) { @@ -52,23 +53,23 @@ class StandardExampleLoader extends ExampleLoader { } _completer.complete( - exampleState.loadExampleInfo(example), + exampleCache.loadExampleInfo(example), ); } - Future _loadExampleWithoutInfo() { - return exampleState.hasExampleCatalog - ? exampleState.getCatalogExampleByPath(descriptor.path) + Future _loadExampleWithoutInfo() { + return exampleCache.hasCatalog + ? exampleCache.getCatalogExampleByPath(descriptor.path) : _loadExampleFromRepository(); } - Future _loadExampleFromRepository() async { - final sdk = SDK.tryParseExamplePath(descriptor.path); + Future _loadExampleFromRepository() async { + final sdk = Sdk.tryParseExamplePath(descriptor.path); if (sdk == null) { return null; } - return exampleState.getExample(descriptor.path, sdk); + return exampleCache.getExample(descriptor.path, sdk); } } diff --git a/playground/frontend/lib/pages/playground/states/example_loaders/user_shared_example_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/user_shared_example_loader.dart similarity index 65% rename from playground/frontend/lib/pages/playground/states/example_loaders/user_shared_example_loader.dart rename to playground/frontend/playground_components/lib/src/controllers/example_loaders/user_shared_example_loader.dart index 0a658718a0bab..f256d5c563793 100644 --- a/playground/frontend/lib/pages/playground/states/example_loaders/user_shared_example_loader.dart +++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/user_shared_example_loader.dart @@ -16,21 +16,21 @@ * limitations under the License. */ -import 'package:playground/modules/examples/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/pages/playground/states/example_loaders/example_loader.dart'; -import 'package:playground/pages/playground/states/examples_state.dart'; +import '../../cache/example_cache.dart'; +import '../../models/example.dart'; +import '../../models/example_loading_descriptors/user_shared_example_loading_descriptor.dart'; +import 'example_loader.dart'; class UserSharedExampleLoader extends ExampleLoader { final UserSharedExampleLoadingDescriptor descriptor; - final ExampleState exampleState; + final ExampleCache exampleCache; UserSharedExampleLoader({ required this.descriptor, - required this.exampleState, + required this.exampleCache, }); @override - Future get future => - exampleState.loadSharedExample(descriptor.snippetId); + Future get future => + exampleCache.loadSharedExample(descriptor.snippetId); } diff --git a/playground/frontend/lib/pages/playground/states/playground_state.dart b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart similarity index 74% rename from playground/frontend/lib/pages/playground/states/playground_state.dart rename to playground/frontend/playground_components/lib/src/controllers/playground_controller.dart index 2080f2005f5c4..7944491107727 100644 --- a/playground/frontend/lib/pages/playground/states/playground_state.dart +++ b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart @@ -19,21 +19,24 @@ import 'dart:async'; import 'dart:math'; -import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:playground/modules/editor/controllers/snippet_editing_controller.dart'; -import 'package:playground/modules/editor/parsers/run_options_parser.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_repository.dart'; -import 'package:playground/modules/editor/repository/code_repository/run_code_request.dart'; -import 'package:playground/modules/editor/repository/code_repository/run_code_result.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/examples_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/modules/examples/models/outputs_model.dart'; -import 'package:playground/modules/examples/repositories/models/shared_file_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; -import 'package:playground/pages/playground/states/example_loaders/examples_loader.dart'; -import 'package:playground/pages/playground/states/examples_state.dart'; +import 'package:flutter/services.dart'; + +import '../cache/example_cache.dart'; +import '../models/example.dart'; +import '../models/example_base.dart'; +import '../models/example_loading_descriptors/examples_loading_descriptor.dart'; +import '../models/intents.dart'; +import '../models/outputs.dart'; +import '../models/sdk.dart'; +import '../models/shortcut.dart'; +import '../repositories/code_repository.dart'; +import '../repositories/models/run_code_request.dart'; +import '../repositories/models/run_code_result.dart'; +import '../repositories/models/shared_file.dart'; +import '../util/pipeline_options.dart'; +import 'example_loaders/examples_loader.dart'; +import 'snippet_editing_controller.dart'; const kTitleLength = 15; const kExecutionTimeUpdate = 100; @@ -45,40 +48,33 @@ const kPipelineOptionsParseError = const kCachedResultsLog = 'The results of this example are taken from the Apache Beam Playground cache.\n'; -class PlaygroundState with ChangeNotifier { - final ExampleState exampleState; +class PlaygroundController with ChangeNotifier { + final ExampleCache exampleCache; final ExamplesLoader examplesLoader; - final ExamplesLoadingDescriptor examplesLoadingDescriptor; - final _snippetEditingControllers = {}; + final _snippetEditingControllers = {}; + + Sdk? _sdk; + final CodeRepository? _codeRepository; - SDK? _sdk; - CodeRepository? _codeRepository; RunCodeResult? _result; StreamSubscription? _runSubscription; StreamController? _executionTime; - OutputType? selectedOutputFilterType; + + // TODO(alexeyinkin): Extract along with run status, https://github.com/apache/beam/issues/23248 + OutputType selectedOutputFilterType = OutputType.all; String outputResult = ''; - PlaygroundState({ - required this.exampleState, + PlaygroundController({ + required this.exampleCache, required this.examplesLoader, CodeRepository? codeRepository, - }) : examplesLoadingDescriptor = - ExamplesLoadingDescriptorFactory.fromUriParts( - path: Uri.base.path, - params: Uri.base.queryParameters, - ) { - examplesLoader.setPlaygroundState(this); - examplesLoader.load(examplesLoadingDescriptor); - - _codeRepository = codeRepository; - selectedOutputFilterType = OutputType.all; - outputResult = ''; + }) : _codeRepository = codeRepository { + examplesLoader.setPlaygroundController(this); } SnippetEditingController _getOrCreateSnippetEditingController( - SDK sdk, { + Sdk sdk, { required bool loadDefaultIfNot, }) { final existing = _snippetEditingControllers[sdk]; @@ -90,30 +86,21 @@ class PlaygroundState with ChangeNotifier { _snippetEditingControllers[sdk] = result; if (loadDefaultIfNot) { - final descriptor = - examplesLoadingDescriptor.lazyLoadDescriptors[sdk]?.firstOrNull; - - if (descriptor != null) { - examplesLoader.loadOne( - group: examplesLoadingDescriptor, - one: descriptor, - ); - } + examplesLoader.loadDefaultIfAny(sdk); } return result; } - // TODO: Return full, then shorten. + // TODO(alexeyinkin): Return full, then shorten, https://github.com/apache/beam/issues/23250 String get examplesTitle { final name = snippetEditingController?.selectedExample?.name ?? kTitle; return name.substring(0, min(kTitleLength, name.length)); } - ExampleModel? get selectedExample => - snippetEditingController?.selectedExample; + Example? get selectedExample => snippetEditingController?.selectedExample; - SDK? get sdk => _sdk; + Sdk? get sdk => _sdk; SnippetEditingController? get snippetEditingController => _snippetEditingControllers[_sdk]; @@ -142,12 +129,13 @@ class PlaygroundState with ChangeNotifier { return snippetEditingController?.isChanged ?? false; } + // TODO(alexeyinkin): Single source of truth for whether graph is supported, https://github.com/apache/beam/issues/23251 bool get graphAvailable => selectedExample?.type != ExampleType.test && - [SDK.java, SDK.python].contains(sdk); + [Sdk.java, Sdk.python].contains(sdk); void setExample( - ExampleModel example, { + Example example, { required bool setCurrentSdk, }) { if (setCurrentSdk) { @@ -173,7 +161,7 @@ class PlaygroundState with ChangeNotifier { } void setSdk( - SDK sdk, { + Sdk sdk, { bool notify = true, }) { _sdk = sdk; @@ -233,7 +221,7 @@ class PlaygroundState with ChangeNotifier { final parsedPipelineOptions = parsePipelineOptions(controller.pipelineOptions); if (parsedPipelineOptions == null) { - _result = RunCodeResult( + _result = const RunCodeResult( status: RunCodeStatus.compileError, errorMessage: kPipelineOptionsParseError, ); @@ -245,14 +233,14 @@ class PlaygroundState with ChangeNotifier { if (!isExampleChanged && controller.selectedExample?.outputs != null) { _showPrecompiledResult(controller); } else { - final request = RunCodeRequestWrapper( + final request = RunCodeRequest( code: controller.codeController.text, sdk: controller.sdk, pipelineOptions: parsedPipelineOptions, ); _runSubscription = _codeRepository?.runCode(request).listen((event) { _result = event; - filterOutput(selectedOutputFilterType ?? OutputType.all); + filterOutput(selectedOutputFilterType); if (event.isFinished && onFinish != null) { onFinish(); @@ -265,28 +253,31 @@ class PlaygroundState with ChangeNotifier { } Future cancelRun() async { - _runSubscription?.cancel(); + await _runSubscription?.cancel(); final pipelineUuid = result?.pipelineUuid ?? ''; + if (pipelineUuid.isNotEmpty) { await _codeRepository?.cancelExecution(pipelineUuid); } + _result = RunCodeResult( status: RunCodeStatus.finished, output: _result?.output, log: (_result?.log ?? '') + kExecutionCancelledText, graph: _result?.graph, ); - String log = _result?.log ?? ''; - String output = _result?.output ?? ''; + + final log = _result?.log ?? ''; + final output = _result?.output ?? ''; setOutputResult(log + output); - _executionTime?.close(); + await _executionTime?.close(); notifyListeners(); } Future _showPrecompiledResult( SnippetEditingController snippetEditingController, ) async { - _result = RunCodeResult( + _result = const RunCodeResult( status: RunCodeStatus.preparation, ); final selectedExample = snippetEditingController.selectedExample!; @@ -303,8 +294,8 @@ class PlaygroundState with ChangeNotifier { graph: selectedExample.graph, ); - filterOutput(selectedOutputFilterType ?? OutputType.all); - _executionTime?.close(); + filterOutput(selectedOutputFilterType); + await _executionTime?.close(); notifyListeners(); } @@ -359,7 +350,7 @@ class PlaygroundState with ChangeNotifier { Future getSnippetId() { final controller = requireSnippetEditingController(); - return exampleState.getSnippetId( + return exampleCache.getSnippetId( files: [SharedFile(code: controller.codeController.text, isMain: true)], sdk: controller.sdk, pipelineOptions: controller.pipelineOptions, @@ -377,4 +368,32 @@ class PlaygroundState with ChangeNotifier { .toList(growable: false), ); } + + late BeamShortcut runShortcut = BeamShortcut( + shortcuts: LogicalKeySet( + LogicalKeyboardKey.meta, + LogicalKeyboardKey.enter, + ), + actionIntent: const RunIntent(), + createAction: (BuildContext context) => CallbackAction( + onInvoke: (_) => runCode(), + ), + ); + + late BeamShortcut resetShortcut = BeamShortcut( + shortcuts: LogicalKeySet( + LogicalKeyboardKey.meta, + LogicalKeyboardKey.shift, + LogicalKeyboardKey.keyE, + ), + actionIntent: const ResetIntent(), + createAction: (BuildContext context) => CallbackAction( + onInvoke: (_) => reset(), + ), + ); + + List get shortcuts => [ + runShortcut, + resetShortcut, + ]; } diff --git a/playground/frontend/lib/modules/editor/controllers/snippet_editing_controller.dart b/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart similarity index 80% rename from playground/frontend/lib/modules/editor/controllers/snippet_editing_controller.dart rename to playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart index 92acbfc6d434b..e687a16d451d4 100644 --- a/playground/frontend/lib/modules/editor/controllers/snippet_editing_controller.dart +++ b/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart @@ -18,20 +18,21 @@ import 'package:code_text_field/code_text_field.dart'; import 'package:flutter/widgets.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/content_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; + +import '../models/example.dart'; +import '../models/example_loading_descriptors/content_example_loading_descriptor.dart'; +import '../models/example_loading_descriptors/example_loading_descriptor.dart'; +import '../models/sdk.dart'; class SnippetEditingController extends ChangeNotifier { - final SDK sdk; + final Sdk sdk; final CodeController codeController; - ExampleModel? _selectedExample; + Example? _selectedExample; String _pipelineOptions; SnippetEditingController({ required this.sdk, - ExampleModel? selectedExample, + Example? selectedExample, String pipelineOptions = '', }) : codeController = CodeController( language: sdk.highlightMode, @@ -40,14 +41,14 @@ class SnippetEditingController extends ChangeNotifier { _selectedExample = selectedExample, _pipelineOptions = pipelineOptions; - set selectedExample(ExampleModel? value) { + set selectedExample(Example? value) { _selectedExample = value; codeController.text = _selectedExample?.source ?? ''; _pipelineOptions = _selectedExample?.pipelineOptions ?? ''; notifyListeners(); } - ExampleModel? get selectedExample => _selectedExample; + Example? get selectedExample => _selectedExample; set pipelineOptions(String value) { _pipelineOptions = value; @@ -77,7 +78,8 @@ class SnippetEditingController extends ChangeNotifier { /// current content. ExampleLoadingDescriptor getLoadingDescriptor() { // TODO: Return other classes for unchanged standard examples, - // user-shared examples, and an empty editor. + // user-shared examples, and an empty editor, + // https://github.com/apache/beam/issues/23252 return ContentExampleLoadingDescriptor( content: codeController.text, name: _selectedExample?.name, diff --git a/playground/frontend/lib/modules/examples/repositories/models/get_example_code_response.dart b/playground/frontend/playground_components/lib/src/enums/complexity.dart similarity index 89% rename from playground/frontend/lib/modules/examples/repositories/models/get_example_code_response.dart rename to playground/frontend/playground_components/lib/src/enums/complexity.dart index c7a57e93f3f85..79767efa9b9bd 100644 --- a/playground/frontend/lib/modules/examples/repositories/models/get_example_code_response.dart +++ b/playground/frontend/playground_components/lib/src/enums/complexity.dart @@ -16,8 +16,8 @@ * limitations under the License. */ -class GetExampleCodeResponse { - final String code; - - GetExampleCodeResponse(this.code); +enum Complexity { + basic, + medium, + advanced, } diff --git a/playground/frontend/lib/modules/examples/models/category_model.dart b/playground/frontend/playground_components/lib/src/models/category_with_examples.dart similarity index 56% rename from playground/frontend/lib/modules/examples/models/category_model.dart rename to playground/frontend/playground_components/lib/src/models/category_with_examples.dart index 20a224bb3a56b..e9b1ba392537c 100644 --- a/playground/frontend/lib/modules/examples/models/category_model.dart +++ b/playground/frontend/playground_components/lib/src/models/category_with_examples.dart @@ -16,23 +16,31 @@ * limitations under the License. */ -import 'package:playground/constants/params.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; +import 'example_base.dart'; -class CategoryModel with Comparable { - final String name; - final List examples; +const _pinnedTitle = 'quick start'; - const CategoryModel({required this.name, required this.examples}); +class CategoryWithExamples with Comparable { + // TODO(alexeyinkin): Sort on the backend instead, then make const constructor, https://github.com/apache/beam/issues/23083 + final bool isPinned; + final String title; + final List examples; + + CategoryWithExamples({ + required this.title, + required this.examples, + }) : isPinned = title.toLowerCase() == _pinnedTitle; @override - int compareTo(CategoryModel other) { - if (name.toLowerCase() == kQuickStartCategoryName) { + int compareTo(CategoryWithExamples other) { + if (isPinned && !other.isPinned) { return -1; } - if (other.name.toLowerCase() == kQuickStartCategoryName) { + + if (!isPinned && other.isPinned) { return 1; } - return name.toLowerCase().compareTo(other.name.toLowerCase()); + + return title.toLowerCase().compareTo(other.title.toLowerCase()); } } diff --git a/playground/frontend/playground_components/lib/src/models/example.dart b/playground/frontend/playground_components/lib/src/models/example.dart new file mode 100644 index 0000000000000..a598b6b6446fe --- /dev/null +++ b/playground/frontend/playground_components/lib/src/models/example.dart @@ -0,0 +1,61 @@ +/* + * 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 'example_base.dart'; + +/// A [ExampleBase] that also has all large fields fetched. +class Example extends ExampleBase { + final String source; + final String? outputs; + final String? logs; + final String? graph; + + const Example({ + required super.sdk, + required super.type, + required super.name, + required super.path, + required super.description, + super.contextLine, + super.isMultiFile, + super.link, + required super.pipelineOptions, + required this.source, + this.outputs, + this.logs, + this.graph, + }); + + Example.fromBase( + ExampleBase example, { + required this.source, + required this.outputs, + required this.logs, + this.graph, + }) : super( + sdk: example.sdk, + name: example.name, + path: example.path, + description: example.description, + type: example.type, + contextLine: example.contextLine, + isMultiFile: example.isMultiFile, + link: example.link, + pipelineOptions: example.pipelineOptions, + ); +} diff --git a/playground/frontend/lib/modules/examples/models/example_model.dart b/playground/frontend/playground_components/lib/src/models/example_base.dart similarity index 60% rename from playground/frontend/lib/modules/examples/models/example_model.dart rename to playground/frontend/playground_components/lib/src/models/example_base.dart index 054dddb500a4c..2c493edf79f6d 100644 --- a/playground/frontend/lib/modules/examples/models/example_model.dart +++ b/playground/frontend/playground_components/lib/src/models/example_base.dart @@ -16,7 +16,10 @@ * limitations under the License. */ -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:equatable/equatable.dart'; + +import '../repositories/example_repository.dart'; +import 'sdk.dart'; enum ExampleType { all, @@ -40,22 +43,21 @@ extension ExampleTypeToString on ExampleType { } } -class ExampleModel with Comparable { - final SDK sdk; +/// An example's basic info that does not contain source code +/// and other large fields. +/// These objects are fetched as lists from [ExampleRepository]. +class ExampleBase with Comparable, EquatableMixin { + final Sdk sdk; final ExampleType type; final String name; final String path; final String description; - int contextLine; - bool isMultiFile; - String? link; - String? source; - String? outputs; - String? logs; - String? pipelineOptions; - String? graph; + final int contextLine; + final bool isMultiFile; + final String? link; + final String pipelineOptions; - ExampleModel({ + const ExampleBase({ required this.sdk, required this.name, required this.path, @@ -64,47 +66,14 @@ class ExampleModel with Comparable { this.contextLine = 1, this.isMultiFile = false, this.link, - this.source, - this.outputs, - this.logs, - this.pipelineOptions, - this.graph, + required this.pipelineOptions, }); - setSource(String source) { - this.source = source; - } - - setOutputs(String outputs) { - this.outputs = outputs; - } - - setLogs(String logs) { - this.logs = logs; - } - - setGraph(String graph) { - this.graph = graph; - } - - setContextLine(int contextLine) { - this.contextLine = contextLine; - } - - bool isInfoFetched() { - // checking only source, because outputs/logs can be empty - return source?.isNotEmpty ?? false; - } - - @override - bool operator ==(Object other) => - identical(this, other) || (other is ExampleModel && path == other.path); - @override - int get hashCode => path.hashCode; + List get props => [path]; @override - int compareTo(ExampleModel other) { + int compareTo(ExampleBase other) { return name.toLowerCase().compareTo(other.name.toLowerCase()); } } diff --git a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/catalog_default_example_loading_descriptor.dart b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/catalog_default_example_loading_descriptor.dart new file mode 100644 index 0000000000000..085ee1d9dc0ce --- /dev/null +++ b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/catalog_default_example_loading_descriptor.dart @@ -0,0 +1,31 @@ +/* + * 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 '../sdk.dart'; +import 'example_loading_descriptor.dart'; + +class CatalogDefaultExampleLoadingDescriptor extends ExampleLoadingDescriptor { + final Sdk sdk; + + const CatalogDefaultExampleLoadingDescriptor({ + required this.sdk, + }); + + @override + List get props => [sdk.id]; +} diff --git a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/content_example_loading_descriptor.dart b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/content_example_loading_descriptor.dart similarity index 70% rename from playground/frontend/lib/modules/examples/models/example_loading_descriptors/content_example_loading_descriptor.dart rename to playground/frontend/playground_components/lib/src/models/example_loading_descriptors/content_example_loading_descriptor.dart index f981f8a9d018b..13be92a506d03 100644 --- a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/content_example_loading_descriptor.dart +++ b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/content_example_loading_descriptor.dart @@ -16,9 +16,8 @@ * limitations under the License. */ -import 'package:playground/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_origin.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import '../sdk.dart'; +import 'example_loading_descriptor.dart'; class ContentExampleLoadingDescriptor extends ExampleLoadingDescriptor { /// The source code. @@ -27,7 +26,7 @@ class ContentExampleLoadingDescriptor extends ExampleLoadingDescriptor { /// The name of the example, if any, to show in the dropdown. final String? name; - final SDK sdk; + final Sdk sdk; const ContentExampleLoadingDescriptor({ required this.content, @@ -61,35 +60,17 @@ class ContentExampleLoadingDescriptor extends ExampleLoadingDescriptor { return map['name']?.toString(); } - static SDK? _tryParseSdk(Map map) { - return SDK.tryParse(map['sdk']); + static Sdk? _tryParseSdk(Map map) { + return Sdk.tryParse(map['sdk']); } @override - int get hashCode => Object.hash( - content.hashCode, - sdk.hashCode, - ); - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - - return other is ContentExampleLoadingDescriptor && - content == other.content && - name == other.name && - sdk == other.sdk; - } - - @override - ExampleOrigin get origin => ExampleOrigin.content; + List get props => [content, sdk.id]; @override Map toJson() => { 'content': content, 'name': name, - 'sdk': sdk.name, + 'sdk': sdk.id, }; } diff --git a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/empty_example_loading_descriptor.dart b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/empty_example_loading_descriptor.dart new file mode 100644 index 0000000000000..74721712b66a4 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/empty_example_loading_descriptor.dart @@ -0,0 +1,31 @@ +/* + * 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 '../sdk.dart'; +import 'example_loading_descriptor.dart'; + +class EmptyExampleLoadingDescriptor extends ExampleLoadingDescriptor { + final Sdk sdk; + + const EmptyExampleLoadingDescriptor({ + required this.sdk, + }); + + @override + List get props => [sdk]; +} diff --git a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/example_loading_descriptor.dart similarity index 79% rename from playground/frontend/lib/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart rename to playground/frontend/playground_components/lib/src/models/example_loading_descriptors/example_loading_descriptor.dart index 4867544e50560..35c3cf18100ac 100644 --- a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart +++ b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/example_loading_descriptor.dart @@ -16,15 +16,10 @@ * limitations under the License. */ -import 'package:playground/modules/examples/models/example_origin.dart'; +import 'package:equatable/equatable.dart'; -abstract class ExampleLoadingDescriptor { +abstract class ExampleLoadingDescriptor with EquatableMixin { const ExampleLoadingDescriptor(); - ExampleOrigin get origin; - - @override - String toString() => '$origin'; - - Map toJson(); + Map toJson() => throw UnimplementedError(); } diff --git a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/examples_loading_descriptor.dart b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/examples_loading_descriptor.dart similarity index 88% rename from playground/frontend/lib/modules/examples/models/example_loading_descriptors/examples_loading_descriptor.dart rename to playground/frontend/playground_components/lib/src/models/example_loading_descriptors/examples_loading_descriptor.dart index 0fce67a0d2439..6a221376c3b82 100644 --- a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/examples_loading_descriptor.dart +++ b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/examples_loading_descriptor.dart @@ -17,21 +17,24 @@ */ import 'package:collection/collection.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:meta/meta.dart'; +import '../sdk.dart'; +import 'example_loading_descriptor.dart'; + +@immutable class ExamplesLoadingDescriptor { /// The descriptors to be loaded right away. final List descriptors; /// The descriptors to be loaded when an SDK is selected /// that has nothing loaded yet. - final Map> lazyLoadDescriptors; + final Map> lazyLoadDescriptors; /// If set, sets the SDK to this and does not change it when loading /// new examples. Otherwise sets the SDK to that of each loaded example /// of [descriptors]. - final SDK? initialSdk; + final Sdk? initialSdk; const ExamplesLoadingDescriptor({ required this.descriptors, @@ -46,7 +49,7 @@ class ExamplesLoadingDescriptor { buffer.write(descriptors.map((e) => e.toString()).join('_')); for (final descriptor in lazyLoadDescriptors.entries) { - buffer.write(', Lazy Load ${descriptor.key.name}: '); + buffer.write(', Lazy Load ${descriptor.key.id}: '); buffer.write(descriptor.value.map((e) => e.toString()).join('_')); } diff --git a/playground/frontend/lib/modules/examples/repositories/models/get_list_of_examples_response.dart b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/standard_example_loading_descriptor.dart similarity index 75% rename from playground/frontend/lib/modules/examples/repositories/models/get_list_of_examples_response.dart rename to playground/frontend/playground_components/lib/src/models/example_loading_descriptors/standard_example_loading_descriptor.dart index 609c61df3ad04..8ac57c624a92a 100644 --- a/playground/frontend/lib/modules/examples/repositories/models/get_list_of_examples_response.dart +++ b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/standard_example_loading_descriptor.dart @@ -16,11 +16,15 @@ * limitations under the License. */ -import 'package:playground/modules/examples/models/category_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'example_loading_descriptor.dart'; -class GetListOfExampleResponse { - final Map> categories; +class StandardExampleLoadingDescriptor extends ExampleLoadingDescriptor { + final String path; - GetListOfExampleResponse(this.categories); + const StandardExampleLoadingDescriptor({ + required this.path, + }); + + @override + List get props => [path]; } diff --git a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart new file mode 100644 index 0000000000000..1bcbe0dc60af1 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart @@ -0,0 +1,30 @@ +/* + * 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 'example_loading_descriptor.dart'; + +class UserSharedExampleLoadingDescriptor extends ExampleLoadingDescriptor { + final String snippetId; + + const UserSharedExampleLoadingDescriptor({ + required this.snippetId, + }); + + @override + List get props => [snippetId]; +} diff --git a/playground/frontend/playground_components/lib/src/models/intents.dart b/playground/frontend/playground_components/lib/src/models/intents.dart new file mode 100644 index 0000000000000..74ef7c4c557ee --- /dev/null +++ b/playground/frontend/playground_components/lib/src/models/intents.dart @@ -0,0 +1,35 @@ +/* + * 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/widgets.dart'; + +class BeamIntent extends Intent { + final String slug; + + const BeamIntent({ + required this.slug, + }); +} + +class ResetIntent extends BeamIntent { + const ResetIntent() : super(slug: 'intents.playground.reset'); +} + +class RunIntent extends BeamIntent { + const RunIntent() : super(slug: 'intents.playground.run'); +} diff --git a/playground/frontend/lib/modules/examples/models/outputs_model.dart b/playground/frontend/playground_components/lib/src/models/outputs.dart similarity index 89% rename from playground/frontend/lib/modules/examples/models/outputs_model.dart rename to playground/frontend/playground_components/lib/src/models/outputs.dart index 19f2feaa50bda..fa1a360063c01 100644 --- a/playground/frontend/lib/modules/examples/models/outputs_model.dart +++ b/playground/frontend/playground_components/lib/src/models/outputs.dart @@ -23,10 +23,14 @@ enum OutputType { graph, } -class OutputsModel { +class Outputs { final String output; final String graph; final String log; - OutputsModel(this.output, this.graph, this.log); + const Outputs({ + required this.output, + required this.graph, + required this.log, + }); } diff --git a/playground/frontend/playground_components/lib/src/models/sdk.dart b/playground/frontend/playground_components/lib/src/models/sdk.dart new file mode 100644 index 0000000000000..5d1cc49f79284 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/models/sdk.dart @@ -0,0 +1,96 @@ +/* + * 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:collection/collection.dart'; +import 'package:equatable/equatable.dart'; +import 'package:highlight/highlight_core.dart'; +import 'package:highlight/languages/go.dart' as mode_go; +import 'package:highlight/languages/java.dart' as mode_java; +import 'package:highlight/languages/python.dart' as mode_python; +import 'package:highlight/languages/scala.dart' as mode_scala; + +class Sdk with EquatableMixin { + final String id; + final String title; + + const Sdk({ + required this.id, + required this.title, + }); + + static const java = Sdk(id: 'java', title: 'Java'); + static const go = Sdk(id: 'go', title: 'Go'); + static const python = Sdk(id: 'python', title: 'Python'); + static const scio = Sdk(id: 'scio', title: 'SCIO'); + + static const known = [ + java, + go, + python, + scio, + ]; + + @override + List get props => [ + id, + title, + ]; + + /// A temporary solution while we wait for the backend to add + /// sdk in example responses. + static Sdk? tryParseExamplePath(String? path) { + if (path == null) { + return null; + } + + if (path.startsWith('SDK_JAVA')) { + return java; + } + + if (path.startsWith('SDK_GO')) { + return go; + } + + if (path.startsWith('SDK_PYTHON')) { + return python; + } + + if (path.startsWith('SDK_SCIO')) { + return scio; + } + + return null; + } + + static Sdk? tryParse(Object? value) { + if (value is! String) { + return null; + } + + return known.firstWhereOrNull((e) => e.id == value); + } + + static final _idToHighlightMode = { + Sdk.java.id: mode_java.java, + Sdk.go.id: mode_go.go, + Sdk.python.id: mode_python.python, + Sdk.scio.id: mode_scala.scala, + }; + + Mode? get highlightMode => _idToHighlightMode[id]; +} diff --git a/playground/frontend/lib/modules/shortcuts/utils/shortcuts_display_name.dart b/playground/frontend/playground_components/lib/src/models/shortcut.dart similarity index 56% rename from playground/frontend/lib/modules/shortcuts/utils/shortcuts_display_name.dart rename to playground/frontend/playground_components/lib/src/models/shortcut.dart index eff27c1c446cf..ff4ede80f91d4 100644 --- a/playground/frontend/lib/modules/shortcuts/utils/shortcuts_display_name.dart +++ b/playground/frontend/playground_components/lib/src/models/shortcut.dart @@ -16,21 +16,35 @@ * limitations under the License. */ +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:playground/modules/shortcuts/models/shortcut.dart'; -const kMetaKeyName = 'CMD/CTRL'; -const kShortcutKeyJoinSymbol = ' + '; +import 'intents.dart'; -String getShortcutDisplayName(Shortcut shortcut) { - return shortcut.shortcuts.keys - .map((e) => getKeyDisplayName(e)) - .join(kShortcutKeyJoinSymbol); -} +class BeamShortcut { + final LogicalKeySet shortcuts; + final BeamIntent actionIntent; + final CallbackAction Function(BuildContext) createAction; + + BeamShortcut({ + required this.shortcuts, + required this.actionIntent, + required this.createAction, + }); + + static const _metaKeyName = 'CMD/CTRL'; + static const _glue = ' + '; + + String get title { + return shortcuts.keys + .map(getKeyDisplayName) + .join(_glue); + } -String getKeyDisplayName(LogicalKeyboardKey e) { - if (e.keyId == LogicalKeyboardKey.meta.keyId) { - return kMetaKeyName; + String getKeyDisplayName(LogicalKeyboardKey e) { + if (e.keyId == LogicalKeyboardKey.meta.keyId) { + return _metaKeyName; + } + return e.keyLabel; } - return e.keyLabel; } diff --git a/playground/frontend/lib/modules/notifications/components/base_notification.dart b/playground/frontend/playground_components/lib/src/notifications/base_notification.dart similarity index 82% rename from playground/frontend/lib/modules/notifications/components/base_notification.dart rename to playground/frontend/playground_components/lib/src/notifications/base_notification.dart index e115f8dc578d2..fe23950959ca1 100644 --- a/playground/frontend/lib/modules/notifications/components/base_notification.dart +++ b/playground/frontend/playground_components/lib/src/notifications/base_notification.dart @@ -18,8 +18,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:playground/constants/font_weight.dart'; -import 'package:playground/constants/sizes.dart'; + +import '../constants/sizes.dart'; const kNotificationBorderWidth = 4.0; const kMaxTextWidth = 300.0; @@ -31,12 +31,12 @@ class BaseNotification extends StatelessWidget { final String asset; const BaseNotification({ - Key? key, + super.key, required this.title, required this.notification, required this.color, required this.asset, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -58,8 +58,8 @@ class BaseNotification extends StatelessWidget { decoration: BoxDecoration( color: color, borderRadius: const BorderRadius.only( - topLeft: Radius.circular(kLgBorderRadius), - bottomLeft: Radius.circular(kLgBorderRadius), + topLeft: Radius.circular(BeamSizes.size8), + bottomLeft: Radius.circular(BeamSizes.size8), ), ), ), @@ -70,19 +70,18 @@ class BaseNotification extends StatelessWidget { final textTheme = Theme.of(context).textTheme.bodyText1; return Positioned( child: Padding( - padding: const EdgeInsets.all(kLgSpacing), + padding: const EdgeInsets.all(BeamSizes.size12), child: Row( - crossAxisAlignment: CrossAxisAlignment.center, children: [ SvgPicture.asset(asset), - const SizedBox(width: kLgSpacing), + const SizedBox(width: BeamSizes.size12), Wrap( direction: Axis.vertical, - spacing: kSmSpacing, + spacing: BeamSizes.size4, children: [ Text( title, - style: textTheme?.copyWith(fontWeight: kBoldWeight), + style: textTheme?.copyWith(fontWeight: FontWeight.w600), ), SizedBox( width: kMaxTextWidth, diff --git a/playground/frontend/lib/modules/notifications/components/notification.dart b/playground/frontend/playground_components/lib/src/notifications/notification.dart similarity index 82% rename from playground/frontend/lib/modules/notifications/components/notification.dart rename to playground/frontend/playground_components/lib/src/notifications/notification.dart index 9fcb25e8af3b3..264361174c56a 100644 --- a/playground/frontend/lib/modules/notifications/components/notification.dart +++ b/playground/frontend/playground_components/lib/src/notifications/notification.dart @@ -18,9 +18,10 @@ import 'package:aligned_dialog/aligned_dialog.dart'; import 'package:flutter/material.dart'; -import 'package:playground/constants/assets.dart'; -import 'package:playground/constants/colors.dart'; -import 'package:playground/modules/notifications/components/base_notification.dart'; + +import '../constants/colors.dart'; +import '../generated/assets.gen.dart'; +import 'base_notification.dart'; const kDialogOffset = Offset(0, 30); @@ -35,8 +36,8 @@ class NotificationManager { BaseNotification( title: title, notification: notification, - color: kErrorNotificationColor, - asset: kErrorNotificationIconAsset, + color: BeamNotificationColors.error, + asset: Assets.notificationIcons.error, ), ); } @@ -51,8 +52,8 @@ class NotificationManager { BaseNotification( title: title, notification: notification, - color: kInfoNotificationColor, - asset: kInfoNotificationIconAsset, + color: BeamNotificationColors.info, + asset: Assets.notificationIcons.info, ), ); } @@ -67,8 +68,8 @@ class NotificationManager { BaseNotification( title: title, notification: notification, - color: kWarningNotificationColor, - asset: kWarningNotificationIconAsset, + color: BeamNotificationColors.warning, + asset: Assets.notificationIcons.warning, ), ); } @@ -83,8 +84,8 @@ class NotificationManager { BaseNotification( title: title, notification: notification, - color: kSuccessNotificationColor, - asset: kSuccessNotificationIconAsset, + color: BeamNotificationColors.success, + asset: Assets.notificationIcons.success, ), ); } diff --git a/playground/frontend/lib/modules/editor/repository/code_repository/code_client/code_client.dart b/playground/frontend/playground_components/lib/src/repositories/code_client/code_client.dart similarity index 66% rename from playground/frontend/lib/modules/editor/repository/code_repository/code_client/code_client.dart rename to playground/frontend/playground_components/lib/src/repositories/code_client/code_client.dart index 849429c120af9..2024e0aa82b81 100644 --- a/playground/frontend/lib/modules/editor/repository/code_repository/code_client/code_client.dart +++ b/playground/frontend/playground_components/lib/src/repositories/code_client/code_client.dart @@ -16,53 +16,45 @@ * limitations under the License. */ -import 'package:playground/modules/editor/repository/code_repository/code_client/check_status_response.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/output_response.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/run_code_response.dart'; -import 'package:playground/modules/editor/repository/code_repository/run_code_request.dart'; +import '../models/check_status_response.dart'; +import '../models/output_response.dart'; +import '../models/run_code_request.dart'; +import '../models/run_code_response.dart'; abstract class CodeClient { - Future runCode(RunCodeRequestWrapper request); + Future runCode(RunCodeRequest request); Future cancelExecution(String pipelineUuid); Future checkStatus( String pipelineUuid, - RunCodeRequestWrapper request, ); Future getCompileOutput( String pipelineUuid, - RunCodeRequestWrapper request, ); Future getRunOutput( String pipelineUuid, - RunCodeRequestWrapper request, ); Future getLogOutput( String pipelineUuid, - RunCodeRequestWrapper request, ); Future getRunErrorOutput( String pipelineUuid, - RunCodeRequestWrapper request, ); Future getValidationErrorOutput( String pipelineUuid, - RunCodeRequestWrapper request, ); Future getPreparationErrorOutput( String pipelineUuid, - RunCodeRequestWrapper request, ); Future getGraphOutput( String pipelineUuid, - RunCodeRequestWrapper request, ); } diff --git a/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart b/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart new file mode 100644 index 0000000000000..729ce4cd19f9e --- /dev/null +++ b/playground/frontend/playground_components/lib/src/repositories/code_client/grpc_code_client.dart @@ -0,0 +1,256 @@ +/* + * 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:grpc/grpc.dart'; + +import '../../api/iis_workaround_channel.dart'; +import '../../api/v1/api.pbgrpc.dart' as grpc; +import '../../models/sdk.dart'; +import '../../util/pipeline_options.dart'; +import '../../util/replace_incorrect_symbols.dart'; +import '../models/check_status_response.dart'; +import '../models/output_response.dart'; +import '../models/run_code_error.dart'; +import '../models/run_code_request.dart'; +import '../models/run_code_response.dart'; +import '../models/run_code_result.dart'; +import '../sdk_grpc_extension.dart'; +import 'code_client.dart'; + +const kGeneralError = 'Failed to execute code'; + +class GrpcCodeClient implements CodeClient { + final grpc.PlaygroundServiceClient _defaultClient; + final Map _runnerUrlsById; + + factory GrpcCodeClient({ + required String url, + required Map runnerUrlsById, + }) { + final channel = IisWorkaroundChannel.xhr( + Uri.parse(url), + ); + + return GrpcCodeClient._( + client: grpc.PlaygroundServiceClient(channel), + runnerUrlsById: runnerUrlsById, + ); + } + + GrpcCodeClient._({ + required grpc.PlaygroundServiceClient client, + required Map runnerUrlsById, + }) : _defaultClient = client, + _runnerUrlsById = runnerUrlsById; + + @override + Future runCode(RunCodeRequest request) async { + final client = _createRunCodeClient(request.sdk); + final response = await _runSafely( + () => client.runCode(_grpcRunCodeRequest(request)), + ); + + return RunCodeResponse( + pipelineUuid: response.pipelineUuid, + ); + } + + @override + Future cancelExecution(String pipelineUuid) { + return _runSafely(() => + _defaultClient.cancel(grpc.CancelRequest(pipelineUuid: pipelineUuid))); + } + + @override + Future checkStatus( + String pipelineUuid, + ) async { + final response = await _runSafely( + () => _defaultClient.checkStatus( + grpc.CheckStatusRequest(pipelineUuid: pipelineUuid), + ), + ); + + return CheckStatusResponse( + status: _toClientStatus(response.status), + ); + } + + @override + Future getCompileOutput( + String pipelineUuid, + ) async { + final response = await _runSafely( + () => _defaultClient.getCompileOutput( + grpc.GetCompileOutputRequest(pipelineUuid: pipelineUuid), + ), + ); + + return _toOutputResponse(response.output); + } + + @override + Future getRunOutput( + String pipelineUuid, + ) async { + try { + final response = await _runSafely( + () => _defaultClient.getRunOutput( + grpc.GetRunOutputRequest(pipelineUuid: pipelineUuid), + ), + ); + + return _toOutputResponse(response.output); + } catch (ex) { + print(ex); + return _toOutputResponse(''); + } + } + + @override + Future getLogOutput( + String pipelineUuid, + ) async { + try { + final response = await _defaultClient.getLogs( + grpc.GetLogsRequest(pipelineUuid: pipelineUuid), + ); + + return _toOutputResponse(response.output); + } catch (ex) { + print(ex); + return _toOutputResponse(''); + } + } + + @override + Future getRunErrorOutput( + String pipelineUuid, + ) async { + final response = await _defaultClient.getRunError( + grpc.GetRunErrorRequest(pipelineUuid: pipelineUuid), + ); + + return _toOutputResponse(response.output); + } + + @override + Future getValidationErrorOutput( + String pipelineUuid, + ) async { + final response = await _defaultClient.getValidationOutput( + grpc.GetValidationOutputRequest(pipelineUuid: pipelineUuid), + ); + + return _toOutputResponse(response.output); + } + + @override + Future getPreparationErrorOutput( + String pipelineUuid, + ) async { + final response = await _defaultClient.getPreparationOutput( + grpc.GetPreparationOutputRequest(pipelineUuid: pipelineUuid), + ); + + return _toOutputResponse(response.output); + } + + @override + Future getGraphOutput( + String pipelineUuid, + ) async { + try { + final response = await _defaultClient.getGraph( + grpc.GetGraphRequest(pipelineUuid: pipelineUuid), + ); + + return OutputResponse(output: response.graph); + } catch (ex) { + print(ex); + return _toOutputResponse(''); + } + } + + Future _runSafely(Future Function() invoke) async { + try { + return await invoke(); + } on GrpcError catch (error) { + throw RunCodeError(message: error.message); + } on Exception catch (_) { + throw const RunCodeError(); + } + } + + /// Run Code request should use different urls for each sdk + /// instead of the default one, because we need to code + /// sdk services for it + grpc.PlaygroundServiceClient _createRunCodeClient(Sdk sdk) { + final apiClientURL = _runnerUrlsById[sdk.id]; + + if (apiClientURL == null) { + throw Exception('Runner not found for ${sdk.id}'); + } + + final channel = IisWorkaroundChannel.xhr( + Uri.parse(apiClientURL), + ); + return grpc.PlaygroundServiceClient(channel); + } + + grpc.RunCodeRequest _grpcRunCodeRequest(RunCodeRequest request) { + return grpc.RunCodeRequest() + ..code = request.code + ..sdk = request.sdk.grpc + ..pipelineOptions = pipelineOptionsToString(request.pipelineOptions); + } + + RunCodeStatus _toClientStatus(grpc.Status status) { + switch (status) { + case grpc.Status.STATUS_UNSPECIFIED: + return RunCodeStatus.unspecified; + case grpc.Status.STATUS_VALIDATING: + case grpc.Status.STATUS_PREPARING: + return RunCodeStatus.preparation; + case grpc.Status.STATUS_COMPILING: + return RunCodeStatus.compiling; + case grpc.Status.STATUS_EXECUTING: + return RunCodeStatus.executing; + case grpc.Status.STATUS_CANCELED: + case grpc.Status.STATUS_FINISHED: + return RunCodeStatus.finished; + case grpc.Status.STATUS_COMPILE_ERROR: + return RunCodeStatus.compileError; + case grpc.Status.STATUS_RUN_TIMEOUT: + return RunCodeStatus.timeout; + case grpc.Status.STATUS_RUN_ERROR: + return RunCodeStatus.runError; + case grpc.Status.STATUS_VALIDATION_ERROR: + return RunCodeStatus.validationError; + case grpc.Status.STATUS_PREPARATION_ERROR: + return RunCodeStatus.preparationError; + case grpc.Status.STATUS_ERROR: + return RunCodeStatus.unknownError; + } + return RunCodeStatus.unspecified; + } + + OutputResponse _toOutputResponse(String response) { + return OutputResponse(output: replaceIncorrectSymbols(response)); + } +} diff --git a/playground/frontend/lib/modules/editor/repository/code_repository/code_repository.dart b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart similarity index 76% rename from playground/frontend/lib/modules/editor/repository/code_repository/code_repository.dart rename to playground/frontend/playground_components/lib/src/repositories/code_repository.dart index 37ad6ee0d1fe1..c9efc3146c09c 100644 --- a/playground/frontend/lib/modules/editor/repository/code_repository/code_repository.dart +++ b/playground/frontend/playground_components/lib/src/repositories/code_repository.dart @@ -16,12 +16,12 @@ * limitations under the License. */ -import 'package:playground/modules/editor/repository/code_repository/code_client/code_client.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/output_response.dart'; -import 'package:playground/modules/editor/repository/code_repository/run_code_error.dart'; -import 'package:playground/modules/editor/repository/code_repository/run_code_request.dart'; -import 'package:playground/modules/editor/repository/code_repository/run_code_result.dart'; -import 'package:playground/utils/run_with_retry.dart'; +import '../util/run_with_retry.dart'; +import 'code_client/code_client.dart'; +import 'models/output_response.dart'; +import 'models/run_code_error.dart'; +import 'models/run_code_request.dart'; +import 'models/run_code_result.dart'; const kPipelineCheckDelay = Duration(seconds: 1); const kTimeoutErrorText = @@ -33,25 +33,25 @@ const kUnknownErrorText = 'Something went wrong. Please try again later or create a GitHub issue'; const kProcessingStartedText = 'The processing has started\n'; +// TODO(alexeyinkin): Rename. This is not a repository but a higher level client. class CodeRepository { - late final CodeClient _client; + final CodeClient _client; - CodeRepository(CodeClient client) { - _client = client; - } + CodeRepository({required CodeClient client,}): _client = client; - Stream runCode(RunCodeRequestWrapper request) async* { + Stream runCode(RunCodeRequest request) async* { try { - final initResult = RunCodeResult( + const initResult = RunCodeResult( status: RunCodeStatus.preparation, log: kProcessingStartedText, ); yield initResult; - var runCodeResponse = await _client.runCode(request); + + final runCodeResponse = await _client.runCode(request); final pipelineUuid = runCodeResponse.pipelineUuid; + yield* _checkPipelineExecution( pipelineUuid, - request, prevResult: initResult, ); } on RunCodeError catch (error) { @@ -68,26 +68,23 @@ class CodeRepository { } Stream _checkPipelineExecution( - String pipelineUuid, - RunCodeRequestWrapper request, { + String pipelineUuid, { RunCodeResult? prevResult, }) async* { try { final statusResponse = await runWithRetry( - () => _client.checkStatus(pipelineUuid, request), + () => _client.checkStatus(pipelineUuid), ); final result = await _getPipelineResult( pipelineUuid, statusResponse.status, prevResult, - request, ); yield result; if (!result.isFinished) { await Future.delayed(kPipelineCheckDelay); yield* _checkPipelineExecution( pipelineUuid, - request, prevResult: result, ); } @@ -105,17 +102,14 @@ class CodeRepository { String pipelineUuid, RunCodeStatus status, RunCodeResult? prevResult, - RunCodeRequestWrapper request, ) async { final prevOutput = prevResult?.output ?? ''; final prevLog = prevResult?.log ?? ''; final prevGraph = prevResult?.graph ?? ''; + switch (status) { case RunCodeStatus.compileError: - final compileOutput = await _client.getCompileOutput( - pipelineUuid, - request, - ); + final compileOutput = await _client.getCompileOutput(pipelineUuid); return RunCodeResult( pipelineUuid: pipelineUuid, status: status, @@ -123,6 +117,7 @@ class CodeRepository { log: prevLog, graph: prevGraph, ); + case RunCodeStatus.timeout: return RunCodeResult( pipelineUuid: pipelineUuid, @@ -132,8 +127,9 @@ class CodeRepository { log: prevLog, graph: prevGraph, ); + case RunCodeStatus.runError: - final output = await _client.getRunErrorOutput(pipelineUuid, request); + final output = await _client.getRunErrorOutput(pipelineUuid); return RunCodeResult( pipelineUuid: pipelineUuid, status: status, @@ -141,24 +137,27 @@ class CodeRepository { log: prevLog, graph: prevGraph, ); + case RunCodeStatus.validationError: final output = - await _client.getValidationErrorOutput(pipelineUuid, request); + await _client.getValidationErrorOutput(pipelineUuid); return RunCodeResult( status: status, output: output.output, log: prevLog, graph: prevGraph, ); + case RunCodeStatus.preparationError: final output = - await _client.getPreparationErrorOutput(pipelineUuid, request); + await _client.getPreparationErrorOutput(pipelineUuid); return RunCodeResult( status: status, output: output.output, log: prevLog, graph: prevGraph, ); + case RunCodeStatus.unknownError: return RunCodeResult( pipelineUuid: pipelineUuid, @@ -168,13 +167,14 @@ class CodeRepository { log: prevLog, graph: prevGraph, ); + case RunCodeStatus.executing: final responses = await Future.wait([ - _client.getRunOutput(pipelineUuid, request), - _client.getLogOutput(pipelineUuid, request), + _client.getRunOutput(pipelineUuid), + _client.getLogOutput(pipelineUuid), prevGraph.isEmpty - ? _client.getGraphOutput(pipelineUuid, request) - : Future.value(OutputResponse(prevGraph)), + ? _client.getGraphOutput(pipelineUuid) + : Future.value(OutputResponse(output: prevGraph)), ]); final output = responses[0]; final log = responses[1]; @@ -186,14 +186,15 @@ class CodeRepository { log: prevLog + log.output, graph: graph.output, ); + case RunCodeStatus.finished: final responses = await Future.wait([ - _client.getRunOutput(pipelineUuid, request), - _client.getLogOutput(pipelineUuid, request), - _client.getRunErrorOutput(pipelineUuid, request), + _client.getRunOutput(pipelineUuid), + _client.getLogOutput(pipelineUuid), + _client.getRunErrorOutput(pipelineUuid), prevGraph.isEmpty - ? _client.getGraphOutput(pipelineUuid, request) - : Future.value(OutputResponse(prevGraph)), + ? _client.getGraphOutput(pipelineUuid) + : Future.value(OutputResponse(output: prevGraph)), ]); final output = responses[0]; final log = responses[1]; @@ -206,6 +207,7 @@ class CodeRepository { log: prevLog + log.output, graph: graph.output, ); + default: return RunCodeResult( pipelineUuid: pipelineUuid, diff --git a/playground/frontend/playground_components/lib/src/repositories/example_client/example_client.dart b/playground/frontend/playground_components/lib/src/repositories/example_client/example_client.dart new file mode 100644 index 0000000000000..d2bc5a792a93a --- /dev/null +++ b/playground/frontend/playground_components/lib/src/repositories/example_client/example_client.dart @@ -0,0 +1,67 @@ +/* + * 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 '../models/get_default_precompiled_object_request.dart'; +import '../models/get_precompiled_object_code_response.dart'; +import '../models/get_precompiled_object_request.dart'; +import '../models/get_precompiled_object_response.dart'; +import '../models/get_precompiled_objects_request.dart'; +import '../models/get_precompiled_objects_response.dart'; +import '../models/get_snippet_request.dart'; +import '../models/get_snippet_response.dart'; +import '../models/output_response.dart'; +import '../models/save_snippet_request.dart'; +import '../models/save_snippet_response.dart'; + +abstract class ExampleClient { + Future getPrecompiledObjects( + GetPrecompiledObjectsRequest request, + ); + + Future getPrecompiledObjectCode( + GetPrecompiledObjectRequest request, + ); + + Future getDefaultPrecompiledObject( + GetDefaultPrecompiledObjectRequest request, + ); + + Future getPrecompiledObject( + GetPrecompiledObjectRequest request, + ); + + Future getPrecompiledObjectOutput( + GetPrecompiledObjectRequest request, + ); + + Future getPrecompiledObjectLogs( + GetPrecompiledObjectRequest request, + ); + + Future getPrecompiledObjectGraph( + GetPrecompiledObjectRequest request, + ); + + Future getSnippet( + GetSnippetRequest request, + ); + + Future saveSnippet( + SaveSnippetRequest request, + ); +} diff --git a/playground/frontend/playground_components/lib/src/repositories/example_client/grpc_example_client.dart b/playground/frontend/playground_components/lib/src/repositories/example_client/grpc_example_client.dart new file mode 100644 index 0000000000000..ceaa08a9bdf5b --- /dev/null +++ b/playground/frontend/playground_components/lib/src/repositories/example_client/grpc_example_client.dart @@ -0,0 +1,376 @@ +/* + * 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:grpc/grpc.dart'; + +import '../../api/iis_workaround_channel.dart'; +import '../../api/v1/api.pbgrpc.dart' as grpc; +import '../../models/category_with_examples.dart'; +import '../../models/example_base.dart'; +import '../../models/sdk.dart'; +import '../../util/replace_incorrect_symbols.dart'; +import '../models/get_default_precompiled_object_request.dart'; +import '../models/get_precompiled_object_code_response.dart'; +import '../models/get_precompiled_object_request.dart'; +import '../models/get_precompiled_object_response.dart'; +import '../models/get_precompiled_objects_request.dart'; +import '../models/get_precompiled_objects_response.dart'; +import '../models/get_snippet_request.dart'; +import '../models/get_snippet_response.dart'; +import '../models/output_response.dart'; +import '../models/save_snippet_request.dart'; +import '../models/save_snippet_response.dart'; +import '../models/shared_file.dart'; +import '../sdk_grpc_extension.dart'; +import 'example_client.dart'; + +class GrpcExampleClient implements ExampleClient { + final grpc.PlaygroundServiceClient _defaultClient; + + factory GrpcExampleClient({ + required String url, + }) { + final channel = IisWorkaroundChannel.xhr( + Uri.parse(url), + ); + + return GrpcExampleClient._( + client: grpc.PlaygroundServiceClient(channel), + ); + } + + GrpcExampleClient._({ + required grpc.PlaygroundServiceClient client, + }) : _defaultClient = client; + + @override + Future getPrecompiledObjects( + GetPrecompiledObjectsRequest request, + ) async { + final response = await _runSafely( + () => _defaultClient.getPrecompiledObjects( + _grpcGetPrecompiledObjectsRequest(request), + ), + ); + return GetPrecompiledObjectsResponse( + categories: _toClientCategories(response.sdkCategories), + ); + } + + @override + Future getDefaultPrecompiledObject( + GetDefaultPrecompiledObjectRequest request, + ) async { + final response = await _runSafely( + () => _defaultClient.getDefaultPrecompiledObject( + _grpcGetDefaultPrecompiledObjectRequest(request), + ), + ); + + return GetPrecompiledObjectResponse( + example: _toExampleModel( + request.sdk, + response.precompiledObject, + ), + ); + } + + @override + Future getPrecompiledObject( + GetPrecompiledObjectRequest request, + ) async { + final response = await _runSafely( + () => _defaultClient.getPrecompiledObject( + grpc.GetPrecompiledObjectRequest()..cloudPath = request.path, + ), + ); + + return GetPrecompiledObjectResponse( + example: _toExampleModel( + request.sdk, + response.precompiledObject, + ), + ); + } + + @override + Future getPrecompiledObjectCode( + GetPrecompiledObjectRequest request, + ) async { + final response = await _runSafely( + () => _defaultClient.getPrecompiledObjectCode( + _grpcGetPrecompiledObjectRequest(request), + ), + ); + + return GetPrecompiledObjectCodeResponse( + code: replaceIncorrectSymbols(response.code), + ); + } + + @override + Future getPrecompiledObjectOutput( + GetPrecompiledObjectRequest request, + ) async { + try { + final response = await _runSafely( + () => _defaultClient.getPrecompiledObjectOutput( + _grpcGetPrecompiledObjectOutputRequest(request), + ), + ); + + return OutputResponse( + output: replaceIncorrectSymbols(response.output), + ); + } catch (ex) { + print(ex); + return OutputResponse( + output: '', + ); + } + } + + @override + Future getPrecompiledObjectLogs( + GetPrecompiledObjectRequest request, + ) async { + try { + final response = await _runSafely( + () => _defaultClient.getPrecompiledObjectLogs( + _grpcGetPrecompiledObjectLogRequest(request), + ), + ); + + return OutputResponse( + output: replaceIncorrectSymbols(response.output), + ); + } catch (ex) { + print(ex); + return OutputResponse( + output: '', + ); + } + } + + @override + Future getPrecompiledObjectGraph( + GetPrecompiledObjectRequest request, + ) async { + try { + final response = await _runSafely( + () => _defaultClient.getPrecompiledObjectGraph( + _grpcGetPrecompiledGraphRequest(request), + ), + ); + + return OutputResponse( + output: response.graph, + ); + } catch (ex) { + print(ex); + return OutputResponse( + output: '', + ); + } + } + + @override + Future getSnippet( + GetSnippetRequest request, + ) async { + final response = await _runSafely( + () => _defaultClient.getSnippet( + _grpcGetSnippetRequest(request), + ), + ); + + return GetSnippetResponse( + files: _convertToSharedFileList(response.files), + sdk: response.sdk.model, + pipelineOptions: response.pipelineOptions, + ); + } + + @override + Future saveSnippet( + SaveSnippetRequest request, + ) async { + final response = await _runSafely( + () => _defaultClient.saveSnippet( + _grpcSaveSnippetRequest(request), + ), + ); + + return SaveSnippetResponse( + id: response.id, + ); + } + + Future _runSafely(Future Function() invoke) async { + try { + return await invoke(); + } on GrpcError catch (error) { + throw Exception(error.message); + } + } + + grpc.GetPrecompiledObjectsRequest _grpcGetPrecompiledObjectsRequest( + GetPrecompiledObjectsRequest request, + ) { + return grpc.GetPrecompiledObjectsRequest() + ..category = request.category ?? '' + ..sdk = request.sdk?.grpc ?? grpc.Sdk.SDK_UNSPECIFIED; + } + + grpc.GetDefaultPrecompiledObjectRequest + _grpcGetDefaultPrecompiledObjectRequest( + GetDefaultPrecompiledObjectRequest request, + ) { + return grpc.GetDefaultPrecompiledObjectRequest()..sdk = request.sdk.grpc; + } + + grpc.GetPrecompiledObjectCodeRequest _grpcGetPrecompiledObjectRequest( + GetPrecompiledObjectRequest request, + ) { + return grpc.GetPrecompiledObjectCodeRequest()..cloudPath = request.path; + } + + grpc.GetPrecompiledObjectOutputRequest _grpcGetPrecompiledObjectOutputRequest( + GetPrecompiledObjectRequest request, + ) { + return grpc.GetPrecompiledObjectOutputRequest()..cloudPath = request.path; + } + + grpc.GetPrecompiledObjectLogsRequest _grpcGetPrecompiledObjectLogRequest( + GetPrecompiledObjectRequest request, + ) { + return grpc.GetPrecompiledObjectLogsRequest()..cloudPath = request.path; + } + + grpc.GetPrecompiledObjectGraphRequest _grpcGetPrecompiledGraphRequest( + GetPrecompiledObjectRequest request, + ) { + return grpc.GetPrecompiledObjectGraphRequest()..cloudPath = request.path; + } + + grpc.GetSnippetRequest _grpcGetSnippetRequest( + GetSnippetRequest request, + ) { + return grpc.GetSnippetRequest()..id = request.id; + } + + grpc.SaveSnippetRequest _grpcSaveSnippetRequest( + SaveSnippetRequest request, + ) { + return grpc.SaveSnippetRequest() + ..sdk = request.sdk.grpc + ..pipelineOptions = request.pipelineOptions + ..files.addAll(_convertToSnippetFileList(request.files)); + } + + ExampleType _exampleTypeFromString(grpc.PrecompiledObjectType type) { + switch (type) { + case grpc.PrecompiledObjectType.PRECOMPILED_OBJECT_TYPE_EXAMPLE: + return ExampleType.example; + case grpc.PrecompiledObjectType.PRECOMPILED_OBJECT_TYPE_KATA: + return ExampleType.kata; + case grpc.PrecompiledObjectType.PRECOMPILED_OBJECT_TYPE_UNIT_TEST: + return ExampleType.test; + case grpc.PrecompiledObjectType.PRECOMPILED_OBJECT_TYPE_UNSPECIFIED: + return ExampleType.all; + } + + return ExampleType.example; + } + + Map> _toClientCategories( + List response, + ) { + final result = >{}; + + for (final sdkMap in response) { + final sdk = sdkMap.sdk.model; + final categoriesForSdk = []; + + for (final category in sdkMap.categories) { + final examples = category.precompiledObjects + .map((example) => _toExampleModel(sdk, example)) + .toList(growable: false) + ..sort(); + + categoriesForSdk.add( + CategoryWithExamples( + title: category.categoryName, + examples: examples, + ), + ); + } + + result[sdk] = categoriesForSdk..sort(); + } + + return result; + } + + ExampleBase _toExampleModel(Sdk sdk, grpc.PrecompiledObject example) { + return ExampleBase( + sdk: sdk, + name: example.name, + description: example.description, + type: _exampleTypeFromString(example.type), + path: example.cloudPath, + contextLine: example.contextLine, + pipelineOptions: example.pipelineOptions, + isMultiFile: example.multifile, + link: example.link, + ); + } + + List _convertToSharedFileList( + List snippetFileList, + ) { + final sharedFilesList = []; + + for (final item in snippetFileList) { + sharedFilesList.add(SharedFile( + code: item.content, + isMain: item.isMain, + name: item.name, + )); + } + + return sharedFilesList; + } + + List _convertToSnippetFileList( + List sharedFilesList, + ) { + final snippetFileList = []; + + for (final item in sharedFilesList) { + snippetFileList.add( + grpc.SnippetFile() + ..name = item.name + ..isMain = true + ..content = item.code, + ); + } + + return snippetFileList; + } +} diff --git a/playground/frontend/playground_components/lib/src/repositories/example_repository.dart b/playground/frontend/playground_components/lib/src/repositories/example_repository.dart new file mode 100644 index 0000000000000..9cb624a3464b4 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/repositories/example_repository.dart @@ -0,0 +1,99 @@ +/* + * 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 '../models/category_with_examples.dart'; +import '../models/example_base.dart'; +import '../models/sdk.dart'; +import 'example_client/example_client.dart'; +import 'models/get_default_precompiled_object_request.dart'; +import 'models/get_precompiled_object_request.dart'; +import 'models/get_precompiled_objects_request.dart'; +import 'models/get_snippet_request.dart'; +import 'models/get_snippet_response.dart'; +import 'models/save_snippet_request.dart'; + +class ExampleRepository { + final ExampleClient _client; + + ExampleRepository({ + required ExampleClient client, + }) : _client = client; + + Future>> getListOfExamples( + GetPrecompiledObjectsRequest request, + ) async { + final result = await _client.getPrecompiledObjects(request); + return result.categories; + } + + Future getDefaultExample( + GetDefaultPrecompiledObjectRequest request, + ) async { + final result = await _client.getDefaultPrecompiledObject(request); + return result.example; + } + + Future getExampleSource( + GetPrecompiledObjectRequest request, + ) async { + final result = await _client.getPrecompiledObjectCode(request); + return result.code; + } + + Future getExampleOutput( + GetPrecompiledObjectRequest request, + ) async { + final result = await _client.getPrecompiledObjectOutput(request); + return result.output; + } + + Future getExampleLogs( + GetPrecompiledObjectRequest request, + ) async { + final result = await _client.getPrecompiledObjectLogs(request); + return result.output; + } + + Future getExampleGraph( + GetPrecompiledObjectRequest request, + ) async { + final result = await _client.getPrecompiledObjectGraph(request); + return result.output; + } + + Future getExample( + GetPrecompiledObjectRequest request, + ) async { + final result = await _client.getPrecompiledObject(request); + return result.example; + } + + Future getSnippet( + GetSnippetRequest request, + ) async { + final result = await _client.getSnippet(request); + return result; + } + + Future saveSnippet( + SaveSnippetRequest request, + ) async { + final result = await _client.saveSnippet(request); + return result.id; + } +} diff --git a/playground/frontend/lib/modules/editor/repository/code_repository/code_client/check_status_response.dart b/playground/frontend/playground_components/lib/src/repositories/models/check_status_response.dart similarity index 87% rename from playground/frontend/lib/modules/editor/repository/code_repository/code_client/check_status_response.dart rename to playground/frontend/playground_components/lib/src/repositories/models/check_status_response.dart index 7e77419305ceb..089a9a340bd4f 100644 --- a/playground/frontend/lib/modules/editor/repository/code_repository/code_client/check_status_response.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/check_status_response.dart @@ -16,10 +16,12 @@ * limitations under the License. */ -import 'package:playground/modules/editor/repository/code_repository/run_code_result.dart'; +import 'run_code_result.dart'; class CheckStatusResponse { final RunCodeStatus status; - CheckStatusResponse(this.status); + const CheckStatusResponse({ + required this.status, + }); } diff --git a/playground/frontend/playground_components/lib/src/repositories/models/get_default_precompiled_object_request.dart b/playground/frontend/playground_components/lib/src/repositories/models/get_default_precompiled_object_request.dart new file mode 100644 index 0000000000000..4b42dee695188 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/repositories/models/get_default_precompiled_object_request.dart @@ -0,0 +1,34 @@ +/* + * 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:equatable/equatable.dart'; + +import '../../models/sdk.dart'; + +class GetDefaultPrecompiledObjectRequest with EquatableMixin { + final Sdk sdk; + + const GetDefaultPrecompiledObjectRequest({ + required this.sdk, + }); + + @override + List get props => [ + sdk, + ]; +} diff --git a/playground/frontend/lib/configure_web.dart b/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_code_response.dart similarity index 85% rename from playground/frontend/lib/configure_web.dart rename to playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_code_response.dart index 5fa7eb6de546e..a7bbda2459d88 100644 --- a/playground/frontend/lib/configure_web.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_code_response.dart @@ -16,8 +16,10 @@ * limitations under the License. */ -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +class GetPrecompiledObjectCodeResponse { + final String code; -void configureApp() { - setUrlStrategy(PathUrlStrategy()); + const GetPrecompiledObjectCodeResponse({ + required this.code, + }); } diff --git a/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_request.dart b/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_request.dart new file mode 100644 index 0000000000000..95516e905a158 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_request.dart @@ -0,0 +1,39 @@ +/* + * 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:equatable/equatable.dart'; + +import '../../models/sdk.dart'; + +/// A data object for multiple requests querying separate fields +/// of a given example. +class GetPrecompiledObjectRequest with EquatableMixin { + final String path; + final Sdk sdk; + + const GetPrecompiledObjectRequest({ + required this.path, + required this.sdk, + }); + + @override + List get props => [ + path, + sdk, + ]; +} diff --git a/playground/frontend/lib/modules/examples/repositories/models/get_example_response.dart b/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_response.dart similarity index 81% rename from playground/frontend/lib/modules/examples/repositories/models/get_example_response.dart rename to playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_response.dart index 002c737a1a0ac..0c6ce3d5b59ed 100644 --- a/playground/frontend/lib/modules/examples/repositories/models/get_example_response.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_response.dart @@ -16,10 +16,12 @@ * limitations under the License. */ -import 'package:playground/modules/examples/models/example_model.dart'; +import '../../models/example_base.dart'; -class GetExampleResponse { - final ExampleModel example; +class GetPrecompiledObjectResponse { + final ExampleBase example; - GetExampleResponse(this.example); + const GetPrecompiledObjectResponse({ + required this.example, + }); } diff --git a/playground/frontend/lib/modules/shortcuts/models/shortcut.dart b/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_objects_request.dart similarity index 70% rename from playground/frontend/lib/modules/shortcuts/models/shortcut.dart rename to playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_objects_request.dart index 0aafe060b0524..c5c5b7246c31d 100644 --- a/playground/frontend/lib/modules/shortcuts/models/shortcut.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_objects_request.dart @@ -16,18 +16,22 @@ * limitations under the License. */ -import 'package:flutter/material.dart'; +import 'package:equatable/equatable.dart'; -class Shortcut { - final String name; - final LogicalKeySet shortcuts; - final Intent actionIntent; - final CallbackAction Function(BuildContext) createAction; +import '../../models/sdk.dart'; - Shortcut({ - required this.name, - required this.shortcuts, - required this.actionIntent, - required this.createAction, +class GetPrecompiledObjectsRequest with EquatableMixin { + final Sdk? sdk; + final String? category; + + const GetPrecompiledObjectsRequest({ + required this.sdk, + required this.category, }); + + @override + List get props => [ + sdk, + category, + ]; } diff --git a/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_objects_response.dart b/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_objects_response.dart new file mode 100644 index 0000000000000..69901df75c664 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_objects_response.dart @@ -0,0 +1,28 @@ +/* + * 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 '../../models/category_with_examples.dart'; +import '../../models/sdk.dart'; + +class GetPrecompiledObjectsResponse { + final Map> categories; + + const GetPrecompiledObjectsResponse({ + required this.categories, + }); +} diff --git a/playground/frontend/lib/modules/examples/repositories/models/get_snippet_request.dart b/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_request.dart similarity index 92% rename from playground/frontend/lib/modules/examples/repositories/models/get_snippet_request.dart rename to playground/frontend/playground_components/lib/src/repositories/models/get_snippet_request.dart index 4319884981d93..610cf0d67b747 100644 --- a/playground/frontend/lib/modules/examples/repositories/models/get_snippet_request.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_request.dart @@ -16,10 +16,10 @@ * limitations under the License. */ -class GetSnippetRequestWrapper { +class GetSnippetRequest { final String id; - const GetSnippetRequestWrapper({ + const GetSnippetRequest({ required this.id, }); } diff --git a/playground/frontend/lib/modules/examples/repositories/models/get_snippet_response.dart b/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart similarity index 86% rename from playground/frontend/lib/modules/examples/repositories/models/get_snippet_response.dart rename to playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart index 167f725a5fb8a..9cb9c8ec1280b 100644 --- a/playground/frontend/lib/modules/examples/repositories/models/get_snippet_response.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart @@ -16,12 +16,12 @@ * limitations under the License. */ -import 'package:playground/modules/examples/repositories/models/shared_file_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import '../../models/sdk.dart'; +import 'shared_file.dart'; class GetSnippetResponse { final List files; - final SDK sdk; + final Sdk sdk; final String pipelineOptions; const GetSnippetResponse({ diff --git a/playground/frontend/lib/modules/editor/repository/code_repository/code_client/output_response.dart b/playground/frontend/playground_components/lib/src/repositories/models/output_response.dart similarity index 87% rename from playground/frontend/lib/modules/editor/repository/code_repository/code_client/output_response.dart rename to playground/frontend/playground_components/lib/src/repositories/models/output_response.dart index c8835c707b23e..d33292cf33339 100644 --- a/playground/frontend/lib/modules/editor/repository/code_repository/code_client/output_response.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/output_response.dart @@ -16,8 +16,11 @@ * limitations under the License. */ +/// A common response for anything returning a single string. class OutputResponse { final String output; - OutputResponse(this.output); + const OutputResponse({ + required this.output, + }); } diff --git a/playground/frontend/lib/modules/editor/repository/code_repository/run_code_error.dart b/playground/frontend/playground_components/lib/src/repositories/models/run_code_error.dart similarity index 94% rename from playground/frontend/lib/modules/editor/repository/code_repository/run_code_error.dart rename to playground/frontend/playground_components/lib/src/repositories/models/run_code_error.dart index b9339612ea583..68379a2473bb2 100644 --- a/playground/frontend/lib/modules/editor/repository/code_repository/run_code_error.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/run_code_error.dart @@ -19,5 +19,7 @@ class RunCodeError implements Exception { final String? message; - RunCodeError(this.message); + const RunCodeError({ + this.message, + }); } diff --git a/playground/frontend/lib/modules/editor/repository/code_repository/run_code_request.dart b/playground/frontend/playground_components/lib/src/repositories/models/run_code_request.dart similarity index 88% rename from playground/frontend/lib/modules/editor/repository/code_repository/run_code_request.dart rename to playground/frontend/playground_components/lib/src/repositories/models/run_code_request.dart index aa09fcd0bffb4..16a1e74df4302 100644 --- a/playground/frontend/lib/modules/editor/repository/code_repository/run_code_request.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/run_code_request.dart @@ -16,14 +16,14 @@ * limitations under the License. */ -import 'package:playground/modules/sdk/models/sdk.dart'; +import '../../models/sdk.dart'; -class RunCodeRequestWrapper { +class RunCodeRequest { final String code; - final SDK sdk; + final Sdk sdk; final Map pipelineOptions; - RunCodeRequestWrapper({ + const RunCodeRequest({ required this.code, required this.sdk, required this.pipelineOptions, diff --git a/playground/frontend/lib/modules/editor/repository/code_repository/code_client/run_code_response.dart b/playground/frontend/playground_components/lib/src/repositories/models/run_code_response.dart similarity index 93% rename from playground/frontend/lib/modules/editor/repository/code_repository/code_client/run_code_response.dart rename to playground/frontend/playground_components/lib/src/repositories/models/run_code_response.dart index 2fe480828f67b..20f1a147972dc 100644 --- a/playground/frontend/lib/modules/editor/repository/code_repository/code_client/run_code_response.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/run_code_response.dart @@ -19,5 +19,7 @@ class RunCodeResponse { final String pipelineUuid; - RunCodeResponse(this.pipelineUuid); + const RunCodeResponse({ + required this.pipelineUuid, + }); } diff --git a/playground/frontend/lib/modules/editor/repository/code_repository/run_code_result.dart b/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart similarity index 75% rename from playground/frontend/lib/modules/editor/repository/code_repository/run_code_result.dart rename to playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart index 30a686e73721a..5a3bcd8769168 100644 --- a/playground/frontend/lib/modules/editor/repository/code_repository/run_code_result.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/run_code_result.dart @@ -16,7 +16,7 @@ * limitations under the License. */ -import 'package:flutter/material.dart'; +import 'package:equatable/equatable.dart'; enum RunCodeStatus { unspecified, @@ -42,7 +42,7 @@ const kFinishedStatuses = [ RunCodeStatus.finished, ]; -class RunCodeResult { +class RunCodeResult with EquatableMixin { final RunCodeStatus status; final String? pipelineUuid; final String? output; @@ -50,7 +50,7 @@ class RunCodeResult { final String? graph; final String? errorMessage; - RunCodeResult({ + const RunCodeResult({ required this.status, this.pipelineUuid, this.output, @@ -64,20 +64,14 @@ class RunCodeResult { } @override - bool operator ==(Object other) => - identical(this, other) || - other is RunCodeResult && - runtimeType == other.runtimeType && - pipelineUuid == other.pipelineUuid && - status == other.status && - output == other.output && - log == other.log && - graph == other.graph && - errorMessage == other.errorMessage; - - @override - int get hashCode => - hashValues(pipelineUuid, status, output, log, errorMessage, graph); + List get props => [ + status, + pipelineUuid, + output, + log, + graph, + errorMessage, + ]; @override String toString() { diff --git a/playground/frontend/lib/modules/examples/repositories/models/save_snippet_request.dart b/playground/frontend/playground_components/lib/src/repositories/models/save_snippet_request.dart similarity index 80% rename from playground/frontend/lib/modules/examples/repositories/models/save_snippet_request.dart rename to playground/frontend/playground_components/lib/src/repositories/models/save_snippet_request.dart index c29ee4230f6a9..4d64416efcc39 100644 --- a/playground/frontend/lib/modules/examples/repositories/models/save_snippet_request.dart +++ b/playground/frontend/playground_components/lib/src/repositories/models/save_snippet_request.dart @@ -16,15 +16,15 @@ * limitations under the License. */ -import 'package:playground/modules/examples/repositories/models/shared_file_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import '../../models/sdk.dart'; +import 'shared_file.dart'; -class SaveSnippetRequestWrapper { +class SaveSnippetRequest { final List files; - final SDK sdk; + final Sdk sdk; final String pipelineOptions; - const SaveSnippetRequestWrapper({ + const SaveSnippetRequest({ required this.files, required this.sdk, required this.pipelineOptions, diff --git a/playground/frontend/lib/modules/examples/repositories/models/save_snippet_response.dart b/playground/frontend/playground_components/lib/src/repositories/models/save_snippet_response.dart similarity index 100% rename from playground/frontend/lib/modules/examples/repositories/models/save_snippet_response.dart rename to playground/frontend/playground_components/lib/src/repositories/models/save_snippet_response.dart diff --git a/playground/frontend/lib/modules/examples/repositories/models/shared_file_model.dart b/playground/frontend/playground_components/lib/src/repositories/models/shared_file.dart similarity index 100% rename from playground/frontend/lib/modules/examples/repositories/models/shared_file_model.dart rename to playground/frontend/playground_components/lib/src/repositories/models/shared_file.dart diff --git a/playground/frontend/playground_components/lib/src/repositories/sdk_grpc_extension.dart b/playground/frontend/playground_components/lib/src/repositories/sdk_grpc_extension.dart new file mode 100644 index 0000000000000..d9c1b58633037 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/repositories/sdk_grpc_extension.dart @@ -0,0 +1,51 @@ +/* + * 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 '../api/v1/api.pbgrpc.dart' as g; +import '../models/sdk.dart'; + +extension SdkExtension on Sdk { + static final _idToGrpcEnum = { + Sdk.java.id: g.Sdk.SDK_JAVA, + Sdk.go.id: g.Sdk.SDK_GO, + Sdk.python.id: g.Sdk.SDK_PYTHON, + Sdk.scio.id: g.Sdk.SDK_SCIO, + }; + + g.Sdk get grpc => + _idToGrpcEnum[id] ?? + (throw Exception('SDK not supported for GRPS: $id')); +} + +extension GrpcSdkExtension on g.Sdk { + Sdk get model { + switch (this) { + case g.Sdk.SDK_JAVA: + return Sdk.java; + case g.Sdk.SDK_GO: + return Sdk.go; + case g.Sdk.SDK_PYTHON: + return Sdk.python; + case g.Sdk.SDK_SCIO: + return Sdk.scio; + } + + return Sdk(id: '$value', title: name); + } +} diff --git a/learning/tour-of-beam/frontend/lib/config/theme/switch_notifier.dart b/playground/frontend/playground_components/lib/src/theme/switch_notifier.dart similarity index 79% rename from learning/tour-of-beam/frontend/lib/config/theme/switch_notifier.dart rename to playground/frontend/playground_components/lib/src/theme/switch_notifier.dart index 09851c200a2db..28fe46c58ded0 100644 --- a/learning/tour-of-beam/frontend/lib/config/theme/switch_notifier.dart +++ b/playground/frontend/playground_components/lib/src/theme/switch_notifier.dart @@ -20,23 +20,11 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'colors_provider.dart'; - const kThemeMode = 'theme_mode'; class ThemeSwitchNotifier extends ChangeNotifier { ThemeMode themeMode = ThemeMode.light; - static const _darkThemeColors = ThemeColors.fromBrightness(isDark: true); - static const _lightThemeColors = ThemeColors.fromBrightness(isDark: false); - - ThemeColors get themeColors { - if (themeMode == ThemeMode.dark) { - return _darkThemeColors; - } - return _lightThemeColors; - } - void init() { _setPreferences(); } @@ -73,12 +61,7 @@ class ThemeSwitchNotifierProvider extends StatelessWidget { Widget build(BuildContext context) { return ChangeNotifierProvider( create: (context) => ThemeSwitchNotifier()..init(), - child: Consumer( - builder: (context, themeSwitchNotifier, _) => ThemeColorsProvider( - data: themeSwitchNotifier.themeColors, - child: child, - ), - ), + child: child, ); } } diff --git a/playground/frontend/playground_components/lib/src/theme/theme.dart b/playground/frontend/playground_components/lib/src/theme/theme.dart new file mode 100644 index 0000000000000..98d27fb9e2790 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/theme/theme.dart @@ -0,0 +1,367 @@ +/* + * 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:code_text_field/code_text_field.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../../playground_components.dart'; + +const codeFontSize = 14.0; + +class BeamThemeExtension extends ThemeExtension { + final Color borderColor; + final Color fieldBackgroundColor; + final Color iconColor; + final Color primaryBackgroundTextColor; + final Color lightGreyBackgroundTextColor; + final Color secondaryBackgroundColor; + + final TextStyle codeRootStyle; + final CodeThemeData codeTheme; + + const BeamThemeExtension({ + required this.borderColor, + required this.fieldBackgroundColor, + required this.iconColor, + required this.primaryBackgroundTextColor, + required this.lightGreyBackgroundTextColor, + required this.secondaryBackgroundColor, + required this.codeRootStyle, + required this.codeTheme, + }); + + @override + ThemeExtension copyWith({ + Color? borderColor, + Color? fieldBackgroundColor, + Color? iconColor, + Color? primaryBackgroundTextColor, + Color? lightGreyBackgroundTextColor, + Color? secondaryBackgroundColor, + TextStyle? codeRootStyle, + CodeThemeData? codeTheme, + }) { + return BeamThemeExtension( + borderColor: borderColor ?? this.borderColor, + fieldBackgroundColor: fieldBackgroundColor ?? this.fieldBackgroundColor, + iconColor: iconColor ?? this.iconColor, + primaryBackgroundTextColor: + primaryBackgroundTextColor ?? this.primaryBackgroundTextColor, + lightGreyBackgroundTextColor: + lightGreyBackgroundTextColor ?? this.lightGreyBackgroundTextColor, + secondaryBackgroundColor: + secondaryBackgroundColor ?? this.secondaryBackgroundColor, + codeRootStyle: codeRootStyle ?? this.codeRootStyle, + codeTheme: codeTheme ?? this.codeTheme, + ); + } + + @override + ThemeExtension lerp( + covariant BeamThemeExtension? other, + double t, + ) { + return BeamThemeExtension( + borderColor: Color.lerp(borderColor, other?.borderColor, t)!, + fieldBackgroundColor: + Color.lerp(fieldBackgroundColor, other?.fieldBackgroundColor, t)!, + iconColor: Color.lerp(iconColor, other?.iconColor, t)!, + primaryBackgroundTextColor: Color.lerp( + primaryBackgroundTextColor, other?.primaryBackgroundTextColor, t)!, + lightGreyBackgroundTextColor: Color.lerp(lightGreyBackgroundTextColor, + other?.lightGreyBackgroundTextColor, t)!, + secondaryBackgroundColor: Color.lerp( + secondaryBackgroundColor, other?.secondaryBackgroundColor, t)!, + codeRootStyle: TextStyle.lerp(codeRootStyle, other?.codeRootStyle, t)!, + codeTheme: t == 0.0 ? codeTheme : other?.codeTheme ?? codeTheme, + ); + } +} + +final kLightTheme = ThemeData( + brightness: Brightness.light, + appBarTheme: _getAppBarTheme(BeamLightThemeColors.secondaryBackground), + backgroundColor: BeamLightThemeColors.primaryBackground, + canvasColor: BeamLightThemeColors.primaryBackground, + dividerColor: BeamLightThemeColors.grey, + elevatedButtonTheme: _getElevatedButtonTheme(BeamLightThemeColors.primary), + outlinedButtonTheme: _getOutlineButtonTheme( + BeamLightThemeColors.text, + BeamLightThemeColors.primary, + ), + primaryColor: BeamLightThemeColors.primary, + scaffoldBackgroundColor: BeamLightThemeColors.secondaryBackground, + tabBarTheme: _getTabBarTheme( + textColor: BeamLightThemeColors.text, + indicatorColor: BeamLightThemeColors.primary, + ), + textButtonTheme: _getTextButtonTheme(BeamLightThemeColors.text), + textTheme: _getTextTheme(BeamLightThemeColors.text), + extensions: { + BeamThemeExtension( + borderColor: BeamLightThemeColors.border, + fieldBackgroundColor: BeamLightThemeColors.grey, + iconColor: BeamLightThemeColors.icon, + primaryBackgroundTextColor: BeamColors.white, + lightGreyBackgroundTextColor: BeamColors.black, + secondaryBackgroundColor: BeamLightThemeColors.secondaryBackground, + codeRootStyle: GoogleFonts.sourceCodePro( + backgroundColor: BeamLightThemeColors.primaryBackground, + color: BeamLightThemeColors.text, + fontSize: codeFontSize, + ), + codeTheme: CodeThemeData( + styles: { + 'root': TextStyle( + backgroundColor: BeamLightThemeColors.primaryBackground, + color: BeamLightThemeColors.text, + ), + 'comment': TextStyle(color: BeamLightThemeColors.codeComment), + 'quote': TextStyle(color: BeamLightThemeColors.code2), + 'variable': TextStyle(color: BeamLightThemeColors.code2), + 'keyword': TextStyle(color: BeamLightThemeColors.code2), + 'selector-tag': TextStyle(color: BeamLightThemeColors.code2), + 'built_in': TextStyle(color: BeamLightThemeColors.code2), + 'name': TextStyle(color: BeamLightThemeColors.code2), + 'tag': TextStyle(color: BeamLightThemeColors.code2), + 'string': TextStyle(color: BeamLightThemeColors.code1), + 'title': TextStyle(color: BeamLightThemeColors.code1), + 'section': TextStyle(color: BeamLightThemeColors.code1), + 'attribute': TextStyle(color: BeamLightThemeColors.code1), + 'literal': TextStyle(color: BeamLightThemeColors.code1), + 'template-tag': TextStyle(color: BeamLightThemeColors.code1), + 'template-variable': TextStyle(color: BeamLightThemeColors.code1), + 'type': TextStyle(color: BeamLightThemeColors.code1), + 'addition': TextStyle(color: BeamLightThemeColors.code1), + 'deletion': TextStyle(color: BeamLightThemeColors.code2), + 'selector-attr': TextStyle(color: BeamLightThemeColors.code2), + 'selector-pseudo': TextStyle(color: BeamLightThemeColors.code2), + 'meta': TextStyle(color: BeamLightThemeColors.code2), + 'doctag': TextStyle(color: BeamLightThemeColors.codeComment), + 'attr': TextStyle(color: BeamLightThemeColors.primary), + 'symbol': TextStyle(color: BeamLightThemeColors.code2), + 'bullet': TextStyle(color: BeamLightThemeColors.code2), + 'link': TextStyle(color: BeamLightThemeColors.code2), + 'emphasis': const TextStyle(fontStyle: FontStyle.italic), + 'strong': const TextStyle(fontWeight: FontWeight.bold), + }, + ), + ), + }, +); + +final kDarkTheme = ThemeData( + brightness: Brightness.dark, + appBarTheme: _getAppBarTheme(BeamDarkThemeColors.secondaryBackground), + backgroundColor: BeamDarkThemeColors.primaryBackground, + canvasColor: BeamDarkThemeColors.primaryBackground, + dividerColor: BeamDarkThemeColors.grey, + elevatedButtonTheme: _getElevatedButtonTheme(BeamDarkThemeColors.primary), + outlinedButtonTheme: _getOutlineButtonTheme( + BeamDarkThemeColors.text, + BeamDarkThemeColors.primary, + ), + primaryColor: BeamDarkThemeColors.primary, + scaffoldBackgroundColor: BeamDarkThemeColors.secondaryBackground, + tabBarTheme: _getTabBarTheme( + textColor: BeamDarkThemeColors.text, + indicatorColor: BeamDarkThemeColors.primary, + ), + textButtonTheme: _getTextButtonTheme(BeamDarkThemeColors.text), + textTheme: _getTextTheme(BeamDarkThemeColors.text), + extensions: { + BeamThemeExtension( + borderColor: BeamDarkThemeColors.border, + fieldBackgroundColor: BeamDarkThemeColors.grey, + iconColor: BeamDarkThemeColors.icon, + primaryBackgroundTextColor: BeamColors.white, + lightGreyBackgroundTextColor: BeamColors.black, + secondaryBackgroundColor: BeamDarkThemeColors.secondaryBackground, + codeRootStyle: GoogleFonts.sourceCodePro( + backgroundColor: BeamDarkThemeColors.primaryBackground, + color: BeamDarkThemeColors.text, + fontSize: codeFontSize, + ), + codeTheme: CodeThemeData( + styles: { + 'root': TextStyle( + backgroundColor: BeamDarkThemeColors.primaryBackground, + color: BeamDarkThemeColors.text, + ), + 'comment': TextStyle(color: BeamDarkThemeColors.codeComment), + 'quote': TextStyle(color: BeamDarkThemeColors.code2), + 'variable': TextStyle(color: BeamDarkThemeColors.code2), + 'keyword': TextStyle(color: BeamDarkThemeColors.code2), + 'selector-tag': TextStyle(color: BeamDarkThemeColors.code2), + 'built_in': TextStyle(color: BeamDarkThemeColors.code2), + 'name': TextStyle(color: BeamDarkThemeColors.code2), + 'tag': TextStyle(color: BeamDarkThemeColors.code2), + 'string': TextStyle(color: BeamDarkThemeColors.code1), + 'title': TextStyle(color: BeamDarkThemeColors.code1), + 'section': TextStyle(color: BeamDarkThemeColors.code1), + 'attribute': TextStyle(color: BeamDarkThemeColors.code1), + 'literal': TextStyle(color: BeamDarkThemeColors.code1), + 'template-tag': TextStyle(color: BeamDarkThemeColors.code1), + 'template-variable': TextStyle(color: BeamDarkThemeColors.code1), + 'type': TextStyle(color: BeamDarkThemeColors.code1), + 'addition': TextStyle(color: BeamDarkThemeColors.code1), + 'deletion': TextStyle(color: BeamDarkThemeColors.code2), + 'selector-attr': TextStyle(color: BeamDarkThemeColors.code2), + 'selector-pseudo': TextStyle(color: BeamDarkThemeColors.code2), + 'meta': TextStyle(color: BeamDarkThemeColors.code2), + 'doctag': TextStyle(color: BeamDarkThemeColors.codeComment), + 'attr': TextStyle(color: BeamDarkThemeColors.primary), + 'symbol': TextStyle(color: BeamDarkThemeColors.code2), + 'bullet': TextStyle(color: BeamDarkThemeColors.code2), + 'link': TextStyle(color: BeamDarkThemeColors.code2), + 'emphasis': const TextStyle(fontStyle: FontStyle.italic), + 'strong': const TextStyle(fontWeight: FontWeight.bold), + }, + ), + ), + }, +); + +TextTheme _getTextTheme(Color textColor) { + return GoogleFonts.sourceSansProTextTheme( + const TextTheme( + displayLarge: _emptyTextStyle, + displayMedium: TextStyle( + fontSize: 48, + fontWeight: FontWeight.w900, + ), + displaySmall: TextStyle( + fontFamily: 'Roboto_regular', + fontSize: 18, + fontWeight: FontWeight.w400, + ), + headlineLarge: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + headlineMedium: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + headlineSmall: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + ), + titleLarge: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w600, + ), + titleMedium: _emptyTextStyle, + titleSmall: _emptyTextStyle, + labelLarge: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + labelMedium: _emptyTextStyle, + labelSmall: _emptyTextStyle, + bodyLarge: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w400, + ), + bodyMedium: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w400, + ), + bodySmall: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ).apply( + bodyColor: textColor, + displayColor: textColor, + ), + ); +} + +TextButtonThemeData _getTextButtonTheme(Color textColor) { + return TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: textColor, + shape: _getButtonBorder(BeamBorderRadius.large), + ), + ); +} + +OutlinedButtonThemeData _getOutlineButtonTheme( + Color textColor, + Color outlineColor, +) { + return OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + foregroundColor: textColor, + side: BorderSide(color: outlineColor, width: 3), + padding: const EdgeInsets.symmetric( + vertical: BeamSizes.size20, + horizontal: BeamSizes.size40, + ), + shape: _getButtonBorder(BeamBorderRadius.small), + ), + ); +} + +ElevatedButtonThemeData _getElevatedButtonTheme(Color color) { + return ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.all(BeamSizes.size20), + foregroundColor: BeamColors.white, + backgroundColor: color, + ), + ); +} + +TabBarTheme _getTabBarTheme({ + required Color textColor, + required Color indicatorColor, +}) { + const labelStyle = TextStyle(fontWeight: FontWeight.w600); + return TabBarTheme( + unselectedLabelColor: textColor, + labelColor: textColor, + labelStyle: labelStyle, + unselectedLabelStyle: labelStyle, + indicator: UnderlineTabIndicator( + borderSide: BorderSide(width: 2.0, color: indicatorColor), + ), + ); +} + +AppBarTheme _getAppBarTheme(Color backgroundColor) { + return AppBarTheme( + color: backgroundColor, + elevation: BeamSizes.size1, + centerTitle: false, + toolbarHeight: BeamSizes.appBarHeight, + ); +} + +RoundedRectangleBorder _getButtonBorder(double radius) { + return RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(radius), + ), + ); +} + +/// This is used to easily distinguish unimplemented text styles. +const TextStyle _emptyTextStyle = TextStyle(); diff --git a/playground/frontend/lib/modules/editor/parsers/run_options_parser.dart b/playground/frontend/playground_components/lib/src/util/pipeline_options.dart similarity index 100% rename from playground/frontend/lib/modules/editor/parsers/run_options_parser.dart rename to playground/frontend/playground_components/lib/src/util/pipeline_options.dart diff --git a/playground/frontend/lib/utils/replace_incorrect_symbols.dart b/playground/frontend/playground_components/lib/src/util/replace_incorrect_symbols.dart similarity index 87% rename from playground/frontend/lib/utils/replace_incorrect_symbols.dart rename to playground/frontend/playground_components/lib/src/util/replace_incorrect_symbols.dart index 08d75ee7dc556..69a0bb684009d 100644 --- a/playground/frontend/lib/utils/replace_incorrect_symbols.dart +++ b/playground/frontend/playground_components/lib/src/util/replace_incorrect_symbols.dart @@ -16,11 +16,9 @@ * limitations under the License. */ -const kIncorrectTabSymbol = ' '; -const kTabSymbolReplacement = ' '; - +// TODO(alexeyinkin): Move to the editor, https://github.com/apache/beam/issues/23079 /// sometimes code contains incorrect symbols (like tab which doesn't look properly), /// replace it with correct ones String replaceIncorrectSymbols(String output) { - return output.replaceAll(kIncorrectTabSymbol, kTabSymbolReplacement); + return output.replaceAll('\t', ' '); } diff --git a/playground/frontend/lib/utils/run_with_retry.dart b/playground/frontend/playground_components/lib/src/util/run_with_retry.dart similarity index 100% rename from playground/frontend/lib/utils/run_with_retry.dart rename to playground/frontend/playground_components/lib/src/util/run_with_retry.dart diff --git a/playground/frontend/playground_components/lib/src/widgets/bubble.dart b/playground/frontend/playground_components/lib/src/widgets/bubble.dart new file mode 100644 index 0000000000000..1f6e46a1848a9 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/bubble.dart @@ -0,0 +1,69 @@ +/* + * 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_components/playground_components.dart'; + +import '../constants/sizes.dart'; + +class BubbleWidget extends StatelessWidget { + final bool isSelected; + final VoidCallback onTap; + final String title; + + const BubbleWidget({ + super.key, + required this.isSelected, + required this.onTap, + required this.title, + }); + + @override + Widget build(BuildContext context) { + final themeData = Theme.of(context); + final ext = themeData.extension()!; + + return MouseRegion( + cursor: SystemMouseCursors.click, + child: Padding( + padding: const EdgeInsets.only(right: BeamSizes.size8), + child: GestureDetector( + onTap: onTap, + child: Container( + height: BeamSizes.buttonHeight, + padding: const EdgeInsets.symmetric(horizontal: BeamSizes.size16), + decoration: BoxDecoration( + color: isSelected ? themeData.primaryColor : ext.borderColor, + borderRadius: BorderRadius.circular(BeamBorderRadius.infinite), + ), + child: Center( + child: Text( + title, + style: TextStyle( + color: isSelected + ? ext.primaryBackgroundTextColor + : ext.lightGreyBackgroundTextColor, + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/learning/tour-of-beam/frontend/lib/components/complexity.dart b/playground/frontend/playground_components/lib/src/widgets/complexity.dart similarity index 84% rename from learning/tour-of-beam/frontend/lib/components/complexity.dart rename to playground/frontend/playground_components/lib/src/widgets/complexity.dart index f8c28c1c03406..813d905f95db1 100644 --- a/learning/tour-of-beam/frontend/lib/components/complexity.dart +++ b/playground/frontend/playground_components/lib/src/widgets/complexity.dart @@ -20,8 +20,7 @@ import 'package:flutter/material.dart'; import '../constants/colors.dart'; import '../constants/sizes.dart'; - -enum Complexity { basic, medium, advanced } +import '../enums/complexity.dart'; class ComplexityWidget extends StatelessWidget { final Complexity complexity; @@ -49,8 +48,8 @@ class _Dot extends StatelessWidget { Widget build(BuildContext context) { return Container( margin: const EdgeInsets.only(left: 1), - width: TobSizes.size4, - height: TobSizes.size4, + width: BeamSizes.size4, + height: BeamSizes.size4, decoration: BoxDecoration( shape: BoxShape.circle, color: color, @@ -58,8 +57,8 @@ class _Dot extends StatelessWidget { ); } - static const grey = _Dot(color: TobColors.grey4); - static const green = _Dot(color: TobColors.green); - static const orange = _Dot(color: TobColors.orange); - static const red = _Dot(color: TobColors.red); + static const grey = _Dot(color: BeamColors.grey2); + static const green = _Dot(color: BeamColors.green); + static const orange = _Dot(color: BeamColors.orange); + static const red = _Dot(color: BeamColors.red); } diff --git a/playground/frontend/playground_components/lib/dismissible_overlay.dart b/playground/frontend/playground_components/lib/src/widgets/dismissible_overlay.dart similarity index 100% rename from playground/frontend/playground_components/lib/dismissible_overlay.dart rename to playground/frontend/playground_components/lib/src/widgets/dismissible_overlay.dart diff --git a/playground/frontend/lib/components/horizontal_divider/horizontal_divider.dart b/playground/frontend/playground_components/lib/src/widgets/divider.dart similarity index 64% rename from playground/frontend/lib/components/horizontal_divider/horizontal_divider.dart rename to playground/frontend/playground_components/lib/src/widgets/divider.dart index f935eade10257..fdd0b0ef60920 100644 --- a/playground/frontend/lib/components/horizontal_divider/horizontal_divider.dart +++ b/playground/frontend/playground_components/lib/src/widgets/divider.dart @@ -17,25 +17,20 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/config/theme.dart'; -import 'package:playground/constants/sizes.dart'; -/// Replaces the Flutter's Divider which is buggy with HTML renderer, -/// see https://github.com/flutter/flutter/issues/46339 -class HorizontalDivider extends StatelessWidget { - final double? indent; - - const HorizontalDivider({ - super.key, - this.indent, - }); +/// Replaces Flutter's Divider, which is buggy with HTML renderer. +/// See https://github.com/flutter/flutter/issues/46339 +class BeamDivider extends StatelessWidget { + final EdgeInsets? margin; + const BeamDivider({this.margin}); @override Widget build(BuildContext context) { return Container( - height: kDividerHeight, - margin: EdgeInsets.fromLTRB(indent ?? 0, 0, indent ?? 0, 0), - color: ThemeColors.of(context).divider, + margin: margin, + width: double.infinity, + height: 1, + color: Theme.of(context).dividerColor, ); } } diff --git a/playground/frontend/playground_components/lib/src/widgets/drag_handle.dart b/playground/frontend/playground_components/lib/src/widgets/drag_handle.dart new file mode 100644 index 0000000000000..505b8afb0c9a7 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/drag_handle.dart @@ -0,0 +1,43 @@ +/* + * 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_svg/svg.dart'; + +import '../constants/playground_components.dart'; +import '../generated/assets.gen.dart'; + +class DragHandle extends StatelessWidget { + final Axis direction; + + const DragHandle({ + required this.direction, + }); + + @override + Widget build(BuildContext context) { + // TODO: Use a single file and just rotate it if needed. + // Currently a rotated widget gets blurred in HTML renderer. Find a fix. + return SvgPicture.asset( + direction == Axis.horizontal + ? Assets.svg.dragHorizontal + : Assets.svg.dragVertical, + package: PlaygroundComponents.packageName, + ); + } +} diff --git a/playground/frontend/lib/modules/editor/components/editor_textarea.dart b/playground/frontend/playground_components/lib/src/widgets/editor_textarea.dart similarity index 80% rename from playground/frontend/lib/modules/editor/components/editor_textarea.dart rename to playground/frontend/playground_components/lib/src/widgets/editor_textarea.dart index d3e7d3b6bda88..05231d0ee8b7b 100644 --- a/playground/frontend/lib/modules/editor/components/editor_textarea.dart +++ b/playground/frontend/playground_components/lib/src/widgets/editor_textarea.dart @@ -16,14 +16,14 @@ * limitations under the License. */ +// TODO(alexeyinkin): Refactor this, merge into snippet_editor.dart + import 'package:code_text_field/code_text_field.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:playground/config/theme.dart'; -import 'package:playground/constants/fonts.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; + +import '../models/example.dart'; +import '../models/sdk.dart'; +import '../theme/theme.dart'; const kJavaRegExp = r'import\s[A-z.0-9]*\;\n\n[(\/\*\*)|(public)|(class)]'; const kPythonRegExp = r'[^\S\r\n](import|as)[^\S\r\n][A-z]*\n\n'; @@ -36,21 +36,21 @@ const kAdditionalLinesForScrolling = 4; class EditorTextArea extends StatefulWidget { final CodeController codeController; - final SDK sdk; - final ExampleModel? example; + final Sdk sdk; + final Example? example; final bool enabled; final bool isEditable; - final bool isEmbedded; + final bool goToContextLine; const EditorTextArea({ - Key? key, + super.key, required this.codeController, required this.sdk, this.example, required this.enabled, required this.isEditable, - this.isEmbedded = false, - }) : super(key: key); + required this.goToContextLine, + }); @override State createState() => _EditorTextAreaState(); @@ -68,32 +68,30 @@ class _EditorTextAreaState extends State { @override Widget build(BuildContext context) { - if (!widget.isEmbedded) { + if (widget.goToContextLine) { WidgetsBinding.instance.addPostFrameCallback((_) => _setTextScrolling()); } + final ext = Theme.of(context).extension()!; + return Semantics( container: true, textField: true, multiline: true, enabled: widget.enabled, readOnly: widget.enabled, - label: AppLocalizations.of(context)!.codeTextArea, + label: 'widgets.codeEditor.label', child: FocusScope( node: FocusScopeNode(canRequestFocus: widget.isEditable), - child: CodeField( - key: codeFieldKey, - focusNode: focusNode, - enabled: widget.enabled, - controller: widget.codeController, - textStyle: getCodeFontStyle( - textStyle: const TextStyle(fontSize: kCodeFontSize), - ), - expands: true, - lineNumberStyle: LineNumberStyle( - textStyle: TextStyle( - color: ThemeColors.of(context).grey1Color, - ), + child: CodeTheme( + data: ext.codeTheme, + child: CodeField( + key: codeFieldKey, + focusNode: focusNode, + enabled: widget.enabled, + controller: widget.codeController, + textStyle: ext.codeRootStyle, + expands: true, ), ), ), @@ -143,7 +141,7 @@ class _EditorTextAreaState extends State { codeFieldKey.currentContext?.findRenderObject() as RenderBox; double height = rBox.size.height * .75; - return height ~/ kCodeFontSize; + return height ~/ codeFontSize; } int _getIndexOfContextLine() { diff --git a/playground/frontend/lib/modules/actions/components/header_icon_button.dart b/playground/frontend/playground_components/lib/src/widgets/header_icon_button.dart similarity index 91% rename from playground/frontend/lib/modules/actions/components/header_icon_button.dart rename to playground/frontend/playground_components/lib/src/widgets/header_icon_button.dart index 09d42e8aa1877..01e4a62de1c84 100644 --- a/playground/frontend/lib/modules/actions/components/header_icon_button.dart +++ b/playground/frontend/playground_components/lib/src/widgets/header_icon_button.dart @@ -17,7 +17,8 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/constants/sizes.dart'; + +import '../constants/sizes.dart'; class HeaderIconButton extends StatelessWidget { final VoidCallback onPressed; @@ -25,16 +26,16 @@ class HeaderIconButton extends StatelessWidget { final Widget icon; const HeaderIconButton({ - Key? key, + super.key, required this.onPressed, required this.label, required this.icon, - }) : super(key: key); + }); @override Widget build(BuildContext context) { return SizedBox( - height: kHeaderButtonHeight, + height: BeamSizes.headerButtonHeight, child: TextButton.icon( icon: icon, label: Text(label), diff --git a/playground/frontend/lib/components/loading_indicator/loading_indicator.dart b/playground/frontend/playground_components/lib/src/widgets/loading_indicator.dart similarity index 83% rename from playground/frontend/lib/components/loading_indicator/loading_indicator.dart rename to playground/frontend/playground_components/lib/src/widgets/loading_indicator.dart index 8d7fb142a28d4..60b696ed880f5 100644 --- a/playground/frontend/lib/components/loading_indicator/loading_indicator.dart +++ b/playground/frontend/playground_components/lib/src/widgets/loading_indicator.dart @@ -17,12 +17,16 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/constants/colors.dart'; + +import '../constants/sizes.dart'; class LoadingIndicator extends StatelessWidget { final double size; - const LoadingIndicator({Key? key, required this.size}) : super(key: key); + const LoadingIndicator({ + super.key, + this.size = BeamSizes.loadingIndicator, + }); @override Widget build(BuildContext context) { @@ -30,8 +34,8 @@ class LoadingIndicator extends StatelessWidget { child: SizedBox( height: size, width: size, - child: const CircularProgressIndicator( - color: kLightPrimary, + child: CircularProgressIndicator( + color: Theme.of(context).primaryColor, ), ), ); diff --git a/playground/frontend/playground_components/lib/src/widgets/logo.dart b/playground/frontend/playground_components/lib/src/widgets/logo.dart new file mode 100644 index 0000000000000..2a0bdad9519be --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/logo.dart @@ -0,0 +1,36 @@ +/* + * 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 '../constants/playground_components.dart'; +import '../constants/sizes.dart'; +import '../generated/assets.gen.dart'; + +class BeamLogo extends StatelessWidget { + const BeamLogo(); + + @override + Widget build(BuildContext context) { + return Image.asset( + Assets.png.beamLogo.path, + height: BeamIconSizes.large, + package: PlaygroundComponents.packageName, + ); + } +} diff --git a/playground/frontend/lib/modules/output/components/graph.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph.dart similarity index 83% rename from playground/frontend/lib/modules/output/components/graph.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/graph.dart index 01fcfbd4b08e3..6f5e95c01d24a 100644 --- a/playground/frontend/lib/modules/output/components/graph.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph.dart @@ -17,11 +17,11 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/graph/graph_builder/canvas_drawer.dart'; -import 'package:playground/modules/graph/graph_builder/graph_builder.dart'; -import 'package:playground/modules/graph/graph_builder/painters/graph_painter.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/playground_components.dart'; + +import 'graph_builder/canvas_drawer.dart'; +import 'graph_builder/graph_builder.dart'; +import 'graph_builder/painters/graph_painter.dart'; class GraphCustomPainter extends CustomPainter { final GraphPainter graph; @@ -41,15 +41,15 @@ class GraphCustomPainter extends CustomPainter { class GraphTab extends StatefulWidget { final String graph; - final SDK sdk; - final GraphDirection direction; + final Sdk sdk; + final Axis direction; const GraphTab({ - Key? key, + super.key, required this.graph, required this.sdk, required this.direction, - }) : super(key: key); + }); @override State createState() => _GraphTabState(); @@ -88,9 +88,8 @@ class _GraphTabState extends State { return Container(); } return Padding( - padding: const EdgeInsets.all(kXlSpacing), + padding: const EdgeInsets.all(BeamSizes.size16), child: SingleChildScrollView( - scrollDirection: Axis.vertical, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: ClipRRect( diff --git a/playground/frontend/lib/modules/graph/graph_builder/canvas_drawer.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/canvas_drawer.dart similarity index 93% rename from playground/frontend/lib/modules/graph/graph_builder/canvas_drawer.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/canvas_drawer.dart index 59cbda7bb8485..65a95919021e3 100644 --- a/playground/frontend/lib/modules/graph/graph_builder/canvas_drawer.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/canvas_drawer.dart @@ -20,7 +20,8 @@ import 'dart:ui'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; -import 'package:playground/constants/colors.dart'; + +import '../../../../constants/colors.dart'; const kDashSize = 4; const kArrowSize = 4; @@ -32,13 +33,13 @@ class CanvasDrawer { CanvasDrawer(this.canvas); final borderPaint = Paint() - ..color = kLightGrey2 + ..color = BeamGraphColors.border ..strokeWidth = 1 ..isAntiAlias = true ..style = PaintingStyle.fill; final linePaint = Paint() - ..color = kLightPrimary + ..color = BeamGraphColors.edge ..strokeWidth = 2 ..isAntiAlias = true ..style = PaintingStyle.fill; @@ -71,7 +72,7 @@ class CanvasDrawer { createParagraph( text, width, - color: kLightGrey1, + color: BeamGraphColors.node, ), offset, ); @@ -82,7 +83,6 @@ class CanvasDrawer { } drawDashedLine(double x1, double y1, double x2, double y2) { - double startX = x1; double startY = y1; @@ -123,12 +123,12 @@ class CanvasDrawer { } drawRect( - double left, - double top, - double width, - double height, - double radius, - ) { + double left, + double top, + double width, + double height, + double radius, + ) { final borderRadius = Radius.circular(radius); final rect = Rect.fromLTWH(left, top, width, height); final rRect = RRect.fromRectAndRadius(rect, borderRadius); diff --git a/playground/frontend/lib/modules/graph/graph_builder/extractors/edge_extractor.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/edge_extractor.dart similarity index 93% rename from playground/frontend/lib/modules/graph/graph_builder/extractors/edge_extractor.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/edge_extractor.dart index 1c7685d45cb39..27c62de3177b2 100644 --- a/playground/frontend/lib/modules/graph/graph_builder/extractors/edge_extractor.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/edge_extractor.dart @@ -16,8 +16,8 @@ * limitations under the License. */ -import 'package:playground/modules/graph/graph_builder/extractors/extractors.dart'; -import 'package:playground/modules/graph/models/graph.dart'; +import '../../models/graph.dart'; +import 'extractors.dart'; final RegExp kEdgeRegExp = RegExp(r'''.+ -> .+'''); const kPrimaryEdgeStyle = 'solid'; diff --git a/playground/frontend/lib/modules/graph/graph_builder/extractors/element_extractor.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/element_extractor.dart similarity index 90% rename from playground/frontend/lib/modules/graph/graph_builder/extractors/element_extractor.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/element_extractor.dart index d533e2d230feb..e4af2ec9bab9b 100644 --- a/playground/frontend/lib/modules/graph/graph_builder/extractors/element_extractor.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/element_extractor.dart @@ -16,9 +16,9 @@ * limitations under the License. */ -import 'package:playground/modules/graph/graph_builder/extractors/extractor_utils.dart'; -import 'package:playground/modules/graph/graph_builder/extractors/extractors.dart'; -import 'package:playground/modules/graph/models/graph.dart'; +import '../../models/graph.dart'; +import 'extractor_utils.dart'; +import 'extractors.dart'; final RegExp kGraphElementRegExp = RegExp(r'''subgraph cluster_0'''); final RegExp kSubgraphElementRegExp = RegExp(r'''subgraph cluster_\d+'''); diff --git a/playground/frontend/lib/modules/graph/graph_builder/extractors/extractor_utils.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/extractor_utils.dart similarity index 100% rename from playground/frontend/lib/modules/graph/graph_builder/extractors/extractor_utils.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/extractor_utils.dart diff --git a/playground/frontend/lib/modules/graph/graph_builder/extractors/extractors.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/extractors.dart similarity index 100% rename from playground/frontend/lib/modules/graph/graph_builder/extractors/extractors.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/extractors.dart diff --git a/playground/frontend/lib/modules/graph/graph_builder/extractors/label_extractor.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/label_extractor.dart similarity index 93% rename from playground/frontend/lib/modules/graph/graph_builder/extractors/label_extractor.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/label_extractor.dart index 1c04558c594a4..1c9ce981388ed 100644 --- a/playground/frontend/lib/modules/graph/graph_builder/extractors/label_extractor.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/extractors/label_extractor.dart @@ -16,7 +16,7 @@ * limitations under the License. */ -import 'package:playground/modules/graph/graph_builder/extractors/extractors.dart'; +import 'extractors.dart'; final RegExp kLabelRegExp = RegExp(r'''label = ".*"'''); const kLabelStart = 'label = "'; diff --git a/playground/frontend/lib/modules/graph/graph_builder/graph_builder.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/graph_builder.dart similarity index 85% rename from playground/frontend/lib/modules/graph/graph_builder/graph_builder.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/graph_builder.dart index 53901aaa9500f..9549a76fa601c 100644 --- a/playground/frontend/lib/modules/graph/graph_builder/graph_builder.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/graph_builder.dart @@ -18,15 +18,17 @@ import 'dart:convert'; -import 'package:playground/modules/graph/graph_builder/extractors/edge_extractor.dart'; -import 'package:playground/modules/graph/graph_builder/extractors/element_extractor.dart'; -import 'package:playground/modules/graph/graph_builder/extractors/label_extractor.dart'; -import 'package:playground/modules/graph/graph_builder/painters/edge_painter.dart'; -import 'package:playground/modules/graph/graph_builder/painters/graph_painter.dart'; -import 'package:playground/modules/graph/graph_builder/painters/node_painter.dart'; -import 'package:playground/modules/graph/models/graph.dart'; -import 'package:playground/modules/graph/models/table_cell.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:flutter/widgets.dart' as widgets; +import 'package:playground_components/playground_components.dart'; + +import '../models/graph.dart'; +import '../models/table_cell.dart'; +import 'extractors/edge_extractor.dart'; +import 'extractors/element_extractor.dart'; +import 'extractors/label_extractor.dart'; +import 'painters/edge_painter.dart'; +import 'painters/graph_painter.dart'; +import 'painters/node_painter.dart'; final kGraphElementExtractor = GraphElementExtractor(); final kLabelExtractor = LabelExtractor(); @@ -37,20 +39,23 @@ abstract class GraphBuilder { final List edges = []; final Map elementsMap = {}; - static GraphBuilder? parseDot(String dot, SDK sdk) { + // TODO(alexeyinkin): Use this as the source of truth + // of whether a graph is available for an SDK, + // https://github.com/apache/beam/issues/23251 + static final _graphBuilderFactoriesBySdk = { + Sdk.java: JavaGraphBuilder.new, + Sdk.python: PythonGraphBuilder.new, + }; + + static GraphBuilder? parseDot(String dot, Sdk sdk) { LineSplitter ls = const LineSplitter(); List lines = ls.convert(dot); - GraphBuilder builder; - switch (sdk) { - case SDK.java: - builder = JavaGraphBuilder(); - break; - case SDK.python: - builder = PythonGraphBuilder(); - break; - default: - return null; + final builder = _graphBuilderFactoriesBySdk[sdk]?.call(); + + if (builder == null) { + return null; } + for (var line in lines) { builder.parseNextLine(line); } @@ -60,7 +65,7 @@ abstract class GraphBuilder { void parseNextLine(String line); - GraphPainter getPainter(GraphDirection direction) { + GraphPainter getPainter(widgets.Axis direction) { final List nodeElements = elements .where((element) => element.type == NodeType.node) .toList() @@ -86,9 +91,9 @@ abstract class GraphBuilder { .map((element) { final cell = nodeToCellMap[element.name]!; final row = - direction == GraphDirection.horizontal ? cell.row : cell.column; + direction == widgets.Axis.horizontal ? cell.row : cell.column; final column = - direction == GraphDirection.horizontal ? cell.column : cell.row; + direction == widgets.Axis.horizontal ? cell.column : cell.row; return NodeElementPainter( element: element as Node, row: row, diff --git a/playground/frontend/lib/modules/graph/graph_builder/painters/edge_painter.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/painters/edge_painter.dart similarity index 90% rename from playground/frontend/lib/modules/graph/graph_builder/painters/edge_painter.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/painters/edge_painter.dart index d676105301044..93a3d15d91080 100644 --- a/playground/frontend/lib/modules/graph/graph_builder/painters/edge_painter.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/painters/edge_painter.dart @@ -18,13 +18,14 @@ import 'dart:math'; import 'package:collection/collection.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/graph/graph_builder/canvas_drawer.dart'; -import 'package:playground/modules/graph/graph_builder/painters/graph_painter.dart'; -import 'package:playground/modules/graph/graph_builder/painters/node_painter.dart'; -import 'package:playground/modules/graph/models/graph.dart'; +import 'package:flutter/widgets.dart'; -const kEdgeSpacing = 2 * kXlSpacing; +import '../../../../../constants/sizes.dart'; +import '../../models/graph.dart'; +import '../canvas_drawer.dart'; +import 'node_painter.dart'; + +const kEdgeSpacing = 2 * BeamSizes.size16; class EdgePainter { final Edge edge; @@ -38,9 +39,9 @@ class EdgePainter { Map columnStarts, Map rowSizes, Map columnSizes, - GraphDirection direction, + Axis direction, ) { - if (direction == GraphDirection.vertical) { + if (direction == Axis.vertical) { _drawVertical( drawer, elementsMap, rowStarts, columnStarts, rowSizes, columnSizes); } else { @@ -109,7 +110,7 @@ class EdgePainter { }); drawer.drawRightArrow( - optimizedMovePoints[0].x + kXlSpacing, optimizedMovePoints[0].y); + optimizedMovePoints[0].x + BeamSizes.size16, optimizedMovePoints[0].y); _drawLine(drawer, optimizedMovePoints); } @@ -174,7 +175,7 @@ class EdgePainter { }); drawer.drawBottomArrow( - optimizedMovePoints[0].x, optimizedMovePoints[0].y + kXlSpacing); + optimizedMovePoints[0].x, optimizedMovePoints[0].y + BeamSizes.size16); _drawLine(drawer, optimizedMovePoints); } diff --git a/playground/frontend/lib/modules/graph/graph_builder/painters/graph_painter.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/painters/graph_painter.dart similarity index 83% rename from playground/frontend/lib/modules/graph/graph_builder/painters/graph_painter.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/painters/graph_painter.dart index 81d5635316302..8c2c4c356c025 100644 --- a/playground/frontend/lib/modules/graph/graph_builder/painters/graph_painter.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/painters/graph_painter.dart @@ -19,18 +19,17 @@ import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/graph/graph_builder/canvas_drawer.dart'; -import 'package:playground/modules/graph/graph_builder/painters/edge_painter.dart'; -import 'package:playground/modules/graph/graph_builder/painters/node_painter.dart'; -import 'package:playground/modules/graph/models/graph.dart'; -enum GraphDirection { vertical, horizontal } +import '../../../../../constants/sizes.dart'; +import '../../models/graph.dart'; +import '../canvas_drawer.dart'; +import 'edge_painter.dart'; +import 'node_painter.dart'; class GraphPainter { final List elementsPainter; final List edges; - final GraphDirection direction; + final Axis direction; final Map elementsMap = {}; final Map rowSizes = {}; final Map columnSizes = {}; @@ -41,8 +40,8 @@ class GraphPainter { final lastColumn = columnStarts.length - 1; final lastRow = rowStarts.length - 1; final width = - columnStarts[lastColumn]! + columnSizes[lastColumn]! + 4 * kXlSpacing; - final height = rowStarts[lastRow]! + rowSizes[lastRow]! + 4 * kXlSpacing; + columnStarts[lastColumn]! + columnSizes[lastColumn]! + 4 * BeamSizes.size16; + final height = rowStarts[lastRow]! + rowSizes[lastRow]! + 4 * BeamSizes.size16; return Size(width, height); } @@ -70,11 +69,11 @@ class GraphPainter { var top = 0.0; for (var r = 0; r < rowSizes.length; r++) { rowStarts[r] = top; - top = top + rowSizes[r]! + 4 * kXlSpacing; + top = top + rowSizes[r]! + 4 * BeamSizes.size16; } for (var c = 0; c < columnSizes.length; c++) { columnStarts[c] = left; - left = left + columnSizes[c]! + 4 * kXlSpacing; + left = left + columnSizes[c]! + 4 * BeamSizes.size16; } } diff --git a/playground/frontend/lib/modules/graph/graph_builder/painters/node_painter.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/painters/node_painter.dart similarity index 82% rename from playground/frontend/lib/modules/graph/graph_builder/painters/node_painter.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/painters/node_painter.dart index 6e04f880bddb8..f0b78632d99ae 100644 --- a/playground/frontend/lib/modules/graph/graph_builder/painters/node_painter.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/graph/graph_builder/painters/node_painter.dart @@ -15,12 +15,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/graph/graph_builder/canvas_drawer.dart'; -import 'package:playground/modules/graph/models/graph.dart'; + +import '../../../../../constants/sizes.dart'; +import '../../models/graph.dart'; +import '../canvas_drawer.dart'; class NodeElementPainter { int row; @@ -47,18 +49,18 @@ class NodeElementPainter { drawer.drawText( parentLabel, maxTextWidth, - Offset(left! + kXlSpacing, top! + kLgSpacing), + Offset(left! + BeamSizes.size16, top! + BeamSizes.size12), ); drawer.drawSecondaryText( element.label, maxTextWidth, - Offset(left! + kXlSpacing, top! + kLgSpacing + kMdSpacing + 10.0), + Offset(left! + BeamSizes.size16, top! + BeamSizes.size12 + BeamSizes.size8 + 10.0), ); } else { drawer.drawText( element.label, maxTextWidth, - Offset(left! + kXlSpacing, top! + (56 / 2 - 5)), + Offset(left! + BeamSizes.size16, top! + (56 / 2 - 5)), ); } } @@ -75,8 +77,8 @@ class NodeElementPainter { if (size != null) { return size!; } - final fullWidth = maxTextWidth + kXlSpacing * 2; - size = Size(fullWidth, kLgSpacing * 2 + kMdSpacing + 10.0 * 2); + final fullWidth = maxTextWidth + BeamSizes.size16 * 2; + size = Size(fullWidth, BeamSizes.size12 * 2 + BeamSizes.size8 + 10.0 * 2); return size!; } diff --git a/playground/frontend/lib/modules/graph/models/graph.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/models/graph.dart similarity index 100% rename from playground/frontend/lib/modules/graph/models/graph.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/models/graph.dart diff --git a/playground/frontend/lib/modules/graph/models/table_cell.dart b/playground/frontend/playground_components/lib/src/widgets/output/graph/models/table_cell.dart similarity index 100% rename from playground/frontend/lib/modules/graph/models/table_cell.dart rename to playground/frontend/playground_components/lib/src/widgets/output/graph/models/table_cell.dart diff --git a/playground/frontend/lib/modules/output/components/output.dart b/playground/frontend/playground_components/lib/src/widgets/output/output.dart similarity index 64% rename from playground/frontend/lib/modules/output/components/output.dart rename to playground/frontend/playground_components/lib/src/widgets/output/output.dart index b99692749cbca..194b5d7549371 100644 --- a/playground/frontend/lib/modules/output/components/output.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/output.dart @@ -17,39 +17,43 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/modules/output/components/output_area.dart'; -import 'package:playground/modules/output/components/output_header/output_placements.dart'; -import 'package:playground/modules/output/components/output_header/output_tabs.dart'; -import 'package:playground/modules/output/components/output_header/tab_header.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; + +import '../../controllers/playground_controller.dart'; +import '../tab_header.dart'; +import 'output_area.dart'; +import 'output_tabs.dart'; const kTabsCount = 2; -class Output extends StatefulWidget { - final bool isEmbedded; - final bool showGraph; +class OutputWidget extends StatefulWidget { + final PlaygroundController playgroundController; + final Widget? trailing; + final Axis graphDirection; - Output({ - required this.isEmbedded, - required PlaygroundState playgroundState, - }) : showGraph = playgroundState.graphAvailable, - super( + OutputWidget({ + required this.playgroundController, + required this.graphDirection, + this.trailing, + }) : super( key: ValueKey( - '${playgroundState.sdk}_${playgroundState.selectedExample?.path}', + '${playgroundController.sdk}_${playgroundController.selectedExample?.path}', ), ); @override - State createState() => _OutputState(); + State createState() => _OutputWidgetState(); } -class _OutputState extends State with SingleTickerProviderStateMixin { +class _OutputWidgetState extends State + with SingleTickerProviderStateMixin { late final TabController tabController; int selectedTab = 0; @override void initState() { - final tabsCount = widget.showGraph ? kTabsCount : kTabsCount - 1; + final tabsCount = widget.playgroundController.graphAvailable + ? kTabsCount + : kTabsCount - 1; tabController = TabController(vsync: this, length: tabsCount); tabController.addListener(_onTabChange); super.initState(); @@ -78,17 +82,18 @@ class _OutputState extends State with SingleTickerProviderStateMixin { TabHeader( tabController: tabController, tabsWidget: OutputTabs( + playgroundController: widget.playgroundController, tabController: tabController, - showGraph: widget.showGraph, ), ), - const OutputPlacements(), + if (widget.trailing != null) widget.trailing!, ], ), Expanded( child: OutputArea( + playgroundController: widget.playgroundController, tabController: tabController, - showGraph: widget.showGraph, + graphDirection: widget.graphDirection, ), ), ], diff --git a/playground/frontend/playground_components/lib/src/widgets/output/output_area.dart b/playground/frontend/playground_components/lib/src/widgets/output/output_area.dart new file mode 100644 index 0000000000000..f1bf48b2c260c --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/output/output_area.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:playground_components/playground_components.dart'; + +import 'graph/graph.dart'; +import 'output_result.dart'; + +class OutputArea extends StatelessWidget { + final PlaygroundController playgroundController; + final TabController tabController; + final Axis graphDirection; + + const OutputArea({ + Key? key, + required this.playgroundController, + required this.tabController, + required this.graphDirection, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final sdk = playgroundController.sdk; + + return Container( + color: Theme.of(context).backgroundColor, + child: TabBarView( + controller: tabController, + physics: const NeverScrollableScrollPhysics(), + children: [ + OutputResult( + text: playgroundController.outputResult, + isSelected: tabController.index == 0, + ), + if (playgroundController.graphAvailable) + sdk == null + ? Container() + : GraphTab( + graph: playgroundController.result?.graph ?? '', + sdk: sdk, + direction: graphDirection, + ), + ], + ), + ); + } +} diff --git a/playground/frontend/lib/modules/output/components/output_result.dart b/playground/frontend/playground_components/lib/src/widgets/output/output_result.dart similarity index 86% rename from playground/frontend/lib/modules/output/components/output_result.dart rename to playground/frontend/playground_components/lib/src/widgets/output/output_result.dart index 63c90b2770a85..236a4856ff027 100644 --- a/playground/frontend/lib/modules/output/components/output_result.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/output_result.dart @@ -17,8 +17,9 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/constants/fonts.dart'; -import 'package:playground/constants/sizes.dart'; + +import '../../constants/sizes.dart'; +import '../../theme/theme.dart'; class OutputResult extends StatefulWidget { final String text; @@ -48,6 +49,7 @@ class _OutputResultState extends State { @override Widget build(BuildContext context) { + final ext = Theme.of(context).extension()!; return SingleChildScrollView( controller: _scrollController, child: Scrollbar( @@ -55,8 +57,11 @@ class _OutputResultState extends State { trackVisibility: true, controller: _scrollController, child: Padding( - padding: const EdgeInsets.all(kXlSpacing), - child: SelectableText(widget.text, style: getCodeFontStyle()), + padding: const EdgeInsets.all(BeamSizes.size16), + child: SelectableText( + widget.text, + style: ext.codeRootStyle, + ), ), ), ); diff --git a/playground/frontend/lib/modules/output/components/output_header/output_tab.dart b/playground/frontend/playground_components/lib/src/widgets/output/output_tab.dart similarity index 78% rename from playground/frontend/lib/modules/output/components/output_header/output_tab.dart rename to playground/frontend/playground_components/lib/src/widgets/output/output_tab.dart index 4b6daed2a08fc..326b1c72448fd 100644 --- a/playground/frontend/lib/modules/output/components/output_header/output_tab.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/output_tab.dart @@ -18,23 +18,26 @@ import 'package:aligned_dialog/aligned_dialog.dart'; import 'package:flutter/material.dart'; -import 'package:playground/config/theme.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/output/components/output_header/result_filter_popover.dart'; + +import '../../constants/sizes.dart'; +import '../../controllers/playground_controller.dart'; +import 'result_filter_popover.dart'; class OutputTab extends StatefulWidget { + final PlaygroundController playgroundController; final String name; final bool isSelected; final String value; final bool hasFilter; const OutputTab({ - Key? key, + super.key, + required this.playgroundController, required this.name, required this.isSelected, required this.value, this.hasFilter = false, - }) : super(key: key); + }); @override State createState() => _OutputTabState(); @@ -61,11 +64,13 @@ class _OutputTabState extends State { @override Widget build(BuildContext context) { + final themeData = Theme.of(context); + return Tab( child: Wrap( direction: Axis.horizontal, alignment: WrapAlignment.center, - spacing: kMdSpacing, + spacing: BeamSizes.size8, children: [ Text(widget.name), widget.hasFilter @@ -73,7 +78,9 @@ class _OutputTabState extends State { onTap: () { showAlignedDialog( context: context, - builder: (dialogContext) => const ResultFilterPopover(), + builder: (dialogContext) => ResultFilterPopover( + playgroundController: widget.playgroundController, + ), followerAnchor: Alignment.topLeft, targetAnchor: Alignment.topLeft, barrierColor: Colors.transparent, @@ -81,17 +88,17 @@ class _OutputTabState extends State { }, child: Icon( Icons.filter_alt_outlined, - size: kIconSizeSm, - color: ThemeColors.of(context).primary, + size: BeamIconSizes.small, + color: themeData.primaryColor, ), ) : const SizedBox(), if (hasNewContent) Container( - width: kIconSizeXs, - height: kIconSizeXs, + width: BeamIconSizes.xs, + height: BeamIconSizes.xs, decoration: BoxDecoration( - color: ThemeColors.of(context).primary, + color: themeData.primaryColor, shape: BoxShape.circle, ), ), diff --git a/playground/frontend/lib/modules/output/components/output_header/output_tabs.dart b/playground/frontend/playground_components/lib/src/widgets/output/output_tabs.dart similarity index 51% rename from playground/frontend/lib/modules/output/components/output_header/output_tabs.dart rename to playground/frontend/playground_components/lib/src/widgets/output/output_tabs.dart index 6107769a52985..80839296fbfa7 100644 --- a/playground/frontend/lib/modules/output/components/output_header/output_tabs.dart +++ b/playground/frontend/playground_components/lib/src/widgets/output/output_tabs.dart @@ -16,46 +16,45 @@ * 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/modules/output/components/output_header/output_tab.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; -import 'package:provider/provider.dart'; + +import '../../controllers/playground_controller.dart'; +import 'output_tab.dart'; class OutputTabs extends StatelessWidget { + final PlaygroundController playgroundController; final TabController tabController; - final bool showGraph; const OutputTabs({ - Key? key, + super.key, + required this.playgroundController, required this.tabController, - required this.showGraph, - }) : super(key: key); + }); @override Widget build(BuildContext context) { - AppLocalizations appLocale = AppLocalizations.of(context)!; - return Consumer(builder: (context, state, child) { - return SizedBox( - width: 300, - child: TabBar( - controller: tabController, - tabs: [ + return SizedBox( + width: 300, + child: TabBar( + controller: tabController, + tabs: [ + OutputTab( + playgroundController: playgroundController, + name: 'widgets.output.result'.tr(), + isSelected: tabController.index == 0, + value: playgroundController.outputResult, + hasFilter: true, + ), + if (playgroundController.graphAvailable) OutputTab( - name: appLocale.result, - isSelected: tabController.index == 0, - value: state.outputResult, - hasFilter: true, + playgroundController: playgroundController, + name: 'widgets.output.graph'.tr(), + isSelected: tabController.index == 2, + value: playgroundController.result?.graph ?? '', ), - if (showGraph) - OutputTab( - name: appLocale.graph, - isSelected: tabController.index == 2, - value: state.result?.graph ?? '', - ), - ], - ), - ); - }); + ], + ), + ); } } diff --git a/playground/frontend/playground_components/lib/src/widgets/output/result_filter_bubble.dart b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_bubble.dart new file mode 100644 index 0000000000000..a18ddb01d81a9 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_bubble.dart @@ -0,0 +1,52 @@ +/* + * 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 '../../controllers/playground_controller.dart'; +import '../../models/outputs.dart'; +import '../bubble.dart'; + +class ResultFilterBubble extends StatelessWidget { + final PlaygroundController playgroundController; + final OutputType type; + final String name; + + const ResultFilterBubble({ + super.key, + required this.playgroundController, + required this.type, + required this.name, + }); + + @override + Widget build(BuildContext context) { + final isSelected = type == playgroundController.selectedOutputFilterType; + + return BubbleWidget( + isSelected: isSelected, + onTap: () { + if (!isSelected) { + playgroundController.setSelectedOutputFilterType(type); + playgroundController.filterOutput(type); + } + }, + title: name, + ); + } +} diff --git a/playground/frontend/playground_components/lib/src/widgets/output/result_filter_popover.dart b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_popover.dart new file mode 100644 index 0000000000000..a74c67e2fa173 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/output/result_filter_popover.dart @@ -0,0 +1,84 @@ +/* + * 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_components/playground_components.dart'; + +import 'result_filter_bubble.dart'; + +const kPopoverWidth = 240.0; +const kPopoverPadding = 50.0; + +class ResultFilterPopover extends StatelessWidget { + final PlaygroundController playgroundController; + + const ResultFilterPopover({ + required this.playgroundController, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: kPopoverPadding), + child: SizedBox( + width: kPopoverWidth, + child: Card( + child: Padding( + padding: const EdgeInsets.all(BeamSizes.size8), + child: Wrap( + runSpacing: BeamSizes.size8, + children: [ + const Text('widgets.output.filterTitle').tr(), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: BeamSizes.size4, + vertical: BeamSizes.size4, + ), + child: AnimatedBuilder( + animation: playgroundController, + builder: (context, child) => Row( + children: [ + ResultFilterBubble( + playgroundController: playgroundController, + type: OutputType.all, + name: 'widgets.output.filter.all'.tr(), + ), + ResultFilterBubble( + playgroundController: playgroundController, + type: OutputType.log, + name: 'widgets.output.filter.log'.tr(), + ), + ResultFilterBubble( + playgroundController: playgroundController, + type: OutputType.output, + name: 'widgets.output.filter.output'.tr(), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/playground/frontend/playground_components/lib/src/widgets/reset_button.dart b/playground/frontend/playground_components/lib/src/widgets/reset_button.dart new file mode 100644 index 0000000000000..079587dddedb3 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/reset_button.dart @@ -0,0 +1,57 @@ +/* + * 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_svg/flutter_svg.dart'; + +import '../constants/playground_components.dart'; +import '../controllers/playground_controller.dart'; +import '../generated/assets.gen.dart'; +import '../theme/theme.dart'; +import 'header_icon_button.dart'; +import 'shortcut_tooltip.dart'; + +class ResetButton extends StatelessWidget { + final PlaygroundController playgroundController; + final VoidCallback? beforeReset; + + const ResetButton({ + required this.playgroundController, + this.beforeReset, + }); + + @override + Widget build(BuildContext context) { + return ShortcutTooltip( + shortcut: playgroundController.resetShortcut, + child: HeaderIconButton( + icon: SvgPicture.asset( + Assets.buttons.reset, + color: Theme.of(context).extension()?.iconColor, + package: PlaygroundComponents.packageName, + ), + label: 'widgets.resetButton.label'.tr(), + onPressed: () { + beforeReset?.call(); + playgroundController.reset(); + }, + ), + ); + } +} diff --git a/playground/frontend/lib/modules/editor/components/run_button.dart b/playground/frontend/playground_components/lib/src/widgets/run_button.dart similarity index 69% rename from playground/frontend/lib/modules/editor/components/run_button.dart rename to playground/frontend/playground_components/lib/src/widgets/run_button.dart index d31e36d213515..9aabaeb6b1f48 100644 --- a/playground/frontend/lib/modules/editor/components/run_button.dart +++ b/playground/frontend/playground_components/lib/src/widgets/run_button.dart @@ -16,55 +16,58 @@ * limitations under the License. */ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:playground/config/theme.dart'; -import 'package:playground/constants/sizes.dart'; -import 'package:playground/modules/shortcuts/components/shortcut_tooltip.dart'; -import 'package:playground/modules/shortcuts/constants/global_shortcuts.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; -import 'package:provider/provider.dart'; + +import '../constants/sizes.dart'; +import '../controllers/playground_controller.dart'; +import '../theme/theme.dart'; +import 'shortcut_tooltip.dart'; const kMsToSec = 1000; const kSecondsFractions = 1; +const _width = 150.0; + class RunButton extends StatelessWidget { + final PlaygroundController playgroundController; final bool isRunning; final VoidCallback runCode; final VoidCallback cancelRun; final bool disabled; const RunButton({ - Key? key, + super.key, + required this.playgroundController, required this.isRunning, required this.runCode, required this.cancelRun, this.disabled = false, - }) : super(key: key); + }); @override Widget build(BuildContext context) { return SizedBox( - width: kRunButtonWidth, - height: kButtonHeight, + width: _width, + height: BeamSizes.buttonHeight, child: ShortcutTooltip( - shortcut: kRunShortcut, + shortcut: playgroundController.runShortcut, child: ElevatedButton.icon( icon: isRunning ? SizedBox( - width: kIconSizeSm, - height: kIconSizeSm, + width: BeamIconSizes.small, + height: BeamIconSizes.small, child: CircularProgressIndicator( - color: ThemeColors.of(context).primaryBackgroundTextColor, + color: Theme.of(context).extension()!.primaryBackgroundTextColor, ), ) : const Icon(Icons.play_arrow), label: StreamBuilder( - stream: Provider.of(context).executionTime, + stream: playgroundController.executionTime, builder: (context, AsyncSnapshot state) { final seconds = (state.data ?? 0) / kMsToSec; - final runText = AppLocalizations.of(context)!.run; - final cancelText = AppLocalizations.of(context)!.cancel; + final runText = 'widgets.runOrCancelButton.titles.run'.tr(); + final cancelText = 'widgets.runOrCancelButton.titles.cancel'.tr(); final buttonText = isRunning ? cancelText : runText; if (seconds > 0) { return Text( diff --git a/playground/frontend/playground_components/lib/src/widgets/run_or_cancel_button.dart b/playground/frontend/playground_components/lib/src/widgets/run_or_cancel_button.dart new file mode 100644 index 0000000000000..7bbd204fcb2c0 --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/run_or_cancel_button.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:easy_localization/easy_localization.dart'; +import 'package:flutter/widgets.dart'; + +import '../controllers/playground_controller.dart'; +import '../notifications/notification.dart'; +import 'run_button.dart'; + +class RunOrCancelButton extends StatelessWidget { + final VoidCallback? beforeCancel; + final VoidCallback? onComplete; + final VoidCallback? beforeRun; + final PlaygroundController playgroundController; + + const RunOrCancelButton({ + required this.playgroundController, + this.beforeCancel, + this.onComplete, + this.beforeRun, + }); + + @override + Widget build(BuildContext context) { + return RunButton( + playgroundController: playgroundController, + disabled: playgroundController.selectedExample?.isMultiFile ?? false, + isRunning: playgroundController.isCodeRunning, + cancelRun: () { + beforeCancel?.call(); + playgroundController.cancelRun().catchError( + (_) => NotificationManager.showError( + context, + 'widgets.runOrCancelButton.notificationTitles.runCode'.tr(), + 'widgets.runOrCancelButton.notificationTitles.cancelExecution'.tr(), + ), + ); + }, + runCode: () { + beforeRun?.call(); + playgroundController.runCode( + onFinish: onComplete, + ); + }, + ); + } +} diff --git a/playground/frontend/lib/modules/shortcuts/components/shortcut_tooltip.dart b/playground/frontend/playground_components/lib/src/widgets/shortcut_tooltip.dart similarity index 81% rename from playground/frontend/lib/modules/shortcuts/components/shortcut_tooltip.dart rename to playground/frontend/playground_components/lib/src/widgets/shortcut_tooltip.dart index 94f14677c14bf..80e75b9e0788a 100644 --- a/playground/frontend/lib/modules/shortcuts/components/shortcut_tooltip.dart +++ b/playground/frontend/playground_components/lib/src/widgets/shortcut_tooltip.dart @@ -17,24 +17,24 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/modules/shortcuts/models/shortcut.dart'; -import 'package:playground/modules/shortcuts/utils/shortcuts_display_name.dart'; + +import '../models/shortcut.dart'; class ShortcutTooltip extends StatelessWidget { - final Shortcut shortcut; + final BeamShortcut shortcut; final Widget child; const ShortcutTooltip({ - Key? key, + super.key, required this.shortcut, required this.child, - }) : super(key: key); + }); @override Widget build(BuildContext context) { return Tooltip( excludeFromSemantics: true, - message: getShortcutDisplayName(shortcut), + message: shortcut.title, child: child, ); } diff --git a/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart b/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart new file mode 100644 index 0000000000000..fe7ecc4e6037d --- /dev/null +++ b/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart @@ -0,0 +1,46 @@ +/* + * 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/widgets.dart'; + +import '../controllers/snippet_editing_controller.dart'; +import 'editor_textarea.dart'; + +class SnippetEditor extends StatelessWidget { + final SnippetEditingController controller; + final bool isEditable; + final bool goToContextLine; + + const SnippetEditor({ + required this.controller, + required this.isEditable, + required this.goToContextLine, + }); + + @override + Widget build(BuildContext context) { + return EditorTextArea( + codeController: controller.codeController, + sdk: controller.sdk, + enabled: !(controller.selectedExample?.isMultiFile ?? false), + example: controller.selectedExample, + isEditable: isEditable, + goToContextLine: goToContextLine, + ); + } +} diff --git a/playground/frontend/lib/components/split_view/split_view.dart b/playground/frontend/playground_components/lib/src/widgets/split_view.dart similarity index 77% rename from playground/frontend/lib/components/split_view/split_view.dart rename to playground/frontend/playground_components/lib/src/widgets/split_view.dart index 06403d6759a7a..904d03788d619 100644 --- a/playground/frontend/lib/components/split_view/split_view.dart +++ b/playground/frontend/playground_components/lib/src/widgets/split_view.dart @@ -17,14 +17,9 @@ */ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:playground/config/theme.dart'; -import 'package:playground/constants/assets.dart'; -enum SplitViewDirection { - vertical, - horizontal, -} +import '../constants/sizes.dart'; +import 'drag_handle.dart'; const minRatio = 0.3; const maxRatio = 0.7; @@ -33,18 +28,16 @@ const defaultRatio = 0.5; class SplitView extends StatefulWidget { final Widget first; final Widget second; - final double dividerSize; - final SplitViewDirection direction; - final double ratio; + final Axis direction; + final double initialRatio; const SplitView({ - Key? key, + super.key, required this.first, required this.second, - required this.dividerSize, required this.direction, - this.ratio = defaultRatio, - }) : super(key: key); + this.initialRatio = defaultRatio, + }); @override State createState() => _SplitViewState(); @@ -59,14 +52,14 @@ class _SplitViewState extends State { get _sizeSecond => (1 - _ratio) * _maxSize; - get _isHorizontal => widget.direction == SplitViewDirection.horizontal; + get _isHorizontal => widget.direction == Axis.horizontal; - get _isVertical => widget.direction == SplitViewDirection.vertical; + get _isVertical => widget.direction == Axis.vertical; @override void initState() { super.initState(); - _ratio = widget.ratio; + _ratio = widget.initialRatio; } @override @@ -79,7 +72,8 @@ class _SplitViewState extends State { }); } - _buildHorizontalLayout(BuildContext context, BoxConstraints constraints) { + Widget _buildHorizontalLayout( + BuildContext context, BoxConstraints constraints) { return SizedBox( width: constraints.maxWidth, child: Row( @@ -126,14 +120,13 @@ class _SplitViewState extends State { child: GestureDetector( behavior: HitTestBehavior.translucent, child: Container( - width: _isHorizontal ? widget.dividerSize : double.infinity, - height: _isVertical ? widget.dividerSize : double.infinity, - color: ThemeColors.of(context).divider, - child: Center( - child: SvgPicture.asset(_isHorizontal - ? kDragHorizontalIconAsset - : kDragVerticalIconAsset), - )), + width: _isHorizontal ? BeamSizes.splitViewSeparator : double.infinity, + height: _isVertical ? BeamSizes.splitViewSeparator : double.infinity, + color: Theme.of(context).dividerColor, + child: Center( + child: DragHandle(direction: widget.direction), + ), + ), onPanUpdate: (DragUpdateDetails details) { setState(() { _updateRatio(details); @@ -171,7 +164,7 @@ class _SplitViewState extends State { void _calculateMaxSize(double maxSize) { if (_maxSize != maxSize) { - _maxSize = maxSize - widget.dividerSize; + _maxSize = maxSize - BeamSizes.splitViewSeparator; } } } diff --git a/playground/frontend/lib/modules/output/components/output_header/tab_header.dart b/playground/frontend/playground_components/lib/src/widgets/tab_header.dart similarity index 91% rename from playground/frontend/lib/modules/output/components/output_header/tab_header.dart rename to playground/frontend/playground_components/lib/src/widgets/tab_header.dart index dbf60b30f8a40..714f025814e0e 100644 --- a/playground/frontend/lib/modules/output/components/output_header/tab_header.dart +++ b/playground/frontend/playground_components/lib/src/widgets/tab_header.dart @@ -17,7 +17,8 @@ */ import 'package:flutter/material.dart'; -import 'package:playground/constants/sizes.dart'; + +import '../constants/sizes.dart'; const kHeaderHeight = 50.0; @@ -37,8 +38,7 @@ class TabHeader extends StatelessWidget { height: 50, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: kXlSpacing, - vertical: kZeroSpacing, + horizontal: BeamSizes.size16, ), child: tabsWidget, ), diff --git a/learning/tour-of-beam/frontend/lib/components/toggle_theme_button.dart b/playground/frontend/playground_components/lib/src/widgets/toggle_theme_button.dart similarity index 73% rename from learning/tour-of-beam/frontend/lib/components/toggle_theme_button.dart rename to playground/frontend/playground_components/lib/src/widgets/toggle_theme_button.dart index 2e0c84b68bf0a..927bda590ebe0 100644 --- a/learning/tour-of-beam/frontend/lib/components/toggle_theme_button.dart +++ b/playground/frontend/playground_components/lib/src/widgets/toggle_theme_button.dart @@ -21,9 +21,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; -import '../config/theme/switch_notifier.dart'; -import '../constants/assets.dart'; -import '../constants/sizes.dart'; +import '../constants/playground_components.dart'; +import '../generated/assets.gen.dart'; +import '../theme/switch_notifier.dart'; class ToggleThemeButton extends StatelessWidget { const ToggleThemeButton(); @@ -35,18 +35,15 @@ class ToggleThemeButton extends StatelessWidget { final text = notifier.isDarkMode ? 'ui.lightMode'.tr() : 'ui.darkMode'.tr(); - return Padding( - padding: const EdgeInsets.symmetric( - vertical: TobSizes.size4, - horizontal: TobSizes.size8, - ), - child: TextButton.icon( - icon: SvgPicture.asset(TobAssets.themeMode), - label: Text(text), - onPressed: () { - notifier.toggleTheme(); - }, + return TextButton.icon( + icon: SvgPicture.asset( + Assets.buttons.themeMode, + package: PlaygroundComponents.packageName, ), + label: Text(text), + onPressed: () { + notifier.toggleTheme(); + }, ); }, ); diff --git a/playground/frontend/lib/components/toggle_theme_button/toggle_theme_icon_button.dart b/playground/frontend/playground_components/lib/src/widgets/toggle_theme_icon_button.dart similarity index 73% rename from playground/frontend/lib/components/toggle_theme_button/toggle_theme_icon_button.dart rename to playground/frontend/playground_components/lib/src/widgets/toggle_theme_icon_button.dart index f82270826fea5..982aa39f80e6f 100644 --- a/playground/frontend/lib/components/toggle_theme_button/toggle_theme_icon_button.dart +++ b/playground/frontend/playground_components/lib/src/widgets/toggle_theme_icon_button.dart @@ -18,21 +18,26 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:playground/config/theme.dart'; -import 'package:playground/constants/assets.dart'; -import 'package:playground/constants/sizes.dart'; import 'package:provider/provider.dart'; +import '../constants/playground_components.dart'; +import '../constants/sizes.dart'; +import '../generated/assets.gen.dart'; +import '../theme/switch_notifier.dart'; + class ToggleThemeIconButton extends StatelessWidget { - const ToggleThemeIconButton({Key? key}) : super(key: key); + const ToggleThemeIconButton({super.key}); @override Widget build(BuildContext context) { return Consumer(builder: (context, notifier, child) { return IconButton( - iconSize: kIconSizeLg, - splashRadius: kIconButtonSplashRadius, - icon: SvgPicture.asset(kThemeIconAsset), + iconSize: BeamIconSizes.large, + splashRadius: BeamIconSizes.largeSplashRadius, + icon: SvgPicture.asset( + Assets.buttons.themeMode, + package: PlaygroundComponents.packageName, + ), onPressed: notifier.toggleTheme, ); }); diff --git a/playground/frontend/playground_components/pubspec.yaml b/playground/frontend/playground_components/pubspec.yaml index 56037dea76969..801fcf29abbdb 100644 --- a/playground/frontend/playground_components/pubspec.yaml +++ b/playground/frontend/playground_components/pubspec.yaml @@ -18,17 +18,48 @@ name: playground_components description: Reusable Playground components version: 0.0.1 +publish_to: none environment: - sdk: '>=2.17.6 <3.0.0' - flutter: '>=1.17.0' + sdk: '>=2.18.1 <3.0.0' + flutter: '>=3.3.2' dependencies: + aligned_dialog: ^0.0.6 + code_text_field: + git: + url: https://github.com/BertrandBev/code_field.git + ref: 9e2c9fe52a69481f038f4b6609e8a0a776429437 + collection: ^1.16.0 + easy_localization: ^3.0.1 + easy_localization_ext: ^0.1.1 + easy_localization_loader: ^1.0.0 + equatable: ^2.0.5 flutter: { sdk: flutter } - total_lints: ^2.17.4 + flutter_svg: ^1.0.3 + google_fonts: ^3.0.1 + grpc: ^3.0.2 + highlight: ^0.7.0 + meta: ^1.7.0 + protobuf: ^2.1.0 + provider: ^6.0.3 + shared_preferences: ^2.0.15 dev_dependencies: - flutter_lints: ^2.0.0 + build_runner: ^2.2.0 + flutter_gen_runner: ^4.3.0 flutter_test: { sdk: flutter } + mockito: 5.2.0 + total_lints: ^2.17.4 flutter: + uses-material-design: true + assets: + - assets/buttons/ + - assets/notification_icons/ + - assets/png/ + - assets/svg/ + - assets/translations/en.yaml + +flutter_gen: + output: lib/src/generated/ diff --git a/playground/frontend/test/pages/playground/states/examples_state_test.dart b/playground/frontend/playground_components/test/src/cache/example_cache_test.dart similarity index 52% rename from playground/frontend/test/pages/playground/states/examples_state_test.dart rename to playground/frontend/playground_components/test/src/cache/example_cache_test.dart index 169dce298a2e2..4b461ba69e9c3 100644 --- a/playground/frontend/test/pages/playground/states/examples_state_test.dart +++ b/playground/frontend/playground_components/test/src/cache/example_cache_test.dart @@ -16,35 +16,37 @@ * limitations under the License. */ +import 'dart:collection'; + import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; -import 'package:playground/pages/playground/states/examples_state.dart'; - -import 'mocks/categories_mock.dart'; -import 'mocks/example_mock.dart'; -import 'mocks/example_repository_mock.dart'; -import 'mocks/example_repository_mock.mocks.dart'; -import 'mocks/request_mock.dart'; - -final kDefaultExamplesMapMock = { - SDK.java: exampleWithAllAdditionsMock, - SDK.go: exampleWithAllAdditionsMock, - SDK.python: exampleWithAllAdditionsMock, - SDK.scio: exampleWithAllAdditionsMock, -}; +import 'package:playground_components/src/cache/example_cache.dart'; +import 'package:playground_components/src/models/sdk.dart'; + +import '../common/categories.dart'; +import '../common/example_repository_mock.dart'; +import '../common/example_repository_mock.mocks.dart'; +import '../common/examples.dart'; +import '../common/requests.dart'; + +final kDefaultExamplesMapMock = UnmodifiableMapView({ + Sdk.java: exampleWithAllAdditionsMock, + Sdk.go: exampleWithAllAdditionsMock, + Sdk.python: exampleWithAllAdditionsMock, + Sdk.scio: exampleWithAllAdditionsMock, +}); void main() { - late ExampleState state; + late ExampleCache state; late MockExampleRepository mockRepo; setUp(() { mockRepo = getMockExampleRepository(); - state = ExampleState(mockRepo); + state = ExampleCache(exampleRepository: mockRepo, hasCatalog: true); }); - test('Initial value of defaultExamplesMap should be an empty map', () { - expect(state.defaultExamplesMap, {}); + test('Initial value of defaultExamplesBySdk should be an empty map', () { + expect(state.defaultExamplesBySdk, {}); }); test('Initial value of isSelectorOpened should be false', () { @@ -52,20 +54,20 @@ void main() { }); test( - 'Example state init should initiate loading of sdkCategories from server', + 'Example state init should initiate loading of categoryListsBySdk from server', () async { - when(mockRepo.getListOfExamples(kGetListOfExamplesRequestMock)) - .thenAnswer((_) async => kGetListOfExamplesResponseMock.categories); + when(mockRepo.getListOfExamples(kGetPrecompiledObjectsRequest)) + .thenAnswer((_) async => kGetPrecompiledObjectsResponse.categories); await state.init(); - expect(state.sdkCategories, sdkCategoriesFromServerMock); + expect(state.categoryListsBySdk, sdkCategoriesFromServerMock); }, ); test( - 'Example state should notify all listeners about sdkCategories is set', + 'Example state should notify all listeners about categoryListsBySdk is set', () { state.addListener(() { - expect(state.sdkCategories, sdkCategoriesFromServerMock); + expect(state.categoryListsBySdk, sdkCategoriesFromServerMock); }); state.setSdkCategories(sdkCategoriesFromServerMock); }, @@ -85,21 +87,24 @@ void main() { 'Example state getCategories should get the categories list for each SDK', () { state.setSdkCategories(sdkCategoriesFromServerMock); - expect(state.getCategories(SDK.java), categoriesMock); - expect(state.getCategories(SDK.go), categoriesMock); - expect(state.getCategories(SDK.python), categoriesMock); - expect(state.getCategories(SDK.scio), categoriesMock); + expect(state.getCategories(Sdk.java), categoriesMock); + expect(state.getCategories(Sdk.go), categoriesMock); + expect(state.getCategories(Sdk.python), categoriesMock); + expect(state.getCategories(Sdk.scio), categoriesMock); }, ); test( 'Example state getExampleOutput should return output for example', () async { - when(mockRepo.getExampleOutput(kGetExampleRequestMock)) - .thenAnswer((_) async => kOutputResponseMock.output); + when(mockRepo.getExampleOutput(kRequestForExampleInfo)) + .thenAnswer((_) async => kOutputResponse.output); expect( - await state.getExampleOutput('', SDK.java), - kOutputResponseMock.output, + await state.getExampleOutput( + kRequestForExampleInfo.path, + kRequestForExampleInfo.sdk, + ), + kOutputResponse.output, ); }, ); @@ -107,11 +112,14 @@ void main() { test( 'Example state getExampleSource should return source code for example', () async { - when(mockRepo.getExampleSource(kGetExampleRequestMock)) - .thenAnswer((_) async => kOutputResponseMock.output); + when(mockRepo.getExampleSource(kRequestForExampleInfo)) + .thenAnswer((_) async => kOutputResponse.output); expect( - await state.getExampleSource('', SDK.java), - kOutputResponseMock.output, + await state.getExampleSource( + kRequestForExampleInfo.path, + kRequestForExampleInfo.sdk, + ), + kOutputResponse.output, ); }, ); @@ -119,11 +127,14 @@ void main() { test( 'Example state getExampleLogs should return logs for example', () async { - when(mockRepo.getExampleLogs(kGetExampleRequestMock)) - .thenAnswer((_) async => kOutputResponseMock.output); + when(mockRepo.getExampleLogs(kRequestForExampleInfo)) + .thenAnswer((_) async => kOutputResponse.output); expect( - await state.getExampleLogs('', SDK.java), - kOutputResponseMock.output, + await state.getExampleLogs( + kRequestForExampleInfo.path, + kRequestForExampleInfo.sdk, + ), + kOutputResponse.output, ); }, ); @@ -131,18 +142,21 @@ void main() { test( 'Example state getExampleGraph should return output for example', () async { - when(mockRepo.getExampleGraph(kGetExampleRequestMock)) - .thenAnswer((_) async => kOutputResponseMock.output); + when(mockRepo.getExampleGraph(kRequestForExampleInfo)) + .thenAnswer((_) async => kOutputResponse.output); expect( - await state.getExampleGraph('', SDK.java), - kOutputResponseMock.output, + await state.getExampleGraph( + kRequestForExampleInfo.path, + kRequestForExampleInfo.sdk, + ), + kOutputResponse.output, ); }, ); group('loadExampleInfo tests', () { test( - 'If example info is fetched (source is not empty),' + 'If example info is fetched (source is not empty), ' 'then loadExampleInfo should return example immediately', () async { expect( @@ -165,31 +179,31 @@ void main() { group('loadDefaultExamples tests', () { test( - 'If defaultExamplesMap is not empty, then loadDefaultExamples should not change it', + 'If defaultExamplesBySdk is not empty, then loadDefaultExamples should not change it', () async { - state.defaultExamplesMap = kDefaultExamplesMapMock; + state.defaultExamplesBySdk.addAll(kDefaultExamplesMapMock); await state.loadDefaultExamples(); - expect(state.defaultExamplesMap, kDefaultExamplesMapMock); + expect(state.defaultExamplesBySdk, kDefaultExamplesMapMock); }, ); test( - 'Example state loadDefaultExamples should load default example' + 'Example state loadDefaultExamples should load default example ' 'with all additions for every Sdk', () async { // stubs when(mockRepo.getExampleOutput(kRequestForExampleInfo)) - .thenAnswer((_) async => kOutputResponseMock.output); + .thenAnswer((_) async => kOutputResponse.output); when(mockRepo.getExampleSource(kRequestForExampleInfo)) - .thenAnswer((_) async => kOutputResponseMock.output); + .thenAnswer((_) async => kOutputResponse.output); when(mockRepo.getExampleLogs(kRequestForExampleInfo)) - .thenAnswer((_) async => kOutputResponseMock.output); + .thenAnswer((_) async => kOutputResponse.output); when(mockRepo.getExampleGraph(kRequestForExampleInfo)) - .thenAnswer((_) async => kOutputResponseMock.output); + .thenAnswer((_) async => kOutputResponse.output); // test assertion await state.loadDefaultExamples(); expect( - state.defaultExamplesMap, + state.defaultExamplesBySdk, kDefaultExamplesMapMock, ); }, diff --git a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/empty_example_loading_descriptor.dart b/playground/frontend/playground_components/test/src/common/categories.dart similarity index 51% rename from playground/frontend/lib/modules/examples/models/example_loading_descriptors/empty_example_loading_descriptor.dart rename to playground/frontend/playground_components/test/src/common/categories.dart index aec65ba2f246f..39c0f4c38bcbc 100644 --- a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/empty_example_loading_descriptor.dart +++ b/playground/frontend/playground_components/test/src/common/categories.dart @@ -16,29 +16,31 @@ * limitations under the License. */ -import 'package:playground/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_origin.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'dart:collection'; -class EmptyExampleLoadingDescriptor extends ExampleLoadingDescriptor { - final SDK sdk; +import 'package:playground_components/src/models/category_with_examples.dart'; +import 'package:playground_components/src/models/sdk.dart'; - const EmptyExampleLoadingDescriptor({ - required this.sdk, - }); +import 'examples.dart'; - @override - ExampleOrigin get origin => ExampleOrigin.empty; +final categoriesMock = [ + CategoryWithExamples(title: 'Sorted', examples: [exampleMock1]), + CategoryWithExamples(title: 'Unsorted', examples: [exampleMock2]), +]; - @override - int get hashCode => sdk.hashCode; +final sortedCategories = [ + CategoryWithExamples(title: 'Sorted', examples: [exampleMock1]), +]; - @override - bool operator ==(Object other) { - return other is EmptyExampleLoadingDescriptor && sdk == other.sdk; - } +const unsortedExamples = [exampleMock1, exampleMock2]; - // Only ContentExampleLoadingDescriptor is serialized now. - @override - Map toJson() => throw UnimplementedError(); -} +const examplesSortedByTypeMock = [exampleMock2]; + +const examplesSortedByNameMock = [exampleMock1]; + +final sdkCategoriesFromServerMock = UnmodifiableMapView({ + Sdk.java: categoriesMock, + Sdk.python: categoriesMock, + Sdk.go: categoriesMock, + Sdk.scio: categoriesMock, +}); diff --git a/playground/frontend/test/pages/playground/states/mocks/example_repository_mock.dart b/playground/frontend/playground_components/test/src/common/example_repository_mock.dart similarity index 82% rename from playground/frontend/test/pages/playground/states/mocks/example_repository_mock.dart rename to playground/frontend/playground_components/test/src/common/example_repository_mock.dart index e44c5f03b4e63..108d9b1195a1f 100644 --- a/playground/frontend/test/pages/playground/states/mocks/example_repository_mock.dart +++ b/playground/frontend/playground_components/test/src/common/example_repository_mock.dart @@ -18,11 +18,11 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:playground/modules/examples/repositories/example_repository.dart'; +import 'package:playground_components/src/repositories/example_repository.dart'; import 'example_repository_mock.mocks.dart'; -import 'example_mock.dart'; -import 'request_mock.dart'; +import 'examples.dart'; +import 'requests.dart'; @GenerateMocks([ExampleRepository]) MockExampleRepository getMockExampleRepository() { @@ -39,13 +39,13 @@ MockExampleRepository getMockExampleRepository() { .thenAnswer((_) async => exampleWithoutSourceMock); when(m.getExampleOutput(kRequestForExampleInfo)) - .thenAnswer((_) async => kOutputResponseMock.output); + .thenAnswer((_) async => kOutputResponse.output); when(m.getExampleSource(kRequestForExampleInfo)) - .thenAnswer((_) async => kOutputResponseMock.output); + .thenAnswer((_) async => kOutputResponse.output); when(m.getExampleLogs(kRequestForExampleInfo)) - .thenAnswer((_) async => kOutputResponseMock.output); + .thenAnswer((_) async => kOutputResponse.output); when(m.getExampleGraph(kRequestForExampleInfo)) - .thenAnswer((_) async => kOutputResponseMock.output); + .thenAnswer((_) async => kOutputResponse.output); return m; } diff --git a/playground/frontend/test/pages/playground/states/mocks/example_mock.dart b/playground/frontend/playground_components/test/src/common/examples.dart similarity index 72% rename from playground/frontend/test/pages/playground/states/mocks/example_mock.dart rename to playground/frontend/playground_components/test/src/common/examples.dart index f0833e8afb48d..a522668827de0 100644 --- a/playground/frontend/test/pages/playground/states/mocks/example_mock.dart +++ b/playground/frontend/playground_components/test/src/common/examples.dart @@ -16,37 +16,41 @@ * limitations under the License. */ -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/src/models/example.dart'; +import 'package:playground_components/src/models/example_base.dart'; +import 'package:playground_components/src/models/sdk.dart'; -final ExampleModel exampleMock1 = ExampleModel( - sdk: SDK.python, +const exampleMock1 = Example( + sdk: Sdk.python, source: 'ex1', name: 'Example', type: ExampleType.example, description: 'description', path: 'SDK_PYTHON/Category/Name', + pipelineOptions: '', ); -final ExampleModel exampleMock2 = ExampleModel( - sdk: SDK.python, +const exampleMock2 = Example( + sdk: Sdk.python, source: 'ex2', name: 'Kata', type: ExampleType.kata, description: 'description', path: 'SDK_PYTHON/Category/Name', + pipelineOptions: '', ); -final ExampleModel exampleWithoutSourceMock = ExampleModel( - sdk: SDK.python, +const exampleWithoutSourceMock = ExampleBase( + sdk: Sdk.python, name: 'Test example', type: ExampleType.example, description: 'description', path: 'SDK_PYTHON/Category/Name', + pipelineOptions: '', ); -final ExampleModel exampleWithAllAdditionsMock = ExampleModel( - sdk: SDK.python, +const exampleWithAllAdditionsMock = Example( + sdk: Sdk.python, name: 'Test example', type: ExampleType.example, description: 'description', @@ -55,13 +59,15 @@ final ExampleModel exampleWithAllAdditionsMock = ExampleModel( outputs: 'test outputs', logs: 'test outputs', graph: 'test outputs', + pipelineOptions: '', ); -final ExampleModel exampleMockGo = ExampleModel( - sdk: SDK.go, +const exampleMockGo = Example( + sdk: Sdk.go, source: 'ex1', name: 'Example', type: ExampleType.example, description: 'description', path: 'SDK_GO/Category/Name', + pipelineOptions: '', ); diff --git a/playground/frontend/playground_components/test/src/common/requests.dart b/playground/frontend/playground_components/test/src/common/requests.dart new file mode 100644 index 0000000000000..84d015884df18 --- /dev/null +++ b/playground/frontend/playground_components/test/src/common/requests.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:playground_components/src/models/sdk.dart'; +import 'package:playground_components/src/repositories/models/get_default_precompiled_object_request.dart'; +import 'package:playground_components/src/repositories/models/get_precompiled_object_code_response.dart'; +import 'package:playground_components/src/repositories/models/get_precompiled_object_request.dart'; +import 'package:playground_components/src/repositories/models/get_precompiled_object_response.dart'; +import 'package:playground_components/src/repositories/models/get_precompiled_objects_request.dart'; +import 'package:playground_components/src/repositories/models/get_precompiled_objects_response.dart'; +import 'package:playground_components/src/repositories/models/output_response.dart'; + +import 'categories.dart'; +import 'examples.dart'; + +const kGetPrecompiledObjectsRequest = GetPrecompiledObjectsRequest( + sdk: null, + category: null, +); +final kGetPrecompiledObjectsResponse = GetPrecompiledObjectsResponse( + categories: sdkCategoriesFromServerMock, +); + +const kGetDefaultPrecompiledObjectRequest = GetDefaultPrecompiledObjectRequest( + sdk: Sdk.java, +); +const kGetDefaultPrecompiledObjectResponse = GetPrecompiledObjectResponse( + example: exampleMock1, +); + +const kGetPrecompiledObjectCodeResponse = GetPrecompiledObjectCodeResponse( + code: 'test source', +); +const kOutputResponse = OutputResponse(output: 'test outputs'); + +const kRequestForExampleInfo = GetPrecompiledObjectRequest( + path: 'SDK_PYTHON/Category/Name', + sdk: Sdk.python, +); +const kRequestDefaultExampleForJava = GetDefaultPrecompiledObjectRequest( + sdk: Sdk.java, +); +const kRequestDefaultExampleForGo = GetDefaultPrecompiledObjectRequest( + sdk: Sdk.go, +); +const kRequestDefaultExampleForPython = GetDefaultPrecompiledObjectRequest( + sdk: Sdk.python, +); +const kRequestDefaultExampleForScio = GetDefaultPrecompiledObjectRequest( + sdk: Sdk.scio, +); diff --git a/playground/frontend/test/pages/playground/states/playground_state_test.dart b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart similarity index 79% rename from playground/frontend/test/pages/playground/states/playground_state_test.dart rename to playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart index 411a935bc2a23..506102cce5cc6 100644 --- a/playground/frontend/test/pages/playground/states/playground_state_test.dart +++ b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart @@ -19,32 +19,32 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; -import 'package:playground/pages/playground/states/example_loaders/examples_loader.dart'; -import 'package:playground/pages/playground/states/examples_state.dart'; -import 'package:playground/pages/playground/states/playground_state.dart'; +import 'package:playground_components/src/cache/example_cache.dart'; +import 'package:playground_components/src/controllers/example_loaders/examples_loader.dart'; +import 'package:playground_components/src/controllers/playground_controller.dart'; +import 'package:playground_components/src/models/sdk.dart'; -import 'mocks/example_mock.dart'; -import 'playground_state_test.mocks.dart'; +import '../common/examples.dart'; +import 'playground_controller_test.mocks.dart'; -@GenerateMocks([ExamplesLoader, ExampleState]) +@GenerateMocks([ExamplesLoader, ExampleCache]) void main() { - late PlaygroundState state; + late PlaygroundController state; final mockExamplesLoader = MockExamplesLoader(); when(mockExamplesLoader.load(any)).thenAnswer((_) async => 1); setUp(() { - state = PlaygroundState( + state = PlaygroundController( examplesLoader: MockExamplesLoader(), - exampleState: MockExampleState(), + exampleCache: MockExampleCache(), ); }); test('Initial value of SDK field should be null', () { expect(state.sdk, null); - state.setSdk(SDK.go); - expect(state.sdk, SDK.go); + state.setSdk(Sdk.go); + expect(state.sdk, Sdk.go); }); test('Initial value of examplesTitle should be equal to kTitle', () { @@ -57,13 +57,13 @@ void main() { test('Initial value of pipelineOptions should be empty string', () { expect(state.pipelineOptions, null); - state.setSdk(SDK.go); + state.setSdk(Sdk.go); expect(state.pipelineOptions, ''); }); test('Initial value of source should be empty string', () { expect(state.source, null); - state.setSdk(SDK.go); + state.setSdk(Sdk.go); expect(state.source, ''); }); @@ -73,7 +73,10 @@ void main() { () { state.setExample(exampleMock1, setCurrentSdk: true); expect(state.isExampleChanged, false); - state.selectedExample!.setSource('test'); + // 'test' in this line hits a bug fixed here: + // https://github.com/akvelon/flutter-code-editor/commit/c74ce566bf873dc76a5269ce6fe7b02df9c148e0 + // TODO(alexeyinkin): revert from 'test1' to 'test' when Akvelon's editor is integrated. + state.setSource('test1'); expect(state.isExampleChanged, true); }, ); @@ -101,7 +104,7 @@ void main() { 'Playground state setExample should update source and example and notify all listeners', () { state.addListener(() { - expect(state.sdk, SDK.go); + expect(state.sdk, Sdk.go); expect(state.source, exampleMockGo.source); expect(state.selectedExample, exampleMockGo); }); @@ -111,9 +114,9 @@ void main() { test('Playground state should notify all listeners about sdk change', () { state.addListener(() { - expect(state.sdk, SDK.go); + expect(state.sdk, Sdk.go); }); - state.setSdk(SDK.go); + state.setSdk(Sdk.go); }); test( @@ -138,7 +141,7 @@ void main() { test( 'Playground state should notify all listeners about pipeline options change', () { - state.setSdk(SDK.go); + state.setSdk(Sdk.go); state.addListener(() { expect(state.pipelineOptions, 'test options'); }); diff --git a/playground/frontend/test/modules/editor/repository/code_repository/code_repository_test.dart b/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart similarity index 65% rename from playground/frontend/test/modules/editor/repository/code_repository/code_repository_test.dart rename to playground/frontend/playground_components/test/src/repositories/code_repository_test.dart index 344840d29d806..757e4f57ba3f0 100644 --- a/playground/frontend/test/modules/editor/repository/code_repository/code_repository_test.dart +++ b/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart @@ -19,20 +19,20 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/check_status_response.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/code_client.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/output_response.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_client/run_code_response.dart'; -import 'package:playground/modules/editor/repository/code_repository/code_repository.dart'; -import 'package:playground/modules/editor/repository/code_repository/run_code_request.dart'; -import 'package:playground/modules/editor/repository/code_repository/run_code_result.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/src/models/sdk.dart'; +import 'package:playground_components/src/repositories/code_client/code_client.dart'; +import 'package:playground_components/src/repositories/code_repository.dart'; +import 'package:playground_components/src/repositories/models/check_status_response.dart'; +import 'package:playground_components/src/repositories/models/output_response.dart'; +import 'package:playground_components/src/repositories/models/run_code_request.dart'; +import 'package:playground_components/src/repositories/models/run_code_response.dart'; +import 'package:playground_components/src/repositories/models/run_code_result.dart'; import 'code_repository_test.mocks.dart'; -final kRequestMock = RunCodeRequestWrapper( +const kRequestMock = RunCodeRequest( code: 'code', - sdk: SDK.java, + sdk: Sdk.java, pipelineOptions: {}, ); @@ -45,25 +45,25 @@ const kRunErrorOutput = 'RunErrorOutput'; const kPreparationErrorOutput = 'PreparationErrorOutput'; const kValidationErrorOutput = 'ValidationErrorOutput'; -final kRunCodeResponse = RunCodeResponse(kPipelineUuid); -final kFinishedStatusResponse = CheckStatusResponse(RunCodeStatus.finished); -final kErrorStatusResponse = CheckStatusResponse(RunCodeStatus.unknownError); -final kRunErrorStatusResponse = CheckStatusResponse(RunCodeStatus.runError); -final kExecutingStatusResponse = CheckStatusResponse(RunCodeStatus.executing); -final kCompileErrorStatusResponse = - CheckStatusResponse(RunCodeStatus.compileError); -final kValidationErrorStatusResponse = - CheckStatusResponse(RunCodeStatus.validationError); -final kPreparationErrorStatusResponse = - CheckStatusResponse(RunCodeStatus.preparationError); +const kRunCodeResponse = RunCodeResponse(pipelineUuid: kPipelineUuid); +const kFinishedStatusResponse = CheckStatusResponse(status: RunCodeStatus.finished,); +const kErrorStatusResponse = CheckStatusResponse(status: RunCodeStatus.unknownError,); +const kRunErrorStatusResponse = CheckStatusResponse(status: RunCodeStatus.runError,); +const kExecutingStatusResponse = CheckStatusResponse(status: RunCodeStatus.executing,); +const kCompileErrorStatusResponse = + CheckStatusResponse(status: RunCodeStatus.compileError,); +const kValidationErrorStatusResponse = + CheckStatusResponse(status: RunCodeStatus.validationError,); +const kPreparationErrorStatusResponse = + CheckStatusResponse(status: RunCodeStatus.preparationError,); -final kRunOutputResponse = OutputResponse(kRunOutput); -final kLogOutputResponse = OutputResponse(kLogOutput); -final kCompileOutputResponse = OutputResponse(kCompileOutput); -final kRunErrorOutputResponse = OutputResponse(kRunErrorOutput); -final kGraphResponse = OutputResponse(kGraphOutput); -final kValidationErrorOutputResponse = OutputResponse(kValidationErrorOutput); -final kPreparationErrorOutputResponse = OutputResponse(kPreparationErrorOutput); +const kRunOutputResponse = OutputResponse(output: kRunOutput); +const kLogOutputResponse = OutputResponse(output: kLogOutput); +const kCompileOutputResponse = OutputResponse(output: kCompileOutput); +const kRunErrorOutputResponse = OutputResponse(output: kRunErrorOutput); +const kGraphResponse = OutputResponse(output: kGraphOutput); +const kValidationErrorOutputResponse = OutputResponse(output: kValidationErrorOutput); +const kPreparationErrorOutputResponse = OutputResponse(output: kPreparationErrorOutput); @GenerateMocks([CodeClient]) void main() { @@ -74,27 +74,27 @@ void main() { when(client.runCode(kRequestMock)).thenAnswer( (_) async => kRunCodeResponse, ); - when(client.checkStatus(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.checkStatus(kPipelineUuid)).thenAnswer( (_) async => kFinishedStatusResponse, ); - when(client.getRunOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getRunOutput(kPipelineUuid)).thenAnswer( (_) async => kRunOutputResponse, ); - when(client.getCompileOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getCompileOutput(kPipelineUuid)).thenAnswer( (_) async => kCompileOutputResponse, ); - when(client.getRunErrorOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getRunErrorOutput(kPipelineUuid)).thenAnswer( (_) async => kRunErrorOutputResponse, ); - when(client.getLogOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getLogOutput(kPipelineUuid)).thenAnswer( (_) async => kLogOutputResponse, ); - when(client.getGraphOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getGraphOutput(kPipelineUuid)).thenAnswer( (_) async => kGraphResponse, ); // test variables - final repository = CodeRepository(client); + final repository = CodeRepository(client: client); final stream = repository.runCode(kRequestMock); // test assertion @@ -115,7 +115,7 @@ void main() { ]), ); // compile output should not be called - verifyNever(client.getCompileOutput(kPipelineUuid, kRequestMock)); + verifyNever(client.getCompileOutput(kPipelineUuid)); }); test('should return output from compilation if failed', () async { @@ -124,24 +124,24 @@ void main() { when(client.runCode(kRequestMock)).thenAnswer( (_) async => kRunCodeResponse, ); - when(client.checkStatus(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.checkStatus(kPipelineUuid)).thenAnswer( (_) async => kCompileErrorStatusResponse, ); - when(client.getCompileOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getCompileOutput(kPipelineUuid)).thenAnswer( (_) async => kCompileOutputResponse, ); - when(client.getRunOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getRunOutput(kPipelineUuid)).thenAnswer( (_) async => kRunOutputResponse, ); - when(client.getLogOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getLogOutput(kPipelineUuid)).thenAnswer( (_) async => kLogOutputResponse, ); - when(client.getGraphOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getGraphOutput(kPipelineUuid)).thenAnswer( (_) async => kGraphResponse, ); // test variables - final repository = CodeRepository(client); + final repository = CodeRepository(client: client); final stream = repository.runCode(kRequestMock); // test assertion @@ -170,19 +170,19 @@ void main() { when(client.runCode(kRequestMock)).thenAnswer( (_) async => kRunCodeResponse, ); - when(client.checkStatus(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.checkStatus(kPipelineUuid)).thenAnswer( (_) async => kValidationErrorStatusResponse, ); - when(client.getValidationErrorOutput(kPipelineUuid, kRequestMock)) + when(client.getValidationErrorOutput(kPipelineUuid)) .thenAnswer( (_) async => kValidationErrorOutputResponse, ); - when(client.getGraphOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getGraphOutput(kPipelineUuid)).thenAnswer( (_) async => kGraphResponse, ); // test variables - final repository = CodeRepository(client); + final repository = CodeRepository(client: client); final stream = repository.runCode(kRequestMock); // test assertion @@ -210,19 +210,19 @@ void main() { when(client.runCode(kRequestMock)).thenAnswer( (_) async => kRunCodeResponse, ); - when(client.checkStatus(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.checkStatus(kPipelineUuid)).thenAnswer( (_) async => kPreparationErrorStatusResponse, ); - when(client.getPreparationErrorOutput(kPipelineUuid, kRequestMock)) + when(client.getPreparationErrorOutput(kPipelineUuid)) .thenAnswer( (_) async => kPreparationErrorOutputResponse, ); - when(client.getGraphOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getGraphOutput(kPipelineUuid)).thenAnswer( (_) async => kGraphResponse, ); // test variables - final repository = CodeRepository(client); + final repository = CodeRepository(client: client); final stream = repository.runCode(kRequestMock); // test assertion @@ -250,27 +250,27 @@ void main() { when(client.runCode(kRequestMock)).thenAnswer( (_) async => kRunCodeResponse, ); - when(client.checkStatus(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.checkStatus(kPipelineUuid)).thenAnswer( (_) async => kRunErrorStatusResponse, ); - when(client.getCompileOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getCompileOutput(kPipelineUuid)).thenAnswer( (_) async => kCompileOutputResponse, ); - when(client.getRunOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getRunOutput(kPipelineUuid)).thenAnswer( (_) async => kRunOutputResponse, ); - when(client.getRunErrorOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getRunErrorOutput(kPipelineUuid)).thenAnswer( (_) async => kRunErrorOutputResponse, ); - when(client.getLogOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getLogOutput(kPipelineUuid)).thenAnswer( (_) async => kLogOutputResponse, ); - when(client.getGraphOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getGraphOutput(kPipelineUuid)).thenAnswer( (_) async => kGraphResponse, ); // test variables - final repository = CodeRepository(client); + final repository = CodeRepository(client: client); final stream = repository.runCode(kRequestMock); // test assertion @@ -305,24 +305,24 @@ void main() { kExecutingStatusResponse, kFinishedStatusResponse ]; - when(client.checkStatus(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.checkStatus(kPipelineUuid)).thenAnswer( (_) async => answers.removeAt(0), ); - when(client.getRunOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getRunOutput(kPipelineUuid)).thenAnswer( (_) async => kRunOutputResponse, ); - when(client.getRunErrorOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getRunErrorOutput(kPipelineUuid)).thenAnswer( (_) async => kRunErrorOutputResponse, ); - when(client.getLogOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getLogOutput(kPipelineUuid)).thenAnswer( (_) async => kLogOutputResponse, ); - when(client.getGraphOutput(kPipelineUuid, kRequestMock)).thenAnswer( + when(client.getGraphOutput(kPipelineUuid)).thenAnswer( (_) async => kGraphResponse, ); // test variables - final repository = CodeRepository(client); + final repository = CodeRepository(client: client); final stream = repository.runCode(kRequestMock); // test assertion diff --git a/playground/frontend/playground_components/test/src/repositories/example_repository_test.dart b/playground/frontend/playground_components/test/src/repositories/example_repository_test.dart new file mode 100644 index 0000000000000..7046f1e553c60 --- /dev/null +++ b/playground/frontend/playground_components/test/src/repositories/example_repository_test.dart @@ -0,0 +1,130 @@ +/* + * 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_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:playground_components/src/repositories/example_client/example_client.dart'; +import 'package:playground_components/src/repositories/example_repository.dart'; + +import '../common/requests.dart'; +import 'example_repository_test.mocks.dart'; + +@GenerateMocks([ExampleClient]) +void main() { + late ExampleRepository repo; + late ExampleClient client; + + setUp( + () { + client = MockExampleClient(); + repo = ExampleRepository(client: client); + }, + ); + + test( + 'Example repository getListOfExamples should return response with categories', + () async { + when(client.getPrecompiledObjects(kGetPrecompiledObjectsRequest)) + .thenAnswer((_) async => kGetPrecompiledObjectsResponse); + expect( + await repo.getListOfExamples(kGetPrecompiledObjectsRequest), + kGetPrecompiledObjectsResponse.categories, + ); + verify(client.getPrecompiledObjects(kGetPrecompiledObjectsRequest)).called(1); + }, + ); + + test( + 'Example repository getDefaultExample should return defaultExample for chosen Sdk', + () async { + when(client.getDefaultPrecompiledObject(kGetDefaultPrecompiledObjectRequest)) + .thenAnswer((_) async => kGetDefaultPrecompiledObjectResponse); + expect( + await repo.getDefaultExample(kGetDefaultPrecompiledObjectRequest), + kGetDefaultPrecompiledObjectResponse.example, + ); + verify(client.getDefaultPrecompiledObject(kGetDefaultPrecompiledObjectRequest)).called(1); + }, + ); + + test( + 'Example repository getExampleSource should return source code for example', + () async { + when(client.getPrecompiledObjectCode(kRequestForExampleInfo)) + .thenAnswer((_) async => kGetPrecompiledObjectCodeResponse); + expect( + await repo.getExampleSource(kRequestForExampleInfo), + kGetPrecompiledObjectCodeResponse.code, + ); + verify(client.getPrecompiledObjectCode(kRequestForExampleInfo)).called(1); + }, + ); + + test( + 'Example repository getExampleOutput should return output for example', + () async { + when(client.getPrecompiledObjectOutput(kRequestForExampleInfo)) + .thenAnswer((_) async => kOutputResponse); + expect( + await repo.getExampleOutput(kRequestForExampleInfo), + kOutputResponse.output, + ); + verify(client.getPrecompiledObjectOutput(kRequestForExampleInfo)).called(1); + }, + ); + + test( + 'Example repository getExampleLogs should return logs for example', + () async { + when(client.getPrecompiledObjectLogs(kRequestForExampleInfo)) + .thenAnswer((_) async => kOutputResponse); + expect( + await repo.getExampleLogs(kRequestForExampleInfo), + kOutputResponse.output, + ); + verify(client.getPrecompiledObjectLogs(kRequestForExampleInfo)).called(1); + }, + ); + + test( + 'Example repository getExampleGraph should return logs for example', + () async { + when(client.getPrecompiledObjectGraph(kRequestForExampleInfo)) + .thenAnswer((_) async => kOutputResponse); + expect( + await repo.getExampleGraph(kRequestForExampleInfo), + kOutputResponse.output, + ); + verify(client.getPrecompiledObjectGraph(kRequestForExampleInfo)).called(1); + }, + ); + + test( + 'Example repository getExample should return ExampleModel', + () async { + when(client.getPrecompiledObject(kRequestForExampleInfo)) + .thenAnswer((_) async => kGetDefaultPrecompiledObjectResponse); + expect( + await repo.getExample(kRequestForExampleInfo), + kGetDefaultPrecompiledObjectResponse.example, + ); + verify(client.getPrecompiledObject(kRequestForExampleInfo)).called(1); + }, + ); +} diff --git a/playground/frontend/test/modules/editor/parsers/run_options_parser_test.dart b/playground/frontend/playground_components/test/src/util/pipeline_options_test.dart similarity index 96% rename from playground/frontend/test/modules/editor/parsers/run_options_parser_test.dart rename to playground/frontend/playground_components/test/src/util/pipeline_options_test.dart index 26657af6cc6c8..cb15e5b046e63 100644 --- a/playground/frontend/test/modules/editor/parsers/run_options_parser_test.dart +++ b/playground/frontend/playground_components/test/src/util/pipeline_options_test.dart @@ -17,7 +17,7 @@ */ import 'package:flutter_test/flutter_test.dart'; -import 'package:playground/modules/editor/parsers/run_options_parser.dart'; +import 'package:playground_components/src/util/pipeline_options.dart'; void main() { group('PipelineOptions parser', () { diff --git a/playground/frontend/test/utils/run_with_retry_test.dart b/playground/frontend/playground_components/test/src/util/run_with_retry_test.dart similarity index 97% rename from playground/frontend/test/utils/run_with_retry_test.dart rename to playground/frontend/playground_components/test/src/util/run_with_retry_test.dart index 39d7a9ddb6b67..5c655a01f3c44 100644 --- a/playground/frontend/test/utils/run_with_retry_test.dart +++ b/playground/frontend/playground_components/test/src/util/run_with_retry_test.dart @@ -17,7 +17,7 @@ */ import 'package:flutter_test/flutter_test.dart'; -import 'package:playground/utils/run_with_retry.dart'; +import 'package:playground_components/src/util/run_with_retry.dart'; class ExecutionTime { final int time; diff --git a/learning/tour-of-beam/frontend/test/config/theme/switch_notifier_test.dart b/playground/frontend/playground_components/test/theme/switch_notifier_test.dart similarity index 93% rename from learning/tour-of-beam/frontend/test/config/theme/switch_notifier_test.dart rename to playground/frontend/playground_components/test/theme/switch_notifier_test.dart index 2565c073d2a4c..b94ee69585240 100644 --- a/learning/tour-of-beam/frontend/test/config/theme/switch_notifier_test.dart +++ b/playground/frontend/playground_components/test/theme/switch_notifier_test.dart @@ -17,7 +17,7 @@ */ import 'package:flutter_test/flutter_test.dart'; -import 'package:tour_of_beam/config/theme/switch_notifier.dart'; +import 'package:playground_components/src/theme/switch_notifier.dart'; void main() { group('theme mode', () { diff --git a/playground/frontend/pubspec.lock b/playground/frontend/pubspec.lock index eb1df6618a43b..7b3186dbd6bd4 100644 --- a/playground/frontend/pubspec.lock +++ b/playground/frontend/pubspec.lock @@ -35,7 +35,7 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "3.3.0" + version: "3.3.1" args: dependency: transitive description: @@ -49,7 +49,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -119,7 +119,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" charcode: dependency: transitive description: @@ -140,7 +140,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: @@ -149,7 +149,7 @@ packages: source: hosted version: "4.1.0" code_text_field: - dependency: "direct main" + dependency: transitive description: path: "." ref: "9e2c9fe52a69481f038f4b6609e8a0a776429437" @@ -185,6 +185,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.17.2" + csv: + dependency: transitive + description: + name: csv + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.1" dart_style: dependency: transitive description: @@ -192,6 +199,41 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.2.3" + easy_localization: + dependency: "direct main" + description: + name: easy_localization + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + easy_localization_ext: + dependency: "direct main" + description: + name: easy_localization_ext + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1" + easy_localization_loader: + dependency: "direct main" + description: + name: easy_localization_loader + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + easy_logger: + dependency: transitive + description: + name: easy_logger + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.2" + equatable: + dependency: transitive + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" expansion_widget: dependency: "direct main" description: @@ -205,7 +247,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: transitive description: @@ -257,7 +299,7 @@ packages: name: flutter_svg url: "https://pub.dartlang.org" source: hosted - version: "0.22.0" + version: "1.0.3" flutter_test: dependency: "direct dev" description: flutter @@ -288,7 +330,7 @@ packages: name: google_fonts url: "https://pub.dartlang.org" source: hosted - version: "2.3.3" + version: "3.0.1" googleapis_auth: dependency: transitive description: @@ -304,14 +346,14 @@ packages: source: hosted version: "2.1.0" grpc: - dependency: "direct main" + dependency: transitive description: name: grpc url: "https://pub.dartlang.org" source: hosted version: "3.0.2" highlight: - dependency: "direct main" + dependency: transitive description: name: highlight url: "https://pub.dartlang.org" @@ -407,21 +449,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" mime: dependency: transitive description: @@ -463,21 +505,21 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" path_drawing: dependency: transitive description: name: path_drawing url: "https://pub.dartlang.org" source: hosted - version: "0.5.1+1" + version: "1.0.1" path_parsing: dependency: transitive description: name: path_parsing url: "https://pub.dartlang.org" source: hosted - version: "0.2.1" + version: "1.0.1" path_provider: dependency: transitive description: @@ -541,6 +583,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.0" + playground_components: + dependency: "direct main" + description: + path: playground_components + relative: true + source: path + version: "0.0.1" plugin_platform_interface: dependency: transitive description: @@ -568,7 +617,7 @@ packages: name: protobuf url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.1.0" provider: dependency: "direct main" description: @@ -678,7 +727,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -706,21 +755,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" timing: dependency: transitive description: @@ -805,6 +854,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.1" + url_strategy: + dependency: "direct main" + description: + name: url_strategy + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" usage: dependency: "direct main" description: @@ -869,5 +925,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.17.0 <3.0.0" - flutter: ">=3.0.0" + dart: ">=2.18.1 <3.0.0" + flutter: ">=3.3.2" diff --git a/playground/frontend/pubspec.yaml b/playground/frontend/pubspec.yaml index 91027ecd99fc5..adba7a981e246 100644 --- a/playground/frontend/pubspec.yaml +++ b/playground/frontend/pubspec.yaml @@ -21,29 +21,29 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: ">=2.17.0 <3.0.0" + sdk: ">=2.18.1 <3.0.0" + flutter: ">=3.3.2" dependencies: akvelon_flutter_issue_106664_workaround: ^0.1.2 aligned_dialog: ^0.0.6 - code_text_field: - git: - url: https://github.com/BertrandBev/code_field.git - ref: 9e2c9fe52a69481f038f4b6609e8a0a776429437 collection: ^1.15.0 + easy_localization: ^3.0.1 + easy_localization_ext: ^0.1.1 + easy_localization_loader: ^1.0.0 expansion_widget: ^0.0.2 flutter: { sdk: flutter } flutter_localizations: { sdk: flutter } - flutter_svg: ^0.22.0 + flutter_svg: ^1.0.3 flutter_web_plugins: { sdk: flutter } - google_fonts: ^2.3.1 - grpc: ^3.0.0 - highlight: ^0.7.0 + google_fonts: ^3.0.1 intl: ^0.17.0 onmessage: ^0.2.0 + playground_components: { path: playground_components } provider: ^6.0.0 shared_preferences: ^2.0.12 url_launcher: ^6.0.12 + url_strategy: ^0.2.0 usage: ^4.0.2 dev_dependencies: @@ -56,6 +56,7 @@ dev_dependencies: flutter: assets: - assets/ + - assets/translations/en.yaml generate: true uses-material-design: true diff --git a/playground/frontend/test/modules/editor/repository/code_repository/code_repository_test.mocks.dart b/playground/frontend/test/modules/editor/repository/code_repository/code_repository_test.mocks.dart deleted file mode 100644 index b2429f18816c5..0000000000000 --- a/playground/frontend/test/modules/editor/repository/code_repository/code_repository_test.mocks.dart +++ /dev/null @@ -1,138 +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. - */ -// Mocks generated by Mockito 5.0.16 from annotations -// in playground/test/modules/editor/repository/code_repository/code_repository_test.dart. -// Do not manually edit this file. - -import 'dart:async' as _i6; - -import 'package:mockito/mockito.dart' as _i1; -import 'package:playground/modules/editor/repository/code_repository/code_client/check_status_response.dart' - as _i3; -import 'package:playground/modules/editor/repository/code_repository/code_client/code_client.dart' - as _i5; -import 'package:playground/modules/editor/repository/code_repository/code_client/output_response.dart' - as _i4; -import 'package:playground/modules/editor/repository/code_repository/code_client/run_code_response.dart' - as _i2; -import 'package:playground/modules/editor/repository/code_repository/run_code_request.dart' - as _i7; - -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types - -class _FakeRunCodeResponse_0 extends _i1.Fake implements _i2.RunCodeResponse {} - -class _FakeCheckStatusResponse_1 extends _i1.Fake - implements _i3.CheckStatusResponse {} - -class _FakeOutputResponse_2 extends _i1.Fake implements _i4.OutputResponse {} - -/// A class which mocks [CodeClient]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockCodeClient extends _i1.Mock implements _i5.CodeClient { - MockCodeClient() { - _i1.throwOnMissingStub(this); - } - - @override - _i6.Future<_i2.RunCodeResponse> runCode(_i7.RunCodeRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#runCode, [request]), - returnValue: - Future<_i2.RunCodeResponse>.value(_FakeRunCodeResponse_0())) - as _i6.Future<_i2.RunCodeResponse>); - @override - _i6.Future cancelExecution(String? pipelineUuid) => - (super.noSuchMethod(Invocation.method(#cancelExecution, [pipelineUuid]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i6.Future); - @override - _i6.Future<_i3.CheckStatusResponse> checkStatus( - String? pipelineUuid, _i7.RunCodeRequestWrapper? request) => - (super.noSuchMethod( - Invocation.method(#checkStatus, [pipelineUuid, request]), - returnValue: Future<_i3.CheckStatusResponse>.value( - _FakeCheckStatusResponse_1())) - as _i6.Future<_i3.CheckStatusResponse>); - @override - _i6.Future<_i4.OutputResponse> getCompileOutput( - String? pipelineUuid, _i7.RunCodeRequestWrapper? request) => - (super.noSuchMethod( - Invocation.method(#getCompileOutput, [pipelineUuid, request]), - returnValue: - Future<_i4.OutputResponse>.value(_FakeOutputResponse_2())) - as _i6.Future<_i4.OutputResponse>); - @override - _i6.Future<_i4.OutputResponse> getRunOutput( - String? pipelineUuid, _i7.RunCodeRequestWrapper? request) => - (super.noSuchMethod( - Invocation.method(#getRunOutput, [pipelineUuid, request]), - returnValue: - Future<_i4.OutputResponse>.value(_FakeOutputResponse_2())) - as _i6.Future<_i4.OutputResponse>); - @override - _i6.Future<_i4.OutputResponse> getLogOutput( - String? pipelineUuid, _i7.RunCodeRequestWrapper? request) => - (super.noSuchMethod( - Invocation.method(#getLogOutput, [pipelineUuid, request]), - returnValue: - Future<_i4.OutputResponse>.value(_FakeOutputResponse_2())) - as _i6.Future<_i4.OutputResponse>); - @override - _i6.Future<_i4.OutputResponse> getRunErrorOutput( - String? pipelineUuid, _i7.RunCodeRequestWrapper? request) => - (super.noSuchMethod( - Invocation.method(#getRunErrorOutput, [pipelineUuid, request]), - returnValue: - Future<_i4.OutputResponse>.value(_FakeOutputResponse_2())) - as _i6.Future<_i4.OutputResponse>); - @override - _i6.Future<_i4.OutputResponse> getValidationErrorOutput( - String? pipelineUuid, _i7.RunCodeRequestWrapper? request) => - (super.noSuchMethod( - Invocation.method(#getValidationErrorOutput, [pipelineUuid, request]), - returnValue: - Future<_i4.OutputResponse>.value(_FakeOutputResponse_2())) as _i6 - .Future<_i4.OutputResponse>); - @override - _i6.Future<_i4.OutputResponse> getPreparationErrorOutput( - String? pipelineUuid, _i7.RunCodeRequestWrapper? request) => - (super.noSuchMethod( - Invocation.method( - #getPreparationErrorOutput, [pipelineUuid, request]), - returnValue: - Future<_i4.OutputResponse>.value(_FakeOutputResponse_2())) - as _i6.Future<_i4.OutputResponse>); - @override - _i6.Future<_i4.OutputResponse> getGraphOutput( - String? pipelineUuid, _i7.RunCodeRequestWrapper? request) => - (super.noSuchMethod( - Invocation.method(#getGraphOutput, [pipelineUuid, request]), - returnValue: - Future<_i4.OutputResponse>.value(_FakeOutputResponse_2())) - as _i6.Future<_i4.OutputResponse>); - @override - String toString() => super.toString(); -} diff --git a/playground/frontend/test/modules/editor/repository/example_repository/example_repository_test.dart b/playground/frontend/test/modules/editor/repository/example_repository/example_repository_test.dart deleted file mode 100644 index 5e08b37738479..0000000000000 --- a/playground/frontend/test/modules/editor/repository/example_repository/example_repository_test.dart +++ /dev/null @@ -1,130 +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:flutter_test/flutter_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:playground/modules/examples/repositories/example_client/example_client.dart'; -import 'package:playground/modules/examples/repositories/example_repository.dart'; - -import '../../../../pages/playground/states/mocks/request_mock.dart'; -import 'example_repository_test.mocks.dart'; - -@GenerateMocks([ExampleClient]) -void main() { - late ExampleRepository repo; - late ExampleClient client; - - setUp( - () { - client = MockExampleClient(); - repo = ExampleRepository(client); - }, - ); - - test( - 'Example repository getListOfExamples should return response with categories', - () async { - when(client.getListOfExamples(kGetListOfExamplesRequestMock)) - .thenAnswer((_) async => kGetListOfExamplesResponseMock); - expect( - await repo.getListOfExamples(kGetListOfExamplesRequestMock), - kGetListOfExamplesResponseMock.categories, - ); - verify(client.getListOfExamples(kGetListOfExamplesRequestMock)).called(1); - }, - ); - - test( - 'Example repository getDefaultExample should return defaultExample for chosen Sdk', - () async { - when(client.getDefaultExample(kGetExampleRequestMock)) - .thenAnswer((_) async => kGetExampleResponseMock); - expect( - await repo.getDefaultExample(kGetExampleRequestMock), - kGetExampleResponseMock.example, - ); - verify(client.getDefaultExample(kGetExampleRequestMock)).called(1); - }, - ); - - test( - 'Example repository getExampleSource should return source code for example', - () async { - when(client.getExampleSource(kGetExampleRequestMock)) - .thenAnswer((_) async => kGetExampleCodeResponseMock); - expect( - await repo.getExampleSource(kGetExampleRequestMock), - kGetExampleCodeResponseMock.code, - ); - verify(client.getExampleSource(kGetExampleRequestMock)).called(1); - }, - ); - - test( - 'Example repository getExampleOutput should return output for example', - () async { - when(client.getExampleOutput(kGetExampleRequestMock)) - .thenAnswer((_) async => kOutputResponseMock); - expect( - await repo.getExampleOutput(kGetExampleRequestMock), - kOutputResponseMock.output, - ); - verify(client.getExampleOutput(kGetExampleRequestMock)).called(1); - }, - ); - - test( - 'Example repository getExampleLogs should return logs for example', - () async { - when(client.getExampleLogs(kGetExampleRequestMock)) - .thenAnswer((_) async => kOutputResponseMock); - expect( - await repo.getExampleLogs(kGetExampleRequestMock), - kOutputResponseMock.output, - ); - verify(client.getExampleLogs(kGetExampleRequestMock)).called(1); - }, - ); - - test( - 'Example repository getExampleLogs should return logs for example', - () async { - when(client.getExampleGraph(kGetExampleRequestMock)) - .thenAnswer((_) async => kOutputResponseMock); - expect( - await repo.getExampleGraph(kGetExampleRequestMock), - kOutputResponseMock.output, - ); - verify(client.getExampleGraph(kGetExampleRequestMock)).called(1); - }, - ); - - test( - 'Example repository getExample should return ExampleModel', - () async { - when(client.getExample(kGetExampleRequestMock)) - .thenAnswer((_) async => kGetExampleResponseMock); - expect( - await repo.getExample(kGetExampleRequestMock), - kGetExampleResponseMock.example, - ); - verify(client.getExample(kGetExampleRequestMock)).called(1); - }, - ); -} diff --git a/playground/frontend/test/modules/editor/repository/example_repository/example_repository_test.mocks.dart b/playground/frontend/test/modules/editor/repository/example_repository/example_repository_test.mocks.dart deleted file mode 100644 index 92bcb2546a5a8..0000000000000 --- a/playground/frontend/test/modules/editor/repository/example_repository/example_repository_test.mocks.dart +++ /dev/null @@ -1,147 +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. - */ - -// Mocks generated by Mockito 5.1.0 from annotations -// in playground/test/modules/editor/repository/example_repository/example_repository_test.dart. -// Do not manually edit this file. - -import 'dart:async' as _i9; - -import 'package:mockito/mockito.dart' as _i1; -import 'package:playground/modules/editor/repository/code_repository/code_client/output_response.dart' - as _i5; -import 'package:playground/modules/examples/repositories/example_client/example_client.dart' - as _i8; -import 'package:playground/modules/examples/repositories/models/get_example_code_response.dart' - as _i3; -import 'package:playground/modules/examples/repositories/models/get_example_request.dart' - as _i11; -import 'package:playground/modules/examples/repositories/models/get_example_response.dart' - as _i4; -import 'package:playground/modules/examples/repositories/models/get_list_of_examples_request.dart' - as _i10; -import 'package:playground/modules/examples/repositories/models/get_list_of_examples_response.dart' - as _i2; -import 'package:playground/modules/examples/repositories/models/get_snippet_request.dart' - as _i12; -import 'package:playground/modules/examples/repositories/models/get_snippet_response.dart' - as _i6; -import 'package:playground/modules/examples/repositories/models/save_snippet_request.dart' - as _i13; -import 'package:playground/modules/examples/repositories/models/save_snippet_response.dart' - as _i7; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types - -class _FakeGetListOfExampleResponse_0 extends _i1.Fake - implements _i2.GetListOfExampleResponse {} - -class _FakeGetExampleCodeResponse_1 extends _i1.Fake - implements _i3.GetExampleCodeResponse {} - -class _FakeGetExampleResponse_2 extends _i1.Fake - implements _i4.GetExampleResponse {} - -class _FakeOutputResponse_3 extends _i1.Fake implements _i5.OutputResponse {} - -class _FakeGetSnippetResponse_4 extends _i1.Fake - implements _i6.GetSnippetResponse {} - -class _FakeSaveSnippetResponse_5 extends _i1.Fake - implements _i7.SaveSnippetResponse {} - -/// A class which mocks [ExampleClient]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockExampleClient extends _i1.Mock implements _i8.ExampleClient { - MockExampleClient() { - _i1.throwOnMissingStub(this); - } - - @override - _i9.Future<_i2.GetListOfExampleResponse> getListOfExamples( - _i10.GetListOfExamplesRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getListOfExamples, [request]), - returnValue: Future<_i2.GetListOfExampleResponse>.value( - _FakeGetListOfExampleResponse_0())) - as _i9.Future<_i2.GetListOfExampleResponse>); - @override - _i9.Future<_i3.GetExampleCodeResponse> getExampleSource( - _i11.GetExampleRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getExampleSource, [request]), - returnValue: Future<_i3.GetExampleCodeResponse>.value( - _FakeGetExampleCodeResponse_1())) - as _i9.Future<_i3.GetExampleCodeResponse>); - @override - _i9.Future<_i4.GetExampleResponse> getDefaultExample( - _i11.GetExampleRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getDefaultExample, [request]), - returnValue: Future<_i4.GetExampleResponse>.value( - _FakeGetExampleResponse_2())) - as _i9.Future<_i4.GetExampleResponse>); - @override - _i9.Future<_i4.GetExampleResponse> getExample( - _i11.GetExampleRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getExample, [request]), - returnValue: Future<_i4.GetExampleResponse>.value( - _FakeGetExampleResponse_2())) - as _i9.Future<_i4.GetExampleResponse>); - @override - _i9.Future<_i5.OutputResponse> getExampleOutput( - _i11.GetExampleRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getExampleOutput, [request]), - returnValue: - Future<_i5.OutputResponse>.value(_FakeOutputResponse_3())) - as _i9.Future<_i5.OutputResponse>); - @override - _i9.Future<_i5.OutputResponse> getExampleLogs( - _i11.GetExampleRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getExampleLogs, [request]), - returnValue: - Future<_i5.OutputResponse>.value(_FakeOutputResponse_3())) - as _i9.Future<_i5.OutputResponse>); - @override - _i9.Future<_i5.OutputResponse> getExampleGraph( - _i11.GetExampleRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getExampleGraph, [request]), - returnValue: - Future<_i5.OutputResponse>.value(_FakeOutputResponse_3())) - as _i9.Future<_i5.OutputResponse>); - @override - _i9.Future<_i6.GetSnippetResponse> getSnippet( - _i12.GetSnippetRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getSnippet, [request]), - returnValue: Future<_i6.GetSnippetResponse>.value( - _FakeGetSnippetResponse_4())) - as _i9.Future<_i6.GetSnippetResponse>); - @override - _i9.Future<_i7.SaveSnippetResponse> saveSnippet( - _i13.SaveSnippetRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#saveSnippet, [request]), - returnValue: Future<_i7.SaveSnippetResponse>.value( - _FakeSaveSnippetResponse_5())) - as _i9.Future<_i7.SaveSnippetResponse>); -} diff --git a/playground/frontend/test/modules/messages/handlers/messages_debouncer_test.dart b/playground/frontend/test/modules/messages/handlers/messages_debouncer_test.dart index df5266c23dd5f..7999493bea33b 100644 --- a/playground/frontend/test/modules/messages/handlers/messages_debouncer_test.dart +++ b/playground/frontend/test/modules/messages/handlers/messages_debouncer_test.dart @@ -24,7 +24,7 @@ import 'package:playground/modules/messages/handlers/abstract_message_handler.da import 'package:playground/modules/messages/handlers/messages_debouncer.dart'; import 'package:playground/modules/messages/models/abstract_message.dart'; import 'package:playground/modules/messages/models/set_sdk_message.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/playground_components.dart'; void main() { group('MessagesDebouncer', () { @@ -38,13 +38,13 @@ void main() { test('drops sequential calls, no time limit', () { fakeAsync((async) { - debouncer.handle(SetSdkMessage(sdk: SDK.java)); - debouncer.handle(SetSdkMessage(sdk: SDK.java)); + debouncer.handle(SetSdkMessage(sdk: Sdk.java)); + debouncer.handle(SetSdkMessage(sdk: Sdk.java)); async.elapse(const Duration(days: 36500)); - debouncer.handle(SetSdkMessage(sdk: SDK.java)); + debouncer.handle(SetSdkMessage(sdk: Sdk.java)); }); - expect(recorder.messages, [SetSdkMessage(sdk: SDK.java)]); + expect(recorder.messages, [SetSdkMessage(sdk: Sdk.java)]); }); test('returns the last result on debouncing', () { diff --git a/playground/frontend/test/modules/messages/models/set_content_message_test.dart b/playground/frontend/test/modules/messages/models/set_content_message_test.dart index c472a8269e44f..a552501b84a9e 100644 --- a/playground/frontend/test/modules/messages/models/set_content_message_test.dart +++ b/playground/frontend/test/modules/messages/models/set_content_message_test.dart @@ -17,14 +17,11 @@ */ import 'package:flutter_test/flutter_test.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/content_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/empty_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/examples_loading_descriptor.dart'; import 'package:playground/modules/messages/models/set_content_message.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/playground_components.dart'; const _content = 'my_code'; -const _sdk = SDK.python; +const _sdk = Sdk.python; void main() { group('SetContentMessage.tryParse', () { @@ -50,7 +47,7 @@ void main() { parsed, const SetContentMessage( descriptor: ExamplesLoadingDescriptor( - descriptors: [EmptyExampleLoadingDescriptor(sdk: SDK.java)], + descriptors: [EmptyExampleLoadingDescriptor(sdk: Sdk.java)], ), ), ); @@ -71,7 +68,7 @@ void main() { parsed, const SetContentMessage( descriptor: ExamplesLoadingDescriptor( - descriptors: [EmptyExampleLoadingDescriptor(sdk: SDK.java)], + descriptors: [EmptyExampleLoadingDescriptor(sdk: Sdk.java)], ), ), ); @@ -94,16 +91,16 @@ void main() { { 'content': _content, 'name': 'name', - 'sdk': _sdk.name, + 'sdk': _sdk.id, }, { 'content': _content, 'name': null, - 'sdk': _sdk.name, + 'sdk': _sdk.id, }, { 'content': _content, - 'sdk': _sdk.name, + 'sdk': _sdk.id, }, ], }, diff --git a/playground/frontend/test/modules/messages/models/set_sdk_message_test.dart b/playground/frontend/test/modules/messages/models/set_sdk_message_test.dart index e608a5b5c0244..c9223849bbfeb 100644 --- a/playground/frontend/test/modules/messages/models/set_sdk_message_test.dart +++ b/playground/frontend/test/modules/messages/models/set_sdk_message_test.dart @@ -18,9 +18,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:playground/modules/messages/models/set_sdk_message.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/playground_components.dart'; -const _sdk = SDK.python; +const _sdk = Sdk.python; void main() { group('SetSdkMessage.tryParse', () { @@ -49,7 +49,7 @@ void main() { test( 'parses an SDK', () { - final map = {'type': SetSdkMessage.type, 'sdk': _sdk.name}; + final map = {'type': SetSdkMessage.type, 'sdk': _sdk.id}; final parsed = SetSdkMessage.tryParse(map); diff --git a/playground/frontend/test/modules/messages/parsers/message_parser_test.dart b/playground/frontend/test/modules/messages/parsers/message_parser_test.dart index 24173cece3193..00b580143457c 100644 --- a/playground/frontend/test/modules/messages/parsers/message_parser_test.dart +++ b/playground/frontend/test/modules/messages/parsers/message_parser_test.dart @@ -17,14 +17,12 @@ */ import 'package:flutter_test/flutter_test.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/empty_example_loading_descriptor.dart'; -import 'package:playground/modules/examples/models/example_loading_descriptors/examples_loading_descriptor.dart'; import 'package:playground/modules/messages/models/set_content_message.dart'; import 'package:playground/modules/messages/models/set_sdk_message.dart'; import 'package:playground/modules/messages/parsers/messages_parser.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; +import 'package:playground_components/playground_components.dart'; -const _sdk = SDK.python; +const _sdk = Sdk.python; void main() { group('MessageParser.parse returns null for invalid inputs', () { @@ -96,7 +94,7 @@ void main() { parsed, const SetContentMessage( descriptor: ExamplesLoadingDescriptor( - descriptors: [EmptyExampleLoadingDescriptor(sdk: SDK.java)], + descriptors: [EmptyExampleLoadingDescriptor(sdk: Sdk.java)], ), ), ); @@ -106,7 +104,7 @@ void main() { test( 'MessageParser.parse parses SetSdkMessage', () { - final value = {'type': SetSdkMessage.type, 'sdk': _sdk.name}; + final value = {'type': SetSdkMessage.type, 'sdk': _sdk.id}; final parsed = MessagesParser().tryParse(value); diff --git a/playground/frontend/test/pages/playground/states/example_selector_state_test.dart b/playground/frontend/test/pages/playground/states/example_selector_state_test.dart index 99d847f38e07b..1d1c37d4d9570 100644 --- a/playground/frontend/test/pages/playground/states/example_selector_state_test.dart +++ b/playground/frontend/test/pages/playground/states/example_selector_state_test.dart @@ -18,30 +18,34 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/pages/playground/states/example_loaders/examples_loader.dart'; +import 'package:playground_components/src/models/example_base.dart'; +import 'package:playground_components/src/controllers/example_loaders/examples_loader.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:playground_components/src/cache/example_cache.dart'; +import 'package:playground_components/src/controllers/playground_controller.dart'; import 'example_selector_state_test.mocks.dart'; -import 'mocks/categories_mock.dart'; -import 'mocks/example_repository_mock.dart'; +import '../../../../playground_components/test/src/common/categories.dart'; +import '../../../../playground_components/test/src/common/example_repository_mock.dart'; @GenerateMocks([ExamplesLoader]) void main() { - late PlaygroundState playgroundState; - late ExampleState exampleState; + late PlaygroundController playgroundController; + late ExampleCache exampleCache; late ExampleSelectorState state; final mockExampleRepository = getMockExampleRepository(); setUp(() { - exampleState = ExampleState(mockExampleRepository); - playgroundState = PlaygroundState( + exampleCache = ExampleCache( + exampleRepository: mockExampleRepository, + hasCatalog: true, + ); + + playgroundController = PlaygroundController( examplesLoader: MockExamplesLoader(), - exampleState: exampleState, + exampleCache: exampleCache, ); - state = ExampleSelectorState(playgroundState, []); + state = ExampleSelectorState(playgroundController, []); }); test( @@ -95,7 +99,7 @@ void main() { '- affect Example state categories', () { state.addListener(() { expect(state.categories, []); - expect(exampleState.sdkCategories, exampleState.sdkCategories); + expect(exampleCache.categoryListsBySdk, exampleCache.categoryListsBySdk); }); state.sortCategories(); }); @@ -107,12 +111,12 @@ void main() { 'but should NOT:' '- affect Example state categories', () { final state = ExampleSelectorState( - playgroundState, + playgroundController, categoriesMock, ); state.addListener(() { expect(state.categories, examplesSortedByTypeMock); - expect(exampleState.sdkCategories, exampleState.sdkCategories); + expect(exampleCache.categoryListsBySdk, exampleCache.categoryListsBySdk); }); state.sortExamplesByType(unsortedExamples, ExampleType.kata); }); @@ -126,12 +130,12 @@ void main() { '- be sensitive for register,' '- affect Example state categories', () { final state = ExampleSelectorState( - playgroundState, + playgroundController, categoriesMock, ); state.addListener(() { expect(state.categories, examplesSortedByNameMock); - expect(exampleState.sdkCategories, exampleState.sdkCategories); + expect(exampleCache.categoryListsBySdk, exampleCache.categoryListsBySdk); }); state.sortExamplesByName(unsortedExamples, 'X1'); }); diff --git a/playground/frontend/test/pages/playground/states/example_selector_state_test.mocks.dart b/playground/frontend/test/pages/playground/states/example_selector_state_test.mocks.dart deleted file mode 100644 index 253c7b5d4aac7..0000000000000 --- a/playground/frontend/test/pages/playground/states/example_selector_state_test.mocks.dart +++ /dev/null @@ -1,69 +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. - */ -// Mocks generated by Mockito 5.0.16 from annotations -// in playground/test/pages/playground/states/example_selector_state_test.dart. -// Do not manually edit this file. - -import 'dart:async' as _i4; - -import 'package:mockito/mockito.dart' as _i1; -import 'package:playground/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart' - as _i6; -import 'package:playground/modules/examples/models/example_loading_descriptors/examples_loading_descriptor.dart' - as _i5; -import 'package:playground/pages/playground/states/example_loaders/examples_loader.dart' - as _i2; -import 'package:playground/pages/playground/states/playground_state.dart' - as _i3; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types - -/// A class which mocks [ExamplesLoader]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockExamplesLoader extends _i1.Mock implements _i2.ExamplesLoader { - MockExamplesLoader() { - _i1.throwOnMissingStub(this); - } - - @override - void setPlaygroundState(_i3.PlaygroundState? value) => - super.noSuchMethod(Invocation.method(#setPlaygroundState, [value]), - returnValueForMissingStub: null); - @override - _i4.Future load(_i5.ExamplesLoadingDescriptor? descriptor) => - (super.noSuchMethod(Invocation.method(#load, [descriptor]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); - @override - _i4.Future loadOne( - {_i5.ExamplesLoadingDescriptor? group, - _i6.ExampleLoadingDescriptor? one}) => - (super.noSuchMethod( - Invocation.method(#loadOne, [], {#group: group, #one: one}), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i4.Future); -} diff --git a/playground/frontend/test/pages/playground/states/mocks/categories_mock.dart b/playground/frontend/test/pages/playground/states/mocks/categories_mock.dart deleted file mode 100644 index 0079697013c0a..0000000000000 --- a/playground/frontend/test/pages/playground/states/mocks/categories_mock.dart +++ /dev/null @@ -1,45 +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:playground/modules/examples/models/category_model.dart'; -import 'package:playground/modules/examples/models/example_model.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; - -import 'example_mock.dart'; - -final categoriesMock = [ - CategoryModel(name: 'Sorted', examples: [exampleMock1]), - CategoryModel(name: 'Unsorted', examples: [exampleMock2]), -]; - -final List sortedCategories = [ - CategoryModel(name: 'Sorted', examples: [exampleMock1]), -]; - -final List unsortedExamples = [exampleMock1, exampleMock2]; - -final List examplesSortedByTypeMock = [exampleMock2]; - -final List examplesSortedByNameMock = [exampleMock1]; - -final sdkCategoriesFromServerMock = { - SDK.java: categoriesMock, - SDK.python: categoriesMock, - SDK.go: categoriesMock, - SDK.scio: categoriesMock, -}; diff --git a/playground/frontend/test/pages/playground/states/mocks/example_repository_mock.mocks.dart b/playground/frontend/test/pages/playground/states/mocks/example_repository_mock.mocks.dart deleted file mode 100644 index 8215315d7fc88..0000000000000 --- a/playground/frontend/test/pages/playground/states/mocks/example_repository_mock.mocks.dart +++ /dev/null @@ -1,113 +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. - */ - -// Mocks generated by Mockito 5.2.0 from annotations -// in playground/test/pages/playground/states/mocks/example_repository_mock.dart. -// Do not manually edit this file. - -import 'dart:async' as _i5; - -import 'package:mockito/mockito.dart' as _i1; -import 'package:playground/modules/examples/models/category_model.dart' as _i7; -import 'package:playground/modules/examples/models/example_model.dart' as _i2; -import 'package:playground/modules/examples/repositories/example_repository.dart' - as _i4; -import 'package:playground/modules/examples/repositories/models/get_example_request.dart' - as _i9; -import 'package:playground/modules/examples/repositories/models/get_list_of_examples_request.dart' - as _i8; -import 'package:playground/modules/examples/repositories/models/get_snippet_request.dart' - as _i10; -import 'package:playground/modules/examples/repositories/models/get_snippet_response.dart' - as _i3; -import 'package:playground/modules/examples/repositories/models/save_snippet_request.dart' - as _i11; -import 'package:playground/modules/sdk/models/sdk.dart' as _i6; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types - -class _FakeExampleModel_0 extends _i1.Fake implements _i2.ExampleModel {} - -class _FakeGetSnippetResponse_1 extends _i1.Fake - implements _i3.GetSnippetResponse {} - -/// A class which mocks [ExampleRepository]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockExampleRepository extends _i1.Mock implements _i4.ExampleRepository { - MockExampleRepository() { - _i1.throwOnMissingStub(this); - } - - @override - _i5.Future>> getListOfExamples( - _i8.GetListOfExamplesRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getListOfExamples, [request]), - returnValue: Future>>.value( - <_i6.SDK, List<_i7.CategoryModel>>{})) - as _i5.Future>>); - @override - _i5.Future<_i2.ExampleModel> getDefaultExample( - _i9.GetExampleRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getDefaultExample, [request]), - returnValue: - Future<_i2.ExampleModel>.value(_FakeExampleModel_0())) - as _i5.Future<_i2.ExampleModel>); - @override - _i5.Future getExampleSource(_i9.GetExampleRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getExampleSource, [request]), - returnValue: Future.value('')) as _i5.Future); - @override - _i5.Future getExampleOutput(_i9.GetExampleRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getExampleOutput, [request]), - returnValue: Future.value('')) as _i5.Future); - @override - _i5.Future getExampleLogs(_i9.GetExampleRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getExampleLogs, [request]), - returnValue: Future.value('')) as _i5.Future); - @override - _i5.Future getExampleGraph(_i9.GetExampleRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getExampleGraph, [request]), - returnValue: Future.value('')) as _i5.Future); - @override - _i5.Future<_i2.ExampleModel> getExample( - _i9.GetExampleRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getExample, [request]), - returnValue: - Future<_i2.ExampleModel>.value(_FakeExampleModel_0())) - as _i5.Future<_i2.ExampleModel>); - @override - _i5.Future<_i3.GetSnippetResponse> getSnippet( - _i10.GetSnippetRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#getSnippet, [request]), - returnValue: Future<_i3.GetSnippetResponse>.value( - _FakeGetSnippetResponse_1())) - as _i5.Future<_i3.GetSnippetResponse>); - @override - _i5.Future saveSnippet(_i11.SaveSnippetRequestWrapper? request) => - (super.noSuchMethod(Invocation.method(#saveSnippet, [request]), - returnValue: Future.value('')) as _i5.Future); -} diff --git a/playground/frontend/test/pages/playground/states/mocks/request_mock.dart b/playground/frontend/test/pages/playground/states/mocks/request_mock.dart deleted file mode 100644 index b08a81ebd55dc..0000000000000 --- a/playground/frontend/test/pages/playground/states/mocks/request_mock.dart +++ /dev/null @@ -1,45 +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:playground/modules/editor/repository/code_repository/code_client/output_response.dart'; -import 'package:playground/modules/examples/repositories/models/get_example_code_response.dart'; -import 'package:playground/modules/examples/repositories/models/get_example_request.dart'; -import 'package:playground/modules/examples/repositories/models/get_example_response.dart'; -import 'package:playground/modules/examples/repositories/models/get_list_of_examples_request.dart'; -import 'package:playground/modules/examples/repositories/models/get_list_of_examples_response.dart'; -import 'package:playground/modules/sdk/models/sdk.dart'; - -import 'categories_mock.dart'; -import 'example_mock.dart'; - -final kGetListOfExamplesRequestMock = - GetListOfExamplesRequestWrapper(sdk: null, category: null); -final kGetListOfExamplesResponseMock = - GetListOfExampleResponse(sdkCategoriesFromServerMock); -final kGetExampleRequestMock = GetExampleRequestWrapper('', SDK.java); -final kGetExampleResponseMock = GetExampleResponse(exampleMock1); -final kGetExampleCodeResponseMock = GetExampleCodeResponse('test source'); -final kOutputResponseMock = OutputResponse('test outputs'); - -final kRequestForExampleInfo = - GetExampleRequestWrapper('SDK_PYTHON/Category/Name', SDK.python); -final kRequestDefaultExampleForJava = GetExampleRequestWrapper('', SDK.java); -final kRequestDefaultExampleForGo = GetExampleRequestWrapper('', SDK.go); -final kRequestDefaultExampleForPython = - GetExampleRequestWrapper('', SDK.python); -final kRequestDefaultExampleForScio = GetExampleRequestWrapper('', SDK.scio); diff --git a/playground/frontend/test/pages/playground/states/playground_state_test.mocks.dart b/playground/frontend/test/pages/playground/states/playground_state_test.mocks.dart deleted file mode 100644 index e8c4b07d0d1e9..0000000000000 --- a/playground/frontend/test/pages/playground/states/playground_state_test.mocks.dart +++ /dev/null @@ -1,216 +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. - */ - -// Mocks generated by Mockito 5.2.0 from annotations -// in playground/test/pages/playground/states/playground_state_test.dart. -// Do not manually edit this file. - -import 'dart:async' as _i5; -import 'dart:ui' as _i12; - -import 'package:mockito/mockito.dart' as _i1; -import 'package:playground/modules/examples/models/category_model.dart' as _i10; -import 'package:playground/modules/examples/models/example_loading_descriptors/example_loading_descriptor.dart' - as _i7; -import 'package:playground/modules/examples/models/example_loading_descriptors/examples_loading_descriptor.dart' - as _i6; -import 'package:playground/modules/examples/models/example_model.dart' as _i2; -import 'package:playground/modules/examples/repositories/models/shared_file_model.dart' - as _i11; -import 'package:playground/modules/sdk/models/sdk.dart' as _i9; -import 'package:playground/pages/playground/states/example_loaders/examples_loader.dart' - as _i3; -import 'package:playground/pages/playground/states/examples_state.dart' as _i8; -import 'package:playground/pages/playground/states/playground_state.dart' - as _i4; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types - -class _FakeExampleModel_0 extends _i1.Fake implements _i2.ExampleModel {} - -/// A class which mocks [ExamplesLoader]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockExamplesLoader extends _i1.Mock implements _i3.ExamplesLoader { - MockExamplesLoader() { - _i1.throwOnMissingStub(this); - } - - @override - void setPlaygroundState(_i4.PlaygroundState? value) => - super.noSuchMethod(Invocation.method(#setPlaygroundState, [value]), - returnValueForMissingStub: null); - @override - _i5.Future load(_i6.ExamplesLoadingDescriptor? descriptor) => - (super.noSuchMethod(Invocation.method(#load, [descriptor]), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future loadOne( - {_i6.ExamplesLoadingDescriptor? group, - _i7.ExampleLoadingDescriptor? one}) => - (super.noSuchMethod( - Invocation.method(#loadOne, [], {#group: group, #one: one}), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); -} - -/// A class which mocks [ExampleState]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockExampleState extends _i1.Mock implements _i8.ExampleState { - MockExampleState() { - _i1.throwOnMissingStub(this); - } - - @override - set sdkCategories(Map<_i9.SDK, List<_i10.CategoryModel>>? _sdkCategories) => - super.noSuchMethod(Invocation.setter(#sdkCategories, _sdkCategories), - returnValueForMissingStub: null); - @override - Map<_i9.SDK, _i2.ExampleModel> get defaultExamplesMap => - (super.noSuchMethod(Invocation.getter(#defaultExamplesMap), - returnValue: <_i9.SDK, _i2.ExampleModel>{}) - as Map<_i9.SDK, _i2.ExampleModel>); - @override - set defaultExamplesMap(Map<_i9.SDK, _i2.ExampleModel>? _defaultExamplesMap) => - super.noSuchMethod( - Invocation.setter(#defaultExamplesMap, _defaultExamplesMap), - returnValueForMissingStub: null); - @override - set defaultExample(_i2.ExampleModel? _defaultExample) => - super.noSuchMethod(Invocation.setter(#defaultExample, _defaultExample), - returnValueForMissingStub: null); - @override - bool get isSelectorOpened => - (super.noSuchMethod(Invocation.getter(#isSelectorOpened), - returnValue: false) as bool); - @override - set isSelectorOpened(bool? _isSelectorOpened) => super.noSuchMethod( - Invocation.setter(#isSelectorOpened, _isSelectorOpened), - returnValueForMissingStub: null); - @override - _i5.Future get allExamplesFuture => - (super.noSuchMethod(Invocation.getter(#allExamplesFuture), - returnValue: Future.value()) as _i5.Future); - @override - bool get hasExampleCatalog => - (super.noSuchMethod(Invocation.getter(#hasExampleCatalog), - returnValue: false) as bool); - @override - bool get hasListeners => - (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) - as bool); - @override - _i5.Future init() => (super.noSuchMethod(Invocation.method(#init, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - void setSdkCategories(Map<_i9.SDK, List<_i10.CategoryModel>>? map) => - super.noSuchMethod(Invocation.method(#setSdkCategories, [map]), - returnValueForMissingStub: null); - @override - List<_i10.CategoryModel> getCategories(_i9.SDK? sdk) => - (super.noSuchMethod(Invocation.method(#getCategories, [sdk]), - returnValue: <_i10.CategoryModel>[]) as List<_i10.CategoryModel>); - @override - _i5.Future getExampleOutput(String? id, _i9.SDK? sdk) => - (super.noSuchMethod(Invocation.method(#getExampleOutput, [id, sdk]), - returnValue: Future.value('')) as _i5.Future); - @override - _i5.Future getExampleSource(String? id, _i9.SDK? sdk) => - (super.noSuchMethod(Invocation.method(#getExampleSource, [id, sdk]), - returnValue: Future.value('')) as _i5.Future); - @override - _i5.Future<_i2.ExampleModel> getExample(String? path, _i9.SDK? sdk) => - (super.noSuchMethod(Invocation.method(#getExample, [path, sdk]), - returnValue: - Future<_i2.ExampleModel>.value(_FakeExampleModel_0())) - as _i5.Future<_i2.ExampleModel>); - @override - _i5.Future getExampleLogs(String? id, _i9.SDK? sdk) => - (super.noSuchMethod(Invocation.method(#getExampleLogs, [id, sdk]), - returnValue: Future.value('')) as _i5.Future); - @override - _i5.Future getExampleGraph(String? id, _i9.SDK? sdk) => - (super.noSuchMethod(Invocation.method(#getExampleGraph, [id, sdk]), - returnValue: Future.value('')) as _i5.Future); - @override - _i5.Future<_i2.ExampleModel> loadSharedExample(String? id) => - (super.noSuchMethod(Invocation.method(#loadSharedExample, [id]), - returnValue: - Future<_i2.ExampleModel>.value(_FakeExampleModel_0())) - as _i5.Future<_i2.ExampleModel>); - @override - _i5.Future getSnippetId( - {List<_i11.SharedFile>? files, - _i9.SDK? sdk, - String? pipelineOptions}) => - (super.noSuchMethod( - Invocation.method(#getSnippetId, [], - {#files: files, #sdk: sdk, #pipelineOptions: pipelineOptions}), - returnValue: Future.value('')) as _i5.Future); - @override - _i5.Future<_i2.ExampleModel> loadExampleInfo(_i2.ExampleModel? example) => - (super.noSuchMethod(Invocation.method(#loadExampleInfo, [example]), - returnValue: - Future<_i2.ExampleModel>.value(_FakeExampleModel_0())) - as _i5.Future<_i2.ExampleModel>); - @override - void changeSelectorVisibility() => - super.noSuchMethod(Invocation.method(#changeSelectorVisibility, []), - returnValueForMissingStub: null); - @override - _i5.Future loadDefaultExamples() => - (super.noSuchMethod(Invocation.method(#loadDefaultExamples, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future loadDefaultExamplesIfNot() => - (super.noSuchMethod(Invocation.method(#loadDefaultExamplesIfNot, []), - returnValue: Future.value(), - returnValueForMissingStub: Future.value()) as _i5.Future); - @override - _i5.Future<_i2.ExampleModel?> getCatalogExampleByPath(String? path) => - (super.noSuchMethod(Invocation.method(#getCatalogExampleByPath, [path]), - returnValue: Future<_i2.ExampleModel?>.value()) - as _i5.Future<_i2.ExampleModel?>); - @override - void addListener(_i12.VoidCallback? listener) => - super.noSuchMethod(Invocation.method(#addListener, [listener]), - returnValueForMissingStub: null); - @override - void removeListener(_i12.VoidCallback? listener) => - super.noSuchMethod(Invocation.method(#removeListener, [listener]), - returnValueForMissingStub: null); - @override - void dispose() => super.noSuchMethod(Invocation.method(#dispose, []), - returnValueForMissingStub: null); - @override - void notifyListeners() => - super.noSuchMethod(Invocation.method(#notifyListeners, []), - returnValueForMissingStub: null); -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 71fe8bd8107fc..d17d4caead0ad 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -60,6 +60,7 @@ include(":model:pipeline") include(":playground") include(":playground:backend") include(":playground:frontend") +include(":playground:frontend:playground_components") include(":playground:backend:containers") include(":playground:backend:containers:java") include(":playground:backend:containers:go")