UNPKG

@angelengineering/filepicker

Version:
601 lines 28.8 kB
/* eslint-disable no-var */ import { MediaType } from './index.common'; import { Application, File, ImageAsset, ImageSource } from '@nativescript/core'; import { iOSNativeHelper } from '@nativescript/core/utils'; import { AssetDownloader, TempFile } from './files'; export { MediaType } from './index.common'; let _iosDocumentPickerController; let _iosGalleryPickerController; let _iosPHPickerController; //for iOS<14 we use UIImagePicker. ios14+ uses PHPicker /** * @function galleryPicker * Present a Photos gallery picker filtered by MediaType and using single or multiple selection mode. Note: Android will just call showPicker currently. * @param {MediaType} type OR'ed from all possible MediaType's to describe types of files allowed in selection * @param {boolean} multiple if multiple selections are allowed * @returns {Promise<File[]>} Promise<File[]> Returns an array of Photos gallery files selected by user. */ export function galleryPicker(type, multiple) { //NOTE: iOS 14+ adds new photo/video gallery privacy access restrictions for UIImagePicker, and introduces // the new PHPicker component which doesn't requires perms and supports multiple selections. if (+iOSNativeHelper.MajorVersion >= 14) { return PHPicker(type, multiple); } //gallery UIImage Picker version(images and video) for ios<14 //NOTE: the iOS UIImagePickerController does not allow multiple selections else { if (multiple) console.warn('iOS UIImagePickerController only allows single selections!'); return ImgPicker(type); } } /** * @function getFreeMBs * Returns the number of megabytes free on file system containing the filepath * @param {string} filepath full filepath on device */ export function getFreeMBs(filepath) { //iOS devices only have a single storage partition to work with, so we can use any path to check stats const attributeDictionary = NSFileManager.defaultManager.attributesOfFileSystemForPathError(filepath); // let totalsize: number = +attributeDictionary.valueForKey(NSFileSystemSize) / (1024 * 1024); const freesize = +attributeDictionary.valueForKey(NSFileSystemFreeSize) / (1024 * 1024); return freesize; } /** * @function PHPicker * Present a Photos gallery picker filtered by MediaType and using single or multiple selection mode. Note: Android will just call showPicker currently. * Note: This is used by galleryPicker for iOS 14+ * @param {MediaType} type OR'ed from all possible MediaType's to describe types of files allowed in selection * @param {boolean} multiple if multiple selections are allowed * @returns {Promise<File[]>} Promise<File[]> Returns an array of Photos gallery files selected by user. */ function PHPicker(type, multiple) { return new Promise((resolve, reject) => { const config = PHPickerConfiguration.new(); config.selectionLimit = multiple ? 0 : 1; config.filter = PHPickerFilter.anyFilterMatchingSubfilters(iOSNativeHelper.collections.jsArrayToNSArray(getPHTypes(type))); _iosPHPickerController = new PHPickerViewController({ configuration: config }); const delegate = PHPickerViewControllerDelegateImpl.new().initWithCallbackAndOptions(resolve, reject, this); delegate.registerToGlobal(); _iosPHPickerController.delegate = delegate; Application.ios.rootController.presentViewControllerAnimatedCompletion(_iosPHPickerController, true, null); }); } /** * @function ImgPicker * Present a Photos gallery picker filtered by MediaType and using single or multiple selection mode. Note: Android will just call showPicker currently. * Note: This is used by galleryPicker for iOS <14 * @param {MediaType} type OR'ed from all possible MediaType's to describe types of files allowed in selection * @param {boolean} multiple if multiple selections are allowed * @returns {Promise<File[]>} Promise<File[]> Returns an array of Photos gallery files selected by user. */ function ImgPicker(type) { return new Promise((resolve, reject) => { _iosGalleryPickerController = UIImagePickerController.new(); const mediaTypes = iOSNativeHelper.collections.jsArrayToNSArray(getMediaTypes(type)); _iosGalleryPickerController.mediaTypes = mediaTypes; //image/video editing not allowed for now as we would need to process changes manually before returning media _iosGalleryPickerController.allowsEditing = false; _iosGalleryPickerController.allowsImageEditing = false; const delegate = UIImagePickerControllerDelegateImpl.new().initWithCallbackAndOptions(resolve, reject, this); delegate.registerToGlobal(); _iosGalleryPickerController.delegate = delegate; Application.ios.rootController.presentViewControllerAnimatedCompletion(_iosGalleryPickerController, true, null); }); } /** * @function filePicker * Present a system picker filtered by MediaType and using single or multiple selection mode.. * @param {MediaType} type OR'ed from all possible MediaType's to describe types of files allowed in selection * @param {boolean} multiple if multiple selections are allowed * @returns {Promise<File[]>} Promise<File[]> returns an array of Files selected by user */ export function filePicker(type, multiple) { return new Promise((resolve, reject) => { const mediaTypes = iOSNativeHelper.collections.jsArrayToNSArray(getMediaTypes(type)); _iosDocumentPickerController = UIDocumentPickerViewController.alloc().initWithDocumentTypesInMode(mediaTypes, 0 /* UIDocumentPickerMode.Import */ //Import mode is less restrictive than Open and doesn't require a file coordinator ); // This picker does allow multiple selections if user chooses selection view _iosDocumentPickerController.allowsMultipleSelection = multiple; // This doesn't actually show extensions, but we'll set it anyway _iosDocumentPickerController.shouldShowFileExtensions = true; // If fullscreen style is used, delegate return handler throws an access error // If CurrentContext or others are used, delegate methods are never called _iosDocumentPickerController.modalPresentationStyle = 2 /* UIModalPresentationStyle.FormSheet */; const delegate = UIDocumentPickerDelegateImpl.new().initWithCallbackAndOptions(resolve, reject, this); delegate.registerToGlobal(); _iosDocumentPickerController.delegate = delegate; Application.ios.rootController.presentViewControllerAnimatedCompletion(_iosDocumentPickerController, true, null); }); } var UIDocumentPickerDelegateImpl = /** @class */ (function (_super) { __extends(UIDocumentPickerDelegateImpl, _super); function UIDocumentPickerDelegateImpl() { return _super !== null && _super.apply(this, arguments) || this; } UIDocumentPickerDelegateImpl.new = function () { return _super.new.call(this); }; UIDocumentPickerDelegateImpl.prototype.initWithCallbackAndOptions = function (callback, errorCallback, owner) { this._resolve = callback; this._reject = errorCallback; this._owner = owner; return this; }; //Need to maintain a reference otherwise iOS tends to clean it up when leaving app to launch picker UIDocumentPickerDelegateImpl.prototype.registerToGlobal = function () { global.documentPickerDelegate = this; }; UIDocumentPickerDelegateImpl.prototype.deRegisterFromGlobal = function () { global.documentPickerDelegate = null; }; UIDocumentPickerDelegateImpl.prototype.owner = function () { if (!this._owner) return null; return this._owner.get(); }; // if only single selection is allowed? sometimes, usually the next one handles all returns UIDocumentPickerDelegateImpl.prototype.documentPickerDidPickDocumentAtURL = function (controller, url) { var access = url.startAccessingSecurityScopedResource(); var tmppath = TempFile.getPath('asset', '.tmp'); File.fromPath(tmppath).removeSync(); var success = NSFileManager.defaultManager.copyItemAtPathToPathError(url.path, tmppath); var file = File.fromPath(tmppath); // persist original file name and extension in tmp file var originalFilename = url.lastPathComponent; var newPath = tmppath.replace(/\/[^/]+$/, "/".concat(originalFilename)); if (File.exists(newPath)) { // remove file if it exists File.fromPath(newPath).removeSync(); } // add originalFilename property file['originalFilename'] = originalFilename; // update name so uploaded file will have the same name as the original file file.renameSync(originalFilename); url.stopAccessingSecurityScopedResource(); controller.dismissViewControllerAnimatedCompletion(true, null); this._resolve([file]); this.deRegisterFromGlobal(); }; //if multiple selections allowed: UIDocumentPickerDelegateImpl.prototype.documentPickerDidPickDocumentsAtURLs = function (controller, urls) { var files = []; //This view can't display an UIActivityIndicatorView inside it using the usual ios spinner approach, // but picker shows a small spinner on the "Open" button while processing //Process picker results for (var i = 0; i < urls.count; i++) { var url = urls.objectAtIndex(i); //urls[0]; var access = url.startAccessingSecurityScopedResource(); //can't access directly, need to copy first to local app directory var tmppath = TempFile.getPath('asset', '.tmp'); File.fromPath(tmppath).removeSync(); var suc = NSFileManager.defaultManager.copyItemAtPathToPathError(url.path, tmppath); var file = File.fromPath(tmppath); // persist original file name and extension in tmp file var originalFilename = url.lastPathComponent; var newPath = tmppath.replace(/\/[^/]+$/, "/".concat(originalFilename)); if (File.exists(newPath)) { // remove file if it exists File.fromPath(newPath).removeSync(); } // add originalFilename property file['originalFilename'] = originalFilename; // update name so uploaded file will have the same name as the original file file.renameSync(originalFilename); files.push(file); if (access) url.stopAccessingSecurityScopedResource(); } controller.dismissViewControllerAnimatedCompletion(true, null); this._resolve(files); this.deRegisterFromGlobal(); }; UIDocumentPickerDelegateImpl.prototype.documentPickerWasCancelled = function (controller) { controller.dismissViewControllerAnimatedCompletion(true, null); this._reject(null); this.deRegisterFromGlobal(); }; UIDocumentPickerDelegateImpl.ObjCProtocols = [UIDocumentPickerDelegate]; return UIDocumentPickerDelegateImpl; }(NSObject)); var UIImagePickerControllerDelegateImpl = /** @class */ (function (_super) { __extends(UIImagePickerControllerDelegateImpl, _super); function UIImagePickerControllerDelegateImpl() { return _super !== null && _super.apply(this, arguments) || this; } UIImagePickerControllerDelegateImpl.new = function () { return _super.new.call(this); }; UIImagePickerControllerDelegateImpl.prototype.initWithCallbackAndOptions = function (callback, errorCallback, owner) { this._resolve = callback; this._reject = errorCallback; this._owner = owner; return this; }; //Need to maintain a reference otherwise iOS tends to clean it up when leaving app to launch picker UIImagePickerControllerDelegateImpl.prototype.registerToGlobal = function () { global.galleryPickerDelegate = this; }; UIImagePickerControllerDelegateImpl.prototype.deRegisterFromGlobal = function () { global.galleryPickerDelegate = null; }; UIImagePickerControllerDelegateImpl.prototype.owner = function () { if (!this._owner) return null; return this._owner.get(); }; //returns media item picked from gallery UIImagePickerControllerDelegateImpl.prototype.imagePickerControllerDidFinishPickingMediaWithInfo = function (picker, info) { var _this = this; if (info) { //This view can't display an UIActivityIndicatorView inside it using the usual ios spinner approach, // but picker shows a progress indicator when preparing video media, and images return almost instantly var asset = info.valueForKey(UIImagePickerControllerPHAsset); var downloader = new AssetDownloader(asset); downloader.download().then(function (res) { _this._resolve([res]); //returns a NS file object although filename will be of form assset???.tmp, but has originalFilename attached }); } else { this._reject(); } if (!!picker && !!picker.presentingViewController) picker.presentingViewController.dismissViewControllerAnimatedCompletion(true, null); this.deRegisterFromGlobal(); }; //this should never be called unless we enable editing for ios gallery selections UIImagePickerControllerDelegateImpl.prototype.imagePickerControllerDidFinishPickingImageEditingInfo = function (picker, image, editingInfo) { console.warn('WARNING: image picker editing feature has not yet been fully implemented!'); if (!!picker && !!picker.presentingViewController) picker.presentingViewController.dismissViewControllerAnimatedCompletion(true, null); this._reject(); this.deRegisterFromGlobal(); }; UIImagePickerControllerDelegateImpl.prototype.imagePickerControllerDidCancel = function (picker) { if (!!picker && !!picker.presentingViewController) picker.presentingViewController.dismissViewControllerAnimatedCompletion(true, null); this._reject(); this.deRegisterFromGlobal(); }; UIImagePickerControllerDelegateImpl.ObjCProtocols = [UIImagePickerControllerDelegate]; return UIImagePickerControllerDelegateImpl; }(NSObject)); var PHPickerViewControllerDelegateImpl = /** @class */ (function (_super) { __extends(PHPickerViewControllerDelegateImpl, _super); function PHPickerViewControllerDelegateImpl() { return _super !== null && _super.apply(this, arguments) || this; } PHPickerViewControllerDelegateImpl.new = function () { return _super.new.call(this); }; PHPickerViewControllerDelegateImpl.prototype.initWithCallbackAndOptions = function (callback, errorCallback, owner) { this._resolve = callback; this._reject = errorCallback; this._owner = owner; return this; }; //Need to maintain a reference otherwise iOS tends to clean it up when leaving app to launch picker PHPickerViewControllerDelegateImpl.prototype.registerToGlobal = function () { global.documentPickerDelegate = this; }; PHPickerViewControllerDelegateImpl.prototype.deRegisterFromGlobal = function () { global.documentPickerDelegate = null; }; PHPickerViewControllerDelegateImpl.prototype.owner = function () { if (!this._owner) return null; return this._owner.get(); }; //returns media items picked from gallery PHPickerViewControllerDelegateImpl.prototype.pickerDidFinishPicking = function (picker, results) { var files = []; var waitCount = results.count; var errorCount = 0; if (results) { //show activity indicator while processing selections var currentView = picker.view; var loaderView = UIView.alloc().initWithFrame(CGRectMake(0, 0, 90, 90)); loaderView.center = currentView.center; loaderView.layer.cornerRadius = 4; loaderView.backgroundColor = UIColor.lightGrayColor; var indicator = UIActivityIndicatorView.alloc().initWithActivityIndicatorStyle(UIActivityIndicatorViewStyle.WhiteLarge); indicator.center = CGPointMake(45, 45); loaderView.addSubview(indicator); currentView.addSubview(loaderView); indicator.startAnimating(); //process picker results, but warn user if a livePhoto is selected as those are not yet supported for (var i = 0; i < results.count; i++) { var pickerresult = results.objectAtIndex(i); var typeIdentifier = pickerresult.itemProvider.registeredTypeIdentifiers.firstObject; //special handling for Live Photo Bundles of an heic and a mov file (if user selects loop in their Gallery app, then these will become animated gifs) if (typeIdentifier == 'com.apple.live-photo-bundle') { pickerresult.itemProvider.loadObjectOfClassCompletionHandler(PHLivePhoto.class(), function (livePhoto, err) { if (err) { console.error(err.description); errorCount++; waitCount--; } else { var resources = PHAssetResource.assetResourcesForLivePhoto(livePhoto); var photo = resources.firstObject; var originalFilename_1 = photo.originalFilename; var imageData_1 = NSMutableData.alloc().init(); var options = PHAssetResourceRequestOptions.alloc().init(); PHAssetResourceManager.defaultManager().requestDataForAssetResourceOptionsDataReceivedHandlerCompletionHandler(photo, options, function (data) { imageData_1.appendData(data); }, function (err) { if (err) { console.error(err.description); waitCount--; errorCount++; } else { var image = new UIImage({ data: imageData_1, scale: 1 }); var imageAsset = new ImageAsset(image); var tmppath_1 = TempFile.getPath('asset', '.tmp'); File.fromPath(tmppath_1).removeSync(); ImageSource.fromAsset(imageAsset).then(function (source) { source.saveToFile(tmppath_1, 'jpeg', 0.95); var file = File.fromPath(tmppath_1); var newPath = tmppath_1.replace(/\/[^/]+$/, "/".concat(originalFilename_1)); if (File.exists(newPath)) { File.fromPath(newPath).removeSync(); } file.renameSync(originalFilename_1); files.push(file); waitCount--; }); } }); } }); } else { pickerresult.itemProvider.loadFileRepresentationForTypeIdentifierCompletionHandler(typeIdentifier, function (result, err) { if (result) { //copy this to somewhere app has access to var tmppath = TempFile.getPath('asset', '.tmp'); File.fromPath(tmppath).removeSync(); var suc = NSFileManager.defaultManager.copyItemAtPathToPathError(result.path, tmppath); var file = File.fromPath(tmppath); // persist original file name and extension in tmp file var originalFilename = result.lastPathComponent; var newPath = tmppath.replace(/\/[^/]+$/, "/".concat(originalFilename)); if (File.exists(newPath)) { // remove file if it exists File.fromPath(newPath).removeSync(); } // add originalFilename property file['originalFilename'] = originalFilename; // update name so uploaded file will have the same name as the original file file.renameSync(originalFilename); files.push(file); } if (err) { console.error(err.description); errorCount++; } waitCount--; }); } } // eslint-disable-next-line no-inner-declarations function waitForComplete(delegate) { if (waitCount > 0) { setTimeout(function () { return waitForComplete(delegate); }, 500); } else { if (!!picker && !!picker.presentingViewController) picker.presentingViewController.dismissViewControllerAnimatedCompletion(true, null); delegate._resolve(files); delegate.deRegisterFromGlobal(); if (errorCount > 0) console.warn("Unable to select ".concat(errorCount, "? selections from your device")); } } waitForComplete(this); } else { this._reject(); this.deRegisterFromGlobal(); } }; PHPickerViewControllerDelegateImpl.ObjCProtocols = [+iOSNativeHelper.MajorVersion >= 14 ? PHPickerViewControllerDelegate : UIImagePickerControllerDelegate]; return PHPickerViewControllerDelegateImpl; }(NSObject)); // used to configure media types for PHPicker function getPHTypes(type) { let fileTypes = []; if (type & MediaType.VIDEO) { fileTypes = fileTypes.concat(PHPickerFilter.videosFilter); } if (type & MediaType.IMAGE) { fileTypes = fileTypes.concat(PHPickerFilter.imagesFilter); } return fileTypes; } // used to configure media types for UIImagePicker function getMediaTypes(type) { let fileTypes = []; if (type & MediaType.AUDIO) { fileTypes = fileTypes.concat(MediaFileTypes[MediaType.AUDIO]); } if (type & MediaType.VIDEO) { fileTypes = fileTypes.concat(MediaFileTypes[MediaType.VIDEO]); } if (type & MediaType.IMAGE) { fileTypes = fileTypes.concat(MediaFileTypes[MediaType.IMAGE]); } if (type & MediaType.DOCUMENT) { fileTypes = fileTypes.concat(MediaFileTypes[MediaType.DOCUMENT]); } if (type & MediaType.ARCHIVE) { fileTypes = fileTypes.concat(MediaFileTypes[MediaType.ARCHIVE]); } return fileTypes; } // iOS File types for file picker //https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html //https://escapetech.eu/manuals/qdrop/uti.html //node_modules/@nativescript/types-ios/lib/ios/objc-x86_64/objc!CoreServices.d.ts var MediaFileTypes = { [MediaType.AUDIO]: [ kUTTypeMP3, kUTTypeMPEG4Audio, kUTTypeAudio, kUTTypeAudioInterchangeFileFormat, kUTTypeAppleProtectedMPEG4Audio, kUTTypeMIDIAudio, kUTTypeWaveformAudio, 'public.aifc-audio', 'public.aiff-audio', 'com.microsoft.waveform-​audio', 'com.microsoft.windows-​media-wma', 'public.audio', 'public.ulaw-audio', 'com.apple.coreaudio-​format', 'public.ogg-audio', ], [MediaType.IMAGE]: [ kUTTypeImage, kUTTypeBMP, kUTTypeGIF, kUTTypeJPEG, kUTTypeJPEG2000, kUTTypePNG, kUTTypeQuickTimeImage, kUTTypeRawImage, kUTTypeScalableVectorGraphics, kUTTypeTIFF, 'public.image', 'public.camera-raw-image', kUTTypePICT, kUTTypeAppleICNS, kUTTypeICO, kUTTypeLivePhoto, 'com.apple.private.auto-loop-gif', ], [MediaType.VIDEO]: [ kUTTypeVideo, kUTTypeMovie, kUTTypeAudiovisualContent, kUTTypeAVIMovie, kUTTypeAppleProtectedMPEG4Video, kUTTypeMPEG, kUTTypeMPEG2TransportStream, kUTTypeMPEG2Video, kUTTypeMPEG4, kUTTypeQuickTimeMovie, 'public.movie', 'public.audiovisual-content', 'public.avi', 'public.3gpp', 'public.3gpp2', ], [MediaType.DOCUMENT]: [ kUTTypePDF, kUTTypeText, kUTTypePlainText, kUTTypeUTF8PlainText, kUTTypeUTF16ExternalPlainText, kUTTypeUTF16PlainText, kUTTypeUTF8TabSeparatedText, kUTTypePresentation, kUTTypeRTF, kUTTypeRTFD, kUTTypeSpreadsheet, kUTTypeHTML, kUTTypeXML, kUTTypeSourceCode, 'com.microsoft.word.doc', 'com.microsoft.word.docx', 'org.openxmlformats.wordprocessingml.document', 'com.microsoft.powerpoint.ppt', 'com.microsoft.powerpoint.pptx', 'org.openxmlformats.presentationml.presentation', 'public.rtf', 'com.adobe.postscript', 'com.adobe.encapsulated-postscript', 'public.presentation', 'public.text', kUTTypeCommaSeparatedText, kUTTypeDelimitedText, kUTTypeElectronicPublication, kUTTypeFlatRTFD, kUTTypeScript, kUTTypeShellScript, ], [MediaType.ARCHIVE]: [ kUTTypeArchive, kUTTypeBzip2Archive, kUTTypeGNUZipArchive, 'com.sun.java-archive', 'org.gnu.gnu-tar-archive', 'public.tar-archive', 'org.gnu.gnu-zip-archive', 'org.gnu.gnu-zip-tar-archive', 'com.apple.binhex-archive', 'com.apple.macbinary-​archive', 'public.cpio-archive', 'com.pkware.zip-archive', kUTTypeWebArchive, kUTTypeZipArchive, ], }; if (+iOSNativeHelper.MajorVersion >= 14) { MediaFileTypes[MediaType.IMAGE] = MediaFileTypes[MediaType.IMAGE].concat([ UTTypeWebP.identifier, UTTypeBMP.identifier, UTTypeGIF.identifier, UTTypeHEIC.identifier, UTTypeHEIF.identifier, UTTypeImage.identifier, UTTypeJPEG.identifier, UTTypeLivePhoto.identifier, UTTypePNG.identifier, UTTypeRAWImage.identifier, UTTypeSVG.identifier, UTTypeTIFF.identifier, ]); MediaFileTypes[MediaType.AUDIO] = MediaFileTypes[MediaType.AUDIO].concat([ UTTypeAIFF.identifier, UTTypeAppleProtectedMPEG4Audio.identifier, UTTypeAudio.identifier, UTTypeMP3.identifier, UTTypeMPEG4Audio.identifier, UTTypeWAV.identifier, ]); MediaFileTypes[MediaType.ARCHIVE] = MediaFileTypes[MediaType.ARCHIVE].concat([ UTTypeAppleArchive.identifier, UTTypeArchive.identifier, UTTypeBZ2.identifier, UTTypeDiskImage.identifier, UTTypeGZIP.identifier, UTTypeZIP.identifier, ]); MediaFileTypes[MediaType.VIDEO] = MediaFileTypes[MediaType.VIDEO].concat([ UTTypeAVI.identifier, UTTypeAppleProtectedMPEG4Video.identifier, UTTypeMPEG.identifier, UTTypeMPEG2TransportStream.identifier, UTTypeMPEG2Video.identifier, UTTypeMPEG4Movie.identifier, UTTypeMovie.identifier, UTTypeQuickTimeMovie.identifier, UTTypeVideo.identifier, ]); MediaFileTypes[MediaType.DOCUMENT] = MediaFileTypes[MediaType.DOCUMENT].concat([ UTTypeCommaSeparatedText.identifier, UTTypeEPUB.identifier, UTTypeFlatRTFD.identifier, UTTypePDF.identifier, UTTypePresentation.identifier, UTTypePlainText.identifier, UTTypeRTF.identifier, UTTypeRTFD.identifier, UTTypeSpreadsheet.identifier, UTTypeTabSeparatedText.identifier, UTTypeText.identifier, ]); } //# sourceMappingURL=index.ios.js.map