From 0c8eef971a1d5f3443c7271c69453460aecdb2f9 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 2 Mar 2021 12:13:05 +0600 Subject: [PATCH] whole view complete, files handling left --- .../dev/inmo/kmppscriptbuilder/web/main.kt | 10 +- .../web/utils/keepScrolling.kt | 10 ++ .../web/views/BuilderView.kt | 23 ++++ .../web/views/DevelopersView.kt | 12 +- .../web/views/LicensesView.kt | 113 ++++++++++++++++++ .../kmppscriptbuilder/web/views/ListView.kt | 80 ++++++------- .../web/views/MavenProjectInfoView.kt | 8 +- .../web/views/MutableListView.kt | 41 +++++++ .../web/views/RepositoriesView.kt | 31 +++++ .../web/views/ViewElements.kt | 37 +++--- web/src/jsMain/resources/index.html | 41 +++---- 11 files changed, 311 insertions(+), 95 deletions(-) create mode 100644 web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/utils/keepScrolling.kt create mode 100644 web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/BuilderView.kt create mode 100644 web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/LicensesView.kt create mode 100644 web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/MutableListView.kt create mode 100644 web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/RepositoriesView.kt diff --git a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/main.kt b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/main.kt index 7d6d374..2f6822d 100644 --- a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/main.kt +++ b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/main.kt @@ -1,15 +1,17 @@ package dev.inmo.kmppscriptbuilder.web -import dev.inmo.kmppscriptbuilder.web.views.MavenProjectInfoView -import dev.inmo.kmppscriptbuilder.web.views.ProjectTypeView +import dev.inmo.kmppscriptbuilder.web.views.* import kotlinx.browser.document fun main() { document.addEventListener( "DOMContentLoaded", { - val projectTypeView = ProjectTypeView() - val mavenInfoTypeView = MavenProjectInfoView() + val builderView = BuilderView() + document.body ?.onclick = { + println(builderView.config) + Unit + } } ) } diff --git a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/utils/keepScrolling.kt b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/utils/keepScrolling.kt new file mode 100644 index 0000000..00d7b0e --- /dev/null +++ b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/utils/keepScrolling.kt @@ -0,0 +1,10 @@ +package dev.inmo.kmppscriptbuilder.web.utils + +import kotlinx.browser.document + +inline fun keepScrolling(crossinline block: () -> R): R = document.body ?.let { + val (x, y) = (it.scrollLeft to it.scrollTop) + return block().also { _ -> + it.scrollTo(x, y) + } +} ?: block() diff --git a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/BuilderView.kt b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/BuilderView.kt new file mode 100644 index 0000000..ee06ba5 --- /dev/null +++ b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/BuilderView.kt @@ -0,0 +1,23 @@ +package dev.inmo.kmppscriptbuilder.web.views + +import dev.inmo.kmppscriptbuilder.core.models.Config +import kotlinx.browser.document +import org.w3c.dom.HTMLElement + +class BuilderView : View { + private val projectTypeView = ProjectTypeView() + private val licensesView = LicensesView(document.getElementById("licensesListDiv") as HTMLElement) + private val mavenInfoTypeView = MavenProjectInfoView() + + var config: Config + get() = Config( + licensesView.licenses, + mavenInfoTypeView.mavenConfig, + projectTypeView.projectType + ) + set(value) { + licensesView.licenses = value.licenses + mavenInfoTypeView.mavenConfig = value.mavenConfig + projectTypeView.projectType = value.type + } +} \ No newline at end of file diff --git a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/DevelopersView.kt b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/DevelopersView.kt index 38ac510..2fdc7bd 100644 --- a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/DevelopersView.kt +++ b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/DevelopersView.kt @@ -3,16 +3,16 @@ package dev.inmo.kmppscriptbuilder.web.views import dev.inmo.kmppscriptbuilder.core.models.Developer import org.w3c.dom.* -class DevelopersView(rootElement: HTMLElement) : ListView(rootElement, "Add developer", "Remove developer") { +class DevelopersView(rootElement: HTMLElement) : MutableListView(rootElement, "Add developer", "Remove developer") { private val HTMLElement.usernameElement: HTMLInputElement - get() = children[0] as HTMLInputElement + get() = getElementsByTagName("input")[0] as HTMLInputElement private val HTMLElement.nameElement: HTMLInputElement - get() = children[1] as HTMLInputElement + get() = getElementsByTagName("input")[1] as HTMLInputElement private val HTMLElement.emailElement: HTMLInputElement - get() = children[2] as HTMLInputElement + get() = getElementsByTagName("input")[2] as HTMLInputElement var developers: List - get() = elements.values.map { + get() = elements.map { Developer(it.usernameElement.value, it.nameElement.value, it.emailElement.value) } set(value) { @@ -21,7 +21,7 @@ class DevelopersView(rootElement: HTMLElement) : ListView(rootElement override fun createPlainObject(): Developer = Developer("", "", "") - override fun HTMLElement.placeElement(value: Developer) { + override fun HTMLElement.addContentBeforeRemoveButton(value: Developer) { createTextField("Developer ID", "Developer username").value = value.id createTextField("Developer name", "").value = value.name createTextField("Developer E-Mail", "").value = value.eMail diff --git a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/LicensesView.kt b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/LicensesView.kt new file mode 100644 index 0000000..9588e26 --- /dev/null +++ b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/LicensesView.kt @@ -0,0 +1,113 @@ +package dev.inmo.kmppscriptbuilder.web.views + +import dev.inmo.kmppscriptbuilder.core.models.License +import dev.inmo.kmppscriptbuilder.core.models.getLicenses +import dev.inmo.micro_utils.coroutines.safeActor +import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import io.ktor.client.HttpClient +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.dom.appendElement +import org.w3c.dom.* + +class LicensesView( + rootElement: HTMLElement, + client: HttpClient = HttpClient(), + scope: CoroutineScope = CoroutineScope(Dispatchers.Default) +) : MutableListView(rootElement, "Add empty license", "Remove license") { + private val HTMLElement.idElement: HTMLInputElement + get() = getElementsByTagName("input")[0] as HTMLInputElement + private val HTMLElement.titleElement: HTMLInputElement + get() = getElementsByTagName("input")[1] as HTMLInputElement + private val HTMLElement.urlElement: HTMLInputElement + get() = getElementsByTagName("input")[2] as HTMLInputElement + + private class LicenseOfferList( + rootElement: HTMLElement, + private val licensesView: LicensesView, + client: HttpClient, + scope: CoroutineScope + ) : ListView(rootElement, useSimpleDiffStrategy = true) { + private var licensesTemplates: List = emptyList() + + init { + scope.launch { + licensesTemplates = client.getLicenses().values.toList() + changeActor.send(Unit) // update list of searches + } + } + + private val changeActor: SendChannel = scope.run { + val onChangeActor = Channel(Channel.CONFLATED) + onChangeActor.consumeAsFlow().subscribeSafelyWithoutExceptions(scope) { + val lowercased = searchString + data = if (lowercased.isEmpty()) { + emptyList() + } else { + licensesTemplates.filter { + val lowercasedTitle = it.title.toLowerCase() + lowercased.all { it in lowercasedTitle } + } + } + } + onChangeActor + } + private val searchElement = rootElement.createTextField("Quick add", "Type some license name part to find it").apply { + oninput = { + changeActor.offer(Unit) + false + } + } + private var searchString: String + get() = searchElement.value.toLowerCase() + set(value) { + searchElement.value = value + } + + override fun HTMLElement.placeElement(value: License) { + createCommonButton(value.title).onclick = { + searchString = "" + licensesView.licenses += value + changeActor.offer(Unit) + false + } + } + + override fun HTMLElement.updateElement(from: License, to: License) { + getElementsByTagName("button")[0] ?.remove() + placeElement(to) + } + } + + private val licensesOffersList = LicenseOfferList( + rootElement.appendElement("div") { classList.add("uk-padding-small") } as HTMLElement, + this, + client, + scope + ) + + var licenses: List + get() = elements.map { + License(it.idElement.value, it.titleElement.value, it.urlElement.value) + } + set(value) { + data = value + } + + override fun createPlainObject(): License = License("", "", "") + + override fun HTMLElement.addContentBeforeRemoveButton(value: License) { + createTextField("License Id", "Short name like \"Apache-2.0\"").value = value.id + createTextField("License Title", "Official title of license (like \"Apache Software License 2.0\")").value = value.title + createTextField("License URL", "Link to your LICENSE file OR official license file (like \"https://opensource.org/licenses/Apache-2.0\")").value = value.url ?: "" + } + + override fun HTMLElement.updateElement(from: License, to: License) { + idElement.value = to.id + titleElement.value = to.title + urlElement.value = to.url ?: "" + } +} \ No newline at end of file diff --git a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/ListView.kt b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/ListView.kt index f402162..700b325 100644 --- a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/ListView.kt +++ b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/ListView.kt @@ -1,61 +1,57 @@ package dev.inmo.kmppscriptbuilder.web.views -import dev.inmo.micro_utils.common.calculateDiff -import kotlinx.browser.document +import dev.inmo.micro_utils.common.calculateStrictDiff import kotlinx.dom.appendElement import org.w3c.dom.HTMLElement abstract class ListView( - private val rootElement: HTMLElement, - addButtonText: String = "Add", - private val removeButtonText: String = "Remove" + protected val rootElement: HTMLElement, + useSimpleDiffStrategy: Boolean = false ) : View { - protected val elements = mutableMapOf() + protected val elements = mutableListOf() + private val diffHandling: (old: List, new: List) -> Unit = if (useSimpleDiffStrategy) { + { _, new -> + elements.forEach { it.remove() } + elements.clear() + new.forEach { + val element = instantiateElement() + elements.add(element) + element.placeElement(it) + } + } + } else { + { old, new -> + val diff = old.calculateStrictDiff(new) + diff.removed.forEach { + elements[it.index].remove() + elements.removeAt(it.index) + println(it.value) + } + diff.added.forEach { + val element = instantiateElement() + elements.add(element) + element.placeElement(it.value) + } + diff.replaced.forEach { (old, new) -> + val element = elements.getOrNull(old.index) ?.also { it.updateElement(old.value, new.value) } + if (element == null) { + val newElement = instantiateElement() + newElement.placeElement(new.value) + elements[new.index] = newElement + } + } + } + } protected var data: List = emptyList() set(value) { val old = field field = value - val diff = old.calculateDiff(value) - diff.removed.forEach { - rootElement.removeChild(elements[it.value] ?: return@forEach) - } - diff.added.forEach { - val element = instantiateElement() - element.placeElement(it.value) - elements[it.value] = element - element.addRemoveButton(it.value) - } - diff.replaced.forEach { (old, new) -> - val element = elements[old.value] ?.also { it.updateElement(old.value, new.value) } - if (element == null) { - val newElement = instantiateElement() - newElement.placeElement(new.value) - elements[new.value] = newElement - } - } + diffHandling(old, value) } - init { - rootElement.createButton(addButtonText).apply { - onclick = { - data += createPlainObject() - Unit - } - } - } - - protected abstract fun createPlainObject(): T protected abstract fun HTMLElement.placeElement(value: T) protected abstract fun HTMLElement.updateElement(from: T, to: T) - private fun HTMLElement.addRemoveButton(value: T) { - createButton(removeButtonText).onclick = { - data = data.filter { - it != value - } - Unit - } - } private fun instantiateElement() = rootElement.appendElement("div") { classList.add("uk-padding-small") } as HTMLElement diff --git a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/MavenProjectInfoView.kt b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/MavenProjectInfoView.kt index 4964806..b9cbddc 100644 --- a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/MavenProjectInfoView.kt +++ b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/MavenProjectInfoView.kt @@ -14,6 +14,7 @@ class MavenProjectInfoView : View { private val includeGpgElement = document.getElementById("includeGpgSignToggle") as HTMLInputElement private val includeMavenCentralElement = document.getElementById("includeMavenCentralTargetRepoToggle") as HTMLInputElement private val developersView = DevelopersView(document.getElementById("developersListDiv") as HTMLElement) + private val repositoriesView = RepositoriesView(document.getElementById("repositoriesListDiv") as HTMLElement) var mavenConfig: MavenConfig get() = MavenConfig( @@ -22,9 +23,8 @@ class MavenProjectInfoView : View { urlElement.value, vcsUrlElement.value, includeGpgElement.checked, - developersView.developers,// TODO:: Add developers - // TODO:: Add repositories - if (includeMavenCentralElement.checked) { + developersView.developers, + repositoriesView.repositories + if (includeMavenCentralElement.checked) { listOf(SonatypeRepository) } else { emptyList() @@ -37,8 +37,8 @@ class MavenProjectInfoView : View { vcsUrlElement.value = value.vcsUrl includeGpgElement.checked = value.includeGpgSigning developersView.developers = value.developers - // TODO:: Add repositories val reposWithoutSonatype = value.repositories.filter { it != SonatypeRepository } includeMavenCentralElement.checked = value.repositories.size != reposWithoutSonatype.size + repositoriesView.repositories = value.repositories } } \ No newline at end of file diff --git a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/MutableListView.kt b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/MutableListView.kt new file mode 100644 index 0000000..05d7ee9 --- /dev/null +++ b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/MutableListView.kt @@ -0,0 +1,41 @@ +package dev.inmo.kmppscriptbuilder.web.views + +import dev.inmo.kmppscriptbuilder.web.utils.keepScrolling +import org.w3c.dom.HTMLElement + +abstract class MutableListView( + rootElement: HTMLElement, + addButtonText: String = "Add", + private val removeButtonText: String = "Remove" +) : ListView(rootElement) { + init { + rootElement.createPrimaryButton(addButtonText).apply { + onclick = { + keepScrolling { + val newObject = createPlainObject() + data += newObject + } + false + } + } + } + + protected abstract fun createPlainObject(): T + protected open fun HTMLElement.addContentBeforeRemoveButton(value: T) {} + protected open fun HTMLElement.addContentAfterRemoveButton(value: T) {} + final override fun HTMLElement.placeElement(value: T) { + addContentBeforeRemoveButton(value) + addRemoveButton() + addContentAfterRemoveButton(value) + } + + private fun HTMLElement.addRemoveButton() { + val button = createPrimaryButton(removeButtonText) + button.onclick = { + elements.indexOf(button.parentElement).takeIf { it > -1 } ?.also { + data -= data[it] + } ?: rootElement.removeChild(this@addRemoveButton) + false + } + } +} \ No newline at end of file diff --git a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/RepositoriesView.kt b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/RepositoriesView.kt new file mode 100644 index 0000000..93234ca --- /dev/null +++ b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/RepositoriesView.kt @@ -0,0 +1,31 @@ +package dev.inmo.kmppscriptbuilder.web.views + +import dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository +import org.w3c.dom.* + +class RepositoriesView(rootElement: HTMLElement) : MutableListView(rootElement, "Add repository", "Remove repository") { + private val HTMLElement.nameElement: HTMLInputElement + get() = getElementsByTagName("input")[0] as HTMLInputElement + private val HTMLElement.urlElement: HTMLInputElement + get() = getElementsByTagName("input")[1] as HTMLInputElement + + var repositories: List + get() = elements.map { + MavenPublishingRepository(it.nameElement.value, it.urlElement.value) + } + set(value) { + data = value + } + + override fun createPlainObject(): MavenPublishingRepository = MavenPublishingRepository("", "") + + override fun HTMLElement.addContentBeforeRemoveButton(value: MavenPublishingRepository) { + createTextField("Repository name", "This name will be used to identify repository in grade").value = value.name + createTextField("Repository URL", "For example: https://repo.maven.apache.org/maven2/").value = value.name + } + + override fun HTMLElement.updateElement(from: MavenPublishingRepository, to: MavenPublishingRepository) { + nameElement.value = to.name + urlElement.value = to.url + } +} \ No newline at end of file diff --git a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/ViewElements.kt b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/ViewElements.kt index 4398718..7f70fdc 100644 --- a/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/ViewElements.kt +++ b/web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/ViewElements.kt @@ -2,29 +2,34 @@ package dev.inmo.kmppscriptbuilder.web.views import kotlinx.dom.appendElement import org.w3c.dom.* -import kotlin.random.Random fun HTMLElement.createTextField( label: String, placeholder: String ): HTMLInputElement { - val uuid = "r" + Random.nextLong() return appendElement("div") { classList.add("uk-margin", "uk-width-1-1") - appendElement("label") { - classList.add("uk-form-label") - setAttribute("for", uuid) - innerText = label - } - }.appendElement("input") { - id = uuid - classList.add("uk-input", "uk-width-expand") - setAttribute("type", "text") - setAttribute("placeholder", placeholder) - } as HTMLInputElement + }.appendElement("label") { + classList.add("uk-form-label") + innerHTML = label + }.run { + val input = appendElement("input") { + classList.add("uk-input", "uk-width-expand") + setAttribute("type", "text") + setAttribute("placeholder", placeholder) + } as HTMLInputElement + input + } } -fun HTMLElement.createButton(text: String): HTMLButtonElement = appendElement("button") { +fun HTMLElement.createPrimaryButton(text: String): HTMLButtonElement = (appendElement("button") { classList.add("uk-button", "uk-button-primary") - innerHTML = text -} as HTMLButtonElement +} as HTMLButtonElement).apply { + innerText = text +} + +fun HTMLElement.createCommonButton(text: String): HTMLButtonElement = (appendElement("button") { + classList.add("uk-button", "uk-button-default") +} as HTMLButtonElement).apply { + innerText = text +} diff --git a/web/src/jsMain/resources/index.html b/web/src/jsMain/resources/index.html index 7b94609..3f798e4 100644 --- a/web/src/jsMain/resources/index.html +++ b/web/src/jsMain/resources/index.html @@ -7,19 +7,17 @@ -
- -
+
Project type @@ -30,11 +28,11 @@ Licenses -
-
- -
- +
+ + + +
Project information @@ -66,13 +64,10 @@
Developers info -
-
+
Repositories info -
- -
+