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

@ -2,6 +2,7 @@ plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
alias(libs.plugins.compose)
}
apply from: "$mppProjectWithSerializationPresetPath"
@ -11,8 +12,14 @@ kotlin {
commonMain {
dependencies {
api project(":postssystem.features.common.client")
api project(":postssystem.features.roles.client")
api project(":postssystem.features.status.client")
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()) }
}

@ -0,0 +1,18 @@
package dev.inmo.postssystem.features.auth.client.settings
import dev.inmo.postssystem.features.auth.client.ui.AuthUIError
import dev.inmo.postssystem.features.auth.common.AuthCreds
import dev.inmo.postssystem.features.roles.common.Role
import dev.inmo.postssystem.features.users.common.User
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.StateFlow
import org.koin.core.module.Module
interface AuthSettings {
val authorizedDIModule: StateFlow<Module?>
val user: StateFlow<User?>
val userRoles: StateFlow<List<Role>>
val loadingJob: Job
suspend fun auth(serverUrl: String, creds: AuthCreds): AuthUIError?
}

@ -0,0 +1,118 @@
package dev.inmo.postssystem.features.auth.client.settings
import dev.inmo.postssystem.features.auth.client.AuthUnavailableException
import dev.inmo.postssystem.features.auth.client.ui.*
import dev.inmo.postssystem.features.auth.common.*
import dev.inmo.postssystem.features.roles.common.Role
import dev.inmo.postssystem.features.roles.common.RolesStorage
import dev.inmo.postssystem.features.status.client.StatusFeatureClient
import dev.inmo.postssystem.features.users.common.User
import dev.inmo.micro_utils.common.Either
import dev.inmo.micro_utils.common.either
import dev.inmo.micro_utils.coroutines.plus
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
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.flow.*
import org.koin.core.Koin
import org.koin.core.module.Module
data class DefaultAuthSettings(
private val repo: KeyValueRepo<String, Any>,
private val scope: CoroutineScope,
private val koin: Koin,
private val dbDropper: DBDropper
) : AuthSettings {
private val _authorizedDIModule = MutableStateFlow<Module?>(null)
override val authorizedDIModule: StateFlow<Module?> = _authorizedDIModule.asStateFlow()
private val _user = MutableStateFlow<User?>(null)
override val user: StateFlow<User?> = _user.asStateFlow()
private val _userRoles = MutableStateFlow<List<Role>>(emptyList())
override val userRoles: StateFlow<List<Role>> = _userRoles.asStateFlow()
private suspend fun getCurrentServerURL() = repo.get(SERVER_URL_FIELD) as? String
private suspend fun getCurrentUsername() = repo.get(USERNAME_FIELD) as? String
private suspend fun getCurrentToken() = repo.get(TOKEN_FIELD) as? AuthTokenInfo
override val loadingJob: Job = scope.launch {
val serverUrl = getCurrentServerURL() ?: return@launch
val token = getCurrentToken() ?: return@launch
updateModule(serverUrl, token.either())
}
val rolesUpdatingJob = (user + authorizedDIModule).subscribeSafelyWithoutExceptions(scope) {
val user = user.value
if (user == null || authorizedDIModule.value == null) {
_userRoles.value = emptyList()
} else {
_userRoles.value = koin.get<RolesStorage<Role>>().getRoles(user.id)
}
println(user)
println(userRoles.value)
}
override suspend fun auth(serverUrl: String, creds: AuthCreds): AuthUIError? {
return runCatching {
if (getCurrentServerURL() != serverUrl || getCurrentUsername() != creds.username.string) {
dbDropper()
}
repo.set(SERVER_URL_FIELD, serverUrl)
repo.set(USERNAME_FIELD, creds.username.string)
repo.unset(TOKEN_FIELD)
updateModule(serverUrl, creds.either())
}.onFailure {
it.printStackTrace()
}.getOrThrow()
}
private suspend fun updateModule(
serverUrl: String,
initialAuthKey: Either<AuthKey, AuthTokenInfo>,
): AuthUIError? {
val currentModule = authorizedDIModule.value
val newModule = createAuthorizedFeaturesDIModule(
serverUrl,
initialAuthKey,
{
repo.set(TOKEN_FIELD, it)
},
{
_user.value = it
}
) {
repo.unset(SERVER_URL_FIELD, USERNAME_FIELD, TOKEN_FIELD)
_authorizedDIModule.value = null
_user.value = null
throw AuthUnavailableException
}
currentModule ?.let { koin.unloadModules(listOf(currentModule)) }
koin.loadModules(listOf(newModule))
val statusFeature = koin.get<StatusFeatureClient>()
val serverAvailable = statusFeature.checkServerStatus()
val authCorrect = serverAvailable && runCatching {
statusFeature.checkServerStatusWithAuth()
}.getOrElse { false }
if (!serverAvailable && !authCorrect) {
koin.unloadModules(listOf(newModule))
currentModule ?.let { koin.loadModules(listOf(currentModule)) }
}
return when {
!serverAvailable -> ServerUnavailableAuthUIError
!authCorrect -> AuthIncorrectAuthUIError
else -> {
_authorizedDIModule.value = newModule
null
}
}
}
companion object {
private const val SERVER_URL_FIELD = "AuthServerURL"
private const val USERNAME_FIELD = "AuthUsername"
private const val TOKEN_FIELD = "AuthToken"
}
}

@ -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
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> {
suspend fun initAuth(serverUrl: String, creds: AuthCreds)

@ -1,7 +1,7 @@
package dev.inmo.postssystem.features.auth.client.ui
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 kotlinx.coroutines.flow.StateFlow

@ -0,0 +1,35 @@
package dev.inmo.postssystem.features.auth.client.ui
import dev.inmo.postssystem.features.auth.client.settings.AuthSettings
import dev.inmo.postssystem.features.auth.common.AuthCreds
import dev.inmo.postssystem.features.common.common.ui.AbstractUIModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
class DefaultAuthUIModel(
private val scope: CoroutineScope,
private val authSettings: AuthSettings
) : AbstractUIModel<AuthUIState>(LoadingAuthUIState), AuthUIModel {
init {
scope.launch {
_currentState.value = LoadingAuthUIState
authSettings.loadingJob.join()
if (authSettings.authorizedDIModule.value == null) {
_currentState.value = DefaultInitAuthUIState
} else {
_currentState.value = AuthorizedAuthUIState
}
}
}
override suspend fun initAuth(serverUrl: String, creds: AuthCreds) {
_currentState.value = LoadingAuthUIState
val authError = authSettings.auth(serverUrl, creds)
if (authError == null) {
_currentState.value = AuthorizedAuthUIState
} else {
_currentState.value = InitAuthUIState(authError)
}
}
}

@ -0,0 +1,119 @@
package dev.inmo.postssystem.features.auth.client
import androidx.compose.runtime.mutableStateOf
import dev.inmo.jsuikit.elements.*
import dev.inmo.jsuikit.modifiers.*
import dev.inmo.jsuikit.utils.Attrs
import dev.inmo.micro_utils.coroutines.compose.renderComposableAndLinkToContextAndRoot
import dev.inmo.postssystem.features.auth.client.ui.*
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.fsm.common.StatesMachine
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.coroutines.*
import kotlinx.dom.*
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.dom.Text
import org.w3c.dom.*
val loader = DefaultModuleLoader {
factory { AuthView(get(), get(DefaultQualifiers.UIScopeQualifier), getAllDistinct()) }
singleWithRandomQualifier<UIFSMHandler.Registrator> {
UIFSMHandler.Registrator {
strictlyOn(get<AuthView>())
}
}
}
class AuthView(
private val viewModel: AuthUIViewModel,
private val uiScope: CoroutineScope,
defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
) : JSView<AuthUIFSMState>(defaultExceptionsHandlers) {
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
htmlElement: HTMLElement,
state: AuthUIFSMState
): UIFSMState? {
val completion = CompletableDeferred<UIFSMState?>()
val usernameState = mutableStateOf("")
val passwordState = mutableStateOf("")
val disabled = mutableStateOf(true)
val errorText = mutableStateOf<String?>(null)
val root = htmlElement.appendElement("div") {}
val composition = renderComposableAndLinkToContextAndRoot(root) {
val authBtnDisabled = usernameState.value.isBlank() || passwordState.value.isBlank()
Flex(
UIKitFlex.Alignment.Horizontal.Center
) {
Card(
Attrs(UIKitText.Alignment.Horizontal.Center),
bodyAttrs = Attrs(UIKitWidth.Fixed.Medium),
) {
CardTitle { Text("Log in") }
if (errorText.value != null) {
CardBadge(Attrs(UIKitLabel.Error)) {
Text(errorText.value.toString())
}
}
TextField(
InputType.Text,
usernameState,
disabled,
"Username",
)
TextField(
InputType.Password,
passwordState,
disabled,
"Password"
)
DefaultButton("Authorise", UIKitButton.Type.Primary, UIKitMargin.Small, disabled = authBtnDisabled) {
it.nativeEvent.preventDefault()
val serverUrl = document.location ?.run { "$hostname:$port" }
if (serverUrl != null) {
uiScope.launchSafelyWithoutExceptions { viewModel.initAuth(serverUrl, usernameState.value, passwordState.value) }
}
}
}
}
}
val viewJob = viewModel.currentState.subscribeSafelyWithoutExceptions(uiScope) {
when (it) {
is InitAuthUIState -> {
disabled.value = false
errorText.value = when (it.showError) {
ServerUnavailableAuthUIError -> "Server unavailable"
AuthIncorrectAuthUIError -> {
passwordState.value = ""
"Username or password is incorrect"
}
null -> null
}
}
LoadingAuthUIState -> {
disabled.value = true
errorText.value = null
}
AuthorizedAuthUIState -> {
completion.complete(state.from)
}
}
}
return completion.await().also {
composition.dispose()
viewJob.cancel()
}
}
}

@ -16,6 +16,11 @@ kotlin {
api "io.ktor:ktor-client-auth:$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
}
}

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

@ -0,0 +1,12 @@
package dev.inmo.postssystem.features.common.common
import dev.inmo.micro_utils.pagination.utils.getAllByWithNextPaging
import dev.inmo.micro_utils.repos.KeyValueRepo
class DBDropper(
private val repo: KeyValueRepo<String, Any>
) {
suspend operator fun invoke() {
repo.unset(repo.getAllByWithNextPaging { keys(it) })
}
}

@ -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
fun interface ModuleLoader {
interface ModuleLoader {
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

@ -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.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.asStateFlow
@ -7,9 +7,7 @@ interface UIViewModel<StateType> {
val currentState: StateFlow<StateType>
}
abstract class AbstractUIViewModel<StateType> : UIViewModel<StateType> {
protected val _currentState = DefaultMVVMStateFlow(initState())
abstract class AbstractUIViewModel<StateType>(initState: StateType) : UIViewModel<StateType> {
protected val _currentState = DefaultMVVMStateFlow(initState)
override val currentState: StateFlow<StateType> = _currentState.asStateFlow()
abstract fun initState(): StateType
}

@ -0,0 +1,17 @@
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.buildFSM
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManager
import dev.inmo.micro_utils.fsm.common.managers.DefaultStatesManagerRepo
import org.koin.core.qualifier.StringQualifier
val UIFSMQualifier = StringQualifier("UIFSM")
fun UIFSM(
repo: DefaultStatesManagerRepo<UIFSMState>,
handlersSetter: FSMBuilder<UIFSMState>.() -> Unit
) = buildFSM<UIFSMState> {
statesManager = DefaultStatesManager(repo)
handlersSetter()
}

@ -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
)

@ -0,0 +1,19 @@
package dev.inmo.postssystem.features.common.common.ui
import dev.inmo.micro_utils.fsm.common.StatesMachine
import dev.inmo.postssystem.features.common.common.ui.fsm.*
import kotlinx.browser.document
import org.w3c.dom.HTMLElement
abstract class JSView<T : UIFSMState>(
override val defaultExceptionsHandlers: Iterable<UIFSMExceptionHandler>
) : UIFSMHandler<T> {
open suspend fun StatesMachine<in UIFSMState>.safeHandleState(
htmlElement: HTMLElement,
state: T
): UIFSMState? = null
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(state: T): UIFSMState? {
return safeHandleState(document.getElementById(state.context) as? HTMLElement ?: return null, state)
}
}

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

@ -12,6 +12,7 @@ kotlin {
dependencies {
api project(":postssystem.features.files.common")
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 {
api project(":postssystem.features.roles.common")
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 kotlinx.serialization.Serializable
private val justForLoading = RolesManagerRoleSerializer
@Serializable(RolesManagerRoleSerializer::class)
interface RolesManagerRole : Role {
companion object {

@ -12,6 +12,7 @@ kotlin {
dependencies {
api project(":postssystem.features.users.common")
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()) }
}