From df783cb0ed12d5859f0c6f5de04afb13668a01e1 Mon Sep 17 00:00:00 2001 From: Matteo Pagani Date: Mon, 2 Aug 2021 22:06:00 +0200 Subject: [PATCH 1/2] feat: added pickDirectory implementation for Windows --- README.md | 6 +- src/index.tsx | 4 +- .../RCTDocumentPickerModule.cs | 366 ++++++++++-------- 3 files changed, 207 insertions(+), 169 deletions(-) diff --git a/README.md b/README.md index 7df98a89..ca1369e3 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ If you are using RN >= 0.63, only run `pod install` from the ios directory. Then Use `pickMultiple`, `pickSingle` or `pick` to open a document picker for the user to select file(s). All methods return a Promise. -#### [Android only] `DocumentPicker.pickDirectory()` +#### [Android and Windows only] `DocumentPicker.pickDirectory()` Open a system directory picker. Returns a promise that resolves to (`{ uri: string }`) of the directory selected by user. @@ -77,7 +77,7 @@ If specified, the picked file is copied to `NSCachesDirectory` / `NSDocumentDire This should help if you need to work with the file(s) later on, because by default, [the picked documents are temporary files. They remain available only until your application terminates](https://developer.apple.com/documentation/uikit/uidocumentpickerdelegate/2902364-documentpicker). This may impact performance for large files, so keep this in mind if you expect users to pick particularly large files and your app does not need immediate read access. -##### [UWP only] `readContent`:`boolean` +##### [Windows only] `readContent`:`boolean` Defaults to `false`. If `readContent` is set to true the content of the picked file/files will be read and supplied in the result object. @@ -118,7 +118,7 @@ The display name of the file. _This is normally the filename of the file, but An The file size of the document. _On Android some DocumentProviders may not provide this information for a document._ -##### [UWP only] `content`: +##### [Windows only] `content`: The base64 encoded content of the picked file if the option `readContent` was set to `true`. diff --git a/src/index.tsx b/src/index.tsx index e795ee57..f0adbe0d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -47,10 +47,10 @@ type DocumentPickerOptions = { } export function pickDirectory(): Promise { - if (Platform.OS === 'android') { + if (Platform.OS === 'android' || Platform.OS === 'windows') { return RNDocumentPicker.pickDirectory() } else { - // TODO windows impl + // TODO iOS impl return Promise.resolve(null) } } diff --git a/windows/ReactNativeDocumentPicker/RCTDocumentPickerModule.cs b/windows/ReactNativeDocumentPicker/RCTDocumentPickerModule.cs index 1a409c15..7ab3ffd1 100644 --- a/windows/ReactNativeDocumentPicker/RCTDocumentPickerModule.cs +++ b/windows/ReactNativeDocumentPicker/RCTDocumentPickerModule.cs @@ -1,4 +1,4 @@ -using Microsoft.ReactNative.Managed; +using Microsoft.ReactNative.Managed; using System; using System.Collections.Generic; using System.IO; @@ -12,119 +12,157 @@ namespace RNDocumentPicker { - [ReactModule("RNDocumentPicker")] - internal sealed class RCTDocumentPickerModule + [ReactModule("RNDocumentPicker")] + internal sealed class RCTDocumentPickerModule + { + private static readonly string OPTION_TYPE = "type"; + private static readonly string CACHE_TYPE = "cache"; + private static readonly string OPTION_MULIPLE = "allowMultiSelection"; + private static readonly string OPTION_READ_CONTENT = "readContent"; + private static readonly string FIELD_URI = "uri"; + private static readonly string FIELD_FILE_COPY_URI = "fileCopyUri"; + private static readonly string FIELD_NAME = "name"; + private static readonly string FIELD_TYPE = "type"; + private static readonly string FIELD_SIZE = "size"; + private static readonly string FIELD_CONTENT = "content"; + + + [ReactMethod("pick")] + public async Task> Pick(JSValue options) { - private static readonly string OPTION_TYPE = "type"; - private static readonly string CACHE_TYPE = "cache"; - private static readonly string OPTION_MULIPLE = "allowMultiSelection"; - private static readonly string OPTION_READ_CONTENT = "readContent"; - private static readonly string FIELD_URI = "uri"; - private static readonly string FIELD_FILE_COPY_URI = "fileCopyUri"; - private static readonly string FIELD_NAME = "name"; - private static readonly string FIELD_TYPE = "type"; - private static readonly string FIELD_SIZE = "size"; - private static readonly string FIELD_CONTENT = "content"; - - - [ReactMethod("pick")] - public async Task> Pick(JSValue options) + FileOpenPicker openPicker = new FileOpenPicker(); + openPicker.ViewMode = PickerViewMode.Thumbnail; + openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; + + // Get file type array options + + var fileTypes = options.AsObject()[OPTION_TYPE].AsArray(); + + //var fileTypeArray = options.AsObject()[OPTION_TYPE][0].AsString(); + bool cache = false; + if (options.AsObject().ContainsKey(CACHE_TYPE)) + { + cache = options.AsObject()[CACHE_TYPE][0].AsBoolean(); + } + + var isMultiple = options.AsObject()[OPTION_MULIPLE].AsBoolean(); + bool readContent = false; + if (options.AsObject().ContainsKey(OPTION_READ_CONTENT)) + { + readContent = options.AsObject()[OPTION_READ_CONTENT].AsBoolean(); + } + + //if pick called to launch folder picker. + bool isFolderPicker = false; + + // Init file type filter + if (fileTypes != null) + { + if (fileTypes.Contains("folder")) { - FileOpenPicker openPicker = new FileOpenPicker(); - openPicker.ViewMode = PickerViewMode.Thumbnail; - openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; - - // Get file type array options - - var fileTypeArray = options.AsObject()[OPTION_TYPE][0].AsString(); - bool cache = false; - if (options.AsObject().ContainsKey(CACHE_TYPE)) - { - cache = options.AsObject()[CACHE_TYPE][0].AsBoolean(); - } + isFolderPicker = true; + } - var isMultiple = options.AsObject()[OPTION_MULIPLE].AsBoolean(); - bool readContent = false; - if (options.AsObject().ContainsKey(OPTION_READ_CONTENT)) + foreach (var type in fileTypes) + { + var item = type.AsString(); + var list = item.Split(" "); + foreach (var extension in list) + { + if (Regex.Match(extension, "(^[.]+[A-Za-z0-9]*$)|(^[*]$)").Success) { - readContent = options.AsObject()[OPTION_READ_CONTENT].AsBoolean(); + openPicker.FileTypeFilter.Add(extension); } + } + } + } + else + { + openPicker.FileTypeFilter.Add("*"); + } + + List result; + if (isFolderPicker) + { + var openFolderPicker = new FolderPicker(); + + openFolderPicker.ViewMode = PickerViewMode.List; + openFolderPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; + openFolderPicker.FileTypeFilter.Add("*"); + + result = await PickFolderAsync(openFolderPicker, cache, readContent); + + } + else + { + if (isMultiple) + { + result = await PickMultipleFileAsync(openPicker, cache, readContent); + } + else + { + result = await PickSingleFileAsync(openPicker, cache, readContent); + } + } - //if pick called to launch folder picker. - bool isFolderPicker = false; - - // Init file type filter - if (fileTypeArray != null) - { - if (fileTypeArray.Contains("folder")) - { - isFolderPicker = true; - } - - List types = fileTypeArray.Split(' ').ToList(); - foreach (string type in types) - { - if (Regex.Match(type, "(^[.]+[A-Za-z0-9]*$)|(^[*]$)").Success) - { - openPicker.FileTypeFilter.Add(type); - } - } - } - else - { - openPicker.FileTypeFilter.Add("*"); - } + return result; + } - List result; - if (isFolderPicker) - { - var openFolderPicker = new FolderPicker(); + [ReactMethod("pickDirectory")] + public async Task PickDirectory() + { + TaskCompletionSource tcs = new TaskCompletionSource(); - openFolderPicker.ViewMode = PickerViewMode.List; - openFolderPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; - openFolderPicker.FileTypeFilter.Add("*"); + await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => + { + var openFolderPicker = new FolderPicker(); - result = await PickFolderAsync(openFolderPicker, cache, readContent); + openFolderPicker.ViewMode = PickerViewMode.List; + openFolderPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary; + openFolderPicker.FileTypeFilter.Add("*"); - } - else - { - if (isMultiple) - { - result = await PickMultipleFileAsync(openPicker, cache, readContent); - } - else + var folder = await openFolderPicker.PickSingleFolderAsync(); + if (folder != null) + { + JSValueObject obj = new JSValueObject { - result = await PickSingleFileAsync(openPicker, cache, readContent); - } - } + { "uri", folder.Path } + }; - return result; + tcs.SetResult(obj); } + else + { + tcs.SetResult(null); + } + }); - private async Task PrepareFile(StorageFile file, bool cache, bool readContent) + var result = await tcs.Task; + return result; + } + private async Task PrepareFile(StorageFile file, bool cache, bool readContent) + { + string base64Content = null; + if (readContent) + { + var fileStream = await file.OpenReadAsync(); + using (StreamReader reader = new StreamReader(fileStream.AsStream())) { - string base64Content = null; - if (readContent) - { - var fileStream = await file.OpenReadAsync(); - using (StreamReader reader = new StreamReader(fileStream.AsStream())) - { - using (var memstream = new MemoryStream()) - { - await reader.BaseStream.CopyToAsync(memstream); - var bytes = memstream.ToArray(); - base64Content = Convert.ToBase64String(bytes); - } - } - } + using (var memstream = new MemoryStream()) + { + await reader.BaseStream.CopyToAsync(memstream); + var bytes = memstream.ToArray(); + base64Content = Convert.ToBase64String(bytes); + } + } + } - if (cache == true) - { - var fileInCache = await file.CopyAsync(ApplicationData.Current.TemporaryFolder, file.Name.ToString(), NameCollisionOption.ReplaceExisting); - var basicProperties = await fileInCache.GetBasicPropertiesAsync(); + if (cache == true) + { + var fileInCache = await file.CopyAsync(ApplicationData.Current.TemporaryFolder, file.Name.ToString(), NameCollisionOption.ReplaceExisting); + var basicProperties = await fileInCache.GetBasicPropertiesAsync(); - JSValueObject result = new JSValueObject + JSValueObject result = new JSValueObject { { FIELD_URI, file.Path }, { FIELD_FILE_COPY_URI, file.Path }, @@ -134,13 +172,13 @@ private async Task PrepareFile(StorageFile file, bool cache, bool { FIELD_CONTENT, base64Content } }; - return result; - } - else - { - var basicProperties = await file.GetBasicPropertiesAsync(); + return result; + } + else + { + var basicProperties = await file.GetBasicPropertiesAsync(); - JSValueObject result = new JSValueObject + JSValueObject result = new JSValueObject { { FIELD_URI, file.Path }, { FIELD_FILE_COPY_URI, file.Path }, @@ -150,78 +188,78 @@ private async Task PrepareFile(StorageFile file, bool cache, bool { FIELD_CONTENT, base64Content } }; - return result; - } - } + return result; + } + } - private async Task> PickMultipleFileAsync(FileOpenPicker picker, bool cache, bool readContent) - { - TaskCompletionSource> tcs = new TaskCompletionSource>(); + private async Task> PickMultipleFileAsync(FileOpenPicker picker, bool cache, bool readContent) + { + TaskCompletionSource> tcs = new TaskCompletionSource>(); - await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => - { - IReadOnlyList files = await picker.PickMultipleFilesAsync(); - if (files.Count > 0) - { - List jarrayObj = new List(); - foreach (var file in files) - { - var processedFile = await PrepareFile(file, cache, readContent); - jarrayObj.Add(processedFile); - } - - tcs.SetResult(jarrayObj); - } - }); - - var result = await tcs.Task; - return result; + await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => + { + IReadOnlyList files = await picker.PickMultipleFilesAsync(); + if (files.Count > 0) + { + List jarrayObj = new List(); + foreach (var file in files) + { + var processedFile = await PrepareFile(file, cache, readContent); + jarrayObj.Add(processedFile); + } + + tcs.SetResult(jarrayObj); } + }); + + var result = await tcs.Task; + return result; + } + + private async Task> PickSingleFileAsync(FileOpenPicker picker, bool cache, bool readContent) + { + TaskCompletionSource tcs = new TaskCompletionSource(); - private async Task> PickSingleFileAsync(FileOpenPicker picker, bool cache, bool readContent) + await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => + { + var file = await picker.PickSingleFileAsync(); + if (file != null) { - TaskCompletionSource tcs = new TaskCompletionSource(); + var processedFile = await PrepareFile(file, cache, readContent); + tcs.SetResult(processedFile); + } + }); - await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => - { - var file = await picker.PickSingleFileAsync(); - if (file != null) - { - var processedFile = await PrepareFile(file, cache, readContent); - tcs.SetResult(processedFile); - } - }); + var result = await tcs.Task; - var result = await tcs.Task; + List list = new List() { result }; - List list = new List() { result }; + return list; + } - return list; - } + private async Task> PickFolderAsync(FolderPicker picker, bool cache, bool readContent) + { + TaskCompletionSource> tcs = new TaskCompletionSource>(); - private async Task> PickFolderAsync(FolderPicker picker, bool cache, bool readContent) + await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => + { + var folder = await picker.PickSingleFolderAsync(); + if (folder != null) { - TaskCompletionSource> tcs = new TaskCompletionSource>(); - - await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => - { - var folder = await picker.PickSingleFolderAsync(); - if (folder != null) - { - List jarrayObj = new List(); - var files = await folder.GetFilesAsync(); - foreach (var file in files) - { - var preparedFile = await PrepareFile(file, cache, readContent); - jarrayObj.Add(preparedFile); - } - - tcs.SetResult(jarrayObj); - } - }); - - var result = await tcs.Task; - return result; + List jarrayObj = new List(); + var files = await folder.GetFilesAsync(); + foreach (var file in files) + { + var preparedFile = await PrepareFile(file, cache, readContent); + jarrayObj.Add(preparedFile); + } + + tcs.SetResult(jarrayObj); } + }); + + var result = await tcs.Task; + return result; } + } } From c0f9d90655288c0fcba69bc9d1c067fabf3e9a94 Mon Sep 17 00:00:00 2001 From: Matteo Pagani Date: Mon, 2 Aug 2021 22:06:29 +0200 Subject: [PATCH 2/2] fix: fixed typo in property name --- .../ReactNativeDocumentPicker/RCTDocumentPickerModule.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/windows/ReactNativeDocumentPicker/RCTDocumentPickerModule.cs b/windows/ReactNativeDocumentPicker/RCTDocumentPickerModule.cs index 7ab3ffd1..01be093c 100644 --- a/windows/ReactNativeDocumentPicker/RCTDocumentPickerModule.cs +++ b/windows/ReactNativeDocumentPicker/RCTDocumentPickerModule.cs @@ -17,7 +17,7 @@ internal sealed class RCTDocumentPickerModule { private static readonly string OPTION_TYPE = "type"; private static readonly string CACHE_TYPE = "cache"; - private static readonly string OPTION_MULIPLE = "allowMultiSelection"; + private static readonly string OPTION_MULTIPLE = "allowMultiSelection"; private static readonly string OPTION_READ_CONTENT = "readContent"; private static readonly string FIELD_URI = "uri"; private static readonly string FIELD_FILE_COPY_URI = "fileCopyUri"; @@ -45,7 +45,7 @@ public async Task> Pick(JSValue options) cache = options.AsObject()[CACHE_TYPE][0].AsBoolean(); } - var isMultiple = options.AsObject()[OPTION_MULIPLE].AsBoolean(); + var isMultiple = options.AsObject()[OPTION_MULTIPLE].AsBoolean(); bool readContent = false; if (options.AsObject().ContainsKey(OPTION_READ_CONTENT)) { @@ -133,7 +133,7 @@ await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPrio } else { - tcs.SetResult(null); + tcs.SetResult(new JSValueObject()); } });