Skip to content

Commit 0c8eef9

Browse files
whole view complete, files handling left
1 parent 7aa30ef commit 0c8eef9

File tree

11 files changed

+302
-86
lines changed

11 files changed

+302
-86
lines changed
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package dev.inmo.kmppscriptbuilder.web
22

3-
import dev.inmo.kmppscriptbuilder.web.views.MavenProjectInfoView
4-
import dev.inmo.kmppscriptbuilder.web.views.ProjectTypeView
3+
import dev.inmo.kmppscriptbuilder.web.views.*
54
import kotlinx.browser.document
65

76
fun main() {
87
document.addEventListener(
98
"DOMContentLoaded",
109
{
11-
val projectTypeView = ProjectTypeView()
12-
val mavenInfoTypeView = MavenProjectInfoView()
10+
val builderView = BuilderView()
11+
document.body ?.onclick = {
12+
println(builderView.config)
13+
Unit
14+
}
1315
}
1416
)
1517
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dev.inmo.kmppscriptbuilder.web.utils
2+
3+
import kotlinx.browser.document
4+
5+
inline fun <R> keepScrolling(crossinline block: () -> R): R = document.body ?.let {
6+
val (x, y) = (it.scrollLeft to it.scrollTop)
7+
return block().also { _ ->
8+
it.scrollTo(x, y)
9+
}
10+
} ?: block()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dev.inmo.kmppscriptbuilder.web.views
2+
3+
import dev.inmo.kmppscriptbuilder.core.models.Config
4+
import kotlinx.browser.document
5+
import org.w3c.dom.HTMLElement
6+
7+
class BuilderView : View {
8+
private val projectTypeView = ProjectTypeView()
9+
private val licensesView = LicensesView(document.getElementById("licensesListDiv") as HTMLElement)
10+
private val mavenInfoTypeView = MavenProjectInfoView()
11+
12+
var config: Config
13+
get() = Config(
14+
licensesView.licenses,
15+
mavenInfoTypeView.mavenConfig,
16+
projectTypeView.projectType
17+
)
18+
set(value) {
19+
licensesView.licenses = value.licenses
20+
mavenInfoTypeView.mavenConfig = value.mavenConfig
21+
projectTypeView.projectType = value.type
22+
}
23+
}

web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/DevelopersView.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ package dev.inmo.kmppscriptbuilder.web.views
33
import dev.inmo.kmppscriptbuilder.core.models.Developer
44
import org.w3c.dom.*
55

6-
class DevelopersView(rootElement: HTMLElement) : ListView<Developer>(rootElement, "Add developer", "Remove developer") {
6+
class DevelopersView(rootElement: HTMLElement) : MutableListView<Developer>(rootElement, "Add developer", "Remove developer") {
77
private val HTMLElement.usernameElement: HTMLInputElement
8-
get() = children[0] as HTMLInputElement
8+
get() = getElementsByTagName("input")[0] as HTMLInputElement
99
private val HTMLElement.nameElement: HTMLInputElement
10-
get() = children[1] as HTMLInputElement
10+
get() = getElementsByTagName("input")[1] as HTMLInputElement
1111
private val HTMLElement.emailElement: HTMLInputElement
12-
get() = children[2] as HTMLInputElement
12+
get() = getElementsByTagName("input")[2] as HTMLInputElement
1313

1414
var developers: List<Developer>
15-
get() = elements.values.map {
15+
get() = elements.map {
1616
Developer(it.usernameElement.value, it.nameElement.value, it.emailElement.value)
1717
}
1818
set(value) {
@@ -21,7 +21,7 @@ class DevelopersView(rootElement: HTMLElement) : ListView<Developer>(rootElement
2121

2222
override fun createPlainObject(): Developer = Developer("", "", "")
2323

24-
override fun HTMLElement.placeElement(value: Developer) {
24+
override fun HTMLElement.addContentBeforeRemoveButton(value: Developer) {
2525
createTextField("Developer ID", "Developer username").value = value.id
2626
createTextField("Developer name", "").value = value.name
2727
createTextField("Developer E-Mail", "").value = value.eMail
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package dev.inmo.kmppscriptbuilder.web.views
2+
3+
import dev.inmo.kmppscriptbuilder.core.models.License
4+
import dev.inmo.kmppscriptbuilder.core.models.getLicenses
5+
import dev.inmo.micro_utils.coroutines.safeActor
6+
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
7+
import io.ktor.client.HttpClient
8+
import kotlinx.coroutines.*
9+
import kotlinx.coroutines.channels.Channel
10+
import kotlinx.coroutines.channels.SendChannel
11+
import kotlinx.coroutines.flow.consumeAsFlow
12+
import kotlinx.coroutines.flow.debounce
13+
import kotlinx.dom.appendElement
14+
import org.w3c.dom.*
15+
16+
class LicensesView(
17+
rootElement: HTMLElement,
18+
client: HttpClient = HttpClient(),
19+
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
20+
) : MutableListView<License>(rootElement, "Add empty license", "Remove license") {
21+
private val HTMLElement.idElement: HTMLInputElement
22+
get() = getElementsByTagName("input")[0] as HTMLInputElement
23+
private val HTMLElement.titleElement: HTMLInputElement
24+
get() = getElementsByTagName("input")[1] as HTMLInputElement
25+
private val HTMLElement.urlElement: HTMLInputElement
26+
get() = getElementsByTagName("input")[2] as HTMLInputElement
27+
28+
private class LicenseOfferList(
29+
rootElement: HTMLElement,
30+
private val licensesView: LicensesView,
31+
client: HttpClient,
32+
scope: CoroutineScope
33+
) : ListView<License>(rootElement, useSimpleDiffStrategy = true) {
34+
private var licensesTemplates: List<License> = emptyList()
35+
36+
init {
37+
scope.launch {
38+
licensesTemplates = client.getLicenses().values.toList()
39+
changeActor.send(Unit) // update list of searches
40+
}
41+
}
42+
43+
private val changeActor: SendChannel<Unit> = scope.run {
44+
val onChangeActor = Channel<Unit>(Channel.CONFLATED)
45+
onChangeActor.consumeAsFlow().subscribeSafelyWithoutExceptions(scope) {
46+
val lowercased = searchString
47+
data = if (lowercased.isEmpty()) {
48+
emptyList()
49+
} else {
50+
licensesTemplates.filter {
51+
val lowercasedTitle = it.title.toLowerCase()
52+
lowercased.all { it in lowercasedTitle }
53+
}
54+
}
55+
}
56+
onChangeActor
57+
}
58+
private val searchElement = rootElement.createTextField("Quick add", "Type some license name part to find it").apply {
59+
oninput = {
60+
changeActor.offer(Unit)
61+
false
62+
}
63+
}
64+
private var searchString: String
65+
get() = searchElement.value.toLowerCase()
66+
set(value) {
67+
searchElement.value = value
68+
}
69+
70+
override fun HTMLElement.placeElement(value: License) {
71+
createCommonButton(value.title).onclick = {
72+
searchString = ""
73+
licensesView.licenses += value
74+
changeActor.offer(Unit)
75+
false
76+
}
77+
}
78+
79+
override fun HTMLElement.updateElement(from: License, to: License) {
80+
getElementsByTagName("button")[0] ?.remove()
81+
placeElement(to)
82+
}
83+
}
84+
85+
private val licensesOffersList = LicenseOfferList(
86+
rootElement.appendElement("div") { classList.add("uk-padding-small") } as HTMLElement,
87+
this,
88+
client,
89+
scope
90+
)
91+
92+
var licenses: List<License>
93+
get() = elements.map {
94+
License(it.idElement.value, it.titleElement.value, it.urlElement.value)
95+
}
96+
set(value) {
97+
data = value
98+
}
99+
100+
override fun createPlainObject(): License = License("", "", "")
101+
102+
override fun HTMLElement.addContentBeforeRemoveButton(value: License) {
103+
createTextField("License Id", "Short name like \"Apache-2.0\"").value = value.id
104+
createTextField("License Title", "Official title of license (like \"Apache Software License 2.0\")").value = value.title
105+
createTextField("License URL", "Link to your LICENSE file OR official license file (like \"https://opensource.org/licenses/Apache-2.0\")").value = value.url ?: ""
106+
}
107+
108+
override fun HTMLElement.updateElement(from: License, to: License) {
109+
idElement.value = to.id
110+
titleElement.value = to.title
111+
urlElement.value = to.url ?: ""
112+
}
113+
}

web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/ListView.kt

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,57 @@
11
package dev.inmo.kmppscriptbuilder.web.views
22

3-
import dev.inmo.micro_utils.common.calculateDiff
4-
import kotlinx.browser.document
3+
import dev.inmo.micro_utils.common.calculateStrictDiff
54
import kotlinx.dom.appendElement
65
import org.w3c.dom.HTMLElement
76

87
abstract class ListView<T>(
9-
private val rootElement: HTMLElement,
10-
addButtonText: String = "Add",
11-
private val removeButtonText: String = "Remove"
8+
protected val rootElement: HTMLElement,
9+
useSimpleDiffStrategy: Boolean = false
1210
) : View {
13-
protected val elements = mutableMapOf<T, HTMLElement>()
14-
protected var data: List<T> = emptyList()
15-
set(value) {
16-
val old = field
17-
field = value
18-
val diff = old.calculateDiff(value)
11+
protected val elements = mutableListOf<HTMLElement>()
12+
private val diffHandling: (old: List<T>, new: List<T>) -> Unit = if (useSimpleDiffStrategy) {
13+
{ _, new ->
14+
elements.forEach { it.remove() }
15+
elements.clear()
16+
new.forEach {
17+
val element = instantiateElement()
18+
elements.add(element)
19+
element.placeElement(it)
20+
}
21+
}
22+
} else {
23+
{ old, new ->
24+
val diff = old.calculateStrictDiff(new)
1925
diff.removed.forEach {
20-
rootElement.removeChild(elements[it.value] ?: return@forEach)
26+
elements[it.index].remove()
27+
elements.removeAt(it.index)
28+
println(it.value)
2129
}
2230
diff.added.forEach {
2331
val element = instantiateElement()
32+
elements.add(element)
2433
element.placeElement(it.value)
25-
elements[it.value] = element
26-
element.addRemoveButton(it.value)
2734
}
2835
diff.replaced.forEach { (old, new) ->
29-
val element = elements[old.value] ?.also { it.updateElement(old.value, new.value) }
36+
val element = elements.getOrNull(old.index) ?.also { it.updateElement(old.value, new.value) }
3037
if (element == null) {
3138
val newElement = instantiateElement()
3239
newElement.placeElement(new.value)
33-
elements[new.value] = newElement
40+
elements[new.index] = newElement
3441
}
3542
}
3643
}
37-
38-
init {
39-
rootElement.createButton(addButtonText).apply {
40-
onclick = {
41-
data += createPlainObject()
42-
Unit
43-
}
44-
}
4544
}
45+
protected var data: List<T> = emptyList()
46+
set(value) {
47+
val old = field
48+
field = value
49+
diffHandling(old, value)
50+
}
4651

47-
protected abstract fun createPlainObject(): T
4852
protected abstract fun HTMLElement.placeElement(value: T)
4953
protected abstract fun HTMLElement.updateElement(from: T, to: T)
5054

51-
private fun HTMLElement.addRemoveButton(value: T) {
52-
createButton(removeButtonText).onclick = {
53-
data = data.filter {
54-
it != value
55-
}
56-
Unit
57-
}
58-
}
5955
private fun instantiateElement() = rootElement.appendElement("div") {
6056
classList.add("uk-padding-small")
6157
} as HTMLElement

web/src/jsMain/kotlin/dev/inmo/kmppscriptbuilder/web/views/MavenProjectInfoView.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class MavenProjectInfoView : View {
1414
private val includeGpgElement = document.getElementById("includeGpgSignToggle") as HTMLInputElement
1515
private val includeMavenCentralElement = document.getElementById("includeMavenCentralTargetRepoToggle") as HTMLInputElement
1616
private val developersView = DevelopersView(document.getElementById("developersListDiv") as HTMLElement)
17+
private val repositoriesView = RepositoriesView(document.getElementById("repositoriesListDiv") as HTMLElement)
1718

1819
var mavenConfig: MavenConfig
1920
get() = MavenConfig(
@@ -22,9 +23,8 @@ class MavenProjectInfoView : View {
2223
urlElement.value,
2324
vcsUrlElement.value,
2425
includeGpgElement.checked,
25-
developersView.developers,// TODO:: Add developers
26-
// TODO:: Add repositories
27-
if (includeMavenCentralElement.checked) {
26+
developersView.developers,
27+
repositoriesView.repositories + if (includeMavenCentralElement.checked) {
2828
listOf(SonatypeRepository)
2929
} else {
3030
emptyList()
@@ -37,8 +37,8 @@ class MavenProjectInfoView : View {
3737
vcsUrlElement.value = value.vcsUrl
3838
includeGpgElement.checked = value.includeGpgSigning
3939
developersView.developers = value.developers
40-
// TODO:: Add repositories
4140
val reposWithoutSonatype = value.repositories.filter { it != SonatypeRepository }
4241
includeMavenCentralElement.checked = value.repositories.size != reposWithoutSonatype.size
42+
repositoriesView.repositories = value.repositories
4343
}
4444
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package dev.inmo.kmppscriptbuilder.web.views
2+
3+
import dev.inmo.kmppscriptbuilder.web.utils.keepScrolling
4+
import org.w3c.dom.HTMLElement
5+
6+
abstract class MutableListView<T>(
7+
rootElement: HTMLElement,
8+
addButtonText: String = "Add",
9+
private val removeButtonText: String = "Remove"
10+
) : ListView<T>(rootElement) {
11+
init {
12+
rootElement.createPrimaryButton(addButtonText).apply {
13+
onclick = {
14+
keepScrolling {
15+
val newObject = createPlainObject()
16+
data += newObject
17+
}
18+
false
19+
}
20+
}
21+
}
22+
23+
protected abstract fun createPlainObject(): T
24+
protected open fun HTMLElement.addContentBeforeRemoveButton(value: T) {}
25+
protected open fun HTMLElement.addContentAfterRemoveButton(value: T) {}
26+
final override fun HTMLElement.placeElement(value: T) {
27+
addContentBeforeRemoveButton(value)
28+
addRemoveButton()
29+
addContentAfterRemoveButton(value)
30+
}
31+
32+
private fun HTMLElement.addRemoveButton() {
33+
val button = createPrimaryButton(removeButtonText)
34+
button.onclick = {
35+
elements.indexOf(button.parentElement).takeIf { it > -1 } ?.also {
36+
data -= data[it]
37+
} ?: rootElement.removeChild(this@addRemoveButton)
38+
false
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)