From 5d7139de89b68bcdfdb90f70a50c934e93dda9e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=96=D0=B8=D1=80=D0=BA=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 10 Jun 2024 22:30:31 +0300 Subject: [PATCH] small refactoring --- compottie-dot/build.gradle.kts | 2 +- .../compottie/DotLottieCompositionSpec.kt | 13 ++ .../compottie/DotLottie.jvmNative.kt | 9 +- .../compottie/Conversion.wasmJs.kt | 94 +++++++++++++++ .../compottie/DecompressionStream.wasmJs.kt | 23 ---- .../compottie/ZipFileSystem.wasmJs.kt | 40 ++---- compottie-network/build.gradle.kts | 18 +-- .../compottie/DefaultHttpClient.kt | 1 + .../compottie/DiskCacheStrategy.kt | 8 ++ .../compottie/NetworkAssetsManager.kt | 44 ++++--- ...mpositionSpec.kt => UrlCompositionSpec.kt} | 13 +- compottie/build.gradle.kts | 17 ++- .../compottie/ExperimentalCompottieApi.kt | 5 + .../alexzhirkevich/compottie/IODispatcher.kt | 6 + .../compottie/LottieAnimationFormat.kt | 2 +- .../compottie/LottieComposition.kt | 47 +++++--- .../compottie/LottieCompositionSpec.kt | 7 +- .../alexzhirkevich/compottie/LottiePainter.kt | 19 +-- .../compottie/assets/LottieAssetManager.kt | 10 +- .../compottie/internal/layers/BaseLayer.kt | 114 +++++++++--------- .../compottie/IoDispatcher.jvmNative.kt | 8 ++ .../compottie/IoDispatcher.web.kt | 7 ++ example/shared/build.gradle.kts | 18 +-- example/shared/src/commonMain/kotlin/App.kt | 105 +++++++++------- example/webApp/build.gradle.kts | 12 ++ gradle/libs.versions.toml | 2 + kotlin-js-store/yarn.lock | 42 +++++++ 27 files changed, 443 insertions(+), 243 deletions(-) create mode 100644 compottie-dot/src/wasmJsMain/kotlin/io/github/alexzhirkevich/compottie/Conversion.wasmJs.kt rename compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/{NetworkCompositionSpec.kt => UrlCompositionSpec.kt} (91%) create mode 100644 compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/ExperimentalCompottieApi.kt create mode 100644 compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/IODispatcher.kt create mode 100644 compottie/src/jvmNativeMain/kotlin/io/github/alexzhirkevich/compottie/IoDispatcher.jvmNative.kt create mode 100644 compottie/src/webMain/kotlin/io/github/alexzhirkevich/compottie/IoDispatcher.web.kt diff --git a/compottie-dot/build.gradle.kts b/compottie-dot/build.gradle.kts index 8215891d..ab8c63fe 100644 --- a/compottie-dot/build.gradle.kts +++ b/compottie-dot/build.gradle.kts @@ -75,8 +75,8 @@ kotlin { implementation(libs.serialization) implementation(libs.okio) implementation(libs.okio.fakefilesystem) + implementation(libs.coroutines.core) implementation(project(":compottie")) - } val desktopMain by getting diff --git a/compottie-dot/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/DotLottieCompositionSpec.kt b/compottie-dot/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/DotLottieCompositionSpec.kt index 64d20a01..77d1bf83 100644 --- a/compottie-dot/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/DotLottieCompositionSpec.kt +++ b/compottie-dot/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/DotLottieCompositionSpec.kt @@ -7,6 +7,19 @@ import kotlinx.serialization.json.Json import okio.Path.Companion.toPath import okio.fakefilesystem.FakeFileSystem + + +private var _useStableWasmMemoryManagement : Boolean = false + +/** + * Stable memory management will be much slower but more compatible with Kotlin compiler + * versions. + * + * It is disabled by default. Turn this on if you have problems with dotLottie decompression on wasm + * */ +@ExperimentalCompottieApi +var L.useStableWasmMemoryManagement by ::_useStableWasmMemoryManagement + /** * [LottieComposition] from a dotLottie zip archive. * diff --git a/compottie-dot/src/jvmNativeMain/kotlin/io/github/alexzhirkevich/compottie/DotLottie.jvmNative.kt b/compottie-dot/src/jvmNativeMain/kotlin/io/github/alexzhirkevich/compottie/DotLottie.jvmNative.kt index fcda118d..c834eb9e 100644 --- a/compottie-dot/src/jvmNativeMain/kotlin/io/github/alexzhirkevich/compottie/DotLottie.jvmNative.kt +++ b/compottie-dot/src/jvmNativeMain/kotlin/io/github/alexzhirkevich/compottie/DotLottie.jvmNative.kt @@ -1,5 +1,8 @@ package io.github.alexzhirkevich.compottie +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.withContext import okio.FileSystem import okio.Path import okio.openZip @@ -13,8 +16,10 @@ internal actual class ZipFileSystem actual constructor( private val zipFileSystem = parent.openZip(path) actual suspend fun read(path: Path): ByteArray { - return zipFileSystem.read(path) { - readByteArray() + return withContext(Dispatchers.IO) { + zipFileSystem.read(path) { + readByteArray() + } } } } diff --git a/compottie-dot/src/wasmJsMain/kotlin/io/github/alexzhirkevich/compottie/Conversion.wasmJs.kt b/compottie-dot/src/wasmJsMain/kotlin/io/github/alexzhirkevich/compottie/Conversion.wasmJs.kt new file mode 100644 index 00000000..c2baf1e8 --- /dev/null +++ b/compottie-dot/src/wasmJsMain/kotlin/io/github/alexzhirkevich/compottie/Conversion.wasmJs.kt @@ -0,0 +1,94 @@ +package io.github.alexzhirkevich.compottie + +import org.khronos.webgl.Int8Array +import org.khronos.webgl.get +import org.khronos.webgl.set +import kotlin.wasm.unsafe.UnsafeWasmMemoryApi +import kotlin.wasm.unsafe.withScopedMemoryAllocator + +internal fun createSourceStable(data : Int8Array) : JsAny = js(""" +({ + start(c){ + console.log(data) + c.enqueue(data) + c.close() + } +}) +""") + +internal fun createSourceUnstable(data : JsReference) : JsAny = js(""" +({ + start(c){ + const size = wasmExports.kotlinArraySize(data); + const result = new Int8Array(size); + for (let i = 0; i < size; i++) { + result[i] = wasmExports.kotlinArrayGet(data, i); + } + c.enqueue(result) + c.close() + } +}) +""") + +@OptIn(ExperimentalCompottieApi::class) +internal fun Int8Array.toByteArray(): Array { + if (L.useStableWasmMemoryManagement) { + return Array(byteLength) { + this[it] + } + } + return jsInt8ArrayToKotlinByteArray(this) +} + +@OptIn(ExperimentalCompottieApi::class) +internal fun ByteArray.toInt8Array() : Int8Array { + if (L.useStableWasmMemoryManagement) { + val result = Int8Array(size) + for (i in indices) { + result[i] = this[i] + } + return result + } + return byteArrayToInt8ArrayImpl(toJsReference()) +} + +@OptIn(ExperimentalJsExport::class) +@JsExport +private fun kotlinArrayGet(a: JsReference, i: Int): Byte = a.get()[i] + +@OptIn(ExperimentalJsExport::class) +@JsExport +private fun kotlinArraySize(a: JsReference): Int = a.get().size + + +private fun byteArrayToInt8ArrayImpl(a: JsReference): Int8Array = js("""{ + const size = wasmExports.kotlinArraySize(a); + const result = new Int8Array(size); + for (let i = 0; i < size; i++) { + result[i] = wasmExports.kotlinArrayGet(a, i); + } + return result; +}""") + + + +@JsFun( + """ (src, size, dstAddr) => { + const mem8 = new Int8Array(wasmExports.memory.buffer, dstAddr, size); + mem8.set(src); + } +""" +) +private external fun jsExportInt8ArrayToWasm(src: Int8Array, size: Int, dstAddr: Int) + +private fun jsInt8ArrayToKotlinByteArray(x: Int8Array): Array { + val size = x.length + + @OptIn(UnsafeWasmMemoryApi::class) + return withScopedMemoryAllocator { allocator -> + val memBuffer = allocator.allocate(size) + val dstAddress = memBuffer.address.toInt() + jsExportInt8ArrayToWasm(x, size, dstAddress) + Array(size) { i -> (memBuffer + i).loadByte() } + } +} diff --git a/compottie-dot/src/wasmJsMain/kotlin/io/github/alexzhirkevich/compottie/DecompressionStream.wasmJs.kt b/compottie-dot/src/wasmJsMain/kotlin/io/github/alexzhirkevich/compottie/DecompressionStream.wasmJs.kt index d53b40de..9fb25032 100644 --- a/compottie-dot/src/wasmJsMain/kotlin/io/github/alexzhirkevich/compottie/DecompressionStream.wasmJs.kt +++ b/compottie-dot/src/wasmJsMain/kotlin/io/github/alexzhirkevich/compottie/DecompressionStream.wasmJs.kt @@ -2,7 +2,6 @@ package io.github.alexzhirkevich.compottie import org.khronos.webgl.ArrayBufferView import org.khronos.webgl.Int8Array -import org.khronos.webgl.set import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine @@ -10,28 +9,6 @@ import kotlin.js.Promise external class DecompressionStream(alg : String) -//external interface UnderlyingSource : JsAny { -// fun start(controller: SourceController) -//} -// -//interface SourceController { -// fun enqueue(data : ByteArray) -// -// fun close() -//} - -fun createSource(data : Int8Array) : JsAny = js(""" -({ - start(c){ - console.log(data) - c.enqueue(data) - c.close() - } -}) -""") - - - external class ReadableStream( source: JsAny ) { diff --git a/compottie-dot/src/wasmJsMain/kotlin/io/github/alexzhirkevich/compottie/ZipFileSystem.wasmJs.kt b/compottie-dot/src/wasmJsMain/kotlin/io/github/alexzhirkevich/compottie/ZipFileSystem.wasmJs.kt index e19d759f..58e5ad64 100644 --- a/compottie-dot/src/wasmJsMain/kotlin/io/github/alexzhirkevich/compottie/ZipFileSystem.wasmJs.kt +++ b/compottie-dot/src/wasmJsMain/kotlin/io/github/alexzhirkevich/compottie/ZipFileSystem.wasmJs.kt @@ -2,15 +2,18 @@ package io.github.alexzhirkevich.compottie import org.khronos.webgl.Int8Array -import org.khronos.webgl.get -import org.khronos.webgl.set -import kotlin.time.measureTime +@OptIn(ExperimentalCompottieApi::class) internal actual suspend fun decompress(array: ByteArray, inflatedSize : Int) : ByteArray { val ds = DecompressionStream("deflate-raw") - val stream = ReadableStream(createSource(copyToInt8Array(array))) + val source = if (L.useStableWasmMemoryManagement) { + createSourceStable(array.toInt8Array()) + } else { + createSourceUnstable(array.toJsReference()) + } + val stream = ReadableStream(source) val reader = stream .pipeThrough(ds) @@ -18,42 +21,15 @@ internal actual suspend fun decompress(array: ByteArray, inflatedSize : Int) : B val inflatedResult = ArrayList(inflatedSize) - while (true) { val result = reader.read().await() if (result.done) { break } - inflatedResult.addAll( - copyToByteArray(Int8Array(result.value.buffer)) - ) + inflatedResult += Int8Array(result.value.buffer).toByteArray() } return inflatedResult.toByteArray() } -internal fun copyToByteArray(array: Int8Array): List { - var res : List - measureTime { - res = List(array.byteLength) { - array[it] - } - }.also { - println("Copy to js array. Size: ${array.byteLength}. Time: $it") - } - - return res -} - -private fun copyToInt8Array(array: ByteArray): Int8Array { - val result = Int8Array(array.size) - measureTime { - for (i in array.indices) { - result[i] = array[i] - } - }.also { - println("Copy from js array. Size: ${array.size}. Time:$it") - } - return result -} \ No newline at end of file diff --git a/compottie-network/build.gradle.kts b/compottie-network/build.gradle.kts index 9dd180e6..ec6e333f 100644 --- a/compottie-network/build.gradle.kts +++ b/compottie-network/build.gradle.kts @@ -166,12 +166,14 @@ if (System.getenv("GPG_KEY") != null) { } -configurations.all { - resolutionStrategy.eachDependency { - if (requested.group.startsWith("io.ktor") && - requested.name.startsWith("ktor-client-") - ) { - useVersion("3.0.0-wasm2") +configurations + .filter { it.name.contains("wasmJs") } + .onEach { + it.resolutionStrategy.eachDependency { + if (requested.group.startsWith("io.ktor") && + requested.name.startsWith("ktor-client-") + ) { + useVersion("3.0.0-wasm2") + } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/DefaultHttpClient.kt b/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/DefaultHttpClient.kt index ef481645..2744ec41 100644 --- a/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/DefaultHttpClient.kt +++ b/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/DefaultHttpClient.kt @@ -1,5 +1,6 @@ package io.github.alexzhirkevich.compottie +import androidx.compose.runtime.Stable import io.ktor.client.HttpClient import io.ktor.client.request.get import io.ktor.client.statement.HttpResponse diff --git a/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/DiskCacheStrategy.kt b/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/DiskCacheStrategy.kt index 56991fda..2d9c3a33 100644 --- a/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/DiskCacheStrategy.kt +++ b/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/DiskCacheStrategy.kt @@ -28,6 +28,14 @@ class DiskCacheStrategy : LottieCacheStrategy { override suspend fun load(url: String): ByteArray? { return null } + + override fun equals(other: Any?): Boolean { + return true + } + + override fun hashCode(): Int { + return 1 + } } //internal expect fun FileSystem.Companion.System : FileSystem \ No newline at end of file diff --git a/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/NetworkAssetsManager.kt b/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/NetworkAssetsManager.kt index 7aed1d12..97a3e33f 100644 --- a/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/NetworkAssetsManager.kt +++ b/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/NetworkAssetsManager.kt @@ -1,6 +1,7 @@ package io.github.alexzhirkevich.compottie import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState @@ -12,25 +13,36 @@ import io.ktor.http.URLParserException import io.ktor.http.Url import io.ktor.util.toByteArray -@Composable -fun rememberNetworkAssetsManager( +//@Composable +//@Stable +//fun rememberNetworkAssetsManager( +// client: HttpClient = DefaultHttpClient, +// cacheStrategy: LottieCacheStrategy = rememberDiskCacheStrategy(), +// request : NetworkRequest = GetRequest, +//) : NetworkAssetsManager { +// val updatedRequest by rememberUpdatedState(request) +// +// return remember(client, cacheStrategy) { +// NetworkAssetsManager(client, cacheStrategy) { c, u -> +// updatedRequest.invoke(c, u) +// } +// } +//} + +fun NetworkAssetsManager( client: HttpClient = DefaultHttpClient, - cacheStrategy: LottieCacheStrategy = rememberDiskCacheStrategy(), + cacheStrategy: LottieCacheStrategy = DiskCacheStrategy(), request : NetworkRequest = GetRequest, -) { - val updatedRequest by rememberUpdatedState(request) - - return remember(client, cacheStrategy) { - NetworkAssetsManager(client, cacheStrategy) { c, u -> - updatedRequest.invoke(c, u) - } - } -} +) : LottieAssetsManager = NetworkAssetsManagerImpl( + client = client, + cacheStrategy = cacheStrategy, + request = request +) -class NetworkAssetsManager( - private val client: HttpClient, - private val cacheStrategy: LottieCacheStrategy, - private val request : NetworkRequest, +private class NetworkAssetsManagerImpl( + private val client: HttpClient = DefaultHttpClient, + private val cacheStrategy: LottieCacheStrategy = DiskCacheStrategy(), + private val request : NetworkRequest = GetRequest, ) : LottieAssetsManager { override suspend fun fetch(asset: LottieAsset): ByteArray? { diff --git a/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/NetworkCompositionSpec.kt b/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/UrlCompositionSpec.kt similarity index 91% rename from compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/NetworkCompositionSpec.kt rename to compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/UrlCompositionSpec.kt index 42e88c4a..06ab55df 100644 --- a/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/NetworkCompositionSpec.kt +++ b/compottie-network/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/UrlCompositionSpec.kt @@ -11,7 +11,6 @@ import io.github.alexzhirkevich.compottie.LottieCompositionSpec import io.github.alexzhirkevich.compottie.NetworkAssetsManager import io.github.alexzhirkevich.compottie.NetworkRequest import io.github.alexzhirkevich.compottie.assets.LottieAssetsManager -import io.github.alexzhirkevich.compottie.rememberNetworkAssetsManager import io.ktor.client.HttpClient import io.ktor.client.plugins.ClientRequestException import io.ktor.client.statement.bodyAsChannel @@ -25,19 +24,17 @@ import io.ktor.util.toByteArray * [LottieComposition] from web [url] * * @param client Ktor http client to use - * @param assetsManager lottie assets manager. By default no-op manager is used. - * Use [NetworkAssetsManager] if assets use web URLs too - * - * @see rememberNetworkAssetsManager + * @param assetsManager manager for assets that not embedded to the animation JSON or dotLottie archive. + * [NetworkAssetsManager] is used by default * */ @Stable fun LottieCompositionSpec.Companion.Url( url : String, - format: LottieAnimationFormat = LottieAnimationFormat.Unknown, + format: LottieAnimationFormat = LottieAnimationFormat.Undefined, client: HttpClient = DefaultHttpClient, - assetsManager: LottieAssetsManager = LottieAssetsManager, - cacheStrategy: LottieCacheStrategy = DiskCacheStrategy(), request : NetworkRequest = GetRequest, + cacheStrategy: LottieCacheStrategy = DiskCacheStrategy(), + assetsManager: LottieAssetsManager = NetworkAssetsManager(client,cacheStrategy, request), ) : LottieCompositionSpec = NetworkCompositionSpec( url = url, format = format, diff --git a/compottie/build.gradle.kts b/compottie/build.gradle.kts index 2b5d9848..89b11764 100644 --- a/compottie/build.gradle.kts +++ b/compottie/build.gradle.kts @@ -1,5 +1,6 @@ @file:Suppress("DSL_SCOPE_VIOLATION") +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import java.util.Base64 plugins { @@ -20,7 +21,21 @@ val _jvmTarget = findProperty("jvmTarget") as String kotlin { - applyDefaultHierarchyTemplate() + applyDefaultHierarchyTemplate{ + @OptIn(ExperimentalKotlinGradlePluginApi::class) + common { + group("jvmNative") { + withAndroidTarget() + withJvm() + withIos() + withMacos() + } + group("web"){ + withJs() + withWasmJs() + } + } + } androidTarget{ publishLibraryVariants("release") diff --git a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/ExperimentalCompottieApi.kt b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/ExperimentalCompottieApi.kt new file mode 100644 index 00000000..2046d3e8 --- /dev/null +++ b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/ExperimentalCompottieApi.kt @@ -0,0 +1,5 @@ +package io.github.alexzhirkevich.compottie + +@RequiresOptIn("This is experimental/temporary/unstable api", RequiresOptIn.Level.WARNING) +@Retention(value = AnnotationRetention.BINARY) +annotation class ExperimentalCompottieApi() diff --git a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/IODispatcher.kt b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/IODispatcher.kt new file mode 100644 index 00000000..a8614e94 --- /dev/null +++ b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/IODispatcher.kt @@ -0,0 +1,6 @@ +package io.github.alexzhirkevich.compottie + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +internal expect val Dispatchers.IODispatcher : CoroutineDispatcher \ No newline at end of file diff --git a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottieAnimationFormat.kt b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottieAnimationFormat.kt index 72269ef1..48337dc8 100644 --- a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottieAnimationFormat.kt +++ b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottieAnimationFormat.kt @@ -1,5 +1,5 @@ package io.github.alexzhirkevich.compottie enum class LottieAnimationFormat { - Json, DotLottie, Unknown + Json, DotLottie, Undefined } diff --git a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottieComposition.kt b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottieComposition.kt index a38b99d6..dff91d9b 100644 --- a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottieComposition.kt +++ b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottieComposition.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.text.font.FontFamily @@ -92,13 +93,14 @@ class LottieComposition internal constructor( } /** - * Load and prepare [LottieComposition] for displaying + * Load and prepare [LottieComposition]. + * + * [spec] should be remembered * */ @Composable @Stable fun rememberLottieComposition( spec : LottieCompositionSpec, - assetsManager: LottieAssetsManager = LottieAssetsManager, ) : LottieCompositionResult { val result = remember(spec) { @@ -106,9 +108,9 @@ fun rememberLottieComposition( } LaunchedEffect(result) { - withContext(Dispatchers.Default) { + withContext(Dispatchers.IODispatcher) { try { - result.complete(spec.load().apply { prepare(assetsManager) }) + result.complete(spec.load()) } catch (c: CancellationException) { throw c } catch (t: Throwable) { @@ -120,20 +122,35 @@ fun rememberLottieComposition( return result } +/** + * Load and prepare [LottieComposition] for displaying. + * + * Instance produces by [spec] will be remembered until [keys] are changed + * */ +@Composable +@Stable +fun rememberLottieComposition( + vararg keys : Any?, + spec : suspend () -> LottieCompositionSpec, +) : LottieCompositionResult { + val updatedSpec by rememberUpdatedState(spec) - -@Immutable -@JvmInline -internal value class JsonStringCompositionSpec( - private val jsonString: String -) : LottieCompositionSpec { - - override suspend fun load(): LottieComposition { - return LottieComposition.parse(jsonString) + val result = remember(*keys) { + LottieCompositionResultImpl() } - override fun toString(): String { - return "JsonString(jsonString='$jsonString')" + LaunchedEffect(result) { + withContext(Dispatchers.IODispatcher) { + try { + result.complete(updatedSpec().load()) + } catch (c: CancellationException) { + throw c + } catch (t: Throwable) { + result.completeExceptionally(t) + } + } } + + return result } diff --git a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottieCompositionSpec.kt b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottieCompositionSpec.kt index 8e63ba93..cf39c2ae 100644 --- a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottieCompositionSpec.kt +++ b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottieCompositionSpec.kt @@ -32,7 +32,6 @@ interface LottieCompositionSpec { * * Lambda should be stable. Otherwise this spec must be remembered if created in composition * */ - @OptIn(InternalCompottieApi::class) @Stable fun JsonString( assetsManager: LottieAssetsManager = LottieAssetsManager, @@ -64,7 +63,9 @@ private class LazyJsonString( ) : LottieCompositionSpec { override suspend fun load(): LottieComposition { - return LottieComposition.parse(jsonString()) + return LottieComposition.parse(jsonString()).apply { + prepare(assetsManager) + } } override fun toString(): String { @@ -81,8 +82,6 @@ private class LazyJsonString( override fun hashCode(): Int { return 31 * jsonString.hashCode() + assetsManager.hashCode() } - - } diff --git a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottiePainter.kt b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottiePainter.kt index f63a22b1..5bbe4c30 100644 --- a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottiePainter.kt +++ b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/LottiePainter.kt @@ -134,7 +134,7 @@ private class LottiePainter( p.coerceAtLeast(0f) } - val compositionLayer: BaseCompositionLayer = composition.lottieData + val compositionLayer: BaseCompositionLayer = composition.lottieData .layers .takeIf { it.size == 1 // don't create extra composition layer @@ -186,20 +186,9 @@ private class LottiePainter( matrix.reset() - measureTime { - scale(scale.scaleX, scale.scaleY) { - translate(offset.x.toFloat(), offset.y.toFloat()) { - try { - compositionLayer.draw(this, matrix, alpha, AnimationState(frame)) - } catch (t: Throwable) { - println("Lottie crashed in draw :(") - t.printStackTrace() - } - } - } - }.let { - if (it.inWholeMilliseconds > 0) { -// println(it.inWholeMilliseconds) + scale(scale.scaleX, scale.scaleY) { + translate(offset.x.toFloat(), offset.y.toFloat()) { + compositionLayer.draw(this, matrix, alpha, AnimationState(frame)) } } } diff --git a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/assets/LottieAssetManager.kt b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/assets/LottieAssetManager.kt index c8cc2563..0d075d38 100644 --- a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/assets/LottieAssetManager.kt +++ b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/assets/LottieAssetManager.kt @@ -26,17 +26,9 @@ fun interface LottieAssetsManager { * */ suspend fun fetch(asset: LottieAsset): ByteArray? + @Stable companion object : LottieAssetsManager { override suspend fun fetch(asset: LottieAsset): ByteArray? = null } } - - -@Composable -fun rememberLottieAssetsManager( - fetch : suspend (LottieAsset) -> ByteArray? -) : LottieAssetsManager { - return remember { LottieAssetsManager(fetch) } -} - diff --git a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/internal/layers/BaseLayer.kt b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/internal/layers/BaseLayer.kt index be7c3c22..442d7fca 100644 --- a/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/internal/layers/BaseLayer.kt +++ b/compottie/src/commonMain/kotlin/io/github/alexzhirkevich/compottie/internal/layers/BaseLayer.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.isIdentity import androidx.compose.ui.util.fastFirstOrNull import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.util.fastForEachReversed +import io.github.alexzhirkevich.compottie.L import io.github.alexzhirkevich.compottie.internal.AnimationState import io.github.alexzhirkevich.compottie.internal.content.Content import io.github.alexzhirkevich.compottie.internal.content.DrawingContent @@ -109,79 +110,81 @@ internal abstract class BaseLayer() : Layer, DrawingContent { state: AnimationState, ) { - val frame = state.frame - - if (hidden || (inPoint ?: 0f) > frame || (outPoint ?: Float.MAX_VALUE) < frame) - return + try { - buildParentLayerListIfNeeded() - matrix.reset() + val frame = state.frame - matrix.setFrom(parentMatrix) - parentLayers?.fastForEachReversed { - matrix.preConcat(it.transform.matrix(state)) - } + if (hidden || (inPoint ?: 0f) > frame || (outPoint ?: Float.MAX_VALUE) < frame) + return - var alpha = parentAlpha + buildParentLayerListIfNeeded() + matrix.reset() - transform.opacity?.interpolated(state)?.let { - alpha = (alpha * (it / 100f)).coerceIn(0f, 1f) - } + matrix.setFrom(parentMatrix) + parentLayers?.fastForEachReversed { + matrix.preConcat(it.transform.matrix(state)) + } + var alpha = parentAlpha - if (matteLayer == null && !hasMask()) { - matrix.preConcat(transform.matrix(state)) - drawLayer(drawScope, matrix, alpha, state) - return - } + transform.opacity?.interpolated(state)?.let { + alpha = (alpha * (it / 100f)).coerceIn(0f, 1f) + } - getBounds(drawScope, matrix, false, state, rect) - intersectBoundsWithMatte(drawScope, rect, matrix, state) + if (matteLayer == null && !hasMask()) { + matrix.preConcat(transform.matrix(state)) + drawLayer(drawScope, matrix, alpha, state) + return + } - matrix.preConcat(transform.matrix(state)) - intersectBoundsWithMask(rect, matrix, state) + getBounds(drawScope, matrix, false, state, rect) - // Intersect the mask and matte rect with the canvas bounds. - // If the canvas has a transform, then we need to transform its bounds by its matrix - // so that we know the coordinate space that the canvas is showing. - canvasBounds.set(0f, 0f, drawScope.size.width, drawScope.size.height) - drawScope.drawIntoCanvas { canvas -> - canvas.getMatrix(canvasMatrix) + intersectBoundsWithMatte(drawScope, rect, matrix, state) - //TODO: fix mask canvas mapping - if (!canvasMatrix.isIdentity()) { - canvasMatrix.invert() - canvasMatrix.map(canvasBounds) - } + matrix.preConcat(transform.matrix(state)) + intersectBoundsWithMask(rect, matrix, state) + + // Intersect the mask and matte rect with the canvas bounds. + // If the canvas has a transform, then we need to transform its bounds by its matrix + // so that we know the coordinate space that the canvas is showing. + canvasBounds.set(0f, 0f, drawScope.size.width, drawScope.size.height) + drawScope.drawIntoCanvas { canvas -> + canvas.getMatrix(canvasMatrix) + + //TODO: fix mask canvas mapping + if (!canvasMatrix.isIdentity()) { + canvasMatrix.invert() + canvasMatrix.map(canvasBounds) + } - rect.intersectOrReset(canvasBounds) + rect.intersectOrReset(canvasBounds) - // Ensure that what we are drawing is >=1px of width and height. - // On older devices, drawing to an offscreen buffer of <1px would draw back as a black bar. - // https://github.com/airbnb/lottie-android/issues/1625 - if (rect.width >= 1f && rect.height >= 1f) { - contentPaint.alpha = 1f - canvas.saveLayer(rect, contentPaint) + // Ensure that what we are drawing is >=1px of width and height. + // On older devices, drawing to an offscreen buffer of <1px would draw back as a black bar. + // https://github.com/airbnb/lottie-android/issues/1625 + if (rect.width >= 1f && rect.height >= 1f) { + contentPaint.alpha = 1f + canvas.saveLayer(rect, contentPaint) - // Clear the off screen buffer. This is necessary for some phones. - clearCanvas(canvas) - drawLayer(drawScope, matrix, alpha, state) + // Clear the off screen buffer. This is necessary for some phones. + clearCanvas(canvas) + drawLayer(drawScope, matrix, alpha, state) - if (hasMask()) { - applyMasks(canvas, matrix, state) - } + if (hasMask()) { + applyMasks(canvas, matrix, state) + } + + matteLayer?.let { + canvas.saveLayer(rect, mattePaint, SAVE_FLAGS) + clearCanvas(canvas) + it.draw(drawScope, parentMatrix, alpha, state) + canvas.restore() + } - matteLayer?.let { - canvas.saveLayer(rect, mattePaint, SAVE_FLAGS) - clearCanvas(canvas) - it.draw(drawScope, parentMatrix, alpha, state) canvas.restore() } - canvas.restore() - } - // if (outlineMasksAndMattes && outlineMasksAndMattesPaint != null) { // outlineMasksAndMattesPaint.setStyle(android.graphics.Paint.Style.STROKE) // outlineMasksAndMattesPaint.setColor(-0x3d7fd) @@ -191,6 +194,9 @@ internal abstract class BaseLayer() : Layer, DrawingContent { // outlineMasksAndMattesPaint.setColor(0x50EBEBEB) // canvas.drawRect(rect, outlineMasksAndMattesPaint) // } + } + } catch (t: Throwable) { + L.logger.error("Lottie crashed in draw :(", t) } } diff --git a/compottie/src/jvmNativeMain/kotlin/io/github/alexzhirkevich/compottie/IoDispatcher.jvmNative.kt b/compottie/src/jvmNativeMain/kotlin/io/github/alexzhirkevich/compottie/IoDispatcher.jvmNative.kt new file mode 100644 index 00000000..556ff4c8 --- /dev/null +++ b/compottie/src/jvmNativeMain/kotlin/io/github/alexzhirkevich/compottie/IoDispatcher.jvmNative.kt @@ -0,0 +1,8 @@ +package io.github.alexzhirkevich.compottie + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO + +internal actual val Dispatchers.IODispatcher : CoroutineDispatcher + get() = Dispatchers.IO \ No newline at end of file diff --git a/compottie/src/webMain/kotlin/io/github/alexzhirkevich/compottie/IoDispatcher.web.kt b/compottie/src/webMain/kotlin/io/github/alexzhirkevich/compottie/IoDispatcher.web.kt new file mode 100644 index 00000000..9933539a --- /dev/null +++ b/compottie/src/webMain/kotlin/io/github/alexzhirkevich/compottie/IoDispatcher.web.kt @@ -0,0 +1,7 @@ +package io.github.alexzhirkevich.compottie + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers + +internal actual val Dispatchers.IODispatcher : CoroutineDispatcher + get() = Dispatchers.Default \ No newline at end of file diff --git a/example/shared/build.gradle.kts b/example/shared/build.gradle.kts index 15bdca68..d8ce4025 100644 --- a/example/shared/build.gradle.kts +++ b/example/shared/build.gradle.kts @@ -96,12 +96,14 @@ android { } } -configurations.all { - resolutionStrategy.eachDependency { - if (requested.group.startsWith("io.ktor") && - requested.name.startsWith("ktor-client-") - ) { - useVersion("3.0.0-wasm2") +configurations + .filter { it.name.contains("wasmJs") } + .onEach { + it.resolutionStrategy.eachDependency { + if (requested.group.startsWith("io.ktor") && + requested.name.startsWith("ktor-client-") + ) { + useVersion("3.0.0-wasm2") + } } - } -} \ No newline at end of file + } \ No newline at end of file diff --git a/example/shared/src/commonMain/kotlin/App.kt b/example/shared/src/commonMain/kotlin/App.kt index 1e55204c..5ffa1d19 100644 --- a/example/shared/src/commonMain/kotlin/App.kt +++ b/example/shared/src/commonMain/kotlin/App.kt @@ -1,17 +1,16 @@ import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.graphics.vector.group -import androidx.compose.ui.graphics.vector.path -import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import compottie.example.shared.generated.resources.Res @@ -19,7 +18,7 @@ import io.github.alexzhirkevich.compottie.DotLottie import io.github.alexzhirkevich.compottie.LottieComposition import io.github.alexzhirkevich.compottie.LottieCompositionSpec import io.github.alexzhirkevich.compottie.LottieConstants -import io.github.alexzhirkevich.compottie.assets.rememberLottieAssetsManager +import io.github.alexzhirkevich.compottie.assets.LottieAssetsManager import io.github.alexzhirkevich.compottie.rememberLottieComposition import io.github.alexzhirkevich.compottie.rememberLottiePainter import org.jetbrains.compose.resources.ExperimentalResourceApi @@ -51,34 +50,48 @@ private val IMAGE_ASSET_EMBEDDED = "image_asset_embedded.json" private val DOT = "dotlottie/dot.lottie" private val DOT_WITH_IMAGE = "dotlottie/dot_with_image.lottie" + @OptIn(ExperimentalResourceApi::class) @Composable fun App() { - val composition = rememberLottieComposition( -// spec = LottieCompositionSpec.DotLottie { -// Res.readBytes("files/$DOT_WITH_IMAGE") -// }, - spec = LottieCompositionSpec.Resource(ROBOT), -// spec = LottieCompositionSpec.Url("https://assets-v2.lottiefiles.com/a/e3b38514-1150-11ee-9dde-3789514b5871/ZNdbOpdPyr.lottie"), - assetsManager = rememberResourcesAssetsManager() - ) + + val composition = rememberLottieComposition { + + LottieCompositionSpec.DotLottie(ResourcesAssetsManager()) { + Res.readBytes("files/$DOT") + } +// LottieCompositionSpec.Resource(ROBOT) + +// LottieCompositionSpec.Resource(IMAGE_ASSET) + +// LottieCompositionSpec.Url( +// url = "https://assets-v2.lottiefiles.com/a/e3b38514-1150-11ee-9dde-3789514b5871/ZNdbOpdPyr.lottie", +// assetsManager = NetworkAssetsManager() +// ) + } LaunchedEffect(composition) { composition.await() } - Image( - modifier = Modifier - .fillMaxSize() - .opacityGrid(), - painter = rememberLottiePainter( - composition = composition.value, - iterations = LottieConstants.IterateForever - ), - contentDescription = null - ) + Box(contentAlignment = Alignment.Center) { + Image( + modifier = Modifier + .fillMaxSize() + .opacityGrid(), + painter = rememberLottiePainter( + composition = composition.value, + iterations = LottieConstants.IterateForever + ), + contentDescription = null + ) + + if (composition.value == null) { + CircularProgressIndicator() + } + } } /** @@ -89,8 +102,9 @@ fun App() { fun LottieCompositionSpec.Companion.Resource( path : String, dir : String = "files", + assetsManager: LottieAssetsManager = ResourcesAssetsManager(), readBytes: suspend (path: String) -> ByteArray = Res::readBytes -) : LottieCompositionSpec = JsonString { readBytes("$dir/$path").decodeToString() } +) : LottieCompositionSpec = JsonString(assetsManager) { readBytes("$dir/$path").decodeToString() } /** * Compose resources asset manager. @@ -104,31 +118,30 @@ fun LottieCompositionSpec.Companion.Resource( * - path="", name="images/image.png" * */ @OptIn(ExperimentalResourceApi::class) -@Composable -private fun rememberResourcesAssetsManager( +private fun ResourcesAssetsManager( relativeTo : String = "files", readBytes : suspend (path : String) -> ByteArray = Res::readBytes, -) = - rememberLottieAssetsManager { asset -> - try { - val trimPath = asset.path - .removePrefix("/") - .removeSuffix("/") - .takeIf(String::isNotEmpty) - - val trimName = asset.name - .removePrefix("/") - .removeSuffix("/") - .takeIf(String::isNotEmpty) - - val fullPath = listOfNotNull(relativeTo.takeIf(String::isNotEmpty), trimPath, trimName) - .joinToString("/") - - readBytes(fullPath) - } catch (x: MissingResourceException) { - null - } +) = LottieAssetsManager { asset -> + try { + val trimPath = asset.path + .removePrefix("/") + .removeSuffix("/") + .takeIf(String::isNotEmpty) + + val trimName = asset.name + .removePrefix("/") + .removeSuffix("/") + .takeIf(String::isNotEmpty) + + val fullPath = listOfNotNull(relativeTo.takeIf(String::isNotEmpty), trimPath, trimName) + .joinToString("/") + + readBytes(fullPath) + } catch (x: MissingResourceException) { + null } +} + private val DarkOpacity = Color(0xff7f7f7f) private val LightOpacity = Color(0xffb2b2b2) diff --git a/example/webApp/build.gradle.kts b/example/webApp/build.gradle.kts index 50f6f50e..4efa0779 100644 --- a/example/webApp/build.gradle.kts +++ b/example/webApp/build.gradle.kts @@ -24,3 +24,15 @@ kotlin { } } +configurations + .filter { it.name.contains("wasmJs") } + .onEach { + it.resolutionStrategy.eachDependency { + if (requested.group.startsWith("io.ktor") && + requested.name.startsWith("ktor-client-") + ) { + useVersion("3.0.0-wasm2") + } + } + } + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 29ac2ff7..bbfc2352 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,7 @@ okio = "3.9.0" serialization="1.6.2" ktor="2.3.11" atomicfu="0.23.1" +coroutines="1.8.0" [libraries] lottie-android = { module ="com.airbnb.android:lottie-compose", version.ref = "lottie-android" } @@ -20,6 +21,7 @@ ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" } ktor-client-ios = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } gp-atomicfu = { module = "org.jetbrains.kotlinx:atomicfu-gradle-plugin", version.ref = "atomicfu" } +coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } [plugins] compose = { id = "org.jetbrains.compose", version.ref = "compose" } diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 5ca1dbde..d2598c6d 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -436,6 +436,13 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +abort-controller@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -1112,6 +1119,11 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + eventemitter3@^4.0.0: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" @@ -2010,6 +2022,13 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +node-fetch@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + node-forge@^1: version "1.3.1" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" @@ -2770,6 +2789,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + tree-dump@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.1.tgz#b448758da7495580e6b7830d6b7834fca4c45b96" @@ -2868,6 +2892,11 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + webpack-cli@5.1.4: version "5.1.4" resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.4.tgz#c8e046ba7eaae4911d7e71e2b25b776fcc35759b" @@ -3000,6 +3029,14 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which@^1.2.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -3047,6 +3084,11 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +ws@8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + ws@^8.16.0: version "8.17.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea"