simpliest version of content upload

This commit is contained in:
InsanusMokrassar 2022-03-10 21:35:32 +06:00
parent d15ace3c49
commit a06324568e
12 changed files with 190 additions and 60 deletions

View File

@ -18,7 +18,13 @@ object UIFSMStateSerializer : KSerializer<UIFSMState> by TypedSerializer(
@Serializable @Serializable
data class AuthUIFSMState( data class AuthUIFSMState(
override val from: UIFSMState? = null, override val from: UIFSMState? = CreatePostUIFSMState(),
override val context: String = "main" override val context: String = "main"
) : UIFSMState ) : UIFSMState
val DefaultAuthUIFSMState = AuthUIFSMState() val DefaultAuthUIFSMState = AuthUIFSMState()
@Serializable
data class CreatePostUIFSMState(
override val from: UIFSMState? = null,
override val context: String = "main"
) : UIFSMState

View File

@ -13,8 +13,8 @@ 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.client.settings.auth.AuthSettings
import kotlinx.browser.document import dev.inmo.postssystem.services.posts.client.ui.create.*
import kotlinx.browser.localStorage import kotlinx.browser.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
@ -77,7 +77,7 @@ fun baseKoin(): Koin {
) )
}, },
{ {
JSUIFSMStatesRepo(get(), localStorage) JSUIFSMStatesRepo(window.history)
} }
) { ) {
first.apply { first.apply {
@ -89,6 +89,10 @@ fun baseKoin(): Koin {
factory<AuthUIModel> { DefaultAuthUIModel(get(), get()) } factory<AuthUIModel> { DefaultAuthUIModel(get(), get()) }
factory { AuthUIViewModel(get()) } factory { AuthUIViewModel(get()) }
factory { AuthView(get(), get(UIScopeQualifier)) } factory { AuthView(get(), get(UIScopeQualifier)) }
factory<PostCreateUIModel> { DefaultPostCreateUIModel(get()) }
factory { PostCreateUIViewModel(get()) }
factory { PostCreateView(get(), get(UIScopeQualifier)) }
} }
) )
strictlyOn<AuthUIFSMState>(get<AuthView>()) strictlyOn<AuthUIFSMState>(get<AuthView>())
@ -135,6 +139,7 @@ fun baseKoin(): Koin {
) )
) )
registerHandler(PostCreateView::class)
} }
} }
} }

View File

@ -2,62 +2,78 @@ package dev.inmo.postssystem.client
import dev.inmo.postssystem.client.ui.fsm.* 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 kotlinx.browser.window
import kotlinx.serialization.StringFormat import kotlinx.serialization.StringFormat
import org.w3c.dom.* import org.w3c.dom.*
import org.w3c.dom.url.URL
class JSUIFSMStatesRepo( private fun History.refreshHistory(
private val serialFormat: StringFormat, states: Iterable<UIFSMState>,
private val storage: Storage, ) {
private val initialState: UIFSMState = DefaultAuthUIFSMState val currentUrl = window.location.pathname
) : DefaultStatesManagerRepo<UIFSMState> { val params = states.mapNotNull<UIFSMState, Pair<String, String>> {
private val String.storageKey when (it) {
get() = "${FSMStateSettingsFieldPrefix}$this" is AuthUIFSMState -> null
private val String.UIFSMState is CreatePostUIFSMState -> null
get() = runCatching {
serialFormat.decodeFromString(UIFSMStateSerializer, this)
}.onFailure { it.printStackTrace() }.getOrNull()
init {
if (states().isEmpty()) {
setState(initialState)
} }
} }
pushState(
null,
"Posts System",
"$currentUrl${params.joinToString("&", "?") { "${it.first}=${it.second}" }}"
)
}
private fun setState(state: UIFSMState) { private fun takeStates(initialState: UIFSMState): List<UIFSMState> {
storage[state.context.storageKey] = serialFormat.encodeToString(UIFSMStateSerializer, state) val params = (URL(window.location.href)).searchParams
val additionalStates = listOfNotNull<UIFSMState>()
return additionalStates + listOfNotNull(
if (additionalStates.isEmpty()) {
initialState
} else {
null
}
)
}
class JSUIFSMStatesRepo(
private val history: History,
private val initialState: UIFSMState = DefaultAuthUIFSMState
) : DefaultStatesManagerRepo<UIFSMState> {
private val statesMap = mutableMapOf<String, UIFSMState>()
init {
val states = takeStates(initialState)
states.forEach {
statesMap[it.context] = it
}
} }
override suspend fun getContextState(context: Any): UIFSMState? { override suspend fun getContextState(context: Any): UIFSMState? {
return when (context) { return when (context) {
is String -> storage[context.storageKey] ?.UIFSMState ?: return DefaultAuthUIFSMState is String -> statesMap[context]
else -> null else -> null
} }
} }
override suspend fun contains(context: Any): Boolean = when (context) { override suspend fun contains(context: Any): Boolean = when (context) {
is String -> storage.get(context) ?.UIFSMState != null is String -> statesMap.contains(context)
else -> super.contains(context) else -> super.contains(context)
} }
private fun states(): List<UIFSMState> = storage.iterator().asSequence().mapNotNull { (k, v) -> override suspend fun getStates(): List<UIFSMState> = statesMap.values.toList()
if (k.startsWith(FSMStateSettingsFieldPrefix)) {
v.UIFSMState
} else {
null
}
}.toList()
override suspend fun getStates(): List<UIFSMState> = states()
override suspend fun removeState(state: UIFSMState) { override suspend fun removeState(state: UIFSMState) {
storage.removeItem((state.context as? String) ?.storageKey ?: return) statesMap.remove((state.context as? String) ?: return)
history.refreshHistory(statesMap.values)
} }
override suspend fun set(state: UIFSMState) { override suspend fun set(state: UIFSMState) {
setState(state) console.log(state)
} statesMap[state.context] = state
history.refreshHistory(statesMap.values)
companion object {
private const val FSMStateSettingsFieldPrefix = "UIFSMState_"
} }
} }

View File

@ -0,0 +1,81 @@
package dev.inmo.postssystem.client.fsm.ui
import androidx.compose.runtime.mutableStateOf
import dev.inmo.jsuikit.elements.DefaultButton
import dev.inmo.jsuikit.elements.DropArea
import dev.inmo.jsuikit.utils.InputAttrs
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.filename
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.fsm.common.StatesMachine
import dev.inmo.postssystem.client.ui.fsm.CreatePostUIFSMState
import dev.inmo.postssystem.client.ui.fsm.UIFSMState
import dev.inmo.postssystem.client.utils.renderComposableAndLinkToContext
import dev.inmo.postssystem.features.common.common.*
import dev.inmo.postssystem.features.content.common.BinaryContent
import dev.inmo.postssystem.features.content.text.common.TextContent
import dev.inmo.postssystem.services.posts.client.ui.create.PostCreateUIModel
import dev.inmo.postssystem.services.posts.client.ui.create.PostCreateUIViewModel
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import org.jetbrains.compose.web.dom.*
import org.w3c.dom.HTMLElement
import org.w3c.files.get
class PostCreateView(
private val createPostCreateUIModel: PostCreateUIViewModel,
private val uiScope: CoroutineScope
) : JSView<CreatePostUIFSMState>() {
override suspend fun StatesMachine<in UIFSMState>.safeHandleState(
htmlElement: HTMLElement,
state: CreatePostUIFSMState
): UIFSMState? {
val result = CompletableDeferred<UIFSMState?>()
val textContent = mutableStateOf("")
val fileState = mutableStateOf<MPPFile?>(null)
renderComposableAndLinkToContext(htmlElement) {
Form {
TextArea(textContent.value) {
onInput { textContent.value = it.value }
}
DropArea (
inputAttrs = InputAttrs {
onInput {
it.target.files ?.get(0) ?.let {
fileState.value = it
}
}
}
)
DefaultButton(
"Upload",
disabled = fileState.value == null || textContent.value.isBlank()
) {
it.preventDefault()
fileState.value ?.let { file ->
uiScope.launchSafelyWithoutExceptions {
createPostCreateUIModel.create(
listOf(
TextContent(
textContent.value
),
BinaryContent(
file.filename,
file.mimeType,
file.inputProvider()
)
)
)
}
}
console.log("")
}
}
}
return result.await()
}
}

View File

@ -1,14 +1,16 @@
package dev.inmo.postssystem.features.common.common package dev.inmo.postssystem.features.common.common
import dev.inmo.micro_utils.common.MPPFile import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.common.bytes
import io.ktor.utils.io.core.ByteReadPacket import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.Input import io.ktor.utils.io.core.Input
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Serializable(SimpleInputProviderSerializer::class) @Serializable(SimpleInputProviderSerializer::class)
actual class FileBasedInputProvider internal constructor( actual class FileBasedInputProvider internal constructor(
actual val file: MPPFile, actual val file: MPPFile
private val bytes: ByteArray
) : SimpleInputProvider { ) : SimpleInputProvider {
override fun invoke(): Input = ByteReadPacket(bytes) override fun invoke(): Input = error("Files inputs must not be used directly")
} }
fun MPPFile.inputProvider() = FileBasedInputProvider(this)

View File

@ -1,6 +1,7 @@
package dev.inmo.postssystem.features.content.common package dev.inmo.postssystem.features.content.common
import dev.inmo.micro_utils.common.FileName import dev.inmo.micro_utils.common.FileName
import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.mime_types.MimeType import dev.inmo.micro_utils.mime_types.MimeType
import dev.inmo.postssystem.features.common.common.SimpleInputProvider import dev.inmo.postssystem.features.common.common.SimpleInputProvider
import kotlinx.serialization.PolymorphicSerializer import kotlinx.serialization.PolymorphicSerializer

View File

@ -12,16 +12,7 @@ class ClientStaticRoutingConfiguration(
private val clientStatic: String? private val clientStatic: String?
) : ApplicationRoutingConfigurator.Element { ) : ApplicationRoutingConfigurator.Element {
override fun Route.invoke() { override fun Route.invoke() {
clientStatic ?.let { resources("web")
static("client") { defaultResource("/web/index.html")
resources(it)
get {
call.respondRedirect("client/index.html")
}
}
get {
call.respondRedirect("client")
}
}
} }
} }

View File

@ -29,6 +29,8 @@ import dev.inmo.postssystem.features.content.binary.server.BinaryServerContentSt
import dev.inmo.postssystem.features.content.common.* import dev.inmo.postssystem.features.content.common.*
import dev.inmo.postssystem.features.content.server.storage.ServerContentStorage import dev.inmo.postssystem.features.content.server.storage.ServerContentStorage
import dev.inmo.postssystem.features.content.server.ServerContentStorageAggregator import dev.inmo.postssystem.features.content.server.ServerContentStorageAggregator
import dev.inmo.postssystem.features.content.server.ServerContentStorageWrapper
import dev.inmo.postssystem.features.content.text.common.TextContent
import dev.inmo.postssystem.features.content.text.common.TextContentSerializerModuleConfigurator import dev.inmo.postssystem.features.content.text.common.TextContentSerializerModuleConfigurator
import dev.inmo.postssystem.features.content.text.server.TextServerContentStorage import dev.inmo.postssystem.features.content.text.server.TextServerContentStorage
import dev.inmo.postssystem.features.posts.server.ExposedServerPostsStorage import dev.inmo.postssystem.features.posts.server.ExposedServerPostsStorage
@ -55,6 +57,7 @@ import org.koin.core.module.Module
import org.koin.core.parameter.ParametersHolder import org.koin.core.parameter.ParametersHolder
import org.koin.core.qualifier.StringQualifier import org.koin.core.qualifier.StringQualifier
import org.koin.dsl.* import org.koin.dsl.*
import org.w3c.dom.Text
import java.io.File import java.io.File
private fun Route.print() { private fun Route.print() {
@ -169,8 +172,18 @@ fun getDIModule(
WriteDistFilesStorage(get(binaryFilesFolderQualifier), get(binaryFilesMetasKeyValueRepoQualifier)) WriteDistFilesStorage(get(binaryFilesFolderQualifier), get(binaryFilesMetasKeyValueRepoQualifier))
) )
} }
singleWithRandomQualifier { BinaryServerContentStorage(get(binaryStorageFilesQualifier)) } singleWithRandomQualifier {
singleWithRandomQualifier { TextServerContentStorage(get()) } ServerContentStorageWrapper(
BinaryServerContentStorage(get(binaryStorageFilesQualifier)),
BinaryContent::class
)
}
singleWithRandomQualifier {
ServerContentStorageWrapper(
TextServerContentStorage(get()),
TextContent::class
)
}
single<ServerContentStorage<Content>> { ServerContentStorageAggregator(getAll(), get()) } single<ServerContentStorage<Content>> { ServerContentStorageAggregator(getAll(), get()) }
@ -213,7 +226,7 @@ fun getDIModule(
) )
} }
singleWithBinds { AuthenticationRoutingConfigurator(get(), get(), get()) } singleWithBinds { AuthenticationRoutingConfigurator(get(), get(), get()) }
singleWithBinds { NotFoundStatusPageRedirectToIndex("/client") } singleWithBinds { NotFoundStatusPageRedirectToIndex("/") }
if (config.debugMode) { if (config.debugMode) {
single<ApplicationRoutingConfigurator.Element>(StringQualifier("Tracer")) { ApplicationRoutingConfigurator.Element {(this as Routing).trace { application.log.trace(it.buildText()) } } } single<ApplicationRoutingConfigurator.Element>(StringQualifier("Tracer")) { ApplicationRoutingConfigurator.Element {(this as Routing).trace { application.log.trace(it.buildText()) } } }

File diff suppressed because one or more lines are too long

View File

@ -48,7 +48,7 @@ class ClientWritePostsService(
removeRoute removeRoute
) )
private val tempUploadFullPath = buildStandardUrl( private val tempUploadFullPath = buildStandardUrl(
baseUrl, root,
postsCreateTempPathPart postsCreateTempPathPart
) )
@ -60,6 +60,7 @@ class ClientWritePostsService(
tempUploadFullPath, tempUploadFullPath,
provider.file provider.file
) )
println(fileId)
it.copy(inputProvider = TempFileIdentifierInputProvider(fileId)) it.copy(inputProvider = TempFileIdentifierInputProvider(fileId))
} }
is TempFileIdentifierInputProvider -> it is TempFileIdentifierInputProvider -> it

View File

@ -0,0 +1,15 @@
package dev.inmo.postssystem.services.posts.client.ui.create
import dev.inmo.postssystem.features.common.common.*
import dev.inmo.postssystem.features.content.common.Content
import kotlinx.coroutines.flow.StateFlow
class PostCreateUIViewModel(
private val model: PostCreateUIModel
) : UIViewModel<PostCreateUIState> {
override val currentState: StateFlow<PostCreateUIState>
get() = model.currentState
suspend fun create(content: List<Content>) {
model.create(content)
}
}

View File

@ -3,9 +3,8 @@ package dev.inmo.postssystem.services.posts.client
import dev.inmo.micro_utils.common.MPPFile import dev.inmo.micro_utils.common.MPPFile
import dev.inmo.micro_utils.ktor.client.UnifiedRequester import dev.inmo.micro_utils.ktor.client.UnifiedRequester
import dev.inmo.postssystem.features.files.common.FileId import dev.inmo.postssystem.features.files.common.FileId
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.*
import kotlinx.coroutines.GlobalScope.coroutineContext import kotlinx.coroutines.GlobalScope.coroutineContext
import kotlinx.coroutines.job
import org.w3c.xhr.* import org.w3c.xhr.*
internal actual suspend fun UnifiedRequester.tempUpload( internal actual suspend fun UnifiedRequester.tempUpload(
@ -39,7 +38,7 @@ internal actual suspend fun UnifiedRequester.tempUpload(
request.open("POST", fullTempUploadDraftPath, true) request.open("POST", fullTempUploadDraftPath, true)
request.send(formData) request.send(formData)
coroutineContext.job.invokeOnCompletion { currentCoroutineContext().job.invokeOnCompletion {
runCatching { runCatching {
request.abort() request.abort()
} }