diff --git a/packages/cross_file/CHANGELOG.md b/packages/cross_file/CHANGELOG.md index b10acb4529e..2591d2ea9d2 100644 --- a/packages/cross_file/CHANGELOG.md +++ b/packages/cross_file/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.3.3+8 + +* Now supports `dart2wasm` compilation. +* Updates minimum supported SDK version to Flutter 3.16/Dart 3.2. + ## 0.3.3+7 * Updates README to improve example of instantiating an XFile. diff --git a/packages/cross_file/example/test/readme_excerpts_test.dart b/packages/cross_file/example/test/readme_excerpts_test.dart index e9a4bd3d1d7..93f1adcfb9d 100644 --- a/packages/cross_file/example/test/readme_excerpts_test.dart +++ b/packages/cross_file/example/test/readme_excerpts_test.dart @@ -6,7 +6,7 @@ import 'package:cross_file/cross_file.dart'; import 'package:cross_file_example/readme_excerpts.dart'; import 'package:test/test.dart'; -const bool kIsWeb = bool.fromEnvironment('dart.library.js_util'); +const bool kIsWeb = bool.fromEnvironment('dart.library.js_interop'); void main() { test('instantiateXFile loads asset file', () async { diff --git a/packages/cross_file/lib/src/types/html.dart b/packages/cross_file/lib/src/types/html.dart index 7f5a1e5396a..9eb95b448d8 100644 --- a/packages/cross_file/lib/src/types/html.dart +++ b/packages/cross_file/lib/src/types/html.dart @@ -4,10 +4,11 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:html'; +import 'dart:js_interop'; import 'dart:typed_data'; import 'package:meta/meta.dart'; +import 'package:web/helpers.dart'; import '../web_helpers/web_helpers.dart'; import 'base.dart'; @@ -65,7 +66,9 @@ class XFile extends XFileBase { super(path) { if (path == null) { _browserBlob = _createBlobFromBytes(bytes, mimeType); - _path = Url.createObjectUrl(_browserBlob); + // TODO(kevmoo): drop ignore when pkg:web constraint excludes v0.3 + // ignore: unnecessary_cast + _path = URL.createObjectURL(_browserBlob! as JSObject); } else { _path = path; } @@ -74,8 +77,9 @@ class XFile extends XFileBase { // Initializes a Blob from a bunch of `bytes` and an optional `mimeType`. Blob _createBlobFromBytes(Uint8List bytes, String? mimeType) { return (mimeType == null) - ? Blob([bytes]) - : Blob([bytes], mimeType); + ? Blob([bytes.toJS].toJS) + : Blob( + [bytes.toJS].toJS, BlobPropertyBag(type: mimeType)); } // Overridable (meta) data that can be specified by the constructors. @@ -127,11 +131,13 @@ class XFile extends XFileBase { // Attempt to re-hydrate the blob from the `path` via a (local) HttpRequest. // Note that safari hangs if the Blob is >=4GB, so bail out in that case. + // TODO(kevmoo): Remove ignore and fix when the MIN Dart SDK is 3.3 + // ignore: unnecessary_non_null_assertion if (isSafari() && _length != null && _length! >= _fourGigabytes) { throw Exception('Safari cannot handle XFiles larger than 4GB.'); } - late HttpRequest request; + late XMLHttpRequest request; try { request = await HttpRequest.request(path, responseType: 'blob'); } on ProgressEvent catch (e) { @@ -181,7 +187,8 @@ class XFile extends XFileBase { await reader.onLoadEnd.first; - final Uint8List? result = reader.result as Uint8List?; + final Uint8List? result = + (reader.result as JSArrayBuffer?)?.toDart.asUint8List(); if (result == null) { throw Exception('Cannot read bytes from Blob. Is it still available?'); @@ -201,12 +208,14 @@ class XFile extends XFileBase { // Create an tag with the appropriate download attributes and click it // May be overridden with CrossFileTestOverrides - final AnchorElement element = _hasTestOverrides - ? _overrides!.createAnchorElement(this.path, name) as AnchorElement + final HTMLAnchorElement element = _hasTestOverrides + ? _overrides!.createAnchorElement(this.path, name) as HTMLAnchorElement : createAnchorElement(this.path, name); // Clear the children in _target and add an element to click - _target.children.clear(); + while (_target.children.length > 0) { + _target.removeChild(_target.children.item(0)!); + } addElementToContainerAndClick(_target, element); } } diff --git a/packages/cross_file/lib/src/types/io.dart b/packages/cross_file/lib/src/types/io.dart index fc34d496669..a6979e53498 100644 --- a/packages/cross_file/lib/src/types/io.dart +++ b/packages/cross_file/lib/src/types/io.dart @@ -82,6 +82,8 @@ class XFile extends XFileBase { await _file.copy(path); } else { final File fileToSave = File(path); + // TODO(kevmoo): Remove ignore and fix when the MIN Dart SDK is 3.3 + // ignore: unnecessary_non_null_assertion await fileToSave.writeAsBytes(_bytes!); } } @@ -106,6 +108,8 @@ class XFile extends XFileBase { @override Future readAsString({Encoding encoding = utf8}) { if (_bytes != null) { + // TODO(kevmoo): Remove ignore and fix when the MIN Dart SDK is 3.3 + // ignore: unnecessary_non_null_assertion return Future.value(String.fromCharCodes(_bytes!)); } return _file.readAsString(encoding: encoding); diff --git a/packages/cross_file/lib/src/web_helpers/web_helpers.dart b/packages/cross_file/lib/src/web_helpers/web_helpers.dart index da023b68215..ec9a2e86e7d 100644 --- a/packages/cross_file/lib/src/web_helpers/web_helpers.dart +++ b/packages/cross_file/lib/src/web_helpers/web_helpers.dart @@ -2,26 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:html'; +import 'package:web/helpers.dart'; /// Create anchor element with download attribute -AnchorElement createAnchorElement(String href, String? suggestedName) { - final AnchorElement element = AnchorElement(href: href); - - if (suggestedName == null) { - element.download = 'download'; - } else { - element.download = suggestedName; - } - - return element; -} +HTMLAnchorElement createAnchorElement(String href, String? suggestedName) => + (document.createElement('a') as HTMLAnchorElement) + ..href = href + ..download = suggestedName ?? 'download'; /// Add an element to a container and click it -void addElementToContainerAndClick(Element container, Element element) { +void addElementToContainerAndClick(Element container, HTMLElement element) { // Add the element and click it // All previous elements will be removed before adding the new one - container.children.add(element); + container.appendChild(element); element.click(); } @@ -29,9 +22,9 @@ void addElementToContainerAndClick(Element container, Element element) { Element ensureInitialized(String id) { Element? target = querySelector('#$id'); if (target == null) { - final Element targetElement = Element.tag('flt-x-file')..id = id; + final Element targetElement = document.createElement('flt-x-file')..id = id; - querySelector('body')!.children.add(targetElement); + querySelector('body')!.appendChild(targetElement); target = targetElement; } return target; diff --git a/packages/cross_file/lib/src/x_file.dart b/packages/cross_file/lib/src/x_file.dart index a6022e73920..00dda82f024 100644 --- a/packages/cross_file/lib/src/x_file.dart +++ b/packages/cross_file/lib/src/x_file.dart @@ -3,5 +3,5 @@ // found in the LICENSE file. export 'types/interface.dart' - if (dart.library.html) 'types/html.dart' + if (dart.library.js_interop) 'types/html.dart' if (dart.library.io) 'types/io.dart'; diff --git a/packages/cross_file/pubspec.yaml b/packages/cross_file/pubspec.yaml index f1216d3e775..c2b8f2f5342 100644 --- a/packages/cross_file/pubspec.yaml +++ b/packages/cross_file/pubspec.yaml @@ -2,14 +2,14 @@ name: cross_file description: An abstraction to allow working with files across multiple platforms. repository: https://github.com/flutter/packages/tree/main/packages/cross_file issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+cross_file%22 -version: 0.3.3+7 +version: 0.3.3+8 environment: - sdk: ">=3.0.0 <4.0.0" + sdk: ^3.2.0 dependencies: - js: ^0.6.3 meta: ^1.3.0 + web: '>=0.3.0 <0.5.0' dev_dependencies: path: ^1.8.1 diff --git a/packages/cross_file/test/x_file_html_test.dart b/packages/cross_file/test/x_file_html_test.dart index a19e5098eba..4e1cac634ae 100644 --- a/packages/cross_file/test/x_file_html_test.dart +++ b/packages/cross_file/test/x_file_html_test.dart @@ -5,17 +5,21 @@ @TestOn('chrome') // Uses web-only Flutter SDK import 'dart:convert'; -import 'dart:html' as html; +import 'dart:js_interop'; import 'dart:typed_data'; import 'package:cross_file/cross_file.dart'; -import 'package:js/js_util.dart' as js_util; import 'package:test/test.dart'; +import 'package:web/helpers.dart' as html; const String expectedStringContents = 'Hello, world! I ❤ ñ! 空手'; final Uint8List bytes = Uint8List.fromList(utf8.encode(expectedStringContents)); -final html.File textFile = html.File([bytes], 'hello.txt'); -final String textFileUrl = html.Url.createObjectUrl(textFile); +final html.File textFile = + html.File([bytes.toJS].toJS, 'hello.txt'); +final String textFileUrl = + // TODO(kevmoo): drop ignore when pkg:web constraint excludes v0.3 + // ignore: unnecessary_cast + html.URL.createObjectURL(textFile as JSObject); void main() { group('Create with an objectUrl', () { @@ -63,16 +67,16 @@ void main() { test('Stores data as a Blob', () async { // Read the blob from its path 'natively' - final Object response = await html.window.fetch(file.path) as Object; - // Call '.arrayBuffer()' on the fetch response object to look at its bytes. - final ByteBuffer data = await js_util.promiseToFuture( - js_util.callMethod(response, 'arrayBuffer', []), - ); + final html.Response response = + (await html.window.fetch(file.path.toJS).toDart)! as html.Response; + + final JSAny? arrayBuffer = await response.arrayBuffer().toDart; + final ByteBuffer data = (arrayBuffer! as JSArrayBuffer).toDart; expect(data.asUint8List(), equals(bytes)); }); test('Data may be purged from the blob!', () async { - html.Url.revokeObjectUrl(file.path); + html.URL.revokeObjectURL(file.path); expect(() async { await file.readAsBytes(); @@ -102,9 +106,15 @@ void main() { final html.Element container = html.querySelector('#$crossFileDomElementId')!; - final html.AnchorElement element = container.children - .firstWhere((html.Element element) => element.tagName == 'A') - as html.AnchorElement; + + late html.HTMLAnchorElement element; + for (int i = 0; i < container.childNodes.length; i++) { + final html.Element test = container.children.item(i)!; + if (test.tagName == 'A') { + element = test as html.HTMLAnchorElement; + break; + } + } // if element is not found, the `firstWhere` call will throw StateError. expect(element.href, file.path); @@ -112,7 +122,8 @@ void main() { }); test('anchor element is clicked', () async { - final html.AnchorElement mockAnchor = html.AnchorElement(); + final html.HTMLAnchorElement mockAnchor = + html.document.createElement('a') as html.HTMLAnchorElement; final CrossFileTestOverrides overrides = CrossFileTestOverrides( createAnchorElement: (_, __) => mockAnchor,