@imgly/editor-react-native 1.40.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.
- package/IMGLYEditorModule.podspec +21 -0
- package/LICENSE.md +137 -0
- package/README.md +31 -0
- package/android/.gradle/8.9/checksums/checksums.lock +0 -0
- package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
- package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
- package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
- package/android/.gradle/8.9/gc.properties +0 -0
- package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
- package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
- package/android/.gradle/vcs-1/gc.properties +0 -0
- package/android/build.gradle +65 -0
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/assets/photo-ui-empty.png +0 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/IMGLYEditorModule.kt +252 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/IMGLYEditorPackage.kt +35 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/activity/EditorActivity.kt +112 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/builder/EditorBuilder.kt +478 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/helper/AssetHelper.kt +18 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/helper/IntentHelper.kt +61 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/model/EditorPreset.kt +47 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/model/EditorResult.kt +73 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/model/EditorSettings.kt +63 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/model/EditorSourceType.kt +39 -0
- package/android/src/main/kotlin/ly/img/editor/reactnative/module/model/Source.kt +44 -0
- package/android/src/newarch/IMGLYEditorModuleSpec.kt +7 -0
- package/android/src/oldarch/IMGLYEditorModuleSpec.kt +18 -0
- package/index.d.ts +3 -0
- package/index.js +3 -0
- package/ios/Constants/IMGLYConstants.h +13 -0
- package/ios/Constants/IMGLYConstants.m +13 -0
- package/ios/Extensions/OnCreate+.swift +75 -0
- package/ios/Extensions/OnExport+.swift +64 -0
- package/ios/Extensions/String+.swift +7 -0
- package/ios/Helper/ExportHelper.swift +67 -0
- package/ios/IMGLYEditorModule-Bridging-Header.h +3 -0
- package/ios/IMGLYEditorModule.h +11 -0
- package/ios/IMGLYEditorModule.mm +86 -0
- package/ios/IMGLYEditorModule.swift +110 -0
- package/ios/Model/EditorBuilder.swift +293 -0
- package/ios/Model/EditorPreset.swift +52 -0
- package/ios/Model/EditorResult.swift +27 -0
- package/ios/Model/EditorSettings.swift +33 -0
- package/ios/Model/EditorSourceType.swift +11 -0
- package/ios/Model/Source.swift +8 -0
- package/ios/View/ModalEditor.swift +55 -0
- package/package.json +46 -0
- package/src/NativeIMGLYEditor.ts +195 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package ly.img.editor.reactnative.module.model
|
|
2
|
+
|
|
3
|
+
import android.os.Parcel
|
|
4
|
+
import android.os.Parcelable
|
|
5
|
+
|
|
6
|
+
/** An enum containing all prebuilt editor configurations. */
|
|
7
|
+
enum class EditorPreset(
|
|
8
|
+
val value: String,
|
|
9
|
+
) : Parcelable {
|
|
10
|
+
/** The apparel editor. */
|
|
11
|
+
APPAREL("apparel"),
|
|
12
|
+
|
|
13
|
+
/** The postcard editor. */
|
|
14
|
+
POSTCARD("postcard"),
|
|
15
|
+
|
|
16
|
+
/** The photo editor. */
|
|
17
|
+
PHOTO("photo"),
|
|
18
|
+
|
|
19
|
+
/** The design editor. */
|
|
20
|
+
DESIGN("design"),
|
|
21
|
+
|
|
22
|
+
/** The video editor. */
|
|
23
|
+
VIDEO("video"),
|
|
24
|
+
;
|
|
25
|
+
|
|
26
|
+
override fun toString(): String = value
|
|
27
|
+
|
|
28
|
+
override fun writeToParcel(
|
|
29
|
+
parcel: Parcel,
|
|
30
|
+
flags: Int,
|
|
31
|
+
) {
|
|
32
|
+
parcel.writeString(value)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override fun describeContents(): Int = 0
|
|
36
|
+
|
|
37
|
+
companion object CREATOR : Parcelable.Creator<EditorPreset> {
|
|
38
|
+
override fun createFromParcel(parcel: Parcel): EditorPreset {
|
|
39
|
+
val value = parcel.readString()
|
|
40
|
+
return entries.find { it.value == value } ?: throw IllegalArgumentException("Invalid value for EditorPreset")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override fun newArray(size: Int): Array<EditorPreset?> = arrayOfNulls(size)
|
|
44
|
+
|
|
45
|
+
fun fromValue(value: String): EditorPreset? = entries.find { it.value == value }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package ly.img.editor.reactnative.module.model
|
|
2
|
+
|
|
3
|
+
import android.os.Parcel
|
|
4
|
+
import android.os.Parcelable
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A class representing the result of an editor export.
|
|
8
|
+
* @property scene The source of the exported scene.
|
|
9
|
+
* @property artifact The source of the exported artifact, e.g. image, video.
|
|
10
|
+
* @property thumbnail The source of the exported thumbnail.
|
|
11
|
+
* @property metadata The associated metadata.
|
|
12
|
+
*/
|
|
13
|
+
data class EditorResult(
|
|
14
|
+
val scene: String?,
|
|
15
|
+
val artifact: String?,
|
|
16
|
+
val thumbnail: String?,
|
|
17
|
+
val metadata: Map<String, Any> = emptyMap(),
|
|
18
|
+
) : Parcelable {
|
|
19
|
+
/**
|
|
20
|
+
* Creates a new instance from a given [Parcel].
|
|
21
|
+
* @param parcel The [Parcel].
|
|
22
|
+
*/
|
|
23
|
+
constructor(parcel: Parcel) : this(
|
|
24
|
+
parcel.readString(),
|
|
25
|
+
parcel.readString(),
|
|
26
|
+
parcel.readString(),
|
|
27
|
+
readMap(parcel),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
override fun writeToParcel(
|
|
31
|
+
parcel: Parcel,
|
|
32
|
+
flags: Int,
|
|
33
|
+
) {
|
|
34
|
+
parcel.writeString(scene)
|
|
35
|
+
parcel.writeString(artifact)
|
|
36
|
+
parcel.writeString(thumbnail)
|
|
37
|
+
writeMap(parcel, metadata, flags)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun describeContents(): Int = 0
|
|
41
|
+
|
|
42
|
+
companion object CREATOR : Parcelable.Creator<EditorResult> {
|
|
43
|
+
override fun createFromParcel(parcel: Parcel): EditorResult = EditorResult(parcel)
|
|
44
|
+
|
|
45
|
+
override fun newArray(size: Int): Array<EditorResult?> = arrayOfNulls(size)
|
|
46
|
+
|
|
47
|
+
private fun writeMap(
|
|
48
|
+
parcel: Parcel,
|
|
49
|
+
map: Map<String, Any>,
|
|
50
|
+
flags: Int,
|
|
51
|
+
) {
|
|
52
|
+
parcel.writeInt(map.size)
|
|
53
|
+
for ((key, value) in map) {
|
|
54
|
+
parcel.writeString(key)
|
|
55
|
+
parcel.writeValue(value)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private fun readMap(parcel: Parcel): Map<String, Any> {
|
|
60
|
+
val size = parcel.readInt()
|
|
61
|
+
val map = mutableMapOf<String, Any>()
|
|
62
|
+
repeat(size) {
|
|
63
|
+
val key = parcel.readString()
|
|
64
|
+
val value = parcel.readValue(this::class.java.classLoader)
|
|
65
|
+
key ?: throw (IllegalStateException("Key must not be null."))
|
|
66
|
+
value ?: throw (IllegalStateException("Value must not be null."))
|
|
67
|
+
|
|
68
|
+
map[key] = value
|
|
69
|
+
}
|
|
70
|
+
return map
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
package ly.img.editor.reactnative.module.model
|
|
2
|
+
|
|
3
|
+
import android.os.Parcel
|
|
4
|
+
import android.os.Parcelable
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A class containing all necessary information to configure an editor.
|
|
8
|
+
* @property license The license key.
|
|
9
|
+
* @property baseUri The base uri for the assets included in the scene that have a relative source.
|
|
10
|
+
* @property assetBaseUri The base uri of the default assets in the asset library.
|
|
11
|
+
* @property userId The id of the current user.
|
|
12
|
+
* @property source The source to load into the editor.
|
|
13
|
+
*/
|
|
14
|
+
data class EditorSettings(
|
|
15
|
+
val license: String,
|
|
16
|
+
val baseUri: String,
|
|
17
|
+
val assetBaseUri: String?,
|
|
18
|
+
val userId: String?,
|
|
19
|
+
var source: Source?,
|
|
20
|
+
) : Parcelable {
|
|
21
|
+
/**
|
|
22
|
+
* Creates a new instance from a given [Parcel].
|
|
23
|
+
* @param parcel The [Parcel].
|
|
24
|
+
*/
|
|
25
|
+
constructor(parcel: Parcel) : this(
|
|
26
|
+
parcel.readString() ?: throw (Exception("Missing value for key 'license.'")),
|
|
27
|
+
parcel.readString() ?: throw (Exception("Missing value for key 'baseUri.'")),
|
|
28
|
+
parcel.readString(),
|
|
29
|
+
parcel.readString(),
|
|
30
|
+
parcel.readParcelable(Source::class.java.classLoader),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
override fun writeToParcel(
|
|
34
|
+
parcel: Parcel,
|
|
35
|
+
flags: Int,
|
|
36
|
+
) {
|
|
37
|
+
parcel.writeString(license)
|
|
38
|
+
parcel.writeString(baseUri)
|
|
39
|
+
parcel.writeString(assetBaseUri)
|
|
40
|
+
parcel.writeString(userId)
|
|
41
|
+
parcel.writeParcelable(source, flags)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
override fun describeContents(): Int = 0
|
|
45
|
+
|
|
46
|
+
companion object CREATOR : Parcelable.Creator<EditorSettings> {
|
|
47
|
+
override fun createFromParcel(parcel: Parcel): EditorSettings = EditorSettings(parcel)
|
|
48
|
+
|
|
49
|
+
override fun newArray(size: Int): Array<EditorSettings?> = arrayOfNulls(size)
|
|
50
|
+
|
|
51
|
+
fun createFromMap(map: Map<String, Any>): EditorSettings? =
|
|
52
|
+
try {
|
|
53
|
+
val license = map["license"] as? String ?: throw (Exception("Missing value for key 'license.'"))
|
|
54
|
+
val sceneBaseUri = map["sceneBaseUri"] as? String ?: throw (Exception("Missing value for key 'sceneBaseUri.'"))
|
|
55
|
+
val assetBaseUri = map["assetBaseUri"] as? String
|
|
56
|
+
val userId = map["userId"] as? String
|
|
57
|
+
val source = (map["source"] as? Map<String, Any>)?.let { Source.createFromMap(it) }
|
|
58
|
+
EditorSettings(license, sceneBaseUri, assetBaseUri, userId, source)
|
|
59
|
+
} catch (e: Exception) {
|
|
60
|
+
null
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
package ly.img.editor.reactnative.module.model
|
|
2
|
+
|
|
3
|
+
import android.os.Parcel
|
|
4
|
+
import android.os.Parcelable
|
|
5
|
+
|
|
6
|
+
/** The *EditorSourceType* defines the type of source that is loaded into the editor. */
|
|
7
|
+
enum class EditorSourceType(
|
|
8
|
+
val value: String,
|
|
9
|
+
) : Parcelable {
|
|
10
|
+
/** A `.scene` file. */
|
|
11
|
+
SCENE("scene"),
|
|
12
|
+
|
|
13
|
+
/** An image file, e.g. `.png`. */
|
|
14
|
+
IMAGE("image"),
|
|
15
|
+
|
|
16
|
+
/** A video file, e.g. `.mp4`. */
|
|
17
|
+
VIDEO("video"),
|
|
18
|
+
;
|
|
19
|
+
|
|
20
|
+
override fun writeToParcel(
|
|
21
|
+
parcel: Parcel,
|
|
22
|
+
flags: Int,
|
|
23
|
+
) {
|
|
24
|
+
parcel.writeString(value)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override fun describeContents(): Int = 0
|
|
28
|
+
|
|
29
|
+
companion object CREATOR : Parcelable.Creator<EditorSourceType> {
|
|
30
|
+
override fun createFromParcel(parcel: Parcel): EditorSourceType {
|
|
31
|
+
val value = parcel.readString()
|
|
32
|
+
return entries.find { it.value == value } ?: throw IllegalArgumentException("Invalid value for EditorSourceType")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override fun newArray(size: Int): Array<EditorSourceType?> = arrayOfNulls(size)
|
|
36
|
+
|
|
37
|
+
fun fromValue(value: String): EditorSourceType? = entries.find { it.value == value }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
package ly.img.editor.reactnative.module.model
|
|
2
|
+
|
|
3
|
+
import android.os.Parcel
|
|
4
|
+
import android.os.Parcelable
|
|
5
|
+
|
|
6
|
+
data class Source(
|
|
7
|
+
var source: String,
|
|
8
|
+
val type: EditorSourceType,
|
|
9
|
+
) : Parcelable {
|
|
10
|
+
constructor(parcel: Parcel) : this(
|
|
11
|
+
parcel.readString() ?: throw (Exception("Missing value for key 'source'.")),
|
|
12
|
+
parcel.readParcelable(EditorSourceType::class.java.classLoader) ?: throw (Exception("Missing value for key 'type'.")),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
override fun writeToParcel(
|
|
16
|
+
parcel: Parcel,
|
|
17
|
+
flags: Int,
|
|
18
|
+
) {
|
|
19
|
+
parcel.writeString(source)
|
|
20
|
+
parcel.writeParcelable(type, flags)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override fun describeContents(): Int = 0
|
|
24
|
+
|
|
25
|
+
companion object CREATOR : Parcelable.Creator<Source> {
|
|
26
|
+
override fun createFromParcel(parcel: Parcel): Source = Source(parcel)
|
|
27
|
+
|
|
28
|
+
override fun newArray(size: Int): Array<Source?> = arrayOfNulls(size)
|
|
29
|
+
|
|
30
|
+
fun createFromMap(map: Map<String, Any>): Source? =
|
|
31
|
+
runCatching {
|
|
32
|
+
val source = map["source"] as? String
|
|
33
|
+
val typeRaw = map["type"] as? String
|
|
34
|
+
|
|
35
|
+
source?.let { s ->
|
|
36
|
+
typeRaw?.let { t ->
|
|
37
|
+
EditorSourceType.fromValue(t)?.let { type ->
|
|
38
|
+
Source(s, type)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}.getOrNull()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package ly.img.editor.reactnative.module
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Promise
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
|
+
import com.facebook.react.bridge.ReadableMap
|
|
7
|
+
|
|
8
|
+
abstract class IMGLYEditorModuleSpec internal constructor(
|
|
9
|
+
context: ReactApplicationContext,
|
|
10
|
+
) : ReactContextBaseJavaModule(context) {
|
|
11
|
+
abstract fun openEditor(
|
|
12
|
+
settings: ReadableMap?,
|
|
13
|
+
source: ReadableMap?,
|
|
14
|
+
preset: String?,
|
|
15
|
+
metadata: ReadableMap?,
|
|
16
|
+
promise: Promise?,
|
|
17
|
+
)
|
|
18
|
+
}
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
|
|
3
|
+
@interface IMGLYConstants : NSObject
|
|
4
|
+
|
|
5
|
+
// Error codes
|
|
6
|
+
extern NSString* const IMGLYErrorExportFailed;
|
|
7
|
+
extern NSString* const IMGLYErrorParsing;
|
|
8
|
+
|
|
9
|
+
// Error messages
|
|
10
|
+
extern NSString* const IMGLYErrorExportFailedMessage;
|
|
11
|
+
extern NSString* const IMGLYErrorParsingMessage;
|
|
12
|
+
|
|
13
|
+
@end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#import "IMGLYConstants.h"
|
|
2
|
+
|
|
3
|
+
@implementation IMGLYConstants
|
|
4
|
+
|
|
5
|
+
// Error codes
|
|
6
|
+
NSString* const IMGLYErrorExportFailed = @"E_EXPORT_FAILED";
|
|
7
|
+
NSString* const IMGLYErrorParsing = @"E_PARSING";
|
|
8
|
+
|
|
9
|
+
// Error messages
|
|
10
|
+
NSString* const IMGLYErrorExportFailedMessage = @"The export of the asset failed due to: ";
|
|
11
|
+
NSString* const IMGLYErrorParsingMessage = @"Unable to parse the argument(s): ";
|
|
12
|
+
|
|
13
|
+
@end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import IMGLYEditor
|
|
3
|
+
import IMGLYEngine
|
|
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
|
+
let pages = try engine.scene.getPages()
|
|
37
|
+
let images = try engine.block.find(byType: .graphic)
|
|
38
|
+
guard let image = images.first, let page = pages.first else { throw "Scene could not be loaded." }
|
|
39
|
+
try engine.block.setFill(page, fill: engine.block.getFill(image))
|
|
40
|
+
try engine.block.destroy(image)
|
|
41
|
+
case .video:
|
|
42
|
+
try await engine.scene.create(fromVideo: url)
|
|
43
|
+
default:
|
|
44
|
+
try await engine.scene.load(from: url)
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
try await OnCreate.default(engine)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
var assetUrl: URL?
|
|
51
|
+
if let assetBaseUri = settings?.assetBaseUri {
|
|
52
|
+
if let temp = URL(string: assetBaseUri), temp.scheme != nil {
|
|
53
|
+
assetUrl = temp
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
try await Self.loadDefaultAssets(baseURL: assetUrl)(engine)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Loads the default assets for the asset library.
|
|
61
|
+
/// - Parameter baseURL: The base `URL` from your CDN where the assets are hosted.
|
|
62
|
+
/// - Returns: A `Callback`.
|
|
63
|
+
static func loadDefaultAssets(baseURL: URL?) -> Callback {
|
|
64
|
+
{ engine in
|
|
65
|
+
let baseURI = baseURL ?? Engine.assetBaseURL
|
|
66
|
+
try await engine.addDefaultAssetSources(baseURL: baseURI)
|
|
67
|
+
try await engine.addDemoAssetSources(
|
|
68
|
+
exclude: [.image, .video, .audio],
|
|
69
|
+
sceneMode: engine.scene.getMode(),
|
|
70
|
+
withUploadAssetSources: true
|
|
71
|
+
)
|
|
72
|
+
try await engine.asset.addSource(TextAssetSource(engine: engine))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import IMGLYEditor
|
|
3
|
+
import IMGLYEngine
|
|
4
|
+
|
|
5
|
+
/// An extension that provides convenience methods for `OnExport` calls.
|
|
6
|
+
public extension OnExport {
|
|
7
|
+
/// Exports the asset to an `EditorResult`.
|
|
8
|
+
/// - Parameters:
|
|
9
|
+
/// - engine: The `Engine` that should be used.
|
|
10
|
+
/// - mimeType: The `MIMEType` of the exported artifact.
|
|
11
|
+
/// - thumbnailHeight: The height of the generated thumbnail.
|
|
12
|
+
/// - Returns: A populates `EditorResult`.
|
|
13
|
+
@MainActor
|
|
14
|
+
static func export(_ engine: Engine, _ mimeType: MIMEType,
|
|
15
|
+
_ thumbnailHeight: CGFloat = 100) async throws -> EditorResult {
|
|
16
|
+
let helper = ExportHelper()
|
|
17
|
+
let (data, type) = try await OnExport.export(engine, mimeType: mimeType)
|
|
18
|
+
let file = try helper.saveFile(data, type)
|
|
19
|
+
let scene = try await engine.scene.saveToString()
|
|
20
|
+
let localScene = try helper.saveScene(scene)
|
|
21
|
+
|
|
22
|
+
var thumbnail: URL?
|
|
23
|
+
if let firstPage = try engine.scene.getPages().first {
|
|
24
|
+
thumbnail = try await helper.saveThumbnail(firstPage, engine, thumbnailHeight)
|
|
25
|
+
}
|
|
26
|
+
return EditorResult(
|
|
27
|
+
scene: localScene?.absoluteString,
|
|
28
|
+
artifact: file.absoluteString,
|
|
29
|
+
thumbnail: thumbnail?.absoluteString
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Exports a video to an `EditorResult`.
|
|
34
|
+
/// - Parameters:
|
|
35
|
+
/// - engine: The `Engine` that should be used.
|
|
36
|
+
/// - eventHandler: The `EditorEventHandler` used for event handling.
|
|
37
|
+
/// - mimeType: The `MIMEType` of the exported artifact.
|
|
38
|
+
/// - thumbnailHeight: The height of the generated thumbnail.
|
|
39
|
+
/// - Returns: A populates `EditorResult`.
|
|
40
|
+
@MainActor
|
|
41
|
+
static func exportVideo(
|
|
42
|
+
_ engine: Engine,
|
|
43
|
+
_ eventHandler: EditorEventHandler,
|
|
44
|
+
_ mimeType: MIMEType,
|
|
45
|
+
_ thumbnailHeight: CGFloat = 100
|
|
46
|
+
) async throws -> EditorResult {
|
|
47
|
+
let helper = ExportHelper()
|
|
48
|
+
let (data, type) = try await OnExport.exportVideo(engine, eventHandler, mimeType: mimeType)
|
|
49
|
+
let file = try helper.saveFile(data, type)
|
|
50
|
+
let scene = try await engine.scene.saveToString()
|
|
51
|
+
let localScene = try helper.saveScene(scene)
|
|
52
|
+
|
|
53
|
+
var thumbnail: URL?
|
|
54
|
+
if let firstPage = try engine.scene.getPages().first {
|
|
55
|
+
thumbnail = try await helper.saveThumbnail(firstPage, engine, thumbnailHeight)
|
|
56
|
+
}
|
|
57
|
+
eventHandler.send(.exportCompleted {})
|
|
58
|
+
return EditorResult(
|
|
59
|
+
scene: localScene?.absoluteString,
|
|
60
|
+
artifact: file.absoluteString,
|
|
61
|
+
thumbnail: thumbnail?.absoluteString
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import IMGLYEngine
|
|
2
|
+
import UIKit
|
|
3
|
+
import UniformTypeIdentifiers
|
|
4
|
+
|
|
5
|
+
/// A helper class for exporting.
|
|
6
|
+
@MainActor
|
|
7
|
+
class ExportHelper {
|
|
8
|
+
/// Saves given `data` to a file.
|
|
9
|
+
/// - Parameters:
|
|
10
|
+
/// - data: The data to save.
|
|
11
|
+
/// - contentType: The `UTType` of the file.
|
|
12
|
+
/// - Returns: The file `URL`.
|
|
13
|
+
func saveFile(_ data: Data, _ contentType: UTType) throws -> URL {
|
|
14
|
+
let fileName = "cesdk_export_artifact_" + UUID().uuidString
|
|
15
|
+
let url = FileManager.default.temporaryDirectory.appendingPathComponent(fileName, conformingTo: contentType)
|
|
16
|
+
try data.write(to: url, options: [.atomic])
|
|
17
|
+
return url
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Saves given scene string to a file.
|
|
21
|
+
/// - Parameter scene: The scene as a `String`.
|
|
22
|
+
/// - Returns: The file `URL`.
|
|
23
|
+
func saveScene(_ scene: String) throws -> URL? {
|
|
24
|
+
let fileName = "cesdk_export_scene_" + UUID().uuidString
|
|
25
|
+
let url = FileManager.default.temporaryDirectory.appendingPathComponent(fileName, conformingTo: .plainText)
|
|
26
|
+
if let data = scene.data(using: .utf8) {
|
|
27
|
+
try data.write(to: url, options: [.atomic])
|
|
28
|
+
return url
|
|
29
|
+
}
|
|
30
|
+
return nil
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// MARK: - Thumbnails
|
|
34
|
+
|
|
35
|
+
/// Generates and saves a thumbnail for a given block.
|
|
36
|
+
/// - Parameters:
|
|
37
|
+
/// - id: The `DesignBlockID` of the block.
|
|
38
|
+
/// - engine: The `Engine` to use.
|
|
39
|
+
/// - height: The height of the thumbnail.
|
|
40
|
+
/// - Returns: The `URL` to the thumbnail.
|
|
41
|
+
func saveThumbnail(_ id: DesignBlockID, _ engine: Engine, _ height: CGFloat) async throws -> URL {
|
|
42
|
+
let thumbnail = try await generateThumbnail(id, engine, height)
|
|
43
|
+
let fileName = "cesdk_export_thumbnail_" + UUID().uuidString
|
|
44
|
+
let url = FileManager.default.temporaryDirectory.appendingPathComponent(fileName, conformingTo: .png)
|
|
45
|
+
try thumbnail?.pngData()?.write(to: url, options: [.atomic])
|
|
46
|
+
return url
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Generates a thumbnail for a given block.
|
|
50
|
+
/// - Parameters:
|
|
51
|
+
/// - id: The `DesignBlockID` of the block.
|
|
52
|
+
/// - engine: The `Engine` to use.
|
|
53
|
+
/// - height: The height of the thumbnail.
|
|
54
|
+
/// - Returns: The thumbnail as an `UIImage`.
|
|
55
|
+
private func generateThumbnail(_ id: DesignBlockID, _ engine: Engine, _ height: CGFloat) async throws -> UIImage? {
|
|
56
|
+
let stream = engine.block.generateVideoThumbnailSequence(
|
|
57
|
+
id,
|
|
58
|
+
thumbnailHeight: Int(height * UIScreen.main.scale),
|
|
59
|
+
timeRange: 0 ... 0.1,
|
|
60
|
+
numberOfFrames: 1
|
|
61
|
+
)
|
|
62
|
+
for try await thumbnail in stream {
|
|
63
|
+
return .init(cgImage: thumbnail.image, scale: UIScreen.main.scale, orientation: .up)
|
|
64
|
+
}
|
|
65
|
+
return nil
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
2
|
+
#import <IMGLYEditorModuleSpec/IMGLYEditorModuleSpec.h>
|
|
3
|
+
|
|
4
|
+
@interface IMGLYEditorModule : NSObject <NativeIMGLYEditorSpec>
|
|
5
|
+
#else
|
|
6
|
+
#import <React/RCTBridgeModule.h>
|
|
7
|
+
|
|
8
|
+
@interface IMGLYEditorModule : NSObject <RCTBridgeModule>
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
@end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#import "IMGLYEditorModule.h"
|
|
2
|
+
#import "IMGLYEditorModule/IMGLYEditorModule-Swift.h"
|
|
3
|
+
#import "IMGLYConstants.h"
|
|
4
|
+
|
|
5
|
+
@implementation IMGLYEditorModule
|
|
6
|
+
|
|
7
|
+
RCT_EXPORT_MODULE(IMGLYEditor)
|
|
8
|
+
|
|
9
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
10
|
+
- (void)openEditor:(JS::NativeIMGLYEditor::EditorSettings&)settings
|
|
11
|
+
source:(JS::NativeIMGLYEditor::Source&)source
|
|
12
|
+
preset:(NSString*)preset
|
|
13
|
+
metadata:(NSDictionary*)metadata
|
|
14
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
15
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
16
|
+
NSMutableDictionary* mergedSettings = [@{
|
|
17
|
+
@"license" : settings.license(),
|
|
18
|
+
@"sceneBaseUri" : settings.sceneBaseUri(),
|
|
19
|
+
} mutableCopy];
|
|
20
|
+
if (settings.assetBaseUri() != nil) {
|
|
21
|
+
mergedSettings[@"assetBaseUri"] = settings.assetBaseUri();
|
|
22
|
+
}
|
|
23
|
+
if (settings.userId() != nil) {
|
|
24
|
+
mergedSettings[@"userId"] = settings.userId();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
JS::NativeIMGLYEditor::Source* sourcePtr = &source;
|
|
28
|
+
if (sourcePtr != nullptr) {
|
|
29
|
+
mergedSettings[@"source"] = @{@"source" : source.source(), @"type" : source.type()};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
[self open:mergedSettings preset:preset metadata:metadata resolve:resolve reject:reject];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
36
|
+
(const facebook::react::ObjCTurboModule::InitParams&)params {
|
|
37
|
+
return std::make_shared<facebook::react::NativeIMGLYEditorSpecJSI>(params);
|
|
38
|
+
}
|
|
39
|
+
#else
|
|
40
|
+
RCT_EXPORT_METHOD(openEditor
|
|
41
|
+
: (nonnull NSDictionary*)settings source
|
|
42
|
+
: (nullable NSDictionary*)source preset
|
|
43
|
+
: (NSString*)preset metadata
|
|
44
|
+
: (NSDictionary*)metadata resolve
|
|
45
|
+
: (RCTPromiseResolveBlock)resolve reject
|
|
46
|
+
: (RCTPromiseRejectBlock)reject) {
|
|
47
|
+
NSMutableDictionary* mergedSettings = [settings mutableCopy];
|
|
48
|
+
mergedSettings[@"source"] = source;
|
|
49
|
+
[self open:mergedSettings preset:preset metadata:metadata resolve:resolve reject:reject];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#endif
|
|
53
|
+
|
|
54
|
+
- (void)open:(nonnull NSDictionary*)settings
|
|
55
|
+
preset:(NSString*)preset
|
|
56
|
+
metadata:(NSDictionary*)metadata
|
|
57
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
58
|
+
reject:(RCTPromiseRejectBlock)reject {
|
|
59
|
+
EditorSettings* convertedSettings = [EditorSettings fromDictionary:settings];
|
|
60
|
+
if (convertedSettings == nil) {
|
|
61
|
+
reject(IMGLYErrorParsing, IMGLYErrorParsingMessage, nil);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
EditorPreset convertedPreset = [EditorPresetParser fromString:preset];
|
|
65
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
66
|
+
[[IMGLYEditorModuleSwiftAdapter shared] openEditor:convertedPreset
|
|
67
|
+
settings:convertedSettings
|
|
68
|
+
metadata:metadata
|
|
69
|
+
completion:^(EditorResult* _Nullable result, NSError* _Nullable error) {
|
|
70
|
+
if (result != nil) {
|
|
71
|
+
resolve(@ {
|
|
72
|
+
@"artifact" : result.artifact,
|
|
73
|
+
@"scene" : result.scene,
|
|
74
|
+
@"thumbnail" : result.thumbnail,
|
|
75
|
+
@"metadata" : result.metadata
|
|
76
|
+
});
|
|
77
|
+
} else if (error != nil) {
|
|
78
|
+
reject(IMGLYErrorExportFailed, IMGLYErrorExportFailedMessage, error);
|
|
79
|
+
} else {
|
|
80
|
+
resolve([NSNull null]);
|
|
81
|
+
}
|
|
82
|
+
}];
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@end
|