191 lines
5.4 KiB
Dart
191 lines
5.4 KiB
Dart
import 'dart:async';
|
|
import 'dart:html';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:file_picker/file_picker.dart';
|
|
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
|
|
|
class FilePickerWeb extends FilePicker {
|
|
late Element _target;
|
|
final String _kFilePickerInputsDomId = '__file_picker_web-file-input';
|
|
|
|
final int _readStreamChunkSize = 1000 * 1000; // 1 MB
|
|
|
|
static final FilePickerWeb platform = FilePickerWeb._();
|
|
|
|
FilePickerWeb._() {
|
|
_target = _ensureInitialized(_kFilePickerInputsDomId);
|
|
}
|
|
|
|
static void registerWith(Registrar registrar) {
|
|
FilePicker.platform = platform;
|
|
}
|
|
|
|
/// Initializes a DOM container where we can host input elements.
|
|
Element _ensureInitialized(String id) {
|
|
Element? target = querySelector('#$id');
|
|
if (target == null) {
|
|
final Element targetElement = Element.tag('flt-file-picker-inputs')
|
|
..id = id;
|
|
|
|
querySelector('body')!.children.add(targetElement);
|
|
target = targetElement;
|
|
}
|
|
return target;
|
|
}
|
|
|
|
@override
|
|
Future<FilePickerResult?> pickFiles({
|
|
String? dialogTitle,
|
|
FileType type = FileType.any,
|
|
List<String>? allowedExtensions,
|
|
bool allowMultiple = false,
|
|
Function(FilePickerStatus)? onFileLoading,
|
|
bool? allowCompression,
|
|
bool? withData = true,
|
|
bool? withReadStream = false,
|
|
}) async {
|
|
if (type != FileType.custom && (allowedExtensions?.isNotEmpty ?? false)) {
|
|
throw Exception(
|
|
'You are setting a type [$type]. Custom extension filters are only allowed with FileType.custom, please change it or remove filters.');
|
|
}
|
|
|
|
final Completer<List<PlatformFile>?> filesCompleter =
|
|
Completer<List<PlatformFile>?>();
|
|
|
|
String accept = _fileType(type, allowedExtensions);
|
|
InputElement uploadInput = FileUploadInputElement() as InputElement;
|
|
uploadInput.draggable = true;
|
|
uploadInput.multiple = allowMultiple;
|
|
uploadInput.accept = accept;
|
|
|
|
bool changeEventTriggered = false;
|
|
|
|
if (onFileLoading != null) {
|
|
onFileLoading(FilePickerStatus.picking);
|
|
}
|
|
|
|
void changeEventListener(e) {
|
|
if (changeEventTriggered) {
|
|
return;
|
|
}
|
|
changeEventTriggered = true;
|
|
|
|
final List<File> files = uploadInput.files!;
|
|
final List<PlatformFile> pickedFiles = [];
|
|
|
|
void addPickedFile(
|
|
File file,
|
|
Uint8List? bytes,
|
|
String? path,
|
|
Stream<List<int>>? readStream,
|
|
) {
|
|
pickedFiles.add(PlatformFile(
|
|
name: file.name,
|
|
path: path,
|
|
size: bytes != null ? bytes.length : file.size,
|
|
bytes: bytes,
|
|
readStream: readStream,
|
|
));
|
|
|
|
if (pickedFiles.length >= files.length) {
|
|
if (onFileLoading != null) {
|
|
onFileLoading(FilePickerStatus.done);
|
|
}
|
|
filesCompleter.complete(pickedFiles);
|
|
}
|
|
}
|
|
|
|
files.forEach((File file) {
|
|
if (withReadStream!) {
|
|
addPickedFile(file, null, null, _openFileReadStream(file));
|
|
return;
|
|
}
|
|
|
|
if (!withData!) {
|
|
final FileReader reader = FileReader();
|
|
reader.onLoadEnd.listen((e) {
|
|
addPickedFile(file, null, reader.result as String?, null);
|
|
});
|
|
reader.readAsDataUrl(file);
|
|
return;
|
|
}
|
|
|
|
final FileReader reader = FileReader();
|
|
reader.onLoadEnd.listen((e) {
|
|
addPickedFile(file, reader.result as Uint8List?, null, null);
|
|
});
|
|
reader.readAsArrayBuffer(file);
|
|
});
|
|
}
|
|
|
|
void cancelledEventListener(_) {
|
|
window.removeEventListener('focus', cancelledEventListener);
|
|
|
|
// This listener is called before the input changed event,
|
|
// and the `uploadInput.files` value is still null
|
|
// Wait for results from js to dart
|
|
Future.delayed(Duration(milliseconds: 500)).then((value) {
|
|
if (!changeEventTriggered) {
|
|
changeEventTriggered = true;
|
|
filesCompleter.complete(null);
|
|
}
|
|
});
|
|
}
|
|
|
|
uploadInput.onChange.listen(changeEventListener);
|
|
uploadInput.addEventListener('change', changeEventListener);
|
|
|
|
// Listen focus event for cancelled
|
|
window.addEventListener('focus', cancelledEventListener);
|
|
|
|
//Add input element to the page body
|
|
_target.children.clear();
|
|
_target.children.add(uploadInput);
|
|
uploadInput.click();
|
|
|
|
final List<PlatformFile>? files = await filesCompleter.future;
|
|
|
|
return files == null ? null : FilePickerResult(files);
|
|
}
|
|
|
|
static String _fileType(FileType type, List<String>? allowedExtensions) {
|
|
switch (type) {
|
|
case FileType.any:
|
|
return '';
|
|
|
|
case FileType.audio:
|
|
return 'audio/*';
|
|
|
|
case FileType.image:
|
|
return 'image/*';
|
|
|
|
case FileType.video:
|
|
return 'video/*';
|
|
|
|
case FileType.media:
|
|
return 'video/*|image/*';
|
|
|
|
case FileType.custom:
|
|
return allowedExtensions!
|
|
.fold('', (prev, next) => '${prev.isEmpty ? '' : '$prev,'} .$next');
|
|
}
|
|
}
|
|
|
|
Stream<List<int>> _openFileReadStream(File file) async* {
|
|
final reader = FileReader();
|
|
|
|
int start = 0;
|
|
while (start < file.size) {
|
|
final end = start + _readStreamChunkSize > file.size
|
|
? file.size
|
|
: start + _readStreamChunkSize;
|
|
final blob = file.slice(start, end);
|
|
reader.readAsArrayBuffer(blob);
|
|
await reader.onLoad.first;
|
|
yield reader.result as List<int>;
|
|
start += _readStreamChunkSize;
|
|
}
|
|
}
|
|
}
|