total rework

This commit is contained in:
InsanusMokrassar 2022-11-16 00:56:24 +06:00
parent 53a76c7a73
commit 8430e68167
69 changed files with 904 additions and 1586 deletions

View File

@ -7,6 +7,9 @@ plugins {
apply from: "$mppProjectWithSerializationPresetPath" apply from: "$mppProjectWithSerializationPresetPath"
kotlin { kotlin {
js (IR) {
binaries.executable()
}
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
@ -17,10 +20,27 @@ kotlin {
api(compose.runtime) api(compose.runtime)
} }
} }
jsMain {
dependencies {
implementation(compose.web.core)
api libs.ktor.client.js
api libs.jsuikit
}
}
jvmMain { jvmMain {
dependencies { dependencies {
implementation(compose.desktop.currentOs) implementation(compose.desktop.currentOs)
api libs.ktor.client.cio
} }
} }
} }
} }
compose {
desktop {
application {
mainClass = "dev.inmo.kmppscriptbuilder.desktop.BuilderKt"
}
}
}

View File

@ -5,18 +5,26 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.Config import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultDivider
expect object BuilderViewDrawer : Drawer<BuilderView> @Composable
expect fun TopAppBar(
config: Config,
saveAvailable: Boolean,
onSaveAvailable: (Boolean) -> Unit,
onNewConfig: (Config) -> Unit
)
class BuilderView : View() { class BuilderView : View() {
internal val projectTypeView = ProjectTypeView() internal val projectTypeView by mutableStateOf(ProjectTypeView())
internal val mavenInfoView = MavenInfoView() internal val mavenInfoView by mutableStateOf(MavenInfoView())
internal val licensesView = LicensesView() internal val licensesView by mutableStateOf(LicensesView())
internal var saveAvailableState by mutableStateOf(false) internal var saveAvailableState by mutableStateOf(false)
var config: Config var config: Config
get() = Config(licensesView.licenses, mavenInfoView.mavenConfig, projectTypeView.projectType) get() {
return Config(licensesView.licenses, mavenInfoView.mavenConfig, projectTypeView.projectType)
}
set(value) { set(value) {
licensesView.licenses = value.licenses licensesView.licenses = value.licenses
mavenInfoView.mavenConfig = value.mavenConfig mavenInfoView.mavenConfig = value.mavenConfig
@ -26,6 +34,20 @@ class BuilderView : View() {
@Composable @Composable
override fun build() { override fun build() {
with(BuilderViewDrawer) { draw() } TopAppBar(
config,
saveAvailableState,
{
saveAvailableState = it
}
) {
config = it
}
projectTypeView.build()
DefaultDivider()
licensesView.build()
DefaultDivider()
mavenInfoView.build()
} }
} }

View File

@ -1,10 +1,12 @@
package dev.inmo.kmppscriptbuilder.core.ui package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.Developer import dev.inmo.kmppscriptbuilder.core.models.Developer
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
class DeveloperState( class DeveloperState(
id: String = "", id: String = "",
@ -21,11 +23,32 @@ class DeveloperState(
private fun Developer.toDeveloperState() = DeveloperState(id, name, eMail) private fun Developer.toDeveloperState() = DeveloperState(id, name, eMail)
class DevelopersView : ListView<DeveloperState>("Developers info") { class DevelopersView : ListView<DeveloperState>("Developers info") {
var developers = mutableStateListOf<Developer>() var developers: List<Developer>
get() = itemsList.map { it.toDeveloper() }
set(value) {
itemsList.apply {
clear()
addAll(value.map { it.toDeveloperState() })
}
}
override val addItemText: String = "Add developer" override val addItemText: String = "Add developer"
override val removeItemText: String = "Remove developer" override val removeItemText: String = "Remove developer"
override fun createItem(): DeveloperState = DeveloperState() override fun createItem(): DeveloperState = DeveloperState()
@Composable
override fun buildView(item: DeveloperState) {
CommonTextField(
item.id,
"Developer username",
) { item.id = it }
CommonTextField(
item.name,
"Developer name",
) { item.name = it }
CommonTextField(
item.eMail,
"Developer E-Mail",
) { item.eMail = it }
}
} }

View File

@ -1,12 +1,15 @@
package dev.inmo.kmppscriptbuilder.core.ui package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.License import dev.inmo.kmppscriptbuilder.core.models.License
import dev.inmo.kmppscriptbuilder.core.models.getLicenses import dev.inmo.kmppscriptbuilder.core.models.getLicenses
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -38,8 +41,14 @@ class LicensesView: VerticalView("Licenses") {
licensesListState.addAll(value.map { it.toLicenseState() }) licensesListState.addAll(value.map { it.toLicenseState() })
} }
internal val availableLicensesState = mutableStateListOf<License>() internal val availableLicensesState = mutableStateListOf<License>()
internal val licensesOffersToShow = mutableStateListOf<License>()
internal var licenseSearchFilter by mutableStateOf("") internal var licenseSearchFilter by mutableStateOf("")
internal val searchFieldFocused = mutableStateOf(false)
internal val licensesOffersToShow = derivedStateOf {
val query = licenseSearchFilter.lowercase()
availableLicensesState.filter {
it.title.lowercase().contains(query)
}
}
init { init {
CoroutineScope(Dispatchers.Default).launch { CoroutineScope(Dispatchers.Default).launch {
@ -50,6 +59,16 @@ class LicensesView: VerticalView("Licenses") {
} }
override val content: @Composable () -> Unit = { override val content: @Composable () -> Unit = {
CommonTextField(
licenseSearchFilter,
"Search filter",
onFocusChanged = {
searchFieldFocused.value = it
}
) { filterText ->
licenseSearchFilter = filterText
}
with(LicensesDrawer) { draw() } with(LicensesDrawer) { draw() }
} }
} }

View File

@ -18,7 +18,7 @@ abstract class ListView<T>(title: String) : VerticalView(title) {
protected val drawer = ListViewDrawer<T>() protected val drawer = ListViewDrawer<T>()
override val content: () -> Unit = { override val content: @Composable () -> Unit = {
with(drawer) { with(drawer) {
draw() draw()
} }

View File

@ -2,9 +2,17 @@ package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.* import androidx.compose.runtime.*
import dev.inmo.kmppscriptbuilder.core.models.GpgSigning import dev.inmo.kmppscriptbuilder.core.models.GpgSigning
import dev.inmo.kmppscriptbuilder.core.models.MavenConfig
import dev.inmo.kmppscriptbuilder.core.models.SonatypeRepository
import dev.inmo.kmppscriptbuilder.core.models.defaultProjectDescription
import dev.inmo.kmppscriptbuilder.core.models.defaultProjectName
import dev.inmo.kmppscriptbuilder.core.ui.utils.ButtonsPanel
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.ui.utils.SwitchWithLabel
expect object MavenInfoDrawer : Drawer<MavenInfoView> expect class GpgSigningOptionDrawer : Drawer<GpgSigning>
expect fun GpgSigningOptionDrawerWithView(view: MavenInfoView): GpgSigningOptionDrawer
class MavenInfoView : VerticalView("Project information") { class MavenInfoView : VerticalView("Project information") {
internal var projectNameProperty by mutableStateOf("") internal var projectNameProperty by mutableStateOf("")
@ -43,10 +51,46 @@ class MavenInfoView : VerticalView("Project information") {
publishToMavenCentralProperty = value.repositories.any { it == SonatypeRepository } publishToMavenCentralProperty = value.repositories.any { it == SonatypeRepository }
developersView.developers = value.developers developersView.developers = value.developers
repositoriesView.repositories = value.repositories.filter { it != SonatypeRepository } repositoriesView.repositories = value.repositories.filter { it != SonatypeRepository }
// developersView.developers = value.developers
} }
private val gpgSigningDrawer = GpgSigningOptionDrawerWithView(this)
override val content: @Composable () -> Unit = { override val content: @Composable () -> Unit = {
with (MavenInfoDrawer) { draw() } CommonTextField(
projectNameProperty,
"Public project name",
) { projectNameProperty = it }
CommonTextField(
projectDescriptionProperty,
"Public project description",
) { projectDescriptionProperty = it }
CommonTextField(
projectUrlProperty,
"Public project URL",
) { projectUrlProperty = it }
CommonTextField(
projectVcsUrlProperty,
"Public project VCS URL (with .git)",
) { projectVcsUrlProperty = it }
ButtonsPanel(
GpgSigning.Disabled,
GpgSigning.Optional,
GpgSigning.Enabled
) {
with(gpgSigningDrawer) {
with (it) {
draw()
}
}
}
SwitchWithLabel(
"Include publication to MavenCentral",
publishToMavenCentralProperty,
placeSwitchAtTheStart = true
) { publishToMavenCentralProperty = it }
developersView.build()
repositoriesView.build()
} }
} }

View File

@ -4,16 +4,31 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.JSProjectType
import dev.inmo.kmppscriptbuilder.core.models.JVMProjectType
import dev.inmo.kmppscriptbuilder.core.models.MultiplatformProjectType import dev.inmo.kmppscriptbuilder.core.models.MultiplatformProjectType
import dev.inmo.kmppscriptbuilder.core.models.ProjectType import dev.inmo.kmppscriptbuilder.core.models.ProjectType
import dev.inmo.kmppscriptbuilder.core.ui.utils.ButtonsPanel
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
expect object ProjectTypeDrawer : Drawer<ProjectTypeView> expect class ProjectTypeDrawer : Drawer<ProjectType>
expect fun ProjectTypeDrawerWithView(view: ProjectTypeView): ProjectTypeDrawer
class ProjectTypeView : VerticalView("Project type") { class ProjectTypeView : VerticalView("Project type") {
var projectType by mutableStateOf<ProjectType>(MultiplatformProjectType) var projectType by mutableStateOf<ProjectType>(MultiplatformProjectType)
private val typeDrawer = ProjectTypeDrawerWithView(this)
override val content: @Composable () -> Unit = { override val content: @Composable () -> Unit = {
with(ProjectTypeDrawer) { draw() } ButtonsPanel(
MultiplatformProjectType,
JVMProjectType,
JSProjectType
) {
with(typeDrawer) {
with (it) {
draw()
}
}
}
} }
} }

View File

@ -2,9 +2,11 @@ package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository import dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
class RepositoryState( class RepositoryState(
@ -19,8 +21,6 @@ class RepositoryState(
private fun MavenPublishingRepository.toRepositoryState() = RepositoryState(name, url) private fun MavenPublishingRepository.toRepositoryState() = RepositoryState(name, url)
expect object RepositoryStateDrawer : Drawer<RepositoryState>
class RepositoriesView : ListView<RepositoryState>("Repositories info") { class RepositoriesView : ListView<RepositoryState>("Repositories info") {
var repositories: List<MavenPublishingRepository> var repositories: List<MavenPublishingRepository>
get() = itemsList.map { it.toRepository() } get() = itemsList.map { it.toRepository() }
@ -38,7 +38,14 @@ class RepositoriesView : ListView<RepositoryState>("Repositories info") {
@Composable @Composable
override fun buildView(item: RepositoryState) { override fun buildView(item: RepositoryState) {
with(RepositoryStateDrawer) { with(item) { draw() } } CommonTextField(
item.name,
"Repository name",
) { item.name = it }
CommonTextField(
item.url,
"Repository url",
) { item.url = it }
} }
} }

View File

@ -2,7 +2,7 @@ package dev.inmo.kmppscriptbuilder.core.ui.utils
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
interface Drawer<T> { fun interface Drawer<T> {
@Composable @Composable
fun T.draw() fun T.draw()
} }

View File

@ -9,7 +9,12 @@ expect fun TitleText(text: String)
expect fun CommonText(text: String, onClick: (() -> Unit)? = null) expect fun CommonText(text: String, onClick: (() -> Unit)? = null)
@Composable @Composable
expect fun CommonTextField(presetText: String, hint: String, onChange: (String) -> Unit) expect fun CommonTextField(
presetText: String,
hint: String,
onFocusChanged: (Boolean) -> Unit = {},
onChange: (String) -> Unit
)
@Composable @Composable
expect fun SwitchWithLabel( expect fun SwitchWithLabel(
@ -19,3 +24,18 @@ expect fun SwitchWithLabel(
switchEnabled: Boolean = true, switchEnabled: Boolean = true,
onCheckedChange: (Boolean) -> Unit onCheckedChange: (Boolean) -> Unit
) )
@Composable
expect fun <T> ButtonsPanel(
data: Iterable<T>,
itemDrawer: @Composable (T) -> Unit
)
@Composable
fun <T> ButtonsPanel(
vararg data: T,
itemDrawer: @Composable (T) -> Unit
) = ButtonsPanel(data.toList(), itemDrawer)
@Composable
expect fun DefaultDivider()

View File

@ -5,16 +5,7 @@ import dev.inmo.micro_utils.common.MPPFile
internal const val appExtension = "kpsb" internal const val appExtension = "kpsb"
private var lastFile: MPPFile? = null expect fun openNewConfig(onParsed: (Config) -> Unit)
fun loadConfigFile(file: MPPFile): Config {
lastFile = file
return serialFormat.decodeFromString(Config.serializer(), file.text())
}
expect fun MPPFile.text(): String
expect fun loadConfig(): Config?
expect fun saveConfig(config: Config): Boolean expect fun saveConfig(config: Config): Boolean

View File

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 463 B

View File

Before

Width:  |  Height:  |  Size: 452 B

After

Width:  |  Height:  |  Size: 452 B

View File

Before

Width:  |  Height:  |  Size: 744 B

After

Width:  |  Height:  |  Size: 744 B

View File

Before

Width:  |  Height:  |  Size: 502 B

After

Width:  |  Height:  |  Size: 502 B

View File

@ -0,0 +1,14 @@
package dev.inmo.kmppscriptbuilder.core
import dev.inmo.kmppscriptbuilder.core.ui.BuilderView
import kotlinx.browser.window
import org.jetbrains.compose.web.renderComposableInBody
fun main() {
window.addEventListener("load", {
val builder = BuilderView()
renderComposableInBody {
builder.build()
}
})
}

View File

@ -0,0 +1,25 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.elements.DefaultButton
import dev.inmo.jsuikit.modifiers.UIKitButton
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.kmppscriptbuilder.core.models.GpgSigning
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual class GpgSigningOptionDrawer(
private val mavenInfoView: MavenInfoView
) : Drawer<GpgSigning> {
@Composable
override fun GpgSigning.draw() {
if (mavenInfoView.gpgSignProperty == this) {
DefaultButton(name, UIKitButton.Type.Primary)
} else {
DefaultButton(name, UIKitButton.Type.Default) {
mavenInfoView.gpgSignProperty = this
}
}
}
}
actual fun GpgSigningOptionDrawerWithView(view: MavenInfoView): GpgSigningOptionDrawer = GpgSigningOptionDrawer(mavenInfoView = view)

View File

@ -0,0 +1,59 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.elements.DefaultButton
import dev.inmo.jsuikit.elements.Divider
import dev.inmo.jsuikit.modifiers.UIKitButton
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.jsuikit.modifiers.builder
import dev.inmo.jsuikit.utils.Attrs
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import org.jetbrains.compose.web.dom.Div
actual object LicensesDrawer : Drawer<LicensesView> {
@Composable
override fun LicensesView.draw() {
dev.inmo.jsuikit.elements.List(
licensesOffersToShow.value,
Attrs {
if (!searchFieldFocused.value) {
hidden()
}
}
) {
DefaultButton(
it.title,
UIKitButton.Type.Text
) { _ ->
licensesListState.add(it.toLicenseState())
licenseSearchFilter = ""
}
Divider.Common()
}
DefaultButton("Add empty license", UIKitButton.Type.Primary, UIKitMargin.Small) {
licensesListState.add(LicenseState())
}
licensesListState.forEach { license ->
Div(UIKitMargin.Small.builder()) {
CommonTextField(
license.id,
"License ID",
) { license.id = it }
CommonTextField(
license.title,
"License title",
) { license.title = it }
CommonTextField(
license.url ?: "",
"License URL",
) { license.url = it }
DefaultButton("Remove", UIKitButton.Type.Danger, UIKitMargin.Small) {
licensesListState.remove(license)
}
}
}
}
}

View File

@ -0,0 +1,24 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.elements.DefaultButton
import dev.inmo.jsuikit.modifiers.UIKitButton
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.jsuikit.modifiers.builder
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import org.jetbrains.compose.web.dom.Div
actual class ListViewDrawer<T> : Drawer<ListView<T>> {
@Composable
override fun ListView<T>.draw() {
DefaultButton(addItemText, UIKitButton.Type.Primary ) { itemsList.add(createItem()) }
itemsList.forEach { item ->
Div(UIKitMargin.Small.builder()) {
buildView(item)
DefaultButton(removeItemText, UIKitButton.Type.Danger, UIKitMargin.Small) {
itemsList.remove(item)
}
}
}
}
}

View File

@ -0,0 +1,25 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.elements.DefaultButton
import dev.inmo.jsuikit.modifiers.UIKitButton
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.kmppscriptbuilder.core.models.ProjectType
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual class ProjectTypeDrawer(
private val projectTypeView: ProjectTypeView
) : Drawer<ProjectType> {
@Composable
override fun ProjectType.draw() {
if (projectTypeView.projectType == this) {
DefaultButton(name, UIKitButton.Type.Primary)
} else {
DefaultButton(name, UIKitButton.Type.Default) {
projectTypeView.projectType = this
}
}
}
}
actual fun ProjectTypeDrawerWithView(view: ProjectTypeView): ProjectTypeDrawer = ProjectTypeDrawer(projectTypeView = view)

View File

@ -0,0 +1,82 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.elements.Icon
import dev.inmo.jsuikit.elements.NavItemElement
import dev.inmo.jsuikit.elements.Navbar
import dev.inmo.jsuikit.elements.NavbarNav
import dev.inmo.jsuikit.elements.drawAsLink
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.jsuikit.modifiers.UIKitPadding
import dev.inmo.jsuikit.modifiers.UIKitText
import dev.inmo.jsuikit.modifiers.UIKitTooltipModifier
import dev.inmo.jsuikit.modifiers.builder
import dev.inmo.jsuikit.modifiers.include
import dev.inmo.jsuikit.utils.AttrsWithContentBuilder
import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.core.utils.exportGradle
import dev.inmo.kmppscriptbuilder.core.utils.openNewConfig
import dev.inmo.kmppscriptbuilder.core.utils.saveConfig
import org.jetbrains.compose.web.dom.A
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.Img
import org.jetbrains.compose.web.dom.Text
@Composable
actual fun TopAppBar(
config: Config,
saveAvailable: Boolean,
onSaveAvailable: (Boolean) -> Unit,
onNewConfig: (Config) -> Unit
) {
Navbar(
leftBuilder = AttrsWithContentBuilder {
Div(
{
onClick {
console.log(config)
}
include(UIKitPadding.Size.Small, UIKitText.Style.Lead)
}
) {
Text("Kotlin publication scripts builder")
}
Div(UIKitMargin.Small.builder()) {
A("https://github.com/InsanusMokrassar/KotlinPublicationScriptsBuilder") {
Img("https://img.shields.io/github/stars/InsanusMokrassar/KotlinPublicationScriptsBuilder?label=Github&style=plastic")
}
}
},
rightBuilder = AttrsWithContentBuilder {
NavbarNav(
AttrsWithContentBuilder {
NavItemElement(
UIKitTooltipModifier("Open file")
) {
Icon.Storage.Pull.drawAsLink {
openNewConfig(onNewConfig)
}
}
},
AttrsWithContentBuilder {
NavItemElement(
UIKitTooltipModifier("Save config")
) {
Icon.Storage.Push.drawAsLink {
saveConfig(config)
}
}
},
AttrsWithContentBuilder {
NavItemElement(
UIKitTooltipModifier("Export gradle script")
) {
Icon.Storage.Upload.drawAsLink {
exportGradle(config)
}
}
},
)
}
)
}

View File

@ -1,6 +1,10 @@
package dev.inmo.kmppscriptbuilder.core.ui package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.modifiers.UIKitForm
import dev.inmo.jsuikit.modifiers.builder
import org.jetbrains.compose.web.dom.Legend
import org.jetbrains.compose.web.dom.Text
actual abstract class View { actual abstract class View {
@Composable @Composable
@ -9,4 +13,8 @@ actual abstract class View {
@Composable @Composable
actual fun View.DrawVertically(title: String, block: @Composable () -> Unit) { actual fun View.DrawVertically(title: String, block: @Composable () -> Unit) {
Legend(UIKitForm.Legend.builder()) {
Text(title)
}
block()
} }

View File

@ -0,0 +1,107 @@
package dev.inmo.kmppscriptbuilder.core.ui.utils
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.elements.DefaultButton
import dev.inmo.jsuikit.elements.DefaultInput
import dev.inmo.jsuikit.elements.Divider
import dev.inmo.jsuikit.elements.Flex
import dev.inmo.jsuikit.elements.Icon
import dev.inmo.jsuikit.elements.Label
import dev.inmo.jsuikit.elements.drawAsFormInputPart
import dev.inmo.jsuikit.modifiers.UIKitButton
import dev.inmo.jsuikit.modifiers.UIKitFlex
import dev.inmo.jsuikit.modifiers.UIKitForm
import dev.inmo.jsuikit.modifiers.UIKitInverse
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.jsuikit.modifiers.UIKitText
import dev.inmo.jsuikit.modifiers.UIKitUtility
import dev.inmo.jsuikit.modifiers.builder
import dev.inmo.jsuikit.modifiers.include
import dev.inmo.jsuikit.utils.Attrs
import org.jetbrains.compose.web.attributes.InputType
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.Legend
import org.jetbrains.compose.web.dom.Text
@Composable
actual fun TitleText(text: String) {
Legend(UIKitForm.Legend.builder()) {
Text(text)
}
}
@Composable
actual fun CommonText(text: String, onClick: (() -> Unit)?) {
Div(
{
onClick ?.let {
this.onClick { _ ->
it()
}
}
}
) {
Text(text)
}
}
@Composable
actual fun CommonTextField(
presetText: String,
hint: String,
onFocusChanged: (Boolean) -> Unit,
onChange: (String) -> Unit
) {
DefaultInput(
InputType.Text,
presetText,
false,
placeholder = hint,
attributesCustomizer = {
onFocusIn { onFocusChanged(true) }
onFocusOut { onFocusChanged(false) }
},
onChange = onChange
)
}
@Composable
actual fun SwitchWithLabel(
label: String,
checked: Boolean,
placeSwitchAtTheStart: Boolean,
switchEnabled: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
DefaultButton(
if (checked) {
UIKitButton.Type.Primary
} else {
UIKitButton.Type.Default
},
disabled = !switchEnabled,
onClick = {
onCheckedChange(!checked)
},
attributesCustomizer = {
include(UIKitUtility.Inline)
}
) {
if (checked) {
Icon.App.Check.drawAsFormInputPart(UIKitInverse.Light)
}
Text(label)
}
}
@Composable
actual fun <T> ButtonsPanel(data: Iterable<T>, itemDrawer: @Composable (T) -> Unit) {
Flex(UIKitFlex.Alignment.Vertical.Middle, UIKitMargin.Small) {
data.forEach { itemDrawer(it) }
}
}
@Composable
actual fun DefaultDivider() {
Divider.Common()
}

View File

@ -0,0 +1,83 @@
package dev.inmo.kmppscriptbuilder.core.utils
import dev.inmo.kmppscriptbuilder.core.models.Config
import kotlinx.browser.document
import kotlinx.dom.appendElement
import org.w3c.dom.HTMLAnchorElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.url.URL
import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag
import org.w3c.files.FileReader
import org.w3c.files.get
fun saveFile(content: String, filename: String) {
val a = document.body!!.appendElement("a") {
setAttribute("style", "visibility:hidden; display: none")
} as HTMLAnchorElement
val blob = Blob(arrayOf(content), BlobPropertyBag(
"application/*;charset=utf-8"
))
val url = URL.createObjectURL(blob)
a.href = url
a.download = filename
a.click()
URL.revokeObjectURL(url)
a.remove()
}
actual fun openNewConfig(onParsed: (Config) -> Unit) {
val targetInput = document.body!!.appendElement("input") {
setAttribute("style", "visibility:hidden; display: none")
} as HTMLInputElement
targetInput.type = "file"
targetInput.onchange = {
targetInput.files ?.also { files ->
for (i in (0 until files.length) ) {
files[i] ?.also { file ->
val reader = FileReader()
reader.onload = {
val content = it.target.asDynamic().result as String
onParsed(serialFormat.decodeFromString(Config.serializer(), content))
false
}
reader.readAsText(file)
}
}
}
}
targetInput.click()
targetInput.remove()
}
actual fun saveConfig(config: Config): Boolean {
saveFile(
serialFormat.encodeToString(Config.serializer(), config),
"publish.kpsb"
)
return true
}
actual fun exportGradle(config: Config): Boolean {
val filename = "publish.gradle"
val content = config.run {
type.buildMavenGradleConfig(
mavenConfig,
licenses
)
}
saveFile(content, filename)
return true
}
actual fun saveAs(config: Config): Boolean {
saveFile(
serialFormat.encodeToString(Config.serializer(), config),
"publish.kpsb"
)
return true
}

View File

@ -0,0 +1,6 @@
package dev.inmo.kmppscriptbuilder.core.utils
actual fun openLink(link: String): Boolean {
dev.inmo.micro_utils.common.openLink(link)
return true
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Kotlin Publication Scripts Builder</title>
<!-- UIkit CSS -->
<link rel="stylesheet" href="./css/uikit.min.css" />
</head>
<body>
<!-- <form class="uk-padding-small">-->
<!-- <fieldset class="uk-fieldset">-->
<!-- <legend class="uk-legend">Project type</legend>-->
<!-- <div class="uk-padding-small">-->
<!-- <ul class="uk-subnav uk-subnav-pill">-->
<!-- <li id="mppProjectType" class="uk-active"><a href="#">Multiplatform</a></li>-->
<!-- <li id="jvmProjectType"><a href="#">JVM</a></li>-->
<!-- <li id="jsProjectType"><a href="#">JS</a></li>-->
<!-- </ul>-->
<!-- </div>-->
<!-- <legend class="uk-legend">Licenses</legend>-->
<!-- <div id="licensesListDiv" class="uk-padding-small"></div>-->
<!-- <legend class="uk-legend">Project information</legend>-->
<!-- <div class="uk-padding-small">-->
<!-- <div class="uk-margin uk-width-1-1">-->
<!-- <label class="uk-form-label" for="projectNameInput">Public project name</label>-->
<!-- <input id="projectNameInput" class="uk-input uk-width-expand" type="text" placeholder="${project.name}">-->
<!-- </div>-->
<!-- <div class="uk-margin uk-width-1-1">-->
<!-- <label class="uk-form-label" for="projectDescriptionInput">Public project description</label>-->
<!-- <input id="projectDescriptionInput" class="uk-input uk-width-expand" type="text" placeholder="${project.name}">-->
<!-- </div>-->
<!-- <div class="uk-margin uk-width-1-1">-->
<!-- <label class="uk-form-label" for="projectUrlInput">Public project URL</label>-->
<!-- <input id="projectUrlInput" class="uk-input uk-width-expand" type="text" placeholder="Type url to github or other source with readme">-->
<!-- </div>-->
<!-- <div class="uk-margin uk-width-1-1">-->
<!-- <label class="uk-form-label" for="projectVCSUrlInput">Public project VCS URL (with .git)</label>-->
<!-- <input id="projectVCSUrlInput" class="uk-input uk-width-expand" type="text" placeholder="Type url to github .git file">-->
<!-- </div>-->
<!-- <div class="uk-margin">-->
<!-- <label>GPG Signing</label>-->
<!-- <div class="uk-padding-small">-->
<!-- <ul class="uk-subnav uk-subnav-pill">-->
<!-- <li id="disableGpgSigning" class="uk-active" uk-tooltip="title: Signing will not be added"><a href="#">Disabled</a></li>-->
<!-- <li id="optionalGpgSigning" uk-tooltip="title: Signing will be added, but disabled in case of absence 'signatory.keyId'"><a href="#">Optional</a></li>-->
<!-- <li id="enableGpgSigning" uk-tooltip="title: Signing will be always enabled"><a href="#">Enabled</a></li>-->
<!-- </ul>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="uk-margin">-->
<!-- <label><input id="includeMavenCentralTargetRepoToggle" class="uk-checkbox" type="checkbox"> Include publication to MavenCentral</label>-->
<!-- </div>-->
<!-- </div>-->
<!-- <legend class="uk-legend">Developers info</legend>-->
<!-- <div id="developersListDiv" class="uk-padding-small"></div>-->
<!-- <legend class="uk-legend">Repositories info</legend>-->
<!-- <div id="repositoriesListDiv" class="uk-padding-small"></div>-->
<!-- </fieldset>-->
<!-- </form>-->
<!-- UIkit JS -->
<script src="./js/uikit.min.js"></script>
<script src="./js/uikit-icons.min.js"></script>
<!-- Internal JS -->
<script src="kmppscriptbuilder.core.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
package dev.inmo.kmppscriptbuilder.desktop package dev.inmo.kmppscriptbuilder.core
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@ -9,9 +9,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.window.Window import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application import androidx.compose.ui.window.application
import dev.inmo.kmppscriptbuilder.desktop.utils.init import dev.inmo.kmppscriptbuilder.core.ui.BuilderView
import dev.inmo.kmppscriptbuilder.desktop.utils.loadConfigFile import dev.inmo.kmppscriptbuilder.core.utils.loadConfigFile
import dev.inmo.kmppscriptbuilder.desktop.views.BuilderView
import java.io.File import java.io.File
//private val uncaughtExceptionsBC = BroadcastChannel<DefaultErrorHandler.ErrorEvent>(Channel.CONFLATED) //private val uncaughtExceptionsBC = BroadcastChannel<DefaultErrorHandler.ErrorEvent>(Channel.CONFLATED)
@ -48,7 +47,9 @@ fun main(args: Array<String>) = application {
.fillMaxSize() .fillMaxSize()
.verticalScroll(stateVertical) .verticalScroll(stateVertical)
) { ) {
builder.init() Column {
builder.build()
}
} }

View File

@ -1,65 +0,0 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Divider
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonText
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.utils.exportGradle
import dev.inmo.kmppscriptbuilder.core.utils.loadConfig
import dev.inmo.kmppscriptbuilder.core.utils.saveAs
import dev.inmo.kmppscriptbuilder.core.utils.saveConfig
actual object BuilderViewDrawer : Drawer<BuilderView> {
override fun BuilderView.draw() {
Box(Modifier.fillMaxSize()) {
Column() {
TopAppBar(
@Composable {
Text("Kotlin publication scripts builder", Modifier.clickable { println(config) })
},
actions = {
createIcon("Open file", "images/open_file.svg") {
loadConfig()?.also {
config = it
}
}
if (saveAvailableState) {
createIcon("Save", "images/save_file.svg") {
saveConfig(config)
}
}
if (saveAvailableState) {
createIcon("Export Gradle script", "images/export_gradle.svg") {
exportGradle(config)
}
}
createIcon("Save as", "images/save_as.svg") {
if (saveAs(config)) {
saveAvailableState = true
}
}
}
)
Column(Modifier.padding(8.dp)) {
projectTypeView.init()
Divider()
licensesView.init()
Divider()
mavenInfoView.init()
}
}
}
}
}

View File

@ -0,0 +1,39 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.models.GpgSigning
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.ui.utils.SwitchWithLabel
actual class GpgSigningOptionDrawer(
private val mavenInfoView: MavenInfoView
) : Drawer<GpgSigning> {
@Composable
override fun GpgSigning.draw() {
if (mavenInfoView.gpgSignProperty == this) {
Button({}, Modifier.padding(8.dp)) {
Text(name)
}
} else {
OutlinedButton(
{
mavenInfoView.gpgSignProperty = this
},
Modifier.padding(8.dp)
) {
Text(name)
}
}
}
}
actual fun GpgSigningOptionDrawerWithView(view: MavenInfoView): GpgSigningOptionDrawer = GpgSigningOptionDrawer(mavenInfoView = view)

View File

@ -4,6 +4,9 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.Divider import androidx.compose.material.Divider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonText import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonText
@ -11,44 +14,39 @@ import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual object LicensesDrawer : Drawer<LicensesView> { actual object LicensesDrawer : Drawer<LicensesView> {
@Composable
override fun LicensesView.draw() { override fun LicensesView.draw() {
CommonTextField(licenseSearchFilter, "Search filter") { filterText -> if (searchFieldFocused.value) {
licenseSearchFilter = filterText Column {
licensesOffersToShow.clear() licensesOffersToShow.value.forEach {
if (licenseSearchFilter.isNotEmpty()) { Column(Modifier.padding(16.dp, 8.dp, 8.dp, 8.dp)) {
licensesOffersToShow.addAll( CommonText(it.title) {
availableLicensesState.filter { filterText.all { symbol -> symbol.lowercaseChar() in it.title } } licensesListState.add(it.toLicenseState())
) licenseSearchFilter = ""
} }
} Divider()
Column {
licensesOffersToShow.forEach {
Column(Modifier.padding(16.dp, 8.dp, 8.dp, 8.dp)) {
CommonText(it.title) {
licensesListState.add(it.toLicenseState())
licenseSearchFilter = ""
licensesOffersToShow.clear()
} }
Divider()
} }
} }
} }
Button({ licensesListState.add(LicenseState()) }, Modifier.padding(8.dp)) { Button({ licensesListState.add(LicenseState()) }, Modifier.padding(8.dp)) {
CommonText("Add empty license",) CommonText("Add empty license")
} }
licensesListState.forEach { license -> licensesListState.forEach { license ->
Column(Modifier.padding(8.dp)) { Column(Modifier.padding(8.dp)) {
CommonTextField( CommonTextField(
license.id, license.id,
"License ID" "License ID",
) { license.id = it } ) { license.id = it }
CommonTextField( CommonTextField(
license.title, license.title,
"License title" "License title",
) { license.title = it } ) { license.title = it }
CommonTextField( CommonTextField(
license.url ?: "", license.url ?: "",
"License URL" "License URL",
) { license.url = it } ) { license.url = it }
Button({ licensesListState.remove(license) }, Modifier.padding(8.dp)) { Button({ licensesListState.remove(license) }, Modifier.padding(8.dp)) {
CommonText("Remove",) CommonText("Remove",)

View File

@ -3,12 +3,14 @@ package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonText import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonText
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual class ListViewDrawer<T> : Drawer<ListView<T>> { actual class ListViewDrawer<T> : Drawer<ListView<T>> {
@Composable
override fun ListView<T>.draw() { override fun ListView<T>.draw() {
Button({ itemsList.add(createItem()) }) { Button({ itemsList.add(createItem()) }) {
CommonText(addItemText,) CommonText(addItemText,)

View File

@ -1,68 +0,0 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.models.GpgSigning
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.ui.utils.SwitchWithLabel
actual object MavenInfoDrawer : Drawer<MavenInfoView> {
@Composable
private fun MavenInfoView.addGpgSigningButton(gpgSigning: GpgSigning) {
if (gpgSignProperty == gpgSigning) {
Button({}, Modifier.padding(8.dp)) {
Text(gpgSigning.name)
}
} else {
OutlinedButton(
{
gpgSignProperty = gpgSigning
},
Modifier.padding(8.dp)
) {
Text(gpgSigning.name)
}
}
}
override fun MavenInfoView.draw() {
CommonTextField(
projectNameProperty,
"Public project name"
) { projectNameProperty = it }
CommonTextField(
projectDescriptionProperty,
"Public project description"
) { projectDescriptionProperty = it }
CommonTextField(
projectUrlProperty,
"Public project URL"
) { projectUrlProperty = it }
CommonTextField(
projectVcsUrlProperty,
"Public project VCS URL (with .git)"
) { projectVcsUrlProperty = it }
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Gpg Signing: ")
addGpgSigningButton(GpgSigning.Disabled)
addGpgSigningButton(GpgSigning.Optional)
addGpgSigningButton(GpgSigning.Enabled)
}
SwitchWithLabel(
"Include publication to MavenCentral",
publishToMavenCentralProperty,
placeSwitchAtTheStart = true
) { publishToMavenCentralProperty = it }
developersView.init()
repositoriesView.init()
}
}

View File

@ -15,30 +15,26 @@ import dev.inmo.kmppscriptbuilder.core.models.MultiplatformProjectType
import dev.inmo.kmppscriptbuilder.core.models.ProjectType import dev.inmo.kmppscriptbuilder.core.models.ProjectType
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual object ProjectTypeDrawer : Drawer<ProjectTypeView> { actual class ProjectTypeDrawer(
private val projectTypeView: ProjectTypeView
) : Drawer<ProjectType> {
@Composable @Composable
private fun ProjectTypeView.addProjectTypeButton(newProjectType: ProjectType) { override fun ProjectType.draw() {
if (projectType == newProjectType) { if (projectTypeView.projectType == this) {
Button({}, Modifier.padding(8.dp)) { Button({}, Modifier.padding(8.dp)) {
Text(newProjectType.name) Text(name)
} }
} else { } else {
OutlinedButton( OutlinedButton(
{ {
projectType = newProjectType projectTypeView.projectType = this
}, },
Modifier.padding(8.dp) Modifier.padding(8.dp)
) { ) {
Text(newProjectType.name) Text(name)
} }
} }
} }
override fun ProjectTypeView.draw() {
Row(verticalAlignment = Alignment.CenterVertically) {
addProjectTypeButton(MultiplatformProjectType)
addProjectTypeButton(JVMProjectType)
addProjectTypeButton(JSProjectType)
}
}
} }
actual fun ProjectTypeDrawerWithView(view: ProjectTypeView): ProjectTypeDrawer = ProjectTypeDrawer(projectTypeView = view)

View File

@ -1,18 +0,0 @@
package dev.inmo.kmppscriptbuilder.core.ui
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual object RepositoryStateDrawer : Drawer<RepositoriesView> {
override fun RepositoriesView.draw() {
CommonTextField(
item.name,
"Repository name"
) { item.name = it }
CommonTextField(
item.url,
"Repository url"
) { item.url = it }
}
}

View File

@ -0,0 +1,88 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.TooltipArea
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.primarySurface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.core.utils.exportGradle
import dev.inmo.kmppscriptbuilder.core.utils.openNewConfig
import dev.inmo.kmppscriptbuilder.core.utils.saveAs
import dev.inmo.kmppscriptbuilder.core.utils.saveConfig
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun createIcon(
tooltip: String,
resource: String,
onClick: () -> Unit
) {
TooltipArea(
tooltip = {
Surface(
modifier = Modifier.shadow(4.dp),
color = MaterialTheme.colors.primarySurface,
shape = RoundedCornerShape(4.dp)
) {
Text(tooltip, modifier = Modifier.padding(10.dp), color = MaterialTheme.colors.onPrimary)
}
}
) {
IconButton(onClick) {
Image(
painter = painterResource(resource),
contentDescription = tooltip
)
}
}
}
@Composable
actual fun TopAppBar(
config: Config,
saveAvailable: Boolean,
onSaveAvailable: (Boolean) -> Unit,
onNewConfig: (Config) -> Unit
) {
TopAppBar(
@Composable {
Text("Kotlin publication scripts builder", Modifier.clickable { println(config) })
},
actions = {
createIcon("Open file", "images/open_file.svg") {
openNewConfig(onNewConfig)
}
if (saveAvailable) {
createIcon("Save", "images/save_file.svg") {
saveConfig(config)
}
}
if (saveAvailable) {
createIcon("Export Gradle script", "images/export_gradle.svg") {
exportGradle(config)
}
}
createIcon("Save as", "images/save_as.svg") {
if (saveAs(config)) {
onSaveAvailable(true)
}
}
}
)
}

View File

@ -6,12 +6,14 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.Divider
import androidx.compose.material.OutlinedTextField import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Switch import androidx.compose.material.Switch
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -29,15 +31,15 @@ actual fun SwitchWithLabel(
switchEnabled: Boolean, switchEnabled: Boolean,
onCheckedChange: (Boolean) -> Unit onCheckedChange: (Boolean) -> Unit
) { ) {
Row(Modifier.padding(0.dp, 8.dp), Arrangement.Start, Alignment.Top) { Row(Modifier.padding(0.dp, 8.dp).clickable { onCheckedChange(!checked) }, Arrangement.Start, Alignment.Top) {
val switchCreator = @Composable { val switchCreator = @Composable {
Switch(checked, onCheckedChange, Modifier.padding(8.dp, 0.dp), enabled = switchEnabled) Switch(checked, null, Modifier.padding(8.dp, 0.dp), enabled = switchEnabled)
} }
if (placeSwitchAtTheStart) { if (placeSwitchAtTheStart) {
switchCreator() switchCreator()
} }
Box(Modifier.fillMaxWidth().align(Alignment.CenterVertically).clickable { }) { Box(Modifier.fillMaxWidth().align(Alignment.CenterVertically)) {
CommonText(label,) CommonText(label)
} }
if (!placeSwitchAtTheStart) { if (!placeSwitchAtTheStart) {
switchCreator() switchCreator()
@ -46,11 +48,18 @@ actual fun SwitchWithLabel(
} }
@Composable @Composable
actual fun CommonTextField(presetText: String, hint: String, onChange: (String) -> Unit) { actual fun CommonTextField(
presetText: String,
hint: String,
onFocusChanged: (Boolean) -> Unit,
onChange: (String) -> Unit
) {
OutlinedTextField( OutlinedTextField(
presetText, presetText,
onChange, onChange,
Modifier.fillMaxWidth(), Modifier.fillMaxWidth().onFocusChanged {
onFocusChanged(it.isFocused)
},
singleLine = true, singleLine = true,
label = { label = {
CommonText(hint,) CommonText(hint,)
@ -69,3 +78,15 @@ actual fun TitleText(text: String) {
text, Modifier.padding(0.dp, 8.dp), fontSize = 18.sp text, Modifier.padding(0.dp, 8.dp), fontSize = 18.sp
) )
} }
@Composable
actual fun <T> ButtonsPanel(data: Iterable<T>, itemDrawer: @Composable (T) -> Unit) {
Row {
data.forEach { itemDrawer(it) }
}
}
@Composable
actual fun DefaultDivider() {
Divider()
}

View File

@ -2,33 +2,29 @@ package dev.inmo.kmppscriptbuilder.core.utils
import dev.inmo.kmppscriptbuilder.core.models.Config import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.core.ui.utils.FileFilter import dev.inmo.kmppscriptbuilder.core.ui.utils.FileFilter
import dev.inmo.kmppscriptbuilder.core.utils.serialFormat
import dev.inmo.micro_utils.common.MPPFile import dev.inmo.micro_utils.common.MPPFile
import java.io.File import java.io.File
import javax.swing.JFileChooser import javax.swing.JFileChooser
private const val appExtension = "kpsb" fun MPPFile.text() = readText()
private var lastFile: File? = null internal var lastFile: MPPFile? = null
fun loadConfigFile(file: File): Config { fun loadConfigFile(file: MPPFile): Config {
lastFile = file lastFile = file
return serialFormat.decodeFromString(Config.serializer(), file.readText()) return serialFormat.decodeFromString(Config.serializer(), file.text())
} }
actual fun MPPFile.text() = readText() actual fun openNewConfig(onParsed: (Config) -> Unit) {
actual fun loadConfig(): Config? {
val fc = JFileChooser(lastFile ?.parent) val fc = JFileChooser(lastFile ?.parent)
fc.addChoosableFileFilter(FileFilter("Kotlin Publication Scripts Builder", Regex(".*\\.$appExtension"))) fc.addChoosableFileFilter(FileFilter("Kotlin Publication Scripts Builder", Regex(".*\\.$appExtension")))
fc.addChoosableFileFilter(FileFilter("JSON", Regex(".*\\.json"))) fc.addChoosableFileFilter(FileFilter("JSON", Regex(".*\\.json")))
return when (fc.showOpenDialog(null)) { when (fc.showOpenDialog(null)) {
JFileChooser.APPROVE_OPTION -> { JFileChooser.APPROVE_OPTION -> {
val file = fc.selectedFile val file = fc.selectedFile
lastFile = file lastFile = file
return serialFormat.decodeFromString(Config.serializer(), fc.selectedFile.readText()) onParsed(serialFormat.decodeFromString(Config.serializer(), fc.selectedFile.readText()))
} }
else -> null
} }
} }

View File

@ -1,32 +0,0 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
alias(libs.plugins.jb.compose)
}
apply from: "$mppJavaProjectPresetPath"
kotlin {
jvm()
sourceSets {
commonMain {
dependencies {
implementation project(":kmppscriptbuilder.core")
}
}
jvmMain {
dependencies {
implementation(compose.desktop.currentOs)
implementation libs.ktor.client.cio
}
}
}
}
compose.desktop {
application {
mainClass = "dev.inmo.kmppscriptbuilder.desktop.BuilderKt"
}
}

View File

@ -1,77 +0,0 @@
package dev.inmo.kmppscriptbuilder.desktop.utils
import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.core.ui.utils.FileFilter
import dev.inmo.kmppscriptbuilder.core.utils.serialFormat
import java.io.File
import javax.swing.JFileChooser
private const val appExtension = "kpsb"
private var lastFile: File? = null
fun loadConfigFile(file: File): Config {
lastFile = file
return serialFormat.decodeFromString(Config.serializer(), file.readText())
}
fun loadConfig(): Config? {
val fc = JFileChooser(lastFile ?.parent)
fc.addChoosableFileFilter(FileFilter("Kotlin Publication Scripts Builder", Regex(".*\\.$appExtension")))
fc.addChoosableFileFilter(FileFilter("JSON", Regex(".*\\.json")))
return when (fc.showOpenDialog(null)) {
JFileChooser.APPROVE_OPTION -> {
val file = fc.selectedFile
lastFile = file
return serialFormat.decodeFromString(Config.serializer(), fc.selectedFile.readText())
}
else -> null
}
}
fun saveConfig(config: Config): Boolean {
return lastFile ?.also {
it.writeText(serialFormat.encodeToString(Config.serializer(), config))
} != null
}
fun exportGradle(config: Config): Boolean {
val fc = JFileChooser(lastFile ?.parent)
fc.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
return when (fc.showSaveDialog(null)) {
JFileChooser.APPROVE_OPTION -> {
val file = fc.selectedFile
val mavenConfigContent = config.type.buildMavenGradleConfig(
config.mavenConfig,
config.licenses
)
File(file, "publish.gradle").apply {
delete()
createNewFile()
writeText(mavenConfigContent)
}
true
}
else -> false
}
}
fun saveAs(config: Config): Boolean {
val fc = JFileChooser(lastFile ?.parent)
fc.addChoosableFileFilter(FileFilter("Kotlin Publication Scripts Builder", Regex(".*\\.$appExtension")))
fc.addChoosableFileFilter(FileFilter("JSON", Regex(".*\\.json")))
return when (fc.showSaveDialog(null)) {
JFileChooser.APPROVE_OPTION -> {
val file = fc.selectedFile
val resultFile = if (file.extension == "") {
File(file.parentFile, "${file.name}.$appExtension")
} else {
file
}
resultFile.writeText(serialFormat.encodeToString(Config.serializer(), config))
lastFile = resultFile
true
}
else -> false
}
}

View File

@ -1,60 +0,0 @@
package dev.inmo.kmppscriptbuilder.desktop.utils
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
val commonTextFieldTextStyle = TextStyle(
fontSize = 12.sp
)
@Composable
inline fun TitleText(text: String) = Text(
text, Modifier.padding(0.dp, 8.dp), fontSize = 18.sp
)
@Composable
inline fun CommonText(text: String, modifier: Modifier = Modifier) = Text(
text, modifier = modifier
)
@Composable
inline fun CommonTextField(presetText: String, hint: String, noinline onChange: (String) -> Unit) = OutlinedTextField(
presetText,
onChange,
Modifier.fillMaxWidth(),
singleLine = true,
label = {
CommonText(hint)
}
)
@Composable
inline fun SwitchWithLabel(
label: String,
checked: Boolean,
placeSwitchAtTheStart: Boolean = false,
switchEnabled: Boolean = true,
modifier: Modifier = Modifier.padding(0.dp, 8.dp),
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
switchModifier: Modifier = Modifier.padding(8.dp, 0.dp),
noinline onCheckedChange: (Boolean) -> Unit
) = Row(modifier, horizontalArrangement, verticalAlignment) {
val switchCreator = @Composable {
Switch(checked, onCheckedChange, switchModifier, enabled = switchEnabled)
}
if (placeSwitchAtTheStart) {
switchCreator()
}
CommonText(label, Modifier.align(Alignment.CenterVertically).clickable { })
if (!placeSwitchAtTheStart) {
switchCreator()
}
}

View File

@ -1,34 +0,0 @@
package dev.inmo.kmppscriptbuilder.desktop.utils
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
abstract class View {
protected open val defaultModifier = Modifier.fillMaxWidth().padding(8.dp)
@Composable
abstract fun build()
}
abstract class VerticalView(val title: String) : View() {
abstract val content: @Composable ColumnScope.() -> Unit
@Composable
override fun build() {
TitleText(title)
Column(
defaultModifier
) {
content()
}
Spacer(Modifier.fillMaxWidth().height(8.dp))
}
}
@Composable
fun <T : View> T.init(): T = apply {
build()
}

View File

@ -1,105 +0,0 @@
package dev.inmo.kmppscriptbuilder.desktop.views
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.desktop.utils.*
class BuilderView : View() {
private val projectTypeView = ProjectTypeView()
private val mavenInfoView = MavenInfoView()
private val licensesView = LicensesView()
private var saveAvailableState by mutableStateOf(false)
override val defaultModifier: Modifier = Modifier.fillMaxSize()
var config: Config
get() = Config(licensesView.licenses, mavenInfoView.mavenConfig, projectTypeView.projectType)
set(value) {
licensesView.licenses = value.licenses
mavenInfoView.mavenConfig = value.mavenConfig
projectTypeView.projectType = value.type
saveAvailableState = true
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun createIcon(
tooltip: String,
resource: String,
onClick: () -> Unit
) {
TooltipArea(
tooltip = {
Surface(
modifier = Modifier.shadow(4.dp),
color = MaterialTheme.colors.primarySurface,
shape = RoundedCornerShape(4.dp)
) {
Text(tooltip, modifier = Modifier.padding(10.dp), color = MaterialTheme.colors.onPrimary)
}
}
) {
IconButton(onClick) {
Image(
painter = painterResource(resource),
contentDescription = tooltip
)
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
override fun build() {
Box(Modifier.fillMaxSize()) {
Column() {
TopAppBar(
@Composable {
CommonText("Kotlin publication scripts builder", Modifier.clickable { println(config) })
},
actions = {
createIcon("Open file", "images/open_file.svg") {
loadConfig()?.also {
config = it
}
}
if (saveAvailableState) {
createIcon("Save", "images/save_file.svg") {
saveConfig(config)
}
}
if (saveAvailableState) {
createIcon("Export Gradle script", "images/export_gradle.svg") {
exportGradle(config)
}
}
createIcon("Save as", "images/save_as.svg") {
if (saveAs(config)) {
saveAvailableState = true
}
}
}
)
Column(Modifier.padding(8.dp)) {
projectTypeView.init()
Divider()
licensesView.init()
Divider()
mavenInfoView.init()
}
}
}
}
}

View File

@ -1,51 +0,0 @@
package dev.inmo.kmppscriptbuilder.desktop.views
import androidx.compose.runtime.*
import dev.inmo.kmppscriptbuilder.core.models.Developer
import dev.inmo.kmppscriptbuilder.desktop.utils.CommonTextField
class DeveloperState(
id: String = "",
name: String = "",
eMail: String = ""
) {
var id: String by mutableStateOf(id)
var name: String by mutableStateOf(name)
var eMail: String by mutableStateOf(eMail)
fun toDeveloper() = Developer(id, name, eMail)
}
private fun Developer.toDeveloperState() = DeveloperState(id, name, eMail)
class DevelopersView : ListView<DeveloperState>("Developers info") {
var developers: List<Developer>
get() = itemsList.map { it.toDeveloper() }
set(value) {
itemsList.clear()
itemsList.addAll(
value.map { it.toDeveloperState() }
)
}
override val addItemText: String = "Add developer"
override val removeItemText: String = "Remove developer"
override fun createItem(): DeveloperState = DeveloperState()
@Composable
override fun buildView(item: DeveloperState) {
CommonTextField(
item.id,
"Developer username"
) { item.id = it }
CommonTextField(
item.name,
"Developer name"
) { item.name = it }
CommonTextField(
item.eMail,
"Developer E-Mail"
) { item.eMail = it }
}
}

View File

@ -1,95 +0,0 @@
package dev.inmo.kmppscriptbuilder.desktop.views
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Divider
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.models.License
import dev.inmo.kmppscriptbuilder.core.models.getLicenses
import dev.inmo.kmppscriptbuilder.desktop.utils.*
import io.ktor.client.HttpClient
import kotlinx.coroutines.*
private class LicenseState(
id: String = "",
title: String = "",
url: String? = null
) {
var id: String by mutableStateOf(id)
var title: String by mutableStateOf(title)
var url: String? by mutableStateOf(url)
fun toLicense() = License(id, title, url)
}
private fun License.toLicenseState() = LicenseState(id, title, url)
class LicensesView: VerticalView("Licenses") {
private var licensesListState = mutableStateListOf<LicenseState>()
var licenses: List<License>
get() = licensesListState.map { it.toLicense() }
set(value) {
licensesListState.clear()
licensesListState.addAll(value.map { it.toLicenseState() })
}
private val availableLicensesState = mutableStateListOf<License>()
private val licensesOffersToShow = mutableStateListOf<License>()
private var licenseSearchFilter by mutableStateOf("")
init {
CoroutineScope(Dispatchers.Default).launch {
val client = HttpClient()
availableLicensesState.addAll(client.getLicenses().values)
client.close()
}
}
override val content: @Composable ColumnScope.() -> Unit = {
CommonTextField(licenseSearchFilter, "Search filter") { filterText ->
licenseSearchFilter = filterText
licensesOffersToShow.clear()
if (licenseSearchFilter.isNotEmpty()) {
licensesOffersToShow.addAll(
availableLicensesState.filter { filterText.all { symbol -> symbol.lowercaseChar() in it.title } }
)
}
}
Column {
licensesOffersToShow.forEach {
Column(Modifier.padding(16.dp, 8.dp, 8.dp, 8.dp)) {
CommonText(it.title, Modifier.clickable {
licensesListState.add(it.toLicenseState())
licenseSearchFilter = ""
licensesOffersToShow.clear()
})
Divider()
}
}
}
Button({ licensesListState.add(LicenseState()) }, Modifier.padding(8.dp)) {
CommonText("Add empty license")
}
licensesListState.forEach { license ->
Column(Modifier.padding(8.dp)) {
CommonTextField(
license.id,
"License ID"
) { license.id = it }
CommonTextField(
license.title,
"License title"
) { license.title = it }
CommonTextField(
license.url ?: "",
"License URL"
) { license.url = it }
Button({ licensesListState.remove(license) }, Modifier.padding(8.dp)) {
CommonText("Remove")
}
}
}
}
}

View File

@ -1,35 +0,0 @@
package dev.inmo.kmppscriptbuilder.desktop.views
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.desktop.utils.CommonText
import dev.inmo.kmppscriptbuilder.desktop.utils.VerticalView
abstract class ListView<T>(title: String) : VerticalView(title) {
protected val itemsList = mutableStateListOf<T>()
protected open val addItemText: String = "Add"
protected open val removeItemText: String = "Remove"
protected abstract fun createItem(): T
@Composable
protected abstract fun buildView(item: T)
override val content: @Composable ColumnScope.() -> Unit = {
Button({ itemsList.add(createItem()) }) {
CommonText(addItemText)
}
itemsList.forEach { item ->
Column(Modifier.padding(8.dp)) {
buildView(item)
Button({ itemsList.remove(item) }, Modifier.padding(8.dp)) {
CommonText(removeItemText)
}
}
}
}
}

View File

@ -1,104 +0,0 @@
package dev.inmo.kmppscriptbuilder.desktop.views
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.VerticalAlignmentLine
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.models.*
import dev.inmo.kmppscriptbuilder.desktop.utils.*
class MavenInfoView : VerticalView("Project information") {
private var projectNameProperty by mutableStateOf("")
private var projectDescriptionProperty by mutableStateOf("")
private var projectUrlProperty by mutableStateOf("")
private var projectVcsUrlProperty by mutableStateOf("")
private var gpgSignProperty by mutableStateOf<GpgSigning>(GpgSigning.Disabled)
private var publishToMavenCentralProperty by mutableStateOf(false)
private val developersView = DevelopersView()
private val repositoriesView = RepositoriesView()
var mavenConfig: MavenConfig
get() = MavenConfig(
projectNameProperty.ifBlank { defaultProjectName },
projectDescriptionProperty.ifBlank { defaultProjectDescription },
projectUrlProperty,
projectVcsUrlProperty,
developersView.developers,
repositoriesView.repositories + if (publishToMavenCentralProperty) {
listOf(SonatypeRepository)
} else {
emptyList()
},
gpgSignProperty
)
set(value) {
projectNameProperty = value.name
projectDescriptionProperty = value.description
projectUrlProperty = value.url
projectVcsUrlProperty = value.vcsUrl
gpgSignProperty = if (value.includeGpgSigning) {
GpgSigning.Enabled
} else {
value.gpgSigning
}
publishToMavenCentralProperty = value.repositories.any { it == SonatypeRepository }
developersView.developers = value.developers
repositoriesView.repositories = value.repositories.filter { it != SonatypeRepository }
// developersView.developers = value.developers
}
@Composable
private fun addGpgSigningButton(gpgSigning: GpgSigning) {
if (gpgSignProperty == gpgSigning) {
Button({}, Modifier.padding(8.dp)) {
Text(gpgSigning.name)
}
} else {
OutlinedButton(
{
gpgSignProperty = gpgSigning
},
Modifier.padding(8.dp)
) {
Text(gpgSigning.name)
}
}
}
override val content: @Composable ColumnScope.() -> Unit = {
CommonTextField(
projectNameProperty,
"Public project name"
) { projectNameProperty = it }
CommonTextField(
projectDescriptionProperty,
"Public project description"
) { projectDescriptionProperty = it }
CommonTextField(
projectUrlProperty,
"Public project URL"
) { projectUrlProperty = it }
CommonTextField(
projectVcsUrlProperty,
"Public project VCS URL (with .git)"
) { projectVcsUrlProperty = it }
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Gpg Signing: ")
addGpgSigningButton(GpgSigning.Disabled)
addGpgSigningButton(GpgSigning.Optional)
addGpgSigningButton(GpgSigning.Enabled)
}
SwitchWithLabel(
"Include publication to MavenCentral",
publishToMavenCentralProperty,
placeSwitchAtTheStart = true
) { publishToMavenCentralProperty = it }
developersView.init()
repositoriesView.init()
}
}

View File

@ -1,40 +0,0 @@
package dev.inmo.kmppscriptbuilder.desktop.views
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.models.*
import dev.inmo.kmppscriptbuilder.desktop.utils.VerticalView
class ProjectTypeView : VerticalView("Project type") {
var projectType by mutableStateOf<ProjectType>(MultiplatformProjectType)
@Composable
private fun addProjectTypeButton(newProjectType: ProjectType) {
if (projectType == newProjectType) {
Button({}, Modifier.padding(8.dp)) {
Text(newProjectType.name)
}
} else {
OutlinedButton(
{
projectType = newProjectType
},
Modifier.padding(8.dp)
) {
Text(newProjectType.name)
}
}
}
override val content: @Composable ColumnScope.() -> Unit = {
Row(verticalAlignment = Alignment.CenterVertically) {
addProjectTypeButton(MultiplatformProjectType)
addProjectTypeButton(JVMProjectType)
addProjectTypeButton(JSProjectType)
}
}
}

View File

@ -1,45 +0,0 @@
package dev.inmo.kmppscriptbuilder.desktop.views
import androidx.compose.runtime.*
import dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository
import dev.inmo.kmppscriptbuilder.desktop.utils.CommonTextField
class RepositoryState(
name: String = "",
url: String = ""
) {
var name: String by mutableStateOf(name)
var url: String by mutableStateOf(url)
fun toRepository() = MavenPublishingRepository(name, url)
}
private fun MavenPublishingRepository.toRepositoryState() = RepositoryState(name, url)
class RepositoriesView : ListView<RepositoryState>("Repositories info") {
var repositories: List<MavenPublishingRepository>
get() = itemsList.map { it.toRepository() }
set(value) {
itemsList.clear()
itemsList.addAll(
value.map { it.toRepositoryState() }
)
}
override val addItemText: String = "Add repository"
override val removeItemText: String = "Remove repository"
override fun createItem(): RepositoryState = RepositoryState()
@Composable
override fun buildView(item: RepositoryState) {
CommonTextField(
item.name,
"Repository name"
) { item.name = it }
CommonTextField(
item.url,
"Repository url"
) { item.url = it }
}
}

View File

@ -7,6 +7,7 @@ kt-coroutines = "1.6.4"
jb-compose = "1.2.1" jb-compose = "1.2.1"
jb-dokka = "1.7.20" jb-dokka = "1.7.20"
microutils = "0.14.2" microutils = "0.14.2"
kjsuikit = "0.4.1"
ktor = "2.1.3" ktor = "2.1.3"
@ -22,10 +23,13 @@ kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", vers
ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
microutils-common = { module = "dev.inmo:micro_utils.common", version.ref = "microutils" } microutils-common = { module = "dev.inmo:micro_utils.common", 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" }
jsuikit = { module = "dev.inmo:kjsuikit", version.ref = "kjsuikit" }
kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" } kt-test-js = { module = "org.jetbrains.kotlin:kotlin-test-js", version.ref = "kt" }
kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" } kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }

View File

@ -9,8 +9,8 @@ rootProject.name = 'kmppscriptbuilder'
String[] includes = [ String[] includes = [
":core", ":core",
":desktop", // ":desktop",
":web" // ":web"
] ]

View File

@ -1,17 +0,0 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
}
apply from: "$mppJsProjectPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
implementation project(":kmppscriptbuilder.core")
implementation "dev.inmo:micro_utils.common:$micro_utils_version"
}
}
}
}

View File

@ -1,82 +0,0 @@
package dev.inmo.kmppscriptbuilder.web
import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.core.utils.serialFormat
import dev.inmo.kmppscriptbuilder.web.views.*
import kotlinx.browser.document
import kotlinx.dom.appendElement
import org.w3c.dom.*
import org.w3c.dom.url.URL
import org.w3c.files.*
fun saveFile(content: String, filename: String) {
val a = document.body!!.appendElement("a") {
setAttribute("style", "visibility:hidden; display: none")
} as HTMLAnchorElement
val blob = Blob(arrayOf(content), BlobPropertyBag(
"application/*;charset=utf-8"
))
val url = URL.createObjectURL(blob)
a.href = url
a.download = filename
a.click()
URL.revokeObjectURL(url)
a.remove()
}
fun main() {
document.addEventListener(
"DOMContentLoaded",
{
val builderView = BuilderView()
(document.getElementById("openConfig") as HTMLElement).onclick = {
val targetInput = document.body!!.appendElement("input") {
setAttribute("style", "visibility:hidden; display: none")
} as HTMLInputElement
targetInput.type = "file"
targetInput.onchange = {
targetInput.files ?.also { files ->
for (i in (0 until files.length) ) {
files[i] ?.also { file ->
val reader = FileReader()
reader.onload = {
val content = it.target.asDynamic().result as String
builderView.config = serialFormat.decodeFromString(Config.serializer(), content)
false
}
reader.readAsText(file)
}
}
}
}
targetInput.click()
targetInput.remove()
false
}
(document.getElementById("saveConfig") as HTMLElement).onclick = {
val filename = "publish.kpsb"
val content = serialFormat.encodeToString(Config.serializer(), builderView.config)
saveFile(content, filename)
false
}
(document.getElementById("exportScript") as HTMLElement).onclick = {
val filename = "publish.gradle"
val content = builderView.config.run {
type.buildMavenGradleConfig(
mavenConfig,
licenses
)
}
saveFile(content, filename)
false
}
}
)
}

View File

@ -1,13 +0,0 @@
package dev.inmo.kmppscriptbuilder.web.utils
import org.w3c.dom.HTMLElement
var HTMLElement.ukActive: Boolean
get() = classList.contains("uk-active")
set(value) {
if (value) {
classList.add("uk-active")
} else {
classList.remove("uk-active")
}
}

View File

@ -1,10 +0,0 @@
package dev.inmo.kmppscriptbuilder.web.utils
import kotlinx.browser.document
inline fun <R> keepScrolling(crossinline block: () -> R): R = document.body ?.let {
val (x, y) = (it.scrollLeft to it.scrollTop)
return block().also { _ ->
it.scrollTo(x, y)
}
} ?: block()

View File

@ -1,23 +0,0 @@
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
}
}

View File

@ -1,35 +0,0 @@
package dev.inmo.kmppscriptbuilder.web.views
import dev.inmo.kmppscriptbuilder.core.models.Developer
import org.w3c.dom.*
class DevelopersView(rootElement: HTMLElement) : MutableListView<Developer>(rootElement, "Add developer", "Remove developer") {
private val HTMLElement.usernameElement: HTMLInputElement
get() = getElementsByTagName("input")[0] as HTMLInputElement
private val HTMLElement.nameElement: HTMLInputElement
get() = getElementsByTagName("input")[1] as HTMLInputElement
private val HTMLElement.emailElement: HTMLInputElement
get() = getElementsByTagName("input")[2] as HTMLInputElement
var developers: List<Developer>
get() = elements.map {
Developer(it.usernameElement.value, it.nameElement.value, it.emailElement.value)
}
set(value) {
data = value
}
override fun createPlainObject(): Developer = 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
}
override fun HTMLElement.updateElement(from: Developer, to: Developer) {
usernameElement.value = to.id
nameElement.value = to.name
emailElement.value = to.eMail
}
}

View File

@ -1,113 +0,0 @@
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<License>(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<License>(rootElement, useSimpleDiffStrategy = true) {
private var licensesTemplates: List<License> = emptyList()
init {
scope.launch {
licensesTemplates = client.getLicenses().values.toList()
changeActor.send(Unit) // update list of searches
}
}
private val changeActor: SendChannel<Unit> = scope.run {
val onChangeActor = Channel<Unit>(Channel.CONFLATED)
onChangeActor.consumeAsFlow().subscribeSafelyWithoutExceptions(scope) {
val lowercased = searchString
data = if (lowercased.isEmpty()) {
emptyList()
} else {
licensesTemplates.filter {
val lowercasedTitle = it.title.lowercase()
lowercased.all { it in lowercasedTitle }
}
}
}
onChangeActor
}
private val searchElement = rootElement.createTextField("Quick add", "Type some license name part to find it").apply {
oninput = {
changeActor.trySend(Unit)
false
}
}
private var searchString: String
get() = searchElement.value.lowercase()
set(value) {
searchElement.value = value
}
override fun HTMLElement.placeElement(value: License) {
createCommonButton(value.title).onclick = {
searchString = ""
licensesView.licenses += value
changeActor.trySend(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<License>
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 ?: ""
}
}

View File

@ -1,58 +0,0 @@
package dev.inmo.kmppscriptbuilder.web.views
import dev.inmo.micro_utils.common.calculateStrictDiff
import kotlinx.dom.appendElement
import org.w3c.dom.HTMLElement
abstract class ListView<T>(
protected val rootElement: HTMLElement,
useSimpleDiffStrategy: Boolean = false
) : View {
protected val elements = mutableListOf<HTMLElement>()
private val diffHandling: (old: List<T>, new: List<T>) -> 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<T> = emptyList()
set(value) {
val old = field
field = value
diffHandling(old, value)
}
protected abstract fun HTMLElement.placeElement(value: T)
protected abstract fun HTMLElement.updateElement(from: T, to: T)
private fun instantiateElement() = rootElement.appendElement("div") {
classList.add("uk-padding-small")
} as HTMLElement
}

View File

@ -1,82 +0,0 @@
package dev.inmo.kmppscriptbuilder.web.views
import dev.inmo.kmppscriptbuilder.core.models.*
import dev.inmo.kmppscriptbuilder.web.utils.ukActive
import kotlinx.browser.document
import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLInputElement
class MavenProjectInfoView : View {
private val nameElement = document.getElementById("projectNameInput") as HTMLInputElement
private val descriptionElement = document.getElementById("projectDescriptionInput") as HTMLInputElement
private val urlElement = document.getElementById("projectUrlInput") as HTMLInputElement
private val vcsUrlElement = document.getElementById("projectVCSUrlInput") as HTMLInputElement
private val disableGpgSigningElement = document.getElementById("disableGpgSigning") as HTMLElement
private val optionalGpgSigningElement = document.getElementById("optionalGpgSigning") as HTMLElement
private val enableGpgSigningElement = document.getElementById("enableGpgSigning") as HTMLElement
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)
private var gpgSignMode: GpgSigning = GpgSigning.Disabled
set(value) {
field = value
when (value) {
GpgSigning.Enabled -> {
enableGpgSigningElement.ukActive = true
disableGpgSigningElement.ukActive = false
optionalGpgSigningElement.ukActive = false
}
GpgSigning.Optional -> {
enableGpgSigningElement.ukActive = false
disableGpgSigningElement.ukActive = false
optionalGpgSigningElement.ukActive = true
}
GpgSigning.Disabled -> {
enableGpgSigningElement.ukActive = false
disableGpgSigningElement.ukActive = true
optionalGpgSigningElement.ukActive = false
}
}
}
var mavenConfig: MavenConfig
get() = MavenConfig(
nameElement.value.ifBlank { defaultProjectName },
descriptionElement.value.ifBlank { defaultProjectDescription },
urlElement.value,
vcsUrlElement.value,
developersView.developers,
repositoriesView.repositories + if (includeMavenCentralElement.checked) {
listOf(SonatypeRepository)
} else {
emptyList()
},
when {
optionalGpgSigningElement.ukActive -> GpgSigning.Optional
enableGpgSigningElement.ukActive -> GpgSigning.Enabled
else -> GpgSigning.Disabled
}
)
set(value) {
nameElement.value = value.name
descriptionElement.value = value.description
urlElement.value = value.url
vcsUrlElement.value = value.vcsUrl
gpgSignMode = if (value.includeGpgSigning) {
GpgSigning.Enabled
} else {
value.gpgSigning
}
developersView.developers = value.developers
val reposWithoutSonatype = value.repositories.filter { it != SonatypeRepository }
includeMavenCentralElement.checked = value.repositories.size != reposWithoutSonatype.size
repositoriesView.repositories = reposWithoutSonatype
}
init {
enableGpgSigningElement.onclick = { gpgSignMode = GpgSigning.Enabled; Unit }
disableGpgSigningElement.onclick = { gpgSignMode = GpgSigning.Disabled; Unit }
optionalGpgSigningElement.onclick = { gpgSignMode = GpgSigning.Optional; Unit }
}
}

View File

@ -1,41 +0,0 @@
package dev.inmo.kmppscriptbuilder.web.views
import dev.inmo.kmppscriptbuilder.web.utils.keepScrolling
import org.w3c.dom.HTMLElement
abstract class MutableListView<T>(
rootElement: HTMLElement,
addButtonText: String = "Add",
private val removeButtonText: String = "Remove"
) : ListView<T>(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
}
}
}

View File

@ -1,39 +0,0 @@
package dev.inmo.kmppscriptbuilder.web.views
import dev.inmo.kmppscriptbuilder.core.models.*
import dev.inmo.kmppscriptbuilder.web.utils.ukActive
import kotlinx.browser.document
import org.w3c.dom.HTMLElement
class ProjectTypeView : View {
private val mppProjectTypeElement = document.getElementById("mppProjectType") as HTMLElement
private val jvmProjectTypeElement = document.getElementById("jvmProjectType") as HTMLElement
private val jsProjectTypeElement = document.getElementById("jsProjectType") as HTMLElement
var projectType: ProjectType
get() = when {
jvmProjectTypeElement.ukActive -> JVMProjectType
jsProjectTypeElement.ukActive -> JSProjectType
else -> MultiplatformProjectType
}
set(value) {
mppProjectTypeElement.ukActive = value == MultiplatformProjectType
jvmProjectTypeElement.ukActive = value == JVMProjectType
jsProjectTypeElement.ukActive = value == JSProjectType
}
init {
mppProjectTypeElement.onclick = {
projectType = MultiplatformProjectType
Unit
}
jvmProjectTypeElement.onclick = {
projectType = JVMProjectType
Unit
}
jsProjectTypeElement.onclick = {
projectType = JSProjectType
Unit
}
}
}

View File

@ -1,31 +0,0 @@
package dev.inmo.kmppscriptbuilder.web.views
import dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository
import org.w3c.dom.*
class RepositoriesView(rootElement: HTMLElement) : MutableListView<MavenPublishingRepository>(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<MavenPublishingRepository>
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.url
}
override fun HTMLElement.updateElement(from: MavenPublishingRepository, to: MavenPublishingRepository) {
nameElement.value = to.name
urlElement.value = to.url
}
}

View File

@ -1,3 +0,0 @@
package dev.inmo.kmppscriptbuilder.web.views
interface View

View File

@ -1,35 +0,0 @@
package dev.inmo.kmppscriptbuilder.web.views
import kotlinx.dom.appendElement
import org.w3c.dom.*
fun HTMLElement.createTextField(
label: String,
placeholder: String
): HTMLInputElement {
return appendElement("div") {
classList.add("uk-margin", "uk-width-1-1")
}.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.createPrimaryButton(text: String): HTMLButtonElement = (appendElement("button") {
classList.add("uk-button", "uk-button-primary")
} 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
}

View File

@ -1,85 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Kotlin Publication Scripts Builder</title>
<!-- UIkit CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.6.17/dist/css/uikit.min.css" />
</head>
<body>
<nav class="uk-navbar-container" uk-navbar>
<div class="uk-navbar-left">
<div class="uk-padding-small uk-text-lead">Kotlin Publication Scripts Builder</div>
<div class="uk-padding-small"><a href="https://github.com/InsanusMokrassar/KotlinPublicationScriptsBuilder"><img src="https://img.shields.io/github/stars/InsanusMokrassar/KotlinPublicationScriptsBuilder?label=Github&style=plastic"/></a></div>
</div>
<div class="uk-navbar-right">
<ul class="uk-navbar-nav">
<li uk-tooltip="title: Open config" id="openConfig"><a href="#"><span uk-icon="icon: pull"></span></a></li><!--Open file-->
<li uk-tooltip="title: Save config" id="saveConfig"><a href="#"><span uk-icon="icon: push"></span></a></li><!--Save file-->
<li uk-tooltip="title: Export script" id="exportScript"><a href="#"><span uk-icon="icon: upload"></span></a></li><!--Save file-->
</ul>
</div>
</nav>
<form class="uk-padding-small">
<fieldset class="uk-fieldset">
<legend class="uk-legend">Project type</legend>
<div class="uk-padding-small">
<ul class="uk-subnav uk-subnav-pill">
<li id="mppProjectType" class="uk-active"><a href="#">Multiplatform</a></li>
<li id="jvmProjectType"><a href="#">JVM</a></li>
<li id="jsProjectType"><a href="#">JS</a></li>
</ul>
</div>
<legend class="uk-legend">Licenses</legend>
<div id="licensesListDiv" class="uk-padding-small"></div>
<legend class="uk-legend">Project information</legend>
<div class="uk-padding-small">
<div class="uk-margin uk-width-1-1">
<label class="uk-form-label" for="projectNameInput">Public project name</label>
<input id="projectNameInput" class="uk-input uk-width-expand" type="text" placeholder="${project.name}">
</div>
<div class="uk-margin uk-width-1-1">
<label class="uk-form-label" for="projectDescriptionInput">Public project description</label>
<input id="projectDescriptionInput" class="uk-input uk-width-expand" type="text" placeholder="${project.name}">
</div>
<div class="uk-margin uk-width-1-1">
<label class="uk-form-label" for="projectUrlInput">Public project URL</label>
<input id="projectUrlInput" class="uk-input uk-width-expand" type="text" placeholder="Type url to github or other source with readme">
</div>
<div class="uk-margin uk-width-1-1">
<label class="uk-form-label" for="projectVCSUrlInput">Public project VCS URL (with .git)</label>
<input id="projectVCSUrlInput" class="uk-input uk-width-expand" type="text" placeholder="Type url to github .git file">
</div>
<div class="uk-margin">
<label>GPG Signing</label>
<div class="uk-padding-small">
<ul class="uk-subnav uk-subnav-pill">
<li id="disableGpgSigning" class="uk-active" uk-tooltip="title: Signing will not be added"><a href="#">Disabled</a></li>
<li id="optionalGpgSigning" uk-tooltip="title: Signing will be added, but disabled in case of absence 'signatory.keyId'"><a href="#">Optional</a></li>
<li id="enableGpgSigning" uk-tooltip="title: Signing will be always enabled"><a href="#">Enabled</a></li>
</ul>
</div>
</div>
<div class="uk-margin">
<label><input id="includeMavenCentralTargetRepoToggle" class="uk-checkbox" type="checkbox"> Include publication to MavenCentral</label>
</div>
</div>
<legend class="uk-legend">Developers info</legend>
<div id="developersListDiv" class="uk-padding-small"></div>
<legend class="uk-legend">Repositories info</legend>
<div id="repositoriesListDiv" class="uk-padding-small"></div>
</fieldset>
</form>
<!-- UIkit JS -->
<script src="https://cdn.jsdelivr.net/npm/uikit@3.6.17/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.6.17/dist/js/uikit-icons.min.js"></script>
<!-- Internal JS -->
<script src="kmppscriptbuilder.web.js"></script>
</body>
</html>