include compose
This commit is contained in:
client
features
content
common
src
commonMain
kotlin
dev
inmo
postssystem
features
content
common
server
src
jvmMain
kotlin
dev
inmo
postssystem
features
content
files
common
src
jvmMain
kotlin
dev
inmo
postssystem
features
files
common
gradle
settings.gradletargets/telegram/publication/server/src/jvmMain/kotlin/dev/inmo/postssystem/targets/telegram/publication/server
@ -2,6 +2,7 @@ plugins {
|
|||||||
id "org.jetbrains.kotlin.multiplatform"
|
id "org.jetbrains.kotlin.multiplatform"
|
||||||
id "org.jetbrains.kotlin.plugin.serialization"
|
id "org.jetbrains.kotlin.plugin.serialization"
|
||||||
id "com.android.library"
|
id "com.android.library"
|
||||||
|
alias(libs.plugins.compose)
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$mppProjectWithSerializationPresetPath"
|
apply from: "$mppProjectWithSerializationPresetPath"
|
||||||
@ -29,6 +30,8 @@ kotlin {
|
|||||||
api "dev.inmo:micro_utils.fsm.common:$microutils_version"
|
api "dev.inmo:micro_utils.fsm.common:$microutils_version"
|
||||||
api "dev.inmo:micro_utils.fsm.repos.common:$microutils_version"
|
api "dev.inmo:micro_utils.fsm.repos.common:$microutils_version"
|
||||||
api "dev.inmo:micro_utils.crypto:$microutils_version"
|
api "dev.inmo:micro_utils.crypto:$microutils_version"
|
||||||
|
|
||||||
|
implementation compose.runtime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,7 +43,8 @@ kotlin {
|
|||||||
|
|
||||||
jsMain {
|
jsMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-html:$kotlinx_html_version"
|
implementation compose.web.core
|
||||||
|
implementation libs.jsuikit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,28 @@
|
|||||||
package dev.inmo.postssystem.client.fsm.ui
|
package dev.inmo.postssystem.client.fsm.ui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import dev.inmo.jsuikit.elements.*
|
||||||
|
import dev.inmo.jsuikit.modifiers.UIKitButton
|
||||||
|
import dev.inmo.jsuikit.modifiers.UIKitMargin
|
||||||
import dev.inmo.postssystem.client.ui.fsm.*
|
import dev.inmo.postssystem.client.ui.fsm.*
|
||||||
import dev.inmo.postssystem.client.utils.HTMLViewContainer
|
import dev.inmo.postssystem.client.utils.HTMLViewContainer
|
||||||
import dev.inmo.postssystem.features.auth.client.ui.*
|
import dev.inmo.postssystem.features.auth.client.ui.*
|
||||||
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
|
||||||
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
||||||
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
import dev.inmo.micro_utils.fsm.common.StatesMachine
|
||||||
|
import dev.inmo.postssystem.client.utils.renderComposableAndLinkToContext
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.dom.clear
|
import kotlinx.dom.clear
|
||||||
import kotlinx.html.*
|
import org.jetbrains.compose.web.attributes.InputType
|
||||||
import kotlinx.html.dom.append
|
import org.jetbrains.compose.web.dom.Form
|
||||||
import kotlinx.html.js.form
|
|
||||||
import kotlinx.html.js.onClickFunction
|
|
||||||
import org.w3c.dom.*
|
import org.w3c.dom.*
|
||||||
|
|
||||||
class AuthView(
|
class AuthView(
|
||||||
private val viewModel: AuthUIViewModel,
|
private val viewModel: AuthUIViewModel,
|
||||||
private val uiScope: CoroutineScope
|
private val uiScope: CoroutineScope
|
||||||
) : JSView<AuthUIFSMState>() {
|
) : JSView<AuthUIFSMState>() {
|
||||||
private val usernameInput
|
|
||||||
get() = document.getElementById("authUsername") as? HTMLInputElement
|
|
||||||
private val passwordInput
|
|
||||||
get() = document.getElementById("authPassword") as? HTMLInputElement
|
|
||||||
private val authButton
|
|
||||||
get() = document.getElementById("authButton")
|
|
||||||
private val errorBadge
|
|
||||||
get() = document.getElementById("errorBadge") as? HTMLElement
|
|
||||||
private val progressBarDiv
|
|
||||||
get() = document.getElementById("progressBar") as? HTMLDivElement
|
|
||||||
|
|
||||||
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
|
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
|
||||||
htmlElement: HTMLElement,
|
htmlElement: HTMLElement,
|
||||||
@ -38,42 +32,36 @@ class AuthView(
|
|||||||
val completion = CompletableDeferred<UIFSMState?>()
|
val completion = CompletableDeferred<UIFSMState?>()
|
||||||
htmlElement.clear()
|
htmlElement.clear()
|
||||||
|
|
||||||
htmlElement.append {
|
val usernameState = mutableStateOf("")
|
||||||
form(classes = "vertical_container") {
|
val passwordState = mutableStateOf("")
|
||||||
div(classes = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label") {
|
val usernameDisabled = mutableStateOf(true)
|
||||||
input(type = InputType.text, classes = "mdl-textfield__input") {
|
val passwordDisabled = mutableStateOf(true)
|
||||||
id = "authUsername"
|
val authBtnDisabled = remember {
|
||||||
}
|
usernameState.value.isNotBlank() && passwordState.value.isNotBlank()
|
||||||
label(classes = "mdl-textfield__label") {
|
}
|
||||||
+"Имя пользователя"
|
val errorText = mutableStateOf<String?>(null)
|
||||||
}
|
|
||||||
|
val composition = renderComposableAndLinkToContext(htmlElement) {
|
||||||
|
Form {
|
||||||
|
TextField(
|
||||||
|
InputType.Text,
|
||||||
|
usernameState,
|
||||||
|
disabledState = usernameDisabled
|
||||||
|
)
|
||||||
|
TextField(
|
||||||
|
InputType.Password,
|
||||||
|
passwordState,
|
||||||
|
disabledState = passwordDisabled
|
||||||
|
)
|
||||||
|
|
||||||
|
if (errorText.value != null) {
|
||||||
|
Label.Error.draw(errorText.value.toString(), UIKitMargin.Small.Bottom)
|
||||||
}
|
}
|
||||||
div(classes = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label") {
|
|
||||||
input(type = InputType.password, classes = "mdl-textfield__input") {
|
DefaultButton("Authorise", UIKitButton.Type.Primary, disabled = authBtnDisabled) {
|
||||||
id = "authPassword"
|
val serverUrl = document.location ?.run { "$hostname:$port" }
|
||||||
}
|
if (serverUrl != null) {
|
||||||
label(classes = "mdl-textfield__label") {
|
uiScope.launchSafelyWithoutExceptions { viewModel.initAuth(serverUrl, usernameState.value, passwordState.value) }
|
||||||
+"Пароль"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div(classes = "mdl-progress mdl-js-progress") {
|
|
||||||
id = "progressBar"
|
|
||||||
}
|
|
||||||
span(classes = "material-icons mdl-badge mdl-badge--overlap gone") {
|
|
||||||
id = "errorBadge"
|
|
||||||
attributes["data-badge"] = "!"
|
|
||||||
}
|
|
||||||
button(classes = "mdl-button mdl-js-button mdl-button--raised") {
|
|
||||||
+"Авторизоваться"
|
|
||||||
id = "authButton"
|
|
||||||
onClickFunction = {
|
|
||||||
it.preventDefault()
|
|
||||||
val serverUrl = document.location ?.run { "$hostname:$port" }
|
|
||||||
val username = usernameInput ?.value
|
|
||||||
val password = passwordInput ?.value
|
|
||||||
if (serverUrl != null && username != null && password != null) {
|
|
||||||
uiScope.launchSafelyWithoutExceptions { viewModel.initAuth(serverUrl, username, password) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,38 +70,27 @@ class AuthView(
|
|||||||
val viewJob = viewModel.currentState.subscribeSafelyWithoutExceptions(uiScope) {
|
val viewJob = viewModel.currentState.subscribeSafelyWithoutExceptions(uiScope) {
|
||||||
when (it) {
|
when (it) {
|
||||||
is InitAuthUIState -> {
|
is InitAuthUIState -> {
|
||||||
usernameInput ?.removeAttribute("disabled")
|
usernameDisabled.value = false
|
||||||
passwordInput ?.removeAttribute("disabled")
|
passwordDisabled.value = false
|
||||||
authButton ?.removeAttribute("disabled")
|
|
||||||
errorBadge ?.apply {
|
errorText.value = when (it.showError) {
|
||||||
when (it.showError) {
|
ServerUnavailableAuthUIError -> "Сервер недоступен"
|
||||||
ServerUnavailableAuthUIError -> {
|
AuthIncorrectAuthUIError -> "Данные некорректны"
|
||||||
classList.remove("gone")
|
null -> null
|
||||||
innerText = "Сервер недоступен"
|
|
||||||
}
|
|
||||||
AuthIncorrectAuthUIError -> {
|
|
||||||
classList.remove("gone")
|
|
||||||
innerText = "Данные некорректны"
|
|
||||||
}
|
|
||||||
null -> classList.add("gone")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
progressBarDiv ?.classList ?.add("gone")
|
|
||||||
}
|
}
|
||||||
LoadingAuthUIState -> {
|
LoadingAuthUIState -> {
|
||||||
usernameInput ?.setAttribute("disabled", "")
|
usernameDisabled.value = true
|
||||||
passwordInput ?.setAttribute("disabled", "")
|
passwordDisabled.value = true
|
||||||
authButton ?.setAttribute("disabled", "")
|
errorText.value = null
|
||||||
errorBadge ?.classList ?.add("gone")
|
|
||||||
progressBarDiv ?.classList ?.remove("gone")
|
|
||||||
}
|
}
|
||||||
AuthorizedAuthUIState -> {
|
AuthorizedAuthUIState -> {
|
||||||
htmlElement.clear()
|
|
||||||
completion.complete(state.from)
|
completion.complete(state.from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return completion.await().also {
|
return completion.await().also {
|
||||||
|
composition.dispose()
|
||||||
viewJob.cancel()
|
viewJob.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package dev.inmo.postssystem.client.utils
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import org.jetbrains.compose.web.dom.DOMScope
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
fun Composition.linkWithJob(job: Job) {
|
||||||
|
job.invokeOnCompletion {
|
||||||
|
this@linkWithJob.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Composition.linkWithContext(coroutineContext: CoroutineContext) = linkWithJob(coroutineContext.job)
|
||||||
|
|
||||||
|
suspend fun <TElement : Element> renderComposableAndLinkToContext(
|
||||||
|
root: TElement,
|
||||||
|
monotonicFrameClock: MonotonicFrameClock = DefaultMonotonicFrameClock,
|
||||||
|
content: @Composable DOMScope<TElement>.() -> Unit
|
||||||
|
): Composition = org.jetbrains.compose.web.renderComposable(root, monotonicFrameClock, content).apply {
|
||||||
|
linkWithContext(
|
||||||
|
currentCoroutineContext()
|
||||||
|
)
|
||||||
|
}
|
@ -17,12 +17,7 @@ value class ContentId(val string: String)
|
|||||||
* @see ContentSerializersModuleConfigurator.Element
|
* @see ContentSerializersModuleConfigurator.Element
|
||||||
* @see ContentSerializersModuleConfigurator
|
* @see ContentSerializersModuleConfigurator
|
||||||
*/
|
*/
|
||||||
sealed interface Content
|
interface Content
|
||||||
|
|
||||||
/**
|
|
||||||
* This type of content represents simple content which is easy to serialize/deserialize and to use
|
|
||||||
*/
|
|
||||||
interface SimpleContent : Content
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This type represents some binary data which can be sent with multipart and deserialized from it
|
* This type represents some binary data which can be sent with multipart and deserialized from it
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package dev.inmo.postssystem.features.content.server
|
|
||||||
|
|
||||||
import com.benasher44.uuid.uuid4
|
|
||||||
import dev.inmo.micro_utils.common.FileName
|
|
||||||
import io.ktor.http.content.PartData
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
suspend fun PartData.loadContent() {
|
|
||||||
when (this) {
|
|
||||||
is PartData.FormItem -> TODO()
|
|
||||||
is PartData.FileItem -> {
|
|
||||||
val fileName = FileName(originalFileName ?: return null)
|
|
||||||
val downloadTo = File.createTempFile(
|
|
||||||
uuid4().toString(),
|
|
||||||
fileName.extension
|
|
||||||
)
|
|
||||||
headers["data"]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
is PartData.BinaryItem -> return null/* Currently impossible state */
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,6 +3,8 @@ package dev.inmo.postssystem.features.files.common
|
|||||||
import com.benasher44.uuid.uuid4
|
import com.benasher44.uuid.uuid4
|
||||||
import dev.inmo.postssystem.features.files.common.storage.WriteFilesStorage
|
import dev.inmo.postssystem.features.files.common.storage.WriteFilesStorage
|
||||||
import dev.inmo.micro_utils.repos.*
|
import dev.inmo.micro_utils.repos.*
|
||||||
|
import io.ktor.utils.io.core.copyTo
|
||||||
|
import io.ktor.utils.io.streams.asOutput
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@ -36,7 +38,7 @@ class WriteDistFilesStorage(
|
|||||||
file = newId.file
|
file = newId.file
|
||||||
} while (file.exists())
|
} while (file.exists())
|
||||||
metasKeyValueRepo.set(newId, it.toMetaFileInfo())
|
metasKeyValueRepo.set(newId, it.toMetaFileInfo())
|
||||||
file.writeBytes(it.inputProvider())
|
it.inputProvider().copyTo(file.outputStream().asOutput())
|
||||||
FullFileInfoStorageWrapper(newId, it)
|
FullFileInfoStorageWrapper(newId, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,13 +54,15 @@ class WriteDistFilesStorage(
|
|||||||
override suspend fun update(
|
override suspend fun update(
|
||||||
id: FileId,
|
id: FileId,
|
||||||
value: FullFileInfo
|
value: FullFileInfo
|
||||||
): FullFileInfoStorageWrapper? = id.file.takeIf { it.exists() } ?.writeBytes(value.inputProvider()) ?.let {
|
): FullFileInfoStorageWrapper? {
|
||||||
|
val file = id.file.takeIf { it.exists() } ?: return null
|
||||||
|
value.inputProvider().copyTo(file.outputStream().asOutput())
|
||||||
val result = FullFileInfoStorageWrapper(id, value.copy())
|
val result = FullFileInfoStorageWrapper(id, value.copy())
|
||||||
|
|
||||||
metasKeyValueRepo.set(id, value.toMetaFileInfo())
|
metasKeyValueRepo.set(id, value.toMetaFileInfo())
|
||||||
_updatedObjectsFlow.emit(result)
|
_updatedObjectsFlow.emit(result)
|
||||||
|
|
||||||
result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun update(
|
override suspend fun update(
|
||||||
|
26
gradle/libs.versions.toml
Normal file
26
gradle/libs.versions.toml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[versions]
|
||||||
|
|
||||||
|
jsuikit = "0.0.15"
|
||||||
|
compose = "1.0.1"
|
||||||
|
microutils = "0.9.4"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
|
||||||
|
jsuikit = { module = "dev.inmo:kjsuikit", version.ref = "jsuikit" }
|
||||||
|
|
||||||
|
microutils-common = { module = "dev.inmo:micro_utils.common", version.ref = "microutils" }
|
||||||
|
microutils-pagination-common = { module = "dev.inmo:micro_utils.pagination.common", version.ref = "microutils" }
|
||||||
|
microutils-fsm-common = { module = "dev.inmo:micro_utils.fsm.common", version.ref = "microutils" }
|
||||||
|
microutils-fsm-repos-common = { module = "dev.inmo:micro_utils.fsm.common", version.ref = "microutils" }
|
||||||
|
microutils-crypto = { module = "dev.inmo:micro_utils.crypto", version.ref = "microutils" }
|
||||||
|
microutils-repos-common = { module = "dev.inmo:micro_utils.repos.common", version.ref = "microutils" }
|
||||||
|
microutils-repos-ktor-client = { module = "dev.inmo:micro_utils.repos.ktor.client", version.ref = "microutils" }
|
||||||
|
microutils-repos-ktor-server = { module = "dev.inmo:micro_utils.repos.ktor.server", version.ref = "microutils" }
|
||||||
|
microutils-repos-exposed = { module = "dev.inmo:micro_utils.repos.exposed", version.ref = "microutils" }
|
||||||
|
microutils-mimetypes = { module = "dev.inmo:micro_utils.mime_types", version.ref = "microutils" }
|
||||||
|
microutils-coroutines = { module = "dev.inmo:micro_utils.coroutines", version.ref = "microutils" }
|
||||||
|
microutils-serialization-typedserializer = { module = "dev.inmo:micro_utils.serialization.typed_serializer", version.ref = "microutils" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
|
||||||
|
compose = { id = "org.jetbrains.compose", version.ref = "compose" }
|
@ -70,3 +70,5 @@ includes.each { originalName ->
|
|||||||
project.projectDir = new File(projectDirectory)
|
project.projectDir = new File(projectDirectory)
|
||||||
println(project)
|
println(project)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableFeaturePreview("VERSION_CATALOGS")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package dev.inmo.postssystem.targets.telegram.publication.server
|
package dev.inmo.postssystem.targets.telegram.publication.server
|
||||||
|
|
||||||
import dev.inmo.micro_utils.mime_types.KnownMimeTypes
|
import dev.inmo.micro_utils.mime_types.KnownMimeTypes
|
||||||
import dev.inmo.postssystem.features.content.binary.common.DefaultBinaryContent
|
import dev.inmo.postssystem.features.content.common.BinaryContent
|
||||||
import dev.inmo.postssystem.features.content.text.common.TextContent
|
import dev.inmo.postssystem.features.content.text.common.TextContent
|
||||||
import dev.inmo.postssystem.features.publication.server.PublicationPost
|
import dev.inmo.postssystem.features.publication.server.PublicationPost
|
||||||
import dev.inmo.postssystem.features.publication.server.PublicationTarget
|
import dev.inmo.postssystem.features.publication.server.PublicationTarget
|
||||||
@ -12,6 +12,8 @@ import dev.inmo.tgbotapi.requests.send.SendTextMessage
|
|||||||
import dev.inmo.tgbotapi.requests.send.media.*
|
import dev.inmo.tgbotapi.requests.send.media.*
|
||||||
import dev.inmo.tgbotapi.types.ChatId
|
import dev.inmo.tgbotapi.types.ChatId
|
||||||
import dev.inmo.tgbotapi.utils.StorageFile
|
import dev.inmo.tgbotapi.utils.StorageFile
|
||||||
|
import io.ktor.utils.io.ByteReadChannel
|
||||||
|
import io.ktor.utils.io.core.readBytes
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
class PublicationTargetTelegram(
|
class PublicationTargetTelegram(
|
||||||
@ -22,9 +24,9 @@ class PublicationTargetTelegram(
|
|||||||
post.content.mapNotNull {
|
post.content.mapNotNull {
|
||||||
val content = it.content
|
val content = it.content
|
||||||
when (content) {
|
when (content) {
|
||||||
is DefaultBinaryContent -> {
|
is BinaryContent -> {
|
||||||
val storageFile by lazy {
|
val storageFile by lazy {
|
||||||
StorageFile(content.filename.name, content.bytesAllocator()).asMultipartFile()
|
StorageFile(content.filename.name, content.inputProvider().readBytes()).asMultipartFile()
|
||||||
}
|
}
|
||||||
when (content.mimeType) {
|
when (content.mimeType) {
|
||||||
is KnownMimeTypes.Image.Jpeg,
|
is KnownMimeTypes.Image.Jpeg,
|
||||||
|
Reference in New Issue
Block a user