This is an automated email from the ASF dual-hosted git repository. pabloem pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/beam.git
The following commit(s) were added to refs/heads/master by this push: new a4fcd93 Merge pull request #16505 from [BEAM-13527] [Playground] Pipeline options dialog a4fcd93 is described below commit a4fcd939dcfa35ad2517e5c7f05eb5bb1333f66f Author: Aydar Farrakhov <stranni...@gmail.com> AuthorDate: Fri Jan 28 21:34:33 2022 +0300 Merge pull request #16505 from [BEAM-13527] [Playground] Pipeline options dialog * [BEAM-13527] pipeline options dropdown * [BEAM-13527] playground - parse pipeline error message * [BEAM-13527] playground - fix parse options * [BEAM-13527] playground - move pipelines options lines count to const * [BEAM-13527] playground fix tests * fix merge * [BEAM-13527] pipeline options fix review comments * [BEAM-13527] pipeline options fix review comments --- playground/frontend/lib/config/theme.dart | 16 ++ playground/frontend/lib/constants/sizes.dart | 3 +- playground/frontend/lib/l10n/app_en.arb | 36 ++++ .../pipeline_option_label.dart | 35 ++++ .../pipeline_option_model.dart | 29 +++ .../pipeline_options_dropdown.dart | 51 +++++ .../pipeline_options_dropdown_body.dart | 229 +++++++++++++++++++++ .../pipeline_options_dropdown_input.dart | 48 +++++ .../pipeline_options_dropdown_separator.dart | 35 ++++ .../pipeline_options_form.dart | 86 ++++++++ .../pipeline_options_text_field.dart | 66 ++++++ .../components/pipeline_options_text_field.dart | 81 -------- .../lib/modules/editor/components/run_button.dart | 2 +- .../components/editor_textarea_wrapper.dart | 7 +- .../lib/pages/playground/playground_page.dart | 7 +- .../pages/playground/states/playground_state.dart | 1 + playground/frontend/pubspec.lock | 2 +- playground/frontend/pubspec.yaml | 1 + 18 files changed, 644 insertions(+), 91 deletions(-) diff --git a/playground/frontend/lib/config/theme.dart b/playground/frontend/lib/config/theme.dart index fed037a..d9fd937 100644 --- a/playground/frontend/lib/config/theme.dart +++ b/playground/frontend/lib/config/theme.dart @@ -70,6 +70,17 @@ TextButtonThemeData createTextButtonTheme(Color textColor) { ); } +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), @@ -95,9 +106,12 @@ AppBarTheme createAppBarTheme(Color backgroundColor) { } 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), ), @@ -122,6 +136,7 @@ final kLightTheme = ThemeData( textTheme: createTextTheme(kLightText), popupMenuTheme: createPopupMenuTheme(), textButtonTheme: createTextButtonTheme(kLightText), + outlinedButtonTheme: createOutlineButtonTheme(kLightText), elevatedButtonTheme: createElevatedButtonTheme(kLightPrimary), tabBarTheme: createTabBarTheme(kLightText, kLightPrimary), dialogTheme: createDialogTheme(kLightText), @@ -135,6 +150,7 @@ final kDarkTheme = ThemeData( textTheme: createTextTheme(kDarkText), popupMenuTheme: createPopupMenuTheme(), textButtonTheme: createTextButtonTheme(kDarkText), + outlinedButtonTheme: createOutlineButtonTheme(kDarkText), elevatedButtonTheme: createElevatedButtonTheme(kDarkPrimary), tabBarTheme: createTabBarTheme(kDarkText, kDarkPrimary), dialogTheme: createDialogTheme(kDarkText), diff --git a/playground/frontend/lib/constants/sizes.dart b/playground/frontend/lib/constants/sizes.dart index 24728b0..3962a19 100644 --- a/playground/frontend/lib/constants/sizes.dart +++ b/playground/frontend/lib/constants/sizes.dart @@ -27,7 +27,7 @@ const double kXxlSpacing = 36.0; // sizes const kHeaderButtonHeight = 46.0; const kRunButtonWidth = 150.0; -const kRunButtonHeight = 40.0; +const kButtonHeight = 40.0; const kIconButtonSplashRadius = 24.0; const kFooterHeight = 32.0; @@ -59,3 +59,4 @@ const double kTitleFontSize = 18.0; //divider size const double kDividerHeight = 1.0; +const double kLgDividerHeight = 2.0; diff --git a/playground/frontend/lib/l10n/app_en.arb b/playground/frontend/lib/l10n/app_en.arb index fdb6553..df18234 100644 --- a/playground/frontend/lib/l10n/app_en.arb +++ b/playground/frontend/lib/l10n/app_en.arb @@ -154,5 +154,41 @@ "clearOutput": "Clear Output", "@clearOutput": { "description": "Title for the Clear Output shortcut row" + }, + "pipelineOptions": "Pipeline Options", + "@pipelineOptions": { + "description": "Title for the Pipeline Options" + }, + "rawPipelineOptions": "Raw", + "@rawPipelineOptions": { + "description": "Title for the Raw Pipeline Options Tab" + }, + "optionsPipelineOptions": "Options", + "@optionsPipelineOptions": { + "description": "Title for the Options Pipeline Options Tab" + }, + "saveAndClose": "Save & close", + "@saveAndClose": { + "description": "Text for save and close button on pipeline dropdown" + }, + "addPipelineOptionParameter": "Add parameter", + "@addPipelineOptionParameter": { + "description": "Text for add parameter button on pipeline dropdown" + }, + "input": "Input", + "@input": { + "description": "Text input label" + }, + "name": "Name", + "@name": { + "description": "Text name label" + }, + "value": "Value", + "@value": { + "description": "Text value label" + }, + "pipelineOptionsError": "Please check the format (example: --key1 value1 --key2 value2), only alphanumeric and \",*,/,-,:,;,',. symbols are allowed", + "@value": { + "description": "Pipeline options parse error" } } \ No newline at end of file diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_option_label.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_option_label.dart new file mode 100644 index 0000000..733c433 --- /dev/null +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_option_label.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/material.dart'; +import 'package:playground/constants/font_weight.dart'; +import 'package:playground/constants/sizes.dart'; + +class PipelineOptionLabel extends StatelessWidget { + final String text; + + const PipelineOptionLabel({Key? key, required this.text}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Text( + text, + style: const TextStyle(fontWeight: kMediumWeight, fontSize: kLabelFontSize), + ); + } +} diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_option_model.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_option_model.dart new file mode 100644 index 0000000..8c8a059 --- /dev/null +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_option_model.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/cupertino.dart'; + +class PipelineOptionController { + final TextEditingController name = TextEditingController(); + final TextEditingController value = TextEditingController(); + + PipelineOptionController({String name = '', String value = ''}) { + this.name.text = name; + this.value.text = value; + } +} diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown.dart new file mode 100644 index 0000000..acca8708 --- /dev/null +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown.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 '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/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart'; + +const kDropdownWidth = 400.0; +const kDropdownHeight = 375.0; + +class PipelineOptionsDropdown extends StatelessWidget { + final String pipelineOptions; + final Function(String) setPipelineOptions; + + const PipelineOptionsDropdown({ + Key? key, + required this.pipelineOptions, + required this.setPipelineOptions, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + AppLocalizations appLocale = AppLocalizations.of(context)!; + return AppDropdownButton( + buttonText: Text(appLocale.pipelineOptions), + height: kDropdownHeight, + width: kDropdownWidth, + createDropdown: (close) => PipelineOptionsDropdownBody( + pipelineOptions: pipelineOptions, + setPipelineOptions: setPipelineOptions, + close: close, + ), + ); + } +} 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 new file mode 100644 index 0000000..f2ae442 --- /dev/null +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_body.dart @@ -0,0 +1,229 @@ +/* + * 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/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_model.dart'; +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/modules/notifications/components/notification.dart'; + +const kOptionsTabIndex = 0; +const kRawTabIndex = 1; + +final kDefaultOption = [PipelineOptionController()]; + +class PipelineOptionsDropdownBody extends StatefulWidget { + final String pipelineOptions; + final Function(String) setPipelineOptions; + final Function close; + + const PipelineOptionsDropdownBody({ + Key? key, + required this.pipelineOptions, + required this.setPipelineOptions, + required this.close, + }) : super(key: key); + + @override + State<PipelineOptionsDropdownBody> createState() => + _PipelineOptionsDropdownBodyState(); +} + +class _PipelineOptionsDropdownBodyState + extends State<PipelineOptionsDropdownBody> + with SingleTickerProviderStateMixin { + late final TabController tabController; + final TextEditingController pipelineOptionsController = + TextEditingController(); + List<PipelineOptionController> pipelineOptionsList = kDefaultOption; + int selectedTab = kOptionsTabIndex; + bool showError = false; + + @override + void initState() { + tabController = TabController(vsync: this, length: 2); + tabController.addListener(onTabChange); + pipelineOptionsController.text = widget.pipelineOptions; + pipelineOptionsList = _pipelineOptionsMapToList(widget.pipelineOptions); + if (pipelineOptionsList.isEmpty) { + pipelineOptionsList = kDefaultOption; + } + super.initState(); + } + + @override + void dispose() { + tabController.removeListener(onTabChange); + tabController.dispose(); + super.dispose(); + } + + onTabChange() { + setState(() { + selectedTab = tabController.index; + }); + if (tabController.index == kRawTabIndex) { + _updateRawValue(); + } else { + _updateFormValue(); + } + } + + onDelete(int index) { + setState(() { + pipelineOptionsList.removeAt(index); + }); + } + + @override + Widget build(BuildContext context) { + AppLocalizations appLocale = AppLocalizations.of(context)!; + return Column( + children: [ + TabBar( + controller: tabController, + tabs: <Widget>[ + Tab(text: appLocale.optionsPipelineOptions), + Tab(text: appLocale.rawPipelineOptions), + ], + ), + const PipelineOptionsDropdownSeparator(), + Expanded( + child: Padding( + padding: const EdgeInsets.all(kXlSpacing), + child: TabBarView( + controller: tabController, + physics: const NeverScrollableScrollPhysics(), + children: <Widget>[ + PipelineOptionsForm( + options: pipelineOptionsList, + onDelete: onDelete, + ), + PipelineOptionsDropdownInput( + controller: pipelineOptionsController, + ), + ], + ), + ), + ), + const PipelineOptionsDropdownSeparator(), + Padding( + padding: const EdgeInsets.all(kXlSpacing), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: kButtonHeight, + child: ElevatedButton( + child: Text(appLocale.saveAndClose), + onPressed: () => _save(context), + ), + ), + const SizedBox(width: kLgSpacing), + if (selectedTab == kOptionsTabIndex) + SizedBox( + height: kButtonHeight, + child: OutlinedButton( + child: Text(appLocale.addPipelineOptionParameter), + onPressed: () => setState(() { + pipelineOptionsList.add(PipelineOptionController()); + }), + ), + ), + if (showError && selectedTab == kRawTabIndex) + Flexible( + child: Text( + appLocale.pipelineOptionsError, + style: Theme.of(context) + .textTheme + .caption + !.copyWith(color: kErrorNotificationColor), + softWrap: true, + ), + ), + ], + ), + ) + ], + ); + } + + Map<String, String> get pipelineOptionsListValue { + final notEmptyOptions = pipelineOptionsList + .where((option) => + option.name.text.isNotEmpty && option.value.text.isNotEmpty) + .toList(); + return {for (var item in notEmptyOptions) item.name.text: item.value.text}; + } + + String get pipelineOptionsValue { + if (selectedTab == kRawTabIndex) { + return pipelineOptionsController.text; + } + return pipelineOptionsToString(pipelineOptionsListValue); + } + + _save(BuildContext context) { + if (selectedTab == kRawTabIndex && !_isPipelineOptionsTextValid()) { + setState(() { + showError = true; + }); + return; + } + widget.setPipelineOptions(pipelineOptionsValue); + widget.close(); + } + + bool _isPipelineOptionsTextValid() { + final options = pipelineOptionsController.text; + final parsedOptions = parsePipelineOptions(options); + return options.isEmpty || (parsedOptions != null); + } + + _updateRawValue() { + if (pipelineOptionsListValue.isNotEmpty) { + pipelineOptionsController.text = + pipelineOptionsToString(pipelineOptionsListValue); + } + } + + _updateFormValue() { + final parsedOptions = + _pipelineOptionsMapToList(pipelineOptionsController.text); + if (parsedOptions.isNotEmpty) { + setState(() { + pipelineOptionsList = parsedOptions; + }); + } + } + + List<PipelineOptionController> _pipelineOptionsMapToList( + String pipelineOptions) { + return parsePipelineOptions(pipelineOptions) + ?.entries + .map((e) => PipelineOptionController(name: e.key, value: e.value)) + .toList() ?? + []; + } +} diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart new file mode 100644 index 0000000..c9cd125 --- /dev/null +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_input.dart @@ -0,0 +1,48 @@ +/* + * 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/modules/editor/components/pipeline_options_dropdown/pipeline_option_label.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart'; + +const kPipelineOptionsInputLines = 8; + +class PipelineOptionsDropdownInput extends StatelessWidget { + final TextEditingController controller; + + const PipelineOptionsDropdownInput({ + Key? key, + required this.controller, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + AppLocalizations appLocale = AppLocalizations.of(context)!; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + PipelineOptionLabel(text: appLocale.input), + PipelineOptionsTextField( + lines: kPipelineOptionsInputLines, + controller: controller, + ), + ], + ); + } +} 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 new file mode 100644 index 0000000..17070dd --- /dev/null +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown_separator.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/material.dart'; +import 'package:playground/config/theme.dart'; +import 'package:playground/constants/sizes.dart'; + +class PipelineOptionsDropdownSeparator extends StatelessWidget { + const PipelineOptionsDropdownSeparator({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: kDividerHeight, + decoration: BoxDecoration( + color: ThemeColors.of(context).lightGreyColor, + ), + ); + } +} 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 new file mode 100644 index 0000000..1d2179f --- /dev/null +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_form.dart @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import 'package:flutter/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/sizes.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_option_label.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_option_model.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart'; + +const kSpace = SizedBox(width: kMdSpacing); +const kTextFieldHeight = 50.0; + +class PipelineOptionsForm extends StatelessWidget { + final List<PipelineOptionController> options; + final Function(int) onDelete; + + const PipelineOptionsForm({ + Key? key, + required this.options, + required this.onDelete, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + AppLocalizations appLocale = AppLocalizations.of(context)!; + return Column( + children: [ + Row( + children: [ + Expanded(child: PipelineOptionLabel(text: appLocale.name)), + kSpace, + Expanded(child: PipelineOptionLabel(text: appLocale.value)), + const SizedBox(width: kIconSizeLg), + ], + ), + ...options.mapIndexed( + (index, option) => Row( + children: [ + Expanded( + child: SizedBox( + height: kTextFieldHeight, + child: PipelineOptionsTextField(controller: option.name), + ), + ), + kSpace, + Expanded( + child: SizedBox( + height: kTextFieldHeight, + child: PipelineOptionsTextField(controller: option.value), + ), + ), + SizedBox( + width: kIconSizeLg, + child: IconButton( + iconSize: kIconSizeMd, + splashRadius: kIconButtonSplashRadius, + icon: const Icon(Icons.delete_outlined), + color: ThemeColors.of(context).grey1Color, + 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 new file mode 100644 index 0000000..2c11873 --- /dev/null +++ b/playground/frontend/lib/modules/editor/components/pipeline_options_dropdown/pipeline_options_text_field.dart @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import 'package:flutter/material.dart'; +import 'package:playground/config/theme.dart'; +import 'package:playground/constants/sizes.dart'; + +class PipelineOptionsTextField extends StatelessWidget { + final TextEditingController controller; + final int lines; + + const PipelineOptionsTextField({ + Key? key, + required this.controller, + this.lines = 1, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.only( + top: kMdSpacing, + ), + decoration: BoxDecoration( + color: Theme.of(context).backgroundColor, + borderRadius: BorderRadius.circular(kMdBorderRadius), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(kMdBorderRadius), + child: TextFormField( + minLines: lines, + maxLines: lines, + controller: controller, + decoration: InputDecoration( + contentPadding: const EdgeInsets.all(kMdSpacing), + border: _getInputBorder(ThemeColors.of(context).lightGreyColor), + focusedBorder: _getInputBorder(ThemeColors.of(context).primary), + ), + cursorColor: ThemeColors.of(context).textColor, + ), + ), + ); + } + + _getInputBorder(Color color) { + return OutlineInputBorder( + borderSide: BorderSide(color: color), + borderRadius: BorderRadius.circular(kMdBorderRadius), + ); + } +} diff --git a/playground/frontend/lib/modules/editor/components/pipeline_options_text_field.dart b/playground/frontend/lib/modules/editor/components/pipeline_options_text_field.dart deleted file mode 100644 index f727721..0000000 --- a/playground/frontend/lib/modules/editor/components/pipeline_options_text_field.dart +++ /dev/null @@ -1,81 +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/config/theme.dart'; -import 'package:playground/constants/sizes.dart'; - -class PipelineOptionsTextField extends StatefulWidget { - final String pipelineOptions; - final Function(String value) onChange; - - const PipelineOptionsTextField( - {Key? key, required this.pipelineOptions, required this.onChange}) - : super(key: key); - - @override - State<PipelineOptionsTextField> createState() => - _PipelineOptionsTextFieldState(); -} - -class _PipelineOptionsTextFieldState extends State<PipelineOptionsTextField> { - TextEditingController? _controller; - - @override - void didChangeDependencies() { - _controller = TextEditingController(text: widget.pipelineOptions); - _controller?.addListener(() => widget.onChange(_controller?.text ?? '')); - super.didChangeDependencies(); - } - - @override - void dispose() { - super.dispose(); - _controller?.dispose(); - } - - @override - Widget build(BuildContext context) { - AppLocalizations appLocale = AppLocalizations.of(context)!; - - return TextField( - decoration: InputDecoration( - // it should be prefix props, but text inside prefix disappears without focus - icon: Padding( - padding: - const EdgeInsets.fromLTRB(kLgSpacing, kLgSpacing, 0, kLgSpacing), - child: Text( - appLocale.pipelineOptions, - style: TextStyle( - fontSize: kLabelFontSize, - color: ThemeColors.of(context).textColor, - ), - ), - ), - border: InputBorder.none, - hintText: appLocale.pipelineOptionsPlaceholder, - hintStyle: TextStyle( - fontSize: kHintFontSize, - color: ThemeColors.of(context).grey1Color, - ), - ), - controller: _controller, - ); - } -} diff --git a/playground/frontend/lib/modules/editor/components/run_button.dart b/playground/frontend/lib/modules/editor/components/run_button.dart index f29b014..6aecc1f 100644 --- a/playground/frontend/lib/modules/editor/components/run_button.dart +++ b/playground/frontend/lib/modules/editor/components/run_button.dart @@ -44,7 +44,7 @@ class RunButton extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( width: kRunButtonWidth, - height: kRunButtonHeight, + height: kButtonHeight, child: ShortcutTooltip( shortcut: kRunShortcut, child: ElevatedButton.icon( 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 ca0d1e7..d854ad9 100644 --- a/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart +++ b/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart @@ -21,7 +21,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.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/pipeline_options_text_field.dart'; import 'package:playground/modules/editor/components/run_button.dart'; import 'package:playground/modules/examples/components/description_popover/description_popover_button.dart'; import 'package:playground/modules/examples/models/example_model.dart'; @@ -63,7 +62,7 @@ class CodeTextAreaWrapper extends StatelessWidget { Positioned( right: kXlSpacing, top: kXlSpacing, - height: kRunButtonHeight, + height: kButtonHeight, child: Row( children: [ if (state.selectedExample != null) @@ -104,10 +103,6 @@ class CodeTextAreaWrapper extends StatelessWidget { ], ), ), - PipelineOptionsTextField( - pipelineOptions: state.pipelineOptions, - onChange: state.setPipelineOptions, - ), ], ); }); diff --git a/playground/frontend/lib/pages/playground/playground_page.dart b/playground/frontend/lib/pages/playground/playground_page.dart index 09b4522..8af0437 100644 --- a/playground/frontend/lib/pages/playground/playground_page.dart +++ b/playground/frontend/lib/pages/playground/playground_page.dart @@ -23,6 +23,7 @@ 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'; import 'package:playground/modules/analytics/analytics_service.dart'; +import 'package:playground/modules/editor/components/pipeline_options_dropdown/pipeline_options_dropdown.dart'; import 'package:playground/modules/examples/example_selector.dart'; import 'package:playground/modules/sdk/components/sdk_selector.dart'; import 'package:playground/modules/shortcuts/components/shortcuts_manager.dart'; @@ -72,6 +73,10 @@ class PlaygroundPage extends StatelessWidget { }, setExample: state.setExample, ), + PipelineOptionsDropdown( + pipelineOptions: state.pipelineOptions, + setPipelineOptions: state.setPipelineOptions, + ), const NewExampleAction(), ResetAction(reset: state.reset), ], @@ -87,7 +92,7 @@ class PlaygroundPage extends StatelessWidget { ], ), ), - ), + ) ); } } diff --git a/playground/frontend/lib/pages/playground/states/playground_state.dart b/playground/frontend/lib/pages/playground/states/playground_state.dart index e4df474..d9e612e 100644 --- a/playground/frontend/lib/pages/playground/states/playground_state.dart +++ b/playground/frontend/lib/pages/playground/states/playground_state.dart @@ -120,6 +120,7 @@ class PlaygroundState with ChangeNotifier { setPipelineOptions(String options) { _pipelineOptions = options; + notifyListeners(); } void runCode({Function? onFinish}) { diff --git a/playground/frontend/pubspec.lock b/playground/frontend/pubspec.lock index cb91023..dd9b4bd 100644 --- a/playground/frontend/pubspec.lock +++ b/playground/frontend/pubspec.lock @@ -156,7 +156,7 @@ packages: source: hosted version: "1.0.2" collection: - dependency: transitive + dependency: "direct dev" description: name: collection url: "https://pub.dartlang.org" diff --git a/playground/frontend/pubspec.yaml b/playground/frontend/pubspec.yaml index a518a2b..0d70124 100644 --- a/playground/frontend/pubspec.yaml +++ b/playground/frontend/pubspec.yaml @@ -66,6 +66,7 @@ dev_dependencies: build_runner: ^2.1.4 google_fonts: ^2.1.0 usage: ^4.0.2 + collection: ^1.15.0 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is