From 9e84dc50319645a85105041bd15a03e74aff8180 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sat, 12 Mar 2022 00:55:03 +0600 Subject: [PATCH] add a set of tools for JS and Web Compose --- common/compose/build.gradle | 18 ++++++++ .../compose/DefaultDisposableEffectResult.kt | 16 +++++++ .../micro_utils/common/compose/OpenLink.kt | 10 +++++ common/compose/src/main/AndroidManifest.xml | 1 + .../dev/inmo/micro_utils/common/OpenLink.kt | 8 ++++ .../dev/inmo/micro_utils/common/SelectFile.kt | 29 +++++++++++++ .../micro_utils/common/TriggerDownload.kt | 14 +++++++ coroutines/build.gradle | 5 +++ coroutines/compose/build.gradle | 1 + .../coroutines/compose/FlowToState.kt | 22 ++++++++++ .../SelectFile.kt | 42 +++++++++++++++++++ settings.gradle | 1 + 12 files changed, 167 insertions(+) create mode 100644 common/compose/build.gradle create mode 100644 common/compose/src/commonMain/kotlin/dev/inmo/micro_utils/common/compose/DefaultDisposableEffectResult.kt create mode 100644 common/compose/src/jsMain/kotlin/dev/inmo/micro_utils/common/compose/OpenLink.kt create mode 100644 common/compose/src/main/AndroidManifest.xml create mode 100644 common/src/jsMain/kotlin/dev/inmo/micro_utils/common/OpenLink.kt create mode 100644 common/src/jsMain/kotlin/dev/inmo/micro_utils/common/SelectFile.kt create mode 100644 common/src/jsMain/kotlin/dev/inmo/micro_utils/common/TriggerDownload.kt create mode 100644 coroutines/compose/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/compose/FlowToState.kt create mode 100644 coroutines/src/jsMain/kotlin/dev.inmo.micro_utils.coroutines/SelectFile.kt diff --git a/common/compose/build.gradle b/common/compose/build.gradle new file mode 100644 index 00000000000..0b3570ad169 --- /dev/null +++ b/common/compose/build.gradle @@ -0,0 +1,18 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" + alias(libs.plugins.jb.compose) +} + +apply from: "$mppProjectWithSerializationAndComposePresetPath" + +kotlin { + sourceSets { + commonMain { + dependencies { + api project(":micro_utils.common") + } + } + } +} diff --git a/common/compose/src/commonMain/kotlin/dev/inmo/micro_utils/common/compose/DefaultDisposableEffectResult.kt b/common/compose/src/commonMain/kotlin/dev/inmo/micro_utils/common/compose/DefaultDisposableEffectResult.kt new file mode 100644 index 00000000000..82cf96bff4b --- /dev/null +++ b/common/compose/src/commonMain/kotlin/dev/inmo/micro_utils/common/compose/DefaultDisposableEffectResult.kt @@ -0,0 +1,16 @@ +package dev.inmo.micro_utils.common.compose + +import androidx.compose.runtime.DisposableEffectResult + +class DefaultDisposableEffectResult( + private val onDispose: () -> Unit +) : DisposableEffectResult { + override fun dispose() { + onDispose() + } + + companion object { + val DoNothing = DefaultDisposableEffectResult {} + } +} + diff --git a/common/compose/src/jsMain/kotlin/dev/inmo/micro_utils/common/compose/OpenLink.kt b/common/compose/src/jsMain/kotlin/dev/inmo/micro_utils/common/compose/OpenLink.kt new file mode 100644 index 00000000000..41ed545c41d --- /dev/null +++ b/common/compose/src/jsMain/kotlin/dev/inmo/micro_utils/common/compose/OpenLink.kt @@ -0,0 +1,10 @@ +package dev.inmo.micro_utils.common.compose + +import org.jetbrains.compose.web.attributes.ATarget + +fun openLink(link: String, mode: ATarget = ATarget.Blank, features: String = "") = dev.inmo.micro_utils.common.openLink( + link, + mode.targetStr, + features +) + diff --git a/common/compose/src/main/AndroidManifest.xml b/common/compose/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..65adc963cce --- /dev/null +++ b/common/compose/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/OpenLink.kt b/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/OpenLink.kt new file mode 100644 index 00000000000..24315e95559 --- /dev/null +++ b/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/OpenLink.kt @@ -0,0 +1,8 @@ +package dev.inmo.micro_utils.common + +import kotlinx.browser.window + +fun openLink(link: String, target: String = "_blank", features: String = "") { + window.open(link, target, features) ?.focus() +} + diff --git a/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/SelectFile.kt b/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/SelectFile.kt new file mode 100644 index 00000000000..89c7bc105dd --- /dev/null +++ b/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/SelectFile.kt @@ -0,0 +1,29 @@ +package dev.inmo.micro_utils.common + +import kotlinx.browser.document +import kotlinx.dom.createElement +import org.w3c.dom.HTMLElement +import org.w3c.dom.HTMLInputElement +import org.w3c.files.get + +fun selectFile( + inputSetup: HTMLInputElement.() -> Unit = {}, + onFailure: (Throwable) -> Unit = {}, + onFile: (MPPFile) -> Unit +) { + (document.createElement("input") { + (this as HTMLInputElement).apply { + inputSetup() + onchange = { + runCatching { + files ?.get(0) ?: error("File must not be null") + }.onSuccess { + onFile(it) + }.onFailure { + onFailure(it) + } + } + } + } as HTMLElement).click() +} + diff --git a/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/TriggerDownload.kt b/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/TriggerDownload.kt new file mode 100644 index 00000000000..369050cf196 --- /dev/null +++ b/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/TriggerDownload.kt @@ -0,0 +1,14 @@ +package dev.inmo.micro_utils.common + +import kotlinx.browser.document +import org.w3c.dom.HTMLAnchorElement + +fun triggerDownloadFile(filename: String, fileLink: String) { + val hiddenElement = document.createElement("a") as HTMLAnchorElement + + hiddenElement.href = fileLink + hiddenElement.target = "_blank" + hiddenElement.download = filename + hiddenElement.click() +} + diff --git a/coroutines/build.gradle b/coroutines/build.gradle index c614db7e76c..968d31e1521 100644 --- a/coroutines/build.gradle +++ b/coroutines/build.gradle @@ -13,6 +13,11 @@ kotlin { api libs.kt.coroutines } } + jsMain { + dependencies { + api project(":micro_utils.common") + } + } androidMain { dependencies { api libs.kt.coroutines.android diff --git a/coroutines/compose/build.gradle b/coroutines/compose/build.gradle index ca8aa5b5fce..c71171b88f3 100644 --- a/coroutines/compose/build.gradle +++ b/coroutines/compose/build.gradle @@ -12,6 +12,7 @@ kotlin { commonMain { dependencies { api libs.kt.coroutines + api project(":micro_utils.coroutines") } } } diff --git a/coroutines/compose/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/compose/FlowToState.kt b/coroutines/compose/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/compose/FlowToState.kt new file mode 100644 index 00000000000..22c1b7be83a --- /dev/null +++ b/coroutines/compose/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/compose/FlowToState.kt @@ -0,0 +1,22 @@ +package dev.inmo.micro_utils.coroutines.compose + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +fun Flow.toMutableState( + initial: T, + scope: CoroutineScope +): MutableState { + val state = mutableStateOf(initial) + subscribeSafelyWithoutExceptions(scope) { state.value = it } + return state +} + +inline fun StateFlow.toMutableState( + scope: CoroutineScope +): MutableState = toMutableState(value, scope) + diff --git a/coroutines/src/jsMain/kotlin/dev.inmo.micro_utils.coroutines/SelectFile.kt b/coroutines/src/jsMain/kotlin/dev.inmo.micro_utils.coroutines/SelectFile.kt new file mode 100644 index 00000000000..525a51b83df --- /dev/null +++ b/coroutines/src/jsMain/kotlin/dev.inmo.micro_utils.coroutines/SelectFile.kt @@ -0,0 +1,42 @@ +package dev.inmo.micro_utils.coroutines + +import dev.inmo.micro_utils.common.MPPFile +import dev.inmo.micro_utils.common.selectFile +import kotlinx.coroutines.CompletableDeferred +import org.w3c.dom.HTMLInputElement + +suspend fun selectFile( + inputSetup: HTMLInputElement.() -> Unit = {} +): MPPFile { + val result = CompletableDeferred() + + selectFile( + inputSetup, + { + result.completeExceptionally(it) + } + ) { + result.complete(it) + } + + return result.await() +} + +suspend fun selectOptionalFile( + inputSetup: HTMLInputElement.() -> Unit = {}, + onFailure: (Throwable) -> Unit = {} +): MPPFile? { + val result = CompletableDeferred() + + selectFile( + inputSetup, + { + result.complete(null) + onFailure(it) + } + ) { + result.complete(it) + } + + return result.await() +} diff --git a/settings.gradle b/settings.gradle index d22455ff9dd..d7471ac4ccc 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,6 +2,7 @@ rootProject.name='micro_utils' String[] includes = [ ":common", + ":common:compose", ":matrix", ":crypto", ":selector:common",