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 9ec68dd3df1 Multifile examples on frontend (#24859) (#24865)
9ec68dd3df1 is described below
commit 9ec68dd3df1fb8e602bafe055031b71a39ccc41d
Author: alexeyinkin <[email protected]>
AuthorDate: Wed Jan 11 23:17:48 2023 +0400
Multifile examples on frontend (#24859) (#24865)
* Multifile examples on frontend (#24859)
* Make a wrapper local (#24859)
* Revert sdk.g.dart deletion, minor fixes (#24859)
* Fix after review (#24859)
---
playground/frontend/lib/l10n/app_en.arb | 2 +-
.../example_list/example_item_actions.dart | 50 +++---
.../examples/components/multi_file_icon.dart} | 30 ++--
.../multifile_popover/multifile_popover.dart | 68 -------
.../multifile_popover_button.dart | 95 ----------
.../widgets/editor_textarea_wrapper.dart | 91 ++++------
.../widgets/playground_page_body.dart | 2 +-
.../lib/playground_components.dart | 1 +
.../lib/src/cache/example_cache.dart | 23 +--
.../lib/src/constants/sizes.dart | 1 +
.../example_loaders/content_example_loader.dart | 2 +-
.../example_loaders/http_example_loader.dart | 5 +-
.../lib/src/controllers/playground_controller.dart | 50 +++---
.../controllers/snippet_editing_controller.dart | 199 ++++++++++-----------
.../snippet_file_editing_controller.dart | 151 ++++++++++++++++
.../lib/src/models/example.dart | 11 +-
.../content_example_loading_descriptor.dart | 18 +-
.../lib/src/models/example_view_options.dart | 8 +-
.../shared_file.dart => models/snippet_file.dart} | 31 +++-
.../lib/src/models/snippet_file.g.dart | 20 +++
.../repositories/code_client/grpc_code_client.dart | 7 +-
.../example_client/grpc_example_client.dart | 34 ++--
.../lib/src/repositories/example_repository.dart | 5 +-
.../get_precompiled_object_code_response.dart | 6 +-
.../repositories/models/get_snippet_response.dart | 4 +-
.../src/repositories/models/run_code_request.dart | 9 +-
.../repositories/models/save_snippet_request.dart | 4 +-
...ponse.dart => snippet_file_grpc_extension.dart} | 29 +--
.../lib/src/widgets/complexity.dart | 5 +-
.../lib/src/widgets/run_or_cancel_button.dart | 1 -
.../lib/src/widgets/snippet_editor.dart | 153 ++++++----------
...nippet_editor.dart => snippet_file_editor.dart} | 37 ++--
.../lib/src/widgets/tab_header.dart | 4 +-
.../lib/src/widgets/tabbed_snippet_editor.dart | 81 +++++++++
.../widgets/{tab_header.dart => tabs/tab_bar.dart} | 27 ++-
.../frontend/playground_components/pubspec.yaml | 3 +-
.../test/src/cache/example_cache_test.dart | 41 +++--
.../test/src/common/categories.dart | 14 +-
.../test/src/common/descriptors.dart | 12 +-
.../test/src/common/example_cache.mocks.dart | 5 +-
.../test/src/common/example_repository_mock.dart | 17 +-
.../src/common/example_repository_mock.mocks.dart | 27 +--
.../test/src/common/examples.dart | 42 +++--
.../test/src/common/requests.dart | 5 +-
.../src/controllers/example_loaders/common.dart | 2 +-
.../examples_loader_test.mocks.dart | 77 +++-----
.../example_loaders/http_example_loader_test.dart | 2 +-
.../http_example_loader_test.mocks.dart | 5 +-
.../controllers/playground_controller_test.dart | 41 +++--
.../playground_controller_test.mocks.dart | 5 +-
.../snippet_editing_controller_test.dart | 93 +++++++---
.../content_example_loading_descriptor_test.dart | 9 +-
.../src/repositories/code_repository_test.dart | 3 +-
.../src/repositories/example_repository_test.dart | 25 ++-
.../playground_components_dev/pubspec.yaml | 2 +-
playground/frontend/pubspec.lock | 9 +-
playground/frontend/pubspec.yaml | 2 +-
.../messages/models/set_content_message_test.dart | 35 ++--
58 files changed, 918 insertions(+), 822 deletions(-)
diff --git a/playground/frontend/lib/l10n/app_en.arb
b/playground/frontend/lib/l10n/app_en.arb
index 538df437994..a49ba9ebc2c 100644
--- a/playground/frontend/lib/l10n/app_en.arb
+++ b/playground/frontend/lib/l10n/app_en.arb
@@ -195,7 +195,7 @@
"@exampleDescription": {
"description": "Description icon label"
},
- "exampleMultifile": "Multifile example info",
+ "exampleMultifile": "Multifile",
"@exampleDescription": {
"exampleMultifile": "Multifile icon label"
},
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 df0c8a83750..03f9d2732d4 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
@@ -25,7 +25,7 @@ import 'package:provider/provider.dart';
import '../../../../src/assets/assets.gen.dart';
import '../../models/popover_state.dart';
import '../description_popover/description_popover_button.dart';
-import '../multifile_popover/multifile_popover_button.dart';
+import '../multi_file_icon.dart';
class ExampleItemActions extends StatelessWidget {
final ExampleBase example;
@@ -39,24 +39,15 @@ class ExampleItemActions extends StatelessWidget {
Widget build(BuildContext context) {
return Row(
children: [
- if (example.isMultiFile) multifilePopover,
- if (example.usesEmulatedData) const _EmulatedDataIcon(),
+ if (example.isMultiFile) const _IconWrapper(MultiFileIcon()),
+ if (example.usesEmulatedData) const _IconWrapper(_EmulatedDataIcon()),
if (example.complexity != null)
- ComplexityWidget(complexity: example.complexity!),
+ _IconWrapper(ComplexityWidget(complexity: example.complexity!)),
descriptionPopover,
],
);
}
- Widget get multifilePopover => MultifilePopoverButton(
- parentContext: parentContext,
- example: example,
- followerAnchor: Alignment.topLeft,
- targetAnchor: Alignment.topRight,
- onOpen: () => _setPopoverOpen(parentContext, true),
- onClose: () => _setPopoverOpen(parentContext, false),
- );
-
Widget get descriptionPopover => DescriptionPopoverButton(
parentContext: parentContext,
example: example,
@@ -76,14 +67,31 @@ class _EmulatedDataIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Padding(
- padding: const EdgeInsets.only(right: 8.0),
- child: Tooltip(
- message: 'intents.playground.usesEmulatedData'.tr(),
- child: SvgPicture.asset(
- Assets.streaming,
- color: Theme.of(context).extension<BeamThemeExtension>()?.iconColor,
- ),
+ return Tooltip(
+ message: 'intents.playground.usesEmulatedData'.tr(),
+ child: SvgPicture.asset(
+ Assets.streaming,
+ color: Theme.of(context).extension<BeamThemeExtension>()?.iconColor,
+ ),
+ );
+ }
+}
+
+/// A wrapper of a standard size for icons in the example list.
+class _IconWrapper extends StatelessWidget {
+ const _IconWrapper(this.child);
+
+ final Widget child;
+
+ static const double _iconSize = 30;
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(
+ height: _iconSize,
+ width: _iconSize,
+ child: Center(
+ child: child,
),
);
}
diff --git
a/playground/frontend/playground_components/lib/src/widgets/tab_header.dart
b/playground/frontend/lib/modules/examples/components/multi_file_icon.dart
similarity index 66%
copy from
playground/frontend/playground_components/lib/src/widgets/tab_header.dart
copy to playground/frontend/lib/modules/examples/components/multi_file_icon.dart
index 714f025814e..e24ffa5f39c 100644
--- a/playground/frontend/playground_components/lib/src/widgets/tab_header.dart
+++ b/playground/frontend/lib/modules/examples/components/multi_file_icon.dart
@@ -17,30 +17,22 @@
*/
import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:flutter_svg/flutter_svg.dart';
-import '../constants/sizes.dart';
+import '../../../src/assets/assets.gen.dart';
-const kHeaderHeight = 50.0;
-
-class TabHeader extends StatelessWidget {
- final TabController tabController;
- final Widget tabsWidget;
-
- const TabHeader({
- super.key,
- required this.tabController,
- required this.tabsWidget,
- });
+class MultiFileIcon extends StatelessWidget {
+ const MultiFileIcon();
@override
Widget build(BuildContext context) {
- return SizedBox(
- height: 50,
- child: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: BeamSizes.size16,
- ),
- child: tabsWidget,
+ AppLocalizations appLocale = AppLocalizations.of(context)!;
+ return Semantics(
+ container: true,
+ child: Tooltip(
+ message: appLocale.exampleMultifile,
+ child: SvgPicture.asset(Assets.multifile),
),
);
}
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
deleted file mode 100644
index fb1c76fc0c7..00000000000
---
a/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover.dart
+++ /dev/null
@@ -1,68 +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_components/playground_components.dart';
-import 'package:url_launcher/url_launcher.dart';
-
-import '../../../../constants/font_weight.dart';
-import '../../../../constants/sizes.dart';
-import '../../../../src/assets/assets.gen.dart';
-
-const kMultifileWidth = 300.0;
-
-class MultifilePopover extends StatelessWidget {
- final ExampleBase example;
-
- const MultifilePopover({Key? key, required this.example}) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- AppLocalizations appLocale = AppLocalizations.of(context)!;
- return SizedBox(
- width: kMultifileWidth,
- child: Card(
- child: Padding(
- padding: const EdgeInsets.all(kLgSpacing),
- child: Wrap(
- runSpacing: kMdSpacing,
- children: [
- Text(
- appLocale.multifile,
- style: const TextStyle(
- fontSize: kTitleFontSize,
- fontWeight: kBoldWeight,
- ),
- ),
- Text(appLocale.multifileWarning),
- TextButton.icon(
- icon: SvgPicture.asset(Assets.github),
- onPressed: () {
- launchUrl(Uri.parse(example.link ?? ''));
- },
- label: Text(appLocale.viewOnGithub),
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
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
deleted file mode 100644
index 78fbafdf51a..00000000000
---
a/playground/frontend/lib/modules/examples/components/multifile_popover/multifile_popover_button.dart
+++ /dev/null
@@ -1,95 +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:aligned_dialog/aligned_dialog.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-import 'package:flutter_svg/flutter_svg.dart';
-import 'package:playground_components/playground_components.dart';
-
-import '../../../../constants/sizes.dart';
-import '../../../../src/assets/assets.gen.dart';
-import 'multifile_popover.dart';
-
-class MultifilePopoverButton extends StatelessWidget {
- final BuildContext? parentContext;
- final ExampleBase example;
- final Alignment followerAnchor;
- final Alignment targetAnchor;
- final void Function()? onOpen;
- final void Function()? onClose;
-
- const MultifilePopoverButton({
- Key? key,
- this.parentContext,
- required this.example,
- required this.followerAnchor,
- required this.targetAnchor,
- this.onOpen,
- this.onClose,
- }) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- AppLocalizations appLocale = AppLocalizations.of(context)!;
- return Semantics(
- container: true,
- child: IconButton(
- iconSize: kIconSizeMd,
- splashRadius: kIconButtonSplashRadius,
- icon: SvgPicture.asset(Assets.multifile),
- tooltip: appLocale.exampleMultifile,
- onPressed: () {
- _showMultifilePopover(
- parentContext ?? context,
- example,
- followerAnchor,
- targetAnchor,
- );
- },
- ),
- );
- }
-
- void _showMultifilePopover(
- BuildContext context,
- ExampleBase example,
- Alignment followerAnchor,
- Alignment targetAnchor,
- ) async {
- // close previous dialogs
- Navigator.of(context, rootNavigator: true).popUntil((route) {
- return route.isFirst;
- });
- if (onOpen != null) {
- onOpen!();
- }
- await showAlignedDialog(
- context: context,
- builder: (dialogContext) => MultifilePopover(
- example: example,
- ),
- followerAnchor: followerAnchor,
- targetAnchor: targetAnchor,
- barrierColor: Colors.transparent,
- );
- if (onClose != null) {
- onClose!();
- }
- }
-}
diff --git
a/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart
b/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart
index 5ee81cb05cf..86b6a5045d6 100644
---
a/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart
+++
b/playground/frontend/lib/pages/standalone_playground/widgets/editor_textarea_wrapper.dart
@@ -24,84 +24,59 @@ import
'../../../components/playground_run_or_cancel_button.dart';
import '../../../constants/sizes.dart';
import '../../../modules/editor/components/share_dropdown/share_button.dart';
import
'../../../modules/examples/components/description_popover/description_popover_button.dart';
-import
'../../../modules/examples/components/multifile_popover/multifile_popover_button.dart';
/// A code editor with controls stacked above it.
class CodeTextAreaWrapper extends StatelessWidget {
- final PlaygroundController controller;
+ final PlaygroundController playgroundController;
const CodeTextAreaWrapper({
- required this.controller,
+ required this.playgroundController,
});
@override
Widget build(BuildContext context) {
- if (controller.result?.errorMessage?.isNotEmpty ?? false) {
+ if (playgroundController.result?.errorMessage?.isNotEmpty ?? false) {
WidgetsBinding.instance.addPostFrameCallback((_) {
- _handleError(context, controller);
+ _handleError(context, playgroundController);
});
}
- final snippetController = controller.snippetEditingController;
+ final snippetController = playgroundController.snippetEditingController;
if (snippetController == null) {
return const LoadingIndicator();
}
- return Column(
- children: [
- Expanded(
- child: Stack(
- children: [
- Positioned.fill(
- child: SnippetEditor(
- controller: snippetController,
- isEditable: true,
- ),
- ),
- Positioned(
- right: kXlSpacing,
- top: kXlSpacing,
- height: kButtonHeight,
- child: Row(
- children: [
- if (controller.selectedExample != null) ...[
- if (controller.selectedExample?.isMultiFile ?? false)
- Semantics(
- container: true,
- child: MultifilePopoverButton(
- example: controller.selectedExample!,
- followerAnchor: Alignment.topRight,
- targetAnchor: Alignment.bottomRight,
- ),
- ),
- Semantics(
- container: true,
- child: DescriptionPopoverButton(
- example: controller.selectedExample!,
- followerAnchor: Alignment.topRight,
- targetAnchor: Alignment.bottomRight,
- ),
- ),
- ],
- Semantics(
- container: true,
- child: ShareButton(
- playgroundController: controller,
- ),
- ),
- const SizedBox(width: kLgSpacing),
- Semantics(
- container: true,
- child: const PlaygroundRunOrCancelButton(),
- ),
- ],
- ),
+ final example = snippetController.example;
+
+ return SnippetEditor(
+ controller: snippetController,
+ isEditable: true,
+ actionsWidget: Row(
+ children: [
+ if (example != null)
+ Semantics(
+ container: true,
+ child: DescriptionPopoverButton(
+ example: example,
+ followerAnchor: Alignment.topRight,
+ targetAnchor: Alignment.bottomRight,
),
- ],
+ ),
+ Semantics(
+ container: true,
+ child: ShareButton(
+ playgroundController: playgroundController,
+ ),
),
- ),
- ],
+ const SizedBox(width: kLgSpacing),
+ Semantics(
+ container: true,
+ child: const PlaygroundRunOrCancelButton(),
+ ),
+ const SizedBox(width: kLgSpacing),
+ ],
+ ),
);
}
diff --git
a/playground/frontend/lib/pages/standalone_playground/widgets/playground_page_body.dart
b/playground/frontend/lib/pages/standalone_playground/widgets/playground_page_body.dart
index 2ce17d79c35..a027f72e52e 100644
---
a/playground/frontend/lib/pages/standalone_playground/widgets/playground_page_body.dart
+++
b/playground/frontend/lib/pages/standalone_playground/widgets/playground_page_body.dart
@@ -45,7 +45,7 @@ class PlaygroundPageBody extends StatelessWidget {
);
final codeTextArea = CodeTextAreaWrapper(
- controller: controller,
+ playgroundController: controller,
);
switch (outputState.placement) {
diff --git
a/playground/frontend/playground_components/lib/playground_components.dart
b/playground/frontend/playground_components/lib/playground_components.dart
index 007a3ec29b6..8d37335e9cc 100644
--- a/playground/frontend/playground_components/lib/playground_components.dart
+++ b/playground/frontend/playground_components/lib/playground_components.dart
@@ -44,6 +44,7 @@ export 'src/models/loading_status.dart';
export 'src/models/outputs.dart';
export 'src/models/sdk.dart';
export 'src/models/shortcut.dart';
+export 'src/models/snippet_file.dart';
export 'src/models/toast.dart';
export 'src/models/toast_type.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
index d42753b4c27..ee59253a269 100644
--- a/playground/frontend/playground_components/lib/src/cache/example_cache.dart
+++ b/playground/frontend/playground_components/lib/src/cache/example_cache.dart
@@ -29,13 +29,13 @@ import '../models/example.dart';
import '../models/example_base.dart';
import '../models/loading_status.dart';
import '../models/sdk.dart';
+import '../models/snippet_file.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/get_snippet_request.dart';
import '../repositories/models/save_snippet_request.dart';
-import '../repositories/models/shared_file.dart';
/// A runtime cache for examples fetched from a repository.
class ExampleCache extends ChangeNotifier {
@@ -100,7 +100,7 @@ class ExampleCache extends ChangeNotifier {
);
}
- Future<String> _getPrecompiledObjectCode(String path, Sdk sdk) {
+ Future<List<SnippetFile>> _getPrecompiledObjectCode(String path, Sdk sdk) {
return _exampleRepository.getPrecompiledObjectCode(
GetPrecompiledObjectRequest(path: path, sdk: sdk),
);
@@ -125,17 +125,17 @@ class ExampleCache extends ChangeNotifier {
return Example(
complexity: result.complexity,
+ files: result.files,
name: result.files.first.name,
path: id,
sdk: result.sdk,
- source: result.files.first.code,
pipelineOptions: result.pipelineOptions,
type: ExampleType.example,
);
}
Future<String> saveSnippet({
- required List<SharedFile> files,
+ required List<SnippetFile> files,
required Sdk sdk,
required String pipelineOptions,
}) async {
@@ -170,12 +170,13 @@ class ExampleCache extends ChangeNotifier {
return Example.fromBase(
example,
- source: exampleData[0],
- outputs: exampleData[1],
- logs: exampleData[2],
+ files: exampleData[0] as List<SnippetFile>,
+ outputs: exampleData[1] as String,
+ logs: exampleData[2] as String,
);
}
+ // TODO(alexeyinkin): Load in a single request,
https://github.com/apache/beam/issues/24305
final exampleData = await Future.wait([
_getPrecompiledObjectCode(example.path, example.sdk),
_getPrecompiledObjectOutput(example.path, example.sdk),
@@ -185,10 +186,10 @@ class ExampleCache extends ChangeNotifier {
return Example.fromBase(
example,
- source: exampleData[0],
- outputs: exampleData[1],
- logs: exampleData[2],
- graph: exampleData[3],
+ files: exampleData[0] as List<SnippetFile>,
+ outputs: exampleData[1] as String,
+ logs: exampleData[2] as String,
+ graph: exampleData[3] as String,
);
}
diff --git
a/playground/frontend/playground_components/lib/src/constants/sizes.dart
b/playground/frontend/playground_components/lib/src/constants/sizes.dart
index b7ebd6eb38a..88e69669567 100644
--- a/playground/frontend/playground_components/lib/src/constants/sizes.dart
+++ b/playground/frontend/playground_components/lib/src/constants/sizes.dart
@@ -41,6 +41,7 @@ class BeamSizes {
static const double headerButtonHeight = 46;
static const double loadingIndicator = 40;
static const double splitViewSeparator = BeamSizes.size8;
+ static const double tabBarHeight = 50;
}
class BeamBorderRadius {
diff --git
a/playground/frontend/playground_components/lib/src/controllers/example_loaders/content_example_loader.dart
b/playground/frontend/playground_components/lib/src/controllers/example_loaders/content_example_loader.dart
index 53f0288d013..bc629e07001 100644
---
a/playground/frontend/playground_components/lib/src/controllers/example_loaders/content_example_loader.dart
+++
b/playground/frontend/playground_components/lib/src/controllers/example_loaders/content_example_loader.dart
@@ -42,10 +42,10 @@ class ContentExampleLoader extends ExampleLoader {
@override
Future<Example> get future async => Example(
complexity: descriptor.complexity,
+ files: descriptor.files,
name: descriptor.name ?? 'Untitled Example',
path: '',
sdk: descriptor.sdk,
- source: descriptor.content,
type: ExampleType.example,
);
}
diff --git
a/playground/frontend/playground_components/lib/src/controllers/example_loaders/http_example_loader.dart
b/playground/frontend/playground_components/lib/src/controllers/example_loaders/http_example_loader.dart
index 6f95c2e9b77..a3a1887f459 100644
---
a/playground/frontend/playground_components/lib/src/controllers/example_loaders/http_example_loader.dart
+++
b/playground/frontend/playground_components/lib/src/controllers/example_loaders/http_example_loader.dart
@@ -24,6 +24,7 @@ import '../../models/example.dart';
import '../../models/example_base.dart';
import
'../../models/example_loading_descriptors/http_example_loading_descriptor.dart';
import '../../models/sdk.dart';
+import '../../models/snippet_file.dart';
import 'example_loader.dart';
/// The [ExampleLoader] for [HttpExampleLoadingDescriptor].
@@ -48,9 +49,11 @@ class HttpExampleLoader extends ExampleLoader {
return Example(
name: descriptor.uri.path.split('/').lastOrNull ?? 'HTTP Example',
+ files: [
+ SnippetFile(content: response.body, isMain: true),
+ ],
path: descriptor.uri.toString(),
sdk: descriptor.sdk,
- source: response.body,
type: ExampleType.example,
viewOptions: descriptor.viewOptions,
);
diff --git
a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
index 463c7607dda..86aa44f86b9 100644
---
a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
+++
b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
@@ -19,7 +19,6 @@
import 'dart:async';
import 'dart:math';
-import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get_it/get_it.dart';
@@ -39,7 +38,6 @@ 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 '../services/symbols/loaders/map.dart';
import '../services/symbols/symbols_notifier.dart';
import '../util/pipeline_options.dart';
@@ -105,11 +103,11 @@ class PlaygroundController with ChangeNotifier {
// TODO(alexeyinkin): Return full, then shorten,
https://github.com/apache/beam/issues/23250
String get examplesTitle {
- final name = snippetEditingController?.selectedExample?.name ?? kTitle;
+ final name = snippetEditingController?.example?.name ?? kTitle;
return name.substring(0, min(kTitleLength, name.length));
}
- Example? get selectedExample => snippetEditingController?.selectedExample;
+ Example? get selectedExample => snippetEditingController?.example;
Sdk? get sdk => _sdk;
@@ -126,7 +124,8 @@ class PlaygroundController with ChangeNotifier {
return controller;
}
- String? get source => snippetEditingController?.codeController.fullText;
+ String? get source =>
+ snippetEditingController?.activeFileController?.codeController.fullText;
bool get isCodeRunning => !(result?.isFinished ?? true);
@@ -266,12 +265,6 @@ class PlaygroundController with ChangeNotifier {
GetIt.instance.get<SymbolsNotifier>().addLoaderIfNot(mode, loader);
}
- // TODO(alexeyinkin): Remove, used only in tests, refactor them.
- void setSource(String source) {
- final controller = requireSnippetEditingController();
- controller.setSource(source);
- }
-
void setSelectedOutputFilterType(OutputType type) {
selectedOutputFilterType = type;
notifyListeners();
@@ -322,14 +315,14 @@ class PlaygroundController with ChangeNotifier {
}
_executionTime?.close();
_executionTime = _createExecutionTimeStream();
- if (!isExampleChanged && controller.selectedExample?.outputs != null) {
+ if (!isExampleChanged && controller.example?.outputs != null) {
_showPrecompiledResult(controller);
} else {
final request = RunCodeRequest(
- code: controller.codeController.fullText,
- sdk: controller.sdk,
- pipelineOptions: parsedPipelineOptions,
datasets: selectedExample?.datasets ?? [],
+ files: controller.getFiles(),
+ pipelineOptions: parsedPipelineOptions,
+ sdk: controller.sdk,
);
_runSubscription = _codeRepository?.runCode(request).listen((event) {
_result = event;
@@ -373,7 +366,7 @@ class PlaygroundController with ChangeNotifier {
_result = const RunCodeResult(
status: RunCodeStatus.preparation,
);
- final selectedExample = snippetEditingController.selectedExample!;
+ final selectedExample = snippetEditingController.example!;
notifyListeners();
// add a little delay to improve user experience
@@ -439,25 +432,22 @@ class PlaygroundController with ChangeNotifier {
}
Future<UserSharedExampleLoadingDescriptor> saveSnippet() async {
- final controller = requireSnippetEditingController();
- final code = controller.codeController.fullText;
- final name = 'examples.userSharedName'.tr();
+ final snippetController = requireSnippetEditingController();
+ final files = snippetController.getFiles();
final snippetId = await exampleCache.saveSnippet(
- files: [
- SharedFile(code: code, isMain: true, name: name),
- ],
- sdk: controller.sdk,
- pipelineOptions: controller.pipelineOptions,
+ files: files,
+ pipelineOptions: snippetController.pipelineOptions,
+ sdk: snippetController.sdk,
);
final sharedExample = Example(
- datasets: controller.selectedExample?.datasets ?? [],
- source: code,
- name: name,
- sdk: controller.sdk,
- type: ExampleType.example,
+ datasets: snippetController.example?.datasets ?? [],
+ files: files,
+ name: files.first.name,
path: snippetId,
+ sdk: snippetController.sdk,
+ type: ExampleType.example,
);
final descriptor = UserSharedExampleLoadingDescriptor(
@@ -465,7 +455,7 @@ class PlaygroundController with ChangeNotifier {
snippetId: snippetId,
);
- controller.setExample(sharedExample, descriptor: descriptor);
+ snippetController.setExample(sharedExample, descriptor: descriptor);
return descriptor;
}
diff --git
a/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart
b/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart
index 052ee41aea1..c04d10cfb41 100644
---
a/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart
+++
b/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart
@@ -16,11 +16,8 @@
* limitations under the License.
*/
-import 'dart:math';
-
+import 'package:collection/collection.dart';
import 'package:flutter/widgets.dart';
-import 'package:flutter_code_editor/flutter_code_editor.dart';
-import 'package:get_it/get_it.dart';
import '../models/example.dart';
import
'../models/example_loading_descriptors/content_example_loading_descriptor.dart';
@@ -29,43 +26,27 @@ import
'../models/example_loading_descriptors/example_loading_descriptor.dart';
import '../models/example_view_options.dart';
import '../models/loading_status.dart';
import '../models/sdk.dart';
-import '../services/symbols/symbols_notifier.dart';
+import '../models/snippet_file.dart';
+import 'snippet_file_editing_controller.dart';
/// The main state object for a single [sdk].
class SnippetEditingController extends ChangeNotifier {
final Sdk sdk;
- final CodeController codeController;
- final _symbolsNotifier = GetIt.instance.get<SymbolsNotifier>();
- Example? _selectedExample;
+
ExampleLoadingDescriptor? _descriptor;
+ Example? _example;
String _pipelineOptions = '';
+
bool _isChanged = false;
LoadingStatus _exampleLoadingStatus = LoadingStatus.done;
+ SnippetFileEditingController? _activeFileController;
+ final _fileControllers = <SnippetFileEditingController>[];
+ final _fileControllersByName = <String, SnippetFileEditingController>{};
+
SnippetEditingController({
required this.sdk,
- }) : codeController = CodeController(
- language: sdk.highlightMode,
- namedSectionParser: const BracketsStartEndNamedSectionParser(),
- ) {
- codeController.addListener(_onCodeControllerChanged);
- _symbolsNotifier.addListener(_onSymbolsNotifierChanged);
- _onSymbolsNotifierChanged();
- }
-
- void _onCodeControllerChanged() {
- if (!_isChanged) {
- if (_isCodeChanged()) {
- _isChanged = true;
- notifyListeners();
- }
- } else {
- _updateIsChanged();
- if (!_isChanged) {
- notifyListeners();
- }
- }
- }
+ });
/// Attempts to acquire a lock for asynchronous example loading.
///
@@ -95,66 +76,18 @@ class SnippetEditingController extends ChangeNotifier {
ExampleLoadingDescriptor? descriptor,
}) {
_descriptor = descriptor;
- _selectedExample = example;
+ _example = example;
_pipelineOptions = example.pipelineOptions;
_isChanged = false;
releaseExampleLoading();
- final viewOptions = example.viewOptions;
-
- codeController.removeListener(_onCodeControllerChanged);
- setSource(example.source);
- _applyViewOptions(viewOptions);
- _toStartOfContextLineIfAny();
- codeController.addListener(_onCodeControllerChanged);
+ _deleteFileControllers();
+ _createFileControllers(example.files, example.viewOptions);
notifyListeners();
}
- void _applyViewOptions(ExampleViewOptions options) {
- codeController.readOnlySectionNames = options.readOnlySectionNames.toSet();
- codeController.visibleSectionNames = options.showSectionNames.toSet();
-
- if (options.foldCommentAtLineZero) {
- codeController.foldCommentAtLineZero();
- }
-
- if (options.foldImports) {
- codeController.foldImports();
- }
-
- final unfolded = options.unfoldSectionNames;
- if (unfolded.isNotEmpty) {
- codeController.foldOutsideSections(unfolded);
- }
- }
-
- void _toStartOfContextLineIfAny() {
- final contextLine1Based = selectedExample?.contextLine;
-
- if (contextLine1Based == null) {
- return;
- }
-
- _toStartOfFullLine(max(contextLine1Based - 1, 0));
- }
-
- void _toStartOfFullLine(int line) {
- if (line >= codeController.code.lines.length) {
- return;
- }
-
- final fullPosition = codeController.code.lines.lines[line].textRange.start;
- final visiblePosition = codeController.code.hiddenRanges.cutPosition(
- fullPosition,
- );
-
- codeController.selection = TextSelection.collapsed(
- offset: visiblePosition,
- );
- }
-
- Example? get selectedExample => _selectedExample;
+ Example? get example => _example;
ExampleLoadingDescriptor? get descriptor => _descriptor;
@@ -182,26 +115,33 @@ class SnippetEditingController extends ChangeNotifier {
bool get isChanged => _isChanged;
void _updateIsChanged() {
- _isChanged = _isCodeChanged() || _arePipelineOptionsChanged();
+ _isChanged = _calculateIsChanged();
}
- bool _isCodeChanged() {
- return _selectedExample?.source != codeController.fullText;
+ bool _calculateIsChanged() {
+ return _isAnyFileControllerChanged() || _arePipelineOptionsChanged();
+ }
+
+ bool _isAnyFileControllerChanged() {
+ return _fileControllers.any((c) => c.isChanged);
}
bool _arePipelineOptionsChanged() {
- return _pipelineOptions != (_selectedExample?.pipelineOptions ?? '');
+ return _pipelineOptions != (_example?.pipelineOptions ?? '');
}
void reset() {
- codeController.text = _selectedExample?.source ?? '';
- _pipelineOptions = _selectedExample?.pipelineOptions ?? '';
+ for (final controller in _fileControllers) {
+ controller.reset();
+ }
+
+ _pipelineOptions = _example?.pipelineOptions ?? '';
}
/// Creates an [ExampleLoadingDescriptor] that can recover the
/// current content.
ExampleLoadingDescriptor getLoadingDescriptor() {
- final example = selectedExample;
+ final example = this.example;
if (example == null) {
return EmptyExampleLoadingDescriptor(sdk: sdk);
}
@@ -212,39 +152,80 @@ class SnippetEditingController extends ChangeNotifier {
return ContentExampleLoadingDescriptor(
complexity: example.complexity,
- content: codeController.fullText,
+ files: getFiles(),
name: example.name,
sdk: sdk,
);
}
- void setSource(String source) {
- codeController.readOnlySectionNames = const {};
- codeController.visibleSectionNames = const {};
+ void _deleteFileControllers() {
+ for (final controller in _fileControllers) {
+ controller.removeListener(_onFileControllerChanged);
+ controller.dispose();
+ }
- codeController.fullText = source;
- codeController.historyController.deleteHistory();
+ _fileControllers.clear();
+ _fileControllersByName.clear();
}
- void _onSymbolsNotifierChanged() {
- final mode = sdk.highlightMode;
- if (mode == null) {
- return;
+ void _createFileControllers(
+ Iterable<SnippetFile> files,
+ ExampleViewOptions viewOptions,
+ ) {
+ for (final file in files) {
+ final controller = SnippetFileEditingController(
+ contextLine1Based: file.isMain ? _example?.contextLine : null,
+ savedFile: file,
+ sdk: sdk,
+ viewOptions: viewOptions,
+ );
+
+ _fileControllers.add(controller);
+ controller.addListener(_onFileControllerChanged);
}
- final dictionary = _symbolsNotifier.getDictionary(mode);
- if (dictionary == null) {
- return;
+ for (final controller in _fileControllers) {
+ _fileControllersByName[controller.savedFile.name] = controller;
}
- codeController.autocompleter.setCustomWords(dictionary.symbols);
+ _activeFileController =
+ _fileControllers.firstWhereOrNull((c) => c.savedFile.isMain);
}
- @override
- void dispose() {
- _symbolsNotifier.removeListener(
- _onSymbolsNotifierChanged,
- );
- super.dispose();
+ void _onFileControllerChanged() {
+ if (!_isChanged) {
+ if (_isAnyFileControllerChanged()) {
+ _isChanged = true;
+ notifyListeners();
+ }
+ } else {
+ _updateIsChanged();
+ if (!_isChanged) {
+ notifyListeners();
+ }
+ }
+ }
+
+ List<SnippetFileEditingController> get fileControllers =>
+ UnmodifiableListView(_fileControllers);
+
+ SnippetFileEditingController? get activeFileController =>
+ _activeFileController;
+
+ SnippetFileEditingController? getFileControllerByName(String name) {
+ return _fileControllersByName[name];
+ }
+
+ void activateFileControllerByName(String name) {
+ final newController = getFileControllerByName(name);
+
+ if (newController != _activeFileController) {
+ _activeFileController = newController;
+ notifyListeners();
+ }
+ }
+
+ List<SnippetFile> getFiles() {
+ return _fileControllers.map((c) => c.getFile()).toList(growable: false);
}
}
diff --git
a/playground/frontend/playground_components/lib/src/controllers/snippet_file_editing_controller.dart
b/playground/frontend/playground_components/lib/src/controllers/snippet_file_editing_controller.dart
new file mode 100644
index 00000000000..d6802b9655c
--- /dev/null
+++
b/playground/frontend/playground_components/lib/src/controllers/snippet_file_editing_controller.dart
@@ -0,0 +1,151 @@
+/*
+ * 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:math';
+
+import 'package:flutter/widgets.dart';
+import 'package:flutter_code_editor/flutter_code_editor.dart';
+import 'package:get_it/get_it.dart';
+
+import '../models/example_view_options.dart';
+import '../models/sdk.dart';
+import '../models/snippet_file.dart';
+import '../services/symbols/symbols_notifier.dart';
+
+/// The main state object for a file in a snippet.
+class SnippetFileEditingController extends ChangeNotifier {
+ final CodeController codeController;
+ final SnippetFile savedFile;
+ final Sdk sdk;
+
+ bool _isChanged = false;
+
+ final _symbolsNotifier = GetIt.instance.get<SymbolsNotifier>();
+
+ SnippetFileEditingController({
+ required this.savedFile,
+ required this.sdk,
+ required ExampleViewOptions viewOptions,
+ int? contextLine1Based,
+ }) : codeController = CodeController(
+ language: sdk.highlightMode,
+ namedSectionParser: const BracketsStartEndNamedSectionParser(),
+ text: savedFile.content,
+ ) {
+ _applyViewOptions(viewOptions);
+
+ // TODO(alexeyinkin): Scroll to a comment instead of index,
+ // then remove the parameter, https://github.com/apache/beam/issues/23774
+ if (contextLine1Based != null) {
+ _toStartOfFullLine(max(contextLine1Based - 1, 0));
+ }
+
+ codeController.addListener(_onCodeControllerChanged);
+ _symbolsNotifier.addListener(_onSymbolsNotifierChanged);
+ _onSymbolsNotifierChanged();
+ }
+
+ void _applyViewOptions(ExampleViewOptions options) {
+ codeController.readOnlySectionNames = options.readOnlySectionNames.toSet();
+ codeController.visibleSectionNames = options.showSectionNames.toSet();
+
+ if (options.foldCommentAtLineZero) {
+ codeController.foldCommentAtLineZero();
+ }
+
+ if (options.foldImports) {
+ codeController.foldImports();
+ }
+
+ final unfolded = options.unfoldSectionNames;
+ if (unfolded.isNotEmpty) {
+ codeController.foldOutsideSections(unfolded);
+ }
+ }
+
+ void _toStartOfFullLine(int line) {
+ if (line >= codeController.code.lines.length) {
+ return;
+ }
+
+ final fullPosition = codeController.code.lines.lines[line].textRange.start;
+ final visiblePosition = codeController.code.hiddenRanges.cutPosition(
+ fullPosition,
+ );
+
+ codeController.selection = TextSelection.collapsed(
+ offset: visiblePosition,
+ );
+ }
+
+ void _onCodeControllerChanged() {
+ if (!_isChanged) {
+ if (_isCodeChanged()) {
+ _isChanged = true;
+ notifyListeners();
+ }
+ } else {
+ _updateIsChanged();
+ if (!_isChanged) {
+ notifyListeners();
+ }
+ }
+ }
+
+ bool get isChanged => _isChanged;
+
+ bool _isCodeChanged() {
+ return savedFile.content != codeController.fullText;
+ }
+
+ void _updateIsChanged() {
+ _isChanged = _isCodeChanged();
+ }
+
+ void reset() {
+ codeController.text = savedFile.content;
+ }
+
+ void _onSymbolsNotifierChanged() {
+ final mode = sdk.highlightMode;
+ if (mode == null) {
+ return;
+ }
+
+ final dictionary = _symbolsNotifier.getDictionary(mode);
+ if (dictionary == null) {
+ return;
+ }
+
+ codeController.autocompleter.setCustomWords(dictionary.symbols);
+ }
+
+ SnippetFile getFile() => SnippetFile(
+ content: codeController.fullText,
+ isMain: savedFile.isMain,
+ name: savedFile.name,
+ );
+
+ @override
+ void dispose() {
+ _symbolsNotifier.removeListener(
+ _onSymbolsNotifierChanged,
+ );
+ super.dispose();
+ }
+}
diff --git
a/playground/frontend/playground_components/lib/src/models/example.dart
b/playground/frontend/playground_components/lib/src/models/example.dart
index 3f3d89c313b..aa24e82c4c4 100644
--- a/playground/frontend/playground_components/lib/src/models/example.dart
+++ b/playground/frontend/playground_components/lib/src/models/example.dart
@@ -18,16 +18,17 @@
import 'example_base.dart';
import 'sdk.dart';
+import 'snippet_file.dart';
/// A [ExampleBase] that also has all large fields fetched.
class Example extends ExampleBase {
+ final List<SnippetFile> files;
final String? graph;
final String? logs;
final String? outputs;
- final String source;
const Example({
- required this.source,
+ required this.files,
required super.name,
required super.sdk,
required super.type,
@@ -48,9 +49,9 @@ class Example extends ExampleBase {
Example.fromBase(
ExampleBase example, {
+ required this.files,
required this.logs,
required this.outputs,
- required this.source,
this.graph,
}) : super(
complexity: example.complexity,
@@ -68,12 +69,12 @@ class Example extends ExampleBase {
viewOptions: example.viewOptions,
);
- const Example.empty(Sdk sdk)
+ Example.empty(Sdk sdk)
: this(
name: 'Untitled Example',
+ files: [SnippetFile.empty],
path: '',
sdk: sdk,
- source: '',
type: ExampleType.example,
);
}
diff --git
a/playground/frontend/playground_components/lib/src/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
index 531c1bf5a41..b86336023ae 100644
---
a/playground/frontend/playground_components/lib/src/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
@@ -19,12 +19,12 @@
import '../../enums/complexity.dart';
import '../example_view_options.dart';
import '../sdk.dart';
+import '../snippet_file.dart';
import 'example_loading_descriptor.dart';
/// Fully contains an example data to be loaded.
class ContentExampleLoadingDescriptor extends ExampleLoadingDescriptor {
- /// The source code.
- final String content;
+ final List<SnippetFile> files;
/// The name of the example, if any, to show in the dropdown.
final String? name;
@@ -34,7 +34,7 @@ class ContentExampleLoadingDescriptor extends
ExampleLoadingDescriptor {
final Sdk sdk;
const ContentExampleLoadingDescriptor({
- required this.content,
+ required this.files,
required this.sdk,
this.complexity,
this.name,
@@ -42,8 +42,8 @@ class ContentExampleLoadingDescriptor extends
ExampleLoadingDescriptor {
});
static ContentExampleLoadingDescriptor? tryParse(Map<String, dynamic> map) {
- final content = map['content']?.toString();
- if (content == null) {
+ final files = map['files'];
+ if (files is! List) {
return null;
}
@@ -53,7 +53,9 @@ class ContentExampleLoadingDescriptor extends
ExampleLoadingDescriptor {
}
return ContentExampleLoadingDescriptor(
- content: content,
+ files: (map['files'] as List<dynamic>)
+ .map((file) => SnippetFile.fromJson(file as Map<String, dynamic>))
+ .toList(growable: false),
name: map['name']?.toString(),
sdk: sdk,
complexity: Complexity.fromString(map['complexity']),
@@ -64,7 +66,7 @@ class ContentExampleLoadingDescriptor extends
ExampleLoadingDescriptor {
@override
List<Object?> get props => [
complexity,
- content,
+ files,
name,
sdk.id,
];
@@ -72,7 +74,7 @@ class ContentExampleLoadingDescriptor extends
ExampleLoadingDescriptor {
@override
Map<String, dynamic> toJson() => {
'complexity': complexity?.name,
- 'content': content,
+ 'files': files.map((e) => e.toJson()).toList(growable: false),
'name': name,
'sdk': sdk.id,
};
diff --git
a/playground/frontend/playground_components/lib/src/models/example_view_options.dart
b/playground/frontend/playground_components/lib/src/models/example_view_options.dart
index bc12a66db0a..b3d02a3ae86 100644
---
a/playground/frontend/playground_components/lib/src/models/example_view_options.dart
+++
b/playground/frontend/playground_components/lib/src/models/example_view_options.dart
@@ -28,17 +28,15 @@ class ExampleViewOptions with EquatableMixin {
final List<String> unfoldSectionNames;
const ExampleViewOptions({
- required this.foldCommentAtLineZero,
- required this.foldImports,
required this.readOnlySectionNames,
required this.showSectionNames,
required this.unfoldSectionNames,
+ this.foldCommentAtLineZero = true,
+ this.foldImports = true,
});
factory ExampleViewOptions.fromShortMap(Map<String, dynamic> map) {
return ExampleViewOptions(
- foldCommentAtLineZero: true,
- foldImports: true,
readOnlySectionNames: _split(map['readonly']),
showSectionNames: _split(map['show']),
unfoldSectionNames: _split(map['unfold']),
@@ -54,8 +52,6 @@ class ExampleViewOptions with EquatableMixin {
}
static const empty = ExampleViewOptions(
- foldCommentAtLineZero: true,
- foldImports: true,
readOnlySectionNames: [],
showSectionNames: [],
unfoldSectionNames: [],
diff --git
a/playground/frontend/playground_components/lib/src/repositories/models/shared_file.dart
b/playground/frontend/playground_components/lib/src/models/snippet_file.dart
similarity index 60%
rename from
playground/frontend/playground_components/lib/src/repositories/models/shared_file.dart
rename to
playground/frontend/playground_components/lib/src/models/snippet_file.dart
index 7a63f2a91cd..492eb7ba5c0 100644
---
a/playground/frontend/playground_components/lib/src/repositories/models/shared_file.dart
+++ b/playground/frontend/playground_components/lib/src/models/snippet_file.dart
@@ -16,14 +16,37 @@
* limitations under the License.
*/
-class SharedFile {
- final String code;
+import 'package:equatable/equatable.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+part 'snippet_file.g.dart';
+
+@JsonSerializable()
+class SnippetFile with EquatableMixin {
+ final String content;
final bool isMain;
final String name;
- const SharedFile({
- required this.code,
+ const SnippetFile({
+ required this.content,
required this.isMain,
this.name = '',
});
+
+ static const empty = SnippetFile(
+ content: '',
+ isMain: true,
+ );
+
+ Map<String, dynamic> toJson() => _$SnippetFileToJson(this);
+
+ factory SnippetFile.fromJson(Map<String, dynamic> map) =>
+ _$SnippetFileFromJson(map);
+
+ @override
+ List<Object?> get props => [
+ content,
+ isMain,
+ name,
+ ];
}
diff --git
a/playground/frontend/playground_components/lib/src/models/snippet_file.g.dart
b/playground/frontend/playground_components/lib/src/models/snippet_file.g.dart
new file mode 100644
index 00000000000..1d492e0ffb1
--- /dev/null
+++
b/playground/frontend/playground_components/lib/src/models/snippet_file.g.dart
@@ -0,0 +1,20 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'snippet_file.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+SnippetFile _$SnippetFileFromJson(Map<String, dynamic> json) => SnippetFile(
+ content: json['content'] as String,
+ isMain: json['isMain'] as bool,
+ name: json['name'] as String? ?? '',
+ );
+
+Map<String, dynamic> _$SnippetFileToJson(SnippetFile instance) =>
+ <String, dynamic>{
+ 'content': instance.content,
+ 'isMain': instance.isMain,
+ 'name': instance.name,
+ };
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
index a2ebdf8c147..bfeb517966e 100644
---
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
@@ -29,6 +29,7 @@ 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 '../models/snippet_file_grpc_extension.dart';
import '../sdk_grpc_extension.dart';
import 'code_client.dart';
@@ -215,10 +216,10 @@ class GrpcCodeClient implements CodeClient {
grpc.RunCodeRequest _grpcRunCodeRequest(RunCodeRequest request) {
return grpc.RunCodeRequest(
- code: request.code,
- sdk: request.sdk.grpc,
- pipelineOptions: pipelineOptionsToString(request.pipelineOptions),
datasets: request.datasets.map((e) => e.grpc),
+ files: request.files.map((f) => f.grpc),
+ pipelineOptions: pipelineOptionsToString(request.pipelineOptions),
+ sdk: request.sdk.grpc,
);
}
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
index 8df2a6b4204..e4ce41ec824 100644
---
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
@@ -23,6 +23,7 @@ import '../../api/v1/api.pbgrpc.dart' as grpc;
import '../../models/category_with_examples.dart';
import '../../models/example_base.dart';
import '../../models/sdk.dart';
+import '../../models/snippet_file.dart';
import '../complexity_grpc_extension.dart';
import '../dataset_grpc_extension.dart';
import '../models/get_default_precompiled_object_request.dart';
@@ -36,7 +37,7 @@ 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 '../models/snippet_file_grpc_extension.dart';
import '../sdk_grpc_extension.dart';
import 'example_client.dart';
@@ -119,7 +120,9 @@ class GrpcExampleClient implements ExampleClient {
),
);
- return GetPrecompiledObjectCodeResponse(code: response.code);
+ return GetPrecompiledObjectCodeResponse(
+ files: response.files.map((f) => f.model).toList(growable: false),
+ );
}
@override
@@ -340,33 +343,36 @@ class GrpcExampleClient implements ExampleClient {
);
}
- List<SharedFile> _convertToSharedFileList(
+ List<SnippetFile> _convertToSharedFileList(
List<grpc.SnippetFile> snippetFileList,
) {
- final sharedFilesList = <SharedFile>[];
+ final sharedFilesList = <SnippetFile>[];
for (final item in snippetFileList) {
- sharedFilesList.add(SharedFile(
- code: item.content,
- isMain: item.isMain,
- name: item.name,
- ));
+ sharedFilesList.add(
+ SnippetFile(
+ content: item.content,
+ isMain: item.isMain,
+ name: item.name,
+ ),
+ );
}
return sharedFilesList;
}
List<grpc.SnippetFile> _convertToSnippetFileList(
- List<SharedFile> sharedFilesList,
+ List<SnippetFile> sharedFilesList,
) {
final snippetFileList = <grpc.SnippetFile>[];
for (final item in sharedFilesList) {
snippetFileList.add(
- grpc.SnippetFile()
- ..name = item.name
- ..isMain = true
- ..content = item.code,
+ grpc.SnippetFile(
+ content: item.content,
+ isMain: true,
+ name: item.name,
+ ),
);
}
diff --git
a/playground/frontend/playground_components/lib/src/repositories/example_repository.dart
b/playground/frontend/playground_components/lib/src/repositories/example_repository.dart
index 1319ff52f9c..bb35f0c7674 100644
---
a/playground/frontend/playground_components/lib/src/repositories/example_repository.dart
+++
b/playground/frontend/playground_components/lib/src/repositories/example_repository.dart
@@ -19,6 +19,7 @@
import '../models/category_with_examples.dart';
import '../models/example_base.dart';
import '../models/sdk.dart';
+import '../models/snippet_file.dart';
import 'example_client/example_client.dart';
import 'models/get_default_precompiled_object_request.dart';
import 'models/get_precompiled_object_request.dart';
@@ -48,11 +49,11 @@ class ExampleRepository {
return result.example;
}
- Future<String> getPrecompiledObjectCode(
+ Future<List<SnippetFile>> getPrecompiledObjectCode(
GetPrecompiledObjectRequest request,
) async {
final result = await _client.getPrecompiledObjectCode(request);
- return result.code;
+ return result.files;
}
Future<String> getPrecompiledObjectOutput(
diff --git
a/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_code_response.dart
b/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_code_response.dart
index a7bbda2459d..0f1895972c6 100644
---
a/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_code_response.dart
+++
b/playground/frontend/playground_components/lib/src/repositories/models/get_precompiled_object_code_response.dart
@@ -16,10 +16,12 @@
* limitations under the License.
*/
+import '../../models/snippet_file.dart';
+
class GetPrecompiledObjectCodeResponse {
- final String code;
+ final List<SnippetFile> files;
const GetPrecompiledObjectCodeResponse({
- required this.code,
+ required this.files,
});
}
diff --git
a/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart
b/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart
index c935936898d..17e1a92673e 100644
---
a/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart
+++
b/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart
@@ -18,11 +18,11 @@
import '../../enums/complexity.dart';
import '../../models/sdk.dart';
-import 'shared_file.dart';
+import '../../models/snippet_file.dart';
class GetSnippetResponse {
final Complexity? complexity;
- final List<SharedFile> files;
+ final List<SnippetFile> files;
final String pipelineOptions;
final Sdk sdk;
diff --git
a/playground/frontend/playground_components/lib/src/repositories/models/run_code_request.dart
b/playground/frontend/playground_components/lib/src/repositories/models/run_code_request.dart
index d2a49b9ee6d..ddfd695dc39 100644
---
a/playground/frontend/playground_components/lib/src/repositories/models/run_code_request.dart
+++
b/playground/frontend/playground_components/lib/src/repositories/models/run_code_request.dart
@@ -18,17 +18,18 @@
import '../../models/dataset.dart';
import '../../models/sdk.dart';
+import '../../models/snippet_file.dart';
class RunCodeRequest {
- final String code;
final List<Dataset> datasets;
- final Sdk sdk;
+ final List<SnippetFile> files;
final Map<String, String> pipelineOptions;
+ final Sdk sdk;
const RunCodeRequest({
- required this.code,
required this.datasets,
- required this.sdk,
+ required this.files,
required this.pipelineOptions,
+ required this.sdk,
});
}
diff --git
a/playground/frontend/playground_components/lib/src/repositories/models/save_snippet_request.dart
b/playground/frontend/playground_components/lib/src/repositories/models/save_snippet_request.dart
index 4d64416efcc..c2043a754aa 100644
---
a/playground/frontend/playground_components/lib/src/repositories/models/save_snippet_request.dart
+++
b/playground/frontend/playground_components/lib/src/repositories/models/save_snippet_request.dart
@@ -17,10 +17,10 @@
*/
import '../../models/sdk.dart';
-import 'shared_file.dart';
+import '../../models/snippet_file.dart';
class SaveSnippetRequest {
- final List<SharedFile> files;
+ final List<SnippetFile> files;
final Sdk sdk;
final String pipelineOptions;
diff --git
a/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart
b/playground/frontend/playground_components/lib/src/repositories/models/snippet_file_grpc_extension.dart
similarity index 65%
copy from
playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart
copy to
playground/frontend/playground_components/lib/src/repositories/models/snippet_file_grpc_extension.dart
index c935936898d..e4b46ba42c0 100644
---
a/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart
+++
b/playground/frontend/playground_components/lib/src/repositories/models/snippet_file_grpc_extension.dart
@@ -16,20 +16,21 @@
* limitations under the License.
*/
-import '../../enums/complexity.dart';
-import '../../models/sdk.dart';
-import 'shared_file.dart';
+import '../../api/v1/api.pb.dart' as g;
+import '../../models/snippet_file.dart';
-class GetSnippetResponse {
- final Complexity? complexity;
- final List<SharedFile> files;
- final String pipelineOptions;
- final Sdk sdk;
+extension SnippetFileExtension on SnippetFile {
+ g.SnippetFile get grpc => g.SnippetFile(
+ content: content,
+ isMain: isMain,
+ name: name,
+ );
+}
- const GetSnippetResponse({
- required this.complexity,
- required this.files,
- required this.pipelineOptions,
- required this.sdk,
- });
+extension SnippetFileGrpcExtension on g.SnippetFile {
+ SnippetFile get model => SnippetFile(
+ content: content,
+ isMain: isMain,
+ name: name,
+ );
}
diff --git
a/playground/frontend/playground_components/lib/src/widgets/complexity.dart
b/playground/frontend/playground_components/lib/src/widgets/complexity.dart
index 813d905f95d..ce8c7e087cf 100644
--- a/playground/frontend/playground_components/lib/src/widgets/complexity.dart
+++ b/playground/frontend/playground_components/lib/src/widgets/complexity.dart
@@ -29,7 +29,10 @@ class ComplexityWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Row(children: _dots[complexity]!);
+ return Row(
+ mainAxisSize: MainAxisSize.min,
+ children: _dots[complexity]!,
+ );
}
static const Map<Complexity, List<Widget>> _dots = {
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
index 5ec07458401..04b6b911f76 100644
---
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
@@ -42,7 +42,6 @@ class RunOrCancelButton extends StatelessWidget {
Widget build(BuildContext context) {
return RunButton(
playgroundController: playgroundController,
- disabled: playgroundController.selectedExample?.isMultiFile ?? false,
isRunning: playgroundController.isCodeRunning,
cancelRun: () {
beforeCancel?.call();
diff --git
a/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart
b/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart
index ddcba180b89..9f0442695c2 100644
---
a/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart
+++
b/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart
@@ -16,120 +16,67 @@
* limitations under the License.
*/
-import 'dart:math';
-
import 'package:flutter/material.dart';
-import 'package:flutter/scheduler.dart';
-import 'package:flutter_code_editor/flutter_code_editor.dart';
+import '../constants/sizes.dart';
import '../controllers/snippet_editing_controller.dart';
-import '../theme/theme.dart';
-
-class SnippetEditor extends StatefulWidget {
- final SnippetEditingController controller;
- final bool isEditable;
+import 'loading_indicator.dart';
+import 'snippet_file_editor.dart';
+import 'tabbed_snippet_editor.dart';
- SnippetEditor({
+class SnippetEditor extends StatelessWidget {
+ const SnippetEditor({
required this.controller,
required this.isEditable,
- }) : super(
- // When the example is changed, will scroll to the context line again.
- key: ValueKey(controller.selectedExample),
- );
-
- @override
- State<SnippetEditor> createState() => _SnippetEditorState();
-}
-
-class _SnippetEditorState extends State<SnippetEditor> {
- bool _didAutoFocus = false;
- final _focusNode = FocusNode();
- final _scrollController = ScrollController();
-
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
-
- if (!_didAutoFocus) {
- _didAutoFocus = true;
- SchedulerBinding.instance.addPostFrameCallback((_) {
- if (mounted) {
- _scrollSoCursorIsOnTop();
- }
- });
- }
- }
-
- void _scrollSoCursorIsOnTop() {
- _focusNode.requestFocus();
+ this.actionsWidget,
+ });
- final position = max(widget.controller.codeController.selection.start, 0);
- final characterOffset = _getLastCharacterOffset(
- text: widget.controller.codeController.text.substring(0, position),
- style: kLightTheme.extension<BeamThemeExtension>()!.codeRootStyle,
- );
+ final SnippetEditingController controller;
+ final bool isEditable;
- _scrollController.jumpTo(
- min(
- characterOffset.dy,
- _scrollController.position.maxScrollExtent,
- ),
- );
- }
-
- @override
- void dispose() {
- _focusNode.dispose();
- super.dispose();
- }
+ /// A child widget that will be:
+ /// - Hidden if no file is loaded.
+ /// - Shown as an overlay for a single file editor.
+ /// - Built into the tab bar for a multi-file editor.
+ final Widget? actionsWidget;
@override
Widget build(BuildContext context) {
- final ext = Theme.of(context).extension<BeamThemeExtension>()!;
- final isMultiFile = widget.controller.selectedExample?.isMultiFile ??
false;
- final isEnabled = widget.isEditable && !isMultiFile;
-
- return Semantics(
- container: true,
- enabled: isEnabled,
- label: 'widgets.codeEditor.label',
- multiline: true,
- readOnly: isEnabled,
- textField: true,
- child: FocusScope(
- node: FocusScopeNode(canRequestFocus: isEnabled),
- child: CodeTheme(
- data: ext.codeTheme,
- child: Container(
- color: ext.codeTheme.styles['root']?.backgroundColor,
- child: SingleChildScrollView(
- controller: _scrollController,
- child: CodeField(
- key: ValueKey(widget.controller.codeController),
- controller: widget.controller.codeController,
- enabled: isEnabled,
- focusNode: _focusNode,
- textStyle: ext.codeRootStyle,
- ),
- ),
- ),
- ),
- ),
+ return AnimatedBuilder(
+ animation: controller,
+ builder: (context, child) {
+ switch (controller.fileControllers.length) {
+ case 0:
+ return const Center(
+ child: LoadingIndicator(),
+ );
+
+ case 1:
+ return Stack(
+ children: [
+ Positioned.fill(
+ child: SnippetFileEditor(
+ controller: controller.fileControllers.first,
+ isEditable: isEditable,
+ ),
+ ),
+ if (actionsWidget != null)
+ Positioned(
+ right: 0,
+ top: BeamSizes.size10,
+ child: actionsWidget!,
+ ),
+ ],
+ );
+
+ default:
+ return TabbedSnippetEditor(
+ controller: controller,
+ isEditable: isEditable,
+ trailing: actionsWidget,
+ );
+ }
+ },
);
}
}
-
-Offset _getLastCharacterOffset({
- required String text,
- required TextStyle style,
-}) {
- final textPainter = TextPainter(
- textDirection: TextDirection.ltr,
- text: TextSpan(text: text, style: style),
- )..layout();
-
- return textPainter.getOffsetForCaret(
- TextPosition(offset: text.length),
- Rect.zero,
- );
-}
diff --git
a/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart
b/playground/frontend/playground_components/lib/src/widgets/snippet_file_editor.dart
similarity index 82%
copy from
playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart
copy to
playground/frontend/playground_components/lib/src/widgets/snippet_file_editor.dart
index ddcba180b89..85b01027651 100644
---
a/playground/frontend/playground_components/lib/src/widgets/snippet_editor.dart
+++
b/playground/frontend/playground_components/lib/src/widgets/snippet_file_editor.dart
@@ -22,30 +22,31 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_code_editor/flutter_code_editor.dart';
-import '../controllers/snippet_editing_controller.dart';
+import '../controllers/snippet_file_editing_controller.dart';
import '../theme/theme.dart';
-class SnippetEditor extends StatefulWidget {
- final SnippetEditingController controller;
- final bool isEditable;
-
- SnippetEditor({
+class SnippetFileEditor extends StatefulWidget {
+ SnippetFileEditor({
required this.controller,
required this.isEditable,
}) : super(
- // When the example is changed, will scroll to the context line again.
- key: ValueKey(controller.selectedExample),
- );
+ // When the example is changed, will scroll to the context line
again.
+ key: ValueKey(controller.savedFile),
+ );
+
+ final SnippetFileEditingController controller;
+ final bool isEditable;
@override
- State<SnippetEditor> createState() => _SnippetEditorState();
+ State<SnippetFileEditor> createState() => _SnippetFileEditorState();
}
-class _SnippetEditorState extends State<SnippetEditor> {
+class _SnippetFileEditorState extends State<SnippetFileEditor> {
bool _didAutoFocus = false;
final _focusNode = FocusNode();
final _scrollController = ScrollController();
+
@override
void didChangeDependencies() {
super.didChangeDependencies();
@@ -65,8 +66,8 @@ class _SnippetEditorState extends State<SnippetEditor> {
final position = max(widget.controller.codeController.selection.start, 0);
final characterOffset = _getLastCharacterOffset(
- text: widget.controller.codeController.text.substring(0, position),
style: kLightTheme.extension<BeamThemeExtension>()!.codeRootStyle,
+ text: widget.controller.codeController.text.substring(0, position),
);
_scrollController.jumpTo(
@@ -86,18 +87,16 @@ class _SnippetEditorState extends State<SnippetEditor> {
@override
Widget build(BuildContext context) {
final ext = Theme.of(context).extension<BeamThemeExtension>()!;
- final isMultiFile = widget.controller.selectedExample?.isMultiFile ??
false;
- final isEnabled = widget.isEditable && !isMultiFile;
return Semantics(
container: true,
- enabled: isEnabled,
+ enabled: widget.isEditable,
label: 'widgets.codeEditor.label',
multiline: true,
- readOnly: isEnabled,
+ readOnly: !widget.isEditable,
textField: true,
child: FocusScope(
- node: FocusScopeNode(canRequestFocus: isEnabled),
+ node: FocusScopeNode(canRequestFocus: widget.isEditable),
child: CodeTheme(
data: ext.codeTheme,
child: Container(
@@ -107,7 +106,7 @@ class _SnippetEditorState extends State<SnippetEditor> {
child: CodeField(
key: ValueKey(widget.controller.codeController),
controller: widget.controller.codeController,
- enabled: isEnabled,
+ enabled: widget.isEditable,
focusNode: _focusNode,
textStyle: ext.codeRootStyle,
),
@@ -120,8 +119,8 @@ class _SnippetEditorState extends State<SnippetEditor> {
}
Offset _getLastCharacterOffset({
- required String text,
required TextStyle style,
+ required String text,
}) {
final textPainter = TextPainter(
textDirection: TextDirection.ltr,
diff --git
a/playground/frontend/playground_components/lib/src/widgets/tab_header.dart
b/playground/frontend/playground_components/lib/src/widgets/tab_header.dart
index 714f025814e..31d4f132aa7 100644
--- a/playground/frontend/playground_components/lib/src/widgets/tab_header.dart
+++ b/playground/frontend/playground_components/lib/src/widgets/tab_header.dart
@@ -20,8 +20,6 @@ import 'package:flutter/material.dart';
import '../constants/sizes.dart';
-const kHeaderHeight = 50.0;
-
class TabHeader extends StatelessWidget {
final TabController tabController;
final Widget tabsWidget;
@@ -35,7 +33,7 @@ class TabHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SizedBox(
- height: 50,
+ height: BeamSizes.tabBarHeight,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: BeamSizes.size16,
diff --git
a/playground/frontend/playground_components/lib/src/widgets/tabbed_snippet_editor.dart
b/playground/frontend/playground_components/lib/src/widgets/tabbed_snippet_editor.dart
new file mode 100644
index 00000000000..d834f82a800
--- /dev/null
+++
b/playground/frontend/playground_components/lib/src/widgets/tabbed_snippet_editor.dart
@@ -0,0 +1,81 @@
+/*
+ * 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:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:keyed_collection_widgets/keyed_collection_widgets.dart';
+
+import '../controllers/snippet_editing_controller.dart';
+import 'snippet_file_editor.dart';
+import 'tabs/tab_bar.dart';
+
+class TabbedSnippetEditor extends StatelessWidget {
+ const TabbedSnippetEditor({
+ required this.controller,
+ required this.isEditable,
+ this.trailing,
+ });
+
+ final SnippetEditingController controller;
+ final bool isEditable;
+ final Widget? trailing;
+
+ @override
+ Widget build(BuildContext context) {
+ final files = controller.fileControllers.map((c) => c.getFile());
+ final keys = files.map((f) => f.name).toList(growable: false);
+ final initialKey = files.firstWhereOrNull((f) => f.isMain)?.name;
+
+ return DefaultKeyedTabController<String>.fromKeys(
+ animationDuration: Duration.zero,
+ initialKey: initialKey,
+ keys: keys,
+ onChanged: (key) {
+ if (key != null) {
+ controller.activateFileControllerByName(key);
+ }
+ },
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Expanded(
+ child:
+ BeamTabBar(tabs: {for (final key in keys) key: Text(key)}),
+ ),
+ if (trailing != null) trailing!,
+ ],
+ ),
+ Expanded(
+ child: KeyedTabBarView.withDefaultController(
+ children: {
+ for (final key in keys)
+ key: SnippetFileEditor(
+ controller: controller.getFileControllerByName(key)!,
+ isEditable: isEditable,
+ ),
+ },
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git
a/playground/frontend/playground_components/lib/src/widgets/tab_header.dart
b/playground/frontend/playground_components/lib/src/widgets/tabs/tab_bar.dart
similarity index 68%
copy from
playground/frontend/playground_components/lib/src/widgets/tab_header.dart
copy to
playground/frontend/playground_components/lib/src/widgets/tabs/tab_bar.dart
index 714f025814e..7c4a864f7b3 100644
--- a/playground/frontend/playground_components/lib/src/widgets/tab_header.dart
+++
b/playground/frontend/playground_components/lib/src/widgets/tabs/tab_bar.dart
@@ -17,30 +17,25 @@
*/
import 'package:flutter/material.dart';
+import 'package:keyed_collection_widgets/keyed_collection_widgets.dart';
-import '../constants/sizes.dart';
+import '../../constants/sizes.dart';
-const kHeaderHeight = 50.0;
-
-class TabHeader extends StatelessWidget {
- final TabController tabController;
- final Widget tabsWidget;
-
- const TabHeader({
+class BeamTabBar<K extends Object> extends StatelessWidget {
+ const BeamTabBar({
super.key,
- required this.tabController,
- required this.tabsWidget,
+ required this.tabs,
});
+ final Map<K, Widget> tabs;
+
@override
Widget build(BuildContext context) {
return SizedBox(
- height: 50,
- child: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: BeamSizes.size16,
- ),
- child: tabsWidget,
+ height: BeamSizes.tabBarHeight,
+ child: KeyedTabBar.withDefaultController<K>(
+ isScrollable: true,
+ tabs: {for (final key in tabs.keys) key: Tab(child: tabs[key])},
),
);
}
diff --git a/playground/frontend/playground_components/pubspec.yaml
b/playground/frontend/playground_components/pubspec.yaml
index e7c2943c9b7..a87ac0f2a23 100644
--- a/playground/frontend/playground_components/pubspec.yaml
+++ b/playground/frontend/playground_components/pubspec.yaml
@@ -34,7 +34,7 @@ dependencies:
enum_map: ^0.2.1
equatable: ^2.0.5
flutter: { sdk: flutter }
- flutter_code_editor: ^0.2.4
+ flutter_code_editor: ^0.2.5
flutter_markdown: ^0.6.12
flutter_svg: ^1.0.3
fluttertoast: ^8.1.1
@@ -44,6 +44,7 @@ dependencies:
highlight: ^0.7.0
http: ^0.13.5
json_annotation: ^4.7.0
+ keyed_collection_widgets: ^0.4.3
meta: ^1.7.0
protobuf: ^2.1.0
provider: ^6.0.3
diff --git
a/playground/frontend/playground_components/test/src/cache/example_cache_test.dart
b/playground/frontend/playground_components/test/src/cache/example_cache_test.dart
index 38781e1b719..a05785a6681 100644
---
a/playground/frontend/playground_components/test/src/cache/example_cache_test.dart
+++
b/playground/frontend/playground_components/test/src/cache/example_cache_test.dart
@@ -29,11 +29,11 @@ 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,
+final _defaultExamplesMapMock = UnmodifiableMapView({
+ Sdk.java: examplePython3,
+ Sdk.go: examplePython3,
+ Sdk.python: examplePython3,
+ Sdk.scio: examplePython3,
});
void main() {
@@ -75,8 +75,8 @@ void main() {
'then loadExampleInfo should return example immediately',
() async {
expect(
- await cache.loadExampleInfo(exampleMock1),
- exampleMock1,
+ await cache.loadExampleInfo(examplePython1),
+ examplePython1,
);
},
);
@@ -84,9 +84,18 @@ void main() {
test(
'loadExampleInfo loads source, output, logs, graph for given example',
() async {
+ when(mockRepo.getPrecompiledObjectOutput(kRequestForExampleInfo))
+ .thenAnswer((_) async => examplePython3.outputs!);
+ when(mockRepo.getPrecompiledObjectCode(kRequestForExampleInfo))
+ .thenAnswer((_) async => examplePython3.files);
+ when(mockRepo.getPrecompiledObjectLogs(kRequestForExampleInfo))
+ .thenAnswer((_) async => examplePython3.logs!);
+ when(mockRepo.getPrecompiledObjectGraph(kRequestForExampleInfo))
+ .thenAnswer((_) async => examplePython3.graph!);
+
expect(
- await cache.loadExampleInfo(exampleWithoutSourceMock),
- exampleWithAllAdditionsMock,
+ await cache.loadExampleInfo(exampleBasePython3),
+ examplePython3,
);
},
);
@@ -97,9 +106,9 @@ void main() {
'If defaultExamplesBySdk is not empty, '
'loadDefaultExamples should not change it',
() async {
- cache.defaultExamplesBySdk.addAll(kDefaultExamplesMapMock);
+ cache.defaultExamplesBySdk.addAll(_defaultExamplesMapMock);
await cache.loadDefaultPrecompiledObjects();
- expect(cache.defaultExamplesBySdk, kDefaultExamplesMapMock);
+ expect(cache.defaultExamplesBySdk, _defaultExamplesMapMock);
},
);
@@ -109,19 +118,19 @@ void main() {
() async {
// stubs
when(mockRepo.getPrecompiledObjectOutput(kRequestForExampleInfo))
- .thenAnswer((_) async => kOutputResponse.output);
+ .thenAnswer((_) async => examplePython3.outputs!);
when(mockRepo.getPrecompiledObjectCode(kRequestForExampleInfo))
- .thenAnswer((_) async => kOutputResponse.output);
+ .thenAnswer((_) async => examplePython3.files);
when(mockRepo.getPrecompiledObjectLogs(kRequestForExampleInfo))
- .thenAnswer((_) async => kOutputResponse.output);
+ .thenAnswer((_) async => examplePython3.logs!);
when(mockRepo.getPrecompiledObjectGraph(kRequestForExampleInfo))
- .thenAnswer((_) async => kOutputResponse.output);
+ .thenAnswer((_) async => examplePython3.graph!);
// test assertion
await cache.loadDefaultPrecompiledObjects();
expect(
cache.defaultExamplesBySdk,
- kDefaultExamplesMapMock,
+ _defaultExamplesMapMock,
);
},
);
diff --git
a/playground/frontend/playground_components/test/src/common/categories.dart
b/playground/frontend/playground_components/test/src/common/categories.dart
index 2bba911d19c..07a2acfa508 100644
--- a/playground/frontend/playground_components/test/src/common/categories.dart
+++ b/playground/frontend/playground_components/test/src/common/categories.dart
@@ -24,25 +24,25 @@ import 'package:playground_components/src/models/sdk.dart';
import 'examples.dart';
final categoriesMock = [
- CategoryWithExamples(title: 'Filtered', examples: [exampleMock1]),
+ CategoryWithExamples(title: 'Filtered', examples: [examplePython1]),
CategoryWithExamples(
title: 'Unfiltered',
// exampleMock2 is repeated to test that 'tag2' is more frequent than
'tag1'
- examples: [exampleMock1, exampleMock2, exampleMock2, exampleMock2],
+ examples: [examplePython1, examplePython2, examplePython2, examplePython2],
),
];
final filteredCategories = [
- CategoryWithExamples(title: 'Filtered', examples: [exampleMock1]),
+ CategoryWithExamples(title: 'Filtered', examples: [examplePython1]),
];
-const filteredExamples = [exampleMock1, exampleMock2];
+const filteredExamples = [examplePython1, examplePython2];
-const examplesFilteredByTypeMock = [exampleMock2];
+const examplesFilteredByTypeMock = [examplePython2];
-const examplesFilteredByTagsMock = [exampleMock2];
+const examplesFilteredByTagsMock = [examplePython2];
-const examplesFilteredByNameMock = [exampleMock1];
+const examplesFilteredByNameMock = [examplePython1];
final sdkCategoriesFromServerMock = UnmodifiableMapView({
Sdk.java: categoriesMock,
diff --git
a/playground/frontend/playground_components/test/src/common/descriptors.dart
b/playground/frontend/playground_components/test/src/common/descriptors.dart
index ad2259f47ce..6d65c9d591a 100644
--- a/playground/frontend/playground_components/test/src/common/descriptors.dart
+++ b/playground/frontend/playground_components/test/src/common/descriptors.dart
@@ -23,16 +23,16 @@ import 'examples.dart';
const emptyDescriptor = EmptyExampleLoadingDescriptor(sdk: Sdk.java);
final standardDescriptor1 = StandardExampleLoadingDescriptor(
- path: exampleMock1.path,
- sdk: exampleMock1.sdk,
+ path: examplePython1.path,
+ sdk: examplePython1.sdk,
);
final standardDescriptor2 = StandardExampleLoadingDescriptor(
- path: exampleMock2.path,
- sdk: exampleMock2.sdk,
+ path: examplePython2.path,
+ sdk: examplePython2.sdk,
);
final standardGoDescriptor = StandardExampleLoadingDescriptor(
- path: exampleMockGo.path,
- sdk: exampleMockGo.sdk,
+ path: exampleGo6.path,
+ sdk: exampleGo6.sdk,
);
diff --git
a/playground/frontend/playground_components/test/src/common/example_cache.mocks.dart
b/playground/frontend/playground_components/test/src/common/example_cache.mocks.dart
index ccd67c752ba..5ce9a48c2f3 100644
---
a/playground/frontend/playground_components/test/src/common/example_cache.mocks.dart
+++
b/playground/frontend/playground_components/test/src/common/example_cache.mocks.dart
@@ -14,8 +14,7 @@ import
'package:playground_components/src/models/example.dart' as _i3;
import 'package:playground_components/src/models/example_base.dart' as _i2;
import 'package:playground_components/src/models/loading_status.dart' as _i8;
import 'package:playground_components/src/models/sdk.dart' as _i5;
-import 'package:playground_components/src/repositories/models/shared_file.dart'
- as _i9;
+import 'package:playground_components/src/models/snippet_file.dart' as _i9;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -121,7 +120,7 @@ class MockExampleCache extends _i1.Mock implements
_i4.ExampleCache {
) as _i7.Future<_i3.Example>);
@override
_i7.Future<String> saveSnippet({
- List<_i9.SharedFile>? files,
+ List<_i9.SnippetFile>? files,
_i5.Sdk? sdk,
String? pipelineOptions,
}) =>
diff --git
a/playground/frontend/playground_components/test/src/common/example_repository_mock.dart
b/playground/frontend/playground_components/test/src/common/example_repository_mock.dart
index 814651db4fa..5386e52144b 100644
---
a/playground/frontend/playground_components/test/src/common/example_repository_mock.dart
+++
b/playground/frontend/playground_components/test/src/common/example_repository_mock.dart
@@ -30,22 +30,13 @@ MockExampleRepository getMockExampleRepository() {
// stubs
when(m.getDefaultPrecompiledObject(kRequestDefaultExampleForJava))
- .thenAnswer((_) async => exampleWithoutSourceMock);
+ .thenAnswer((_) async => exampleBasePython3);
when(m.getDefaultPrecompiledObject(kRequestDefaultExampleForGo))
- .thenAnswer((_) async => exampleWithoutSourceMock);
+ .thenAnswer((_) async => exampleBasePython3);
when(m.getDefaultPrecompiledObject(kRequestDefaultExampleForPython))
- .thenAnswer((_) async => exampleWithoutSourceMock);
+ .thenAnswer((_) async => exampleBasePython3);
when(m.getDefaultPrecompiledObject(kRequestDefaultExampleForScio))
- .thenAnswer((_) async => exampleWithoutSourceMock);
-
- when(m.getPrecompiledObjectOutput(kRequestForExampleInfo))
- .thenAnswer((_) async => kOutputResponse.output);
- when(m.getPrecompiledObjectCode(kRequestForExampleInfo))
- .thenAnswer((_) async => kOutputResponse.output);
- when(m.getPrecompiledObjectLogs(kRequestForExampleInfo))
- .thenAnswer((_) async => kOutputResponse.output);
- when(m.getPrecompiledObjectGraph(kRequestForExampleInfo))
- .thenAnswer((_) async => kOutputResponse.output);
+ .thenAnswer((_) async => exampleBasePython3);
return m;
}
diff --git
a/playground/frontend/playground_components/test/src/common/example_repository_mock.mocks.dart
b/playground/frontend/playground_components/test/src/common/example_repository_mock.mocks.dart
index aef3e11b804..aa55c5193fb 100644
---
a/playground/frontend/playground_components/test/src/common/example_repository_mock.mocks.dart
+++
b/playground/frontend/playground_components/test/src/common/example_repository_mock.mocks.dart
@@ -10,20 +10,21 @@ import
'package:playground_components/src/models/category_with_examples.dart'
as _i7;
import 'package:playground_components/src/models/example_base.dart' as _i2;
import 'package:playground_components/src/models/sdk.dart' as _i6;
+import 'package:playground_components/src/models/snippet_file.dart' as _i10;
import 'package:playground_components/src/repositories/example_repository.dart'
as _i4;
import
'package:playground_components/src/repositories/models/get_default_precompiled_object_request.dart'
as _i9;
import
'package:playground_components/src/repositories/models/get_precompiled_object_request.dart'
- as _i10;
+ as _i11;
import
'package:playground_components/src/repositories/models/get_precompiled_objects_request.dart'
as _i8;
import
'package:playground_components/src/repositories/models/get_snippet_request.dart'
- as _i11;
+ as _i12;
import
'package:playground_components/src/repositories/models/get_snippet_response.dart'
as _i3;
import
'package:playground_components/src/repositories/models/save_snippet_request.dart'
- as _i12;
+ as _i13;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -71,18 +72,18 @@ class MockExampleRepository extends _i1.Mock implements
_i4.ExampleRepository {
returnValue: Future<_i2.ExampleBase>.value(_FakeExampleBase_0()),
) as _i5.Future<_i2.ExampleBase>);
@override
- _i5.Future<String> getPrecompiledObjectCode(
- _i10.GetPrecompiledObjectRequest? request) =>
+ _i5.Future<List<_i10.SnippetFile>> getPrecompiledObjectCode(
+ _i11.GetPrecompiledObjectRequest? request) =>
(super.noSuchMethod(
Invocation.method(
#getPrecompiledObjectCode,
[request],
),
- returnValue: Future<String>.value(''),
- ) as _i5.Future<String>);
+ returnValue:
Future<List<_i10.SnippetFile>>.value(<_i10.SnippetFile>[]),
+ ) as _i5.Future<List<_i10.SnippetFile>>);
@override
_i5.Future<String> getPrecompiledObjectOutput(
- _i10.GetPrecompiledObjectRequest? request) =>
+ _i11.GetPrecompiledObjectRequest? request) =>
(super.noSuchMethod(
Invocation.method(
#getPrecompiledObjectOutput,
@@ -92,7 +93,7 @@ class MockExampleRepository extends _i1.Mock implements
_i4.ExampleRepository {
) as _i5.Future<String>);
@override
_i5.Future<String> getPrecompiledObjectLogs(
- _i10.GetPrecompiledObjectRequest? request) =>
+ _i11.GetPrecompiledObjectRequest? request) =>
(super.noSuchMethod(
Invocation.method(
#getPrecompiledObjectLogs,
@@ -102,7 +103,7 @@ class MockExampleRepository extends _i1.Mock implements
_i4.ExampleRepository {
) as _i5.Future<String>);
@override
_i5.Future<String> getPrecompiledObjectGraph(
- _i10.GetPrecompiledObjectRequest? request) =>
+ _i11.GetPrecompiledObjectRequest? request) =>
(super.noSuchMethod(
Invocation.method(
#getPrecompiledObjectGraph,
@@ -112,7 +113,7 @@ class MockExampleRepository extends _i1.Mock implements
_i4.ExampleRepository {
) as _i5.Future<String>);
@override
_i5.Future<_i2.ExampleBase> getPrecompiledObject(
- _i10.GetPrecompiledObjectRequest? request) =>
+ _i11.GetPrecompiledObjectRequest? request) =>
(super.noSuchMethod(
Invocation.method(
#getPrecompiledObject,
@@ -122,7 +123,7 @@ class MockExampleRepository extends _i1.Mock implements
_i4.ExampleRepository {
) as _i5.Future<_i2.ExampleBase>);
@override
_i5.Future<_i3.GetSnippetResponse> getSnippet(
- _i11.GetSnippetRequest? request) =>
+ _i12.GetSnippetRequest? request) =>
(super.noSuchMethod(
Invocation.method(
#getSnippet,
@@ -132,7 +133,7 @@ class MockExampleRepository extends _i1.Mock implements
_i4.ExampleRepository {
Future<_i3.GetSnippetResponse>.value(_FakeGetSnippetResponse_1()),
) as _i5.Future<_i3.GetSnippetResponse>);
@override
- _i5.Future<String> saveSnippet(_i12.SaveSnippetRequest? request) =>
+ _i5.Future<String> saveSnippet(_i13.SaveSnippetRequest? request) =>
(super.noSuchMethod(
Invocation.method(
#saveSnippet,
diff --git
a/playground/frontend/playground_components/test/src/common/examples.dart
b/playground/frontend/playground_components/test/src/common/examples.dart
index 50032f0c112..9a2a5f97b82 100644
--- a/playground/frontend/playground_components/test/src/common/examples.dart
+++ b/playground/frontend/playground_components/test/src/common/examples.dart
@@ -20,30 +20,31 @@ import
'package:playground_components/src/enums/complexity.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';
+import 'package:playground_components/src/models/snippet_file.dart';
-const exampleMock1 = Example(
+const examplePython1 = Example(
complexity: Complexity.basic,
description: 'description',
+ files: [SnippetFile(content: 'ex1', isMain: true)],
name: 'Example X1',
path: 'SDK_PYTHON/Category/Name1',
sdk: Sdk.python,
- source: 'ex1',
tags: ['tag1'],
type: ExampleType.example,
);
-const exampleMock2 = Example(
+const examplePython2 = Example(
complexity: Complexity.basic,
description: 'description',
+ files: [SnippetFile(content: 'ex2', isMain: true)],
name: 'Kata',
path: 'SDK_PYTHON/Category/Name2',
sdk: Sdk.python,
- source: 'ex2',
tags: ['tag2'],
type: ExampleType.kata,
);
-const exampleWithoutSourceMock = ExampleBase(
+const exampleBasePython3 = ExampleBase(
complexity: Complexity.basic,
description: 'description',
name: 'Test example',
@@ -52,38 +53,49 @@ const exampleWithoutSourceMock = ExampleBase(
type: ExampleType.example,
);
-const exampleWithAllAdditionsMock = Example(
+const examplePython3 = Example(
complexity: Complexity.basic,
description: 'description',
- graph: 'test outputs',
- logs: 'test outputs',
+ files: [SnippetFile(content: 'test source', isMain: true)],
+ graph: 'test graph',
+ logs: 'test logs',
name: 'Test example',
outputs: 'test outputs',
path: 'SDK_PYTHON/Category/Name',
sdk: Sdk.python,
- source: 'test outputs',
type: ExampleType.example,
);
-const exampleGoPipelineOptions = Example(
+const exampleGo4Multifile = Example(
+ files: [
+ SnippetFile(content: 'go1', isMain: false, name: '1'),
+ SnippetFile(content: 'go2', isMain: true, name: '2'),
+ ],
+ name: 'exampleGo4Multifile',
+ sdk: Sdk.go,
+ type: ExampleType.example,
+ path: 'SDK_GO/Category/exampleGo4Multifile',
+);
+
+const exampleGo5PipelineOptions = Example(
description: 'description',
- graph: 'test outputs',
- logs: 'test outputs',
+ files: [SnippetFile(content: 'test source', isMain: true)],
+ graph: 'test graph',
+ logs: 'test logs',
name: 'Test example',
outputs: 'test outputs',
path: 'SDK_PYTHON/Category/Name',
pipelineOptions: 'pipeline options',
sdk: Sdk.go,
- source: 'test outputs',
type: ExampleType.example,
);
-const exampleMockGo = Example(
+const exampleGo6 = Example(
complexity: Complexity.medium,
description: 'description',
name: 'Example',
+ files: [SnippetFile(content: 'ex6', isMain: true)],
path: 'SDK_GO/Category/Name',
sdk: Sdk.go,
- source: 'ex1',
type: ExampleType.example,
);
diff --git
a/playground/frontend/playground_components/test/src/common/requests.dart
b/playground/frontend/playground_components/test/src/common/requests.dart
index 84d015884df..f22014a4011 100644
--- a/playground/frontend/playground_components/test/src/common/requests.dart
+++ b/playground/frontend/playground_components/test/src/common/requests.dart
@@ -17,6 +17,7 @@
*/
import 'package:playground_components/src/models/sdk.dart';
+import 'package:playground_components/src/models/snippet_file.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';
@@ -40,11 +41,11 @@ const kGetDefaultPrecompiledObjectRequest =
GetDefaultPrecompiledObjectRequest(
sdk: Sdk.java,
);
const kGetDefaultPrecompiledObjectResponse = GetPrecompiledObjectResponse(
- example: exampleMock1,
+ example: examplePython1,
);
const kGetPrecompiledObjectCodeResponse = GetPrecompiledObjectCodeResponse(
- code: 'test source',
+ files: [SnippetFile(content: 'test source', isMain: true)],
);
const kOutputResponse = OutputResponse(output: 'test outputs');
diff --git
a/playground/frontend/playground_components/test/src/controllers/example_loaders/common.dart
b/playground/frontend/playground_components/test/src/controllers/example_loaders/common.dart
index 324ff9c1e00..5015991528d 100644
---
a/playground/frontend/playground_components/test/src/controllers/example_loaders/common.dart
+++
b/playground/frontend/playground_components/test/src/controllers/example_loaders/common.dart
@@ -46,10 +46,10 @@ class TestExampleLoader extends ExampleLoader {
: example = descriptor.sdk == null
? null
: Example(
+ files: [SnippetFile(content: descriptor.sdk!.id, isMain:
true)],
name: descriptor.sdk!.id,
path: descriptor.sdk!.id,
sdk: descriptor.sdk!,
- source: descriptor.sdk!.id,
type: ExampleType.example,
);
diff --git
a/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart
b/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart
index 7bcc1204ebd..6ac39341a84 100644
---
a/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart
+++
b/playground/frontend/playground_components/test/src/controllers/example_loaders/examples_loader_test.mocks.dart
@@ -3,7 +3,7 @@
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
-import 'dart:async' as _i13;
+import 'dart:async' as _i14;
import 'dart:ui' as _i15;
import 'package:mockito/mockito.dart' as _i1;
@@ -19,7 +19,7 @@ import
'package:playground_components/src/models/category_with_examples.dart'
import 'package:playground_components/src/models/example.dart' as _i9;
import 'package:playground_components/src/models/example_base.dart' as _i8;
import
'package:playground_components/src/models/example_loading_descriptors/example_loading_descriptor.dart'
- as _i14;
+ as _i13;
import
'package:playground_components/src/models/example_loading_descriptors/examples_loading_descriptor.dart'
as _i7;
import
'package:playground_components/src/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart'
@@ -28,8 +28,7 @@ import
'package:playground_components/src/models/loading_status.dart' as _i17;
import 'package:playground_components/src/models/outputs.dart' as _i11;
import 'package:playground_components/src/models/sdk.dart' as _i12;
import 'package:playground_components/src/models/shortcut.dart' as _i4;
-import 'package:playground_components/src/repositories/models/shared_file.dart'
- as _i18;
+import 'package:playground_components/src/models/snippet_file.dart' as _i18;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -193,19 +192,9 @@ class MockPlaygroundController extends _i1.Mock
returnValueForMissingStub: null,
);
@override
- _i13.Future<void> setExampleBase(_i8.ExampleBase? exampleBase) =>
- (super.noSuchMethod(
- Invocation.method(
- #setExampleBase,
- [exampleBase],
- ),
- returnValue: Future<void>.value(),
- returnValueForMissingStub: Future<void>.value(),
- ) as _i13.Future<void>);
- @override
void setExample(
_i9.Example? example, {
- _i14.ExampleLoadingDescriptor? descriptor,
+ _i13.ExampleLoadingDescriptor? descriptor,
bool? setCurrentSdk,
}) =>
super.noSuchMethod(
@@ -233,14 +222,6 @@ class MockPlaygroundController extends _i1.Mock
returnValueForMissingStub: null,
);
@override
- void setSource(String? source) => super.noSuchMethod(
- Invocation.method(
- #setSource,
- [source],
- ),
- returnValueForMissingStub: null,
- );
- @override
void setSelectedOutputFilterType(_i11.OutputType? type) =>
super.noSuchMethod(
Invocation.method(
#setSelectedOutputFilterType,
@@ -298,14 +279,14 @@ class MockPlaygroundController extends _i1.Mock
returnValueForMissingStub: null,
);
@override
- _i13.Future<void> cancelRun() => (super.noSuchMethod(
+ _i14.Future<void> cancelRun() => (super.noSuchMethod(
Invocation.method(
#cancelRun,
[],
),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value(),
- ) as _i13.Future<void>);
+ ) as _i14.Future<void>);
@override
void filterOutput(_i11.OutputType? type) => super.noSuchMethod(
Invocation.method(
@@ -315,7 +296,7 @@ class MockPlaygroundController extends _i1.Mock
returnValueForMissingStub: null,
);
@override
- _i13.Future<_i6.UserSharedExampleLoadingDescriptor> saveSnippet() =>
+ _i14.Future<_i6.UserSharedExampleLoadingDescriptor> saveSnippet() =>
(super.noSuchMethod(
Invocation.method(
#saveSnippet,
@@ -323,7 +304,7 @@ class MockPlaygroundController extends _i1.Mock
),
returnValue: Future<_i6.UserSharedExampleLoadingDescriptor>.value(
_FakeUserSharedExampleLoadingDescriptor_4()),
- ) as _i13.Future<_i6.UserSharedExampleLoadingDescriptor>);
+ ) as _i14.Future<_i6.UserSharedExampleLoadingDescriptor>);
@override
_i7.ExamplesLoadingDescriptor getLoadingDescriptor() => (super.noSuchMethod(
Invocation.method(
@@ -399,10 +380,10 @@ class MockExampleCache extends _i1.Mock implements
_i2.ExampleCache {
returnValueForMissingStub: null,
);
@override
- _i13.Future<void> get allExamplesFuture => (super.noSuchMethod(
+ _i14.Future<void> get allExamplesFuture => (super.noSuchMethod(
Invocation.getter(#allExamplesFuture),
returnValue: Future<void>.value(),
- ) as _i13.Future<void>);
+ ) as _i14.Future<void>);
@override
_i17.LoadingStatus get catalogStatus => (super.noSuchMethod(
Invocation.getter(#catalogStatus),
@@ -414,14 +395,14 @@ class MockExampleCache extends _i1.Mock implements
_i2.ExampleCache {
returnValue: false,
) as bool);
@override
- _i13.Future<void> loadAllPrecompiledObjectsIfNot() => (super.noSuchMethod(
+ _i14.Future<void> loadAllPrecompiledObjectsIfNot() => (super.noSuchMethod(
Invocation.method(
#loadAllPrecompiledObjectsIfNot,
[],
),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value(),
- ) as _i13.Future<void>);
+ ) as _i14.Future<void>);
@override
List<_i16.CategoryWithExamples> getCategories(_i12.Sdk? sdk) =>
(super.noSuchMethod(
@@ -432,7 +413,7 @@ class MockExampleCache extends _i1.Mock implements
_i2.ExampleCache {
returnValue: <_i16.CategoryWithExamples>[],
) as List<_i16.CategoryWithExamples>);
@override
- _i13.Future<_i8.ExampleBase> getPrecompiledObject(
+ _i14.Future<_i8.ExampleBase> getPrecompiledObject(
String? path,
_i12.Sdk? sdk,
) =>
@@ -445,18 +426,18 @@ class MockExampleCache extends _i1.Mock implements
_i2.ExampleCache {
],
),
returnValue: Future<_i8.ExampleBase>.value(_FakeExampleBase_6()),
- ) as _i13.Future<_i8.ExampleBase>);
+ ) as _i14.Future<_i8.ExampleBase>);
@override
- _i13.Future<_i9.Example> loadSharedExample(String? id) =>
(super.noSuchMethod(
+ _i14.Future<_i9.Example> loadSharedExample(String? id) =>
(super.noSuchMethod(
Invocation.method(
#loadSharedExample,
[id],
),
returnValue: Future<_i9.Example>.value(_FakeExample_7()),
- ) as _i13.Future<_i9.Example>);
+ ) as _i14.Future<_i9.Example>);
@override
- _i13.Future<String> saveSnippet({
- List<_i18.SharedFile>? files,
+ _i14.Future<String> saveSnippet({
+ List<_i18.SnippetFile>? files,
_i12.Sdk? sdk,
String? pipelineOptions,
}) =>
@@ -471,16 +452,16 @@ class MockExampleCache extends _i1.Mock implements
_i2.ExampleCache {
},
),
returnValue: Future<String>.value(''),
- ) as _i13.Future<String>);
+ ) as _i14.Future<String>);
@override
- _i13.Future<_i9.Example> loadExampleInfo(_i8.ExampleBase? example) =>
+ _i14.Future<_i9.Example> loadExampleInfo(_i8.ExampleBase? example) =>
(super.noSuchMethod(
Invocation.method(
#loadExampleInfo,
[example],
),
returnValue: Future<_i9.Example>.value(_FakeExample_7()),
- ) as _i13.Future<_i9.Example>);
+ ) as _i14.Future<_i9.Example>);
@override
void setSelectorOpened(bool? value) => super.noSuchMethod(
Invocation.method(
@@ -490,41 +471,41 @@ class MockExampleCache extends _i1.Mock implements
_i2.ExampleCache {
returnValueForMissingStub: null,
);
@override
- _i13.Future<_i9.Example?> getDefaultExampleBySdk(_i12.Sdk? sdk) =>
+ _i14.Future<_i9.Example?> getDefaultExampleBySdk(_i12.Sdk? sdk) =>
(super.noSuchMethod(
Invocation.method(
#getDefaultExampleBySdk,
[sdk],
),
returnValue: Future<_i9.Example?>.value(),
- ) as _i13.Future<_i9.Example?>);
+ ) as _i14.Future<_i9.Example?>);
@override
- _i13.Future<void> loadDefaultPrecompiledObjects() => (super.noSuchMethod(
+ _i14.Future<void> loadDefaultPrecompiledObjects() => (super.noSuchMethod(
Invocation.method(
#loadDefaultPrecompiledObjects,
[],
),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value(),
- ) as _i13.Future<void>);
+ ) as _i14.Future<void>);
@override
- _i13.Future<void> loadDefaultPrecompiledObjectsIfNot() =>
(super.noSuchMethod(
+ _i14.Future<void> loadDefaultPrecompiledObjectsIfNot() =>
(super.noSuchMethod(
Invocation.method(
#loadDefaultPrecompiledObjectsIfNot,
[],
),
returnValue: Future<void>.value(),
returnValueForMissingStub: Future<void>.value(),
- ) as _i13.Future<void>);
+ ) as _i14.Future<void>);
@override
- _i13.Future<_i8.ExampleBase?> getCatalogExampleByPath(String? path) =>
+ _i14.Future<_i8.ExampleBase?> getCatalogExampleByPath(String? path) =>
(super.noSuchMethod(
Invocation.method(
#getCatalogExampleByPath,
[path],
),
returnValue: Future<_i8.ExampleBase?>.value(),
- ) as _i13.Future<_i8.ExampleBase?>);
+ ) as _i14.Future<_i8.ExampleBase?>);
@override
void addListener(_i15.VoidCallback? listener) => super.noSuchMethod(
Invocation.method(
diff --git
a/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.dart
b/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.dart
index 5860ff7bb17..b44ec6f81b5 100644
---
a/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.dart
+++
b/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.dart
@@ -51,7 +51,7 @@ void main() {
// TODO(alexeyinkin): Compare whole objects when that gets to include all
fields, https://github.com/apache/beam/issues/23979
expect(example.name, _name);
expect(example.sdk, _sdk);
- expect(example.source, _contents);
+ expect(example.files.first.content, _contents);
expect(example.type, ExampleType.example);
expect(example.path, _path);
});
diff --git
a/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.mocks.dart
b/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.mocks.dart
index ad8a7a54490..a697f1105ad 100644
---
a/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.mocks.dart
+++
b/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.mocks.dart
@@ -14,8 +14,7 @@ import
'package:playground_components/src/models/example.dart' as _i3;
import 'package:playground_components/src/models/example_base.dart' as _i2;
import 'package:playground_components/src/models/loading_status.dart' as _i8;
import 'package:playground_components/src/models/sdk.dart' as _i5;
-import 'package:playground_components/src/repositories/models/shared_file.dart'
- as _i9;
+import 'package:playground_components/src/models/snippet_file.dart' as _i9;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -121,7 +120,7 @@ class MockExampleCache extends _i1.Mock implements
_i4.ExampleCache {
) as _i7.Future<_i3.Example>);
@override
_i7.Future<String> saveSnippet({
- List<_i9.SharedFile>? files,
+ List<_i9.SnippetFile>? files,
_i5.Sdk? sdk,
String? pipelineOptions,
}) =>
diff --git
a/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart
b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart
index 0adfff718aa..30d39e293c9 100644
---
a/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart
+++
b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.dart
@@ -63,10 +63,12 @@ Future<void> main() async {
expect(controller.pipelineOptions, '');
});
- test('Initial value of source should be empty string', () {
+ test('source', () {
expect(controller.source, null);
controller.setSdk(Sdk.go);
- expect(controller.source, '');
+ expect(controller.source, null);
+ controller.snippetEditingController!.setExample(exampleGo4Multifile);
+ expect(controller.source, exampleGo4Multifile.files[1].content);
});
group('isExampleChanged Tests', () {
@@ -74,12 +76,13 @@ Future<void> main() async {
'If example source is changed, value of isExampleChanged should be
true',
() {
controller.setExample(
- exampleMock1,
+ examplePython1,
descriptor: emptyDescriptor,
setCurrentSdk: true,
);
expect(controller.isExampleChanged, false);
- controller.setSource('test');
+ controller.snippetEditingController?.fileControllers.first
+ .codeController.text = 'test';
expect(controller.isExampleChanged, true);
},
);
@@ -88,7 +91,7 @@ Future<void> main() async {
'If pipelineOptions is changed, value of isExampleChanged should be
true',
() {
controller.setExample(
- exampleMock1,
+ examplePython1,
descriptor: emptyDescriptor,
setCurrentSdk: true,
);
@@ -103,7 +106,7 @@ Future<void> main() async {
'If selected example type is not test and SDK is java or python, graph
should be available',
() {
controller.setExample(
- exampleMock1,
+ examplePython1,
descriptor: emptyDescriptor,
setCurrentSdk: true,
);
@@ -116,11 +119,11 @@ Future<void> main() async {
() {
controller.addListener(() {
expect(controller.sdk, Sdk.go);
- expect(controller.source, exampleMockGo.source);
- expect(controller.selectedExample, exampleMockGo);
+ expect(controller.source, exampleGo6.files.first.content);
+ expect(controller.selectedExample, exampleGo6);
});
controller.setExample(
- exampleMockGo,
+ exampleGo6,
descriptor: emptyDescriptor,
setCurrentSdk: true,
);
@@ -138,13 +141,14 @@ Future<void> main() async {
'Playground state reset should reset source to example notify all
listeners',
() {
controller.setExample(
- exampleMock1,
+ examplePython1,
descriptor: emptyDescriptor,
setCurrentSdk: true,
);
- controller.setSource('source');
+ controller.snippetEditingController?.fileControllers.first.codeController
+ .text = 'source';
controller.addListener(() {
- expect(controller.source, exampleMock1.source);
+ expect(controller.source, examplePython1.files.first.content);
});
controller.reset();
});
@@ -170,12 +174,12 @@ Future<void> main() async {
test('getLoadingDescriptor()', () {
controller.setExample(
- exampleMock2,
+ examplePython2,
descriptor: standardDescriptor2,
setCurrentSdk: true,
);
controller.setExample(
- exampleMockGo,
+ exampleGo6,
descriptor: standardGoDescriptor,
setCurrentSdk: false,
);
@@ -199,7 +203,8 @@ Future<void> main() async {
expect(controller.sdk, Sdk.go);
expect(
- controller.requireSnippetEditingController().codeController.text,
+ controller.snippetEditingController?.fileControllers.first
+ .codeController.fullText,
'',
);
@@ -221,11 +226,13 @@ Future<void> main() async {
expect(controller.sdk, Sdk.go);
- controller.requireSnippetEditingController().setSource(text);
+ controller.snippetEditingController?.fileControllers.first
+ .codeController.text = text;
controller.setEmptyIfNotExists(Sdk.go, setCurrentSdk: true);
expect(
- controller.requireSnippetEditingController().codeController.text,
+ controller.snippetEditingController?.fileControllers.first
+ .codeController.fullText,
text,
);
});
diff --git
a/playground/frontend/playground_components/test/src/controllers/playground_controller_test.mocks.dart
b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.mocks.dart
index e44e628a040..9bccb3f91f7 100644
---
a/playground/frontend/playground_components/test/src/controllers/playground_controller_test.mocks.dart
+++
b/playground/frontend/playground_components/test/src/controllers/playground_controller_test.mocks.dart
@@ -22,8 +22,7 @@ import
'package:playground_components/src/models/example_loading_descriptors/exa
as _i8;
import 'package:playground_components/src/models/loading_status.dart' as _i12;
import 'package:playground_components/src/models/sdk.dart' as _i9;
-import 'package:playground_components/src/repositories/models/shared_file.dart'
- as _i13;
+import 'package:playground_components/src/models/snippet_file.dart' as _i13;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
@@ -175,7 +174,7 @@ class MockExampleCache extends _i1.Mock implements
_i10.ExampleCache {
) as _i7.Future<_i4.Example>);
@override
_i7.Future<String> saveSnippet({
- List<_i13.SharedFile>? files,
+ List<_i13.SnippetFile>? files,
_i9.Sdk? sdk,
String? pipelineOptions,
}) =>
diff --git
a/playground/frontend/playground_components/test/src/controllers/snippet_editing_controller_test.dart
b/playground/frontend/playground_components/test/src/controllers/snippet_editing_controller_test.dart
index 8ad2a575a1f..376a27469ac 100644
---
a/playground/frontend/playground_components/test/src/controllers/snippet_editing_controller_test.dart
+++
b/playground/frontend/playground_components/test/src/controllers/snippet_editing_controller_test.dart
@@ -23,6 +23,7 @@ import
'package:playground_components/src/controllers/snippet_editing_controller
import 'package:playground_components/src/enums/complexity.dart';
import
'package:playground_components/src/models/example_loading_descriptors/content_example_loading_descriptor.dart';
import 'package:playground_components/src/models/sdk.dart';
+import 'package:playground_components/src/models/snippet_file.dart';
import 'package:playground_components/src/playground_components.dart';
import '../common/descriptors.dart';
@@ -34,52 +35,54 @@ void main() async {
int notified = 0;
late SnippetEditingController controller;
- setUp((){
+ setUp(() {
notified = 0;
controller = SnippetEditingController(sdk: Sdk.python);
controller.addListener(() => notified++);
});
group('SnippetEditingController.', () {
- group('Changes.', (){
- test('Unchanged initially', (){
+ group('Changes.', () {
+ test('Unchanged initially', () {
expect(controller.isChanged, false);
expect(notified, 0);
});
test('Unchanged after setting an example', () {
- controller.setExample(exampleMock1);
+ controller.setExample(examplePython1);
expect(controller.isChanged, false);
expect(notified, 1);
});
test('Changes when changing code, notifies once', () {
- controller.setExample(exampleMock1);
- controller.codeController.text = exampleMock1.source;
+ controller.setExample(examplePython1);
+ controller.fileControllers.first.codeController.text =
+ examplePython1.files.first.content;
expect(controller.isChanged, false);
expect(notified, 1);
- controller.codeController.text = 'changed';
+ controller.fileControllers.first.codeController.text = 'changed';
expect(controller.isChanged, true);
expect(notified, 2);
- controller.codeController.text = 'changed2';
+ controller.fileControllers.first.codeController.text = 'changed2';
expect(controller.isChanged, true);
expect(notified, 2);
- controller.codeController.text = exampleMock1.source;
+ controller.fileControllers.first.codeController.text =
+ examplePython1.files.first.content;
expect(controller.isChanged, false);
expect(notified, 3);
});
test('Changes when changing pipelineOptions, notifies once', () {
- controller.setExample(exampleGoPipelineOptions);
- controller.pipelineOptions = exampleGoPipelineOptions.pipelineOptions;
+ controller.setExample(exampleGo5PipelineOptions);
+ controller.pipelineOptions = exampleGo5PipelineOptions.pipelineOptions;
expect(controller.isChanged, false);
expect(notified, 1);
@@ -94,17 +97,64 @@ void main() async {
expect(controller.isChanged, true);
expect(notified, 2);
- controller.pipelineOptions = exampleGoPipelineOptions.pipelineOptions;
+ controller.pipelineOptions = exampleGo5PipelineOptions.pipelineOptions;
expect(controller.isChanged, false);
expect(notified, 3);
});
});
+ group('Files.', () {
+ test('activeFileController, activateFileControllerByName', () {
+ expect(controller.activeFileController, null);
+
+ controller.setExample(exampleGo4Multifile);
+
+ expect(
+ controller.activeFileController?.getFile().content,
+ exampleGo4Multifile.files[1].content,
+ );
+
+ controller.activateFileControllerByName(
+ exampleGo4Multifile.files[0].name,
+ );
+ expect(
+ controller.activeFileController?.getFile().content,
+ exampleGo4Multifile.files[0].content,
+ );
+
+ controller.activateFileControllerByName('nonexistent');
+ expect(controller.activeFileController, null);
+ });
+
+ test('getFileControllerByName', () {
+ controller.setExample(exampleGo4Multifile);
+
+ expect(
+ controller
+ .getFileControllerByName(exampleGo4Multifile.files[0].name)
+ ?.savedFile
+ .content,
+ exampleGo4Multifile.files[0].content,
+ );
+ expect(
+ controller
+ .getFileControllerByName(exampleGo4Multifile.files[1].name)
+ ?.savedFile
+ .content,
+ exampleGo4Multifile.files[1].content,
+ );
+ expect(
+ controller.getFileControllerByName('nonexistent'),
+ null,
+ );
+ });
+ });
+
group('Descriptors.', () {
test('Returns the original descriptor if unchanged', () {
controller.setExample(
- exampleMock1,
+ examplePython1,
descriptor: standardDescriptor1,
);
@@ -115,32 +165,33 @@ void main() async {
test('Returns a ContentExampleLoadingDescriptor if changed', () {
controller.setExample(
- exampleMock1,
+ examplePython1,
descriptor: standardDescriptor1,
);
- controller.codeController.value = const TextEditingValue(text: 'ex4');
+ controller.fileControllers.first.codeController.value =
+ const TextEditingValue(text: 'ex4');
final descriptor = controller.getLoadingDescriptor();
const expected = ContentExampleLoadingDescriptor(
- content: 'ex4',
- sdk: Sdk.python,
- name: 'Example X1',
complexity: Complexity.basic,
+ files: [SnippetFile(content: 'ex4', isMain: true, name: '')],
+ name: 'Example X1',
+ sdk: Sdk.python,
);
expect(descriptor, expected);
});
test('Returns a ContentExampleLoadingDescriptor if no descriptor', () {
- controller.setExample(exampleMock1, descriptor: null);
+ controller.setExample(examplePython1, descriptor: null);
- controller.setExample(exampleMock2, descriptor: null);
+ controller.setExample(examplePython2, descriptor: null);
final descriptor = controller.getLoadingDescriptor();
const expected = ContentExampleLoadingDescriptor(
complexity: Complexity.basic,
- content: 'ex2',
+ files: [SnippetFile(content: 'ex2', isMain: true, name: '')],
name: 'Kata',
sdk: Sdk.python,
);
diff --git
a/playground/frontend/playground_components/test/src/models/example_loading_descriptors/content_example_loading_descriptor_test.dart
b/playground/frontend/playground_components/test/src/models/example_loading_descriptors/content_example_loading_descriptor_test.dart
index 75a6aa451a0..d8e5a70c3ea 100644
---
a/playground/frontend/playground_components/test/src/models/example_loading_descriptors/content_example_loading_descriptor_test.dart
+++
b/playground/frontend/playground_components/test/src/models/example_loading_descriptors/content_example_loading_descriptor_test.dart
@@ -25,20 +25,19 @@ void main() {
group('ContentExampleLoadingDescriptor', () {
test('defaults', () {
const descriptorWithDefaults = ContentExampleLoadingDescriptor(
- content: 'abc',
+ files: [SnippetFile(content: 'abc', isMain: true)],
sdk: Sdk.go,
);
- final parsed = ContentExampleLoadingDescriptor.tryParse(
- descriptorWithDefaults.toJson(),
- );
+ final map = descriptorWithDefaults.toJson();
+ final parsed = ContentExampleLoadingDescriptor.tryParse(map);
expect(parsed, descriptorWithDefaults);
});
const descriptor = ContentExampleLoadingDescriptor(
complexity: Complexity.advanced,
- content: 'abc',
+ files: [SnippetFile(content: 'abc', isMain: true)],
name: 'name',
sdk: Sdk.go,
);
diff --git
a/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart
b/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart
index 7615fb115d7..6dfd362282f 100644
---
a/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart
+++
b/playground/frontend/playground_components/test/src/repositories/code_repository_test.dart
@@ -20,6 +20,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:playground_components/src/models/sdk.dart';
+import 'package:playground_components/src/models/snippet_file.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';
@@ -31,7 +32,7 @@ import
'package:playground_components/src/repositories/models/run_code_result.da
import 'code_repository_test.mocks.dart';
const kRequestMock = RunCodeRequest(
- code: 'code',
+ files: [SnippetFile(content: 'code', isMain: true)],
sdk: Sdk.java,
pipelineOptions: {},
datasets: [],
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
index 964f3b34b59..6b7f740d4a4 100644
---
a/playground/frontend/playground_components/test/src/repositories/example_repository_test.dart
+++
b/playground/frontend/playground_components/test/src/repositories/example_repository_test.dart
@@ -46,31 +46,36 @@ void main() {
await repo.getPrecompiledObjects(kGetPrecompiledObjectsRequest),
kGetPrecompiledObjectsResponse.categories,
);
-
verify(client.getPrecompiledObjects(kGetPrecompiledObjectsRequest)).called(1);
+ verify(client.getPrecompiledObjects(kGetPrecompiledObjectsRequest))
+ .called(1);
},
);
test(
'Example repository getDefaultExample should return defaultExample for
chosen Sdk',
() async {
-
when(client.getDefaultPrecompiledObject(kGetDefaultPrecompiledObjectRequest))
+ when(client
+
.getDefaultPrecompiledObject(kGetDefaultPrecompiledObjectRequest))
.thenAnswer((_) async => kGetDefaultPrecompiledObjectResponse);
expect(
- await
repo.getDefaultPrecompiledObject(kGetDefaultPrecompiledObjectRequest),
+ await repo
+ .getDefaultPrecompiledObject(kGetDefaultPrecompiledObjectRequest),
kGetDefaultPrecompiledObjectResponse.example,
);
-
verify(client.getDefaultPrecompiledObject(kGetDefaultPrecompiledObjectRequest)).called(1);
+ verify(client
+
.getDefaultPrecompiledObject(kGetDefaultPrecompiledObjectRequest))
+ .called(1);
},
);
test(
- 'Example repository getExampleSource should return source code for
example',
+ 'Example repository getExampleSource should return files for example',
() async {
when(client.getPrecompiledObjectCode(kRequestForExampleInfo))
.thenAnswer((_) async => kGetPrecompiledObjectCodeResponse);
expect(
await repo.getPrecompiledObjectCode(kRequestForExampleInfo),
- kGetPrecompiledObjectCodeResponse.code,
+ kGetPrecompiledObjectCodeResponse.files,
);
verify(client.getPrecompiledObjectCode(kRequestForExampleInfo)).called(1);
},
@@ -85,7 +90,8 @@ void main() {
await repo.getPrecompiledObjectOutput(kRequestForExampleInfo),
kOutputResponse.output,
);
-
verify(client.getPrecompiledObjectOutput(kRequestForExampleInfo)).called(1);
+ verify(client.getPrecompiledObjectOutput(kRequestForExampleInfo))
+ .called(1);
},
);
@@ -111,13 +117,14 @@ void main() {
await repo.getPrecompiledObjectGraph(kRequestForExampleInfo),
kOutputResponse.output,
);
-
verify(client.getPrecompiledObjectGraph(kRequestForExampleInfo)).called(1);
+ verify(client.getPrecompiledObjectGraph(kRequestForExampleInfo))
+ .called(1);
},
);
test(
'Example repository getExample should return ExampleModel',
- () async {
+ () async {
when(client.getPrecompiledObject(kRequestForExampleInfo))
.thenAnswer((_) async => kGetDefaultPrecompiledObjectResponse);
expect(
diff --git a/playground/frontend/playground_components_dev/pubspec.yaml
b/playground/frontend/playground_components_dev/pubspec.yaml
index a4998c7c1bf..0630dce0633 100644
--- a/playground/frontend/playground_components_dev/pubspec.yaml
+++ b/playground/frontend/playground_components_dev/pubspec.yaml
@@ -26,7 +26,7 @@ environment:
dependencies:
flutter: { sdk: flutter }
- flutter_code_editor: ^0.2.4
+ flutter_code_editor: ^0.2.5
flutter_test: { sdk: flutter }
highlight: ^0.7.0
http: ^0.13.5
diff --git a/playground/frontend/pubspec.lock b/playground/frontend/pubspec.lock
index db8ae40f111..c5d387dc1ec 100644
--- a/playground/frontend/pubspec.lock
+++ b/playground/frontend/pubspec.lock
@@ -292,7 +292,7 @@ packages:
name: flutter_code_editor
url: "https://pub.dartlang.org"
source: hosted
- version: "0.2.4"
+ version: "0.2.5"
flutter_driver:
dependency: transitive
description: flutter
@@ -491,6 +491,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.7.0"
+ keyed_collection_widgets:
+ dependency: transitive
+ description:
+ name: keyed_collection_widgets
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.4.3"
linked_scroll_controller:
dependency: transitive
description:
diff --git a/playground/frontend/pubspec.yaml b/playground/frontend/pubspec.yaml
index 10b92e26371..3c65a5aab12 100644
--- a/playground/frontend/pubspec.yaml
+++ b/playground/frontend/pubspec.yaml
@@ -53,7 +53,7 @@ dependencies:
dev_dependencies:
build_runner: ^2.1.4
fake_async: ^1.3.0
- flutter_code_editor: ^0.2.4
+ flutter_code_editor: ^0.2.5
flutter_lints: ^2.0.1
flutter_test: { sdk: flutter }
integration_test: { sdk: flutter }
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 02ac67dcd44..45169f4d224 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
@@ -21,7 +21,8 @@ import
'package:playground/modules/examples/models/example_loading_descriptors/e
import 'package:playground/modules/messages/models/set_content_message.dart';
import 'package:playground_components/playground_components.dart';
-const _content = 'my_code';
+const _content1 = 'my_code1';
+const _content2 = 'my_code2';
const _sdk = Sdk.python;
void main() {
@@ -94,19 +95,26 @@ void main() {
[],
{'type': 'any-other'},
{
- 'content': _content,
+ 'files': [
+ {'content': _content1, 'isMain': false, 'name': '1'},
+ {'content': _content2, 'isMain': true, 'name': '2'},
+ ],
'name': 'name',
'sdk': _sdk.id,
'complexity': 'basic',
},
{
- 'content': _content,
+ 'files': [
+ {'content': _content1, 'isMain': false, 'name': '1'}
+ ],
'name': null,
'sdk': _sdk.id,
'complexity': 'medium',
},
{
- 'content': _content,
+ 'files': [
+ {'content': _content1, 'isMain': false, 'name': '1'}
+ ],
'sdk': _sdk.id,
'complexity': 'advanced',
},
@@ -122,22 +130,29 @@ void main() {
descriptor: ExamplesLoadingDescriptor(
descriptors: const [
ContentExampleLoadingDescriptor(
- content: _content,
+ complexity: Complexity.basic,
+ files: [
+ SnippetFile(content: _content1, isMain: false, name: '1'),
+ SnippetFile(content: _content2, isMain: true, name: '2'),
+ ],
name: 'name',
sdk: _sdk,
- complexity: Complexity.basic,
),
ContentExampleLoadingDescriptor(
- content: _content,
+ complexity: Complexity.medium,
+ files: [
+ SnippetFile(content: _content1, isMain: false, name: '1'),
+ ],
name: null,
sdk: _sdk,
- complexity: Complexity.medium,
),
ContentExampleLoadingDescriptor(
- content: _content,
+ complexity: Complexity.advanced,
+ files: [
+ SnippetFile(content: _content1, isMain: false, name: '1'),
+ ],
name: null,
sdk: _sdk,
- complexity: Complexity.advanced,
),
],
lazyLoadDescriptors: