├── .gitattributes
├── .gitignore
├── .npmignore
├── README.md
├── android
├── README.md
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── reactlibrary
│ ├── MediaClipboardModule.java
│ └── MediaClipboardPackage.java
├── index.js
├── ios
├── MediaClipboard-Bridging-Header.h
├── MediaClipboard.h
├── MediaClipboard.swift
├── MediaClipboard.xcodeproj
│ └── project.pbxproj
├── MediaClipboard.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── MediaClipboardC++Interop.h
├── MediaClipboardC++Interop.mm
├── MediaClipboardJSI.h
├── MediaClipboardJSI.mm
├── MimeType.swift
├── YeetJSIUtils.h
└── YeetJSIUtils.mm
├── package.json
├── react-native-media-clipboard.podspec
├── scripts
└── examples_postinstall.js
├── src
├── MediaClipboard.ts
├── MediaClipboardContext.tsx
└── index.ts
├── tsconfig.json
└── yarn.lock
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # node.js
6 | #
7 | node_modules/
8 | npm-debug.log
9 | yarn-error.log
10 |
11 | dist
12 |
13 | # Xcode
14 | #
15 | build/
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 | xcuserdata
25 | *.xccheckout
26 | *.moved-aside
27 | DerivedData
28 | *.hmap
29 | *.ipa
30 | *.xcuserstate
31 | project.xcworkspace
32 |
33 | # Android/IntelliJ
34 | #
35 | build/
36 | .idea
37 | .gradle
38 | local.properties
39 | *.iml
40 |
41 | # BUCK
42 | buck-out/
43 | \.buckd/
44 | *.keystore
45 |
46 | ios/Build
47 | ios/DerivedData
48 | ios/Pods
49 | lib/
50 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | example
2 | node_modules
3 | ios/Pods
4 | ios/Build
5 | ios/DerivedData
6 | ios/Pods
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-media-clipboard
2 |
3 |
4 | React Native has several libraries that let you get the contents of the clipboard, but none of them support images.
5 |
6 | `react-native-media-clipboard` suports:
7 |
8 | - images
9 | - strings
10 | - URLs
11 |
12 | ## Getting started
13 |
14 | `$ npm install react-native-media-clipboard --save`
15 |
16 | ### Installation (iOS only)
17 |
18 | 1. `cd ios && pod install`
19 | 2. Add the following line near the top of `AppDelegate.h`:
20 |
21 | ```h
22 | #import
23 | ```
24 |
25 | 3. [Optional] Inside the AppDelegate `@implementation` add this:
26 |
27 | ```objc
28 | - (void)applicationDidBecomeActive:(UIApplication *)application {
29 | [MediaClipboard onApplicationBecomeActive];
30 | }
31 | ```
32 |
33 | This makes sure that the clipboard is in sync if the application went into the background.
34 |
35 | ##### Swift bridging header
36 |
37 | If your project does not contain any Swift code, then you need to create a bridging header – or you'll get a bunch of strange build errors.
38 |
39 | 4. Xcode -> File -> New -> Create an empty .swift file. It will prompt you asking if you want to create a bridging header. Say yes.
40 |
41 | If your project already has Swift code (or a bridging header), just ignore this step.
42 |
43 | 5. Re-build your app: `react-native run-ios`
44 |
45 | ## Usage
46 |
47 | ```javascript
48 | import {
49 | ClipboardContext,
50 | ClipboardProvider
51 | } from "react-native-media-clipboard";
52 | ```
53 |
54 | 7. At the root of your application, add `` in the render method, like this:
55 |
56 | ```javascript
57 |
58 | {children}
59 |
60 | ```
61 |
62 | 8. `ClipboardContext` contains a `clipboard` and a `mediaSource` object. It automatically updates whenever the user copies something to their clipboard or removes something from their clipboard.
63 |
64 | ```javascript
65 | const { clipboard, mediaSource } = React.useContext(ClipboardContext);
66 |
67 | // Example mediaSource:
68 | {
69 | "mimeType": "image/png",
70 | "scale": 1,
71 | "width": 828,
72 | "uri": "file:///tmp/C4A65610-E644-44C2-AC54-25A8AD56A4C6.png",
73 | "height": 1792
74 | }
75 |
76 | // Example clipboard:
77 | {
78 | urls: [],
79 | strings: [],
80 | hasImages: false,
81 | hasURLs: false,
82 | hasStrings: false
83 | };
84 |
85 | // You can just pass in the `mediaSource` object to the built-in Image component. As long as the mediaSource object is not null, it should just work.
86 |
87 | ```
88 |
89 | There are type definitions for these, so you shouldn't need to refer back to this much.
90 |
91 | ---
92 |
93 | This library is iOS only. There is no Android support.
94 |
95 | Images are saved in the temporary directory for the app in a background thread. It does not send `data` URIs across the bridge.
96 |
97 | There is a JSI implementation of this as well, however I haven't finished porting it to this library. A contributor is welcome to submit a PR for that :)
98 |
99 | ### Example repo
100 |
101 | Example repo: [react-native-media-clipboard-example](https://github.com/Jarred-Sumner/react-native-media-clipboard-example)
102 |
103 |
104 |
--------------------------------------------------------------------------------
/android/README.md:
--------------------------------------------------------------------------------
1 | README
2 | ======
3 |
4 | If you want to publish the lib as a maven dependency, follow these steps before publishing a new version to npm:
5 |
6 | 1. Be sure to have the Android [SDK](https://developer.android.com/studio/index.html) and [NDK](https://developer.android.com/ndk/guides/index.html) installed
7 | 2. Be sure to have a `local.properties` file in this folder that points to the Android SDK and NDK
8 | ```
9 | ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle
10 | sdk.dir=/Users/{username}/Library/Android/sdk
11 | ```
12 | 3. Delete the `maven` folder
13 | 4. Run `./gradlew installArchives`
14 | 5. Verify that latest set of generated files is in the maven folder with the correct version number
15 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | // android/build.gradle
2 |
3 | // based on:
4 | //
5 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/build.gradle
6 | // original location:
7 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/build.gradle
8 | //
9 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle
10 | // original location:
11 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle
12 |
13 | def DEFAULT_COMPILE_SDK_VERSION = 28
14 | def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3'
15 | def DEFAULT_MIN_SDK_VERSION = 16
16 | def DEFAULT_TARGET_SDK_VERSION = 28
17 |
18 | def safeExtGet(prop, fallback) {
19 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
20 | }
21 |
22 | apply plugin: 'com.android.library'
23 | apply plugin: 'maven'
24 |
25 | buildscript {
26 | // The Android Gradle plugin is only required when opening the android folder stand-alone.
27 | // This avoids unnecessary downloads and potential conflicts when the library is included as a
28 | // module dependency in an application project.
29 | // ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies
30 | if (project == rootProject) {
31 | repositories {
32 | google()
33 | jcenter()
34 | }
35 | dependencies {
36 | classpath 'com.android.tools.build:gradle:3.4.1'
37 | }
38 | }
39 | }
40 |
41 | apply plugin: 'com.android.library'
42 | apply plugin: 'maven'
43 |
44 | android {
45 | compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
46 | buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
47 | defaultConfig {
48 | minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION)
49 | targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION)
50 | versionCode 1
51 | versionName "1.0"
52 | }
53 | lintOptions {
54 | abortOnError false
55 | }
56 | }
57 |
58 | repositories {
59 | // ref: https://www.baeldung.com/maven-local-repository
60 | mavenLocal()
61 | maven {
62 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
63 | url "$rootDir/../node_modules/react-native/android"
64 | }
65 | maven {
66 | // Android JSC is installed from npm
67 | url "$rootDir/../node_modules/jsc-android/dist"
68 | }
69 | google()
70 | jcenter()
71 | }
72 |
73 | dependencies {
74 | //noinspection GradleDynamicVersion
75 | implementation 'com.facebook.react:react-native:+' // From node_modules
76 | }
77 |
78 | def configureReactNativePom(def pom) {
79 | def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text)
80 |
81 | pom.project {
82 | name packageJson.title
83 | artifactId packageJson.name
84 | version = packageJson.version
85 | group = "com.reactlibrary"
86 | description packageJson.description
87 | url packageJson.repository.baseUrl
88 |
89 | licenses {
90 | license {
91 | name packageJson.license
92 | url packageJson.repository.baseUrl + '/blob/master/' + packageJson.licenseFilename
93 | distribution 'repo'
94 | }
95 | }
96 |
97 | developers {
98 | developer {
99 | id packageJson.author.username
100 | name packageJson.author.name
101 | }
102 | }
103 | }
104 | }
105 |
106 | afterEvaluate { project ->
107 | // some Gradle build hooks ref:
108 | // https://www.oreilly.com/library/view/gradle-beyond-the/9781449373801/ch03.html
109 | task androidJavadoc(type: Javadoc) {
110 | source = android.sourceSets.main.java.srcDirs
111 | classpath += files(android.bootClasspath)
112 | classpath += files(project.getConfigurations().getByName('compile').asList())
113 | include '**/*.java'
114 | }
115 |
116 | task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) {
117 | classifier = 'javadoc'
118 | from androidJavadoc.destinationDir
119 | }
120 |
121 | task androidSourcesJar(type: Jar) {
122 | classifier = 'sources'
123 | from android.sourceSets.main.java.srcDirs
124 | include '**/*.java'
125 | }
126 |
127 | android.libraryVariants.all { variant ->
128 | def name = variant.name.capitalize()
129 | def javaCompileTask = variant.javaCompileProvider.get()
130 |
131 | task "jar${name}"(type: Jar, dependsOn: javaCompileTask) {
132 | from javaCompileTask.destinationDir
133 | }
134 | }
135 |
136 | artifacts {
137 | archives androidSourcesJar
138 | archives androidJavadocJar
139 | }
140 |
141 | task installArchives(type: Upload) {
142 | configuration = configurations.archives
143 | repositories.mavenDeployer {
144 | // Deploy to react-native-event-bridge/maven, ready to publish to npm
145 | repository url: "file://${projectDir}/../android/maven"
146 | configureReactNativePom pom
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactlibrary/MediaClipboardModule.java:
--------------------------------------------------------------------------------
1 | package com.reactlibrary;
2 |
3 | import com.facebook.react.bridge.ReactApplicationContext;
4 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
5 | import com.facebook.react.bridge.ReactMethod;
6 | import com.facebook.react.bridge.Callback;
7 |
8 | public class MediaClipboardModule extends ReactContextBaseJavaModule {
9 |
10 | private final ReactApplicationContext reactContext;
11 |
12 | public MediaClipboardModule(ReactApplicationContext reactContext) {
13 | super(reactContext);
14 | this.reactContext = reactContext;
15 | }
16 |
17 | @Override
18 | public String getName() {
19 | return "MediaClipboard";
20 | }
21 |
22 | @ReactMethod
23 | public void sampleMethod(String stringArgument, int numberArgument, Callback callback) {
24 | // TODO: Implement some actually useful functionality
25 | callback.invoke("Received numberArgument: " + numberArgument + " stringArgument: " + stringArgument);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/android/src/main/java/com/reactlibrary/MediaClipboardPackage.java:
--------------------------------------------------------------------------------
1 | package com.reactlibrary;
2 |
3 | import java.util.Arrays;
4 | import java.util.Collections;
5 | import java.util.List;
6 |
7 | import com.facebook.react.ReactPackage;
8 | import com.facebook.react.bridge.NativeModule;
9 | import com.facebook.react.bridge.ReactApplicationContext;
10 | import com.facebook.react.uimanager.ViewManager;
11 | import com.facebook.react.bridge.JavaScriptModule;
12 |
13 | public class MediaClipboardPackage implements ReactPackage {
14 | @Override
15 | public List createNativeModules(ReactApplicationContext reactContext) {
16 | return Arrays.asList(new MediaClipboardModule(reactContext));
17 | }
18 |
19 | @Override
20 | public List createViewManagers(ReactApplicationContext reactContext) {
21 | return Collections.emptyList();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | export * from "./src/index.ts";
2 |
--------------------------------------------------------------------------------
/ios/MediaClipboard-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import
6 | #import
7 | #import
8 | #import
9 |
10 | @interface RCT_EXTERN_MODULE(MediaClipboard, RCTEventEmitter)
11 |
12 | RCT_EXTERN_METHOD(getContent:(RCTResponseSenderBlock)callback);
13 | RCT_EXTERN_METHOD(clipboardMediaSource:(RCTResponseSenderBlock)callback);
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/ios/MediaClipboard.h:
--------------------------------------------------------------------------------
1 | //
2 | // MediaClipboard.h
3 | // MediaClipboard
4 | //
5 | // Created by Jarred WSumner on 2/14/20.
6 | // Copyright © 2020 Yeet. All rights reserved.
7 | //
8 |
9 | #import "MediaClipboard-Bridging-Header.h"
10 |
11 | @interface MediaClipboard (ext)
12 | + (void)onApplicationBecomeActive;
13 | @end
14 |
--------------------------------------------------------------------------------
/ios/MediaClipboard.swift:
--------------------------------------------------------------------------------
1 |
2 |
3 | //
4 | // MediaClipboard.swift
5 | // Media
6 | //
7 | // Created by Jarred WSumner on 12/13/19.
8 | // Copyright © 2019 Yeet. All rights reserved.
9 | //
10 |
11 | import Foundation
12 | import UIKit
13 |
14 | @objc(MediaClipboard)
15 | class MediaClipboard: RCTEventEmitter {
16 | static let clipboardOperationQueue: OperationQueue = {
17 | var queue = OperationQueue()
18 | queue.name = "MediaClipboard"
19 | queue.maxConcurrentOperationCount = 1
20 |
21 | return queue
22 | }()
23 |
24 | enum EventNames : String {
25 | case change = "MediaClipboardChange"
26 | case remove = "MediaClipboardRemove"
27 | }
28 |
29 | static var changeCount = UIPasteboard.general.changeCount
30 | var listenerCount = 0
31 |
32 | @objc (onApplicationBecomeActive) static func onApplicationBecomeActive() {
33 | if changeCount != UIPasteboard.general.changeCount {
34 | NotificationCenter.default.post(name: UIPasteboard.changedNotification, object: nil)
35 | changeCount = UIPasteboard.general.changeCount
36 | }
37 | }
38 |
39 | override func startObserving() {
40 | super.startObserving()
41 |
42 | let needsSubscription = !hasListeners
43 | listenerCount += 1
44 |
45 | if needsSubscription {
46 | self.observePasteboardChange()
47 | }
48 |
49 | }
50 |
51 | override func stopObserving() {
52 | super.stopObserving()
53 | listenerCount -= 1
54 |
55 | let needsUnsubscription = !hasListeners
56 |
57 | if needsUnsubscription {
58 | self.stopObservingPasteboardChange()
59 | }
60 | }
61 |
62 | func observePasteboardChange() {
63 | NotificationCenter.default.addObserver(self, selector: #selector(handleChangeEvent(_:)), name: UIPasteboard.changedNotification, object: nil)
64 | NotificationCenter.default.addObserver(self, selector: #selector(handleRemoveEvent(_:)), name: UIPasteboard.removedNotification, object: nil)
65 | }
66 |
67 | func stopObservingPasteboardChange() {
68 | NotificationCenter.default.removeObserver(self, name: UIPasteboard.changedNotification, object: nil)
69 | NotificationCenter.default.removeObserver(self, name: UIPasteboard.removedNotification, object: nil)
70 | }
71 |
72 | @objc func handleChangeEvent(_ notification: NSNotification) {
73 | lastDictionaryValue = nil
74 | self.sendChangeEvent()
75 | MediaClipboard.changeCount = UIPasteboard.general.changeCount
76 | }
77 |
78 | @objc func handleRemoveEvent(_ notification: NSNotification) {
79 | lastDictionaryValue = nil
80 | self.sendChangeEvent()
81 | MediaClipboard.changeCount = UIPasteboard.general.changeCount
82 | }
83 |
84 | var hasListeners: Bool {
85 | return listenerCount > 0
86 | }
87 |
88 | override init() {
89 | super.init()
90 | }
91 |
92 | override var bridge: RCTBridge! {
93 | get {
94 | return super.bridge
95 | }
96 |
97 | set (newValue) {
98 | super.bridge = newValue
99 |
100 |
101 | }
102 | }
103 |
104 |
105 | func sendChangeEvent() {
106 | guard hasListeners else {
107 | return
108 | }
109 |
110 | sendEvent(withName: EventNames.change.rawValue, body: MediaClipboard.serializeContents())
111 | }
112 |
113 | func sendRemoveEvent() {
114 | guard hasListeners else {
115 | return
116 | }
117 |
118 | sendEvent(withName: EventNames.remove.rawValue, body: MediaClipboard.serializeContents())
119 | }
120 |
121 | override static func moduleName() -> String! {
122 | return "MediaClipboard";
123 | }
124 |
125 | @objc(serializeContents)
126 | static func serializeContents() -> [String: Any] {
127 | var hasURLs = false
128 | var hasStrings = false
129 |
130 |
131 | if #available(iOS 10.0, *) {
132 | hasStrings = UIPasteboard.general.hasStrings
133 | hasURLs = UIPasteboard.general.hasURLs
134 | }
135 |
136 | var contents = [
137 | "urls": [],
138 | "strings": [],
139 | "hasImages": hasImagesInClipboard,
140 | "hasURLs": hasURLs,
141 | "hasStrings": hasStrings
142 | ] as [String : Any]
143 |
144 | if #available(iOS 10.0, *) {
145 | if UIPasteboard.general.hasURLs {
146 | contents["urls"] = UIPasteboard.general.urls?.map { url in
147 | return url.absoluteString
148 | }
149 | }
150 | } else {
151 | // Fallback on earlier versions
152 | }
153 |
154 | if #available(iOS 10.0, *) {
155 | if UIPasteboard.general.hasStrings {
156 | let strings = UIPasteboard.general.strings?.filter { string in
157 | guard let urls = contents["urls"] as? Array else {
158 | return true
159 | }
160 |
161 | return !urls.contains(string)
162 | }
163 |
164 | if (strings?.count ?? 0) > 0 {
165 | contents["strings"] = strings
166 | } else {
167 | contents["hasStrings"] = false
168 | }
169 | }
170 | } else {
171 | // Fallback on earlier versions
172 | }
173 |
174 | return contents
175 | }
176 |
177 |
178 | override func supportedEvents() -> [String]! {
179 | return [
180 | EventNames.change.rawValue,
181 | EventNames.remove.rawValue
182 | ]
183 | }
184 |
185 | override static func requiresMainQueueSetup() -> Bool {
186 | return false
187 | }
188 |
189 | override func constantsToExport() -> [AnyHashable : Any]! {
190 | return ["clipboard": MediaClipboard.serializeContents(), "mediaSource": self.lastDictionaryValue ?? nil]
191 | }
192 |
193 | @objc(getContent:)
194 | func getContent(_ callback: @escaping RCTResponseSenderBlock) {
195 | callback([nil, MediaClipboard.serializeContents()])
196 | }
197 |
198 | @objc(lastDictionaryValue)
199 | var lastDictionaryValue: [String: Any]? = nil
200 | var lastSavedImage: UIImage? = nil
201 |
202 | @objc(hasImagesInClipboard)
203 | static var hasImagesInClipboard: Bool {
204 | let imageUTIs = MimeType.images().map {image in
205 | return image.utiType()
206 | }
207 |
208 | return UIPasteboard.general.contains(pasteboardTypes: imageUTIs)
209 | }
210 |
211 | @objc(clipboardMediaSource:)
212 | func clipboardMediaSource(_ callback: @escaping RCTResponseSenderBlock) {
213 | guard MediaClipboard.hasImagesInClipboard else {
214 | callback([nil, [:]])
215 | return
216 | }
217 |
218 | let image = UIPasteboard.general.image
219 |
220 | if lastSavedImage != nil && lastSavedImage == image && lastDictionaryValue != nil {
221 | callback([nil, lastDictionaryValue!])
222 | }
223 |
224 | var exportType: MimeType? = nil
225 | if UIPasteboard.general.contains(pasteboardTypes: [MimeType.jpg.utiType()]) {
226 | exportType = MimeType.jpg
227 | } else if UIPasteboard.general.contains(pasteboardTypes: [MimeType.png.utiType()]) {
228 | exportType = MimeType.png
229 | }
230 |
231 |
232 | guard exportType != nil else {
233 | callback([NSError(domain: "com.yeet.react-native-media-clipboard.genericError", code: 111, userInfo: nil)])
234 | return
235 | }
236 |
237 | DispatchQueue.global(qos: .background).async {
238 | guard let image = image else {
239 | callback([NSError(domain: "com.yeet.react-native-media-clipboard.genericError", code: 111, userInfo: nil)])
240 | return
241 | }
242 |
243 | guard let exportType = exportType else {
244 | callback([NSError(domain: "com.yeet.react-native-media-clipboard.genericError", code: 111, userInfo: nil)])
245 | return
246 | }
247 |
248 | let url = self.getExportURL(mimeType: exportType)
249 | var data: Data? = nil
250 |
251 | if exportType == .jpg {
252 | data = image.jpegData(compressionQuality: CGFloat(1.0))
253 | } else if exportType == .png {
254 | data = image.pngData()
255 | }
256 |
257 | guard data != nil else {
258 | callback([NSError(domain: "com.yeet.react-native-media-clipboard.writingDataError", code: 112, userInfo: nil), nil])
259 | return
260 | }
261 |
262 | do {
263 | try data?.write(to: url)
264 | } catch {
265 | callback([NSError(domain: "com.yeet.react-native-media-clipboard.writingDataError", code: 112, userInfo: nil), nil])
266 | return
267 | }
268 |
269 | let size = image.size
270 |
271 |
272 | let value = self.getDictionaryValue(url: url, mimeType: exportType, size: size, scale: image.scale)
273 | self.lastDictionaryValue = value
274 | self.lastSavedImage = image
275 |
276 | callback([nil, value])
277 | }
278 | }
279 |
280 | open func getExportURL(mimeType: MimeType) -> URL {
281 | return URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString.appending(".\(mimeType.fileExtension())"))
282 | }
283 |
284 |
285 | open func getDictionaryValue(url: URL, mimeType: MimeType, size: CGSize, scale: CGFloat) -> [String: Any] {
286 | return [
287 | "uri": url.absoluteString,
288 | "mimeType": mimeType.rawValue,
289 | "width": size.width,
290 | "height": size.height,
291 | "scale": scale,
292 | ]
293 | }
294 |
295 | deinit {
296 | stopObservingPasteboardChange()
297 | }
298 |
299 | }
300 |
301 |
302 |
--------------------------------------------------------------------------------
/ios/MediaClipboard.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 8332B0E523F68717003FB121 /* MediaClipboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8332B0E423F68717003FB121 /* MediaClipboard.swift */; };
11 | 8332B0E823F6878E003FB121 /* MediaClipboardJSI.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8332B0E623F6878E003FB121 /* MediaClipboardJSI.mm */; };
12 | 8332B0EB23F687A6003FB121 /* YeetJSIUtils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8332B0E923F687A6003FB121 /* YeetJSIUtils.mm */; };
13 | 8332B0EE23F687F8003FB121 /* MediaClipboardC++Interop.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8332B0ED23F687F8003FB121 /* MediaClipboardC++Interop.mm */; };
14 | 8332B0F023F68DA3003FB121 /* MimeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8332B0EF23F68DA3003FB121 /* MimeType.swift */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXCopyFilesBuildPhase section */
18 | 58B511D91A9E6C8500147676 /* CopyFiles */ = {
19 | isa = PBXCopyFilesBuildPhase;
20 | buildActionMask = 2147483647;
21 | dstPath = "include/$(PRODUCT_NAME)";
22 | dstSubfolderSpec = 16;
23 | files = (
24 | );
25 | runOnlyForDeploymentPostprocessing = 0;
26 | };
27 | /* End PBXCopyFilesBuildPhase section */
28 |
29 | /* Begin PBXFileReference section */
30 | 134814201AA4EA6300B7C361 /* libMediaClipboard.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMediaClipboard.a; sourceTree = BUILT_PRODUCTS_DIR; };
31 | 8332B0E323F68717003FB121 /* MediaClipboard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MediaClipboard-Bridging-Header.h"; sourceTree = ""; };
32 | 8332B0E423F68717003FB121 /* MediaClipboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaClipboard.swift; sourceTree = ""; };
33 | 8332B0E623F6878E003FB121 /* MediaClipboardJSI.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MediaClipboardJSI.mm; sourceTree = ""; };
34 | 8332B0E723F6878E003FB121 /* MediaClipboardJSI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaClipboardJSI.h; sourceTree = ""; };
35 | 8332B0E923F687A6003FB121 /* YeetJSIUtils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = YeetJSIUtils.mm; sourceTree = ""; };
36 | 8332B0EA23F687A6003FB121 /* YeetJSIUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = YeetJSIUtils.h; sourceTree = ""; };
37 | 8332B0EC23F687F8003FB121 /* MediaClipboardC++Interop.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MediaClipboardC++Interop.h"; sourceTree = ""; };
38 | 8332B0ED23F687F8003FB121 /* MediaClipboardC++Interop.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "MediaClipboardC++Interop.mm"; sourceTree = ""; };
39 | 8332B0EF23F68DA3003FB121 /* MimeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MimeType.swift; sourceTree = ""; };
40 | 8332B0F723F6B6EE003FB121 /* MediaClipboard.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MediaClipboard.h; sourceTree = ""; };
41 | /* End PBXFileReference section */
42 |
43 | /* Begin PBXFrameworksBuildPhase section */
44 | 58B511D81A9E6C8500147676 /* Frameworks */ = {
45 | isa = PBXFrameworksBuildPhase;
46 | buildActionMask = 2147483647;
47 | files = (
48 | );
49 | runOnlyForDeploymentPostprocessing = 0;
50 | };
51 | /* End PBXFrameworksBuildPhase section */
52 |
53 | /* Begin PBXGroup section */
54 | 134814211AA4EA7D00B7C361 /* Products */ = {
55 | isa = PBXGroup;
56 | children = (
57 | 134814201AA4EA6300B7C361 /* libMediaClipboard.a */,
58 | );
59 | name = Products;
60 | sourceTree = "";
61 | };
62 | 58B511D21A9E6C8500147676 = {
63 | isa = PBXGroup;
64 | children = (
65 | 8332B0F723F6B6EE003FB121 /* MediaClipboard.h */,
66 | 8332B0E423F68717003FB121 /* MediaClipboard.swift */,
67 | 8332B0EF23F68DA3003FB121 /* MimeType.swift */,
68 | 134814211AA4EA7D00B7C361 /* Products */,
69 | 8332B0E323F68717003FB121 /* MediaClipboard-Bridging-Header.h */,
70 | 8332B0E623F6878E003FB121 /* MediaClipboardJSI.mm */,
71 | 8332B0E923F687A6003FB121 /* YeetJSIUtils.mm */,
72 | 8332B0EA23F687A6003FB121 /* YeetJSIUtils.h */,
73 | 8332B0E723F6878E003FB121 /* MediaClipboardJSI.h */,
74 | 8332B0EC23F687F8003FB121 /* MediaClipboardC++Interop.h */,
75 | 8332B0ED23F687F8003FB121 /* MediaClipboardC++Interop.mm */,
76 | );
77 | sourceTree = "";
78 | };
79 | /* End PBXGroup section */
80 |
81 | /* Begin PBXNativeTarget section */
82 | 58B511DA1A9E6C8500147676 /* MediaClipboard */ = {
83 | isa = PBXNativeTarget;
84 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "MediaClipboard" */;
85 | buildPhases = (
86 | 58B511D71A9E6C8500147676 /* Sources */,
87 | 58B511D81A9E6C8500147676 /* Frameworks */,
88 | 58B511D91A9E6C8500147676 /* CopyFiles */,
89 | );
90 | buildRules = (
91 | );
92 | dependencies = (
93 | );
94 | name = MediaClipboard;
95 | productName = RCTDataManager;
96 | productReference = 134814201AA4EA6300B7C361 /* libMediaClipboard.a */;
97 | productType = "com.apple.product-type.library.static";
98 | };
99 | /* End PBXNativeTarget section */
100 |
101 | /* Begin PBXProject section */
102 | 58B511D31A9E6C8500147676 /* Project object */ = {
103 | isa = PBXProject;
104 | attributes = {
105 | LastUpgradeCheck = 0920;
106 | ORGANIZATIONNAME = Yeet;
107 | TargetAttributes = {
108 | 58B511DA1A9E6C8500147676 = {
109 | CreatedOnToolsVersion = 6.1.1;
110 | LastSwiftMigration = 1130;
111 | };
112 | };
113 | };
114 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "MediaClipboard" */;
115 | compatibilityVersion = "Xcode 3.2";
116 | developmentRegion = English;
117 | hasScannedForEncodings = 0;
118 | knownRegions = (
119 | English,
120 | en,
121 | );
122 | mainGroup = 58B511D21A9E6C8500147676;
123 | productRefGroup = 58B511D21A9E6C8500147676;
124 | projectDirPath = "";
125 | projectRoot = "";
126 | targets = (
127 | 58B511DA1A9E6C8500147676 /* MediaClipboard */,
128 | );
129 | };
130 | /* End PBXProject section */
131 |
132 | /* Begin PBXSourcesBuildPhase section */
133 | 58B511D71A9E6C8500147676 /* Sources */ = {
134 | isa = PBXSourcesBuildPhase;
135 | buildActionMask = 2147483647;
136 | files = (
137 | 8332B0EB23F687A6003FB121 /* YeetJSIUtils.mm in Sources */,
138 | 8332B0E823F6878E003FB121 /* MediaClipboardJSI.mm in Sources */,
139 | 8332B0E523F68717003FB121 /* MediaClipboard.swift in Sources */,
140 | 8332B0F023F68DA3003FB121 /* MimeType.swift in Sources */,
141 | 8332B0EE23F687F8003FB121 /* MediaClipboardC++Interop.mm in Sources */,
142 | );
143 | runOnlyForDeploymentPostprocessing = 0;
144 | };
145 | /* End PBXSourcesBuildPhase section */
146 |
147 | /* Begin XCBuildConfiguration section */
148 | 58B511ED1A9E6C8500147676 /* Debug */ = {
149 | isa = XCBuildConfiguration;
150 | buildSettings = {
151 | ALWAYS_SEARCH_USER_PATHS = NO;
152 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
153 | CLANG_CXX_LIBRARY = "libc++";
154 | CLANG_ENABLE_MODULES = YES;
155 | CLANG_ENABLE_OBJC_ARC = YES;
156 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
157 | CLANG_WARN_BOOL_CONVERSION = YES;
158 | CLANG_WARN_COMMA = YES;
159 | CLANG_WARN_CONSTANT_CONVERSION = YES;
160 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
161 | CLANG_WARN_EMPTY_BODY = YES;
162 | CLANG_WARN_ENUM_CONVERSION = YES;
163 | CLANG_WARN_INFINITE_RECURSION = YES;
164 | CLANG_WARN_INT_CONVERSION = YES;
165 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
166 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
167 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
168 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
169 | CLANG_WARN_STRICT_PROTOTYPES = YES;
170 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
171 | CLANG_WARN_UNREACHABLE_CODE = YES;
172 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
173 | COPY_PHASE_STRIP = NO;
174 | ENABLE_STRICT_OBJC_MSGSEND = YES;
175 | ENABLE_TESTABILITY = YES;
176 | GCC_C_LANGUAGE_STANDARD = gnu99;
177 | GCC_DYNAMIC_NO_PIC = NO;
178 | GCC_NO_COMMON_BLOCKS = YES;
179 | GCC_OPTIMIZATION_LEVEL = 0;
180 | GCC_PREPROCESSOR_DEFINITIONS = (
181 | "DEBUG=1",
182 | "$(inherited)",
183 | );
184 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
185 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
186 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
187 | GCC_WARN_UNDECLARED_SELECTOR = YES;
188 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
189 | GCC_WARN_UNUSED_FUNCTION = YES;
190 | GCC_WARN_UNUSED_VARIABLE = YES;
191 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
192 | MTL_ENABLE_DEBUG_INFO = YES;
193 | ONLY_ACTIVE_ARCH = YES;
194 | SDKROOT = iphoneos;
195 | };
196 | name = Debug;
197 | };
198 | 58B511EE1A9E6C8500147676 /* Release */ = {
199 | isa = XCBuildConfiguration;
200 | buildSettings = {
201 | ALWAYS_SEARCH_USER_PATHS = NO;
202 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
203 | CLANG_CXX_LIBRARY = "libc++";
204 | CLANG_ENABLE_MODULES = YES;
205 | CLANG_ENABLE_OBJC_ARC = YES;
206 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
207 | CLANG_WARN_BOOL_CONVERSION = YES;
208 | CLANG_WARN_COMMA = YES;
209 | CLANG_WARN_CONSTANT_CONVERSION = YES;
210 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
211 | CLANG_WARN_EMPTY_BODY = YES;
212 | CLANG_WARN_ENUM_CONVERSION = YES;
213 | CLANG_WARN_INFINITE_RECURSION = YES;
214 | CLANG_WARN_INT_CONVERSION = YES;
215 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
216 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
217 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
218 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
219 | CLANG_WARN_STRICT_PROTOTYPES = YES;
220 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
221 | CLANG_WARN_UNREACHABLE_CODE = YES;
222 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
223 | COPY_PHASE_STRIP = YES;
224 | ENABLE_NS_ASSERTIONS = NO;
225 | ENABLE_STRICT_OBJC_MSGSEND = YES;
226 | GCC_C_LANGUAGE_STANDARD = gnu99;
227 | GCC_NO_COMMON_BLOCKS = YES;
228 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
229 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
230 | GCC_WARN_UNDECLARED_SELECTOR = YES;
231 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
232 | GCC_WARN_UNUSED_FUNCTION = YES;
233 | GCC_WARN_UNUSED_VARIABLE = YES;
234 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
235 | MTL_ENABLE_DEBUG_INFO = NO;
236 | SDKROOT = iphoneos;
237 | VALIDATE_PRODUCT = YES;
238 | };
239 | name = Release;
240 | };
241 | 58B511F01A9E6C8500147676 /* Debug */ = {
242 | isa = XCBuildConfiguration;
243 | buildSettings = {
244 | CLANG_ENABLE_MODULES = YES;
245 | HEADER_SEARCH_PATHS = (
246 | "$(inherited)",
247 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
248 | "$(SRCROOT)/../../../React/**",
249 | "$(SRCROOT)/../../react-native/React/**",
250 | );
251 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
252 | LIBRARY_SEARCH_PATHS = "$(inherited)";
253 | OTHER_LDFLAGS = "-ObjC";
254 | PRODUCT_NAME = MediaClipboard;
255 | SKIP_INSTALL = YES;
256 | SWIFT_OBJC_BRIDGING_HEADER = "MediaClipboard-Bridging-Header.h";
257 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
258 | SWIFT_VERSION = 5.0;
259 | };
260 | name = Debug;
261 | };
262 | 58B511F11A9E6C8500147676 /* Release */ = {
263 | isa = XCBuildConfiguration;
264 | buildSettings = {
265 | CLANG_ENABLE_MODULES = YES;
266 | HEADER_SEARCH_PATHS = (
267 | "$(inherited)",
268 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
269 | "$(SRCROOT)/../../../React/**",
270 | "$(SRCROOT)/../../react-native/React/**",
271 | );
272 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
273 | LIBRARY_SEARCH_PATHS = "$(inherited)";
274 | OTHER_LDFLAGS = "-ObjC";
275 | PRODUCT_NAME = MediaClipboard;
276 | SKIP_INSTALL = YES;
277 | SWIFT_OBJC_BRIDGING_HEADER = "MediaClipboard-Bridging-Header.h";
278 | SWIFT_VERSION = 5.0;
279 | };
280 | name = Release;
281 | };
282 | /* End XCBuildConfiguration section */
283 |
284 | /* Begin XCConfigurationList section */
285 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "MediaClipboard" */ = {
286 | isa = XCConfigurationList;
287 | buildConfigurations = (
288 | 58B511ED1A9E6C8500147676 /* Debug */,
289 | 58B511EE1A9E6C8500147676 /* Release */,
290 | );
291 | defaultConfigurationIsVisible = 0;
292 | defaultConfigurationName = Release;
293 | };
294 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "MediaClipboard" */ = {
295 | isa = XCConfigurationList;
296 | buildConfigurations = (
297 | 58B511F01A9E6C8500147676 /* Debug */,
298 | 58B511F11A9E6C8500147676 /* Release */,
299 | );
300 | defaultConfigurationIsVisible = 0;
301 | defaultConfigurationName = Release;
302 | };
303 | /* End XCConfigurationList section */
304 | };
305 | rootObject = 58B511D31A9E6C8500147676 /* Project object */;
306 | }
307 |
--------------------------------------------------------------------------------
/ios/MediaClipboard.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/MediaClipboard.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/MediaClipboardC++Interop.h:
--------------------------------------------------------------------------------
1 | //
2 | // MediaClipboardC++Interop.h
3 | // MediaClipboard
4 | //
5 | // Created by Jarred WSumner on 2/13/20.
6 | // Copyright © 2020 Yeet. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 |
12 | @class MediaClipboard;
13 |
14 | NS_ASSUME_NONNULL_BEGIN
15 |
16 | @interface MediaClipboardC__Interop : NSObject
17 |
18 | + (void)install:(MediaClipboard*)clipboard;
19 |
20 | @end
21 |
22 | NS_ASSUME_NONNULL_END
23 |
--------------------------------------------------------------------------------
/ios/MediaClipboardC++Interop.mm:
--------------------------------------------------------------------------------
1 | //
2 | // MediaClipboardC++Interop.m
3 | // MediaClipboard
4 | //
5 | // Created by Jarred WSumner on 2/13/20.
6 | // Copyright © 2020 Yeet. All rights reserved.
7 | //
8 |
9 | #import "MediaClipboardC++Interop.h"
10 | #import "MediaClipboardJSI.h"
11 |
12 |
13 |
14 | @implementation MediaClipboardC__Interop
15 |
16 | +(void)install:(MediaClipboard *)clipboard {
17 | MediaClipboardJSI::install(clipboard);
18 | }
19 |
20 | @end
21 |
22 |
--------------------------------------------------------------------------------
/ios/MediaClipboardJSI.h:
--------------------------------------------------------------------------------
1 | //
2 | // MediaClipboardJSI.h
3 | // Media
4 | //
5 | // Created by Jarred WSumner on 2/6/20.
6 | // Copyright © 2020 Media. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @class MediaClipboard;
12 |
13 | #ifdef __cplusplus
14 |
15 | #include
16 | #import
17 |
18 |
19 | using namespace facebook;
20 |
21 | @class RCTCxxBridge;
22 |
23 | class JSI_EXPORT MediaClipboardJSIModule : public jsi::HostObject {
24 | public:
25 | MediaClipboardJSIModule(MediaClipboard* clipboard);
26 |
27 | static void install(MediaClipboard *clipboard);
28 |
29 | /*
30 | * `jsi::HostObject` specific overloads.
31 | */
32 | jsi::Value get(jsi::Runtime &runtime, const jsi::PropNameID &name) override;
33 |
34 | jsi::Value getOther(jsi::Runtime &runtime, const jsi::PropNameID &name);
35 |
36 | private:
37 | MediaClipboard* clipboard_;
38 | std::shared_ptr _jsInvoker;
39 | };
40 |
41 | #endif
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/ios/MediaClipboardJSI.mm:
--------------------------------------------------------------------------------
1 | //
2 | // MediaClipboardJSI.cpp
3 | // MediaClipboard
4 | //
5 | // Created by Jarred WSumner on 2/13/20.
6 | // Copyright © 2020 Yeet. All rights reserved.
7 | //
8 |
9 | #include "MediaClipboardJSI.h"
10 | #import
11 | #import "MediaJSIUTils.h"
12 | #import
13 | #import
14 | #import
15 | #import
16 | #import "RCTConvert+PHotos.h"
17 | #import "MediaClipboardJSI.h"
18 |
19 |
20 |
21 | @interface RCTBridge (ext)
22 | - (std::weak_ptr)reactInstance;
23 | @end
24 |
25 | MediaClipboardJSIModule::MediaClipboardJSIModule
26 | (MediaClipboard *clipboard)
27 | : clipboard_(clipboard) {
28 | std::shared_ptr _jsInvoker = std::make_shared(clipboard.bridge.reactInstance);
29 | }
30 |
31 |
32 | void MediaClipboardJSIModule::install(MediaClipboard *clipboard) {
33 | RCTCxxBridge* bridge = clipboard.bridge;
34 |
35 | if (bridge.runtime == nullptr) {
36 | return;
37 | }
38 |
39 | jsi::Runtime &runtime = *(jsi::Runtime *)bridge.runtime;
40 |
41 | auto reaModuleName = "Clipboard";
42 | auto reaJsiModule = std::make_shared(std::move(clipboard));
43 | auto object = jsi::Object::createFromHostObject(runtime, reaJsiModule);
44 | runtime.global().setProperty(runtime, reaModuleName, std::move(object));
45 | }
46 |
47 | jsi::Value MediaClipboardJSIModule::get(jsi::Runtime &runtime, const jsi::PropNameID &name) {
48 | if (_jsInvoker == nullptr) {
49 | RCTCxxBridge* bridge = clipboard_.bridge;
50 | _jsInvoker = std::make_shared(bridge.reactInstance);
51 | }
52 |
53 |
54 | auto methodName = name.utf8(runtime);
55 |
56 | if (methodName == "getMediaSource") {
57 | MediaClipboard *clipboard = clipboard_;
58 | std::shared_ptr jsInvoker = _jsInvoker;
59 |
60 | return jsi::Function::createFromHostFunction(runtime, name, 1, [clipboard, jsInvoker](
61 | jsi::Runtime &runtime,
62 | const jsi::Value &thisValue,
63 | const jsi::Value *arguments,
64 | size_t count) -> jsi::Value {
65 |
66 | // Promise return type is special cased today, i.e. it needs extra 2 function args for resolve() and reject(), to
67 | // be passed to the actual ObjC++ class method.
68 | return createPromise(runtime, jsInvoker, ^(jsi::Runtime &rt, std::shared_ptr wrapper) {
69 | NSMutableArray *retained = [[NSMutableArray alloc] initWithCapacity:2];
70 | if (clipboard.lastMediaSource) {
71 | wrapper->resolveBlock()(clipboard.lastMediaSource.toDictionary);
72 | } else if (UIPasteboard.generalPasteboard.hasImages) {
73 | RCTPromiseResolveBlock resolver = wrapper->resolveBlock();
74 | RCTPromiseRejectBlock rejecter = wrapper->rejectBlock();
75 | [retained addObject:resolver];
76 | [retained addObject:rejecter];
77 |
78 | [clipboard clipboardMediaSource:^(NSArray *response) {
79 | NSError *error = [response objectAtIndex:0];
80 | NSDictionary *value = [response objectAtIndex:1];
81 | if (error && error != [NSNull null]) {
82 | rejecter([NSString stringWithFormat:@"%ldu", (long)error.code], error.domain, error);
83 | } else {
84 | resolver(value);
85 | }
86 |
87 | [retained removeAllObjects];
88 | }];
89 | } else {
90 | wrapper->resolveBlock()(nil);
91 | }
92 | });
93 | });
94 |
95 | }
96 |
97 | return jsi::Value::undefined();
98 | }
99 |
--------------------------------------------------------------------------------
/ios/MimeType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MimeType.swift
3 | // MediaClipboard
4 | //
5 | // Created by Jarred WSumner on 2/14/20.
6 | // Copyright © 2020 Yeet. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import MobileCoreServices
11 |
12 | enum MimeType: String {
13 | case png = "image/png"
14 | case gif = "image/gif"
15 | case webp = "image/webp"
16 | case jpg = "image/jpeg"
17 | case mp4 = "video/mp4"
18 | case m4v = "video/x-m4v"
19 | case heic = "image/heic"
20 | case heif = "image/heif"
21 | case tiff = "image/tiff"
22 | case mov = "video/quicktime"
23 | case bmp = "image/bmp"
24 |
25 | static func images() -> Array {
26 | return [
27 | MimeType.heic,
28 | MimeType.webp,
29 | MimeType.jpg,
30 | MimeType.heif,
31 | MimeType.tiff,
32 | MimeType.bmp,
33 | MimeType.png,
34 | ]
35 | }
36 |
37 | func utiType() -> String {
38 | switch self {
39 |
40 | case .png:
41 | return "public.png"
42 | case .gif:
43 | return "public.gif"
44 | case .webp:
45 | return "public.webp"
46 | case .jpg:
47 | return "public.jpeg"
48 | case .mp4:
49 | return "public.mpeg-4"
50 | case .m4v:
51 | return "public.mpeg-4"
52 | case .heic:
53 | return "public.heic"
54 |
55 | case .heif:
56 | return "public.heif"
57 | case .tiff:
58 | return "public.tiff"
59 | case .mov:
60 | return "com.apple.quicktime-movie"
61 | case .bmp:
62 | return "public.bmp"
63 | }
64 | }
65 |
66 | static func from(uti: String) -> MimeType? {
67 | switch uti {
68 | case "public.png":
69 | return MimeType.png
70 | case "public.gif":
71 | return MimeType.gif
72 | case "public.webp":
73 | return MimeType.webp
74 | case "public.jpeg":
75 | return MimeType.jpg
76 | case "public.mpeg-4":
77 | return MimeType.mp4
78 |
79 | case "public.heic":
80 | return MimeType.heic
81 |
82 | case "public.heif":
83 | return MimeType.heif
84 | case "public.tiff":
85 | return MimeType.tiff
86 | case "com.apple.quicktime-movie":
87 | return MimeType.mov
88 | case "public.bmp":
89 | return MimeType.bmp
90 | default:
91 | return nil
92 | }
93 | }
94 |
95 | func fileExtension() -> String {
96 | switch self {
97 | case .png:
98 | return "png"
99 | case .gif:
100 | return "gif"
101 | case .m4v:
102 | return "m4v"
103 | case .webp:
104 | return "webp"
105 | case .jpg:
106 | return "jpg"
107 | case .mp4:
108 | return "mp4"
109 |
110 | case .heic:
111 | return "heic"
112 |
113 | case .heif:
114 | return "heif"
115 | case .tiff:
116 | return "tiff"
117 | case .mov:
118 | return "mov"
119 | case .bmp:
120 | return "bmp"
121 | }
122 | }
123 |
124 | static func url(_ url: URL) -> MimeType? {
125 | return fileExtension(url.pathExtension)
126 | }
127 |
128 | func isAnimatable() -> Bool {
129 | return [.gif, .webp].contains(self)
130 | }
131 |
132 | static func fileExtension(_ ext: String) -> MimeType? {
133 |
134 |
135 | let fileExtension = ext as CFString
136 |
137 | guard
138 | let extUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension, nil)?.takeUnretainedValue()
139 | else { return nil }
140 |
141 | guard
142 | let mimeUTI = UTTypeCopyPreferredTagWithClass(extUTI, kUTTagClassMIMEType)
143 | else { return nil }
144 |
145 | return MimeType(rawValue: mimeUTI.takeUnretainedValue() as String)
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/ios/YeetJSIUtils.h:
--------------------------------------------------------------------------------
1 | //
2 | // YeetJSIUTils.h
3 | // yeet
4 | //
5 | // Created by Jarred WSumner on 1/30/20.
6 | // Copyright © 2020 Yeet. All rights reserved.
7 | //
8 |
9 | #pragma once
10 | #ifdef __cplusplus
11 |
12 | #import
13 | #import
14 | #import
15 | #import
16 | #import
17 | #import
18 | #import
19 |
20 | using namespace facebook;
21 | /**
22 | * All static helper functions are ObjC++ specific.
23 | // */
24 | jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);
25 | jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value);
26 | jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value);
27 | jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value);
28 | jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value);
29 | jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value);
30 | //std::vector convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value);
31 | //jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);
32 | //id convertJSIValueToObjCObject(
33 | // jsi::Runtime &runtime,
34 | // const jsi::Value &value);
35 | //NSString* convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value);
36 | //NSArray* convertJSIArrayToNSArray(
37 | // jsi::Runtime &runtime,
38 | // const jsi::Array &value);
39 | //NSDictionary *convertJSIObjectToNSDictionary(
40 | // jsi::Runtime &runtime,
41 | // const jsi::Object &value);
42 |
43 |
44 | NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value);
45 | NSArray *convertJSIArrayToNSArray(
46 | jsi::Runtime &runtime,
47 | const jsi::Array &value,
48 | std::shared_ptr jsInvoker
49 | );
50 | NSDictionary *convertJSIObjectToNSDictionary(
51 | jsi::Runtime &runtime,
52 | const jsi::Object &value,
53 | std::shared_ptr jsInvoker);
54 | RCTResponseSenderBlock convertJSIFunctionToCallback(
55 | jsi::Runtime &runtime,
56 | const jsi::Function &value,
57 | std::shared_ptr jsInvoker);
58 | id convertJSIValueToObjCObject(
59 | jsi::Runtime &runtime,
60 | const jsi::Value &value,
61 | std::shared_ptr jsInvoker);
62 |
63 | // Helper for creating Promise object.
64 | struct PromiseWrapper : public react::LongLivedObject {
65 | static std::shared_ptr create(
66 | jsi::Function resolve,
67 | jsi::Function reject,
68 | jsi::Runtime &runtime,
69 | std::shared_ptr jsInvoker)
70 | {
71 | auto instance = std::make_shared(std::move(resolve), std::move(reject), runtime, jsInvoker);
72 | // This instance needs to live longer than the caller's scope, since the resolve/reject functions may not
73 | // be called immediately. Doing so keeps it alive at least until resolve/reject is called, or when the
74 | // collection is cleared (e.g. when JS reloads).
75 | react::LongLivedObjectCollection::get().add(instance);
76 | return instance;
77 | }
78 |
79 | PromiseWrapper(
80 | jsi::Function resolve,
81 | jsi::Function reject,
82 | jsi::Runtime &runtime,
83 | std::shared_ptr jsInvoker)
84 | : resolveWrapper(std::make_shared(std::move(resolve), runtime, jsInvoker)),
85 | rejectWrapper(std::make_shared(std::move(reject), runtime, jsInvoker)),
86 | runtime(runtime),
87 | jsInvoker(jsInvoker)
88 | {
89 | }
90 |
91 | RCTPromiseResolveBlock resolveBlock()
92 | {
93 | return ^(id result) {
94 | if (resolveWrapper == nullptr) {
95 | throw std::runtime_error("Promise resolve arg cannot be called more than once");
96 | }
97 |
98 | // Retain the resolveWrapper so that it stays alive inside the lambda.
99 | std::shared_ptr retainedWrapper = resolveWrapper;
100 | std::shared_ptr invoker = jsInvoker;
101 | jsInvoker->invokeAsync([retainedWrapper, result, invoker]() {
102 | jsi::Runtime &rt = retainedWrapper->runtime();
103 | jsi::Value arg = convertObjCObjectToJSIValue(rt, result);
104 | retainedWrapper->callback().call(rt, arg);
105 | });
106 |
107 | // Prevent future invocation of the same resolve() function.
108 | cleanup();
109 | };
110 | }
111 |
112 | RCTPromiseRejectBlock rejectBlock()
113 | {
114 | return ^(NSString *code, NSString *message, NSError *error) {
115 | // TODO: There is a chance `this` is no longer valid when this block executes.
116 | if (rejectWrapper == nullptr) {
117 | throw std::runtime_error("Promise reject arg cannot be called more than once");
118 | }
119 |
120 | // Retain the resolveWrapper so that it stays alive inside the lambda.
121 | std::shared_ptr retainedWrapper = rejectWrapper;
122 | NSDictionary *jsError = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
123 | jsInvoker->invokeAsync([retainedWrapper, jsError]() {
124 | jsi::Runtime &rt = retainedWrapper->runtime();
125 | jsi::Value arg = convertNSDictionaryToJSIObject(rt, jsError);
126 | retainedWrapper->callback().call(rt, arg);
127 | });
128 |
129 | // Prevent future invocation of the same resolve() function.
130 | cleanup();
131 | };
132 | }
133 |
134 | void cleanup()
135 | {
136 | resolveWrapper = nullptr;
137 | rejectWrapper = nullptr;
138 | allowRelease();
139 | }
140 |
141 | // CallbackWrapper is used here instead of just holding on the jsi jsi::Function in order to force release it after
142 | // either the resolve() or the reject() is called. jsi jsi::Function does not support explicit releasing, so we need
143 | // an extra mechanism to control that lifecycle.
144 | std::shared_ptr resolveWrapper;
145 | std::shared_ptr rejectWrapper;
146 | jsi::Runtime &runtime;
147 | std::shared_ptr jsInvoker;
148 | };
149 |
150 | using PromiseInvocationBlock = void (^)(jsi::Runtime &rt, std::shared_ptr wrapper);
151 | jsi::Value
152 | createPromise(jsi::Runtime &runtime, std::shared_ptr jsInvoker, PromiseInvocationBlock invoke);
153 |
154 | #endif
155 |
--------------------------------------------------------------------------------
/ios/YeetJSIUtils.mm:
--------------------------------------------------------------------------------
1 | //
2 | // REAJsiUtilities.cpp
3 | // RNReanimated
4 | //
5 | // Created by Christian Falch on 25/04/2019.
6 | // Copyright © 2019 Yeet. All rights reserved.
7 | //
8 |
9 | #include "YeetJSIUTils.h"
10 | #import
11 | #import
12 | #import
13 | #import
14 |
15 | jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value)
16 | {
17 | if ([value isKindOfClass:[NSString class]]) {
18 | return convertNSStringToJSIString(runtime, (NSString *)value);
19 | } else if ([value isKindOfClass:[NSNumber class]]) {
20 | if ([value isKindOfClass:[@YES class]]) {
21 | return convertNSNumberToJSIBoolean(runtime, (NSNumber *)value);
22 | }
23 | return convertNSNumberToJSINumber(runtime, (NSNumber *)value);
24 | } else if ([value isKindOfClass:[NSDictionary class]]) {
25 | return convertNSDictionaryToJSIObject(runtime, (NSDictionary *)value);
26 | } else if ([value isKindOfClass:[NSArray class]]) {
27 | return convertNSArrayToJSIArray(runtime, (NSArray *)value);
28 | } else if (value == (id)kCFNull) {
29 | return jsi::Value::null();
30 | }
31 | return jsi::Value::undefined();
32 | }
33 |
34 |
35 | jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value)
36 | {
37 | return jsi::Value((bool)[value boolValue]);
38 | }
39 |
40 | jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value)
41 | {
42 | return jsi::Value([value doubleValue]);
43 | }
44 |
45 | jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value)
46 | {
47 | return jsi::String::createFromUtf8(runtime, [value UTF8String] ?: "");
48 | }
49 |
50 | jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value)
51 | {
52 | jsi::Object result = jsi::Object(runtime);
53 | for (NSString *k in value) {
54 | result.setProperty(runtime, [k UTF8String], convertObjCObjectToJSIValue(runtime, value[k]));
55 | }
56 | return result;
57 | }
58 |
59 | jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value)
60 | {
61 | jsi::Array result = jsi::Array(runtime, value.count);
62 | for (size_t i = 0; i < value.count; i++) {
63 | result.setValueAtIndex(runtime, i, convertObjCObjectToJSIValue(runtime, value[i]));
64 | }
65 | return result;
66 | }
67 |
68 | std::vector convertNSArrayToStdVector(jsi::Runtime &runtime, NSArray *value)
69 | {
70 | std::vector result;
71 | for (size_t i = 0; i < value.count; i++) {
72 | result.emplace_back(convertObjCObjectToJSIValue(runtime, value[i]));
73 | }
74 | return result;
75 | }
76 |
77 | NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value)
78 | {
79 | return [NSString stringWithUTF8String:value.utf8(runtime).c_str()];
80 | }
81 |
82 | NSArray *convertJSIArrayToNSArray(
83 | jsi::Runtime &runtime,
84 | const jsi::Array &value,
85 | std::shared_ptr jsInvoker)
86 | {
87 | size_t size = value.size(runtime);
88 | NSMutableArray *result = [NSMutableArray new];
89 | for (size_t i = 0; i < size; i++) {
90 | // Insert kCFNull when it's `undefined` value to preserve the indices.
91 | [result
92 | addObject:convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i), jsInvoker) ?: (id)kCFNull];
93 | }
94 | return [result copy];
95 | }
96 |
97 | NSDictionary *convertJSIObjectToNSDictionary(
98 | jsi::Runtime &runtime,
99 | const jsi::Object &value,
100 | std::shared_ptr jsInvoker)
101 | {
102 | jsi::Array propertyNames = value.getPropertyNames(runtime);
103 | size_t size = propertyNames.size(runtime);
104 | NSMutableDictionary *result = [NSMutableDictionary new];
105 | for (size_t i = 0; i < size; i++) {
106 | jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime);
107 | NSString *k = convertJSIStringToNSString(runtime, name);
108 | id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name), jsInvoker);
109 | if (v) {
110 | result[k] = v;
111 | }
112 | }
113 | return [result copy];
114 | }
115 |
116 |
117 |
118 |
119 | RCTResponseSenderBlock convertJSIFunctionToCallback(
120 | jsi::Runtime &runtime,
121 | const jsi::Function &value,
122 | std::shared_ptr jsInvoker)
123 | {
124 | __block auto wrapper = std::make_shared(value.getFunction(runtime), runtime, jsInvoker);
125 | return ^(NSArray *responses) {
126 | if (wrapper == nullptr) {
127 | throw std::runtime_error("callback arg cannot be called more than once");
128 | }
129 |
130 | std::shared_ptr rw = wrapper;
131 | wrapper->jsInvoker().invokeAsync([rw, responses]() {
132 | std::vector args = convertNSArrayToStdVector(rw->runtime(), responses);
133 | rw->callback().call(rw->runtime(), (const jsi::Value *)args.data(), args.size());
134 | });
135 |
136 | // The callback is single-use, so force release it here.
137 | // Doing this also releases the jsi::jsi::Function early, since this block may not get released by ARC for a while,
138 | // because the method invocation isn't guarded with @autoreleasepool.
139 | wrapper = nullptr;
140 | };
141 | }
142 |
143 |
144 | using PromiseInvocationBlock = void (^)(jsi::Runtime &rt, std::shared_ptr wrapper);
145 |
146 |
147 | jsi::Value
148 | createPromise(jsi::Runtime &runtime, std::shared_ptr jsInvoker, PromiseInvocationBlock invoke)
149 | {
150 | if (!invoke) {
151 | return jsi::Value::undefined();
152 | }
153 |
154 | jsi::Function Promise = runtime.global().getPropertyAsFunction(runtime, "Promise");
155 |
156 | // Note: the passed invoke() block is not retained by default, so let's retain it here to help keep it longer.
157 | // Otherwise, there's a risk of it getting released before the promise function below executes.
158 | PromiseInvocationBlock invokeCopy = [invoke copy];
159 | jsi::Function fn = jsi::Function::createFromHostFunction(
160 | runtime,
161 | jsi::PropNameID::forAscii(runtime, "fn"),
162 | 2,
163 | [invokeCopy, jsInvoker](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
164 | if (count != 2) {
165 | throw std::invalid_argument("Promise fn arg count must be 2");
166 | }
167 | if (!invokeCopy) {
168 | return jsi::Value::undefined();
169 | }
170 | jsi::Function resolve = args[0].getObject(rt).getFunction(rt);
171 | jsi::Function reject = args[1].getObject(rt).getFunction(rt);
172 | auto wrapper = PromiseWrapper::create(std::move(resolve), std::move(reject), rt, jsInvoker);
173 | invokeCopy(rt, wrapper);
174 | return jsi::Value::undefined();
175 | });
176 |
177 | return Promise.callAsConstructor(runtime, fn);
178 | }
179 |
180 |
181 |
182 |
183 |
184 | //
185 | ///**
186 | // * All helper functions are ObjC++ specific.
187 | // */
188 |
189 | //
190 | //jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value)
191 | //{
192 | // if ([value isKindOfClass:[NSString class]]) {
193 | // return convertNSStringToJSIString(runtime, (NSString *)value);
194 | // } else if ([value isKindOfClass:[NSNumber class]]) {
195 | // if ([value isKindOfClass:[@YES class]]) {
196 | // return convertNSNumberToJSIBoolean(runtime, (NSNumber *)value);
197 | // }
198 | // return convertNSNumberToJSINumber(runtime, (NSNumber *)value);
199 | // } else if ([value isKindOfClass:[NSDictionary class]]) {
200 | // return convertNSDictionaryToJSIObject(runtime, (NSDictionary *)value);
201 | // } else if ([value isKindOfClass:[NSArray class]]) {
202 | // return convertNSArrayToJSIArray(runtime, (NSArray *)value);
203 | // } else if (value == (id)kCFNull) {
204 | // return jsi::Value::null();
205 | // }
206 | // return jsi::Value::undefined();
207 | //}
208 | //
209 |
210 |
211 | //NSArray *convertJSIArrayToNSArray(
212 | // jsi::Runtime &runtime,
213 | // const jsi::Array &value)
214 | //{
215 | // size_t size = value.size(runtime);
216 | // NSMutableArray *result = [NSMutableArray new];
217 | // for (size_t i = 0; i < size; i++) {
218 | // // Insert kCFNull when it's `undefined` value to preserve the indices.
219 | // [result
220 | // addObject:convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i)) ?: (id)kCFNull];
221 | // }
222 | // return [result copy];
223 | //}
224 | //
225 | //NSDictionary *convertJSIObjectToNSDictionary(
226 | // jsi::Runtime &runtime,
227 | // const jsi::Object &value)
228 | //{
229 | // jsi::Array propertyNames = value.getPropertyNames(runtime);
230 | // size_t size = propertyNames.size(runtime);
231 | // NSMutableDictionary *result = [NSMutableDictionary new];
232 | // for (size_t i = 0; i < size; i++) {
233 | // jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime);
234 | // NSString *k = convertJSIStringToNSString(runtime, name);
235 | // id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name));
236 | // if (v) {
237 | // result[k] = v;
238 | // }
239 | // }
240 | // return [result copy];
241 | //}
242 | //
243 | //RCTResponseSenderBlock convertJSIFunctionToCallback(
244 | // jsi::Runtime &runtime,
245 | // const jsi::Function &value)
246 | //{
247 | // __block auto cb = value.getFunction(runtime);
248 | //
249 | // return ^(NSArray *responses) {
250 | // cb.call(runtime, convertNSArrayToJSIArray(runtime, responses), 2);
251 | // };
252 | //}
253 | //
254 | //id convertJSIValueToObjCObject(
255 | // jsi::Runtime &runtime,
256 | // const jsi::Value &value)
257 | //{
258 | // if (value.isUndefined() || value.isNull()) {
259 | // return nil;
260 | // }
261 | // if (value.isBool()) {
262 | // return @(value.getBool());
263 | // }
264 | // if (value.isNumber()) {
265 | // return @(value.getNumber());
266 | // }
267 | // if (value.isString()) {
268 | // return convertJSIStringToNSString(runtime, value.getString(runtime));
269 | // }
270 | // if (value.isObject()) {
271 | // jsi::Object o = value.getObject(runtime);
272 | // if (o.isArray(runtime)) {
273 | // return convertJSIArrayToNSArray(runtime, o.getArray(runtime));
274 | // }
275 | // if (o.isFunction(runtime)) {
276 | // return convertJSIFunctionToCallback(runtime, std::move(o.getFunction(runtime)));
277 | // }
278 | // return convertJSIObjectToNSDictionary(runtime, o);
279 | // }
280 | //
281 | // throw std::runtime_error("Unsupported jsi::jsi::Value kind");
282 | //}
283 | //
284 | //static id convertJSIValueToObjCObject(
285 | // jsi::Runtime &runtime,
286 | // const jsi::Value &value,
287 | // std::shared_ptr jsInvoker);
288 | //static NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value);
289 | //static NSArray *convertJSIArrayToNSArray(
290 | // jsi::Runtime &runtime,
291 | // const jsi::Array &value,
292 | // std::shared_ptr jsInvoker
293 | //);
294 | //static NSDictionary *convertJSIObjectToNSDictionary(
295 | // jsi::Runtime &runtime,
296 | // const jsi::Object &value,
297 | // std::shared_ptr jsInvoker);
298 | //static RCTResponseSenderBlock convertJSIFunctionToCallback(
299 | // jsi::Runtime &runtime,
300 | // const jsi::Function &value,
301 | // std::shared_ptr jsInvoker);
302 | //static id convertJSIValueToObjCObject(
303 | // jsi::Runtime &runtime,
304 | // const jsi::Value &value,
305 | // std::shared_ptr jsInvoker);
306 | //static RCTResponseSenderBlock convertJSIFunctionToCallback(
307 | // jsi::Runtime &runtime,
308 | // const jsi::Function &value,
309 | // std::shared_ptr jsInvoker);
310 | //
311 | //// Helper for creating Promise object.
312 | //struct PromiseWrapper : public react::LongLivedObject {
313 | // static std::shared_ptr create(
314 | // jsi::Function resolve,
315 | // jsi::Function reject,
316 | // jsi::Runtime &runtime,
317 | // std::shared_ptr jsInvoker)
318 | // {
319 | // auto instance = std::make_shared(std::move(resolve), std::move(reject), runtime, jsInvoker);
320 | // // This instance needs to live longer than the caller's scope, since the resolve/reject functions may not
321 | // // be called immediately. Doing so keeps it alive at least until resolve/reject is called, or when the
322 | // // collection is cleared (e.g. when JS reloads).
323 | // react::LongLivedObjectCollection::get().add(instance);
324 | // return instance;
325 | // }
326 | //
327 | // PromiseWrapper(
328 | // jsi::Function resolve,
329 | // jsi::Function reject,
330 | // jsi::Runtime &runtime,
331 | // std::shared_ptr jsInvoker)
332 | // : resolveWrapper(std::make_shared(std::move(resolve), runtime, jsInvoker)),
333 | // rejectWrapper(std::make_shared(std::move(reject), runtime, jsInvoker)),
334 | // runtime(runtime),
335 | // jsInvoker(jsInvoker)
336 | // {
337 | // }
338 | //
339 | // RCTPromiseResolveBlock resolveBlock()
340 | // {
341 | // return ^(id result) {
342 | // if (resolveWrapper == nullptr) {
343 | // throw std::runtime_error("Promise resolve arg cannot be called more than once");
344 | // }
345 | //
346 | // // Retain the resolveWrapper so that it stays alive inside the lambda.
347 | // std::shared_ptr retainedWrapper = resolveWrapper;
348 | // jsInvoker->invokeAsync([retainedWrapper, result]() {
349 | // jsi::Runtime &rt = retainedWrapper->runtime();
350 | // jsi::Value arg = convertObjCObjectToJSIValue(rt, result);
351 | // retainedWrapper->callback().call(rt, arg);
352 | // });
353 | //
354 | // // Prevent future invocation of the same resolve() function.
355 | // cleanup();
356 | // };
357 | // }
358 | //
359 | // RCTPromiseRejectBlock rejectBlock()
360 | // {
361 | // return ^(NSString *code, NSString *message, NSError *error) {
362 | // // TODO: There is a chance `this` is no longer valid when this block executes.
363 | // if (rejectWrapper == nullptr) {
364 | // throw std::runtime_error("Promise reject arg cannot be called more than once");
365 | // }
366 | //
367 | // // Retain the resolveWrapper so that it stays alive inside the lambda.
368 | // std::shared_ptr retainedWrapper = rejectWrapper;
369 | // NSDictionary *jsError = RCTJSErrorFromCodeMessageAndNSError(code, message, error);
370 | // jsInvoker->invokeAsync([retainedWrapper, jsError]() {
371 | // jsi::Runtime &rt = retainedWrapper->runtime();
372 | // jsi::Value arg = convertNSDictionaryToJSIObject(rt, jsError);
373 | // retainedWrapper->callback().call(rt, arg);
374 | // });
375 | //
376 | // // Prevent future invocation of the same resolve() function.
377 | // cleanup();
378 | // };
379 | // }
380 | //
381 | // void cleanup()
382 | // {
383 | // resolveWrapper = nullptr;
384 | // rejectWrapper = nullptr;
385 | // allowRelease();
386 | // }
387 | //
388 | // // CallbackWrapper is used here instead of just holding on the jsi jsi::Function in order to force release it after
389 | // // either the resolve() or the reject() is called. jsi jsi::Function does not support explicit releasing, so we need
390 | // // an extra mechanism to control that lifecycle.
391 | // std::shared_ptr resolveWrapper;
392 | // std::shared_ptr rejectWrapper;
393 | // jsi::Runtime &runtime;
394 | // std::shared_ptr jsInvoker;
395 | //};
396 | //
397 | //using PromiseInvocationBlock = void (^)(jsi::Runtime &rt, std::shared_ptr wrapper);
398 | //static jsi::Value
399 | //createPromise(jsi::Runtime &runtime, std::shared_ptr jsInvoker, PromiseInvocationBlock invoke)
400 | //{
401 | // if (!invoke) {
402 | // return jsi::Value::undefined();
403 | // }
404 | //
405 | // jsi::Function Promise = runtime.global().getPropertyAsFunction(runtime, "Promise");
406 | //
407 | // // Note: the passed invoke() block is not retained by default, so let's retain it here to help keep it longer.
408 | // // Otherwise, there's a risk of it getting released before the promise function below executes.
409 | // PromiseInvocationBlock invokeCopy = [invoke copy];
410 | // jsi::Function fn = jsi::Function::createFromHostFunction(
411 | // runtime,
412 | // jsi::PropNameID::forAscii(runtime, "fn"),
413 | // 2,
414 | // [invokeCopy, jsInvoker](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) {
415 | // if (count != 2) {
416 | // throw std::invalid_argument("Promise fn arg count must be 2");
417 | // }
418 | // if (!invokeCopy) {
419 | // return jsi::Value::undefined();
420 | // }
421 | // jsi::Function resolve = args[0].getObject(rt).getFunction(rt);
422 | // jsi::Function reject = args[1].getObject(rt).getFunction(rt);
423 | // auto wrapper = PromiseWrapper::create(std::move(resolve), std::move(reject), rt, jsInvoker);
424 | // invokeCopy(rt, wrapper);
425 | // return jsi::Value::undefined();
426 | // });
427 | //
428 | // return Promise.callAsConstructor(runtime, fn);
429 | //}
430 |
431 |
432 | id convertJSIValueToObjCObject(
433 | jsi::Runtime &runtime,
434 | const jsi::Value &value,
435 | std::shared_ptr jsInvoker)
436 | {
437 | if (value.isUndefined() || value.isNull()) {
438 | return nil;
439 | }
440 | if (value.isBool()) {
441 | return @(value.getBool());
442 | }
443 | if (value.isNumber()) {
444 | return @(value.getNumber());
445 | }
446 | if (value.isString()) {
447 | return convertJSIStringToNSString(runtime, value.getString(runtime));
448 | }
449 | if (value.isObject()) {
450 | jsi::Object o = value.getObject(runtime);
451 | if (o.isArray(runtime)) {
452 | return convertJSIArrayToNSArray(runtime, o.getArray(runtime), jsInvoker);
453 | }
454 | if (o.isFunction(runtime)) {
455 | return convertJSIFunctionToCallback(runtime, std::move(o.getFunction(runtime)), jsInvoker);
456 | }
457 | return convertJSIObjectToNSDictionary(runtime, o, jsInvoker);
458 | }
459 |
460 | throw std::runtime_error("Unsupported jsi::jsi::Value kind");
461 | }
462 |
463 |
464 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-media-clipboard",
3 | "title": "Clipboard for React Native with images",
4 | "version": "1.0.3",
5 | "description": "TODO",
6 | "scripts": {
7 | "prepare": "bob build",
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "module": "lib/module/index.js",
11 | "react-native": "src/index.ts",
12 | "types": "lib/typescript/src/index.d.ts",
13 | "files": [
14 | "lib/",
15 | "src/",
16 | "react-native-media-clipboard.podspec",
17 | "ios/*.swift",
18 | "ios/*.xcodeproj",
19 | "ios/*.mm",
20 | "ios/*.m",
21 | "ios/*.h",
22 | "android"
23 | ],
24 | "repository": {
25 | "type": "git",
26 | "url": "git+https://github.com/Jarred-Sumner/react-native-media-clipboard.git",
27 | "baseUrl": "https://github.com/Jarred-Sumner/react-native-media-clipboard"
28 | },
29 | "keywords": [
30 | "react-native"
31 | ],
32 | "author": {
33 | "name": "Jarred Sumner",
34 | "email": "jarred@jaredsumner.com"
35 | },
36 | "@react-native-community/bob": {
37 | "source": "src",
38 | "output": "lib",
39 | "targets": [
40 | [
41 | "commonjs",
42 | {
43 | "copyFlow": true
44 | }
45 | ],
46 | "module",
47 | "typescript"
48 | ]
49 | },
50 | "license": "MIT",
51 | "licenseFilename": "LICENSE",
52 | "readmeFilename": "README.md",
53 | "peerDependencies": {
54 | "react": "^16.8.1",
55 | "react-native": ">=0.60.0-rc.0 <1.0.x"
56 | },
57 | "devDependencies": {
58 | "@react-native-community/bob": "^0.9.7",
59 | "react": "^16.9.0",
60 | "react-native": "^0.61.5"
61 | },
62 | "dependencies": {
63 | "lodash": "^4.17.15"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/react-native-media-clipboard.podspec:
--------------------------------------------------------------------------------
1 | require "json"
2 |
3 | package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4 |
5 |
6 | folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
7 | folly_version = '2018.10.22.00'
8 |
9 | Pod::Spec.new do |s|
10 | s.name = "react-native-media-clipboard"
11 | s.version = package["version"]
12 | s.summary = package["description"]
13 | s.description = <<-DESC
14 | react-native-media-clipboard
15 | DESC
16 | s.homepage = "https://github.com/github_account/react-native-media-clipboard"
17 | s.license = "MIT"
18 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" }
19 | s.authors = { "Your Name" => "yourname@email.com" }
20 | s.platforms = { :ios => "9.0" }
21 | s.source = { :git => "https://github.com/github_account/react-native-media-clipboard.git", :tag => "#{s.version}" }
22 |
23 | s.source_files = "ios/**/*.{h,m,swift}"
24 | s.requires_arc = true
25 | s.default_subspec = 'Bridge'
26 |
27 |
28 | s.swift_version = '5.0'
29 |
30 | s.dependency "React"
31 |
32 |
33 | s.subspec 'Bridge' do |lite|
34 |
35 | lite.pod_target_xcconfig = { "LIBRAY_SEARCH_PATHS" => "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"" }
36 | end
37 |
38 | s.subspec 'JSI' do |jsi|
39 | jsi.source_files = "ios/**/*.{h,m,swift,mm}"
40 | jsi.dependency "React-jsi"
41 | jsi.dependency "React-jsiexecutor"
42 | jsi.dependency "ReactCommon/jscallinvoker"
43 | jsi.dependency 'ReactCommon/turbomodule/core'
44 | jsi.dependency 'React-cxxreact'
45 | jsi.dependency 'Folly'
46 | jsi.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Folly\"", 'DEFINES_MODULE' => 'YES', 'ENABLE_BITCODE' => "NO", "LIBRAY_SEARCH_PATHS" => "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"" }
47 | jsi.compiler_flags = folly_compiler_flags
48 | end
49 |
50 |
51 | end
52 |
53 |
--------------------------------------------------------------------------------
/scripts/examples_postinstall.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /*
4 | * Using libraries within examples and linking them within packages.json like:
5 | * "react-native-library-name": "file:../"
6 | * will cause problems with the metro bundler if the example will run via
7 | * `react-native run-[ios|android]`. This will result in an error as the metro
8 | * bundler will find multiple versions for the same module while resolving it.
9 | * The reason for that is that if the library is installed it also copies in the
10 | * example folder itself as well as the node_modules folder of the library
11 | * although their are defined in .npmignore and should be ignored in theory.
12 | *
13 | * This postinstall script removes the node_modules folder as well as all
14 | * entries from the libraries .npmignore file within the examples node_modules
15 | * folder after the library was installed. This should resolve the metro
16 | * bundler issue mentioned above.
17 | *
18 | * It is expected this scripts lives in the libraries root folder within a
19 | * scripts folder. As first parameter the relative path to the libraries
20 | * folder within the example's node_modules folder may be provided.
21 | * This script will determine the path from this project's package.json file
22 | * if no such relative path is provided.
23 | * An example's package.json entry could look like:
24 | * "postinstall": "node ../scripts/examples_postinstall.js node_modules/react-native-library-name/"
25 | */
26 |
27 | 'use strict';
28 |
29 | const fs = require('fs');
30 | const path = require('path');
31 |
32 | /// Delete all files and directories for the given path
33 | const removeFileDirectoryRecursively = fileDirPath => {
34 | // Remove file
35 | if (!fs.lstatSync(fileDirPath).isDirectory()) {
36 | fs.unlinkSync(fileDirPath);
37 | return;
38 | }
39 |
40 | // Go down the directory an remove each file / directory recursively
41 | fs.readdirSync(fileDirPath).forEach(entry => {
42 | const entryPath = path.join(fileDirPath, entry);
43 | removeFileDirectoryRecursively(entryPath);
44 | });
45 | fs.rmdirSync(fileDirPath);
46 | };
47 |
48 | /// Remove example/node_modules/react-native-library-name/node_modules directory
49 | const removeLibraryNodeModulesPath = (libraryNodeModulesPath) => {
50 | const nodeModulesPath = path.resolve(libraryNodeModulesPath, 'node_modules')
51 |
52 | if (!fs.existsSync(nodeModulesPath)) {
53 | console.log(`No node_modules path found at ${nodeModulesPath}. Skipping delete.`)
54 | return;
55 | }
56 |
57 | console.log(`Deleting: ${nodeModulesPath}`)
58 | try {
59 | removeFileDirectoryRecursively(nodeModulesPath);
60 | console.log(`Successfully deleted: ${nodeModulesPath}`)
61 | } catch (err) {
62 | console.log(`Error deleting ${nodeModulesPath}: ${err.message}`);
63 | }
64 | };
65 |
66 | /// Remove all entries from the .npmignore within example/node_modules/react-native-library-name/
67 | const removeLibraryNpmIgnorePaths = (npmIgnorePath, libraryNodeModulesPath) => {
68 | if (!fs.existsSync(npmIgnorePath)) {
69 | console.log(`No .npmignore path found at ${npmIgnorePath}. Skipping deleting content.`);
70 | return;
71 | }
72 |
73 | fs.readFileSync(npmIgnorePath, 'utf8').split(/\r?\n/).forEach(entry => {
74 | if (entry.length === 0) {
75 | return
76 | }
77 |
78 | const npmIgnoreLibraryNodeModulesEntryPath = path.resolve(libraryNodeModulesPath, entry);
79 | if (!fs.existsSync(npmIgnoreLibraryNodeModulesEntryPath)) {
80 | return;
81 | }
82 |
83 | console.log(`Deleting: ${npmIgnoreLibraryNodeModulesEntryPath}`)
84 | try {
85 | removeFileDirectoryRecursively(npmIgnoreLibraryNodeModulesEntryPath);
86 | console.log(`Successfully deleted: ${npmIgnoreLibraryNodeModulesEntryPath}`)
87 | } catch (err) {
88 | console.log(`Error deleting ${npmIgnoreLibraryNodeModulesEntryPath}: ${err.message}`);
89 | }
90 | });
91 | };
92 |
93 | // Main start sweeping process
94 | (() => {
95 | // Read out dir of example project
96 | const exampleDir = process.cwd();
97 |
98 | console.log(`Starting postinstall cleanup for ${exampleDir}`);
99 |
100 | // Resolve the React Native library's path within the example's node_modules directory
101 | const libraryNodeModulesPath = process.argv.length > 2
102 | ? path.resolve(exampleDir, process.argv[2])
103 | : path.resolve(exampleDir, 'node_modules', require('../package.json').name);
104 |
105 | console.log(`Removing unwanted artifacts for ${libraryNodeModulesPath}`);
106 |
107 | removeLibraryNodeModulesPath(libraryNodeModulesPath);
108 |
109 | const npmIgnorePath = path.resolve(__dirname, '../.npmignore');
110 | removeLibraryNpmIgnorePaths(npmIgnorePath, libraryNodeModulesPath);
111 | })();
112 |
--------------------------------------------------------------------------------
/src/MediaClipboard.ts:
--------------------------------------------------------------------------------
1 | import { NativeModules, NativeEventEmitter, Platform } from "react-native";
2 |
3 | export type MediaSource = {
4 | uri: string;
5 | mimeType: string;
6 | width: number;
7 | height: number;
8 | };
9 |
10 | export type ClipboardResponse = {
11 | urls: Array;
12 | strings: Array;
13 | hasImages: Boolean;
14 | hasURLs: Boolean;
15 | hasStrings: Boolean;
16 | };
17 |
18 | export let MediaClipboard = NativeModules["MediaClipboard"];
19 |
20 | if (
21 | // @ts-ignore
22 | process.env.NODE_ENV !== "production" &&
23 | !MediaClipboard &&
24 | Platform.OS === "ios"
25 | ) {
26 | console.log({ MediaClipboard });
27 | throw new Error(
28 | "Please ensure react-native-media-clipboard is linked, that you ran pod install, that you imported in your AppDelegate.m, and that you re-built the iOS app."
29 | );
30 | } else if (!MediaClipboard && Platform.OS !== "ios") {
31 | MediaClipboard = {
32 | clipboard: {
33 | urls: [],
34 | strings: [],
35 | hasImages: false,
36 | hasURLs: false,
37 | hasStrings: false
38 | },
39 | mediaSource: null
40 | };
41 | }
42 |
43 | const emitter = Platform.select({
44 | ios: new NativeEventEmitter(MediaClipboard),
45 | android: null
46 | });
47 |
48 | export const listenToClipboardChanges = listener =>
49 | emitter && emitter.addListener("MediaClipboardChange", listener);
50 |
51 | export const stopListeningToClipboardChanges = listener =>
52 | emitter && emitter.removeListener("MediaClipboardChange", listener);
53 |
54 | export const listenToClipboardRemove = listener =>
55 | emitter && emitter.addListener("MediaClipboardRemove", listener);
56 |
57 | export const stopListeningToClipboardRemove = listener =>
58 | emitter && emitter.removeListener("MediaClipboardRemove", listener);
59 |
60 | export const getClipboardContents = (): Promise => {
61 | return new Promise((resolve, reject) => {
62 | if (Platform.OS === "android") {
63 | resolve({
64 | urls: [],
65 | strings: [],
66 | hasImages: false,
67 | hasURLs: false,
68 | hasStrings: false
69 | });
70 | return;
71 | }
72 |
73 | MediaClipboard.getContent((err, contents) => {
74 | if (err) {
75 | reject(err);
76 | return;
77 | } else {
78 | resolve(contents);
79 | }
80 | });
81 | });
82 | };
83 |
84 | export const getClipboardMediaSource = (): Promise => {
85 | if (Platform.OS === "android") {
86 | return Promise.resolve(null);
87 | }
88 |
89 | // @ts-ignore
90 | if (typeof global.Clipboard !== "undefined") {
91 | // @ts-ignore
92 | return global.Clipboard.getMediaSource();
93 | } else {
94 | return new Promise(resolve =>
95 | MediaClipboard.clipboardMediaSource((_, content) => {
96 | resolve(content);
97 | return;
98 | })
99 | );
100 | }
101 | };
102 |
--------------------------------------------------------------------------------
/src/MediaClipboardContext.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import {
3 | MediaClipboard,
4 | ClipboardResponse,
5 | listenToClipboardChanges,
6 | stopListeningToClipboardChanges,
7 | listenToClipboardRemove,
8 | stopListeningToClipboardRemove,
9 | getClipboardMediaSource,
10 | MediaSource
11 | } from "./MediaClipboard";
12 |
13 | export type Clipboard = {
14 | clipboard: ClipboardResponse;
15 | mediaSource: MediaSource | null;
16 | };
17 |
18 | export const ClipboardContext = React.createContext({
19 | clipboard: MediaClipboard.clipboard,
20 | mediaSource: MediaClipboard.mediaSource || null
21 | });
22 |
23 | export type ClipboardProviderState = {
24 | contextValue: Clipboard;
25 | };
26 |
27 | export type ClipboardProviderProps = { children: any };
28 |
29 | export class ClipboardProvider extends React.Component<
30 | ClipboardProviderProps,
31 | ClipboardProviderState
32 | > {
33 | constructor(props: ClipboardProviderProps) {
34 | super(props);
35 |
36 | // @ts-ignore
37 | this.state = {
38 | contextValue: ClipboardProvider.buildContextValue(
39 | MediaClipboard.clipboard,
40 | MediaClipboard.mediaSource || null
41 | )
42 | };
43 | }
44 |
45 | static buildContextValue(
46 | clipboard: ClipboardResponse,
47 | mediaSource: MediaSource | null
48 | ): Clipboard {
49 | return {
50 | clipboard,
51 | mediaSource:
52 | !mediaSource || Object.keys(mediaSource).length === 0
53 | ? null
54 | : mediaSource
55 | };
56 | }
57 |
58 | handleClipboardChange = (clipboard: ClipboardResponse) => {
59 | getClipboardMediaSource().then(mediaSource => {
60 | // @ts-ignore
61 | this.setState({
62 | contextValue: ClipboardProvider.buildContextValue(
63 | clipboard,
64 | mediaSource || null
65 | )
66 | });
67 | });
68 | };
69 |
70 | componentDidMount() {
71 | listenToClipboardChanges(this.handleClipboardChange);
72 | listenToClipboardRemove(this.handleClipboardChange);
73 |
74 | if (
75 | // @ts-ignore
76 | this.state.contextValue.clipboard.hasImages &&
77 | // @ts-ignore
78 | !this.state.contextValue.mediaSource
79 | ) {
80 | this.updateMediaSource();
81 | }
82 | }
83 |
84 | updateMediaSource = () => {
85 | getClipboardMediaSource().then(mediaSource => {
86 | // @ts-ignore
87 | this.setState({
88 | contextValue: ClipboardProvider.buildContextValue(
89 | // @ts-ignore
90 | this.state.contextValue.clipboard,
91 | mediaSource
92 | )
93 | });
94 | });
95 | };
96 |
97 | componentWillUnmount() {
98 | stopListeningToClipboardChanges(this.handleClipboardChange);
99 | stopListeningToClipboardRemove(this.handleClipboardChange);
100 | }
101 |
102 | render() {
103 | // @ts-ignore
104 | const { children } = this.props;
105 | return (
106 | // @ts-ignore
107 |
108 | {children}
109 |
110 | );
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export {
2 | Clipboard,
3 | ClipboardContext,
4 | ClipboardProvider
5 | } from "./MediaClipboardContext";
6 |
7 | export {
8 | MediaSource,
9 | getClipboardMediaSource,
10 | listenToClipboardChanges,
11 | stopListeningToClipboardChanges,
12 | listenToClipboardRemove,
13 | stopListeningToClipboardRemove,
14 | getClipboardContents,
15 | ClipboardResponse
16 | } from "./MediaClipboard";
17 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowUnreachableCode": true,
4 | "allowUnusedLabels": true,
5 | "alwaysStrict": false,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "jsx": "react",
9 | "lib": ["esnext"],
10 | "module": "esnext",
11 | "moduleResolution": "node",
12 | "noFallthroughCasesInSwitch": true,
13 | "noImplicitAny": false,
14 | "noImplicitReturns": false,
15 | "noImplicitThis": false,
16 | "noImplicitUseStrict": false,
17 | "noStrictGenericChecks": true,
18 | "noUnusedLocals": true,
19 | "noUnusedParameters": true,
20 | "resolveJsonModule": true,
21 | "skipLibCheck": true,
22 | "strict": false,
23 | "target": "esnext"
24 | },
25 | "exclude": ["typings/**/*", "lib/**/*", "templates/**/*"]
26 | }
27 |
--------------------------------------------------------------------------------