Stager scene for Property Editor (#8573)
diff --git a/.vscode/launch.json b/.vscode/launch.json index 26aabef..2f91630 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json
@@ -106,13 +106,20 @@ "name": "standalone_ui/editor_sidebar + experiments", "request": "launch", "type": "dart", - "program": "devtools_app/test/test_infra/scenes/standalone_ui/editor_sidebar.stager_app.g.dart", + "program": "packages/devtools_app/test/test_infra/scenes/standalone_ui/editor_sidebar.stager_app.g.dart", "preLaunchTask": "Start DTD on Port 8500", "args": [ "--dart-define=enable_experiments=true" ], }, { + "name": "standalone_ui/property_editor_sidebar", + "request": "launch", + "type": "dart", + "program": "packages/devtools_app/test/test_infra/scenes/standalone_ui/property_editor_sidebar.stager_app.g.dart", + "preLaunchTask": "Start DTD on Port 8500", + }, + { "name": "devtools_extensions: foo + sim", "request": "launch", "type": "dart",
diff --git a/packages/devtools_app/lib/src/shared/analytics/constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants.dart index 157eb5d..0372549 100644 --- a/packages/devtools_app/lib/src/shared/analytics/constants.dart +++ b/packages/devtools_app/lib/src/shared/analytics/constants.dart
@@ -17,6 +17,7 @@ part 'constants/_memory_constants.dart'; part 'constants/_network_constants.dart'; part 'constants/_performance_constants.dart'; +part 'constants/_property_editor_sidebar_constants.dart'; // Type of events (event_category): const screenViewEvent = 'screen'; // Active screen (tab selected).
diff --git a/packages/devtools_app/lib/src/shared/analytics/constants/_property_editor_sidebar_constants.dart b/packages/devtools_app/lib/src/shared/analytics/constants/_property_editor_sidebar_constants.dart new file mode 100644 index 0000000..0fc2ea4 --- /dev/null +++ b/packages/devtools_app/lib/src/shared/analytics/constants/_property_editor_sidebar_constants.dart
@@ -0,0 +1,18 @@ +// Copyright 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of '../constants.dart'; + +// TODO(elliette): Send the following events from the property editor. +enum PropertyEditorEvents { + /// Analytics event that is sent when the property editor is updated with new + /// properties. + widgetPropertiesUpdate, + + /// Analytics event that is sent when a user requests a property edit. + applyEditRequest; + + /// Analytics id to track events that come from the DTD editor sidebar. + static String get id => 'propertyEditorSidebar'; +}
diff --git a/packages/devtools_app/lib/src/standalone_ui/standalone_screen.dart b/packages/devtools_app/lib/src/standalone_ui/standalone_screen.dart index 18fad3f..bb5954f 100644 --- a/packages/devtools_app/lib/src/standalone_ui/standalone_screen.dart +++ b/packages/devtools_app/lib/src/standalone_ui/standalone_screen.dart
@@ -15,6 +15,8 @@ /// meaning that this screen will not be part of DevTools' normal navigation. /// The only way to access a standalone screen is directly from the url. enum StandaloneScreenType { + // TODO(elliette): Add property editor as a standalone screen, see: + // https://github.com/flutter/devtools/issues/8546 editorSidebar, vsCodeFlutterPanel; // Legacy postMessage version, shows an upgrade message.
diff --git a/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart b/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart index a661e27..def6b2a 100644 --- a/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart +++ b/packages/devtools_app/lib/src/standalone_ui/vs_code/flutter_panel.dart
@@ -12,9 +12,7 @@ import '../../service/editor/api_classes.dart'; import '../../service/editor/editor_client.dart'; import '../../shared/analytics/analytics.dart' as ga; -import '../../shared/feature_flags.dart'; import '../../shared/ui/common_widgets.dart'; -import '../ide_shared/property_editor/property_editor_sidebar.dart'; import 'debug_sessions.dart'; import 'devices.dart'; import 'devtools/devtools_view.dart'; @@ -195,9 +193,6 @@ editor: widget.editor, debugSessions: debugSessions, ), - // TODO(https://github.com/flutter/devtools/issues/8546) Move - // Property Editor to its own sidepanel. - if (FeatureFlags.propertyEditor) const PropertyEditorSidebar(), ], ), ),
diff --git a/packages/devtools_app/lib/src/standalone_ui/vs_code/property_editor_panel.dart b/packages/devtools_app/lib/src/standalone_ui/vs_code/property_editor_panel.dart new file mode 100644 index 0000000..88d165e --- /dev/null +++ b/packages/devtools_app/lib/src/standalone_ui/vs_code/property_editor_panel.dart
@@ -0,0 +1,114 @@ +// Copyright 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:devtools_app_shared/ui.dart'; +import 'package:devtools_app_shared/utils.dart'; +import 'package:dtd/dtd.dart'; +import 'package:flutter/material.dart'; + +import '../../service/editor/editor_client.dart'; +import '../../shared/analytics/analytics.dart' as ga; +import '../../shared/analytics/constants.dart'; +import '../../shared/ui/common_widgets.dart'; +import '../ide_shared/property_editor/property_editor_sidebar.dart'; + +/// The side panel for the Property Editor. +class PropertyEditorSidebarPanel extends StatefulWidget { + const PropertyEditorSidebarPanel(this.dtd, {super.key}); + + final DartToolingDaemon dtd; + + @override + State<PropertyEditorSidebarPanel> createState() => + _PropertyEditorSidebarPanelState(); +} + +class _PropertyEditorSidebarPanelState + extends State<PropertyEditorSidebarPanel> { + _PropertyEditorSidebarPanelState(); + + Future<EditorClient>? _editor; + + @override + void initState() { + super.initState(); + + final editor = EditorClient(widget.dtd); + ga.screen(PropertyEditorEvents.id); + unawaited(_editor = editor.initialized.then((_) => editor)); + } + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.topCenter, + child: FutureBuilder( + future: _editor, + builder: + (context, snapshot) => switch (( + snapshot.connectionState, + snapshot.data, + )) { + (ConnectionState.done, final editor?) => + _PropertyEditorConnectedPanel(editor), + _ => const CenteredCircularProgressIndicator(), + }, + ), + ); + } +} + +/// The property editor panel shown once we know an editor is available. +class _PropertyEditorConnectedPanel extends StatefulWidget { + const _PropertyEditorConnectedPanel(this.editor); + + final EditorClient editor; + + @override + State<_PropertyEditorConnectedPanel> createState() => + _PropertyEditorConnectedPanelState(); +} + +class _PropertyEditorConnectedPanelState + extends State<_PropertyEditorConnectedPanel> + with AutoDisposeMixin { + late final ScrollController scrollController; + + @override + void initState() { + super.initState(); + scrollController = ScrollController(); + } + + @override + void dispose() { + scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scrollbar( + controller: scrollController, + thumbVisibility: true, + child: SingleChildScrollView( + controller: scrollController, + child: const Padding( + padding: EdgeInsets.fromLTRB( + denseSpacing, + defaultSpacing, + defaultSpacing, // Additional right padding for scroll bar. + defaultSpacing, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [PropertyEditorSidebar()], + ), + ), + ), + ); + } +}
diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/editor_sidebar.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/editor_sidebar.dart index 50a7618..f0236cc 100644 --- a/packages/devtools_app/test/test_infra/scenes/standalone_ui/editor_sidebar.dart +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/editor_sidebar.dart
@@ -7,7 +7,6 @@ import 'package:devtools_app/devtools_app.dart'; import 'package:devtools_app/src/standalone_ui/vs_code/flutter_panel.dart'; import 'package:devtools_app_shared/service.dart'; -import 'package:devtools_app_shared/shared.dart'; import 'package:devtools_app_shared/ui.dart'; import 'package:devtools_app_shared/utils.dart'; import 'package:devtools_test/devtools_test.dart'; @@ -17,7 +16,8 @@ import 'editor_service/simulated_editor.dart'; import 'mock_editor_widget.dart'; -import 'utils.dart'; +import 'shared/common_ui.dart'; +import 'shared/utils.dart'; /// To run, use the "standalone_ui/editor_sidebar" launch configuration with the /// `devtools/packages/` folder open in VS Code, or run: @@ -30,17 +30,7 @@ @override Widget build(BuildContext context) { - return MaterialApp( - theme: themeFor( - isDarkTheme: false, - ideTheme: _ideTheme(const VsCodeTheme.light()), - theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme), - ), - darkTheme: themeFor( - isDarkTheme: true, - ideTheme: _ideTheme(const VsCodeTheme.dark()), - theme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme), - ), + return IdeThemedMaterialApp( home: Scaffold( body: MockEditorWidget( editor: editor, @@ -51,15 +41,6 @@ ); } - /// Creates an [IdeTheme] using the colours from the mock editor. - IdeTheme _ideTheme(VsCodeTheme vsCodeTheme) { - return IdeTheme( - backgroundColor: vsCodeTheme.editorBackgroundColor, - foregroundColor: vsCodeTheme.foregroundColor, - embedMode: EmbedMode.embedOne, - ); - } - @override String get title => '$EditorSidebarScene';
diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/mock_editor_widget.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/mock_editor_widget.dart index 2159b2d..7db0edb 100644 --- a/packages/devtools_app/test/test_infra/scenes/standalone_ui/mock_editor_widget.dart +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/mock_editor_widget.dart
@@ -12,6 +12,7 @@ import 'package:flutter/material.dart'; import 'editor_service/simulated_editor.dart'; +import 'shared/common_ui.dart'; /// A simple UI that acts as a stand-in host editor to simplify the development /// workflow when working on embedded tooling. @@ -335,41 +336,3 @@ }; } } - -/// A basic theme that matches the default colours of VS Code dart/light themes -/// so the mock environment can be displayed in either. -class VsCodeTheme { - const VsCodeTheme._({ - required this.activityBarBackgroundColor, - required this.editorBackgroundColor, - required this.foregroundColor, - required this.sidebarBackgroundColor, - }); - - const VsCodeTheme.dark() - : this._( - activityBarBackgroundColor: const Color(0xFF333333), - editorBackgroundColor: const Color(0xFF1E1E1E), - foregroundColor: const Color(0xFFD4D4D4), - sidebarBackgroundColor: const Color(0xFF252526), - ); - - const VsCodeTheme.light() - : this._( - activityBarBackgroundColor: const Color(0xFF2C2C2C), - editorBackgroundColor: const Color(0xFFFFFFFF), - foregroundColor: const Color(0xFF000000), - sidebarBackgroundColor: const Color(0xFFF3F3F3), - ); - - static VsCodeTheme of(BuildContext context) { - return Theme.of(context).isDarkTheme - ? const VsCodeTheme.dark() - : const VsCodeTheme.light(); - } - - final Color activityBarBackgroundColor; - final Color editorBackgroundColor; - final Color foregroundColor; - final Color sidebarBackgroundColor; -}
diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/property_editor_sidebar.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/property_editor_sidebar.dart new file mode 100644 index 0000000..c85a7ec --- /dev/null +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/property_editor_sidebar.dart
@@ -0,0 +1,139 @@ +// Copyright 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +import 'dart:async'; + +import 'package:devtools_app/devtools_app.dart'; +import 'package:devtools_app/src/standalone_ui/vs_code/property_editor_panel.dart'; +import 'package:devtools_app_shared/service.dart'; +import 'package:devtools_app_shared/ui.dart'; +import 'package:devtools_app_shared/utils.dart'; +import 'package:devtools_test/devtools_test.dart'; +import 'package:dtd/dtd.dart'; +import 'package:flutter/material.dart'; +import 'package:stager/stager.dart'; + +import 'editor_service/simulated_editor.dart'; +import 'mock_editor_widget.dart'; +import 'shared/common_ui.dart'; +import 'shared/utils.dart'; + +/// To run, use the "standalone_ui/property_editor_sidebar" launch configuration with the +/// `devtools/packages/` folder open in VS Code, or run: +/// +/// flutter run -t test/test_infra/scenes/standalone_ui/property_editor_sidebar.stager_app.g.dart -d chrome +class PropertyEditorSidebarScene extends Scene { + @override + Widget build(BuildContext context) { + return const _PropertyEditorSidebar(); + } + + @override + String get title => '$PropertyEditorSidebarScene'; + + @override + Future<void> setUp() async { + setStagerMode(); + setGlobal( + DevToolsEnvironmentParameters, + ExternalDevToolsEnvironmentParameters(), + ); + setGlobal(DTDManager, MockDTDManager()); + setGlobal(IdeTheme, IdeTheme()); + setGlobal(PreferencesController, PreferencesController()); + } +} + +class _PropertyEditorSidebar extends StatefulWidget { + const _PropertyEditorSidebar(); + + @override + State<_PropertyEditorSidebar> createState() => _PropertyEditorState(); +} + +class _PropertyEditorState extends State<_PropertyEditorSidebar> { + Stream<String>? clientLog; + DartToolingDaemon? clientDtd; + SimulatedEditor? editor; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return IdeThemedMaterialApp( + home: Scaffold( + body: + clientLog != null && clientDtd != null && editor != null + ? MockEditorWidget( + editor: editor!, + clientLog: clientLog!, + child: PropertyEditorSidebarPanel(clientDtd!), + ) + : _DtdUriForm( + onSaved: _connectToDtd, + formKey: GlobalKey<FormState>(), + ), + ), + ); + } + + Future<void> _connectToDtd(String? dtdUri) async { + if (dtdUri == null) return; + final uri = Uri.parse(dtdUri); + final connection = await createLoggedWebSocketChannel(uri); + setState(() { + clientLog = connection.log; + clientDtd = DartToolingDaemon.fromStreamChannel(connection.channel); + editor = SimulatedEditor(uri); + }); + } +} + +class _DtdUriForm extends StatelessWidget { + const _DtdUriForm({required this.onSaved, required this.formKey}); + + final void Function(String?) onSaved; + final GlobalKey<FormState> formKey; + + // We assume a DTD is available on 8500. There's a VS Code task that + // launches this as part of the standalone_ui/editor_sidebar config. + static const _defaultDtdUri = 'ws://127.0.0.1:8500/'; + + @override + Widget build(BuildContext context) { + return Form( + key: formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('Connect to DTD:'), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: TextFormField( + initialValue: _defaultDtdUri, + style: Theme.of(context).fixedFontStyle, + onSaved: onSaved, + ), + ), + Flexible( + flex: 2, + child: ElevatedButton( + onPressed: () { + formKey.currentState?.save(); + }, + child: const Text('Submit'), + ), + ), + ], + ), + ], + ), + ); + } +}
diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/shared/common_ui.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/shared/common_ui.dart new file mode 100644 index 0000000..ad0503e --- /dev/null +++ b/packages/devtools_app/test/test_infra/scenes/standalone_ui/shared/common_ui.dart
@@ -0,0 +1,77 @@ +// Copyright 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be found +// in the LICENSE file. + +import 'package:devtools_app_shared/shared.dart'; +import 'package:devtools_app_shared/ui.dart'; +import 'package:flutter/material.dart'; + +class IdeThemedMaterialApp extends StatelessWidget { + const IdeThemedMaterialApp({super.key, required this.home}); + + final Widget home; + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: themeFor( + isDarkTheme: false, + ideTheme: _ideTheme(const VsCodeTheme.light()), + theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme), + ), + darkTheme: themeFor( + isDarkTheme: true, + ideTheme: _ideTheme(const VsCodeTheme.dark()), + theme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme), + ), + home: home, + ); + } + + /// Creates an [IdeTheme] using the colours from the mock editor. + IdeTheme _ideTheme(VsCodeTheme vsCodeTheme) { + return IdeTheme( + backgroundColor: vsCodeTheme.editorBackgroundColor, + foregroundColor: vsCodeTheme.foregroundColor, + embedMode: EmbedMode.embedOne, + ); + } +} + +/// A basic theme that matches the default colours of VS Code dart/light themes +/// so the mock environment can be displayed in either. +class VsCodeTheme { + const VsCodeTheme._({ + required this.activityBarBackgroundColor, + required this.editorBackgroundColor, + required this.foregroundColor, + required this.sidebarBackgroundColor, + }); + + const VsCodeTheme.dark() + : this._( + activityBarBackgroundColor: const Color(0xFF333333), + editorBackgroundColor: const Color(0xFF1E1E1E), + foregroundColor: const Color(0xFFD4D4D4), + sidebarBackgroundColor: const Color(0xFF252526), + ); + + const VsCodeTheme.light() + : this._( + activityBarBackgroundColor: const Color(0xFF2C2C2C), + editorBackgroundColor: const Color(0xFFFFFFFF), + foregroundColor: const Color(0xFF000000), + sidebarBackgroundColor: const Color(0xFFF3F3F3), + ); + + static VsCodeTheme of(BuildContext context) { + return Theme.of(context).isDarkTheme + ? const VsCodeTheme.dark() + : const VsCodeTheme.light(); + } + + final Color activityBarBackgroundColor; + final Color editorBackgroundColor; + final Color foregroundColor; + final Color sidebarBackgroundColor; +}
diff --git a/packages/devtools_app/test/test_infra/scenes/standalone_ui/utils.dart b/packages/devtools_app/test/test_infra/scenes/standalone_ui/shared/utils.dart similarity index 100% rename from packages/devtools_app/test/test_infra/scenes/standalone_ui/utils.dart rename to packages/devtools_app/test/test_infra/scenes/standalone_ui/shared/utils.dart