@phucprime/react-native-image-editor 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -31
- package/android/build.gradle +5 -1
- package/android/libs/README.md +7 -0
- package/android/libs/photo-editor-android.jar +0 -0
- package/android/src/main/java/ui/photoeditor/RNPhotoEditorModule.java +155 -60
- package/android/src/main/java/ui/photoeditor/RNPhotoEditorPackage.java +50 -12
- package/android/src/main/res/layout/photo_editor_sdk_image_item_list.xml +16 -0
- package/android/src/main/res/layout/photo_editor_sdk_text_item_list.xml +13 -0
- package/android/src/newarch/java/ui/photoeditor/RNPhotoEditorSpec.java +10 -2
- package/android/src/oldarch/java/ui/photoeditor/RNPhotoEditorSpec.java +22 -4
- package/ios/RNImageEditor.podspec +39 -8
- package/ios/RNPhotoEditor.mm +32 -6
- package/ios/RNPhotoEditor.swift +178 -95
- package/lib/commonjs/NativeRNPhotoEditor.js +41 -0
- package/lib/commonjs/NativeRNPhotoEditor.js.map +1 -0
- package/lib/commonjs/index.js +142 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/module/NativeRNPhotoEditor.js +40 -0
- package/lib/module/NativeRNPhotoEditor.js.map +1 -0
- package/lib/module/index.js +140 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/NativeRNPhotoEditor.d.ts +46 -0
- package/lib/typescript/NativeRNPhotoEditor.d.ts.map +1 -0
- package/lib/{index.d.ts → typescript/index.d.ts} +44 -34
- package/lib/typescript/index.d.ts.map +1 -0
- package/package.json +53 -22
- package/react-native-image-editor.podspec +8 -5
- package/src/NativeRNPhotoEditor.ts +38 -8
- package/src/index.ts +85 -80
- package/ios/RNPhotoEditor.m +0 -24
- package/lib/NativeRNPhotoEditor.d.ts +0 -14
- package/lib/NativeRNPhotoEditor.d.ts.map +0 -1
- package/lib/NativeRNPhotoEditor.js +0 -5
- package/lib/NativeRNPhotoEditor.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js +0 -120
- package/lib/index.js.map +0 -1
package/ios/RNPhotoEditor.mm
CHANGED
|
@@ -4,11 +4,37 @@
|
|
|
4
4
|
#import <RNPhotoEditorSpec/RNPhotoEditorSpec.h>
|
|
5
5
|
#endif
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Objective-C++ bridge that exposes the Swift `RNPhotoEditor` class to
|
|
9
|
+
* React Native.
|
|
10
|
+
*
|
|
11
|
+
* Method selector change
|
|
12
|
+
* ──────────────────────
|
|
13
|
+
* The TurboModule codegen spec defines `edit(props: UnsafeObject): Promise<string>`.
|
|
14
|
+
* Codegen maps a Promise-returning method to two extra parameters on the native
|
|
15
|
+
* side — a resolve block and a reject block — using the naming convention:
|
|
16
|
+
*
|
|
17
|
+
* edit:(NSDictionary *)props
|
|
18
|
+
* resolve:(RCTPromiseResolveBlock)resolve
|
|
19
|
+
* reject:(RCTPromiseRejectBlock)reject
|
|
20
|
+
*
|
|
21
|
+
* The previous selector `Edit:onDone:onCancel:` used `RCTResponseSenderBlock`
|
|
22
|
+
* which is not recognised by the TurboModule codegen validator and is unsafe
|
|
23
|
+
* across JS context reloads.
|
|
24
|
+
*
|
|
25
|
+
* requiresMainQueueSetup / methodQueue
|
|
26
|
+
* ─────────────────────────────────────
|
|
27
|
+
* Returning YES from +requiresMainQueueSetup ensures the module is initialised
|
|
28
|
+
* on the main thread. The Swift side also overrides methodQueue to return
|
|
29
|
+
* DispatchQueue.main, so UI work happens synchronously without dispatch_async.
|
|
30
|
+
*/
|
|
7
31
|
@interface RCT_EXTERN_MODULE(RNPhotoEditor, NSObject)
|
|
8
32
|
|
|
9
|
-
RCT_EXTERN_METHOD(
|
|
10
|
-
|
|
11
|
-
|
|
33
|
+
RCT_EXTERN_METHOD(
|
|
34
|
+
edit:(NSDictionary *)props
|
|
35
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
36
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
37
|
+
)
|
|
12
38
|
|
|
13
39
|
+ (BOOL)requiresMainQueueSetup {
|
|
14
40
|
return YES;
|
|
@@ -17,9 +43,9 @@ RCT_EXTERN_METHOD(Edit:(NSDictionary *)props
|
|
|
17
43
|
@end
|
|
18
44
|
|
|
19
45
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
20
|
-
//
|
|
21
|
-
// The actual implementation is in RNPhotoEditor.swift; this
|
|
22
|
-
//
|
|
46
|
+
// Declare that the Swift class satisfies the codegen-generated protocol.
|
|
47
|
+
// The actual implementation is in RNPhotoEditor.swift; this forward
|
|
48
|
+
// declaration makes the compiler verify conformance at build time.
|
|
23
49
|
@interface RNPhotoEditor () <NativeRNPhotoEditorSpec>
|
|
24
50
|
@end
|
|
25
51
|
#endif
|
package/ios/RNPhotoEditor.swift
CHANGED
|
@@ -2,167 +2,250 @@ import Foundation
|
|
|
2
2
|
import UIKit
|
|
3
3
|
import iOSPhotoEditor
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Native implementation of the RNPhotoEditor TurboModule.
|
|
7
|
+
*
|
|
8
|
+
* Promise migration
|
|
9
|
+
* ─────────────────
|
|
10
|
+
* The previous implementation stored two `RCTResponseSenderBlock` references
|
|
11
|
+
* as instance properties and invoked them from the `PhotoEditorDelegate`
|
|
12
|
+
* callbacks. On New Architecture, `RCTResponseSenderBlock` values are backed
|
|
13
|
+
* by `CallbackHolder` objects tied to the active JS context. If the JS context
|
|
14
|
+
* reloads (Fast Refresh, error-boundary recovery) while the editor is open,
|
|
15
|
+
* those stored blocks become dangling — invoking them is a no-op or a crash.
|
|
16
|
+
*
|
|
17
|
+
* `RCTPromiseResolveBlock` / `RCTPromiseRejectBlock` are designed for exactly
|
|
18
|
+
* this single-shot async pattern and are safe across context reloads.
|
|
19
|
+
*
|
|
20
|
+
* UIApplication.shared.delegate?.window deprecation fix
|
|
21
|
+
* ──────────────────────────────────────────────────────
|
|
22
|
+
* `UIApplication.shared.delegate?.window` is deprecated in iOS 15. Apps that
|
|
23
|
+
* adopt `UIWindowScene` (the default since iOS 13 / Xcode 11) set their window
|
|
24
|
+
* on the scene, not on the app delegate, so the delegate window is nil.
|
|
25
|
+
* The fix enumerates `connectedScenes` and finds the foreground-active
|
|
26
|
+
* `UIWindowScene`, then picks its key window.
|
|
27
|
+
*/
|
|
5
28
|
@objc(RNPhotoEditor)
|
|
6
29
|
class RNPhotoEditor: NSObject {
|
|
7
30
|
|
|
8
|
-
|
|
9
|
-
private var onDoneEditing: RCTResponseSenderBlock?
|
|
10
|
-
private var onCancelEditing: RCTResponseSenderBlock?
|
|
31
|
+
// MARK: - Stored promise blocks
|
|
11
32
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
33
|
+
/// Stored until the delegate fires. Both are set together and cleared together.
|
|
34
|
+
private var pendingResolve: RCTPromiseResolveBlock?
|
|
35
|
+
private var pendingReject: RCTPromiseRejectBlock?
|
|
15
36
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
37
|
+
/// Path of the image currently being edited; needed by doneEditing to write the result.
|
|
38
|
+
private var editImagePath: String?
|
|
19
39
|
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
// MARK: - RN module config
|
|
41
|
+
|
|
42
|
+
@objc static func requiresMainQueueSetup() -> Bool { true }
|
|
43
|
+
|
|
44
|
+
@objc func methodQueue() -> DispatchQueue { .main }
|
|
45
|
+
|
|
46
|
+
// MARK: - Native method (matches RCT_EXTERN_METHOD selector in .mm)
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Opens the photo editor.
|
|
50
|
+
*
|
|
51
|
+
* Called by the JS bridge via RCT_EXTERN_METHOD.
|
|
52
|
+
* Matches the codegen spec: `edit(props: UnsafeObject): Promise<string>`.
|
|
53
|
+
*
|
|
54
|
+
* - Parameters:
|
|
55
|
+
* - props: Editor configuration dictionary (path, colors, stickers, hiddenControls).
|
|
56
|
+
* - resolve: Called with the saved image path on success.
|
|
57
|
+
* - reject: Called with code "CANCELLED" when the user dismisses without saving.
|
|
58
|
+
*/
|
|
59
|
+
@objc(edit:resolve:reject:)
|
|
60
|
+
func edit(
|
|
61
|
+
_ props: NSDictionary,
|
|
62
|
+
resolve: @escaping RCTPromiseResolveBlock,
|
|
63
|
+
reject: @escaping RCTPromiseRejectBlock
|
|
64
|
+
) {
|
|
65
|
+
// Dispatch all UI work to main thread — methodQueue() returns .main, but
|
|
66
|
+
// being explicit here guards against any future threading changes.
|
|
22
67
|
DispatchQueue.main.async { [weak self] in
|
|
23
|
-
guard let self
|
|
68
|
+
guard let self else { return }
|
|
69
|
+
|
|
70
|
+
// Reject immediately if another edit is already in flight
|
|
71
|
+
if self.pendingResolve != nil {
|
|
72
|
+
reject("ALREADY_OPEN", "An editor session is already in progress", nil)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
24
75
|
|
|
25
|
-
self.
|
|
26
|
-
self.
|
|
27
|
-
self.
|
|
76
|
+
self.pendingResolve = resolve
|
|
77
|
+
self.pendingReject = reject
|
|
78
|
+
self.editImagePath = props["path"] as? String
|
|
28
79
|
|
|
80
|
+
// ── Build the PhotoEditorViewController ───────────────────────────
|
|
29
81
|
let photoEditor = PhotoEditorViewController(
|
|
30
82
|
nibName: "PhotoEditorViewController",
|
|
31
83
|
bundle: Bundle(for: PhotoEditorViewController.self)
|
|
32
84
|
)
|
|
33
85
|
|
|
34
|
-
//
|
|
35
|
-
// TODO: Re-enable when TranslationService is added to the iOSPhotoEditor pod
|
|
36
|
-
// if let languages = props["languages"] as? [String: String] {
|
|
37
|
-
// TranslationService.shared.initTranslations(languages)
|
|
38
|
-
// }
|
|
39
|
-
|
|
40
|
-
// Process Image for Editing
|
|
41
|
-
var image: UIImage?
|
|
86
|
+
// ── Image ─────────────────────────────────────────────────────────
|
|
42
87
|
if let path = self.editImagePath {
|
|
43
|
-
image = UIImage(contentsOfFile: path)
|
|
88
|
+
var image = UIImage(contentsOfFile: path)
|
|
44
89
|
if image == nil, let url = URL(string: path), let data = try? Data(contentsOf: url) {
|
|
45
90
|
image = UIImage(data: data)
|
|
46
91
|
}
|
|
92
|
+
photoEditor.image = image
|
|
47
93
|
}
|
|
48
|
-
photoEditor.image = image
|
|
49
94
|
|
|
50
|
-
//
|
|
95
|
+
// ── Stickers ──────────────────────────────────────────────────────
|
|
51
96
|
if let stickers = props["stickers"] as? [String] {
|
|
52
97
|
photoEditor.stickers = stickers.compactMap { UIImage(named: $0) }
|
|
53
98
|
}
|
|
54
99
|
|
|
55
|
-
//
|
|
100
|
+
// ── Hidden controls ───────────────────────────────────────────────
|
|
56
101
|
if let hiddenControls = props["hiddenControls"] as? [String] {
|
|
57
|
-
photoEditor.hiddenControls = hiddenControls.compactMap {
|
|
58
|
-
switch
|
|
59
|
-
case "crop":
|
|
102
|
+
photoEditor.hiddenControls = hiddenControls.compactMap { name in
|
|
103
|
+
switch name.lowercased() {
|
|
104
|
+
case "crop": return .crop
|
|
60
105
|
case "sticker": return .sticker
|
|
61
|
-
case "draw":
|
|
62
|
-
case "text":
|
|
63
|
-
case "save":
|
|
64
|
-
case "share":
|
|
65
|
-
case "clear":
|
|
66
|
-
default:
|
|
106
|
+
case "draw": return .draw
|
|
107
|
+
case "text": return .text
|
|
108
|
+
case "save": return .save
|
|
109
|
+
case "share": return .share
|
|
110
|
+
case "clear": return .clear
|
|
111
|
+
default: return nil
|
|
67
112
|
}
|
|
68
113
|
}
|
|
69
114
|
}
|
|
70
115
|
|
|
71
|
-
//
|
|
116
|
+
// ── Colors ────────────────────────────────────────────────────────
|
|
72
117
|
if let colors = props["colors"] as? [String] {
|
|
73
118
|
photoEditor.colors = colors.compactMap { self.color(fromHexString: $0) }
|
|
74
119
|
}
|
|
75
120
|
|
|
76
|
-
//
|
|
121
|
+
// ── Present ───────────────────────────────────────────────────────
|
|
77
122
|
photoEditor.photoEditorDelegate = self
|
|
78
|
-
|
|
79
|
-
// The default modal presenting is page sheet in iOS 13, not full screen
|
|
80
123
|
photoEditor.modalPresentationStyle = .fullScreen
|
|
81
124
|
|
|
82
|
-
guard let
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
} else {
|
|
87
|
-
rootViewController.present(photoEditor, animated: true, completion: nil)
|
|
125
|
+
guard let presenter = self.topPresentingViewController() else {
|
|
126
|
+
self.clearPending()
|
|
127
|
+
reject("NO_VIEW_CONTROLLER", "Could not find a view controller to present from", nil)
|
|
128
|
+
return
|
|
88
129
|
}
|
|
130
|
+
presenter.present(photoEditor, animated: true)
|
|
89
131
|
}
|
|
90
132
|
}
|
|
91
133
|
|
|
92
|
-
// MARK: -
|
|
134
|
+
// MARK: - Helpers
|
|
93
135
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
136
|
+
/// Clears stored promise blocks and edit path after the session ends.
|
|
137
|
+
private func clearPending() {
|
|
138
|
+
pendingResolve = nil
|
|
139
|
+
pendingReject = nil
|
|
140
|
+
editImagePath = nil
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Returns the topmost view controller that can present modally.
|
|
145
|
+
*
|
|
146
|
+
* Uses `connectedScenes` instead of the deprecated
|
|
147
|
+
* `UIApplication.shared.delegate?.window` API. The delegate window is
|
|
148
|
+
* `nil` in scene-based apps (UIWindowScene, the default since iOS 13).
|
|
149
|
+
*/
|
|
150
|
+
private func topPresentingViewController() -> UIViewController? {
|
|
151
|
+
// Find the foreground-active window scene
|
|
152
|
+
let scene = UIApplication.shared.connectedScenes
|
|
153
|
+
.first { $0.activationState == .foregroundActive } as? UIWindowScene
|
|
154
|
+
|
|
155
|
+
// Pick the key window from that scene (or fall back to any window)
|
|
156
|
+
let rootVC = scene?.windows.first(where: { $0.isKeyWindow })?.rootViewController
|
|
157
|
+
?? scene?.windows.first?.rootViewController
|
|
158
|
+
|
|
159
|
+
guard let root = rootVC else { return nil }
|
|
160
|
+
|
|
161
|
+
// Walk up the presentation chain to find the topmost presenter
|
|
162
|
+
var top: UIViewController = root
|
|
163
|
+
while let presented = top.presentedViewController {
|
|
164
|
+
top = presented
|
|
100
165
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
166
|
+
return top
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// MARK: - Hex color parser
|
|
170
|
+
|
|
171
|
+
private func colorComponent(from string: String, start: Int, length: Int) -> CGFloat {
|
|
172
|
+
let s = string.index(string.startIndex, offsetBy: start)
|
|
173
|
+
let e = string.index(s, offsetBy: length)
|
|
174
|
+
var sub = String(string[s..<e])
|
|
175
|
+
if length == 1 { sub += sub }
|
|
176
|
+
var hex: UInt64 = 0
|
|
177
|
+
Scanner(string: sub).scanHexInt64(&hex)
|
|
178
|
+
return CGFloat(hex) / 255.0
|
|
104
179
|
}
|
|
105
180
|
|
|
106
181
|
private func color(fromHexString hexString: String) -> UIColor? {
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
blue = colorComponent(from: colorString, start: 6, length: 2)
|
|
182
|
+
let s = hexString.replacingOccurrences(of: "#", with: "").uppercased()
|
|
183
|
+
switch s.count {
|
|
184
|
+
case 3:
|
|
185
|
+
return UIColor(
|
|
186
|
+
red: colorComponent(from: s, start: 0, length: 1),
|
|
187
|
+
green: colorComponent(from: s, start: 1, length: 1),
|
|
188
|
+
blue: colorComponent(from: s, start: 2, length: 1),
|
|
189
|
+
alpha: 1)
|
|
190
|
+
case 4:
|
|
191
|
+
return UIColor(
|
|
192
|
+
red: colorComponent(from: s, start: 1, length: 1),
|
|
193
|
+
green: colorComponent(from: s, start: 2, length: 1),
|
|
194
|
+
blue: colorComponent(from: s, start: 3, length: 1),
|
|
195
|
+
alpha: colorComponent(from: s, start: 0, length: 1))
|
|
196
|
+
case 6:
|
|
197
|
+
return UIColor(
|
|
198
|
+
red: colorComponent(from: s, start: 0, length: 2),
|
|
199
|
+
green: colorComponent(from: s, start: 2, length: 2),
|
|
200
|
+
blue: colorComponent(from: s, start: 4, length: 2),
|
|
201
|
+
alpha: 1)
|
|
202
|
+
case 8:
|
|
203
|
+
return UIColor(
|
|
204
|
+
red: colorComponent(from: s, start: 2, length: 2),
|
|
205
|
+
green: colorComponent(from: s, start: 4, length: 2),
|
|
206
|
+
blue: colorComponent(from: s, start: 6, length: 2),
|
|
207
|
+
alpha: colorComponent(from: s, start: 0, length: 2))
|
|
134
208
|
default:
|
|
135
209
|
return nil
|
|
136
210
|
}
|
|
137
|
-
return UIColor(red: red, green: green, blue: blue, alpha: alpha)
|
|
138
211
|
}
|
|
139
212
|
}
|
|
140
213
|
|
|
141
214
|
// MARK: - PhotoEditorDelegate
|
|
142
215
|
|
|
143
216
|
extension RNPhotoEditor: PhotoEditorDelegate {
|
|
217
|
+
|
|
144
218
|
func doneEditing(image: UIImage) {
|
|
145
|
-
guard let
|
|
219
|
+
guard let resolve = pendingResolve,
|
|
220
|
+
let path = editImagePath else {
|
|
221
|
+
clearPending()
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
defer { clearPending() }
|
|
146
225
|
|
|
147
|
-
|
|
148
|
-
|
|
226
|
+
// Determine output format from the file extension
|
|
227
|
+
let isPNG = (path as NSString).pathExtension.lowercased() == "png"
|
|
149
228
|
|
|
150
|
-
|
|
151
|
-
|
|
229
|
+
// Normalise file:// URI → bare POSIX path before writing
|
|
230
|
+
var writePath = path
|
|
231
|
+
if writePath.hasPrefix("file://"), let url = URL(string: path) {
|
|
232
|
+
writePath = url.path
|
|
152
233
|
}
|
|
153
234
|
|
|
154
235
|
let data = isPNG ? image.pngData() : image.jpegData(compressionQuality: 0.8)
|
|
155
236
|
do {
|
|
156
|
-
try data?.write(to: URL(fileURLWithPath:
|
|
237
|
+
try data?.write(to: URL(fileURLWithPath: writePath), options: .atomic)
|
|
238
|
+
resolve(writePath)
|
|
157
239
|
} catch {
|
|
158
|
-
|
|
240
|
+
// Resolve with original path even on write error so the JS side
|
|
241
|
+
// is not left hanging — the error is logged for diagnostics.
|
|
242
|
+
NSLog("[RNPhotoEditor] write error: %@", error.localizedDescription)
|
|
243
|
+
resolve(writePath)
|
|
159
244
|
}
|
|
160
|
-
|
|
161
|
-
onDoneEditing([path])
|
|
162
245
|
}
|
|
163
246
|
|
|
164
247
|
func canceledEditing() {
|
|
165
|
-
|
|
166
|
-
|
|
248
|
+
defer { clearPending() }
|
|
249
|
+
pendingReject?("CANCELLED", "User cancelled the editor", nil)
|
|
167
250
|
}
|
|
168
251
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _reactNative = require("react-native");
|
|
8
|
+
/**
|
|
9
|
+
* `UnsafeObject` is the codegen-recognised passthrough type for an untyped JS
|
|
10
|
+
* object parameter. The React Native codegen AST parser matches on the name
|
|
11
|
+
* "UnsafeObject" in the source and maps it to NSDictionary* (iOS) /
|
|
12
|
+
* ReadableMap (Android).
|
|
13
|
+
*
|
|
14
|
+
* It must be declared as an empty object type `{}` — NOT as `Record<string, any>`,
|
|
15
|
+
* which the codegen parser rejects with "Unrecognised generic type 'Record'".
|
|
16
|
+
* The tsc compiler accepts `{}` fine for this purpose.
|
|
17
|
+
*/
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
19
|
+
/**
|
|
20
|
+
* TurboModule codegen spec for RNPhotoEditor.
|
|
21
|
+
*
|
|
22
|
+
* Design notes
|
|
23
|
+
* ────────────
|
|
24
|
+
* • `props` is typed as `UnsafeObject` — the correct codegen-recognised alias
|
|
25
|
+
* for an untyped JS object. Using plain `Object` is rejected by the strict
|
|
26
|
+
* codegen validator in React Native 0.74+.
|
|
27
|
+
*
|
|
28
|
+
* • The method returns `Promise<string>` instead of accepting two `Callback`
|
|
29
|
+
* parameters. `Callback` parameters are unsafe on New Architecture because
|
|
30
|
+
* they are backed by a `CallbackHolder` tied to the current JS context; if
|
|
31
|
+
* the JS context reloads while the native Activity / ViewController is open
|
|
32
|
+
* (Fast Refresh, error boundary recovery) the stored block/lambda becomes
|
|
33
|
+
* dangling. A `Promise` is resolved or rejected by the native side exactly
|
|
34
|
+
* once and is safe across context reloads.
|
|
35
|
+
*
|
|
36
|
+
* • On Old Architecture `TurboModuleRegistry.getEnforcing` falls through to
|
|
37
|
+
* the `NativeModules` bridge automatically (RN ≥ 0.73), so no separate
|
|
38
|
+
* bridge registration is needed.
|
|
39
|
+
*/
|
|
40
|
+
var _default = exports.default = _reactNative.TurboModuleRegistry.getEnforcing('RNPhotoEditor');
|
|
41
|
+
//# sourceMappingURL=NativeRNPhotoEditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_reactNative","require","_default","exports","default","TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeRNPhotoEditor.ts"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AApBA,IAAAC,QAAA,GAAAC,OAAA,CAAAC,OAAA,GAgCeC,gCAAmB,CAACC,YAAY,CAAO,eAAe,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = exports.PhotoEditor = exports.ImageEditor = void 0;
|
|
7
|
+
var _NativeRNPhotoEditor = _interopRequireDefault(require("./NativeRNPhotoEditor"));
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
/**
|
|
10
|
+
* Localization strings for the image editor UI.
|
|
11
|
+
* All fields are optional — unset keys fall back to the English defaults.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Editor control identifiers that can be hidden via `hiddenControls`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Configuration options for the image editor.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// ─── Defaults ────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/** Built-in 13-colour drawing palette. */
|
|
25
|
+
const DEFAULT_COLORS = ['#000000', '#808080', '#a9a9a9', '#FFFFFE', '#0000ff', '#00ff00', '#ff0000', '#ffff00', '#ffa500', '#800080', '#00ffff', '#a52a2a', '#ff00ff'];
|
|
26
|
+
|
|
27
|
+
/** Built-in English UI strings. */
|
|
28
|
+
const DEFAULT_LANGUAGES = {
|
|
29
|
+
doneTitle: 'Done',
|
|
30
|
+
saveTitle: 'Save',
|
|
31
|
+
clearAllTitle: 'Clear all',
|
|
32
|
+
cameraTitle: 'Camera',
|
|
33
|
+
galleryTitle: 'Gallery',
|
|
34
|
+
uploadDialogTitle: 'Upload Image',
|
|
35
|
+
uploadPickerTitle: 'Select Picture',
|
|
36
|
+
directoryCreateFail: 'Failed to create directory',
|
|
37
|
+
accessMediaPermissionsMsg: 'To attach photos, we need to access media on your device',
|
|
38
|
+
continueTxt: 'Continue',
|
|
39
|
+
notNow: 'NOT NOW',
|
|
40
|
+
mediaAccessDeniedMsg: 'You denied storage access, no photos will be added.',
|
|
41
|
+
saveImageSucceed: 'Image saved',
|
|
42
|
+
eraserTitle: 'Eraser'
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ─── Core native call ─────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Build the props object and call the native TurboModule.
|
|
49
|
+
*
|
|
50
|
+
* The native module exposes a single `edit(props): Promise<string>` method.
|
|
51
|
+
* This is the only point in the codebase that touches the native boundary,
|
|
52
|
+
* keeping the public API (open / edit) as thin wrappers.
|
|
53
|
+
*/
|
|
54
|
+
function callNative(config) {
|
|
55
|
+
const {
|
|
56
|
+
path,
|
|
57
|
+
stickers = [],
|
|
58
|
+
hiddenControls = [],
|
|
59
|
+
colors = DEFAULT_COLORS,
|
|
60
|
+
languages
|
|
61
|
+
} = config;
|
|
62
|
+
const mergedLanguages = languages ? {
|
|
63
|
+
...DEFAULT_LANGUAGES,
|
|
64
|
+
...languages
|
|
65
|
+
} : DEFAULT_LANGUAGES;
|
|
66
|
+
return _NativeRNPhotoEditor.default.edit({
|
|
67
|
+
path,
|
|
68
|
+
colors,
|
|
69
|
+
hiddenControls,
|
|
70
|
+
stickers,
|
|
71
|
+
languages: mergedLanguages
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* React Native Image Editor — native photo editing for iOS and Android.
|
|
79
|
+
*
|
|
80
|
+
* Supports the New Architecture (Fabric + TurboModules) and the classic bridge.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* // Promise / async-await (recommended)
|
|
85
|
+
* const saved = await ImageEditor.edit('/path/to/photo.jpg', {
|
|
86
|
+
* colors: ['#ff0000', '#00ff00', '#0000ff'],
|
|
87
|
+
* stickers: ['heart', 'star'],
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* // Callback
|
|
91
|
+
* ImageEditor.open({
|
|
92
|
+
* path: '/path/to/photo.jpg',
|
|
93
|
+
* onDone: (path) => console.log('saved:', path),
|
|
94
|
+
* onCancel: () => console.log('cancelled'),
|
|
95
|
+
* });
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
class ImageEditor {
|
|
99
|
+
/**
|
|
100
|
+
* Edit an image and receive the result via callbacks.
|
|
101
|
+
*
|
|
102
|
+
* @param config - Editor configuration including `onDone` / `onCancel`.
|
|
103
|
+
*/
|
|
104
|
+
static open(config) {
|
|
105
|
+
callNative(config).then(config.onDone).catch(() => config.onCancel?.());
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Edit an image and return a Promise.
|
|
110
|
+
*
|
|
111
|
+
* Resolves with the saved image path.
|
|
112
|
+
* Rejects with `{ code: 'CANCELLED' }` when the user dismisses.
|
|
113
|
+
*
|
|
114
|
+
* @param path - Local file path of the image to edit.
|
|
115
|
+
* @param options - Optional editor configuration (excludes path, onDone, onCancel).
|
|
116
|
+
*/
|
|
117
|
+
static edit(path, options) {
|
|
118
|
+
return callNative({
|
|
119
|
+
...options,
|
|
120
|
+
path
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @deprecated Use `ImageEditor.open()` instead.
|
|
126
|
+
*/
|
|
127
|
+
static Edit(config) {
|
|
128
|
+
ImageEditor.open(config);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ─── Exports ──────────────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @deprecated Use `ImageEditor` instead.
|
|
136
|
+
*/
|
|
137
|
+
exports.ImageEditor = ImageEditor;
|
|
138
|
+
const PhotoEditor = exports.PhotoEditor = ImageEditor;
|
|
139
|
+
var _default = exports.default = ImageEditor;
|
|
140
|
+
/** @deprecated Use `ImageEditorConfig` instead. */
|
|
141
|
+
/** @deprecated Use `ImageEditorLanguage` instead. */
|
|
142
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_NativeRNPhotoEditor","_interopRequireDefault","require","e","__esModule","default","DEFAULT_COLORS","DEFAULT_LANGUAGES","doneTitle","saveTitle","clearAllTitle","cameraTitle","galleryTitle","uploadDialogTitle","uploadPickerTitle","directoryCreateFail","accessMediaPermissionsMsg","continueTxt","notNow","mediaAccessDeniedMsg","saveImageSucceed","eraserTitle","callNative","config","path","stickers","hiddenControls","colors","languages","mergedLanguages","NativeRNPhotoEditor","edit","ImageEditor","open","then","onDone","catch","onCancel","options","Edit","exports","PhotoEditor","_default"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;AAAA,IAAAA,oBAAA,GAAAC,sBAAA,CAAAC,OAAA;AAAwD,SAAAD,uBAAAE,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAExD;AACA;AACA;AACA;;AAgCA;AACA;AACA;;AAUA;AACA;AACA;;AAgDA;;AAEA;AACA,MAAMG,cAAwB,GAAG,CAC/B,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,CACV;;AAED;AACA,MAAMC,iBAAgD,GAAG;EACvDC,SAAS,EAAE,MAAM;EACjBC,SAAS,EAAE,MAAM;EACjBC,aAAa,EAAE,WAAW;EAC1BC,WAAW,EAAE,QAAQ;EACrBC,YAAY,EAAE,SAAS;EACvBC,iBAAiB,EAAE,cAAc;EACjCC,iBAAiB,EAAE,gBAAgB;EACnCC,mBAAmB,EAAE,4BAA4B;EACjDC,yBAAyB,EACvB,0DAA0D;EAC5DC,WAAW,EAAE,UAAU;EACvBC,MAAM,EAAE,SAAS;EACjBC,oBAAoB,EAAE,qDAAqD;EAC3EC,gBAAgB,EAAE,aAAa;EAC/BC,WAAW,EAAE;AACf,CAAC;;AAED;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASC,UAAUA,CAACC,MAAyB,EAAmB;EAC9D,MAAM;IACJC,IAAI;IACJC,QAAQ,GAAG,EAAE;IACbC,cAAc,GAAG,EAAE;IACnBC,MAAM,GAAGrB,cAAc;IACvBsB;EACF,CAAC,GAAGL,MAAM;EAEV,MAAMM,eAA8C,GAAGD,SAAS,GAC5D;IAAE,GAAGrB,iBAAiB;IAAE,GAAGqB;EAAU,CAAC,GACtCrB,iBAAiB;EAErB,OAAOuB,4BAAmB,CAACC,IAAI,CAAC;IAC9BP,IAAI;IACJG,MAAM;IACND,cAAc;IACdD,QAAQ;IACRG,SAAS,EAAEC;EACb,CAAC,CAAC;AACJ;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMG,WAAW,CAAC;EAChB;AACF;AACA;AACA;AACA;EACE,OAAOC,IAAIA,CAACV,MAAyB,EAAQ;IAC3CD,UAAU,CAACC,MAAM,CAAC,CAACW,IAAI,CAACX,MAAM,CAACY,MAAM,CAAC,CAACC,KAAK,CAAC,MAAMb,MAAM,CAACc,QAAQ,GAAG,CAAC,CAAC;EACzE;;EAEA;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACE,OAAON,IAAIA,CACTP,IAAY,EACZc,OAAiE,EAChD;IACjB,OAAOhB,UAAU,CAAC;MAAE,GAAGgB,OAAO;MAAEd;IAAK,CAAC,CAAC;EACzC;;EAEA;AACF;AACA;EACE,OAAOe,IAAIA,CAAChB,MAAyB,EAAQ;IAC3CS,WAAW,CAACC,IAAI,CAACV,MAAM,CAAC;EAC1B;AACF;;AAEA;;AAEA;AACA;AACA;AAFAiB,OAAA,CAAAR,WAAA,GAAAA,WAAA;AAGA,MAAMS,WAAW,GAAAD,OAAA,CAAAC,WAAA,GAAGT,WAAW;AAAC,IAAAU,QAAA,GAAAF,OAAA,CAAAnC,OAAA,GAGjB2B,WAAW;AAE1B;AAEA","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `UnsafeObject` is the codegen-recognised passthrough type for an untyped JS
|
|
7
|
+
* object parameter. The React Native codegen AST parser matches on the name
|
|
8
|
+
* "UnsafeObject" in the source and maps it to NSDictionary* (iOS) /
|
|
9
|
+
* ReadableMap (Android).
|
|
10
|
+
*
|
|
11
|
+
* It must be declared as an empty object type `{}` — NOT as `Record<string, any>`,
|
|
12
|
+
* which the codegen parser rejects with "Unrecognised generic type 'Record'".
|
|
13
|
+
* The tsc compiler accepts `{}` fine for this purpose.
|
|
14
|
+
*/
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* TurboModule codegen spec for RNPhotoEditor.
|
|
19
|
+
*
|
|
20
|
+
* Design notes
|
|
21
|
+
* ────────────
|
|
22
|
+
* • `props` is typed as `UnsafeObject` — the correct codegen-recognised alias
|
|
23
|
+
* for an untyped JS object. Using plain `Object` is rejected by the strict
|
|
24
|
+
* codegen validator in React Native 0.74+.
|
|
25
|
+
*
|
|
26
|
+
* • The method returns `Promise<string>` instead of accepting two `Callback`
|
|
27
|
+
* parameters. `Callback` parameters are unsafe on New Architecture because
|
|
28
|
+
* they are backed by a `CallbackHolder` tied to the current JS context; if
|
|
29
|
+
* the JS context reloads while the native Activity / ViewController is open
|
|
30
|
+
* (Fast Refresh, error boundary recovery) the stored block/lambda becomes
|
|
31
|
+
* dangling. A `Promise` is resolved or rejected by the native side exactly
|
|
32
|
+
* once and is safe across context reloads.
|
|
33
|
+
*
|
|
34
|
+
* • On Old Architecture `TurboModuleRegistry.getEnforcing` falls through to
|
|
35
|
+
* the `NativeModules` bridge automatically (RN ≥ 0.73), so no separate
|
|
36
|
+
* bridge registration is needed.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
export default TurboModuleRegistry.getEnforcing('RNPhotoEditor');
|
|
40
|
+
//# sourceMappingURL=NativeRNPhotoEditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeRNPhotoEditor.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;;AAElD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAYA,eAAeA,mBAAmB,CAACC,YAAY,CAAO,eAAe,CAAC","ignoreList":[]}
|