You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by pa...@apache.org on 2022/07/07 20:58:33 UTC

[beam] branch master updated: Declarative theming, Remove duplicate PlaygroundState for embedded page, Do not re-create CodeController on language change (#22147)

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 4c2236d2bf5 Declarative theming, Remove duplicate PlaygroundState for embedded page, Do not re-create CodeController on language change (#22147)
     new ad25b8774b5 Merge pull request #22158 from Declarative theming, Remove duplicate PlaygroundState for embedded page, Do not re-create CodeController on language change (#22147)
4c2236d2bf5 is described below

commit 4c2236d2bf5712197783aa8d248a9f0e87bed269
Author: Alexey Inkin <al...@akvelon.com>
AuthorDate: Mon Jul 4 12:50:22 2022 +0400

    Declarative theming, Remove duplicate PlaygroundState for embedded page, Do not re-create CodeController on language change (#22147)
---
 .../modules/editor/components/editor_textarea.dart |  68 +++----------
 .../modules/editor/components/editor_themes.dart   |   9 +-
 .../lib/modules/output/components/output.dart      |  18 +++-
 .../frontend/lib/modules/sdk/models/sdk.dart       |  20 ++++
 .../components/embedded_editor.dart                |   2 +-
 .../embedded_page_providers.dart                   | 106 ---------------------
 .../embedded_playground_page.dart                  |   3 +-
 .../components/editor_textarea_wrapper.dart        |  29 +-----
 .../components/playground_page_body.dart           |   3 +-
 .../components/playground_page_providers.dart      |  99 +++++++++++++++----
 .../pages/playground/states/examples_state.dart    |  17 +++-
 .../pages/playground/states/playground_state.dart  |  47 ++++-----
 playground/frontend/lib/pages/routes.dart          |  20 ++--
 playground/frontend/lib/playground_app.dart        |  10 +-
 playground/frontend/pubspec.lock                   |  10 +-
 playground/frontend/pubspec.yaml                   |   5 +-
 16 files changed, 210 insertions(+), 256 deletions(-)

diff --git a/playground/frontend/lib/modules/editor/components/editor_textarea.dart b/playground/frontend/lib/modules/editor/components/editor_textarea.dart
index dfaf0eb0943..d3e7d3b6bda 100644
--- a/playground/frontend/lib/modules/editor/components/editor_textarea.dart
+++ b/playground/frontend/lib/modules/editor/components/editor_textarea.dart
@@ -19,17 +19,11 @@
 import 'package:code_text_field/code_text_field.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-import 'package:highlight/languages/go.dart';
-import 'package:highlight/languages/java.dart';
-import 'package:highlight/languages/python.dart';
-import 'package:highlight/languages/scala.dart';
 import 'package:playground/config/theme.dart';
 import 'package:playground/constants/fonts.dart';
 import 'package:playground/constants/sizes.dart';
-import 'package:playground/modules/editor/components/editor_themes.dart';
 import 'package:playground/modules/examples/models/example_model.dart';
 import 'package:playground/modules/sdk/models/sdk.dart';
-import 'package:provider/provider.dart';
 
 const kJavaRegExp = r'import\s[A-z.0-9]*\;\n\n[(\/\*\*)|(public)|(class)]';
 const kPythonRegExp = r'[^\S\r\n](import|as)[^\S\r\n][A-z]*\n\n';
@@ -41,18 +35,18 @@ const kGoRegExp = r'[^\S\r\n]+\'
 const kAdditionalLinesForScrolling = 4;
 
 class EditorTextArea extends StatefulWidget {
+  final CodeController codeController;
   final SDK sdk;
   final ExampleModel? example;
   final bool enabled;
-  final void Function(String)? onSourceChange;
   final bool isEditable;
   final bool isEmbedded;
 
   const EditorTextArea({
     Key? key,
+    required this.codeController,
     required this.sdk,
     this.example,
-    this.onSourceChange,
     required this.enabled,
     required this.isEditable,
     this.isEmbedded = false,
@@ -63,37 +57,12 @@ class EditorTextArea extends StatefulWidget {
 }
 
 class _EditorTextAreaState extends State<EditorTextArea> {
-  CodeController? _codeController;
   var focusNode = FocusNode();
   final GlobalKey codeFieldKey = LabeledGlobalKey('CodeFieldKey');
 
-  @override
-  void initState() {
-    super.initState();
-  }
-
-  @override
-  void didChangeDependencies() {
-    final themeProvider = Provider.of<ThemeProvider>(context, listen: true);
-    _codeController = CodeController(
-      text: _codeController?.text ?? widget.example?.source ?? '',
-      language: _getLanguageFromSdk(),
-      theme: themeProvider.isDarkMode ? kDarkCodeTheme : kLightCodeTheme,
-      onChange: (newSource) {
-        if (widget.onSourceChange != null) {
-          widget.onSourceChange!(newSource);
-        }
-      },
-      webSpaceFix: false,
-    );
-
-    super.didChangeDependencies();
-  }
-
   @override
   void dispose() {
     super.dispose();
-    _codeController?.dispose();
     focusNode.dispose();
   }
 
@@ -116,7 +85,7 @@ class _EditorTextAreaState extends State<EditorTextArea> {
           key: codeFieldKey,
           focusNode: focusNode,
           enabled: widget.enabled,
-          controller: _codeController!,
+          controller: widget.codeController,
           textStyle: getCodeFontStyle(
             textStyle: const TextStyle(fontSize: kCodeFontSize),
           ),
@@ -131,10 +100,10 @@ class _EditorTextAreaState extends State<EditorTextArea> {
     );
   }
 
-  _setTextScrolling() {
+  void _setTextScrolling() {
     focusNode.requestFocus();
-    if (_codeController!.text.isNotEmpty) {
-      _codeController!.selection = TextSelection.fromPosition(
+    if (widget.codeController.text.isNotEmpty) {
+      widget.codeController.selection = TextSelection.fromPosition(
         TextPosition(
           offset: _getOffset(),
         ),
@@ -146,10 +115,10 @@ class _EditorTextAreaState extends State<EditorTextArea> {
     int contextLine = _getIndexOfContextLine();
     String pattern = _getPattern(_getQntOfStringsOnScreen());
     if (pattern == '' || pattern == '}') {
-      return _codeController!.text.lastIndexOf(pattern);
+      return widget.codeController.text.lastIndexOf(pattern);
     }
 
-    return _codeController!.text.indexOf(
+    return widget.codeController.text.indexOf(
       pattern,
       contextLine,
     );
@@ -158,7 +127,7 @@ class _EditorTextAreaState extends State<EditorTextArea> {
   String _getPattern(int qntOfStrings) {
     int contextLineIndex = _getIndexOfContextLine();
     List<String> stringsAfterContextLine =
-        _codeController!.text.substring(contextLineIndex).split('\n');
+        widget.codeController.text.substring(contextLineIndex).split('\n');
 
     String result =
         stringsAfterContextLine.length + kAdditionalLinesForScrolling >
@@ -179,14 +148,14 @@ class _EditorTextAreaState extends State<EditorTextArea> {
 
   int _getIndexOfContextLine() {
     int ctxLineNumber = widget.example!.contextLine;
-    String contextLine = _codeController!.text.split('\n')[ctxLineNumber];
+    String contextLine = widget.codeController.text.split('\n')[ctxLineNumber];
 
     while (contextLine == '') {
       ctxLineNumber -= 1;
-      contextLine = _codeController!.text.split('\n')[ctxLineNumber];
+      contextLine = widget.codeController.text.split('\n')[ctxLineNumber];
     }
 
-    return _codeController!.text.indexOf(contextLine);
+    return widget.codeController.text.indexOf(contextLine);
   }
 
   // This function made for more accuracy in the process of finding an exact line.
@@ -207,17 +176,4 @@ class _EditorTextAreaState extends State<EditorTextArea> {
 
     return result.toString();
   }
-
-  _getLanguageFromSdk() {
-    switch (widget.sdk) {
-      case SDK.java:
-        return java;
-      case SDK.go:
-        return go;
-      case SDK.python:
-        return python;
-      case SDK.scio:
-        return scala;
-    }
-  }
 }
diff --git a/playground/frontend/lib/modules/editor/components/editor_themes.dart b/playground/frontend/lib/modules/editor/components/editor_themes.dart
index c26df0636d1..e29f1d6bfe4 100644
--- a/playground/frontend/lib/modules/editor/components/editor_themes.dart
+++ b/playground/frontend/lib/modules/editor/components/editor_themes.dart
@@ -16,10 +16,17 @@
  * limitations under the License.
  */
 
+import 'package:code_text_field/code_text_field.dart';
 import 'package:flutter/material.dart';
 import 'package:playground/config/theme.dart';
 
-Map<String, TextStyle> createTheme(ThemeColors colors) {
+CodeThemeData createTheme(ThemeColors colors) {
+  return CodeThemeData(
+    styles: _createThemeStyles(colors),
+  );
+}
+
+Map<String, TextStyle> _createThemeStyles(ThemeColors colors) {
   return {
     'root': TextStyle(
       backgroundColor: colors.primaryBackground,
diff --git a/playground/frontend/lib/modules/output/components/output.dart b/playground/frontend/lib/modules/output/components/output.dart
index 887a5da3a89..1f6dac63837 100644
--- a/playground/frontend/lib/modules/output/components/output.dart
+++ b/playground/frontend/lib/modules/output/components/output.dart
@@ -19,6 +19,7 @@
 import 'package:flutter/material.dart';
 import 'package:playground/modules/output/components/output_area.dart';
 import 'package:playground/modules/output/components/output_header/output_header.dart';
+import 'package:playground/pages/playground/states/playground_state.dart';
 
 const kTabsCount = 2;
 
@@ -26,8 +27,15 @@ class Output extends StatefulWidget {
   final bool isEmbedded;
   final bool showGraph;
 
-  const Output({Key? key, required this.isEmbedded, required this.showGraph})
-      : super(key: key);
+  Output({
+    required this.isEmbedded,
+    required PlaygroundState playgroundState,
+  })  : showGraph = playgroundState.graphAvailable,
+        super(
+          key: ValueKey(
+            '${playgroundState.sdk}_${playgroundState.selectedExample?.path}',
+          ),
+        );
 
   @override
   State<Output> createState() => _OutputState();
@@ -41,18 +49,18 @@ class _OutputState extends State<Output> with SingleTickerProviderStateMixin {
   void initState() {
     final tabsCount = widget.showGraph ? kTabsCount : kTabsCount - 1;
     tabController = TabController(vsync: this, length: tabsCount);
-    tabController.addListener(onTabChange);
+    tabController.addListener(_onTabChange);
     super.initState();
   }
 
   @override
   void dispose() {
-    tabController.removeListener(onTabChange);
+    tabController.removeListener(_onTabChange);
     tabController.dispose();
     super.dispose();
   }
 
-  onTabChange() {
+  void _onTabChange() {
     setState(() {
       selectedTab = tabController.index;
     });
diff --git a/playground/frontend/lib/modules/sdk/models/sdk.dart b/playground/frontend/lib/modules/sdk/models/sdk.dart
index 392fb07d988..17283fcb0b1 100644
--- a/playground/frontend/lib/modules/sdk/models/sdk.dart
+++ b/playground/frontend/lib/modules/sdk/models/sdk.dart
@@ -16,6 +16,11 @@
  * limitations under the License.
  */
 
+import 'package:highlight/highlight.dart';
+import 'package:highlight/languages/go.dart';
+import 'package:highlight/languages/java.dart';
+import 'package:highlight/languages/python.dart';
+import 'package:highlight/languages/scala.dart';
 import 'package:playground/config.g.dart';
 
 enum SDK {
@@ -56,3 +61,18 @@ extension SdkToRoute on SDK {
     }
   }
 }
+
+extension SdkToHighlightMode on SDK {
+  Mode get highlightMode {
+    switch (this) {
+      case SDK.java:
+        return java;
+      case SDK.go:
+        return go;
+      case SDK.python:
+        return python;
+      case SDK.scio:
+        return scala;
+    }
+  }
+}
diff --git a/playground/frontend/lib/pages/embedded_playground/components/embedded_editor.dart b/playground/frontend/lib/pages/embedded_playground/components/embedded_editor.dart
index 094a12fe618..fdaa8679f7c 100644
--- a/playground/frontend/lib/pages/embedded_playground/components/embedded_editor.dart
+++ b/playground/frontend/lib/pages/embedded_playground/components/embedded_editor.dart
@@ -30,11 +30,11 @@ class EmbeddedEditor extends StatelessWidget {
   Widget build(BuildContext context) {
     final state = Provider.of<PlaygroundState>(context);
     return EditorTextArea(
+      codeController: state.codeController,
       key: ValueKey(state.selectedExample),
       enabled: true,
       sdk: state.sdk,
       example: state.selectedExample,
-      onSourceChange: state.setSource,
       isEditable: isEditable,
       isEmbedded: true,
     );
diff --git a/playground/frontend/lib/pages/embedded_playground/embedded_page_providers.dart b/playground/frontend/lib/pages/embedded_playground/embedded_page_providers.dart
deleted file mode 100644
index dbf497632e5..00000000000
--- a/playground/frontend/lib/pages/embedded_playground/embedded_page_providers.dart
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import 'package:flutter/material.dart';
-import 'package:playground/constants/params.dart';
-import 'package:playground/modules/examples/models/example_model.dart';
-import 'package:playground/modules/output/models/output_placement_state.dart';
-import 'package:playground/pages/embedded_playground/embedded_playground_page.dart';
-import 'package:playground/pages/playground/components/playground_page_providers.dart';
-import 'package:playground/pages/playground/states/examples_state.dart';
-import 'package:playground/pages/playground/states/playground_state.dart';
-import 'package:provider/provider.dart';
-
-class EmbeddedPageProviders extends StatelessWidget {
-  const EmbeddedPageProviders({Key? key}) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return MultiProvider(
-      providers: [
-        ChangeNotifierProvider<ExampleState>(
-          create: (context) => ExampleState(kExampleRepository),
-        ),
-        ChangeNotifierProxyProvider<ExampleState, PlaygroundState>(
-          create: (context) => PlaygroundState(codeRepository: kCodeRepository),
-          update: (context, exampleState, playground) {
-            if (playground == null) {
-              return PlaygroundState(codeRepository: kCodeRepository);
-            }
-
-            if (playground.selectedExample == null) {
-              final example = _getEmbeddedExample();
-              _loadExampleData(
-                example,
-                exampleState,
-                playground,
-              );
-            }
-            return playground;
-          },
-        ),
-        ChangeNotifierProvider<OutputPlacementState>(
-          create: (context) => OutputPlacementState(),
-        ),
-      ],
-      child: EmbeddedPlaygroundPage(
-        isEditable: _isEditableToBool(),
-      ),
-    );
-  }
-
-  bool _isEditableToBool() {
-    final isEditableString = Uri.base.queryParameters[kIsEditable];
-    return isEditableString == 'true';
-  }
-
-  ExampleModel _getEmbeddedExample() {
-    final examplePath = Uri.base.queryParameters[kExampleParam];
-
-    return ExampleModel(
-      name: 'Embedded_Example',
-      path: examplePath ?? '',
-      description: '',
-      type: ExampleType.example,
-    );
-  }
-
-  _loadExampleData(
-    ExampleModel? example,
-    ExampleState exampleState,
-    PlaygroundState playground,
-  ) {
-    if (example == null) {
-      return;
-    }
-
-    if (example.path.isEmpty) {
-      String source = Uri.base.queryParameters[kSourceCode] ?? '';
-      example.setSource(source);
-      playground.setExample(example);
-    } else {
-      exampleState
-          .getExample(example.path, playground.sdk)
-          .then((example) => exampleState.loadExampleInfo(
-                example,
-                playground.sdk,
-              ))
-          .then((exampleWithInfo) => playground.setExample(exampleWithInfo));
-    }
-  }
-}
diff --git a/playground/frontend/lib/pages/embedded_playground/embedded_playground_page.dart b/playground/frontend/lib/pages/embedded_playground/embedded_playground_page.dart
index c2223cd16f8..8d4c41454f3 100644
--- a/playground/frontend/lib/pages/embedded_playground/embedded_playground_page.dart
+++ b/playground/frontend/lib/pages/embedded_playground/embedded_playground_page.dart
@@ -51,8 +51,7 @@ class EmbeddedPlaygroundPage extends StatelessWidget {
             color: Theme.of(context).backgroundColor,
             child: Output(
               isEmbedded: true,
-              showGraph: state.graphAvailable,
-              key: ValueKey(state.selectedExample?.path ?? state.sdk.toString())
+              playgroundState: state,
             ),
           ),
         ),
diff --git a/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart b/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart
index 95a02e5940c..a5c2115fed0 100644
--- a/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart
+++ b/playground/frontend/lib/pages/playground/components/editor_textarea_wrapper.dart
@@ -24,9 +24,7 @@ import 'package:playground/modules/editor/components/editor_textarea.dart';
 import 'package:playground/modules/editor/components/run_button.dart';
 import 'package:playground/modules/examples/components/description_popover/description_popover_button.dart';
 import 'package:playground/modules/examples/components/multifile_popover/multifile_popover_button.dart';
-import 'package:playground/modules/examples/models/example_model.dart';
 import 'package:playground/modules/notifications/components/notification.dart';
-import 'package:playground/modules/sdk/models/sdk.dart';
 import 'package:playground/pages/playground/states/playground_state.dart';
 import 'package:playground/utils/analytics_utils.dart';
 import 'package:provider/provider.dart';
@@ -43,21 +41,16 @@ class CodeTextAreaWrapper extends StatelessWidget {
         });
       }
       return Column(
-        key: ValueKey(EditorKeyObject(
-          state.sdk,
-          state.selectedExample,
-          state.resetKey,
-        )),
         children: [
           Expanded(
             child: Stack(
               children: [
                 Positioned.fill(
                   child: EditorTextArea(
+                    codeController: state.codeController,
                     enabled: !(state.selectedExample?.isMultiFile ?? false),
                     example: state.selectedExample,
                     sdk: state.sdk,
-                    onSourceChange: state.setSource,
                     isEditable: true,
                   ),
                 ),
@@ -141,23 +134,3 @@ class CodeTextAreaWrapper extends StatelessWidget {
     state.resetError();
   }
 }
-
-class EditorKeyObject {
-  final SDK sdk;
-  final ExampleModel? example;
-  final DateTime? resetKey;
-
-  const EditorKeyObject(this.sdk, this.example, this.resetKey);
-
-  @override
-  bool operator ==(Object other) =>
-      identical(this, other) ||
-      other is EditorKeyObject &&
-          runtimeType == other.runtimeType &&
-          sdk == other.sdk &&
-          example == other.example &&
-          resetKey == other.resetKey;
-
-  @override
-  int get hashCode => hashValues(sdk, example, resetKey);
-}
diff --git a/playground/frontend/lib/pages/playground/components/playground_page_body.dart b/playground/frontend/lib/pages/playground/components/playground_page_body.dart
index f7237fc9432..3b005d2a979 100644
--- a/playground/frontend/lib/pages/playground/components/playground_page_body.dart
+++ b/playground/frontend/lib/pages/playground/components/playground_page_body.dart
@@ -65,8 +65,7 @@ class PlaygroundPageBody extends StatelessWidget {
 
   Widget createOutput(PlaygroundState state) => Output(
         isEmbedded: false,
-        showGraph: state.graphAvailable,
-        key: ValueKey(state.selectedExample?.path ?? state.sdk.toString()),
+        playgroundState: state,
       );
 
   Widget getVerticalSeparator(BuildContext context) => Container(
diff --git a/playground/frontend/lib/pages/playground/components/playground_page_providers.dart b/playground/frontend/lib/pages/playground/components/playground_page_providers.dart
index b0a2bea1a97..beeb95e184b 100644
--- a/playground/frontend/lib/pages/playground/components/playground_page_providers.dart
+++ b/playground/frontend/lib/pages/playground/components/playground_page_providers.dart
@@ -58,19 +58,7 @@ class PlaygroundPageProviders extends StatelessWidget {
               return PlaygroundState(codeRepository: kCodeRepository);
             }
 
-            if (playground.selectedExample == null &&
-                !Uri.base.toString().contains(kIsEmbedded)) {
-              final example = _getExample(exampleState, playground);
-              if (example != null) {
-                exampleState
-                    .loadExampleInfo(
-                      example,
-                      playground.sdk,
-                    )
-                    .then((exampleWithInfo) =>
-                        playground.setExample(exampleWithInfo));
-              }
-            }
+            _onExampleStateChanged(exampleState, playground);
             return playground;
           },
         ),
@@ -85,16 +73,93 @@ class PlaygroundPageProviders extends StatelessWidget {
     );
   }
 
-  ExampleModel? _getExample(
+  void _onExampleStateChanged(
     ExampleState exampleState,
-    PlaygroundState playground,
+    PlaygroundState playgroundState,
   ) {
+    // This property currently doubles as a flag of initialization
+    // because it is initialized when an example is ready
+    // and is filled with a null-object if not showing any example.
+    //
+    // TODO: Add a dedicated flag of initialization or make
+    //       PlaygroundState listen for examples and init itself.
+    if (playgroundState.selectedExample != null) {
+      return; // Already initialized.
+    }
+
+    if (_isEmbedded()) {
+      _initEmbedded(exampleState, playgroundState);
+    } else {
+      _initNonEmbedded(exampleState, playgroundState);
+    }
+  }
+
+  bool _isEmbedded() {
+    return Uri.base.toString().contains(kIsEmbedded);
+  }
+
+  Future<void> _initEmbedded(
+    ExampleState exampleState,
+    PlaygroundState playgroundState,
+  ) async {
+    final example = _getEmbeddedExample();
+
+    if (example.path.isEmpty) {
+      String source = Uri.base.queryParameters[kSourceCode] ?? '';
+      example.setSource(source);
+      playgroundState.setExample(example);
+    } else {
+      final loadedExample = await exampleState.getExample(
+        example.path,
+        playgroundState.sdk,
+      );
+
+      final exampleWithInfo = await exampleState.loadExampleInfo(
+        loadedExample,
+        playgroundState.sdk,
+      );
+
+      playgroundState.setExample(exampleWithInfo);
+    }
+  }
+
+  ExampleModel _getEmbeddedExample() {
     final examplePath = Uri.base.queryParameters[kExampleParam];
 
-    if (exampleState.defaultExamplesMap.isEmpty) {
-      exampleState.loadDefaultExamples();
+    return ExampleModel(
+      name: 'Embedded_Example',
+      path: examplePath ?? '',
+      description: '',
+      type: ExampleType.example,
+    );
+  }
+
+  Future<void> _initNonEmbedded(
+    ExampleState exampleState,
+    PlaygroundState playgroundState,
+  ) async {
+    await exampleState.loadDefaultExamplesIfNot();
+
+    final example = await _getExample(exampleState, playgroundState);
+
+    if (example == null) {
+      return;
     }
 
+    final exampleWithInfo = await exampleState.loadExampleInfo(
+      example,
+      playgroundState.sdk,
+    );
+
+    playgroundState.setExample(exampleWithInfo);
+  }
+
+  Future<ExampleModel?> _getExample(
+    ExampleState exampleState,
+    PlaygroundState playground,
+  ) async {
+    final examplePath = Uri.base.queryParameters[kExampleParam];
+
     if (examplePath?.isEmpty ?? true) {
       return exampleState.defaultExamplesMap[playground.sdk];
     }
diff --git a/playground/frontend/lib/pages/playground/states/examples_state.dart b/playground/frontend/lib/pages/playground/states/examples_state.dart
index 256224ad54b..b080e361566 100644
--- a/playground/frontend/lib/pages/playground/states/examples_state.dart
+++ b/playground/frontend/lib/pages/playground/states/examples_state.dart
@@ -124,7 +124,7 @@ class ExampleState with ChangeNotifier {
     notifyListeners();
   }
 
-  loadDefaultExamples() async {
+  Future<void> loadDefaultExamples() async {
     if (defaultExamplesMap.isNotEmpty) {
       return;
     }
@@ -144,10 +144,23 @@ class ExampleState with ChangeNotifier {
     }
 
     defaultExamplesMap.addEntries(defaultExamples);
+    final futures = <Future<void>>[];
+
     for (var entry in defaultExamplesMap.entries) {
-      loadExampleInfo(entry.value, entry.key)
+      final exampleFuture = loadExampleInfo(entry.value, entry.key)
           .then((value) => defaultExamplesMap[entry.key] = value);
+      futures.add(exampleFuture);
     }
     notifyListeners();
+
+    await Future.wait(futures);
+  }
+
+  Future<void> loadDefaultExamplesIfNot() async {
+    if (defaultExamplesMap.isNotEmpty) {
+      return;
+    }
+
+    await loadDefaultExamples();
   }
 }
diff --git a/playground/frontend/lib/pages/playground/states/playground_state.dart b/playground/frontend/lib/pages/playground/states/playground_state.dart
index 4923c756d4c..2340de7b8f8 100644
--- a/playground/frontend/lib/pages/playground/states/playground_state.dart
+++ b/playground/frontend/lib/pages/playground/states/playground_state.dart
@@ -19,6 +19,7 @@
 import 'dart:async';
 import 'dart:math';
 
+import 'package:code_text_field/code_text_field.dart';
 import 'package:flutter/material.dart';
 import 'package:playground/modules/editor/parsers/run_options_parser.dart';
 import 'package:playground/modules/editor/repository/code_repository/code_repository.dart';
@@ -39,14 +40,13 @@ const kCachedResultsLog =
     'The results of this example are taken from the Apache Beam Playground cache.\n';
 
 class PlaygroundState with ChangeNotifier {
-  late SDK _sdk;
+  final CodeController codeController;
+  SDK _sdk;
   CodeRepository? _codeRepository;
   ExampleModel? _selectedExample;
-  String _source = '';
   RunCodeResult? _result;
   StreamSubscription<RunCodeResult>? _runSubscription;
   String _pipelineOptions = '';
-  DateTime? resetKey;
   StreamController<int>? _executionTime;
   OutputType? selectedOutputFilterType;
   String? outputResult;
@@ -55,11 +55,14 @@ class PlaygroundState with ChangeNotifier {
     SDK sdk = SDK.java,
     ExampleModel? selectedExample,
     CodeRepository? codeRepository,
-  }) {
+  })  : _sdk = sdk,
+        codeController = CodeController(
+          language: sdk.highlightMode,
+          webSpaceFix: false,
+        ) {
     _selectedExample = selectedExample;
     _pipelineOptions = selectedExample?.pipelineOptions ?? '';
-    _sdk = sdk;
-    _source = _selectedExample?.source ?? '';
+    codeController.text = _selectedExample?.source ?? '';
     _codeRepository = codeRepository;
     selectedOutputFilterType = OutputType.all;
     outputResult = '';
@@ -74,7 +77,7 @@ class PlaygroundState with ChangeNotifier {
 
   SDK get sdk => _sdk;
 
-  String get source => _source;
+  String get source => codeController.rawText;
 
   bool get isCodeRunning => !(result?.isFinished ?? true);
 
@@ -96,50 +99,50 @@ class PlaygroundState with ChangeNotifier {
       selectedExample?.type != ExampleType.test &&
       [SDK.java, SDK.python].contains(sdk);
 
-  setExample(ExampleModel example) {
+  void setExample(ExampleModel example) {
     _selectedExample = example;
     _pipelineOptions = example.pipelineOptions ?? '';
-    _source = example.source ?? '';
+    codeController.text = example.source ?? '';
     _result = null;
     _executionTime = null;
     setOutputResult('');
     notifyListeners();
   }
 
-  setSdk(SDK sdk) {
+  void setSdk(SDK sdk) {
     _sdk = sdk;
+    codeController.language = sdk.highlightMode;
     notifyListeners();
   }
 
-  setSource(String source) {
-    _source = source;
+  void setSource(String source) {
+    codeController.text = source;
   }
 
-  setSelectedOutputFilterType(OutputType type) {
+  void setSelectedOutputFilterType(OutputType type) {
     selectedOutputFilterType = type;
     notifyListeners();
   }
 
-  setOutputResult(String outputs) {
+  void setOutputResult(String outputs) {
     outputResult = outputs;
     notifyListeners();
   }
 
-  clearOutput() {
+  void clearOutput() {
     _result = null;
     notifyListeners();
   }
 
-  reset() {
-    _source = _selectedExample?.source ?? '';
+  void reset() {
+    codeController.text = _selectedExample?.source ?? '';
     _pipelineOptions = selectedExample?.pipelineOptions ?? '';
-    resetKey = DateTime.now();
     _executionTime = null;
     setOutputResult('');
     notifyListeners();
   }
 
-  resetError() {
+  void resetError() {
     if (result == null) {
       return;
     }
@@ -147,7 +150,7 @@ class PlaygroundState with ChangeNotifier {
     notifyListeners();
   }
 
-  setPipelineOptions(String options) {
+  void setPipelineOptions(String options) {
     _pipelineOptions = options;
     notifyListeners();
   }
@@ -207,7 +210,7 @@ class PlaygroundState with ChangeNotifier {
     notifyListeners();
   }
 
-  _showPrecompiledResult() async {
+  Future<void> _showPrecompiledResult() async {
     _result = RunCodeResult(
       status: RunCodeStatus.preparation,
     );
@@ -254,7 +257,7 @@ class PlaygroundState with ChangeNotifier {
     return streamController;
   }
 
-  filterOutput(OutputType type) {
+  void filterOutput(OutputType type) {
     var output = result?.output ?? '';
     var log = result?.log ?? '';
 
diff --git a/playground/frontend/lib/pages/routes.dart b/playground/frontend/lib/pages/routes.dart
index 34c59a66ea1..274368b7a8c 100644
--- a/playground/frontend/lib/pages/routes.dart
+++ b/playground/frontend/lib/pages/routes.dart
@@ -17,11 +17,11 @@
  */
 
 import 'package:flutter/material.dart';
-import 'package:playground/pages/embedded_playground/embedded_page_providers.dart';
+import 'package:playground/constants/params.dart';
+import 'package:playground/pages/embedded_playground/embedded_playground_page.dart';
 import 'package:playground/pages/playground/playground_page.dart';
 
 class Routes {
-  static const String playground = '/';
   static const String embedded = '/embedded';
 
   static Route<dynamic> generateRoute(RouteSettings settings) {
@@ -29,17 +29,23 @@ class Routes {
     final queryIndex = name.indexOf('?');
     final routePath =
         name.substring(0, queryIndex < 0 ? name.length : queryIndex);
+
     switch (routePath) {
-      case Routes.playground:
-        return Routes.renderRoute(const PlaygroundPage());
       case Routes.embedded:
-        return Routes.renderRoute(const EmbeddedPageProviders());
+        final isEditable = Uri.base.queryParameters[kIsEditable] == 'true';
+
+        return _renderRoute(
+          EmbeddedPlaygroundPage(
+            isEditable: isEditable,
+          ),
+        );
+
       default:
-        return Routes.renderRoute(const PlaygroundPage());
+        return _renderRoute(const PlaygroundPage());
     }
   }
 
-  static renderRoute(Widget widget) {
+  static _renderRoute(Widget widget) {
     return MaterialPageRoute(builder: (context) => widget);
   }
 }
diff --git a/playground/frontend/lib/playground_app.dart b/playground/frontend/lib/playground_app.dart
index 7a7ce8e3b9d..4fbde3ff576 100644
--- a/playground/frontend/lib/playground_app.dart
+++ b/playground/frontend/lib/playground_app.dart
@@ -16,12 +16,14 @@
  * limitations under the License.
  */
 
+import 'package:code_text_field/code_text_field.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:flutter_localizations/flutter_localizations.dart';
 import 'package:playground/config/locale.dart';
 import 'package:playground/config/theme.dart';
 import 'package:playground/l10n/l10n.dart';
+import 'package:playground/modules/editor/components/editor_themes.dart';
 import 'package:playground/pages/playground/components/playground_page_providers.dart';
 import 'package:playground/pages/playground/playground_page.dart';
 import 'package:playground/pages/routes.dart';
@@ -36,7 +38,9 @@ class PlaygroundApp extends StatelessWidget {
       create: (context) => ThemeProvider()..init(),
       builder: (context, _) {
         final themeProvider = Provider.of<ThemeProvider>(context);
-        return ChangeNotifierProvider<LocaleProvider>(
+        return CodeTheme(
+          data: themeProvider.isDarkMode ? kDarkCodeTheme : kLightCodeTheme,
+          child: ChangeNotifierProvider<LocaleProvider>(
             create: (context) => LocaleProvider(),
             builder: (context, state) {
               final localeProvider = Provider.of<LocaleProvider>(context);
@@ -58,7 +62,9 @@ class PlaygroundApp extends StatelessWidget {
                   ],
                 ),
               );
-            });
+            },
+          ),
+        );
       },
     );
   }
diff --git a/playground/frontend/pubspec.lock b/playground/frontend/pubspec.lock
index 5c6ec414b16..48bf1b10966 100644
--- a/playground/frontend/pubspec.lock
+++ b/playground/frontend/pubspec.lock
@@ -144,10 +144,12 @@ packages:
   code_text_field:
     dependency: "direct main"
     description:
-      name: code_text_field
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "1.0.2"
+      path: "."
+      ref: "9e2c9fe52a69481f038f4b6609e8a0a776429437"
+      resolved-ref: "9e2c9fe52a69481f038f4b6609e8a0a776429437"
+      url: "https://github.com/BertrandBev/code_field.git"
+    source: git
+    version: "1.0.3"
   collection:
     dependency: "direct main"
     description:
diff --git a/playground/frontend/pubspec.yaml b/playground/frontend/pubspec.yaml
index b3f304f28ce..758f0f0d100 100644
--- a/playground/frontend/pubspec.yaml
+++ b/playground/frontend/pubspec.yaml
@@ -45,7 +45,10 @@ environment:
 # versions available, run `flutter pub outdated`.
 dependencies:
   aligned_dialog: ^0.0.6
-  code_text_field: ^1.0.0
+  code_text_field:
+    git:
+      url: https://github.com/BertrandBev/code_field.git
+      ref: 9e2c9fe52a69481f038f4b6609e8a0a776429437
   collection: ^1.15.0
   expansion_widget: ^0.0.2
   flutter: