Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 1eabad7

Browse files
authored
[file_selector_web] Add initial implementation (#3141)
Add the file_selector web implementation
1 parent 4fec227 commit 1eabad7

File tree

12 files changed

+553
-0
lines changed

12 files changed

+553
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# 0.7.0
2+
3+
- Initial open-source release.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Copyright 2020 The Flutter Authors. All rights reserved.
2+
3+
Redistribution and use in source and binary forms, with or without modification,
4+
are permitted provided that the following conditions are met:
5+
6+
* Redistributions of source code must retain the above copyright
7+
notice, this list of conditions and the following disclaimer.
8+
* Redistributions in binary form must reproduce the above
9+
copyright notice, this list of conditions and the following
10+
disclaimer in the documentation and/or other materials provided
11+
with the distribution.
12+
* Neither the name of Google Inc. nor the names of its
13+
contributors may be used to endorse or promote products derived
14+
from this software without specific prior written permission.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# file_picker_web
2+
3+
The web implementation of [`file_picker`][1].
4+
5+
## Usage
6+
7+
### Import the package
8+
To use this plugin in your Flutter Web app, simply add it as a dependency in
9+
your pubspec alongside the base `file_picker` plugin.
10+
11+
_(This is only temporary: in the future we hope to make this package an
12+
"endorsed" implementation of `file_picker`, so that it is automatically
13+
included in your Flutter Web app when you depend on `package:file_picker`.)_
14+
15+
This is what the above means to your `pubspec.yaml`:
16+
17+
```yaml
18+
...
19+
dependencies:
20+
...
21+
file_picker: ^0.7.0
22+
file_picker_web: ^0.7.0
23+
...
24+
```
25+
26+
### Use the plugin
27+
Once you have the `file_picker_web` dependency in your pubspec, you should
28+
be able to use `package:file_picker` as normal.
29+
30+
[1]: ../file_picker/file_picker
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2020 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// @dart = 2.9
6+
7+
import 'dart:html';
8+
import 'package:flutter_test/flutter_test.dart';
9+
import 'package:integration_test/integration_test.dart';
10+
import 'package:file_selector_web/src/dom_helper.dart';
11+
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
12+
13+
void main() {
14+
group('FileSelectorWeb', () {
15+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
16+
DomHelper domHelper;
17+
FileUploadInputElement input;
18+
19+
FileList FileListItems(List<File> files) {
20+
final dataTransfer = DataTransfer();
21+
files.forEach(dataTransfer.items.add);
22+
return dataTransfer.files;
23+
}
24+
25+
void setFilesAndTriggerChange(List<File> files) {
26+
input.files = FileListItems(files);
27+
input.dispatchEvent(Event('change'));
28+
}
29+
30+
setUp(() {
31+
domHelper = DomHelper();
32+
input = FileUploadInputElement();
33+
});
34+
35+
group('getFiles', () {
36+
final mockFile1 = File(['123456'], 'file1.txt');
37+
final mockFile2 = File([], 'file2.txt');
38+
39+
testWidgets('works', (_) async {
40+
final Future<List<XFile>> futureFiles = domHelper.getFiles(
41+
input: input,
42+
);
43+
44+
setFilesAndTriggerChange([mockFile1, mockFile2]);
45+
46+
final List<XFile> files = await futureFiles;
47+
48+
expect(files.length, 2);
49+
50+
expect(files[0].name, 'file1.txt');
51+
expect(await files[0].length(), 6);
52+
expect(await files[0].readAsString(), '123456');
53+
expect(await files[0].lastModified(), isNotNull);
54+
55+
expect(files[1].name, 'file2.txt');
56+
expect(await files[1].length(), 0);
57+
expect(await files[1].readAsString(), '');
58+
expect(await files[1].lastModified(), isNotNull);
59+
});
60+
61+
testWidgets('works multiple times', (_) async {
62+
Future<List<XFile>> futureFiles;
63+
List<XFile> files;
64+
65+
// It should work the first time
66+
futureFiles = domHelper.getFiles(input: input);
67+
setFilesAndTriggerChange([mockFile1]);
68+
69+
files = await futureFiles;
70+
71+
expect(files.length, 1);
72+
expect(files.first.name, mockFile1.name);
73+
74+
// The same input should work more than once
75+
futureFiles = domHelper.getFiles(input: input);
76+
setFilesAndTriggerChange([mockFile2]);
77+
78+
files = await futureFiles;
79+
80+
expect(files.length, 1);
81+
expect(files.first.name, mockFile2.name);
82+
});
83+
84+
testWidgets('sets the <input /> attributes and clicks it', (_) async {
85+
final accept = '.jpg,.png';
86+
final multiple = true;
87+
bool wasClicked = false;
88+
89+
//ignore: unawaited_futures
90+
input.onClick.first.then((_) => wasClicked = true);
91+
92+
final futureFile = domHelper.getFiles(
93+
accept: accept,
94+
multiple: multiple,
95+
input: input,
96+
);
97+
98+
expect(input.matchesWithAncestors('body'), true);
99+
expect(input.accept, accept);
100+
expect(input.multiple, multiple);
101+
expect(
102+
wasClicked,
103+
true,
104+
reason:
105+
'The <input /> should be clicked otherwise no dialog will be shown',
106+
);
107+
108+
setFilesAndTriggerChange([]);
109+
await futureFile;
110+
111+
// It should be already removed from the DOM after the file is resolved.
112+
expect(input.parent, isNull);
113+
});
114+
});
115+
});
116+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2020 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// @dart = 2.9
6+
7+
import 'dart:typed_data';
8+
import 'package:flutter_test/flutter_test.dart';
9+
import 'package:mockito/mockito.dart';
10+
import 'package:integration_test/integration_test.dart';
11+
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
12+
import 'package:file_selector_web/file_selector_web.dart';
13+
import 'package:file_selector_web/src/dom_helper.dart';
14+
15+
void main() {
16+
group('FileSelectorWeb', () {
17+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
18+
MockDomHelper mockDomHelper;
19+
FileSelectorWeb plugin;
20+
21+
setUp(() {
22+
mockDomHelper = MockDomHelper();
23+
plugin = FileSelectorWeb(domHelper: mockDomHelper);
24+
});
25+
26+
group('openFile', () {
27+
final mockFile = createXFile('1001', 'identity.png');
28+
29+
testWidgets('works', (WidgetTester _) async {
30+
final typeGroup = XTypeGroup(
31+
label: 'images',
32+
extensions: ['jpg', 'jpeg'],
33+
mimeTypes: ['image/png'],
34+
webWildCards: ['image/*'],
35+
);
36+
37+
when(mockDomHelper.getFiles(
38+
accept: '.jpg,.jpeg,image/png,image/*',
39+
multiple: false,
40+
)).thenAnswer((_) async => [mockFile]);
41+
42+
final file = await plugin.openFile(acceptedTypeGroups: [typeGroup]);
43+
44+
expect(file.name, mockFile.name);
45+
expect(await file.length(), 4);
46+
expect(await file.readAsString(), '1001');
47+
expect(await file.lastModified(), isNotNull);
48+
});
49+
});
50+
51+
group('openFiles', () {
52+
final mockFile1 = createXFile('123456', 'file1.txt');
53+
final mockFile2 = createXFile('', 'file2.txt');
54+
55+
testWidgets('works', (WidgetTester _) async {
56+
final typeGroup = XTypeGroup(
57+
label: 'files',
58+
extensions: ['.txt'],
59+
);
60+
61+
when(mockDomHelper.getFiles(
62+
accept: '.txt',
63+
multiple: true,
64+
)).thenAnswer((_) async => [mockFile1, mockFile2]);
65+
66+
final files = await plugin.openFiles(acceptedTypeGroups: [typeGroup]);
67+
68+
expect(files.length, 2);
69+
70+
expect(files[0].name, mockFile1.name);
71+
expect(await files[0].length(), 6);
72+
expect(await files[0].readAsString(), '123456');
73+
expect(await files[0].lastModified(), isNotNull);
74+
75+
expect(files[1].name, mockFile2.name);
76+
expect(await files[1].length(), 0);
77+
expect(await files[1].readAsString(), '');
78+
expect(await files[1].lastModified(), isNotNull);
79+
});
80+
});
81+
});
82+
}
83+
84+
class MockDomHelper extends Mock implements DomHelper {}
85+
86+
XFile createXFile(String content, String name) {
87+
final data = Uint8List.fromList(content.codeUnits);
88+
return XFile.fromData(data, name: name, lastModified: DateTime.now());
89+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2020 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'package:meta/meta.dart';
7+
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
8+
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
9+
import 'package:file_selector_web/src/dom_helper.dart';
10+
import 'package:file_selector_web/src/utils.dart';
11+
12+
/// The web implementation of [FileSelectorPlatform].
13+
///
14+
/// This class implements the `package:file_selector` functionality for the web.
15+
class FileSelectorWeb extends FileSelectorPlatform {
16+
final _domHelper;
17+
18+
/// Registers this class as the default instance of [FileSelectorPlatform].
19+
static void registerWith(Registrar registrar) {
20+
FileSelectorPlatform.instance = FileSelectorWeb();
21+
}
22+
23+
/// Default constructor, initializes _domHelper that we can use
24+
/// to interact with the DOM.
25+
/// overrides parameter allows for testing to override functions
26+
FileSelectorWeb({@visibleForTesting DomHelper domHelper})
27+
: _domHelper = domHelper ?? DomHelper();
28+
29+
@override
30+
Future<XFile> openFile({
31+
List<XTypeGroup> acceptedTypeGroups,
32+
String initialDirectory,
33+
String confirmButtonText,
34+
}) async {
35+
final files = await _openFiles(acceptedTypeGroups: acceptedTypeGroups);
36+
return files.first;
37+
}
38+
39+
@override
40+
Future<List<XFile>> openFiles({
41+
List<XTypeGroup> acceptedTypeGroups,
42+
String initialDirectory,
43+
String confirmButtonText,
44+
}) async {
45+
return _openFiles(acceptedTypeGroups: acceptedTypeGroups, multiple: true);
46+
}
47+
48+
@override
49+
Future<String> getSavePath({
50+
List<XTypeGroup> acceptedTypeGroups,
51+
String initialDirectory,
52+
String suggestedName,
53+
String confirmButtonText,
54+
}) async =>
55+
null;
56+
57+
@override
58+
Future<String> getDirectoryPath({
59+
String initialDirectory,
60+
String confirmButtonText,
61+
}) async =>
62+
null;
63+
64+
Future<List<XFile>> _openFiles({
65+
List<XTypeGroup> acceptedTypeGroups,
66+
bool multiple = false,
67+
}) async {
68+
final accept = acceptedTypesToString(acceptedTypeGroups);
69+
return _domHelper.getFiles(
70+
accept: accept,
71+
multiple: multiple,
72+
);
73+
}
74+
}

0 commit comments

Comments
 (0)