Merge pull request #6 from InsanusMokrassar/different_credentials_support

Different credentials support
This commit is contained in:
InsanusMokrassar 2022-11-16 17:59:41 +06:00 committed by GitHub
commit 7536c67589
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 1798 additions and 1365 deletions

View File

@ -38,12 +38,12 @@ jobs:
draft: false draft: false
prerelease: true prerelease: true
- name: Upload Ubuntu Release Asset - name: Upload Ubuntu Release Asset
id: upload-ubuntu-release-asset id: upload-ubuntu-release-asset
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: "./desktop/build/compose/jars/kmppscriptbuilder.desktop-linux-x64-${{ env.version }}${{ env.additional_version }}.jar" asset_path: "./core/build/compose/jars/kmppscriptbuilder.desktop-linux-x64-${{ env.version }}${{ env.additional_version }}.jar"
asset_name: KotlinPublicationScriptsBuilder-linux-x64.jar asset_name: KotlinPublicationScriptsBuilder-linux-x64.jar
asset_content_type: application/java-archive asset_content_type: application/java-archive

View File

@ -7,11 +7,10 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.0.4' classpath libs.buildscript.kt.gradle
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath libs.buildscript.kt.serialization
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath libs.buildscript.jb.dokka
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version" classpath libs.buildscript.gh.release
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
} }
} }

View File

@ -1,20 +1,46 @@
plugins { plugins {
id "org.jetbrains.kotlin.multiplatform" id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library" alias(libs.plugins.jb.compose)
} }
apply from: "$mppProjectWithSerializationPresetPath" apply from: "$mppProjectWithSerializationPresetPath"
kotlin { kotlin {
js (IR) {
binaries.executable()
}
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" api libs.kt.coroutines
api "dev.inmo:micro_utils.coroutines:$micro_utils_version" api libs.microutils.common
api libs.microutils.coroutines
api "io.ktor:ktor-client-core:$ktor_version" api libs.ktor.client
api(compose.runtime)
}
}
jsMain {
dependencies {
implementation(compose.web.core)
api libs.ktor.client.js
api libs.jsuikit
}
}
jvmMain {
dependencies {
implementation(compose.desktop.currentOs)
api libs.ktor.client.cio
} }
} }
} }
} }
compose {
desktop {
application {
mainClass = "dev.inmo.kmppscriptbuilder.desktop.BuilderKt"
}
}
}

View File

@ -4,6 +4,7 @@ import dev.inmo.kmppscriptbuilder.core.utils.serialFormat
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.url import io.ktor.client.request.url
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer
@ -20,9 +21,9 @@ private val commonLicensesListDeserializer = MapSerializer(String.serializer(),
private var licenses: Map<String, License>? = null private var licenses: Map<String, License>? = null
suspend fun HttpClient.getLicenses(): Map<String, License> { suspend fun HttpClient.getLicenses(): Map<String, License> {
val answer = get<String> { val answer = get {
url("https://licenses.opendefinition.org/licenses/groups/all.json") url("https://licenses.opendefinition.org/licenses/groups/all.json")
} }.bodyAsText()
return serialFormat.decodeFromString(commonLicensesListDeserializer, answer).also { gotLicenses -> return serialFormat.decodeFromString(commonLicensesListDeserializer, answer).also { gotLicenses ->
licenses = gotLicenses licenses = gotLicenses
} }

View File

@ -31,23 +31,84 @@ data class MavenConfig(
@Serializable @Serializable
data class MavenPublishingRepository( data class MavenPublishingRepository(
val name: String, val name: String,
val url: String val url: String,
val credsType: CredentialsType = CredentialsType.UsernameAndPassword(
CredentialsType.UsernameAndPassword.defaultUsernameProperty(name),
CredentialsType.UsernameAndPassword.defaultPasswordProperty(name),
)
) { ) {
private val nameCapitalized by lazy {
name.toUpperCase()
}
fun build(indent: String): String { @Serializable
val usernameProperty = "${nameCapitalized}_USER" sealed interface CredentialsType {
val passwordProperty = "${nameCapitalized}_PASSWORD" @Serializable
return """if ((project.hasProperty('${usernameProperty}') || System.getenv('${usernameProperty}') != null) && (project.hasProperty('${passwordProperty}') || System.getenv('${passwordProperty}') != null)) { object Nothing: CredentialsType {
maven { override fun buildCheckPart(): String = "true"
name = "$name" override fun buildCredsPart(): String = ""
url = uri("$url") }
@Serializable
data class UsernameAndPassword(
val usernameProperty: String,
val passwordProperty: String
): CredentialsType {
constructor(baseParameter: String) : this(
defaultUsernameProperty(baseParameter),
defaultPasswordProperty(baseParameter)
)
override fun buildCheckPart(): String = "(project.hasProperty('${usernameProperty}') || System.getenv('${usernameProperty}') != null) && (project.hasProperty('${passwordProperty}') || System.getenv('${passwordProperty}') != null)"
override fun buildCredsPart(): String {
return """
credentials { credentials {
username = project.hasProperty('${usernameProperty}') ? project.property('${usernameProperty}') : System.getenv('${usernameProperty}') username = project.hasProperty('${usernameProperty}') ? project.property('${usernameProperty}') : System.getenv('${usernameProperty}')
password = project.hasProperty('${passwordProperty}') ? project.property('${passwordProperty}') : System.getenv('${passwordProperty}') password = project.hasProperty('${passwordProperty}') ? project.property('${passwordProperty}') : System.getenv('${passwordProperty}')
} }
"""
}
companion object {
fun defaultUsernameProperty(name: String): String {
return "${name.uppercase()}_USER"
}
fun defaultPasswordProperty(name: String): String {
return "${name.uppercase()}_PASSWORD"
}
}
}
@Serializable
data class HttpHeaderCredentials(
val headerName: String,
val headerValueProperty: String
): CredentialsType {
override fun buildCheckPart(): String = "project.hasProperty('${headerValueProperty}') || System.getenv('${headerValueProperty}') != null"
override fun buildCredsPart(): String {
return """
credentials(HttpHeaderCredentials) {
name = "$headerName"
value = project.hasProperty('${headerValueProperty}') ? project.property('${headerValueProperty}') : System.getenv('${headerValueProperty}')
}
"""
}
companion object {
fun defaultValueProperty(name: String): String {
return "${name.uppercase()}_TOKEN"
}
}
}
fun buildCheckPart(): String
fun buildCredsPart(): String
}
private val nameCapitalized by lazy {
name.uppercase()
}
fun build(indent: String): String {
return """if (${credsType.buildCheckPart()}) {
maven {
name = "$name"
url = uri("$url")
${credsType.buildCredsPart()}
} }
}""".replace("\n", "\n$indent") }""".replace("\n", "\n$indent")
} }

View File

@ -0,0 +1,56 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultContentColumn
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultDivider
@Composable
expect fun TopAppBar(
config: Config,
saveAvailable: Boolean,
onSaveAvailable: (Boolean) -> Unit,
onNewConfig: (Config) -> Unit
)
class BuilderView : View() {
internal val projectTypeView by mutableStateOf(ProjectTypeView())
internal val mavenInfoView by mutableStateOf(MavenInfoView())
internal val licensesView by mutableStateOf(LicensesView())
internal var saveAvailableState by mutableStateOf(false)
var config: Config
get() {
return Config(licensesView.licenses, mavenInfoView.mavenConfig, projectTypeView.projectType)
}
set(value) {
licensesView.licenses = value.licenses
mavenInfoView.mavenConfig = value.mavenConfig
projectTypeView.projectType = value.type
saveAvailableState = true
}
@Composable
override fun build() {
TopAppBar(
config,
saveAvailableState,
{
saveAvailableState = it
}
) {
config = it
}
DefaultContentColumn {
projectTypeView.build()
DefaultDivider()
licensesView.build()
DefaultDivider()
mavenInfoView.build()
}
}
}

View File

@ -1,8 +1,13 @@
package dev.inmo.kmppscriptbuilder.desktop.views package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.Developer import dev.inmo.kmppscriptbuilder.core.models.Developer
import dev.inmo.kmppscriptbuilder.desktop.utils.CommonTextField import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonText
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultSmallVerticalMargin
class DeveloperState( class DeveloperState(
id: String = "", id: String = "",
@ -22,10 +27,10 @@ class DevelopersView : ListView<DeveloperState>("Developers info") {
var developers: List<Developer> var developers: List<Developer>
get() = itemsList.map { it.toDeveloper() } get() = itemsList.map { it.toDeveloper() }
set(value) { set(value) {
itemsList.clear() itemsList.apply {
itemsList.addAll( clear()
value.map { it.toDeveloperState() } addAll(value.map { it.toDeveloperState() })
) }
} }
override val addItemText: String = "Add developer" override val addItemText: String = "Add developer"
@ -34,18 +39,19 @@ class DevelopersView : ListView<DeveloperState>("Developers info") {
override fun createItem(): DeveloperState = DeveloperState() override fun createItem(): DeveloperState = DeveloperState()
@Composable @Composable
override fun buildView(item: DeveloperState) { override fun buildView(item: DeveloperState) {
CommonText("Developer username")
CommonTextField( CommonTextField(
item.id, item.id,
"Developer username"
) { item.id = it } ) { item.id = it }
DefaultSmallVerticalMargin()
CommonText("Developer name")
CommonTextField( CommonTextField(
item.name, item.name,
"Developer name"
) { item.name = it } ) { item.name = it }
DefaultSmallVerticalMargin()
CommonText("Developer E-Mail")
CommonTextField( CommonTextField(
item.eMail, item.eMail,
"Developer E-Mail"
) { item.eMail = it } ) { item.eMail = it }
} }
} }

View File

@ -0,0 +1,103 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.License
import dev.inmo.kmppscriptbuilder.core.models.getLicenses
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonText
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultSmallVerticalMargin
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import io.ktor.client.HttpClient
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
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)
}
internal fun License.toLicenseState() = LicenseState(id, title, url)
expect object LicensesDrawer : Drawer<LicensesView>
class LicensesView : ListView<LicenseState>("Licenses") {
var licenses: List<License>
get() = itemsList.map { it.toLicense() }
set(value) {
itemsList.clear()
itemsList.addAll(value.map { it.toLicenseState() })
}
internal val availableLicensesState = mutableStateListOf<License>()
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)
}
}
override val addItemText: String
get() = "Add empty license"
init {
CoroutineScope(Dispatchers.Default).launch {
val client = HttpClient()
availableLicensesState.addAll(client.getLicenses().values)
client.close()
}
}
override fun createItem(): LicenseState = LicenseState()
@Composable
override fun buildView(item: LicenseState) {
CommonText("License ID")
CommonTextField(
item.id,
"Short name like \"Apache-2.0\"",
) { item.id = it }
CommonText("License title")
CommonTextField(
item.title,
"Official title of license (like \"Apache Software License 2.0\")",
) { item.title = it }
CommonText("License URL")
CommonTextField(
item.url ?: "",
"Link to your LICENSE file OR official license file (like \"https://opensource.org/licenses/Apache-2.0\")",
) { item.url = it }
}
override val content: @Composable () -> Unit = {
CommonTextField(
licenseSearchFilter,
"Search filter",
onFocusChanged = {
searchFieldFocused.value = it
}
) { filterText ->
licenseSearchFilter = filterText
}
DefaultSmallVerticalMargin()
with(LicensesDrawer) { draw() }
DefaultSmallVerticalMargin()
super.content()
}
}

View File

@ -0,0 +1,26 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
expect class ListViewDrawer<T>() : Drawer<ListView<T>>
abstract class ListView<T>(title: String) : VerticalView(title) {
internal val itemsList = mutableStateListOf<T>()
internal open val addItemText: String = "Add"
internal open val removeItemText: String = "Remove"
internal abstract fun createItem(): T
@Composable
internal abstract fun buildView(item: T)
protected val drawer = ListViewDrawer<T>()
override val content: @Composable () -> Unit = {
with(drawer) {
draw()
}
}
}

View File

@ -0,0 +1,106 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.*
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.CommonText
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultSmallVerticalMargin
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.ui.utils.SwitchWithLabel
expect class GpgSigningOptionDrawer : Drawer<GpgSigning>
expect fun GpgSigningOptionDrawerWithView(view: MavenInfoView): GpgSigningOptionDrawer
class MavenInfoView : VerticalView("Project information") {
internal var projectNameProperty by mutableStateOf("")
internal var projectDescriptionProperty by mutableStateOf("")
internal var projectUrlProperty by mutableStateOf("")
internal var projectVcsUrlProperty by mutableStateOf("")
internal var gpgSignProperty by mutableStateOf<GpgSigning>(GpgSigning.Disabled)
internal var publishToMavenCentralProperty by mutableStateOf(false)
internal val developersView = DevelopersView()
internal 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 }
}
private val gpgSigningDrawer = GpgSigningOptionDrawerWithView(this)
override val content: @Composable () -> Unit = {
CommonText("Public project name")
CommonTextField(
projectNameProperty,
"\${project.name}",
) { projectNameProperty = it }
DefaultSmallVerticalMargin()
CommonText("Public project description")
CommonTextField(
projectDescriptionProperty,
"\${project.name}",
) { projectDescriptionProperty = it }
DefaultSmallVerticalMargin()
CommonText("Public project URL")
CommonTextField(
projectUrlProperty,
"Type url to github or other source with readme",
) { projectUrlProperty = it }
DefaultSmallVerticalMargin()
CommonText("Public project VCS URL (with .git)")
CommonTextField(
projectVcsUrlProperty,
"Type url to github .git file"
) { projectVcsUrlProperty = it }
ButtonsPanel(
"Gpg signing",
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

@ -0,0 +1,36 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.ProjectType
import dev.inmo.kmppscriptbuilder.core.ui.utils.ButtonsPanel
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultSmallVerticalMargin
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
expect class ProjectTypeDrawer : Drawer<ProjectType>
expect fun ProjectTypeDrawerWithView(view: ProjectTypeView): ProjectTypeDrawer
class ProjectTypeView : VerticalView("Project type") {
var projectType by mutableStateOf<ProjectType>(MultiplatformProjectType)
private val typeDrawer = ProjectTypeDrawerWithView(this)
override val content: @Composable () -> Unit = {
ButtonsPanel(
"Project type",
MultiplatformProjectType,
JVMProjectType,
JSProjectType
) {
with(typeDrawer) {
with (it) {
draw()
}
}
}
}
}

View File

@ -0,0 +1,142 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository
import dev.inmo.kmppscriptbuilder.core.ui.utils.ButtonsPanel
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonText
import dev.inmo.kmppscriptbuilder.core.ui.utils.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultContentColumn
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultSmallVerticalMargin
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
class RepositoryState(
name: String = "",
url: String = "",
credsType: MavenPublishingRepository.CredentialsType = MavenPublishingRepository.CredentialsType.UsernameAndPassword(name)
) {
var name: String by mutableStateOf(name)
var url: String by mutableStateOf(url)
var credsType by mutableStateOf(credsType)
fun toRepository() = MavenPublishingRepository(name, url, credsType)
}
private fun MavenPublishingRepository.toRepositoryState() = RepositoryState(name, url, credsType)
expect class RepositoryCredentialTypeDrawer : Drawer<MavenPublishingRepository.CredentialsType>
expect fun RepositoryCredentialTypeDrawerWithState(repositoryState: RepositoryState): RepositoryCredentialTypeDrawer
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) {
val credsTypesDrawer = remember {
RepositoryCredentialTypeDrawerWithState(item)
}
CommonText("Repository name")
CommonTextField(
item.name,
"This name will be used to identify repository in gradle"
) {
val previous = item.name
item.name = it
when (val currentCredsType = item.credsType) {
is MavenPublishingRepository.CredentialsType.HttpHeaderCredentials -> {
if (MavenPublishingRepository.CredentialsType.HttpHeaderCredentials.defaultValueProperty(previous) == currentCredsType.headerValueProperty) {
item.credsType = currentCredsType.copy(
headerValueProperty = MavenPublishingRepository.CredentialsType.HttpHeaderCredentials.defaultValueProperty(it)
)
}
}
MavenPublishingRepository.CredentialsType.Nothing -> {}
is MavenPublishingRepository.CredentialsType.UsernameAndPassword -> {
var current: MavenPublishingRepository.CredentialsType.UsernameAndPassword = currentCredsType
if (MavenPublishingRepository.CredentialsType.UsernameAndPassword.defaultUsernameProperty(previous) == currentCredsType.usernameProperty) {
current = current.copy(
usernameProperty = MavenPublishingRepository.CredentialsType.UsernameAndPassword.defaultUsernameProperty(it)
)
}
if (MavenPublishingRepository.CredentialsType.UsernameAndPassword.defaultPasswordProperty(previous) == currentCredsType.passwordProperty) {
current = current.copy(
passwordProperty = MavenPublishingRepository.CredentialsType.UsernameAndPassword.defaultPasswordProperty(it)
)
}
item.credsType = current
}
}
}
DefaultSmallVerticalMargin()
CommonText("Repository url")
CommonTextField(
item.url,
"For example: https://repo.maven.apache.org/maven2/"
) { item.url = it }
ButtonsPanel(
"Credentials type",
MavenPublishingRepository.CredentialsType.Nothing.takeIf { item.credsType != it } ?: item.credsType,
MavenPublishingRepository.CredentialsType.UsernameAndPassword(item.name).takeIf { item.credsType !is MavenPublishingRepository.CredentialsType.UsernameAndPassword } ?: item.credsType,
MavenPublishingRepository.CredentialsType.HttpHeaderCredentials(
"Authorization",
MavenPublishingRepository.CredentialsType.HttpHeaderCredentials.defaultValueProperty(item.name)
).takeIf { item.credsType !is MavenPublishingRepository.CredentialsType.HttpHeaderCredentials } ?: item.credsType,
) {
with(credsTypesDrawer) {
with(it) {
draw()
}
}
}
DefaultContentColumn {
when (val credsType = item.credsType) {
is MavenPublishingRepository.CredentialsType.HttpHeaderCredentials -> {
CommonText("Header name")
CommonTextField(credsType.headerName) {
item.credsType = credsType.copy(headerName = it)
}
DefaultSmallVerticalMargin()
CommonText("Property name")
CommonTextField(credsType.headerValueProperty) {
item.credsType = credsType.copy(headerValueProperty = it)
}
}
MavenPublishingRepository.CredentialsType.Nothing -> {
CommonText("No parameters for absence of credentials")
}
is MavenPublishingRepository.CredentialsType.UsernameAndPassword -> {
CommonText("Username property name")
CommonTextField(credsType.usernameProperty) {
item.credsType = credsType.copy(usernameProperty = it)
}
DefaultSmallVerticalMargin()
CommonText("Password property name")
CommonTextField(credsType.passwordProperty) {
item.credsType = credsType.copy(passwordProperty = it)
}
}
}
}
}
}

View File

@ -0,0 +1,14 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultContentColumn
abstract class VerticalView(protected val title: String) : View() {
abstract val content: @Composable () -> Unit
@Composable
override fun build() {
DefaultContentColumn {
DrawVertically(title, content)
}
}
}

View File

@ -0,0 +1,16 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
expect abstract class View() {
@Composable
abstract fun build()
}
@Composable
expect fun View.DrawVertically(title: String, block: @Composable () -> Unit)
@Composable
fun <T : View> T.init(): T = apply {
build()
}

View File

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

View File

@ -0,0 +1,49 @@
package dev.inmo.kmppscriptbuilder.core.ui.utils
import androidx.compose.runtime.Composable
@Composable
expect fun TitleText(text: String)
@Composable
expect fun CommonText(text: String, onClick: (() -> Unit)? = null)
@Composable
expect fun CommonTextField(
presetText: String,
hint: String? = null,
onFocusChanged: (Boolean) -> Unit = {},
onChange: (String) -> Unit
)
@Composable
expect fun SwitchWithLabel(
label: String,
checked: Boolean,
placeSwitchAtTheStart: Boolean = false,
switchEnabled: Boolean = true,
onCheckedChange: (Boolean) -> Unit
)
@Composable
expect fun <T> ButtonsPanel(
title: String,
data: Iterable<T>,
itemDrawer: @Composable (T) -> Unit
)
@Composable
fun <T> ButtonsPanel(
title: String,
vararg data: T,
itemDrawer: @Composable (T) -> Unit
) = ButtonsPanel(title, data.toList(), itemDrawer)
@Composable
expect fun DefaultDivider()
@Composable
expect fun DefaultSmallVerticalMargin()
@Composable
expect fun DefaultContentColumn(block: @Composable () -> Unit)

View File

@ -0,0 +1,14 @@
package dev.inmo.kmppscriptbuilder.core.utils
import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.micro_utils.common.MPPFile
internal const val appExtension = "kpsb"
expect fun openNewConfig(onParsed: (Config) -> Unit)
expect fun saveConfig(config: Config): Boolean
expect fun exportGradle(config: Config): Boolean
expect fun saveAs(config: Config): Boolean

View File

@ -0,0 +1,3 @@
package dev.inmo.kmppscriptbuilder.core.utils
expect fun openLink(link: String): 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,35 @@
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.UIKitTooltipModifier
import dev.inmo.jsuikit.modifiers.UIKitUtility
import dev.inmo.kmppscriptbuilder.core.models.GpgSigning
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.ui.utils.NoTransform
actual class GpgSigningOptionDrawer(
private val mavenInfoView: MavenInfoView
) : Drawer<GpgSigning> {
@Composable
override fun GpgSigning.draw() {
val tooltipModifier = UIKitTooltipModifier(
when (this) {
GpgSigning.Disabled -> "Signing will not be added"
GpgSigning.Enabled -> "Signing will be always enabled"
GpgSigning.Optional -> "Signing will be added, but disabled in case of absence 'signatory.keyId'"
}
)
if (mavenInfoView.gpgSignProperty == this) {
DefaultButton(name, UIKitButton.Type.Primary, UIKitButton.Size.Small, UIKitMargin.Small.Horizontal, UIKitUtility.NoTransform, UIKitUtility.Border.Rounded, tooltipModifier)
} else {
DefaultButton(name, UIKitButton.Type.Default, UIKitButton.Size.Small, UIKitMargin.Small.Horizontal, UIKitUtility.NoTransform, UIKitUtility.Border.Rounded, tooltipModifier) {
mavenInfoView.gpgSignProperty = this
}
}
}
}
actual fun GpgSigningOptionDrawerWithView(view: MavenInfoView): GpgSigningOptionDrawer = GpgSigningOptionDrawer(mavenInfoView = view)

View File

@ -0,0 +1,37 @@
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.UIKitUtility
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 dev.inmo.kmppscriptbuilder.core.ui.utils.NoTransform
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
) { _ ->
itemsList.add(it.toLicenseState())
licenseSearchFilter = ""
}
Divider.Common()
}
}
}

View File

@ -0,0 +1,28 @@
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.UIKitUtility
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultContentColumn
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.ui.utils.NoTransform
actual class ListViewDrawer<T> : Drawer<ListView<T>> {
@Composable
override fun ListView<T>.draw() {
itemsList.forEach { item ->
DefaultContentColumn {
buildView(item)
DefaultButton(removeItemText, UIKitButton.Type.Default, UIKitMargin.Small, UIKitUtility.NoTransform, UIKitUtility.Border.Rounded) {
itemsList.remove(item)
}
}
DefaultButton(addItemText, UIKitButton.Type.Primary, UIKitUtility.NoTransform, UIKitUtility.Border.Rounded ) { itemsList.add(createItem()) }
}
if (itemsList.isEmpty()) {
DefaultButton(addItemText, UIKitButton.Type.Primary, UIKitUtility.NoTransform, UIKitUtility.Border.Rounded ) { itemsList.add(createItem()) }
}
}
}

View File

@ -0,0 +1,28 @@
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.UIKitText
import dev.inmo.jsuikit.modifiers.UIKitUtility
import dev.inmo.kmppscriptbuilder.core.models.ProjectType
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.ui.utils.NoTransform
actual class ProjectTypeDrawer(
private val projectTypeView: ProjectTypeView
) : Drawer<ProjectType> {
@Composable
override fun ProjectType.draw() {
if (projectTypeView.projectType == this) {
DefaultButton(name, UIKitButton.Type.Primary, UIKitButton.Size.Small, UIKitMargin.Small.Horizontal, UIKitUtility.NoTransform, UIKitUtility.Border.Rounded)
} else {
DefaultButton(name, UIKitButton.Type.Default, UIKitButton.Size.Small, UIKitMargin.Small.Horizontal, UIKitUtility.NoTransform, UIKitUtility.Border.Rounded) {
projectTypeView.projectType = this
}
}
}
}
actual fun ProjectTypeDrawerWithView(view: ProjectTypeView): ProjectTypeDrawer = ProjectTypeDrawer(projectTypeView = view)

View File

@ -0,0 +1,32 @@
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.UIKitUtility
import dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
import dev.inmo.kmppscriptbuilder.core.ui.utils.NoTransform
actual class RepositoryCredentialTypeDrawer(
private val state: RepositoryState
) : Drawer<MavenPublishingRepository.CredentialsType> {
@Composable
override fun MavenPublishingRepository.CredentialsType.draw() {
val name = when (this@draw) {
is MavenPublishingRepository.CredentialsType.HttpHeaderCredentials -> "Headers"
MavenPublishingRepository.CredentialsType.Nothing -> "No"
is MavenPublishingRepository.CredentialsType.UsernameAndPassword -> "Username and password"
}
if (state.credsType == this) {
DefaultButton(name, UIKitButton.Type.Primary, UIKitButton.Size.Small, UIKitMargin.Small.Horizontal, UIKitUtility.NoTransform, UIKitUtility.Border.Rounded)
} else {
DefaultButton(name, UIKitButton.Type.Default, UIKitButton.Size.Small, UIKitMargin.Small.Horizontal, UIKitUtility.NoTransform, UIKitUtility.Border.Rounded) {
state.credsType = this
}
}
}
}
actual fun RepositoryCredentialTypeDrawerWithState(repositoryState: RepositoryState): RepositoryCredentialTypeDrawer = RepositoryCredentialTypeDrawer(repositoryState)

View File

@ -0,0 +1,92 @@
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.UIKitBackground
import dev.inmo.jsuikit.modifiers.UIKitInverse
import dev.inmo.jsuikit.modifiers.UIKitMargin
import dev.inmo.jsuikit.modifiers.UIKitModifier
import dev.inmo.jsuikit.modifiers.UIKitNavbar
import dev.inmo.jsuikit.modifiers.UIKitPadding
import dev.inmo.jsuikit.modifiers.UIKitSection
import dev.inmo.jsuikit.modifiers.UIKitText
import dev.inmo.jsuikit.modifiers.UIKitTooltipModifier
import dev.inmo.jsuikit.modifiers.attrsBuilder
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.Section
import org.jetbrains.compose.web.dom.Text
@Composable
actual fun TopAppBar(
config: Config,
saveAvailable: Boolean,
onSaveAvailable: (Boolean) -> Unit,
onNewConfig: (Config) -> Unit
) {
Section(attrsBuilder(UIKitSection.Style.Primary, UIKitInverse.Light)) {
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)
}
}
},
)
},
navModifiers = arrayOf(UIKitNavbar.Transparent)
)
}
}

View File

@ -0,0 +1,23 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.runtime.Composable
import dev.inmo.jsuikit.modifiers.UIKitForm
import dev.inmo.jsuikit.modifiers.builder
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultSmallVerticalMargin
import org.jetbrains.compose.web.dom.Legend
import org.jetbrains.compose.web.dom.Text
actual abstract class View {
@Composable
actual abstract fun build()
}
@Composable
actual fun View.DrawVertically(title: String, block: @Composable () -> Unit) {
Legend(UIKitForm.Legend.builder()) {
Text(title)
}
DefaultSmallVerticalMargin()
block()
DefaultSmallVerticalMargin()
}

View File

@ -0,0 +1,128 @@
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.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.UIKitUtility
import dev.inmo.jsuikit.modifiers.attrsBuilder
import dev.inmo.jsuikit.modifiers.builder
import dev.inmo.jsuikit.modifiers.include
import kotlinx.browser.window
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 {
window.setTimeout( // avoid immediate hiding of potential interface data with additional delay
{ onFocusChanged(false) },
100
)
}
},
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, UIKitUtility.NoTransform, UIKitUtility.Border.Rounded)
}
) {
if (checked) {
Icon.App.Check.drawAsFormInputPart(UIKitInverse.Light)
}
Text(label)
}
}
@Composable
actual fun <T> ButtonsPanel(
title: String,
data: Iterable<T>,
itemDrawer: @Composable (T) -> Unit
) {
Flex(UIKitFlex.Alignment.Vertical.Middle, UIKitMargin.Small) {
Div(UIKitMargin.Small.Horizontal.builder()) { Text(title) }
data.forEach { itemDrawer(it) }
}
}
@Composable
actual fun DefaultDivider() {
Divider.Common()
}
@Composable
actual fun DefaultSmallVerticalMargin() {
Div(UIKitMargin.Small.Top.builder())
}
@Composable
actual fun DefaultContentColumn(block: @Composable () -> Unit) {
Div(attrsBuilder(UIKitMargin.Small.Horizontal, UIKitMargin.Small.Vertical)) {
block()
}
}

View File

@ -0,0 +1,9 @@
package dev.inmo.kmppscriptbuilder.core.ui.utils
import dev.inmo.jsuikit.modifiers.UIKitCustom
import dev.inmo.jsuikit.modifiers.UIKitUtility
val ClassNoTransform = UIKitCustom(arrayOf("no-transform"))
val UIKitUtility.Companion.NoTransform
get() = ClassNoTransform

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
}

View File

@ -0,0 +1,4 @@
.no-transform {
text-transform: none;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,73 @@
<!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" />
<link rel="stylesheet" href="./css/internal.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

@ -0,0 +1,33 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.ui.utils.DefaultSmallVerticalMargin
import dev.inmo.kmppscriptbuilder.core.ui.utils.TitleText
actual abstract class View {
internal open val defaultModifier = Modifier.fillMaxWidth()
@Composable
actual abstract fun build()
}
@Composable
actual fun View.DrawVertically(
title: String,
block: @Composable () -> Unit
) {
TitleText(title)
DefaultSmallVerticalMargin()
Column(defaultModifier) {
block()
}
DefaultSmallVerticalMargin()
}

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, 0.dp)) {
Text(name)
}
} else {
OutlinedButton(
{
mavenInfoView.gpgSignProperty = this
},
Modifier.padding(8.dp, 0.dp)
) {
Text(name)
}
}
}
}
actual fun GpgSigningOptionDrawerWithView(view: MavenInfoView): GpgSigningOptionDrawer = GpgSigningOptionDrawer(mavenInfoView = view)

View File

@ -0,0 +1,31 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.Divider
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.CommonTextField
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual object LicensesDrawer : Drawer<LicensesView> {
@Composable
override fun LicensesView.draw() {
if (searchFieldFocused.value) {
Column {
licensesOffersToShow.value.forEach {
Column(Modifier.padding(16.dp, 8.dp, 8.dp, 8.dp)) {
CommonText(it.title) {
itemsList.add(it.toLicenseState())
licenseSearchFilter = ""
}
Divider()
}
}
}
}
}
}

View File

@ -0,0 +1,33 @@
package dev.inmo.kmppscriptbuilder.core.ui
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.OutlinedButton
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.DefaultContentColumn
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual class ListViewDrawer<T> : Drawer<ListView<T>> {
@Composable
override fun ListView<T>.draw() {
itemsList.forEach { item ->
DefaultContentColumn {
buildView(item)
OutlinedButton({ itemsList.remove(item) }, Modifier.padding(8.dp)) {
CommonText(removeItemText,)
}
}
Button({ itemsList.add(createItem()) }) {
CommonText(addItemText,)
}
}
if (itemsList.isEmpty()) {
Button({ itemsList.add(createItem()) }) {
CommonText(addItemText,)
}
}
}
}

View File

@ -0,0 +1,40 @@
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.JSProjectType
import dev.inmo.kmppscriptbuilder.core.models.JVMProjectType
import dev.inmo.kmppscriptbuilder.core.models.MultiplatformProjectType
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) {
Button({}, Modifier.padding(8.dp, 0.dp)) {
Text(name)
}
} else {
OutlinedButton(
{
projectTypeView.projectType = this
},
Modifier.padding(8.dp, 0.dp)
) {
Text(name)
}
}
}
}
actual fun ProjectTypeDrawerWithView(view: ProjectTypeView): ProjectTypeDrawer = ProjectTypeDrawer(projectTypeView = view)

View File

@ -0,0 +1,40 @@
package dev.inmo.kmppscriptbuilder.core.ui
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.Modifier
import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository
import dev.inmo.kmppscriptbuilder.core.ui.utils.Drawer
actual class RepositoryCredentialTypeDrawer(
private val state: RepositoryState
) : Drawer<MavenPublishingRepository.CredentialsType> {
@Composable
override fun MavenPublishingRepository.CredentialsType.draw() {
val name = when (this@draw) {
is MavenPublishingRepository.CredentialsType.HttpHeaderCredentials -> "Headers"
MavenPublishingRepository.CredentialsType.Nothing -> "No"
is MavenPublishingRepository.CredentialsType.UsernameAndPassword -> "Username and password"
}
if (state.credsType == this) {
Button({}, Modifier.padding(8.dp, 0.dp)) {
Text(name)
}
} else {
OutlinedButton(
{
state.credsType = this
},
Modifier.padding(8.dp, 0.dp)
) {
Text(name)
}
}
}
}
actual fun RepositoryCredentialTypeDrawerWithState(repositoryState: RepositoryState): RepositoryCredentialTypeDrawer = RepositoryCredentialTypeDrawer(repositoryState)

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

@ -0,0 +1,113 @@
package dev.inmo.kmppscriptbuilder.core.ui.utils
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Divider
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
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
actual fun SwitchWithLabel(
label: String,
checked: Boolean,
placeSwitchAtTheStart: Boolean,
switchEnabled: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
Row(Modifier.padding(0.dp, 8.dp).clickable { onCheckedChange(!checked) }, Arrangement.Start, Alignment.Top) {
val switchCreator = @Composable {
Switch(checked, null, Modifier.padding(8.dp, 0.dp), enabled = switchEnabled)
}
if (placeSwitchAtTheStart) {
switchCreator()
}
Box(Modifier.fillMaxWidth().align(Alignment.CenterVertically)) {
CommonText(label)
}
if (!placeSwitchAtTheStart) {
switchCreator()
}
}
}
@Composable
actual fun CommonTextField(
presetText: String,
hint: String?,
onFocusChanged: (Boolean) -> Unit,
onChange: (String) -> Unit
) {
OutlinedTextField(
presetText,
onChange,
Modifier.fillMaxWidth().onFocusChanged {
onFocusChanged(it.isFocused)
},
singleLine = true,
label = hint ?.let {
{
CommonText(hint)
}
}
)
}
@Composable
actual fun CommonText(text: String, onClick: (() -> Unit)?) {
Text(text, modifier = Modifier.run { onClick ?.let { clickable(onClick = it) } ?: this })
}
@Composable
actual fun TitleText(text: String) {
Text(
text, Modifier.padding(0.dp, 8.dp), fontSize = 18.sp
)
}
@Composable
actual fun <T> ButtonsPanel(
title: String,
data: Iterable<T>,
itemDrawer: @Composable (T) -> Unit
) {
Row {
Text(title, Modifier.padding(8.dp))
data.forEach { itemDrawer(it) }
}
}
@Composable
actual fun DefaultDivider() {
Divider()
}
@Composable
actual fun DefaultSmallVerticalMargin() {
Spacer(Modifier.padding(0.dp, 4.dp))
}
@Composable
actual fun DefaultContentColumn(block: @Composable () -> Unit) {
Column(Modifier.padding(8.dp)) {
block()
}
}

View File

@ -1,4 +1,4 @@
package dev.inmo.kmppscriptbuilder.desktop.utils package dev.inmo.kmppscriptbuilder.core.ui.utils
import java.io.File import java.io.File
import javax.swing.filechooser.FileFilter import javax.swing.filechooser.FileFilter

View File

@ -1,40 +1,40 @@
package dev.inmo.kmppscriptbuilder.desktop.utils 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.utils.serialFormat import dev.inmo.kmppscriptbuilder.core.ui.utils.FileFilter
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())
} }
fun loadConfig(): Config? { actual fun openNewConfig(onParsed: (Config) -> Unit) {
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
} }
} }
fun saveConfig(config: Config): Boolean { actual fun saveConfig(config: Config): Boolean {
return lastFile ?.also { return lastFile ?.also {
it.writeText(serialFormat.encodeToString(Config.serializer(), config)) it.writeText(serialFormat.encodeToString(Config.serializer(), config))
} != null } != null
} }
fun exportGradle(config: Config): Boolean { actual fun exportGradle(config: Config): Boolean {
val fc = JFileChooser(lastFile ?.parent) val fc = JFileChooser(lastFile ?.parent)
fc.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY fc.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY
return when (fc.showSaveDialog(null)) { return when (fc.showSaveDialog(null)) {
@ -55,7 +55,7 @@ fun exportGradle(config: Config): Boolean {
} }
} }
fun saveAs(config: Config): Boolean { actual fun saveAs(config: Config): Boolean {
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")))

View File

@ -1,9 +1,9 @@
package dev.inmo.kmppscriptbuilder.desktop.utils package dev.inmo.kmppscriptbuilder.core.utils
import java.awt.Desktop import java.awt.Desktop
import java.net.URI import java.net.URI
fun openLink(link: String): Boolean { actual fun openLink(link: String): Boolean {
val desktop = if (Desktop.isDesktopSupported()) Desktop.getDesktop() else null val desktop = if (Desktop.isDesktopSupported()) Desktop.getDesktop() else null
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
try { try {

View File

@ -0,0 +1,2 @@
package dev.inmo.kmppscriptbuilder.core.utils

View File

@ -1 +0,0 @@
<manifest package="dev.inmo.KotlinPublicationScriptsBuilder.core"/>

View File

@ -1,40 +0,0 @@
apply plugin: 'com.getkeepsafe.dexcount'
android {
compileSdkVersion "$android_compileSdkVersion".toInteger()
buildToolsVersion "$android_buildToolsVersion"
defaultConfig {
minSdkVersion "$android_minSdkVersion".toInteger()
targetSdkVersion "$android_compileSdkVersion".toInteger()
versionCode "${android_code_version}".toInteger()
versionName "$version"
}
buildTypes {
release {
minifyEnabled false
}
debug {
debuggable true
}
}
packagingOptions {
exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module'
exclude 'META-INF/kotlinx-serialization-cbor.kotlin_module'
exclude 'META-INF/kotlinx-serialization-properties.kotlin_module'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}

View File

@ -1,36 +0,0 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id("org.jetbrains.compose") version "$compose_version"
}
apply from: "$mppJavaProjectPresetPath"
kotlin {
jvm {
compilations.main.kotlinOptions {
jvmTarget = "11"
}
}
sourceSets {
commonMain {
dependencies {
implementation project(":kmppscriptbuilder.core")
}
}
jvmMain {
dependencies {
implementation(compose.desktop.currentOs)
api "io.ktor:ktor-client-cio:$ktor_version"
}
}
}
}
compose.desktop {
application {
mainClass = "dev.inmo.kmppscriptbuilder.desktop.BuilderKt"
}
}

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,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

@ -16,7 +16,6 @@ allprojects {
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle" mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppJsProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsProject.gradle" mppJsProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsProject.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle" defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"

View File

@ -3,30 +3,18 @@ org.gradle.parallel=true
kotlin.js.generate.externals=true kotlin.js.generate.externals=true
kotlin.incremental=true kotlin.incremental=true
kotlin.incremental.js=true kotlin.incremental.js=true
android.useAndroidX=true
android.enableJetifier=true
kotlin_version=1.6.10 kotlin_version=1.7.20
kotlin_coroutines_version=1.6.0 kotlin_coroutines_version=1.6.4
kotlin_serialisation_core_version=1.3.2 kotlin_serialisation_core_version=1.4.1
ktor_version=1.6.7 ktor_version=2.1.3
micro_utils_version=0.9.0 micro_utils_version=0.14.2
compose_version=1.0.1 compose_version=1.2.1
# ANDROID
android_minSdkVersion=21
android_compileSdkVersion=32
android_buildToolsVersion=32.0.0
dexcount_version=3.0.1
junit_version=4.12
test_ext_junit_version=1.1.2
espresso_core=3.3.0
# Dokka # Dokka
dokka_version=1.6.0 dokka_version=1.7.20
# Project data # Project data

45
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,45 @@
[versions]
kt = "1.7.20"
kt-serialization = "1.4.1"
kt-coroutines = "1.6.4"
jb-compose = "1.2.1"
jb-dokka = "1.7.20"
microutils = "0.14.2"
kjsuikit = "0.4.1"
ktor = "2.1.3"
gh-release = "2.4.1"
[libraries]
kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" }
kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" }
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-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
microutils-common = { module = "dev.inmo:micro_utils.common", 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-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kt" }
buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" }
buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
[plugins]
jb-compose = { id = "org.jetbrains.compose", version.ref = "jb-compose" }

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip

View File

@ -4,9 +4,7 @@ project.group = "$group"
// apply from: "$publishGradlePath" // apply from: "$publishGradlePath"
kotlin { kotlin {
jvm { jvm()
compilations.main.kotlinOptions.useIR = true
}
sourceSets { sourceSets {
commonMain { commonMain {

View File

@ -5,10 +5,8 @@ project.group = "$group"
kotlin { kotlin {
js (IR) { js (IR) {
browser { browser()
binaries.executable() binaries.executable()
}
nodejs()
} }
sourceSets { sourceSets {

View File

@ -4,16 +4,11 @@ project.group = "$group"
// apply from: "$publishGradlePath" // apply from: "$publishGradlePath"
kotlin { kotlin {
jvm { jvm()
compilations.main.kotlinOptions.useIR = true
}
js (IR) { js (IR) {
browser() browser()
nodejs() nodejs()
} }
android {
publishAllLibraryVariants()
}
sourceSets { sourceSets {
commonMain { commonMain {
@ -39,14 +34,5 @@ kotlin {
implementation kotlin('test-junit') implementation kotlin('test-junit')
} }
} }
androidTest {
dependencies {
implementation kotlin('test-junit')
implementation "androidx.test.ext:junit:$test_ext_junit_version"
implementation "androidx.test.espresso:espresso-core:$espresso_core"
}
}
} }
} }
apply from: "$defaultAndroidSettingsPresetPath"

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>