@imgly/editor-react-native 1.72.3 → 1.73.0

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.
@@ -2,70 +2,46 @@ import Foundation
2
2
  import IMGLYEditor
3
3
  import IMGLYEngine
4
4
 
5
- /// An extension that provides convenience methods for `OnCreate` calls.
6
- extension OnCreate {
7
- /// Populates the editor based on a given `config`, `sourceType` and `defaultScene`.
8
- /// - Parameters:
9
- /// - settings: The `EditorSettings` containing the relevant editor information.
10
- /// - sourceType: The `EditorSourceType` defining of which type the source is.
11
- /// - defaultSource: The `URL` of the defaultScene if no `EditorSettings.source` is specified.
12
- /// - Returns: An `OnCreate.Callback`.
13
- @MainActor
14
- public static func load(
15
- _ settings: EditorSettings?,
16
- _ sourceType: EditorSourceType = .scene,
17
- defaultSource: URL? = nil,
18
- ) -> OnCreate.Callback {
19
- { engine in
20
- var url: URL?
21
-
22
- if let source = settings?.source?.source {
23
- if let sourceUrl = URL(string: source), sourceUrl.scheme != nil {
24
- url = sourceUrl
25
- } else {
26
- throw "Specified URL is not valid."
27
- }
28
- } else if let defaultSource {
29
- url = defaultSource
30
- }
31
-
32
- if let url {
33
- switch settings?.source?.type ?? sourceType {
34
- case .image:
35
- try await engine.scene.create(fromImage: url)
36
- case .video:
37
- try await engine.scene.create(fromVideo: url)
38
- default:
39
- try await engine.scene.load(from: url)
40
- }
41
- } else {
42
- try await OnCreate.default(engine)
43
- }
5
+ // MARK: - React Native Source Loading
44
6
 
45
- var baseURL: URL?
46
- if let baseUri = settings?.baseUri {
47
- if let temp = URL(string: baseUri), temp.scheme != nil {
48
- baseURL = temp
49
- }
50
- }
51
- try await Self.loadDefaultAssets(baseURL: baseURL)(engine)
7
+ public extension OnCreate {
8
+ /// Loads a source from React Native editor settings.
9
+ ///
10
+ /// This helper is designed to be used as the `createScene` parameter of starter kit
11
+ /// `defaultOnCreate` callbacks. It resolves the source URL from settings and loads it
12
+ /// based on the specified source type.
13
+ ///
14
+ /// ## Example
15
+ /// ```swift
16
+ /// builder.onCreate { engine, _ in
17
+ /// if let createScene = try OnCreate.loadFromSettings(settings) {
18
+ /// try await DesignEditorConfiguration.defaultOnCreate(createScene: createScene)(engine)
19
+ /// } else {
20
+ /// try await DesignEditorConfiguration.defaultOnCreate()(engine)
21
+ /// }
22
+ /// }
23
+ /// ```
24
+ ///
25
+ /// - Parameter settings: The `EditorSettings` containing the source configuration from React Native.
26
+ /// - Returns: An `OnCreate.Callback` that loads the source, or `nil` if no source is specified
27
+ /// (allowing the starter kit's default scene to be used instead).
28
+ /// - Throws: If a source is specified but its URL is invalid.
29
+ static func loadFromSettings(
30
+ _ settings: EditorSettings?,
31
+ ) throws -> Callback? {
32
+ guard let source = settings?.source else { return nil }
33
+ guard let url = source.url else {
34
+ throw "Specified URL is not valid: \(source.source)"
52
35
  }
53
- }
54
-
55
- /// Loads the default assets for the asset library.
56
- /// - Parameter baseURL: The base `URL` from your CDN where the assets are hosted.
57
- /// - Returns: A `Callback`.
58
- static func loadDefaultAssets(baseURL: URL?) -> Callback {
59
- { engine in
60
- let baseURI = baseURL ?? Engine.assetBaseURL
61
- try await engine.addDefaultAssetSources(baseURL: baseURI)
62
- try await engine.addDemoAssetSources(
63
- baseURL: baseURI,
64
- sceneMode: engine.scene.getMode(),
65
- withUploadAssetSources: true,
66
- )
67
- try await engine.asset.addSource(TextAssetSource(engine: engine))
68
- try engine.asset.addSource(PhotoRollAssetSource(engine: engine))
36
+ return { engine in
37
+ switch source.type {
38
+ case .image:
39
+ try await engine.scene.create(fromImage: url)
40
+ case .video:
41
+ try await engine.scene.create(fromVideo: url)
42
+ case .scene:
43
+ try await engine.scene.load(from: url)
44
+ }
69
45
  }
70
46
  }
71
47
  }
@@ -1,6 +1,7 @@
1
1
  import UIKit
2
2
 
3
3
  /// The iOS implementation for the `@imgly/editor-react-native` React Native module.
4
+ @MainActor
4
5
  @objc(IMGLYEditorModuleSwiftAdapter) @objcMembers public class IMGLYEditorModuleSwiftAdapter: NSObject {
5
6
  // MARK: - Typealias
6
7
 
@@ -1,8 +1,5 @@
1
- import IMGLYApparelEditor
2
- import IMGLYDesignEditor
3
- import IMGLYPhotoEditor
4
- import IMGLYPostcardEditor
5
- import IMGLYVideoEditor
1
+ import IMGLYEditor
2
+ import IMGLYEngine
6
3
  import SwiftUI
7
4
  import UIKit
8
5
 
@@ -18,60 +15,156 @@ public enum EditorBuilder {
18
15
  /// - metadata: The metadata.
19
16
  /// - result: The `EditorBuilderResult` used to return a result.
20
17
  /// - Returns: A `UIViewController`.
21
- public typealias Builder = (
18
+ public typealias Builder = @MainActor (
22
19
  _ settings: EditorSettings,
23
20
  _ preset: EditorPreset?,
24
21
  _ metadata: [String: Any]?,
25
22
  _ result: @escaping EditorBuilderResult
26
23
  ) -> UIViewController
27
24
 
28
- /// The default apparel editor.
29
- public static func apparel() -> Builder {
30
- { config, _, _, result in
31
- @ViewBuilder func apparelEditor(settings _: EditorSettings, result: @escaping EditorBuilderResult) -> some View {
32
- ModalApparelEditor(settings: config, result: result)
33
- }
34
- return UIHostingController(rootView: apparelEditor(settings: config, result: result))
35
- }
36
- }
37
-
38
- /// The default postcard editor.
39
- public static func postcard() -> Builder {
40
- { config, _, _, result in
41
- @ViewBuilder func postcardEditor(settings _: EditorSettings, result: @escaping EditorBuilderResult) -> some View {
42
- ModalPostcardEditor(settings: config, result: result)
43
- }
44
- return UIHostingController(rootView: postcardEditor(settings: config, result: result))
45
- }
46
- }
47
-
48
25
  /// The default design editor.
49
26
  public static func design() -> Builder {
50
27
  { settings, _, _, result in
51
- @ViewBuilder func designEditor(settings: EditorSettings, result: @escaping EditorBuilderResult) -> some View {
52
- ModalDesignEditor(settings: settings, result: result)
53
- }
54
- return UIHostingController(rootView: designEditor(settings: settings, result: result))
28
+ UIHostingController(rootView: NavigationView {
29
+ Editor(engineSettings(for: settings))
30
+ .imgly.configuration {
31
+ DesignEditorConfiguration { builder in
32
+ builder.onCreate { engine, _ in
33
+ if let createScene = try OnCreate.loadFromSettings(settings) {
34
+ try await DesignEditorConfiguration.defaultOnCreate(createScene: createScene)(engine)
35
+ } else {
36
+ try await DesignEditorConfiguration.defaultOnCreate()(engine)
37
+ }
38
+ }
39
+ builder.onExport { engine, _, _ in
40
+ do {
41
+ let editorResult = try await OnExport.export(engine, .pdf)
42
+ result(.success(editorResult))
43
+ } catch {
44
+ result(.failure(error))
45
+ }
46
+ }
47
+ }
48
+ ModalEditorConfiguration(result: result)
49
+ }
50
+ }.navigationViewStyle(.stack))
55
51
  }
56
52
  }
57
53
 
58
54
  /// The default photo editor.
59
55
  public static func photo() -> Builder {
60
56
  { settings, _, _, result in
61
- @ViewBuilder func photoEditor(settings: EditorSettings, result: @escaping EditorBuilderResult) -> some View {
62
- ModalPhotoEditor(settings: settings, result: result)
63
- }
64
- return UIHostingController(rootView: photoEditor(settings: settings, result: result))
57
+ UIHostingController(rootView: NavigationView {
58
+ Editor(engineSettings(for: settings))
59
+ .imgly.configuration {
60
+ PhotoEditorConfiguration { builder in
61
+ builder.onCreate { engine, _ in
62
+ if let createScene = try OnCreate.loadFromSettings(settings) {
63
+ try await PhotoEditorConfiguration.defaultOnCreate(createScene: createScene)(engine)
64
+ } else {
65
+ try await PhotoEditorConfiguration.defaultOnCreate()(engine)
66
+ }
67
+ }
68
+ builder.onExport { engine, _, _ in
69
+ do {
70
+ let editorResult = try await OnExport.export(engine, .png)
71
+ result(.success(editorResult))
72
+ } catch {
73
+ result(.failure(error))
74
+ }
75
+ }
76
+ }
77
+ ModalEditorConfiguration(result: result)
78
+ }
79
+ }.navigationViewStyle(.stack))
65
80
  }
66
81
  }
67
82
 
68
83
  /// The default video editor.
69
84
  public static func video() -> Builder {
70
85
  { settings, _, _, result in
71
- @ViewBuilder func videoEditor(settings: EditorSettings, result: @escaping EditorBuilderResult) -> some View {
72
- ModalVideoEditor(settings: settings, result: result)
73
- }
74
- return UIHostingController(rootView: videoEditor(settings: settings, result: result))
86
+ UIHostingController(rootView: NavigationView {
87
+ Editor(engineSettings(for: settings))
88
+ .imgly.configuration {
89
+ VideoEditorConfiguration { builder in
90
+ builder.onCreate { engine, _ in
91
+ if let createScene = try OnCreate.loadFromSettings(settings) {
92
+ try await VideoEditorConfiguration.defaultOnCreate(createScene: createScene)(engine)
93
+ } else {
94
+ try await VideoEditorConfiguration.defaultOnCreate()(engine)
95
+ }
96
+ }
97
+ builder.onExport { engine, eventHandler, _ in
98
+ do {
99
+ let editorResult = try await OnExport.exportVideo(engine, eventHandler, .mp4)
100
+ result(.success(editorResult))
101
+ } catch {
102
+ if error is CancellationError { return }
103
+ result(.failure(error))
104
+ }
105
+ }
106
+ }
107
+ ModalEditorConfiguration(result: result)
108
+ }
109
+ }.navigationViewStyle(.stack))
110
+ }
111
+ }
112
+
113
+ /// The default apparel editor.
114
+ public static func apparel() -> Builder {
115
+ { settings, _, _, result in
116
+ UIHostingController(rootView: NavigationView {
117
+ Editor(engineSettings(for: settings))
118
+ .imgly.configuration {
119
+ ApparelEditorConfiguration { builder in
120
+ builder.onCreate { engine, _ in
121
+ if let createScene = try OnCreate.loadFromSettings(settings) {
122
+ try await ApparelEditorConfiguration.defaultOnCreate(createScene: createScene)(engine)
123
+ } else {
124
+ try await ApparelEditorConfiguration.defaultOnCreate()(engine)
125
+ }
126
+ }
127
+ builder.onExport { engine, _, _ in
128
+ do {
129
+ let editorResult = try await OnExport.export(engine, .pdf)
130
+ result(.success(editorResult))
131
+ } catch {
132
+ result(.failure(error))
133
+ }
134
+ }
135
+ }
136
+ ModalEditorConfiguration(result: result)
137
+ }
138
+ }.navigationViewStyle(.stack))
139
+ }
140
+ }
141
+
142
+ /// The default postcard editor.
143
+ public static func postcard() -> Builder {
144
+ { settings, _, _, result in
145
+ UIHostingController(rootView: NavigationView {
146
+ Editor(engineSettings(for: settings))
147
+ .imgly.configuration {
148
+ PostcardEditorConfiguration { builder in
149
+ builder.onCreate { engine, _ in
150
+ if let createScene = try OnCreate.loadFromSettings(settings) {
151
+ try await PostcardEditorConfiguration.defaultOnCreate(createScene: createScene)(engine)
152
+ } else {
153
+ try await PostcardEditorConfiguration.defaultOnCreate()(engine)
154
+ }
155
+ }
156
+ builder.onExport { engine, _, _ in
157
+ do {
158
+ let editorResult = try await OnExport.export(engine, .pdf)
159
+ result(.success(editorResult))
160
+ } catch {
161
+ result(.failure(error))
162
+ }
163
+ }
164
+ }
165
+ ModalEditorConfiguration(result: result)
166
+ }
167
+ }.navigationViewStyle(.stack))
75
168
  }
76
169
  }
77
170
 
@@ -91,14 +184,9 @@ public enum EditorBuilder {
91
184
  }
92
185
  }
93
186
 
94
- // MARK: - Custom
187
+ // MARK: - Helpers
95
188
 
96
- /// An extension for the editor views.
97
189
  extension EditorBuilder {
98
- /// The message to send if the native error has been dismissed after throwing an error.
99
- private static let exportFailedMessage =
100
- "Failed to initialize the editor. Please verify that the loaded license is correct."
101
-
102
190
  /// Generates `EngineSettings` based on an `EditorSettings`.
103
191
  /// - Parameter settings: The `EditorSettings`.
104
192
  /// - Returns: The derived `EngineSettings`.
@@ -109,185 +197,4 @@ extension EditorBuilder {
109
197
  EngineSettings(license: settings.license, userID: settings.userId)
110
198
  }
111
199
  }
112
-
113
- /// A modal apparel editor handling native errors.
114
- private struct ModalApparelEditor: View {
115
- @Environment(\.dismiss) private var dismiss
116
- private let settings: EditorSettings
117
- private let result: EditorBuilderResult
118
-
119
- init(settings: EditorSettings, result: @escaping EditorBuilderResult) {
120
- self.settings = settings
121
- self.result = result
122
- }
123
-
124
- var body: some View {
125
- ModalEditor {
126
- ApparelEditor(engineSettings(for: settings))
127
- .imgly.onCreate { engine in
128
- try await OnCreate.load(settings, defaultSource: ApparelEditor.defaultScene)(engine)
129
- }
130
- .imgly.onExport { engine, _ in
131
- do {
132
- let editorResult = try await OnExport.export(engine, .pdf)
133
- result(.success(editorResult))
134
- } catch {
135
- result(.failure(error))
136
- }
137
- }
138
- } onDismiss: { cancelled in
139
- if cancelled {
140
- result(.success(nil))
141
- dismiss()
142
- } else {
143
- result(.failure(exportFailedMessage))
144
- }
145
- }
146
- }
147
- }
148
-
149
- /// A modal postcard editor handling native errors.
150
- private struct ModalPostcardEditor: View {
151
- @Environment(\.dismiss) private var dismiss
152
- private let settings: EditorSettings
153
- private let result: EditorBuilderResult
154
-
155
- init(settings: EditorSettings, result: @escaping EditorBuilderResult) {
156
- self.settings = settings
157
- self.result = result
158
- }
159
-
160
- var body: some View {
161
- ModalEditor {
162
- PostcardEditor(engineSettings(for: settings))
163
- .imgly.onCreate { engine in
164
- try await OnCreate.load(settings, defaultSource: PostcardEditor.defaultScene)(engine)
165
- }
166
- .imgly.onExport { engine, _ in
167
- do {
168
- let editorResult = try await OnExport.export(engine, .pdf)
169
- result(.success(editorResult))
170
- } catch {
171
- result(.failure(error))
172
- }
173
- }
174
- } onDismiss: { cancelled in
175
- if cancelled {
176
- result(.success(nil))
177
- dismiss()
178
- } else {
179
- result(.failure(exportFailedMessage))
180
- }
181
- }
182
- }
183
- }
184
-
185
- /// A modal design editor handling native errors.
186
- private struct ModalDesignEditor: View {
187
- @Environment(\.dismiss) private var dismiss
188
- private let settings: EditorSettings
189
- private let result: EditorBuilderResult
190
-
191
- init(settings: EditorSettings, result: @escaping EditorBuilderResult) {
192
- self.settings = settings
193
- self.result = result
194
- }
195
-
196
- var body: some View {
197
- ModalEditor {
198
- DesignEditor(engineSettings(for: settings))
199
- .imgly.onCreate { engine in
200
- try await OnCreate.load(settings, defaultSource: DesignEditor.defaultScene)(engine)
201
- }
202
- .imgly.onExport { engine, _ in
203
- do {
204
- let editorResult = try await OnExport.export(engine, .pdf)
205
- result(.success(editorResult))
206
- } catch {
207
- result(.failure(error))
208
- }
209
- }
210
- } onDismiss: { cancelled in
211
- if cancelled {
212
- result(.success(nil))
213
- dismiss()
214
- } else {
215
- result(.failure(exportFailedMessage))
216
- }
217
- }
218
- }
219
- }
220
-
221
- /// A modal photo editor handling native errors.
222
- private struct ModalPhotoEditor: View {
223
- @Environment(\.dismiss) private var dismiss
224
- private let settings: EditorSettings
225
- private let result: EditorBuilderResult
226
-
227
- init(settings: EditorSettings, result: @escaping EditorBuilderResult) {
228
- self.settings = settings
229
- self.result = result
230
- }
231
-
232
- var body: some View {
233
- ModalEditor {
234
- PhotoEditor(engineSettings(for: settings))
235
- .imgly.onCreate { engine in
236
- try await OnCreate
237
- .load(settings, settings.source?.type ?? .image, defaultSource: PhotoEditor.defaultImage)(engine)
238
- }
239
- .imgly.onExport { engine, _ in
240
- do {
241
- let editorResult = try await OnExport.export(engine, .png)
242
- result(.success(editorResult))
243
- } catch {
244
- result(.failure(error))
245
- }
246
- }
247
- } onDismiss: { cancelled in
248
- if cancelled {
249
- result(.success(nil))
250
- dismiss()
251
- } else {
252
- result(.failure(exportFailedMessage))
253
- }
254
- }
255
- }
256
- }
257
-
258
- /// A modal video editor handling native errors.
259
- private struct ModalVideoEditor: View {
260
- @Environment(\.dismiss) private var dismiss
261
- private let settings: EditorSettings
262
- private let result: EditorBuilderResult
263
-
264
- init(settings: EditorSettings, result: @escaping EditorBuilderResult) {
265
- self.settings = settings
266
- self.result = result
267
- }
268
-
269
- var body: some View {
270
- ModalEditor {
271
- VideoEditor(engineSettings(for: settings))
272
- .imgly.onCreate { engine in
273
- try await OnCreate.load(settings, defaultSource: VideoEditor.defaultScene)(engine)
274
- }
275
- .imgly.onExport { engine, eventHandler in
276
- do {
277
- let editorResult = try await OnExport.exportVideo(engine, eventHandler, .mp4)
278
- result(.success(editorResult))
279
- } catch {
280
- result(.failure(error))
281
- }
282
- }
283
- } onDismiss: { cancelled in
284
- if cancelled {
285
- result(.success(nil))
286
- dismiss()
287
- } else {
288
- result(.failure(exportFailedMessage))
289
- }
290
- }
291
- }
292
- }
293
200
  }
@@ -0,0 +1,40 @@
1
+ import IMGLYEditor
2
+ import SwiftUI
3
+
4
+ /// An editor configuration that handles modal dismiss behavior for React Native.
5
+ public final class ModalEditorConfiguration: EditorConfiguration {
6
+ private let result: EditorBuilder.EditorBuilderResult
7
+
8
+ /// Creates a modal editor configuration.
9
+ /// - Parameters:
10
+ /// - result: The closure used to return a result to React Native.
11
+ /// - customize: An optional closure to further customize the configuration.
12
+ public init(
13
+ result: @escaping EditorBuilder.EditorBuilderResult,
14
+ customize: (_ builder: Builder) -> Void = { _ in },
15
+ ) {
16
+ self.result = result
17
+ super.init(customize)
18
+ }
19
+
20
+ override public var navigationBar: NavigationBar.Configuration? {
21
+ NavigationBar.Configuration { navBuilder in
22
+ navBuilder.modify { [result] _, items in
23
+ items.replace(id: NavigationBar.Buttons.ID.closeEditor) {
24
+ NavigationBar.Buttons.closeEditor(
25
+ action: { _ in result(.success(nil)) },
26
+ label: { _ in SwiftUI.Label("Home", systemImage: "house") },
27
+ )
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ override public var onError: OnError.Handler? {
34
+ { [result] error, eventHandler, _ in
35
+ eventHandler.send(.showErrorAlert(error) {
36
+ result(.failure("Failed to initialize the editor. Please verify that the loaded license is correct."))
37
+ })
38
+ }
39
+ }
40
+ }
@@ -5,4 +5,12 @@ public struct Source: Codable {
5
5
 
6
6
  /// The source type.
7
7
  public var type: EditorSourceType
8
+
9
+ /// The resolved source URL, or `nil` if the source string is not a valid URL with a scheme.
10
+ public var url: URL? {
11
+ guard let url = URL(string: source), url.scheme != nil else {
12
+ return nil
13
+ }
14
+ return url
15
+ }
8
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imgly/editor-react-native",
3
- "version": "1.72.3",
3
+ "version": "1.73.0",
4
4
  "description": "CreativeEditor SDK for React Native - enable photo, video and design editing for your users on iOS and Android.",
5
5
  "react-native": "index.js",
6
6
  "source": "index.js",
@@ -96,7 +96,7 @@ export class EditorSettingsModel implements EditorSettings {
96
96
  * sources (stickers, filters, and more).
97
97
  */
98
98
  baseUri: string =
99
- 'https://cdn.img.ly/packages/imgly/cesdk-react-native/1.72.3/assets';
99
+ 'https://cdn.img.ly/packages/imgly/cesdk-react-native/1.73.0/assets';
100
100
 
101
101
  /**
102
102
  * Unique ID tied to your application's user.
@@ -1,43 +0,0 @@
1
- import IMGLYEditor
2
- import SwiftUI
3
-
4
- /// The `ModalEditor` contains an editor and adds the top navigation bar.
5
- public struct ModalEditor<Editor: View, Label: View>: View {
6
- @ViewBuilder private let editor: () -> Editor
7
- @ViewBuilder private let dismissLabel: () -> Label
8
- private let onDismiss: (_ cancelled: Bool) -> Void
9
-
10
- /// Initializes a new `ModalEditor`.
11
- /// - Parameters:
12
- /// - editor: The editor that is contained.
13
- /// - dismissLabel: The label used for the dismiss action.
14
- /// - onDismiss: The closure called when the editor is dismissed.
15
- /// - cancelled: Indicates whether the editor has been dismissed by the user.
16
- /// If `false`, the editor has been closed due to an error.
17
- public init(@ViewBuilder editor: @escaping () -> Editor,
18
- @ViewBuilder dismissLabel: @escaping () -> Label = { SwiftUI.Label("Home", systemImage: "house") },
19
- onDismiss: @escaping (_ cancelled: Bool) -> Void) {
20
- self.editor = editor
21
- self.dismissLabel = dismissLabel
22
- self.onDismiss = onDismiss
23
- }
24
-
25
- /// The body.
26
- public var body: some View {
27
- NavigationView {
28
- editor()
29
- .imgly.modifyNavigationBarItems { _, items in
30
- items.replace(id: NavigationBar.Buttons.ID.closeEditor) {
31
- NavigationBar.Buttons.closeEditor(
32
- action: { _ in onDismiss(true) },
33
- label: { _ in dismissLabel() },
34
- )
35
- }
36
- }
37
- .imgly.onError { error, eventHandler in
38
- eventHandler.send(.showErrorAlert(error) { onDismiss(false) })
39
- }
40
- }
41
- .navigationViewStyle(.stack)
42
- }
43
- }