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 <[email protected]>
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