almost complete rework of ui up to module ui

This commit is contained in:
2022-03-23 23:21:02 +06:00
parent 72578f6b58
commit cb5de073fb
62 changed files with 499 additions and 471 deletions
client
features
auth
common
content
binary
client
src
jsMain
kotlin
dev
inmo
postssystem
features
client
src
commonMain
kotlin
dev
inmo
postssystem
features
content
text
client
src
jsMain
kotlin
dev
inmo
postssystem
features
files
client
build.gradle
src
commonMain
kotlin
dev
inmo
postssystem
features
files
roles
client
build.gradle
src
commonMain
kotlin
dev
inmo
postssystem
features
roles
manager
common
src
commonMain
kotlin
dev
inmo
postssystem
features
roles
manager
users
client
build.gradle
src
commonMain
kotlin
dev
inmo
postssystem
features
users
gradle
publicators/simple/client
build.gradle
src
commonMain
kotlin
dev
inmo
postssystem
publicators
simple
services/posts/client
build.gradle
src
commonMain
jsMain
kotlin
dev
inmo
postssystem
services
targets/telegram/loader/client/src/commonMain/kotlin/dev/inmo/postssystem/targets/telegram/loader/client

@ -32,9 +32,6 @@ kotlin {
api project(":postssystem.features.content.binary.client") api project(":postssystem.features.content.binary.client")
api project(":postssystem.services.posts.client") api project(":postssystem.services.posts.client")
api libs.microutils.fsm.common
api libs.microutils.fsm.repos.common
api libs.microutils.crypto api libs.microutils.crypto
implementation compose.runtime implementation compose.runtime

@ -1,148 +0,0 @@
package dev.inmo.postssystem.client
import dev.inmo.postssystem.client.ui.fsm.*
import dev.inmo.postssystem.features.auth.client.installClientAuthenticator
import dev.inmo.postssystem.features.auth.common.*
import dev.inmo.postssystem.features.files.client.ClientReadFilesStorage
import dev.inmo.postssystem.features.files.common.storage.ReadFilesStorage
import dev.inmo.postssystem.features.roles.common.Role
import dev.inmo.postssystem.features.roles.common.RolesStorage
import dev.inmo.postssystem.features.roles.client.ClientRolesStorage
import dev.inmo.postssystem.features.roles.manager.common.RolesManagerRoleSerializer
import dev.inmo.postssystem.features.users.client.UsersStorageKtorClient
import dev.inmo.postssystem.features.users.common.ReadUsersStorage
import dev.inmo.postssystem.features.users.common.User
import dev.inmo.micro_utils.common.Either
import dev.inmo.micro_utils.coroutines.LinkedSupervisorScope
import dev.inmo.micro_utils.fsm.common.StatesMachine
import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.postssystem.client.settings.DefaultSettings
import dev.inmo.postssystem.client.settings.Settings
import dev.inmo.postssystem.client.settings.auth.AuthSettings
import dev.inmo.postssystem.client.settings.auth.DefaultAuthSettings
import dev.inmo.postssystem.features.common.common.*
import dev.inmo.postssystem.features.content.common.ContentSerializersModuleConfigurator
import dev.inmo.postssystem.features.content.common.OtherContentSerializerModuleConfigurator
import dev.inmo.postssystem.features.content.text.common.TextContentSerializerModuleConfigurator
import dev.inmo.postssystem.features.status.client.StatusFeatureClient
import dev.inmo.postssystem.publicators.simple.client.SimplePublicatorService
import dev.inmo.postssystem.publicators.simple.client.SimplePublicatorServiceClient
import dev.inmo.postssystem.services.posts.client.ClientPostsService
import dev.inmo.postssystem.services.posts.common.*
import io.ktor.client.HttpClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.serialization.BinaryFormat
import kotlinx.serialization.StringFormat
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import org.koin.core.Koin
import org.koin.core.context.startKoin
import org.koin.core.module.Module
import org.koin.core.qualifier.*
import org.koin.core.scope.Scope
import org.koin.dsl.binds
import org.koin.dsl.module
val UIScopeQualifier = StringQualifier("CoroutineScopeUI")
val SettingsQualifier = StringQualifier("Settings")
val RolesQualifier = StringQualifier("Roles")
private val FSMHandlersBuilderQualifier = StringQualifier("FSMHandlersBuilder")
val defaultSerialFormat = Json {
ignoreUnknownKeys = true
}
/**
* Entrypoint for getting [org.koin.core.Koin] DI for the client
*
* @param repoFactory Factory for creating of [DefaultStatesManagerRepo] for [dev.inmo.postssystem.client.ui.fsm.UIFSM]
*/
fun baseKoin(
defaultScope: CoroutineScope,
settingsFactory: Scope.() -> KeyValueRepo<String, Any>,
repoFactory: Scope.() -> DefaultStatesManagerRepo<UIFSMState>,
handlersSetter: Pair<Scope, FSMBuilder<UIFSMState>>.() -> Unit
): Koin = startKoin {
modules(
module {
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { OtherContentSerializerModuleConfigurator }
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { TextContentSerializerModuleConfigurator }
singleWithRandomQualifier<SerializersModuleConfigurator.Element> { ContentSerializersModuleConfigurator(getAll()) }
single { SerializersModuleConfigurator(getAll()) }
single {
Json {
ignoreUnknownKeys = true
serializersModule = SerializersModule { get<SerializersModuleConfigurator>().apply { invoke() } }
}
}
single<StringFormat> { get<Json>() }
single(SettingsQualifier) { settingsFactory() }
single { DBDropper(get(SettingsQualifier)) }
single(FSMHandlersBuilderQualifier) { handlersSetter }
single { repoFactory() }
single { defaultScope }
single(UIScopeQualifier) { get<CoroutineScope>().LinkedSupervisorScope(Dispatchers.Main) }
single<StatesMachine<UIFSMState>>(UIFSMQualifier) { UIFSM(get()) { (this@single to this@UIFSM).apply(get(
FSMHandlersBuilderQualifier
)) } }
single<AuthSettings> { DefaultAuthSettings(get(SettingsQualifier), get(), koin, get()) }
single<Settings> { DefaultSettings(get()) }
AdditionalModules.modules.forEach {
it.apply { load() }
}
}
)
}.koin.apply {
loadModules(
listOf(
module { single<Koin> { this@apply } }
)
)
RolesManagerRoleSerializer // Just to activate it in JS client
}
fun getAuthorizedFeaturesDIModule(
serverUrl: String,
initialAuthKey: Either<AuthKey, AuthTokenInfo>,
onAuthKeyUpdated: suspend (AuthTokenInfo) -> Unit,
onUserRetrieved: suspend (User?) -> Unit,
onAuthKeyInvalidated: suspend () -> Unit
): Module {
val serverUrlQualifier = StringQualifier("serverUrl")
val credsQualifier = StringQualifier("creds")
return module {
single(createdAtStart = true) {
HttpClient {
installClientAuthenticator(serverUrl, get(), get(credsQualifier), onAuthKeyUpdated, onUserRetrieved, onAuthKeyInvalidated)
}
}
single(credsQualifier) { initialAuthKey }
single(serverUrlQualifier) { serverUrl }
single<BinaryFormat> {
Cbor {
serializersModule = SerializersModule { get<SerializersModuleConfigurator>().apply { invoke() } }
}
}
single { UnifiedRequester(get(), get()) }
single { StatusFeatureClient(get(serverUrlQualifier), get()) }
single<ReadFilesStorage> { ClientReadFilesStorage(get(serverUrlQualifier), get(), get()) }
single<ReadUsersStorage> { UsersStorageKtorClient(get(serverUrlQualifier), get()) }
single<RolesStorage<Role>> { ClientRolesStorage(get(serverUrlQualifier), get(), Role.serializer()) }
single<PostsService> { ClientPostsService(get(serverUrlQualifier), get()) } binds arrayOf(
ReadPostsService::class,
WritePostsService::class
)
single<SimplePublicatorService> { SimplePublicatorServiceClient(get(serverUrlQualifier), get()) }
}
}

@ -1,8 +0,0 @@
package dev.inmo.postssystem.client.settings
import dev.inmo.postssystem.client.settings.auth.AuthSettings
data class DefaultSettings(
override val authSettings: AuthSettings
) : Settings

@ -1,12 +0,0 @@
package dev.inmo.postssystem.client.settings
import dev.inmo.postssystem.client.settings.auth.AuthSettings
import kotlinx.coroutines.flow.StateFlow
import org.koin.core.module.Module
interface Settings {
val authSettings: AuthSettings
val authorizedDIModule: StateFlow<Module?>
get() = authSettings.authorizedDIModule
}

@ -1,27 +0,0 @@
package dev.inmo.postssystem.client.ui.fsm
import dev.inmo.postssystem.features.auth.client.AuthUnavailableException
import dev.inmo.micro_utils.fsm.common.*
interface UIFSMHandler<T : UIFSMState> : StatesHandler<T, UIFSMState> {
suspend fun StatesMachine<in UIFSMState>.safeHandleState(state: T): UIFSMState?
override suspend fun StatesMachine<in UIFSMState>.handleState(state: T): UIFSMState? {
return runCatching {
safeHandleState(state).also(::println)
}.getOrElse {
errorToNextStep(state, it) ?.let { return it } ?: throw it
}.also(::println)
}
suspend fun errorToNextStep(
currentState: T,
e: Throwable
): UIFSMState? = when (e) {
is AuthUnavailableException -> if (currentState is AuthUIFSMState) {
currentState
} else {
AuthUIFSMState(currentState)
}
else -> null
}
}

@ -1,30 +0,0 @@
package dev.inmo.postssystem.client.ui.fsm
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
import kotlinx.serialization.*
@Serializable(UIFSMStateSerializer::class)
sealed interface UIFSMState : State {
val from: UIFSMState?
get() = null
override val context: String
get() = "main"
}
object UIFSMStateSerializer : KSerializer<UIFSMState> by TypedSerializer(
"auth" to AuthUIFSMState.serializer(),
)
@Serializable
data class AuthUIFSMState(
override val from: UIFSMState? = CreatePostUIFSMState(),
override val context: String = "main"
) : UIFSMState
val DefaultAuthUIFSMState = AuthUIFSMState()
@Serializable
data class CreatePostUIFSMState(
override val from: UIFSMState? = null,
override val context: String = "main"
) : UIFSMState

@ -1,23 +1,23 @@
package dev.inmo.postssystem.client package dev.inmo.postssystem.client
import dev.inmo.postssystem.client.fsm.ui.* import dev.inmo.postssystem.client.fsm.ui.*
import dev.inmo.postssystem.client.ui.*
import dev.inmo.postssystem.client.ui.fsm.*
import dev.inmo.postssystem.client.ui.fsm.UIFSMStateSerializer
import dev.inmo.postssystem.features.auth.client.ui.AuthUIModel
import dev.inmo.postssystem.features.auth.client.ui.AuthUIViewModel
import dev.inmo.postssystem.features.auth.common.AuthTokenInfo import dev.inmo.postssystem.features.auth.common.AuthTokenInfo
import dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler import dev.inmo.micro_utils.coroutines.ContextSafelyExceptionHandler
import dev.inmo.micro_utils.fsm.common.CheckableHandlerHolder import dev.inmo.micro_utils.fsm.common.CheckableHandlerHolder
import dev.inmo.micro_utils.fsm.common.StatesMachine import dev.inmo.micro_utils.fsm.common.StatesMachine
import dev.inmo.micro_utils.repos.mappers.withMapper import dev.inmo.micro_utils.repos.mappers.withMapper
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
import dev.inmo.postssystem.client.settings.auth.AuthSettings import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
import dev.inmo.postssystem.features.auth.client.ui.*
import dev.inmo.postssystem.features.common.common.baseKoin
import dev.inmo.postssystem.features.common.common.getAllDistinct import dev.inmo.postssystem.features.common.common.getAllDistinct
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMHandler
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMStateSerializer
import dev.inmo.postssystem.services.posts.client.ui.create.* import dev.inmo.postssystem.services.posts.client.ui.create.*
import kotlinx.browser.* import kotlinx.browser.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.context.loadKoinModules import org.koin.core.context.loadKoinModules
@ -38,6 +38,9 @@ val defaultTypedSerializer = TypedSerializer<Any>(
"Double" to Double.serializer(), "Double" to Double.serializer(),
"UIFSMState" to UIFSMStateSerializer "UIFSMState" to UIFSMStateSerializer
) )
val defaultSerialFormat = Json {
ignoreUnknownKeys = true
}
fun baseKoin(): Koin { fun baseKoin(): Koin {
val anyToString: suspend Any.() -> String = { val anyToString: suspend Any.() -> String = {
@ -81,66 +84,19 @@ fun baseKoin(): Koin {
JSUIFSMStatesRepo(window.history) JSUIFSMStatesRepo(window.history)
} }
) { ) {
val scope = first
first.apply { first.apply {
second.apply { second.apply {
loadKoinModules( loadKoinModules(
module { module {
factory { document.getElementById("main") as HTMLElement } factory { document.getElementById("main") as HTMLElement }
factory<AuthUIModel> { DefaultAuthUIModel(get(), get()) }
factory { AuthUIViewModel(get()) }
factory { AuthView(get(), get(UIScopeQualifier)) }
factory<PostCreateUIModel> { DefaultPostCreateUIModel(get(), get()) }
factory { PostCreateUIViewModel(get()) }
factory { PostCreateView(get(), getAllDistinct(), get(UIScopeQualifier)) }
} }
) )
strictlyOn<AuthUIFSMState>(get<AuthView>()) getAllDistinct<UIFSMHandler.Registrator>().forEach {
with(it) {
// Костыль, в JS на момент Пн дек 6 14:19:29 +06 2021 если использовать strictlyOn генерируются include()
// некорректные безымянные классы (у них отсутствует метод handleState)
class DefaultStateHandlerWrapper<T : UIFSMState>(
private val klass: KClass<out UIFSMHandler<T>>,
private val stateKlass: KClass<T>,
private val qualifier: Qualifier? = null,
private val parameters: ((T) -> ParametersHolder)? = null
) : CheckableHandlerHolder<UIFSMState, UIFSMState> {
override suspend fun StatesMachine<in UIFSMState>.handleState(state: UIFSMState): UIFSMState? {
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
val state = state as T
return runCatching {
val authSettings = get<AuthSettings>()
authSettings.loadingJob.join()
if (authSettings.authorizedDIModule.value == null) {
error("Can't perform state $state: Auth module was not initialized")
} else {
get<UIFSMHandler<T>>(klass, qualifier, parameters ?.let { { it(state) } }).run {
handleState(state)
} }
} }
}.getOrElse { e ->
e.printStackTrace()
AuthUIFSMState(state)
}
}
override suspend fun checkHandleable(state: UIFSMState): Boolean = stateKlass.isInstance(state)
}
inline fun <reified T : UIFSMState> registerHandler(
klass: KClass<out UIFSMHandler<T>>,
qualifier: Qualifier? = null,
parameters: ((T) -> ParametersHolder)? = null
) = add(
DefaultStateHandlerWrapper<T>(
klass,
T::class,
qualifier,
parameters
)
)
registerHandler(PostCreateView::class)
} }
} }
} }

@ -1,7 +1,7 @@
package dev.inmo.postssystem.client package dev.inmo.postssystem.client
import dev.inmo.postssystem.client.ui.fsm.*
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
import kotlinx.browser.window import kotlinx.browser.window
import kotlinx.serialization.StringFormat import kotlinx.serialization.StringFormat
import org.w3c.dom.* import org.w3c.dom.*

@ -1,14 +1,13 @@
package dev.inmo.postssystem.client package dev.inmo.postssystem.client
import dev.inmo.postssystem.client.ui.fsm.UIFSMQualifier
import dev.inmo.postssystem.client.ui.fsm.UIFSMState
import dev.inmo.micro_utils.fsm.common.StatesMachine import dev.inmo.micro_utils.fsm.common.StatesMachine
import dev.inmo.postssystem.features.common.common.DefaultQualifiers
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
import kotlinx.browser.window import kotlinx.browser.window
fun main() { fun main() {
window.addEventListener("load", { window.addEventListener("load", {
val koin = baseKoin() val koin = baseKoin()
val uiStatesMachine = koin.get<StatesMachine<UIFSMState>>(UIFSMQualifier) val uiStatesMachine = koin.get<StatesMachine<UIFSMState>>(DefaultQualifiers.UIFSMQualifier)
uiStatesMachine.start(koin.get()) uiStatesMachine.start(koin.get())
}) })
} }

@ -1,7 +0,0 @@
package dev.inmo.postssystem.client.fsm.ui
import kotlinx.browser.document
import org.w3c.dom.Element
val mainContainer: Element
get() = document.getElementById("main")!!

@ -1,19 +0,0 @@
package dev.inmo.postssystem.client.utils
import dev.inmo.postssystem.features.files.common.FullFileInfo
import dev.inmo.micro_utils.common.toArrayBuffer
import io.ktor.utils.io.core.readBytes
import kotlinx.browser.document
import org.w3c.dom.HTMLAnchorElement
import org.w3c.dom.url.URL
import org.w3c.files.Blob
fun triggerDownloadFile(fullFileInfo: FullFileInfo) {
val hiddenElement = document.createElement("a") as HTMLAnchorElement
val url = URL.createObjectURL(Blob(arrayOf(fullFileInfo.inputProvider().readBytes().toArrayBuffer())))
hiddenElement.href = url
hiddenElement.target = "_blank"
hiddenElement.download = fullFileInfo.name.name
hiddenElement.click()
}

@ -1,25 +0,0 @@
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()
)
}

@ -1,77 +0,0 @@
package dev.inmo.postssystem.client.utils
import androidx.compose.runtime.MutableState
import dev.inmo.postssystem.features.files.common.FullFileInfo
import dev.inmo.micro_utils.common.*
import dev.inmo.micro_utils.mime_types.KnownMimeTypes
import dev.inmo.micro_utils.mime_types.findBuiltinMimeType
import dev.inmo.postssystem.features.common.common.BytesBasedInputProvider
import io.ktor.utils.io.core.ByteReadPacket
import kotlinx.coroutines.flow.MutableStateFlow
import org.khronos.webgl.ArrayBuffer
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.events.Event
import org.w3c.files.FileReader
import org.w3c.files.get
fun uploadFileCallbackForHTMLInputChange(
onSet: (FullFileInfo) -> Unit
): (Event) -> Unit = {
(it.target as? HTMLInputElement) ?.apply {
files ?.also { files ->
files[0] ?.also { file ->
val reader: FileReader = FileReader()
reader.onload = {
val bytes = ((it.target.asDynamic()).result as ArrayBuffer).toByteArray()
onSet(
FullFileInfo(
FileName(file.name),
findBuiltinMimeType(file.type) ?: KnownMimeTypes.Any,
BytesBasedInputProvider(bytes)
)
)
}
reader.readAsArrayBuffer(file)
}
}
}
}
fun fileCallbackForHTMLInputChange(
onSet: (MPPFile) -> Unit
): (Event) -> Unit = {
(it.target as? HTMLInputElement) ?.apply {
files ?.also { files ->
files[0] ?.also { file ->
onSet(file)
}
}
}
}
fun uploadFileCallbackForHTMLInputChange(
output: MutableState<FullFileInfo?>
): (Event) -> Unit = uploadFileCallbackForHTMLInputChange {
output.value = it
}
fun uploadFileCallbackForHTMLInputChange(
output: MutableStateFlow<FullFileInfo?>
): (Event) -> Unit = uploadFileCallbackForHTMLInputChange {
output.value = it
}
fun fileCallbackForHTMLInputChange(
output: MutableState<MPPFile?>
): (Event) -> Unit = fileCallbackForHTMLInputChange {
output.value = it
}
fun fileCallbackForHTMLInputChange(
output: MutableStateFlow<MPPFile?>
): (Event) -> Unit = fileCallbackForHTMLInputChange {
output.value = it
}

@ -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"
@ -11,8 +12,14 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
api project(":postssystem.features.common.client") api project(":postssystem.features.common.client")
api project(":postssystem.features.roles.client")
api project(":postssystem.features.status.client")
api project(":postssystem.features.auth.common") api project(":postssystem.features.auth.common")
} }
} }
clientMain {
dependencies {
}
}
} }
} }

@ -0,0 +1,39 @@
package dev.inmo.postssystem.features.auth.client
import dev.inmo.micro_utils.common.Either
import dev.inmo.micro_utils.ktor.client.UnifiedRequester
import dev.inmo.postssystem.features.auth.common.AuthKey
import dev.inmo.postssystem.features.auth.common.AuthTokenInfo
import dev.inmo.postssystem.features.common.common.AdditionalModules
import dev.inmo.postssystem.features.status.client.StatusFeatureClient
import dev.inmo.postssystem.features.users.common.User
import io.ktor.client.HttpClient
import org.koin.core.module.Module
import org.koin.dsl.module
fun createAuthorizedFeaturesDIModule(
serverUrl: String,
initialAuthKey: Either<AuthKey, AuthTokenInfo>,
onAuthKeyUpdated: suspend (AuthTokenInfo) -> Unit,
onUserRetrieved: suspend (User?) -> Unit,
onAuthKeyInvalidated: suspend () -> Unit
): Module {
return module {
single(AuthorizedQualifiers.CredsQualifier) { initialAuthKey }
single(AuthorizedQualifiers.ServerUrlQualifier) { serverUrl }
single (createdAtStart = true) {
HttpClient {
installClientAuthenticator(serverUrl, get(), get(AuthorizedQualifiers.CredsQualifier), onAuthKeyUpdated, onUserRetrieved, onAuthKeyInvalidated)
}
}
single { UnifiedRequester(get(), get()) }
single { StatusFeatureClient(get(AuthorizedQualifiers.ServerUrlQualifier), get()) }
AdditionalModules.Authorized.modules.forEach {
with(it) {
load()
}
}
}
}

@ -0,0 +1,15 @@
package dev.inmo.postssystem.features.auth.client
import dev.inmo.postssystem.features.common.common.AdditionalModules
import dev.inmo.postssystem.features.common.common.ModuleLoader
import org.koin.core.module.Module
private val AuthorizedAdditionalModules = AdditionalModules()
val AdditionalModules.Companion.Authorized: AdditionalModules
get() = AuthorizedAdditionalModules
fun AuthorizedModuleLoader(loadingBlock: Module.() -> Unit): ModuleLoader.ByCallback {
val newModuleLoader = ModuleLoader.ByCallback(loadingBlock)
AdditionalModules.Authorized.addModule(newModuleLoader)
return newModuleLoader
}

@ -0,0 +1,8 @@
package dev.inmo.postssystem.features.auth.client
import org.koin.core.qualifier.StringQualifier
object AuthorizedQualifiers {
val CredsQualifier = StringQualifier("creds")
val ServerUrlQualifier = StringQualifier("serverUrl")
}

@ -0,0 +1,59 @@
package dev.inmo.postssystem.features.auth.client
import dev.inmo.micro_utils.fsm.common.CheckableHandlerHolder
import dev.inmo.micro_utils.fsm.common.StatesMachine
import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder
import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
import dev.inmo.postssystem.features.auth.client.ui.AuthUIFSMState
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMHandler
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
import org.koin.core.Koin
import org.koin.core.parameter.ParametersHolder
import org.koin.core.qualifier.Qualifier
import org.koin.core.scope.Scope
import kotlin.reflect.KClass
// Костыль, в JS на момент Пн дек 6 14:19:29 +06 2021 если использовать strictlyOn генерируются
// некорректные безымянные классы (у них отсутствует метод handleState)
class DefaultStateHandlerWrapper<T : UIFSMState>(
private val klass: KClass<out UIFSMHandler<T>>,
private val koin: Koin,
private val stateKlass: KClass<T>,
private val qualifier: Qualifier? = null,
private val parameters: ((T) -> ParametersHolder)? = null
) : CheckableHandlerHolder<UIFSMState, UIFSMState> {
override suspend fun StatesMachine<in UIFSMState>.handleState(state: UIFSMState): UIFSMState? {
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
val state = state as T
return runCatching {
val authSettings = koin.get<AuthSettings>()
authSettings.loadingJob.join()
if (authSettings.authorizedDIModule.value == null) {
error("Can't perform state $state: Auth module was not initialized")
} else {
koin.get<UIFSMHandler<T>>(klass, qualifier, parameters ?.let { { it(state) } }).run {
handleState(state)
}
}
}.getOrElse { e ->
e.printStackTrace()
AuthUIFSMState(state)
}
}
override suspend fun checkHandleable(state: UIFSMState): Boolean = stateKlass.isInstance(state)
}
inline fun <reified T : UIFSMState> FSMBuilder<UIFSMState>.registerAfterAuthHandler(
koin: Koin,
klass: KClass<out UIFSMHandler<T>>,
qualifier: Qualifier? = null,
noinline parameters: ((T) -> ParametersHolder)? = null
) = add(
DefaultStateHandlerWrapper<T>(
klass,
koin,
T::class,
qualifier,
parameters
)
)

@ -0,0 +1,27 @@
package dev.inmo.postssystem.features.auth.client
import dev.inmo.micro_utils.common.Optional
import dev.inmo.micro_utils.common.optional
import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
import dev.inmo.postssystem.features.auth.client.settings.DefaultAuthSettings
import dev.inmo.postssystem.features.auth.client.ui.*
import dev.inmo.postssystem.features.common.common.*
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMExceptionHandler
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
val defaultModuleLoader = DefaultModuleLoader {
single<AuthSettings> { DefaultAuthSettings(get(DefaultQualifiers.SettingsQualifier), get(), getKoin(), get()) }
singleWithRandomQualifier {
UIFSMExceptionHandler { currentState, exception ->
if (exception is AuthUnavailableException) {
Optional.presented(AuthUIFSMState(currentState))
} else {
Optional.absent()
}
}
}
factory<AuthUIModel> { DefaultAuthUIModel(get(), get()) }
factory { AuthUIViewModel(get()) }
}

@ -1,4 +1,4 @@
package dev.inmo.postssystem.client.settings.auth package dev.inmo.postssystem.features.auth.client.settings
import dev.inmo.postssystem.features.auth.client.ui.AuthUIError import dev.inmo.postssystem.features.auth.client.ui.AuthUIError
import dev.inmo.postssystem.features.auth.common.AuthCreds import dev.inmo.postssystem.features.auth.common.AuthCreds

@ -1,7 +1,5 @@
package dev.inmo.postssystem.client.settings.auth package dev.inmo.postssystem.features.auth.client.settings
import dev.inmo.postssystem.client.DBDropper
import dev.inmo.postssystem.client.getAuthorizedFeaturesDIModule
import dev.inmo.postssystem.features.auth.client.AuthUnavailableException import dev.inmo.postssystem.features.auth.client.AuthUnavailableException
import dev.inmo.postssystem.features.auth.client.ui.* import dev.inmo.postssystem.features.auth.client.ui.*
import dev.inmo.postssystem.features.auth.common.* import dev.inmo.postssystem.features.auth.common.*
@ -14,6 +12,8 @@ import dev.inmo.micro_utils.common.either
import dev.inmo.micro_utils.coroutines.plus import dev.inmo.micro_utils.coroutines.plus
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.postssystem.features.auth.client.createAuthorizedFeaturesDIModule
import dev.inmo.postssystem.features.common.common.DBDropper
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.koin.core.Koin import org.koin.core.Koin
@ -73,7 +73,7 @@ data class DefaultAuthSettings(
initialAuthKey: Either<AuthKey, AuthTokenInfo>, initialAuthKey: Either<AuthKey, AuthTokenInfo>,
): AuthUIError? { ): AuthUIError? {
val currentModule = authorizedDIModule.value val currentModule = authorizedDIModule.value
val newModule = getAuthorizedFeaturesDIModule( val newModule = createAuthorizedFeaturesDIModule(
serverUrl, serverUrl,
initialAuthKey, initialAuthKey,
{ {

@ -0,0 +1,14 @@
package dev.inmo.postssystem.features.auth.client.ui
import dev.inmo.postssystem.features.common.common.ui.fsm.UIFSMState
import kotlinx.serialization.Serializable
@Serializable
data class AuthUIFSMState(
override val from: UIFSMState?,
override val context: String = "main"
) : UIFSMState {
companion object {
val default = AuthUIFSMState(null)
}
}

@ -1,7 +1,7 @@
package dev.inmo.postssystem.features.auth.client.ui package dev.inmo.postssystem.features.auth.client.ui
import dev.inmo.postssystem.features.auth.common.AuthCreds import dev.inmo.postssystem.features.auth.common.AuthCreds
import dev.inmo.postssystem.features.common.common.UIModel import dev.inmo.postssystem.features.common.common.ui.UIModel
interface AuthUIModel : UIModel<AuthUIState> { interface AuthUIModel : UIModel<AuthUIState> {
suspend fun initAuth(serverUrl: String, creds: AuthCreds) suspend fun initAuth(serverUrl: String, creds: AuthCreds)

@ -1,7 +1,7 @@
package dev.inmo.postssystem.features.auth.client.ui package dev.inmo.postssystem.features.auth.client.ui
import dev.inmo.postssystem.features.auth.common.AuthCreds import dev.inmo.postssystem.features.auth.common.AuthCreds
import dev.inmo.postssystem.features.common.common.UIViewModel import dev.inmo.postssystem.features.common.common.ui.UIViewModel
import dev.inmo.postssystem.features.users.common.Username import dev.inmo.postssystem.features.users.common.Username
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow

@ -1,9 +1,8 @@
package dev.inmo.postssystem.client.ui package dev.inmo.postssystem.features.auth.client.ui
import dev.inmo.postssystem.client.settings.auth.AuthSettings import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
import dev.inmo.postssystem.features.auth.client.ui.*
import dev.inmo.postssystem.features.auth.common.AuthCreds import dev.inmo.postssystem.features.auth.common.AuthCreds
import dev.inmo.postssystem.features.common.common.AbstractUIModel import dev.inmo.postssystem.features.common.common.ui.AbstractUIModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

@ -1,28 +1,38 @@
package dev.inmo.postssystem.client.fsm.ui package dev.inmo.postssystem.features.auth.client
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import dev.inmo.jsuikit.elements.* import dev.inmo.jsuikit.elements.*
import dev.inmo.jsuikit.modifiers.* import dev.inmo.jsuikit.modifiers.*
import dev.inmo.jsuikit.utils.Attrs import dev.inmo.jsuikit.utils.Attrs
import dev.inmo.postssystem.client.ui.fsm.* import dev.inmo.micro_utils.coroutines.compose.renderComposableAndLinkToContextAndRoot
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 dev.inmo.postssystem.features.common.common.*
import dev.inmo.postssystem.features.common.common.ui.JSView
import dev.inmo.postssystem.features.common.common.ui.fsm.*
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.dom.* import kotlinx.dom.*
import org.jetbrains.compose.web.attributes.InputType import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.dom.*
import org.jetbrains.compose.web.dom.Text import org.jetbrains.compose.web.dom.Text
import org.w3c.dom.* import org.w3c.dom.*
val loader = DefaultModuleLoader {
factory { AuthView(get(), get(DefaultQualifiers.UIScopeQualifier), getAllDistinct()) }
singleWithRandomQualifier<UIFSMHandler.Registrator> {
UIFSMHandler.Registrator {
strictlyOn(get<AuthView>())
}
}
}
class AuthView( class AuthView(
private val viewModel: AuthUIViewModel, private val viewModel: AuthUIViewModel,
private val uiScope: CoroutineScope private val uiScope: CoroutineScope,
) : JSView<AuthUIFSMState>() { defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
) : JSView<AuthUIFSMState>(defaultExceptionsHandlers) {
override suspend fun StatesMachine<in UIFSMState>.safeHandleState( override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
htmlElement: HTMLElement, htmlElement: HTMLElement,
@ -36,7 +46,7 @@ class AuthView(
val errorText = mutableStateOf<String?>(null) val errorText = mutableStateOf<String?>(null)
val root = htmlElement.appendElement("div") {} val root = htmlElement.appendElement("div") {}
val composition = renderComposableAndLinkToContext(root) { val composition = renderComposableAndLinkToContextAndRoot(root) {
val authBtnDisabled = usernameState.value.isBlank() || passwordState.value.isBlank() val authBtnDisabled = usernameState.value.isBlank() || passwordState.value.isBlank()
Flex( Flex(

@ -16,6 +16,11 @@ kotlin {
api "io.ktor:ktor-client-auth:$ktor_version" api "io.ktor:ktor-client-auth:$ktor_version"
api "io.ktor:ktor-client-logging:$ktor_version" api "io.ktor:ktor-client-logging:$ktor_version"
api libs.microutils.common.compose
api libs.microutils.coroutines.compose
api libs.microutils.fsm.common
api libs.microutils.fsm.repos.common
api compose.runtime api compose.runtime
} }
} }

@ -1,6 +1,6 @@
package dev.inmo.postssystem.features.common.common package dev.inmo.postssystem.features.common.common
object AdditionalModules { class AdditionalModules {
private val additionalModules = mutableListOf<ModuleLoader>() private val additionalModules = mutableListOf<ModuleLoader>()
val modules: List<ModuleLoader> val modules: List<ModuleLoader>
get() = additionalModules.toList() get() = additionalModules.toList()
@ -8,4 +8,8 @@ object AdditionalModules {
fun addModule(moduleLoader: ModuleLoader): Boolean { fun addModule(moduleLoader: ModuleLoader): Boolean {
return additionalModules.add(moduleLoader) return additionalModules.add(moduleLoader)
} }
companion object {
val Default = AdditionalModules()
}
} }

@ -1,4 +1,4 @@
package dev.inmo.postssystem.client package dev.inmo.postssystem.features.common.common
import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging
import dev.inmo.micro_utils.repos.KeyValueRepo import dev.inmo.micro_utils.repos.KeyValueRepo

@ -0,0 +1,67 @@
package dev.inmo.postssystem.features.common.common
import dev.inmo.micro_utils.coroutines.LinkedSupervisorScope
import dev.inmo.micro_utils.fsm.common.StatesMachine
import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.postssystem.features.common.common.ui.fsm.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.serialization.BinaryFormat
import kotlinx.serialization.StringFormat
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import org.koin.core.Koin
import org.koin.core.context.startKoin
import org.koin.core.scope.Scope
import org.koin.dsl.module
/**
* Entrypoint for getting [org.koin.core.Koin] DI for the client
*
* @param repoFactory Factory for creating of [DefaultStatesManagerRepo] for [dev.inmo.postssystem.client.ui.fsm.UIFSM]
*/
fun baseKoin(
defaultScope: CoroutineScope,
settingsFactory: Scope.() -> KeyValueRepo<String, Any>,
repoFactory: Scope.() -> DefaultStatesManagerRepo<UIFSMState>,
handlersSetter: Pair<Scope, FSMBuilder<UIFSMState>>.() -> Unit
): Koin = startKoin {
modules(
module {
single {
Json {
ignoreUnknownKeys = true
serializersModule = SerializersModule { get<SerializersModuleConfigurator>().apply { invoke() } }
}
}
single<StringFormat> { get<Json>() }
single {
Cbor { serializersModule = SerializersModule { get<SerializersModuleConfigurator>().apply { invoke() } } }
}
single<BinaryFormat> { get<Cbor>() }
single(DefaultQualifiers.SettingsQualifier) { settingsFactory() }
single { DBDropper(get(DefaultQualifiers.SettingsQualifier)) }
single(DefaultQualifiers.FSMHandlersBuilderQualifier) { handlersSetter }
single { repoFactory() }
single { defaultScope }
single(DefaultQualifiers.UIScopeQualifier) { get<CoroutineScope>().LinkedSupervisorScope(Dispatchers.Main) }
single<StatesMachine<UIFSMState>>(UIFSMQualifier) { UIFSM(get()) { (this@single to this@UIFSM).apply(get(
DefaultQualifiers.FSMHandlersBuilderQualifier
)) } }
AdditionalModules.Default.modules.forEach {
it.apply { load() }
}
}
)
}.koin.apply {
loadModules(
listOf(
module { single<Koin> { this@apply } }
)
)
}

@ -0,0 +1,10 @@
package dev.inmo.postssystem.features.common.common
import org.koin.core.qualifier.StringQualifier
object DefaultQualifiers {
val SettingsQualifier = StringQualifier("Settings")
val FSMHandlersBuilderQualifier = StringQualifier("FSMHandlersBuilder")
val UIScopeQualifier = StringQualifier("CoroutineScopeUI")
val UIFSMQualifier = StringQualifier("FSM")
}

@ -2,6 +2,18 @@ package dev.inmo.postssystem.features.common.common
import org.koin.core.module.Module import org.koin.core.module.Module
fun interface ModuleLoader { interface ModuleLoader {
fun Module.load() fun Module.load()
class ByCallback(private val loadingBlock: Module.() -> Unit) : ModuleLoader {
override fun Module.load() {
loadingBlock()
}
}
}
fun DefaultModuleLoader(loadingBlock: Module.() -> Unit): ModuleLoader.ByCallback {
val newModuleLoader = ModuleLoader.ByCallback(loadingBlock)
AdditionalModules.Default.addModule(newModuleLoader)
return newModuleLoader
} }

@ -1,4 +0,0 @@
package dev.inmo.postssystem.features.common.common
interface UIView {
}

@ -1,4 +1,4 @@
package dev.inmo.postssystem.features.common.common package dev.inmo.postssystem.features.common.common.ui
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow

@ -1,4 +1,4 @@
package dev.inmo.postssystem.features.common.common package dev.inmo.postssystem.features.common.common.ui
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow

@ -0,0 +1,4 @@
package dev.inmo.postssystem.features.common.common.ui
interface UIView {
}

@ -1,4 +1,4 @@
package dev.inmo.postssystem.features.common.common package dev.inmo.postssystem.features.common.common.ui
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -7,9 +7,7 @@ interface UIViewModel<StateType> {
val currentState: StateFlow<StateType> val currentState: StateFlow<StateType>
} }
abstract class AbstractUIViewModel<StateType> : UIViewModel<StateType> { abstract class AbstractUIViewModel<StateType>(initState: StateType) : UIViewModel<StateType> {
protected val _currentState = DefaultMVVMStateFlow(initState()) protected val _currentState = DefaultMVVMStateFlow(initState)
override val currentState: StateFlow<StateType> = _currentState.asStateFlow() override val currentState: StateFlow<StateType> = _currentState.asStateFlow()
abstract fun initState(): StateType
} }

@ -1,4 +1,4 @@
package dev.inmo.postssystem.client.ui.fsm package dev.inmo.postssystem.features.common.common.ui.fsm
import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder
import dev.inmo.micro_utils.fsm.common.dsl.buildFSM import dev.inmo.micro_utils.fsm.common.dsl.buildFSM

@ -0,0 +1,7 @@
package dev.inmo.postssystem.features.common.common.ui.fsm
import dev.inmo.micro_utils.common.Optional
fun interface UIFSMExceptionHandler {
suspend fun handle(currentState: UIFSMState, exception: Throwable): Optional<UIFSMState?>
}

@ -0,0 +1,34 @@
package dev.inmo.postssystem.features.common.common.ui.fsm
import dev.inmo.micro_utils.common.onPresented
import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.micro_utils.fsm.common.dsl.FSMBuilder
import org.koin.core.scope.Scope
interface UIFSMHandler<T : UIFSMState> : StatesHandler<T, UIFSMState> {
fun interface Registrator {
fun FSMBuilder<UIFSMState>.include()
}
val defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
suspend fun StatesMachine<in UIFSMState>.safeHandleState(state: T): UIFSMState?
override suspend fun StatesMachine<in UIFSMState>.handleState(state: T): UIFSMState? {
return runCatching {
safeHandleState(state).also(::println)
}.getOrElse {
errorToNextStep(state, it) ?.let { return it } ?: throw it
}.also(::println)
}
suspend fun errorToNextStep(
currentState: T,
e: Throwable
): UIFSMState? {
defaultExceptionsHandlers.forEach {
it.handle(currentState, e).onPresented { state ->
return state
}
}
return null
}
}

@ -0,0 +1,17 @@
package dev.inmo.postssystem.features.common.common.ui.fsm
import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
import kotlinx.serialization.*
@Serializable(UIFSMStateSerializer::class)
interface UIFSMState : State {
val from: UIFSMState?
get() = null
override val context: String
get() = "main"
}
object UIFSMStateSerializer : KSerializer<UIFSMState>, TypedSerializer<UIFSMState>(
UIFSMState::class
)

@ -1,12 +1,13 @@
package dev.inmo.postssystem.client.fsm.ui package dev.inmo.postssystem.features.common.common.ui
import dev.inmo.postssystem.client.ui.fsm.UIFSMHandler
import dev.inmo.postssystem.client.ui.fsm.UIFSMState
import dev.inmo.micro_utils.fsm.common.StatesMachine import dev.inmo.micro_utils.fsm.common.StatesMachine
import dev.inmo.postssystem.features.common.common.ui.fsm.*
import kotlinx.browser.document import kotlinx.browser.document
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
abstract class JSView<T : UIFSMState> : UIFSMHandler<T> { abstract class JSView<T : UIFSMState>(
override val defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
) : UIFSMHandler<T> {
open suspend fun StatesMachine<in UIFSMState>.safeHandleState( open suspend fun StatesMachine<in UIFSMState>.safeHandleState(
htmlElement: HTMLElement, htmlElement: HTMLElement,
state: T state: T

@ -11,7 +11,7 @@ import org.koin.core.module.Module
object LoadingClientModule : ModuleLoader { object LoadingClientModule : ModuleLoader {
init { init {
AdditionalModules.addModule(this) AdditionalModules.Default.addModule(this)
} }
override fun Module.load() { override fun Module.load() {

@ -0,0 +1,11 @@
package dev.inmo.postssystem.features.content.client
import dev.inmo.postssystem.features.common.common.*
import dev.inmo.postssystem.features.content.common.ContentSerializersModuleConfigurator
import dev.inmo.postssystem.features.content.common.OtherContentSerializerModuleConfigurator
val loader = DefaultModuleLoader {
singleWithRandomQualifier<ContentSerializersModuleConfigurator.Element> { OtherContentSerializerModuleConfigurator }
singleWithRandomQualifier<SerializersModuleConfigurator.Element> { ContentSerializersModuleConfigurator(getAll()) }
single { SerializersModuleConfigurator(getAll()) }
}

@ -12,7 +12,7 @@ import org.koin.core.module.Module
object LoadingClientModule : ModuleLoader { object LoadingClientModule : ModuleLoader {
init { init {
AdditionalModules.addModule(this) AdditionalModules.Default.addModule(this)
} }
override fun Module.load() { override fun Module.load() {

@ -12,6 +12,7 @@ kotlin {
dependencies { dependencies {
api project(":postssystem.features.files.common") api project(":postssystem.features.files.common")
api project(":postssystem.features.common.client") api project(":postssystem.features.common.client")
api project(":postssystem.features.auth.client")
} }
} }
} }

@ -0,0 +1,9 @@
package dev.inmo.postssystem.features.files.client
import dev.inmo.postssystem.features.auth.client.AuthorizedModuleLoader
import dev.inmo.postssystem.features.auth.client.AuthorizedQualifiers
import dev.inmo.postssystem.features.files.common.storage.ReadFilesStorage
val loader = AuthorizedModuleLoader {
single<ReadFilesStorage> { ClientReadFilesStorage(get(AuthorizedQualifiers.ServerUrlQualifier), get(), get()) }
}

@ -12,6 +12,7 @@ kotlin {
dependencies { dependencies {
api project(":postssystem.features.roles.common") api project(":postssystem.features.roles.common")
api project(":postssystem.features.common.client") api project(":postssystem.features.common.client")
api project(":postssystem.features.auth.client")
} }
} }
} }

@ -0,0 +1,10 @@
package dev.inmo.postssystem.features.roles.client
import dev.inmo.postssystem.features.auth.client.AuthorizedModuleLoader
import dev.inmo.postssystem.features.auth.client.AuthorizedQualifiers
import dev.inmo.postssystem.features.roles.common.Role
import dev.inmo.postssystem.features.roles.common.RolesStorage
val loader = AuthorizedModuleLoader {
single<RolesStorage<Role>> { ClientRolesStorage(get(AuthorizedQualifiers.ServerUrlQualifier), get(), Role.serializer()) }
}

@ -5,6 +5,8 @@ import dev.inmo.postssystem.features.roles.common.RoleSerializer
import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer import dev.inmo.micro_utils.serialization.typed_serializer.TypedSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
private val justForLoading = RolesManagerRoleSerializer
@Serializable(RolesManagerRoleSerializer::class) @Serializable(RolesManagerRoleSerializer::class)
interface RolesManagerRole : Role { interface RolesManagerRole : Role {
companion object { companion object {

@ -12,6 +12,7 @@ kotlin {
dependencies { dependencies {
api project(":postssystem.features.users.common") api project(":postssystem.features.users.common")
api project(":postssystem.features.common.client") api project(":postssystem.features.common.client")
api project(":postssystem.features.auth.client")
} }
} }
} }

@ -0,0 +1,9 @@
package dev.inmo.postssystem.features.users.client
import dev.inmo.postssystem.features.auth.client.AuthorizedModuleLoader
import dev.inmo.postssystem.features.auth.client.AuthorizedQualifiers
import dev.inmo.postssystem.features.users.common.ReadUsersStorage
val loader = AuthorizedModuleLoader {
single<ReadUsersStorage> { UsersStorageKtorClient(get(AuthorizedQualifiers.ServerUrlQualifier), get()) }
}

@ -5,7 +5,7 @@ kotlin-serialization = "1.3.2"
jsuikit = "0.0.45" jsuikit = "0.0.45"
compose = "1.1.1" compose = "1.1.1"
microutils = "0.9.16" microutils = "0.9.16"
tgbotapi = "0.38.7" tgbotapi = "0.38.9"
ktor = "1.6.8" ktor = "1.6.8"
klock = "2.6.3" klock = "2.6.3"
exposed = "0.37.3" exposed = "0.37.3"
@ -44,6 +44,7 @@ microutils-repos-ktor-server = { module = "dev.inmo:micro_utils.repos.ktor.serve
microutils-repos-exposed = { module = "dev.inmo:micro_utils.repos.exposed", 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-mimetypes = { module = "dev.inmo:micro_utils.mime_types", version.ref = "microutils" }
microutils-coroutines = { module = "dev.inmo:micro_utils.coroutines", version.ref = "microutils" } microutils-coroutines = { module = "dev.inmo:micro_utils.coroutines", version.ref = "microutils" }
microutils-coroutines-compose = { module = "dev.inmo:micro_utils.coroutines.compose", version.ref = "microutils" }
microutils-ktor-server = { module = "dev.inmo:micro_utils.ktor.server", version.ref = "microutils" } microutils-ktor-server = { module = "dev.inmo:micro_utils.ktor.server", version.ref = "microutils" }
microutils-serialization-typedserializer = { module = "dev.inmo:micro_utils.serialization.typed_serializer", version.ref = "microutils" } microutils-serialization-typedserializer = { module = "dev.inmo:micro_utils.serialization.typed_serializer", version.ref = "microutils" }

@ -11,6 +11,7 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
api project(":postssystem.publicators.simple.common") api project(":postssystem.publicators.simple.common")
api project(":postssystem.features.auth.client")
api project(":postssystem.features.publication.client") api project(":postssystem.features.publication.client")
} }
} }

@ -0,0 +1,8 @@
package dev.inmo.postssystem.publicators.simple.client
import dev.inmo.postssystem.features.auth.client.AuthorizedModuleLoader
import dev.inmo.postssystem.features.auth.client.AuthorizedQualifiers
val loader = AuthorizedModuleLoader {
single<SimplePublicatorService> { SimplePublicatorServiceClient(get(AuthorizedQualifiers.ServerUrlQualifier), get()) }
}

@ -13,6 +13,8 @@ kotlin {
api project(":postssystem.features.common.client") api project(":postssystem.features.common.client")
api project(":postssystem.services.posts.common") api project(":postssystem.services.posts.common")
api project(":postssystem.features.posts.client") api project(":postssystem.features.posts.client")
api project(":postssystem.features.auth.client")
api project(":postssystem.features.content.client")
api project(":postssystem.publicators.simple.client") api project(":postssystem.publicators.simple.client")
} }
} }

@ -0,0 +1,20 @@
package dev.inmo.postssystem.services.posts.client
import dev.inmo.postssystem.features.auth.client.AuthorizedModuleLoader
import dev.inmo.postssystem.features.auth.client.AuthorizedQualifiers
import dev.inmo.postssystem.features.common.common.DefaultQualifiers
import dev.inmo.postssystem.features.common.common.getAllDistinct
import dev.inmo.postssystem.services.posts.client.ui.create.*
import dev.inmo.postssystem.services.posts.common.*
import org.koin.core.qualifier.Qualifier
import org.koin.dsl.binds
val loader = AuthorizedModuleLoader {
single<PostsService> { ClientPostsService(get(AuthorizedQualifiers.ServerUrlQualifier), get()) } binds arrayOf(
ReadPostsService::class,
WritePostsService::class
)
factory<PostCreateUIModel> { DefaultPostCreateUIModel(get(), get()) }
factory { PostCreateUIViewModel(get()) }
}

@ -1,6 +1,6 @@
package dev.inmo.postssystem.services.posts.client.ui.create package dev.inmo.postssystem.services.posts.client.ui.create
import dev.inmo.postssystem.features.common.common.AbstractUIModel import dev.inmo.postssystem.features.common.common.ui.AbstractUIModel
import dev.inmo.postssystem.features.content.common.Content import dev.inmo.postssystem.features.content.common.Content
import dev.inmo.postssystem.publicators.simple.client.SimplePublicatorService import dev.inmo.postssystem.publicators.simple.client.SimplePublicatorService
import dev.inmo.postssystem.services.posts.common.FullNewPost import dev.inmo.postssystem.services.posts.common.FullNewPost

@ -1,7 +1,7 @@
package dev.inmo.postssystem.services.posts.client.ui.create package dev.inmo.postssystem.services.posts.client.ui.create
import dev.inmo.postssystem.features.common.common.UIModel import dev.inmo.postssystem.features.common.common.ui.UIModel
import dev.inmo.postssystem.features.common.common.UIViewModel import dev.inmo.postssystem.features.common.common.ui.UIViewModel
import dev.inmo.postssystem.features.content.common.Content import dev.inmo.postssystem.features.content.common.Content
interface PostCreateUIModel : UIModel<PostCreateUIState>, UIViewModel<PostCreateUIState> { interface PostCreateUIModel : UIModel<PostCreateUIState>, UIViewModel<PostCreateUIState> {

@ -1,6 +1,6 @@
package dev.inmo.postssystem.services.posts.client.ui.create package dev.inmo.postssystem.services.posts.client.ui.create
import dev.inmo.postssystem.features.common.common.* import dev.inmo.postssystem.features.common.common.ui.UIViewModel
import dev.inmo.postssystem.features.content.common.Content import dev.inmo.postssystem.features.content.common.Content
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow

@ -1,28 +1,48 @@
package dev.inmo.postssystem.client.fsm.ui package dev.inmo.postssystem.services.posts.client
import androidx.compose.runtime.* import androidx.compose.runtime.*
import dev.inmo.jsuikit.elements.* import dev.inmo.jsuikit.elements.*
import dev.inmo.jsuikit.modifiers.* import dev.inmo.jsuikit.modifiers.*
import dev.inmo.jsuikit.utils.Attrs import dev.inmo.jsuikit.utils.Attrs
import dev.inmo.micro_utils.coroutines.compose.renderComposableAndLinkToContextAndRoot
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.fsm.common.StatesMachine import dev.inmo.micro_utils.fsm.common.StatesMachine
import dev.inmo.postssystem.client.ui.fsm.CreatePostUIFSMState import dev.inmo.postssystem.features.auth.client.registerAfterAuthHandler
import dev.inmo.postssystem.client.ui.fsm.UIFSMState import dev.inmo.postssystem.features.common.common.*
import dev.inmo.postssystem.client.utils.renderComposableAndLinkToContext import dev.inmo.postssystem.features.common.common.ui.JSView
import dev.inmo.postssystem.features.common.common.ui.fsm.*
import dev.inmo.postssystem.features.content.client.ContentClientProvider import dev.inmo.postssystem.features.content.client.ContentClientProvider
import dev.inmo.postssystem.features.content.common.Content import dev.inmo.postssystem.features.content.common.Content
import dev.inmo.postssystem.services.posts.client.ui.create.PostCreateUIViewModel import dev.inmo.postssystem.services.posts.client.ui.create.PostCreateUIViewModel
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.Serializable
import org.jetbrains.compose.web.dom.Div import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.Text import org.jetbrains.compose.web.dom.Text
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
val jsLoader = DefaultModuleLoader {
factory { PostCreateView(get(), getAllDistinct(), get(DefaultQualifiers.UIScopeQualifier), getAllDistinct()) }
singleWithRandomQualifier<UIFSMHandler.Registrator> {
UIFSMHandler.Registrator {
registerAfterAuthHandler(getKoin(), PostCreateView::class)
}
}
}
@Serializable
data class CreatePostUIFSMState(
override val from: UIFSMState? = null,
override val context: String = "main"
) : UIFSMState
class PostCreateView( class PostCreateView(
private val createPostCreateUIModel: PostCreateUIViewModel, private val createPostCreateUIModel: PostCreateUIViewModel,
private val contentClientProviders: List<ContentClientProvider>, private val contentClientProviders: List<ContentClientProvider>,
private val uiScope: CoroutineScope private val uiScope: CoroutineScope,
) : JSView<CreatePostUIFSMState>() { defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
) : JSView<CreatePostUIFSMState>(defaultExceptionsHandlers) {
override suspend fun StatesMachine<in UIFSMState>.safeHandleState( override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
htmlElement: HTMLElement, htmlElement: HTMLElement,
state: CreatePostUIFSMState state: CreatePostUIFSMState
@ -31,7 +51,7 @@ class PostCreateView(
val contentProvidersList = mutableStateListOf<Pair<ContentClientProvider, MutableState<Content?>>>() val contentProvidersList = mutableStateListOf<Pair<ContentClientProvider, MutableState<Content?>>>()
renderComposableAndLinkToContext(htmlElement) { renderComposableAndLinkToContextAndRoot(htmlElement) {
Flex( Flex(
UIKitFlex.Alignment.Horizontal.Center UIKitFlex.Alignment.Horizontal.Center
) { ) {

@ -9,7 +9,7 @@ val defaultTelegramTargetModuleLoader = TelegramTargetModuleLoader
object TelegramTargetModuleLoader : ModuleLoader { object TelegramTargetModuleLoader : ModuleLoader {
init { init {
AdditionalModules.addModule(this) AdditionalModules.Default.addModule(this)
} }
override fun Module.load() { override fun Module.load() {