From 4d7507e90070094e1329306f9478dc68562b7d0f Mon Sep 17 00:00:00 2001 From: Parker Lougheed Date: Wed, 20 Nov 2024 17:45:08 +0800 Subject: [PATCH] Implement `/flutter` for `flutter.new` as firebase redirect (#3093) --- pkgs/dartpad_ui/firebase.json | 10 +++ pkgs/dartpad_ui/lib/main.dart | 7 +- pkgs/dartpad_ui/lib/model.dart | 3 +- pkgs/dartpad_ui/lib/samples.g.dart | 112 ++++++++++++------------- pkgs/dartpad_ui/test/samples_test.dart | 8 +- pkgs/samples/README.md | 2 + pkgs/samples/lib/default_dart.dart | 6 +- pkgs/samples/lib/default_flutter.dart | 4 + pkgs/samples/lib/samples.json | 96 +++++++++++---------- pkgs/samples/tool/samples.dart | 61 ++++++-------- 10 files changed, 159 insertions(+), 150 deletions(-) diff --git a/pkgs/dartpad_ui/firebase.json b/pkgs/dartpad_ui/firebase.json index a49f4f904..5e5d1bbcb 100644 --- a/pkgs/dartpad_ui/firebase.json +++ b/pkgs/dartpad_ui/firebase.json @@ -9,6 +9,16 @@ "**/node_modules/**" ], "redirects": [ + { + "source": "/dart", + "destination": "/?sample=dart", + "type": 301 + }, + { + "source": "/flutter", + "destination": "/?sample=flutter", + "type": 301 + }, { "source": "/embed-dart?(.html)", "destination": "/?embed=true", diff --git a/pkgs/dartpad_ui/lib/main.dart b/pkgs/dartpad_ui/lib/main.dart index 2aaebff08..807c9853c 100644 --- a/pkgs/dartpad_ui/lib/main.dart +++ b/pkgs/dartpad_ui/lib/main.dart @@ -119,7 +119,6 @@ class _DartPadAppState extends State { Widget _homePageBuilder(BuildContext context, GoRouterState state, {String? gist}) { - final path = state.path; final gistId = gist ?? state.uri.queryParameters['id']; final builtinSampleId = state.uri.queryParameters['sample']; final flutterSampleId = state.uri.queryParameters['sample_id']; @@ -128,7 +127,6 @@ class _DartPadAppState extends State { final runOnLoad = state.uri.queryParameters['run'] == 'true'; return DartPadMainPage( - path: path, initialChannel: channelParam, embedMode: embedMode, runOnLoad: runOnLoad, @@ -207,7 +205,6 @@ class DartPadMainPage extends StatefulWidget { final String? gistId; final String? builtinSampleId; final String? flutterSampleId; - final String? path; DartPadMainPage({ required this.initialChannel, @@ -217,7 +214,6 @@ class DartPadMainPage extends StatefulWidget { this.gistId, this.builtinSampleId, this.flutterSampleId, - this.path, }) : super( key: ValueKey( 'sample:$builtinSampleId gist:$gistId flutter:$flutterSampleId', @@ -276,14 +272,13 @@ class _DartPadMainPageState extends State ); appServices.populateVersions(); - final fallBackSnippetType = widget.path == '/flutter' ? 'flutter' : 'dart'; appServices .performInitialLoad( gistId: widget.gistId, sampleId: widget.builtinSampleId, flutterSampleId: widget.flutterSampleId, channel: widget.initialChannel, - fallbackSnippet: Samples.getDefault(type: fallBackSnippetType)) + fallbackSnippet: Samples.defaultSnippet()) .then((value) { // Start listening for inject code messages. handleEmbedMessage(appServices, runOnInject: widget.runOnLoad); diff --git a/pkgs/dartpad_ui/lib/model.dart b/pkgs/dartpad_ui/lib/model.dart index 02875b6cf..d3a09a93f 100644 --- a/pkgs/dartpad_ui/lib/model.dart +++ b/pkgs/dartpad_ui/lib/model.dart @@ -168,7 +168,8 @@ class AppServices { void resetTo({String? type}) { type ??= 'dart'; - final source = Samples.getDefault(type: type); + final source = + Samples.defaultSnippet(forFlutter: type.toLowerCase() == 'flutter'); // Reset the source. appModel.sourceCodeController.text = source; diff --git a/pkgs/dartpad_ui/lib/samples.g.dart b/pkgs/dartpad_ui/lib/samples.g.dart index a4745437c..72effebf0 100644 --- a/pkgs/dartpad_ui/lib/samples.g.dart +++ b/pkgs/dartpad_ui/lib/samples.g.dart @@ -1,6 +1,6 @@ -// Copyright 2023 the Dart project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file. +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. // This file has been automatically generated - please do not edit it manually. @@ -23,6 +23,8 @@ class Sample { bool get isDart => category == 'Dart'; + bool get shouldList => category != 'Defaults'; + @override String toString() => '[$category] $name ($id)'; } @@ -31,6 +33,8 @@ abstract final class Samples { static const List all = [ _fibonacci, _helloWorld, + _dart, + _flutter, _flameGame, _googleSdk, _counter, @@ -54,52 +58,16 @@ abstract final class Samples { static Sample? getById(String? id) => all.firstWhereOrNull((s) => s.id == id); - static String getDefault({required String type}) => _defaults[type]!; -} - -const Map _defaults = { - 'dart': r''' -void main() { - for (int i = 0; i < 10; i++) { - print('hello ${i + 1}'); - } -} -''', - 'flutter': r''' -import 'package:flutter/material.dart'; - -void main() { - runApp(const MyApp()); + static String defaultSnippet({bool forFlutter = false}) => + getById(forFlutter ? 'flutter' : 'dart')!.source; } -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return const MaterialApp( - debugShowCheckedModeBanner: false, - home: Scaffold( - body: Center( - child: Text('Hello, World!'), - ), - ), - ); - } -} -''', -}; - const _fibonacci = Sample( category: 'Dart', icon: 'dart', name: 'Fibonacci', id: 'fibonacci', source: r''' -// Copyright 2015 the Dart project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file. - void main() { const i = 20; @@ -119,10 +87,20 @@ const _helloWorld = Sample( name: 'Hello world', id: 'hello-world', source: r''' -// Copyright 2015 the Dart project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file. +void main() { + for (var i = 0; i < 10; i++) { + print('hello ${i + 1}'); + } +} +''', +); +const _dart = Sample( + category: 'Defaults', + icon: 'dart', + name: 'Dart snippet', + id: 'dart', + source: r''' void main() { for (var i = 0; i < 10; i++) { print('hello ${i + 1}'); @@ -131,16 +109,42 @@ void main() { ''', ); +const _flutter = Sample( + category: 'Defaults', + icon: 'flutter', + name: 'Flutter snippet', + id: 'flutter', + source: r''' +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + debugShowCheckedModeBanner: false, + home: Scaffold( + body: Center( + child: Text('Hello, World!'), + ), + ), + ); + } +} +''', +); + const _flameGame = Sample( category: 'Ecosystem', icon: 'flame', name: 'Flame game', id: 'flame-game', source: r''' -// Copyright 2024 the Dart project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file. - /// A simplified brick-breaker game, /// built using the Flame game engine for Flutter. /// @@ -474,10 +478,6 @@ const _googleSdk = Sample( name: 'Google AI SDK', id: 'google-ai-sdk', source: r''' -// Copyright 2024 the Dart project 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:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:google_generative_ai/google_generative_ai.dart'; @@ -822,10 +822,6 @@ const _counter = Sample( name: 'Counter', id: 'counter', source: r''' -// Copyright 2019 the Dart project 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:flutter/material.dart'; void main() => runApp(const MyApp()); @@ -904,10 +900,6 @@ const _sunflower = Sample( name: 'Sunflower', id: 'sunflower', source: r''' -// Copyright 2019 the Dart project 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:math' as math; import 'package:flutter/material.dart'; diff --git a/pkgs/dartpad_ui/test/samples_test.dart b/pkgs/dartpad_ui/test/samples_test.dart index d268920b4..6abc23da8 100644 --- a/pkgs/dartpad_ui/test/samples_test.dart +++ b/pkgs/dartpad_ui/test/samples_test.dart @@ -7,13 +7,13 @@ import 'package:test/test.dart'; void main() { group('samples', () { - test('has dart default', () { - final sample = Samples.getDefault(type: 'dart'); + test('has default dart sample', () { + final sample = Samples.getById('dart'); expect(sample, isNotNull); }); - test('has flutter default', () { - final sample = Samples.getDefault(type: 'flutter'); + test('has default flutter sample', () { + final sample = Samples.getById('flutter'); expect(sample, isNotNull); }); }); diff --git a/pkgs/samples/README.md b/pkgs/samples/README.md index bd28f3df7..e5171827d 100644 --- a/pkgs/samples/README.md +++ b/pkgs/samples/README.md @@ -9,6 +9,8 @@ Sample code snippets for DartPad. | --- | --- | --- | --- | | Dart | Fibonacci | [fibonacci.dart](lib/fibonacci.dart) | `fibonacci` | | Dart | Hello world | [hello_world.dart](lib/hello_world.dart) | `hello-world` | +| Defaults | Dart snippet | [default_dart.dart](lib/default_dart.dart) | `dart` | +| Defaults | Flutter snippet | [default_flutter.dart](lib/default_flutter.dart) | `flutter` | | Ecosystem | Flame game | [brick_breaker.dart](lib/brick_breaker.dart) | `flame-game` | | Ecosystem | Google AI SDK | [google_ai.dart](lib/google_ai.dart) | `google-ai-sdk` | | Flutter | Counter | [main.dart](lib/main.dart) | `counter` | diff --git a/pkgs/samples/lib/default_dart.dart b/pkgs/samples/lib/default_dart.dart index 9848f3069..105fb48f5 100644 --- a/pkgs/samples/lib/default_dart.dart +++ b/pkgs/samples/lib/default_dart.dart @@ -1,5 +1,9 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + void main() { - for (int i = 0; i < 10; i++) { + for (var i = 0; i < 10; i++) { print('hello ${i + 1}'); } } diff --git a/pkgs/samples/lib/default_flutter.dart b/pkgs/samples/lib/default_flutter.dart index 155affed6..277190914 100644 --- a/pkgs/samples/lib/default_flutter.dart +++ b/pkgs/samples/lib/default_flutter.dart @@ -1,3 +1,7 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. 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:flutter/material.dart'; void main() { diff --git a/pkgs/samples/lib/samples.json b/pkgs/samples/lib/samples.json index 811cd818e..2508ed901 100644 --- a/pkgs/samples/lib/samples.json +++ b/pkgs/samples/lib/samples.json @@ -1,44 +1,52 @@ -{ - "defaults": { - "dart": "lib/default_dart.dart", - "flutter": "lib/default_flutter.dart" - }, - "samples": [ - { - "category": "Dart", - "icon": "dart", - "name": "Hello world", - "path": "lib/hello_world.dart" - }, - { - "category": "Dart", - "icon": "dart", - "name": "Fibonacci", - "path": "lib/fibonacci.dart" - }, - { - "category": "Flutter", - "icon": "flutter", - "name": "Counter", - "path": "lib/main.dart" - }, - { - "category": "Flutter", - "icon": "flutter", - "name": "Sunflower", - "path": "lib/sunflower.dart" - }, - { - "category": "Ecosystem", - "icon": "gemini", - "name": "Google AI SDK", - "path": "lib/google_ai.dart" - }, - { - "category": "Ecosystem", - "icon": "flame", - "name": "Flame game", - "path": "lib/brick_breaker.dart" - } - ] -} \ No newline at end of file +[ + { + "category": "Defaults", + "icon": "dart", + "id": "dart", + "name": "Dart snippet", + "path": "lib/default_dart.dart" + }, + { + "category": "Defaults", + "icon": "flutter", + "id": "flutter", + "name": "Flutter snippet", + "path": "lib/default_flutter.dart" + }, + { + "category": "Dart", + "icon": "dart", + "name": "Hello world", + "path": "lib/hello_world.dart" + }, + { + "category": "Dart", + "icon": "dart", + "name": "Fibonacci", + "path": "lib/fibonacci.dart" + }, + { + "category": "Flutter", + "icon": "flutter", + "name": "Counter", + "path": "lib/main.dart" + }, + { + "category": "Flutter", + "icon": "flutter", + "name": "Sunflower", + "path": "lib/sunflower.dart" + }, + { + "category": "Ecosystem", + "icon": "gemini", + "name": "Google AI SDK", + "path": "lib/google_ai.dart" + }, + { + "category": "Ecosystem", + "icon": "flame", + "name": "Flame game", + "path": "lib/brick_breaker.dart" + } +] diff --git a/pkgs/samples/tool/samples.dart b/pkgs/samples/tool/samples.dart index ed4420224..5e2940100 100644 --- a/pkgs/samples/tool/samples.dart +++ b/pkgs/samples/tool/samples.dart @@ -1,6 +1,6 @@ -// Copyright 2023 the Dart project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file. +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. 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:convert'; import 'dart:io'; @@ -32,13 +32,13 @@ void main(List args) { } const Set categories = { + 'Defaults', 'Dart', 'Flutter', 'Ecosystem', }; class Samples { - late final Map defaults; late final List samples; void parse() { @@ -46,8 +46,7 @@ class Samples { final json = jsonDecode(File(p.join('lib', 'samples.json')).readAsStringSync()); - defaults = (json['defaults'] as Map).cast(); - samples = (json['samples'] as List).map((j) => Sample.fromJson(j)).toList(); + samples = (json as List).map((j) => Sample.fromJson(j)).toList(); // do basic validation var hadFailure = false; @@ -56,12 +55,6 @@ class Samples { hadFailure = true; } - for (final entry in defaults.entries) { - if (!File(entry.value).existsSync()) { - fail('File ${entry.value} not found.'); - } - } - for (final sample in samples) { print(sample); @@ -153,9 +146,9 @@ ${samples.map((s) => s.toTableRow()).join('\n')} String _generateSourceContent() { final buf = StringBuffer(''' -// Copyright 2023 the Dart project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file. +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. // This file has been automatically generated - please do not edit it manually. @@ -178,6 +171,8 @@ class Sample { bool get isDart => category == 'Dart'; + bool get shouldList => category != 'Defaults'; + @override String toString() => '[\$category] \$name (\$id)'; } @@ -188,25 +183,17 @@ abstract final class Samples { ]; static const Map> categories = { - ${categories.map((category) => _mapForCategory(category)).join(',\n ')}, + ${categories.where((category) => category != 'Defaults').map((category) => _mapForCategory(category)).join(',\n ')}, }; static Sample? getById(String? id) => all.firstWhereOrNull((s) => s.id == id); - static String getDefault({required String type}) => _defaults[type]!; + static String defaultSnippet({bool forFlutter = false}) => + getById(forFlutter ? 'flutter' : 'dart')!.source; } '''); - buf.writeln('const Map _defaults = {'); - - for (final entry in defaults.entries) { - final source = File(entry.value).readAsStringSync().trimRight(); - buf.writeln(" '${entry.key}': r'''\n$source\n''',"); - } - - buf.writeln('};\n'); - buf.write(samples.map((sample) => sample.sourceDef).join('\n')); return buf.toString(); @@ -235,13 +222,13 @@ class Sample implements Comparable { required this.path, }); - factory Sample.fromJson(Map json) { + factory Sample.fromJson(Map json) { return Sample( - category: json['category'], - icon: json['icon'], - name: json['name'], - id: (json['id'] as String?) ?? _idFromName(json['name']), - path: json['path'], + category: json['category'] as String, + icon: json['icon'] as String, + name: json['name'] as String, + id: (json['id'] as String?) ?? _idFromName(json['name'] as String), + path: json['path'] as String, ); } @@ -256,7 +243,10 @@ class Sample implements Comparable { return '_$gen'; } - String get source => File(path).readAsStringSync(); + String get _rawSource => File(path).readAsStringSync(); + + String get source => + _rawSource.replaceFirst(_copyrightCommentPattern, '').trim(); String get sourceDef { return ''' @@ -266,7 +256,7 @@ const $sourceId = Sample( name: '$name', id: '$id', source: r\'\'\' -${source.trimRight()} +$source \'\'\', ); '''; @@ -289,4 +279,7 @@ ${source.trimRight()} static String _idFromName(String name) => name.trim().toLowerCase().replaceAll(' ', '-'); + + static final RegExp _copyrightCommentPattern = + RegExp(r'^\/\/ Copyright.*LICENSE file.', multiLine: true, dotAll: true); }