diff --git a/pkgs/sketch_pad/lib/editor/codemirror.dart b/pkgs/sketch_pad/lib/editor/codemirror.dart index 762bfb7bd..86fcf1959 100644 --- a/pkgs/sketch_pad/lib/editor/codemirror.dart +++ b/pkgs/sketch_pad/lib/editor/codemirror.dart @@ -6,6 +6,7 @@ library; import 'dart:js_interop'; + import 'package:web/web.dart'; extension type CodeMirrorOptions._(JSObject _) implements JSObject { diff --git a/pkgs/sketch_pad/lib/gists.dart b/pkgs/sketch_pad/lib/gists.dart index cece7ecb0..c0056f568 100644 --- a/pkgs/sketch_pad/lib/gists.dart +++ b/pkgs/sketch_pad/lib/gists.dart @@ -28,17 +28,22 @@ class GistLoader { } class Gist { + static const String defaultFileName = 'main.dart'; + final String id; final String? description; final String? owner; final List files; + final List validationIssues = []; Gist({ required this.id, required this.description, required this.owner, required this.files, - }); + }) { + _validateGist(); + } factory Gist.fromJson(Map json) { /* { @@ -80,9 +85,28 @@ class Gist { } String? get mainDartSource { - return files - .firstWhereOrNull((file) => file.fileName == 'main.dart') - ?.content; + GistFile? file; + + // First, try and load 'main.dart'. + file = files.firstWhereOrNull((file) => file.fileName == defaultFileName); + + // Fall back on the older (unintentional) contention - loading from the + // single dart file in a gist. + file ??= files.singleWhereOrNull((file) => file.fileName.endsWith('.dart')); + + return file?.content; + } + + void _validateGist() { + final file = + files.singleWhereOrNull((file) => file.fileName.endsWith('.dart')); + + if (file == null) { + validationIssues.add('Warning: no Dart file found in the gist'); + } else if (file.fileName != defaultFileName) { + validationIssues.add('Warning: no gist content in $defaultFileName ' + '(loading from ${file.fileName})'); + } } } diff --git a/pkgs/sketch_pad/lib/model.dart b/pkgs/sketch_pad/lib/model.dart index 0ff9d5545..babff7fec 100644 --- a/pkgs/sketch_pad/lib/model.dart +++ b/pkgs/sketch_pad/lib/model.dart @@ -262,6 +262,14 @@ class AppServices { appModel.sourceCodeController.text = fallbackSnippet; } else { appModel.sourceCodeController.text = source; + + if (gist.validationIssues.isNotEmpty) { + final message = gist.validationIssues.join('\n'); + appModel.editorStatus.showToast( + message, + duration: const Duration(seconds: 10), + ); + } } appModel.appReady.value = true; diff --git a/pkgs/sketch_pad/test/gists_test.dart b/pkgs/sketch_pad/test/gists_test.dart index 17c55359a..5a484a73b 100644 --- a/pkgs/sketch_pad/test/gists_test.dart +++ b/pkgs/sketch_pad/test/gists_test.dart @@ -32,6 +32,20 @@ void main() { expect(gist.mainDartSource, isNull); }); + + test('validates main.dart missing', () { + final gist = + Gist.fromJson(jsonDecode(jsonSampleNoMain) as Map); + + expect(gist.validationIssues, isNotEmpty); + }); + + test('validates unexpected dart content file', () { + final gist = Gist.fromJson( + jsonDecode(jsonSampleAlternativeFile) as Map); + + expect(gist.validationIssues, isNotEmpty); + }); }); } @@ -76,7 +90,7 @@ const String jsonSampleNoMain = ''' "user": null, "truncated": false, "files": { - "main.dart": { + "main.html": { "filename": "main.html", "type": "application/vnd.dart", "language": "Dart", @@ -88,3 +102,30 @@ const String jsonSampleNoMain = ''' } } '''; + +const String jsonSampleAlternativeFile = ''' +{ + "id": "d3bd83918d21b6d5f778bdc69c3d36d6", + "description": "Fibonacci", + "owner": { + "login": "flutterdevrelgists" + }, + "public": false, + "created_at": "2021-08-23T23:27:20Z", + "updated_at": "2023-05-30T10:59:27Z", + "comments": 0, + "user": null, + "truncated": false, + "files": { + "sample_1.dart": { + "filename": "sample_1.dart", + "type": "application/vnd.dart", + "language": "Dart", + "raw_url": "https://gist.githubusercontent.com/flutterdevrelgists/d3bd83918d21b6d5f778bdc69c3d36d6/raw/5eaecf3519fe453298d077068194720c9729be62/main.dart", + "size": 369, + "truncated": false, + "content": "..." + } + } +} +''';