Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19,402 changes: 19,402 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,8 @@
"prettier": "^3.4.2",
"typescript": "^5.7.3"
},
"devDependencies": {
"@react-native-community/cli": "latest"
},
"packageManager": "yarn@4.6.0"
}
17 changes: 17 additions & 0 deletions packages/document-picker/macos/RCTConvert+RNDocumentPicker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// LICENSE: see License.md in the package root

#import <React/RCTConvert.h>

// When using use_frameworks! :linkage => :static in Podfile
#if __has_include(<react_native_document_picker/react_native_document_picker-Swift.h>)
#import <react_native_document_picker/react_native_document_picker-Swift.h>
#else
#import "react_native_document_picker-Swift.h"
#endif

@interface RCTConvert (RNDocumentPicker)

+ (PickerOptions *)PickerOptions:(id)json;
+ (SaverOptions *)SaverOptions:(id)json;

@end
67 changes: 67 additions & 0 deletions packages/document-picker/macos/RCTConvert+RNDocumentPicker.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// LICENSE: see License.md in the package root

#import "RCTConvert+RNDocumentPicker.h"
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>

@implementation RCTConvert (RNDocumentPicker)

+ (PickerOptions *)PickerOptions:(id)json
{
NSDictionary *options = [RCTConvert NSDictionary:json];
PickerOptions *pickerOptions = [[PickerOptions alloc] init];

// Set default values
pickerOptions.allowMultiSelection = NO;
pickerOptions.mode = @"open";
pickerOptions.copyTo = @"cachesDirectory";

if (options[@"allowMultiSelection"]) {
pickerOptions.allowMultiSelection = [RCTConvert BOOL:options[@"allowMultiSelection"]];
}

if (options[@"mode"]) {
pickerOptions.mode = [RCTConvert NSString:options[@"mode"]];
}

if (options[@"copyTo"]) {
pickerOptions.copyTo = [RCTConvert NSString:options[@"copyTo"]];
}

if (options[@"type"]) {
NSArray *types = [RCTConvert NSArray:options[@"type"]];
NSMutableArray<UTType *> *utTypes = [[NSMutableArray alloc] init];

for (NSString *typeString in types) {
UTType *utType = [UTType typeWithIdentifier:typeString];
if (utType) {
[utTypes addObject:utType];
}
}

pickerOptions.allowedTypes = [utTypes copy];
}

return pickerOptions;
}

+ (SaverOptions *)SaverOptions:(id)json
{
NSDictionary *options = [RCTConvert NSDictionary:json];
SaverOptions *saverOptions = [[SaverOptions alloc] init];

if (options[@"fileName"]) {
saverOptions.fileName = [RCTConvert NSString:options[@"fileName"]];
}

if (options[@"data"]) {
saverOptions.data = [RCTConvert NSString:options[@"data"]];
}

if (options[@"uri"]) {
saverOptions.uri = [RCTConvert NSString:options[@"uri"]];
}

return saverOptions;
}

@end
19 changes: 19 additions & 0 deletions packages/document-picker/macos/RNDocumentPicker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// LICENSE: see License.md in the package root


#ifdef RCT_NEW_ARCH_ENABLED
#import <rndocumentpickerCGen/rndocumentpickerCGen.h>
#else
#import <React/RCTBridgeModule.h>
#endif

@interface RNDocumentPicker : NSObject <
#ifdef RCT_NEW_ARCH_ENABLED
NativeDocumentPickerSpec
#else
RCTBridgeModule
#endif
>


@end
108 changes: 108 additions & 0 deletions packages/document-picker/macos/RNDocumentPicker.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// LICENSE: see License.md in the package root


#import "RNDocumentPicker.h"

#import "RCTConvert+RNDocumentPicker.h"
// this header file is generated by Xcode: https://developer.apple.com/documentation/swift/importing-swift-into-objective-c
// if it cannot be found, try cleaning the build folder and Xcode derived data folder

// When using use_frameworks! :linkage => :static in Podfile
#if __has_include(<react_native_document_picker/react_native_document_picker-Swift.h>)
#import <react_native_document_picker/react_native_document_picker-Swift.h>
#else
#import "react_native_document_picker-Swift.h"
#endif

@interface RNDocumentPicker ()
@end

@implementation RNDocumentPicker {
DocPicker *docPicker;
DocSaver *docSaver;
}

- (instancetype)init {
if ((self = [super init])) {
docPicker = [DocPicker new];
docSaver = [DocSaver new];
}
return self;
}

+ (BOOL)requiresMainQueueSetup {
return NO;
}

RCT_EXPORT_MODULE()

RCT_EXPORT_METHOD(pick:
(NSDictionary *) options
resolve:
(RCTPromiseResolveBlock) resolve
reject:
(RCTPromiseRejectBlock) reject)
{
PickerOptions *pickerOptions = [RCTConvert PickerOptions:options];
[docPicker present:pickerOptions resolve:resolve reject:reject];
}

RCT_EXPORT_METHOD(pickDirectory:
(NSDictionary *) options
resolve:
(RCTPromiseResolveBlock) resolve
reject:
(RCTPromiseRejectBlock) reject)
{
PickerOptions *pickerOptions = [RCTConvert PickerOptions:options];
[docPicker presentDirectory:pickerOptions resolve:resolve reject:reject];
}

RCT_EXPORT_METHOD(saveDocument:
(NSDictionary *) options
resolve:
(RCTPromiseResolveBlock) resolve
reject:
(RCTPromiseRejectBlock) reject)
{
SaverOptions *saverOptions = [RCTConvert SaverOptions:options];
[docSaver present:saverOptions resolve:resolve reject:reject];
}

RCT_EXPORT_METHOD(releaseSecurityScopedResource:
(NSString *) uri
resolve:
(RCTPromiseResolveBlock) resolve
reject:
(RCTPromiseRejectBlock) reject)
{
NSURL *url = [NSURL URLWithString:uri];
if (url == nil) {
reject(@"INVALID_URI", @"Invalid URI provided", nil);
return;
}

[url stopAccessingSecurityScopedResource];
resolve(@(YES));
}

RCT_EXPORT_METHOD(isKnownType:
(NSString *) type
resolve:
(RCTPromiseResolveBlock) resolve
reject:
(RCTPromiseRejectBlock) reject)
{
BOOL isKnown = [IsKnownTypeImpl isKnownType:type];
resolve(@(isKnown));
}

#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeDocumentPickerSpecJSI>(params);
}
#endif

@end
101 changes: 101 additions & 0 deletions packages/document-picker/macos/swift/DocPicker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// LICENSE: see License.md in the package root

import Foundation
import UniformTypeIdentifiers
import CoreServices
import AppKit

@objc public class DocPicker: PickerWithMetadataImpl {

var currentOptions: PickerOptions? = nil

@objc public func present(options: PickerOptions, resolve: @escaping RNDPPromiseResolveBlock, reject: @escaping RNDPPromiseRejectBlock) {
if (!promiseWrapper.trySetPromiseRejectingIncoming(resolve, rejecter: reject, fromCallSite: "pick")) {
return;
}
currentOptions = options;
DispatchQueue.main.async {
let openPanel = NSOpenPanel()

openPanel.allowsMultipleSelection = options.allowMultiSelection
openPanel.canChooseDirectories = false
openPanel.canChooseFiles = true
openPanel.canCreateDirectories = false

// Set allowed file types
if !options.allowedTypes.isEmpty {
var allowedExtensions: [String] = []
for utType in options.allowedTypes {
if let extensions = utType.tags[.filenameExtension] {
allowedExtensions.append(contentsOf: extensions)
}
}
if !allowedExtensions.isEmpty {
openPanel.allowedContentTypes = options.allowedTypes
}
}

openPanel.begin { (result) in
if result == .OK {
self.handlePickerResult(urls: openPanel.urls)
} else {
self.promiseWrapper.reject(fromCallSite: "pick", code: "DOCUMENT_PICKER_CANCELED", message: "User canceled document picker", error: nil)
}
}
}
}

@objc public func presentDirectory(options: PickerOptions, resolve: @escaping RNDPPromiseResolveBlock, reject: @escaping RNDPPromiseRejectBlock) {
if (!promiseWrapper.trySetPromiseRejectingIncoming(resolve, rejecter: reject, fromCallSite: "pickDirectory")) {
return;
}
currentOptions = options;
DispatchQueue.main.async {
let openPanel = NSOpenPanel()

openPanel.allowsMultipleSelection = options.allowMultiSelection
openPanel.canChooseDirectories = true
openPanel.canChooseFiles = false
openPanel.canCreateDirectories = false

openPanel.begin { (result) in
if result == .OK {
self.handlePickerResult(urls: openPanel.urls)
} else {
self.promiseWrapper.reject(fromCallSite: "pickDirectory", code: "DOCUMENT_PICKER_CANCELED", message: "User canceled directory picker", error: nil)
}
}
}
}

public func getMetadataFor(url: URL) throws -> DocumentMetadataBuilder {
return if (currentOptions?.isOpenMode() == true) {
try self.getOpenedDocumentInfo(url: url, requestLongTermAccess: currentOptions?.requestLongTermAccess ?? false)
} else {
try self.getAnyModeMetadata(url: url)
}
}

private func getAnyModeMetadata(url: URL) throws -> DocumentMetadataBuilder {
let resourceValues = try url.resourceValues(forKeys: [.fileSizeKey, .nameKey, .isDirectoryKey, .contentTypeKey])

return DocumentMetadataBuilder(forUri: url, resourceValues: resourceValues)
}

enum KeepLocalCopyError: Error {
case sourceAccessError
}

func getOpenedDocumentInfo(url: URL, requestLongTermAccess: Bool) throws -> DocumentMetadataBuilder {
// On macOS, we don't need security scoped resources for files picked by NSOpenPanel
// They are automatically accessible
defer {
// No need to stop accessing on macOS for NSOpenPanel results
}

let resourceValues = try url.resourceValues(forKeys: [.fileSizeKey, .nameKey, .isDirectoryKey, .contentTypeKey])
let builder = DocumentMetadataBuilder(forUri: url, resourceValues: resourceValues)

return builder
}
}
Loading