Compare commits

...

21 Commits

Author SHA1 Message Date
26fe225577 update desktop part 2022-01-04 22:47:37 +06:00
9a95bddf08 add support of JS publish scripts 2022-01-04 21:49:16 +06:00
c880d8e657 updates 2022-01-04 19:46:47 +06:00
ac87a140cc fixes 2021-09-04 19:34:24 +06:00
0dbe3a866b dependencies update 2021-09-04 19:09:16 +06:00
7c50c58a90 fix 2021-05-01 20:22:12 +06:00
ceff1eb1ef updates 2021-05-01 20:12:06 +06:00
46d6d429bb one more fix for web 2021-03-14 21:53:26 +06:00
dd9e71c9a2 fix in web view for loading of config 2021-03-14 21:46:41 +06:00
6d2ffb8a6e Update gradle.properties 2021-03-12 00:11:37 +06:00
25767eecb2 add github badge onto web version 2021-03-02 17:19:17 +06:00
603da9a021 fixes in saves 2021-03-02 16:51:08 +06:00
b43016bb24 repositories properties now checked 2021-03-02 16:45:46 +06:00
4c4845803d Merge pull request #3 from InsanusMokrassar/rewrite_with_web
add and web target
2021-03-02 14:11:38 +06:00
4ec7d3847f update workflows 2021-03-02 14:02:57 +06:00
2bbfe99ff4 add web publish step 2021-03-02 13:53:12 +06:00
881d268ea9 complete main part 2021-03-02 13:44:33 +06:00
0c8eef971a whole view complete, files handling left 2021-03-02 12:13:05 +06:00
7aa30ef46c temporal state 2021-03-02 02:43:30 +06:00
aa1756724a add and web target 2021-03-01 22:42:15 +06:00
741ee98e35 Merge pull request #2 from InsanusMokrassar/rewrite_on_multiplatform
rewrite on multiplatform
2021-03-01 21:20:34 +06:00
43 changed files with 1165 additions and 277 deletions

17
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
on: [push]
name: Build
jobs:
build-ubuntu:
name: Commit release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup JDK
uses: actions/setup-java@v1
with:
java-version: 11
- name: Build
run: ./gradlew build packageUberJarForCurrentOS

View File

@@ -1,4 +1,7 @@
on: [push] on:
push:
branches:
- master
name: Commit release name: Commit release
@@ -16,9 +19,13 @@ jobs:
- name: Set version from gradle.properties - name: Set version from gradle.properties
run: echo "version=` cat gradle.properties | grep ^version= | grep -o [\\.0-9]* `" >> $GITHUB_ENV run: echo "version=` cat gradle.properties | grep ^version= | grep -o [\\.0-9]* `" >> $GITHUB_ENV
- name: Build - name: Build
run: ./gradlew packageUberJarForCurrentOS run: ./gradlew build packageUberJarForCurrentOS
env: - name: Publish Web
additional_version: "" uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./web/build/distributions
publish_branch: site
- name: Create Release - name: Create Release
id: create_release id: create_release
uses: actions/create-release@v1 uses: actions/create-release@v1

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ build/
out/ out/
local.properties local.properties
kotlin-js-store/

View File

@@ -1,6 +1,5 @@
buildscript { buildscript {
repositories { repositories {
jcenter()
google() google()
mavenCentral() mavenCentral()
mavenLocal() mavenLocal()
@@ -8,7 +7,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.2' classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version" classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
@@ -19,10 +18,8 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
mavenLocal() mavenLocal()
jcenter()
mavenCentral() mavenCentral()
google() google()
maven { url "https://kotlin.bintray.com/kotlinx" }
maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" }
} }
} }

View File

@@ -0,0 +1,27 @@
package dev.inmo.kmppscriptbuilder.core.export
import dev.inmo.kmppscriptbuilder.core.models.GpgSigning
fun GpgSigning.generateMavenConfig() = when (this) {
GpgSigning.Disabled -> ""
GpgSigning.Optional ->
"""
if (project.hasProperty("signing.gnupg.keyName")) {
apply plugin: 'signing'
signing {
useGpgCmd()
sign publishing.publications
}
}"""
GpgSigning.Enabled ->
"""
apply plugin: 'signing'
signing {
useGpgCmd()
sign publishing.publications
}"""
}

View File

@@ -0,0 +1,67 @@
package dev.inmo.kmppscriptbuilder.core.export.js_only
import dev.inmo.kmppscriptbuilder.core.export.generateMavenConfig
import dev.inmo.kmppscriptbuilder.core.models.*
fun MavenConfig.buildJsOnlyMavenConfig(licenses: List<License>): String = """
apply plugin: 'maven-publish'
task javadocJar(type: Jar) {
classifier = 'javadoc'
}
task sourcesJar(type: Jar) {
kotlin.sourceSets.all {
from(kotlin)
}
classifier = 'sources'
}
publishing {
publications {
maven(MavenPublication) {
kotlin.js().components.forEach {
from(it)
}
artifact javadocJar
artifact sourcesJar
pom {
resolveStrategy = Closure.DELEGATE_FIRST
description = "$description"
name = "$name"
url = "$url"
scm {
developerConnection = "scm:git:[fetch=]${vcsUrl}[push=]${vcsUrl}"
url = "$vcsUrl"
}
developers {
${developers.joinToString("\n") { """
developer {
id = "${it.id}"
name = "${it.name}"
email = "${it.eMail}"
}
""" }}
}
licenses {
${licenses.joinToString("\n") { """
license {
name = "${it.title}"
url = "${it.url}"
}
""" }}
}
}
repositories {
${repositories.joinToString("\n ") { it.build(" ") }}
}
}
}
}
${gpgSigning.generateMavenConfig()}
""".trimIndent()

View File

@@ -1,69 +1,64 @@
package dev.inmo.kmppscriptbuilder.core.export.jvm_only package dev.inmo.kmppscriptbuilder.core.export.jvm_only
import dev.inmo.kmppscriptbuilder.core.export.generateMavenConfig
import dev.inmo.kmppscriptbuilder.core.models.* import dev.inmo.kmppscriptbuilder.core.models.*
fun MavenConfig.buildJvmOnlyMavenConfig(licenses: List<License>): String = """ fun MavenConfig.buildJvmOnlyMavenConfig(licenses: List<License>): String = """
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
${if (includeGpgSigning) "apply plugin: 'signing'\n" else ""}
task javadocJar(type: Jar) { task javadocJar(type: Jar) {
from javadoc from javadoc
classifier = 'javadoc' classifier = 'javadoc'
} }
task sourcesJar(type: Jar) { task sourcesJar(type: Jar) {
from sourceSets.main.allSource from sourceSets.main.allSource
classifier = 'sources' classifier = 'sources'
} }
publishing { publishing {
publications { publications {
maven(MavenPublication) { maven(MavenPublication) {
from components.java from components.java
artifact javadocJar artifact javadocJar
artifact sourcesJar artifact sourcesJar
pom { pom {
resolveStrategy = Closure.DELEGATE_FIRST resolveStrategy = Closure.DELEGATE_FIRST
description = "$description" description = "$description"
name = "$name" name = "$name"
url = "$url" url = "$url"
scm { scm {
developerConnection = "scm:git:[fetch=]${vcsUrl}[push=]${vcsUrl}" developerConnection = "scm:git:[fetch=]${vcsUrl}[push=]${vcsUrl}"
url = "$vcsUrl" url = "$vcsUrl"
}
developers {
${developers.joinToString("\n") { """
developer {
id = "${it.id}"
name = "${it.name}"
email = "${it.eMail}"
}
""" }}
}
licenses {
${licenses.joinToString("\n") { """
license {
name = "${it.title}"
url = "${it.url}"
}
""" }}
}
} }
repositories {
${repositories.joinToString("\n ") { it.build(" ") }} developers {
${developers.joinToString("\n") { """
developer {
id = "${it.id}"
name = "${it.name}"
email = "${it.eMail}"
}
""" }}
} }
licenses {
${licenses.joinToString("\n") { """
license {
name = "${it.title}"
url = "${it.url}"
}
""" }}
}
}
repositories {
${repositories.joinToString("\n ") { it.build(" ") }}
} }
} }
} }
${if (includeGpgSigning) """ }
signing { ${gpgSigning.generateMavenConfig()}
useGpgCmd()
sign publishing.publications
}
""" else ""}
""".trimIndent() """.trimIndent()

View File

@@ -1,56 +1,52 @@
package dev.inmo.kmppscriptbuilder.core.export.mpp package dev.inmo.kmppscriptbuilder.core.export.mpp
import dev.inmo.kmppscriptbuilder.core.export.generateMavenConfig
import dev.inmo.kmppscriptbuilder.core.models.* import dev.inmo.kmppscriptbuilder.core.models.*
fun MavenConfig.buildMultiplatformMavenConfig(licenses: List<License>): String = """ fun MavenConfig.buildMultiplatformMavenConfig(licenses: List<License>): String = """
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
${if (includeGpgSigning) "apply plugin: 'signing'\n" else ""}
task javadocsJar(type: Jar) {
classifier = 'javadoc'
}
publishing { task javadocsJar(type: Jar) {
publications.all { classifier = 'javadoc'
artifact javadocsJar }
pom { publishing {
description = "$description" publications.all {
name = "$name" artifact javadocsJar
url = "$url"
scm { pom {
developerConnection = "scm:git:[fetch=]${vcsUrl}[push=]${vcsUrl}" description = "$description"
url = "$vcsUrl" name = "$name"
} url = "$url"
developers { scm {
${developers.joinToString("\n") { """ developerConnection = "scm:git:[fetch=]${vcsUrl}[push=]${vcsUrl}"
developer { url = "$vcsUrl"
id = "${it.id}"
name = "${it.name}"
email = "${it.eMail}"
}
""" }}
}
licenses {
${licenses.joinToString("\n") { """
license {
name = "${it.title}"
url = "${it.url}"
}
""" }}
}
} }
repositories {
${repositories.joinToString("\n ") { it.build(" ") }} developers {
${developers.joinToString("\n") { """
developer {
id = "${it.id}"
name = "${it.name}"
email = "${it.eMail}"
}
""" }}
}
licenses {
${licenses.joinToString("\n") { """
license {
name = "${it.title}"
url = "${it.url}"
}
""" }}
} }
} }
repositories {
${repositories.joinToString("\n ") { it.build(" ") }}
}
} }
${if (includeGpgSigning) """ }
signing { ${gpgSigning.generateMavenConfig()}
useGpgCmd()
sign publishing.publications
}
""" else ""}
""".trimIndent() """.trimIndent()

View File

@@ -1,5 +1,6 @@
package dev.inmo.kmppscriptbuilder.core.models package dev.inmo.kmppscriptbuilder.core.models
import dev.inmo.kmppscriptbuilder.core.export.js_only.buildJsOnlyMavenConfig
import dev.inmo.kmppscriptbuilder.core.export.jvm_only.buildJvmOnlyMavenConfig import dev.inmo.kmppscriptbuilder.core.export.jvm_only.buildJvmOnlyMavenConfig
import dev.inmo.kmppscriptbuilder.core.export.mpp.buildMultiplatformMavenConfig import dev.inmo.kmppscriptbuilder.core.export.mpp.buildMultiplatformMavenConfig
import kotlinx.serialization.* import kotlinx.serialization.*
@@ -32,12 +33,6 @@ object ProjectTypeSerializer : KSerializer<ProjectType> {
object MultiplatformProjectType : ProjectType() { object MultiplatformProjectType : ProjectType() {
override val name: String = "Multiplatform" override val name: String = "Multiplatform"
// override fun buildBintrayGradleConfig(
// bintrayConfig: BintrayConfig,
// licenses: List<License>
// ): String = bintrayConfig.buildMultiplatformGradleConfig(
// licenses
// )
override fun buildMavenGradleConfig( override fun buildMavenGradleConfig(
mavenConfig: MavenConfig, mavenConfig: MavenConfig,
@@ -49,12 +44,6 @@ object MultiplatformProjectType : ProjectType() {
object JVMProjectType : ProjectType() { object JVMProjectType : ProjectType() {
override val name: String = "JVM" override val name: String = "JVM"
// override fun buildBintrayGradleConfig(
// bintrayConfig: BintrayConfig,
// licenses: List<License>
// ): String = bintrayConfig.buildJvmOnlyGradleConfig(
// licenses
// )
override fun buildMavenGradleConfig( override fun buildMavenGradleConfig(
mavenConfig: MavenConfig, mavenConfig: MavenConfig,
@@ -64,6 +53,15 @@ object JVMProjectType : ProjectType() {
) )
} }
object JSProjectType : ProjectType() {
override val name: String = "JS"
override fun buildMavenGradleConfig(
mavenConfig: MavenConfig,
licenses: List<License>
): String = mavenConfig.buildJsOnlyMavenConfig(licenses)
}
@Serializable @Serializable
data class Config( data class Config(
val licenses: List<License>, val licenses: List<License>,

View File

@@ -30,19 +30,19 @@ suspend fun HttpClient.getLicenses(): Map<String, License> {
suspend fun HttpClient.searchLicense(name: String): List<License> { suspend fun HttpClient.searchLicense(name: String): List<License> {
val licenses = licenses ?: getLicenses() val licenses = licenses ?: getLicenses()
val lowerCase = name.toLowerCase() val lowerCase = name.lowercase()
val upperCase = name.toUpperCase() val upperCase = name.uppercase()
return licenses.values.filter { return licenses.values.filter {
it.title.toLowerCase().contains(lowerCase) || it.title.toUpperCase().contains(upperCase) || it.title.contains(name) it.title.lowercase().contains(lowerCase) || it.title.uppercase().contains(upperCase) || it.title.contains(name)
|| it.id.toLowerCase().contains(lowerCase) || it.id.toUpperCase().contains(upperCase) || it.id.contains(name) || it.id.lowercase().contains(lowerCase) || it.id.uppercase().contains(upperCase) || it.id.contains(name)
} }
} }
fun Map<String, License>.searchLicense(name: String): List<License> { fun Map<String, License>.searchLicense(name: String): List<License> {
val lowerCase = name.toLowerCase() val lowerCase = name.lowercase()
val upperCase = name.toUpperCase() val upperCase = name.uppercase()
return values.filter { return values.filter {
it.title.toLowerCase().contains(lowerCase) || it.title.toUpperCase().contains(upperCase) || it.title.contains(name) it.title.lowercase().contains(lowerCase) || it.title.uppercase().contains(upperCase) || it.title.contains(name)
|| it.id.toLowerCase().contains(lowerCase) || it.id.toUpperCase().contains(upperCase) || it.id.contains(name) || it.id.lowercase().contains(lowerCase) || it.id.uppercase().contains(upperCase) || it.id.contains(name)
} }
} }

View File

@@ -2,15 +2,30 @@ package dev.inmo.kmppscriptbuilder.core.models
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
const val defaultProjectName = "\${project.name}"
const val defaultProjectDescription = "\${project.name}"
@Serializable
sealed class GpgSigning(val name: String) {
@Serializable
object Disabled : GpgSigning("Disabled")
@Serializable
object Optional : GpgSigning("Optional")
@Serializable
object Enabled : GpgSigning("Enabled")
}
@Serializable @Serializable
data class MavenConfig( data class MavenConfig(
val name: String, val name: String,
val description: String, val description: String,
val url: String, val url: String,
val vcsUrl: String, val vcsUrl: String,
val includeGpgSigning: Boolean = false,
val developers: List<Developer>, val developers: List<Developer>,
val repositories: List<MavenPublishingRepository> = emptyList() val repositories: List<MavenPublishingRepository> = emptyList(),
val gpgSigning: GpgSigning = GpgSigning.Disabled,
@Deprecated("Replaced with gpgSigning")
val includeGpgSigning: Boolean = false,
) )
@Serializable @Serializable
@@ -22,14 +37,20 @@ data class MavenPublishingRepository(
name.toUpperCase() name.toUpperCase()
} }
fun build(indent: String) = """maven { fun build(indent: String): String {
name = "$name" val usernameProperty = "${nameCapitalized}_USER"
url = uri("$url") val passwordProperty = "${nameCapitalized}_PASSWORD"
credentials { return """if ((project.hasProperty('${usernameProperty}') || System.getenv('${usernameProperty}') != null) && (project.hasProperty('${passwordProperty}') || System.getenv('${passwordProperty}') != null)) {
username = project.hasProperty('${nameCapitalized}_USER') ? project.property('${nameCapitalized}_USER') : System.getenv('${nameCapitalized}_USER') maven {
password = project.hasProperty('${nameCapitalized}_PASSWORD') ? project.property('${nameCapitalized}_PASSWORD') : System.getenv('${nameCapitalized}_PASSWORD') name = "$name"
url = uri("$url")
credentials {
username = project.hasProperty('${usernameProperty}') ? project.property('${usernameProperty}') : System.getenv('${usernameProperty}')
password = project.hasProperty('${passwordProperty}') ? project.property('${passwordProperty}') : System.getenv('${passwordProperty}')
}
} }
}""".replace("\n", "\n$indent") }""".replace("\n", "\n$indent")
}
} }
val SonatypeRepository = MavenPublishingRepository("sonatype", "https://oss.sonatype.org/service/local/staging/deploy/maven2/") val SonatypeRepository = MavenPublishingRepository("sonatype", "https://oss.sonatype.org/service/local/staging/deploy/maven2/")

View File

@@ -10,7 +10,6 @@ kotlin {
jvm { jvm {
compilations.main.kotlinOptions { compilations.main.kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
useIR = true
} }
} }
sourceSets { sourceSets {

View File

@@ -1,62 +1,67 @@
package dev.inmo.kmppscriptbuilder.desktop package dev.inmo.kmppscriptbuilder.desktop
import androidx.compose.desktop.Window
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.* import androidx.compose.material.Colors
import androidx.compose.material.MaterialTheme
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import dev.inmo.kmppscriptbuilder.desktop.utils.* import androidx.compose.ui.window.Window
import dev.inmo.kmppscriptbuilder.desktop.views.* import androidx.compose.ui.window.application
import dev.inmo.kmppscriptbuilder.desktop.utils.init
import dev.inmo.kmppscriptbuilder.desktop.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)
//val uncaughtExceptionsFlow: Flow<DefaultErrorHandler.ErrorEvent> = uncaughtExceptionsBC.asFlow() //val uncaughtExceptionsFlow: Flow<DefaultErrorHandler.ErrorEvent> = uncaughtExceptionsBC.asFlow()
fun main(args: Array<String>) = Window(title = "Kotlin Multiplatform Publishing Builder") { fun main(args: Array<String>) = application {
val builder = BuilderView() Window(onCloseRequest = ::exitApplication, title = "Kotlin Multiplatform Publishing Builder") {
MaterialTheme( val builder = BuilderView()
Colors( MaterialTheme(
primary = Color(0x01, 0x57, 0x9b), Colors(
primaryVariant = Color(0x00, 0x2f, 0x6c), primary = Color(0x01, 0x57, 0x9b),
secondary = Color(0xb2, 0xeb, 0xf2), primaryVariant = Color(0x00, 0x2f, 0x6c),
secondaryVariant = Color(0x81, 0xb9, 0xbf), secondary = Color(0xb2, 0xeb, 0xf2),
background = Color(0xe1, 0xe2, 0xe1), secondaryVariant = Color(0x81, 0xb9, 0xbf),
surface = Color(0xf5, 0xf5, 0xf6), background = Color(0xe1, 0xe2, 0xe1),
error = Color(0xb7, 0x1c, 0x1c), surface = Color(0xf5, 0xf5, 0xf6),
onPrimary = Color.White, error = Color(0xb7, 0x1c, 0x1c),
onSecondary = Color.Black, onPrimary = Color.White,
onBackground = Color.Black, onSecondary = Color.Black,
onSurface = Color.Black, onBackground = Color.Black,
onError = Color.White, onSurface = Color.Black,
isLight = MaterialTheme.colors.isLight, onError = Color.White,
) isLight = MaterialTheme.colors.isLight,
) {
Box(
Modifier.fillMaxSize()
.background(color = Color(245, 245, 245))
) {
val stateVertical = rememberScrollState(0)
Box(
modifier = Modifier
.fillMaxSize()
.verticalScroll(stateVertical)
) {
builder.init()
}
VerticalScrollbar(
modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(),
adapter = rememberScrollbarAdapter(stateVertical)
) )
) {
Box(
Modifier.fillMaxSize()
.background(color = Color(245, 245, 245))
) {
val stateVertical = rememberScrollState(0)
Box(
modifier = Modifier
.fillMaxSize()
.verticalScroll(stateVertical)
) {
builder.init()
}
VerticalScrollbar(
modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(),
adapter = rememberScrollbarAdapter(stateVertical)
)
}
}
if (args.isNotEmpty()) {
val config = loadConfigFile(File(args.first()))
builder.config = config
} }
} }
if (args.isNotEmpty()) {
val config = loadConfigFile(File(args.first()))
builder.config = config
}
} }

View File

@@ -0,0 +1,17 @@
package dev.inmo.kmppscriptbuilder.desktop.utils
import java.awt.Desktop
import java.net.URI
fun openLink(link: String): Boolean {
val desktop = if (Desktop.isDesktopSupported()) Desktop.getDesktop() else null
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
try {
desktop.browse(URI(link))
return true
} catch (e: Exception) {
e.printStackTrace()
}
}
return false
}

View File

@@ -1,12 +1,14 @@
package dev.inmo.kmppscriptbuilder.desktop.views package dev.inmo.kmppscriptbuilder.desktop.views
import androidx.compose.foundation.Image import androidx.compose.foundation.*
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.svgResource 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 androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.core.models.Config import dev.inmo.kmppscriptbuilder.core.models.Config
import dev.inmo.kmppscriptbuilder.desktop.utils.* import dev.inmo.kmppscriptbuilder.desktop.utils.*
@@ -28,6 +30,34 @@ class BuilderView : View() {
saveAvailableState = true 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 @Composable
override fun build() { override fun build() {
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize()) {
@@ -37,56 +67,28 @@ class BuilderView : View() {
CommonText("Kotlin publication scripts builder", Modifier.clickable { println(config) }) CommonText("Kotlin publication scripts builder", Modifier.clickable { println(config) })
}, },
actions = { actions = {
IconButton( createIcon("Open file", "images/open_file.svg") {
{ loadConfig()?.also {
loadConfig()?.also { config = it
config = it
}
}
) {
Image(
painter = svgResource("images/open_file.svg"),
contentDescription = "Open file"
)
}
if (saveAvailableState) {
IconButton(
{
saveConfig(config)
}
) {
Image(
painter = svgResource("images/save_file.svg"),
contentDescription = "Save file"
)
} }
} }
if (saveAvailableState) { if (saveAvailableState) {
IconButton( createIcon("Save", "images/save_file.svg") {
{ saveConfig(config)
exportGradle(config)
}
) {
Image(
painter = svgResource("images/export_gradle.svg"),
contentDescription = "Export Gradle script"
)
} }
} }
IconButton( if (saveAvailableState) {
{ createIcon("Export Gradle script", "images/export_gradle.svg") {
if (saveAs(config)) { exportGradle(config)
saveAvailableState = true }
} }
createIcon("Save as", "images/save_as.svg") {
if (saveAs(config)) {
saveAvailableState = true
} }
) {
Image(
painter = svgResource("images/save_as.svg"),
contentDescription = "Export Gradle script"
)
} }
} }
) )

View File

@@ -2,7 +2,7 @@ package dev.inmo.kmppscriptbuilder.desktop.views
import androidx.compose.runtime.* import androidx.compose.runtime.*
import dev.inmo.kmppscriptbuilder.core.models.Developer import dev.inmo.kmppscriptbuilder.core.models.Developer
import dev.inmo.kmppscriptbuilder.desktop.utils.* import dev.inmo.kmppscriptbuilder.desktop.utils.CommonTextField
class DeveloperState( class DeveloperState(
id: String = "", id: String = "",

View File

@@ -53,7 +53,7 @@ class LicensesView: VerticalView("Licenses") {
licensesOffersToShow.clear() licensesOffersToShow.clear()
if (licenseSearchFilter.isNotEmpty()) { if (licenseSearchFilter.isNotEmpty()) {
licensesOffersToShow.addAll( licensesOffersToShow.addAll(
availableLicensesState.filter { filterText.all { symbol -> symbol.toLowerCase() in it.title } } availableLicensesState.filter { filterText.all { symbol -> symbol.lowercaseChar() in it.title } }
) )
} }
} }

View File

@@ -1,11 +1,13 @@
package dev.inmo.kmppscriptbuilder.desktop.views package dev.inmo.kmppscriptbuilder.desktop.views
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.* import androidx.compose.material.Button
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import dev.inmo.kmppscriptbuilder.desktop.utils.* import dev.inmo.kmppscriptbuilder.desktop.utils.CommonText
import dev.inmo.kmppscriptbuilder.desktop.utils.VerticalView
abstract class ListView<T>(title: String) : VerticalView(title) { abstract class ListView<T>(title: String) : VerticalView(title) {
protected val itemsList = mutableStateListOf<T>() protected val itemsList = mutableStateListOf<T>()

View File

@@ -1,9 +1,13 @@
package dev.inmo.kmppscriptbuilder.desktop.views package dev.inmo.kmppscriptbuilder.desktop.views
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import dev.inmo.kmppscriptbuilder.core.models.MavenConfig import androidx.compose.ui.Alignment
import dev.inmo.kmppscriptbuilder.core.models.SonatypeRepository 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.* import dev.inmo.kmppscriptbuilder.desktop.utils.*
class MavenInfoView : VerticalView("Project information") { class MavenInfoView : VerticalView("Project information") {
@@ -11,37 +15,59 @@ class MavenInfoView : VerticalView("Project information") {
private var projectDescriptionProperty by mutableStateOf("") private var projectDescriptionProperty by mutableStateOf("")
private var projectUrlProperty by mutableStateOf("") private var projectUrlProperty by mutableStateOf("")
private var projectVcsUrlProperty by mutableStateOf("") private var projectVcsUrlProperty by mutableStateOf("")
private var includeGpgSignProperty by mutableStateOf(true) private var gpgSignProperty by mutableStateOf<GpgSigning>(GpgSigning.Disabled)
private var publishToMavenCentralProperty by mutableStateOf(false) private var publishToMavenCentralProperty by mutableStateOf(false)
private val developersView = DevelopersView() private val developersView = DevelopersView()
private val repositoriesView = RepositoriesView() private val repositoriesView = RepositoriesView()
var mavenConfig: MavenConfig var mavenConfig: MavenConfig
get() = MavenConfig( get() = MavenConfig(
projectNameProperty, projectNameProperty.ifBlank { defaultProjectName },
projectDescriptionProperty, projectDescriptionProperty.ifBlank { defaultProjectDescription },
projectUrlProperty, projectUrlProperty,
projectVcsUrlProperty, projectVcsUrlProperty,
includeGpgSignProperty,
developersView.developers, developersView.developers,
repositoriesView.repositories + if (publishToMavenCentralProperty) { repositoriesView.repositories + if (publishToMavenCentralProperty) {
listOf(SonatypeRepository) listOf(SonatypeRepository)
} else { } else {
emptyList() emptyList()
} },
gpgSignProperty
) )
set(value) { set(value) {
projectNameProperty = value.name projectNameProperty = value.name
projectDescriptionProperty = value.description projectDescriptionProperty = value.description
projectUrlProperty = value.url projectUrlProperty = value.url
projectVcsUrlProperty = value.vcsUrl projectVcsUrlProperty = value.vcsUrl
includeGpgSignProperty = value.includeGpgSigning gpgSignProperty = if (value.includeGpgSigning) {
GpgSigning.Enabled
} else {
value.gpgSigning
}
publishToMavenCentralProperty = value.repositories.any { it == SonatypeRepository } publishToMavenCentralProperty = value.repositories.any { it == SonatypeRepository }
developersView.developers = value.developers developersView.developers = value.developers
repositoriesView.repositories = value.repositories.filter { it != SonatypeRepository } repositoriesView.repositories = value.repositories.filter { it != SonatypeRepository }
// developersView.developers = value.developers // 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 = { override val content: @Composable ColumnScope.() -> Unit = {
CommonTextField( CommonTextField(
projectNameProperty, projectNameProperty,
@@ -60,11 +86,12 @@ class MavenInfoView : VerticalView("Project information") {
"Public project VCS URL (with .git)" "Public project VCS URL (with .git)"
) { projectVcsUrlProperty = it } ) { projectVcsUrlProperty = it }
SwitchWithLabel( Row(verticalAlignment = Alignment.CenterVertically) {
"Include GPG Signing", Text("Gpg Signing: ")
includeGpgSignProperty, addGpgSigningButton(GpgSigning.Disabled)
placeSwitchAtTheStart = true addGpgSigningButton(GpgSigning.Optional)
) { includeGpgSignProperty = it } addGpgSigningButton(GpgSigning.Enabled)
}
SwitchWithLabel( SwitchWithLabel(
"Include publication to MavenCentral", "Include publication to MavenCentral",

View File

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

View File

@@ -2,7 +2,7 @@ package dev.inmo.kmppscriptbuilder.desktop.views
import androidx.compose.runtime.* import androidx.compose.runtime.*
import dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository import dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository
import dev.inmo.kmppscriptbuilder.desktop.utils.* import dev.inmo.kmppscriptbuilder.desktop.utils.CommonTextField
class RepositoryState( class RepositoryState(
name: String = "", name: String = "",

View File

@@ -15,6 +15,7 @@ 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"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle" mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle" defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"

View File

@@ -6,27 +6,27 @@ kotlin.incremental.js=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
kotlin_version=1.4.30 kotlin_version=1.6.10
kotlin_coroutines_version=1.4.2 kotlin_coroutines_version=1.6.0
kotlin_serialisation_core_version=1.1.0 kotlin_serialisation_core_version=1.3.2
ktor_version=1.5.1 ktor_version=1.6.7
micro_utils_version=0.4.27 micro_utils_version=0.9.0
compose_version=0.3.0 compose_version=1.0.1
# ANDROID # ANDROID
android_minSdkVersion=21 android_minSdkVersion=21
android_compileSdkVersion=30 android_compileSdkVersion=32
android_buildToolsVersion=30.0.2 android_buildToolsVersion=32.0.0
dexcount_version=2.0.0 dexcount_version=3.0.1
junit_version=4.12 junit_version=4.12
test_ext_junit_version=1.1.2 test_ext_junit_version=1.1.2
espresso_core=3.3.0 espresso_core=3.3.0
# Dokka # Dokka
dokka_version=1.4.20 dokka_version=1.6.0
# Project data # Project data

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-6.8-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip

View File

@@ -1,4 +1,4 @@
project.version = "$version" + System.getenv("additional_version") project.version = "$version" + (System.getenv("additional_version") != null ? System.getenv("additional_version") : "")
project.group = "$group" project.group = "$group"
// apply from: "$publishGradlePath" // apply from: "$publishGradlePath"

View File

@@ -1,4 +1,4 @@
project.version = "$version" + System.getenv("additional_version") project.version = "$version" + (System.getenv("additional_version") != null ? System.getenv("additional_version") : "")
project.group = "$group" project.group = "$group"
// apply from: "$publishGradlePath" // apply from: "$publishGradlePath"

34
mppJsProject.gradle Normal file
View File

@@ -0,0 +1,34 @@
project.version = "$version" + (System.getenv("additional_version") != null ? System.getenv("additional_version") : "")
project.group = "$group"
// apply from: "$publishGradlePath"
kotlin {
js (IR) {
browser {
binaries.executable()
}
nodejs()
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version"
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jsTest {
dependencies {
implementation kotlin('test-js')
implementation kotlin('test-junit')
}
}
}
}

View File

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

17
web/build.gradle Normal file
View File

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

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

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

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

@@ -0,0 +1,23 @@
package dev.inmo.kmppscriptbuilder.web.views
import dev.inmo.kmppscriptbuilder.core.models.Config
import kotlinx.browser.document
import org.w3c.dom.HTMLElement
class BuilderView : View {
private val projectTypeView = ProjectTypeView()
private val licensesView = LicensesView(document.getElementById("licensesListDiv") as HTMLElement)
private val mavenInfoTypeView = MavenProjectInfoView()
var config: Config
get() = Config(
licensesView.licenses,
mavenInfoTypeView.mavenConfig,
projectTypeView.projectType
)
set(value) {
licensesView.licenses = value.licenses
mavenInfoTypeView.mavenConfig = value.mavenConfig
projectTypeView.projectType = value.type
}
}

View File

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

@@ -0,0 +1,113 @@
package dev.inmo.kmppscriptbuilder.web.views
import dev.inmo.kmppscriptbuilder.core.models.License
import dev.inmo.kmppscriptbuilder.core.models.getLicenses
import dev.inmo.micro_utils.coroutines.safeActor
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import io.ktor.client.HttpClient
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.dom.appendElement
import org.w3c.dom.*
class LicensesView(
rootElement: HTMLElement,
client: HttpClient = HttpClient(),
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) : MutableListView<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

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

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

@@ -0,0 +1,41 @@
package dev.inmo.kmppscriptbuilder.web.views
import dev.inmo.kmppscriptbuilder.web.utils.keepScrolling
import org.w3c.dom.HTMLElement
abstract class MutableListView<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

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

@@ -0,0 +1,31 @@
package dev.inmo.kmppscriptbuilder.web.views
import dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository
import org.w3c.dom.*
class RepositoriesView(rootElement: HTMLElement) : MutableListView<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

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

View File

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

@@ -0,0 +1,85 @@
<!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>