Skip to content

Commit ed07560

Browse files
authored
Merge pull request #90 from pcprinz/feature-pick-file-for-windows
feature: `pickFile()` is now implemented for windows
2 parents 8bece2f + 877d790 commit ed07560

File tree

5 files changed

+199
-2
lines changed

5 files changed

+199
-2
lines changed

README.md

+19-2
Original file line numberDiff line numberDiff line change
@@ -872,11 +872,17 @@ For more information read the [Adding an App to an App Group](https://developer.
872872
```ts
873873
function pickFile(options?: PickFileOptionsT): Promise<string[]>;
874874
```
875-
**SUPPORTED**: Android, iOS, macOS. **NOT YET SUPPORTED**: Windows.
875+
**SUPPORTED**: Android, iOS, macOS, Windows.
876876

877877
Prompts the user to select file(s) using a platform-provided file picker UI,
878878
which also allows to access files outside the app sandbox.
879879

880+
**BEWARE:** On **windows** the options differ from the other platform since the
881+
native file piper doesn't support `mimeTypes` but `fileExtensions` and different
882+
picker types like `singleFile`, `multipleFIle` and `folder`. For more information
883+
see [PickFileOptionsT].
884+
885+
880886
**BEWARE:** On **macOS (Catalyst)** for this function to work you MUST go to
881887
_Signing & Capabilities_ settings of your project, and inside its _App Sandbox_
882888
section to set _File Access_ > _User Selected Files_ to _Read/Write_ value
@@ -1404,14 +1410,25 @@ Type of extra options argument for [mkdir()].
14041410
```ts
14051411
type PickFileOptionsT = {
14061412
mimeTypes?: string[];
1413+
pickerType?: 'singleFile' | 'multipleFiles' | 'folder';
1414+
fileExtensions?: FileExtension[]
14071415
};
14081416
```
14091417
Optional parameters for [pickFile()] function.
14101418

1411-
- `mimeTypes` &mdash; **string[]** &mdash; Optional. An array of
1419+
- `mimeTypes` &mdash; **string[]** &mdash; *[Android - iOS - macOS]* Optional. An array of
14121420
[MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types)
14131421
of files user is allowed to select. Defaults to `['*/*']` allowing to select
14141422
any file.
1423+
- `pickerType` &mdash; **'singleFile' | 'multipleFiles' | 'folder'** &mdash; *[Windows]* Optional.
1424+
The type of objects to pick can be either a single file, multiple files or one folder.
1425+
- Multiple folders are not supported by windows.
1426+
- Defaults to `'singleFile'` */
1427+
- `fileExtensions` &mdash; **FileExtension[]** &mdash; *[Windows]* Optional, The file extensions to pick from.
1428+
- Only applies to `pickerType !== 'folder'`
1429+
- Defaults to `[]` (all file extensions) */
1430+
- `FileExtension = ´.${string` - e.g `'.bmp'`, `'.mp3'`
1431+
14151432

14161433
### ReadDirAssetsResItemT
14171434
[ReadDirAssetsResItemT]: #readdirassetsresitemt

src/NativeReactNativeFs.ts

+14
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { TurboModuleRegistry } from 'react-native';
44
// Note: It would be better to have all these type definitions in a dedicated
55
// module, however as of its current version RN's Codegen does not seem to handle
66
// type imports correctly.
7+
// TODO everything needs jsdoc comments. probably the same as in the README.md file.
8+
// It's better to read jsdoc through intellisense than to open and find things in the README.md.
79

810
export type DownloadBeginCallbackResultT = {
911
jobId: number; // The download job ID, required if one wishes to cancel the download. See `stopDownload`.
@@ -40,8 +42,20 @@ export type NativeDownloadFileOptionsT = {
4042
hasResumableCallback: boolean;
4143
};
4244

45+
export type FileExtension = `.${string}`;
4346
export type PickFileOptionsT = {
47+
/** **[Android - iOS - macOS]** The mime types that restrict the type of files that can be picked.
48+
* - For more information, see [Common MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types/Common_types). */
4449
mimeTypes: string[];
50+
/** **[Windows]**
51+
* The type of objects to pick can be either a single file, multiple files or one folder.
52+
* - Multiple folders are not supported by windows.
53+
* - Defaults to `'singleFile'` */
54+
pickerType: 'singleFile' | 'multipleFiles' | 'folder';
55+
/** **[Windows]** The file extensions to pick from.
56+
* - Only applies to `pickerType !== 'folder'`
57+
* - Defaults to `[]` (all file extensions) */
58+
fileExtensions: FileExtension[]
4559
};
4660

4761
export type DownloadFileOptionsT = {

src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,8 @@ export function pickFile(
231231
): Promise<string[]> {
232232
return RNFS.pickFile({
233233
mimeTypes: options.mimeTypes || ['*/*'],
234+
pickerType: options.pickerType || 'singleFile',
235+
fileExtensions: options.fileExtensions || [],
234236
});
235237
}
236238

windows/ReactNativeFs/ReactNativeModule.cpp

+161
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include <winrt/Windows.Web.Http.Headers.h>
1717
#include <winrt/Windows.ApplicationModel.h>
1818
#include <winrt/Windows.Foundation.h>
19+
#include <winrt/Windows.Storage.Pickers.h>
20+
#include <winrt/Windows.Storage.AccessCache.h>
1921

2022
#include "RNFSException.h"
2123

@@ -24,6 +26,7 @@ using namespace winrt::ReactNativeFs;
2426
using namespace winrt::Windows::ApplicationModel;
2527
using namespace winrt::Windows::Storage;
2628
using namespace winrt::Windows::Storage::Streams;
29+
using namespace winrt::Windows::Storage::Pickers;
2730
using namespace winrt::Windows::Foundation;
2831
using namespace winrt::Windows::Web::Http;
2932

@@ -1127,6 +1130,164 @@ IAsyncAction ReactNativeModule::ProcessUploadRequestAsync(ReactPromise<JSValueOb
11271130
}
11281131
}
11291132

1133+
void ReactNativeModule::pickFile(JSValueObject options, ReactPromise<JSValueArray> promise) noexcept
1134+
{
1135+
m_reactContext.UIDispatcher().Post([this, options = std::move(options), promise = std::move(promise)]() mutable {
1136+
try
1137+
{
1138+
// read options
1139+
std::string pickerType = "multipleFiles"; // Default value
1140+
if (options.find("pickerType") != options.end())
1141+
{
1142+
pickerType = options["pickerType"].AsString();
1143+
}
1144+
1145+
std::vector<std::wstring> fileTypes;
1146+
if (options.find("fileExtensions") != options.end())
1147+
{
1148+
for (const auto& mimeType : options["fileExtensions"].AsArray())
1149+
{
1150+
fileTypes.push_back(std::wstring(winrt::to_hstring(mimeType.AsString())));
1151+
}
1152+
}
1153+
1154+
// folder picker
1155+
if (pickerType == "folder")
1156+
{
1157+
FolderPicker picker;
1158+
picker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary);
1159+
picker.FileTypeFilter().Append(L"*");
1160+
1161+
picker.PickSingleFolderAsync().Completed([promise = std::move(promise)](IAsyncOperation<StorageFolder> const& operation, AsyncStatus const status) mutable {
1162+
try
1163+
{
1164+
if (status == AsyncStatus::Completed)
1165+
{
1166+
StorageFolder folder = operation.GetResults();
1167+
if (folder)
1168+
{
1169+
JSValueArray result;
1170+
result.push_back(JSValueObject{
1171+
{"name", winrt::to_string(folder.Name())},
1172+
{"path", winrt::to_string(folder.Path())}
1173+
});
1174+
promise.Resolve(std::move(result));
1175+
}
1176+
else
1177+
{
1178+
promise.Reject("No folder was picked.");
1179+
}
1180+
}
1181+
else
1182+
{
1183+
promise.Reject("Folder picker operation was not completed.");
1184+
}
1185+
}
1186+
catch (const hresult_error& ex)
1187+
{
1188+
promise.Reject(winrt::to_string(ex.message()).c_str());
1189+
}
1190+
});
1191+
}
1192+
1193+
// file picker
1194+
else
1195+
{
1196+
FileOpenPicker picker;
1197+
picker.ViewMode(PickerViewMode::Thumbnail);
1198+
picker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary);
1199+
1200+
if (!fileTypes.empty())
1201+
{
1202+
for (const auto& fileType : fileTypes)
1203+
{
1204+
picker.FileTypeFilter().Append(fileType);
1205+
}
1206+
}
1207+
else
1208+
{
1209+
picker.FileTypeFilter().Append(L"*");
1210+
}
1211+
1212+
// single files
1213+
if (pickerType == "singleFile")
1214+
{
1215+
picker.PickSingleFileAsync().Completed([promise = std::move(promise)](IAsyncOperation<StorageFile> const& operation, AsyncStatus const status) mutable {
1216+
try
1217+
{
1218+
if (status == AsyncStatus::Completed)
1219+
{
1220+
StorageFile file = operation.GetResults();
1221+
if (file)
1222+
{
1223+
JSValueArray result;
1224+
result.push_back(winrt::to_string(file.Path()));
1225+
promise.Resolve(std::move(result));
1226+
}
1227+
else
1228+
{
1229+
promise.Reject("No file was picked.");
1230+
}
1231+
}
1232+
else
1233+
{
1234+
promise.Reject("File picker operation was not completed.");
1235+
}
1236+
}
1237+
catch (const hresult_error& ex)
1238+
{
1239+
promise.Reject(winrt::to_string(ex.message()).c_str());
1240+
}
1241+
});
1242+
}
1243+
1244+
// multiple files
1245+
else if (pickerType == "multipleFiles")
1246+
{
1247+
picker.PickMultipleFilesAsync().Completed([promise = std::move(promise)](IAsyncOperation<IVectorView<StorageFile>> const& operation, AsyncStatus const status) mutable {
1248+
try
1249+
{
1250+
if (status == AsyncStatus::Completed)
1251+
{
1252+
auto files = operation.GetResults();
1253+
if (files.Size() > 0)
1254+
{
1255+
JSValueArray result;
1256+
for (const auto& file : files)
1257+
{
1258+
result.push_back(winrt::to_string(file.Path()));
1259+
}
1260+
promise.Resolve(std::move(result));
1261+
}
1262+
else
1263+
{
1264+
promise.Reject("No files were picked.");
1265+
}
1266+
}
1267+
else
1268+
{
1269+
promise.Reject("File picker operation was not completed.");
1270+
}
1271+
}
1272+
catch (const hresult_error& ex)
1273+
{
1274+
promise.Reject(winrt::to_string(ex.message()).c_str());
1275+
}
1276+
});
1277+
}
1278+
else
1279+
{
1280+
promise.Reject("Invalid pickerType option.");
1281+
}
1282+
}
1283+
}
1284+
catch (const hresult_error& ex)
1285+
{
1286+
promise.Reject(winrt::to_string(ex.message()).c_str());
1287+
}
1288+
});
1289+
}
1290+
11301291
void ReactNativeModule::addListener(std::string eventName) noexcept
11311292
{
11321293
// Keep: Required for RN built in Event Emitter Calls.

windows/ReactNativeFs/ReactNativeModule.h

+3
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ struct ReactNativeModule
167167
REACT_METHOD(removeListeners);
168168
void removeListeners(int count) noexcept;
169169

170+
REACT_METHOD(pickFile);
171+
void pickFile(JSValueObject options, ReactPromise<JSValueArray> promise) noexcept;
172+
170173
private:
171174
void splitPath(const std::wstring& fullPath, winrt::hstring& directoryPath, winrt::hstring& fileName) noexcept;
172175

0 commit comments

Comments
 (0)