Compare commits
	
		
			38 Commits
		
	
	
		
			build-603d
			...
			build-abb2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| abb22519eb | |||
| 87f77543e2 | |||
| 429f2176f2 | |||
| a56b8ae2b5 | |||
| 4324620932 | |||
| 265e839dc7 | |||
| 2144ca2cca | |||
| bf21f92c6f | |||
| 41e8d2c540 | |||
| 047f51fd96 | |||
| 08da50705c | |||
| 3c216af814 | |||
| 6b5ab5acba | |||
| f723d55d7e | |||
| c0d0b7521e | |||
| 7536c67589 | |||
| 0770772e7d | |||
| c6b1289f5a | |||
| 788fe49aa4 | |||
| 3be0f24eac | |||
| 9fe7c458e9 | |||
| 8430e68167 | |||
| 53a76c7a73 | |||
| 70baa30127 | |||
| 283bc5acb4 | |||
| 26a5d20e26 | |||
| ca1a91e0f0 | |||
| 26fe225577 | |||
| 9a95bddf08 | |||
| c880d8e657 | |||
| ac87a140cc | |||
| 0dbe3a866b | |||
| 7c50c58a90 | |||
| ceff1eb1ef | |||
| 46d6d429bb | |||
| dd9e71c9a2 | |||
| 6d2ffb8a6e | |||
| 25767eecb2 | 
							
								
								
									
										17
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,17 +0,0 @@ | ||||
| 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 | ||||
							
								
								
									
										6
									
								
								.github/workflows/commit-release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -19,12 +19,12 @@ jobs: | ||||
|       - name: Set version from gradle.properties | ||||
|         run: echo "version=` cat gradle.properties | grep ^version= | grep -o [\\.0-9]* `" >> $GITHUB_ENV | ||||
|       - name: Build | ||||
|         run: ./gradlew build packageUberJarForCurrentOS | ||||
|         run: ./gradlew build packageReleaseUberJarForCurrentOS | ||||
|       - name: Publish Web | ||||
|         uses: peaceiris/actions-gh-pages@v3 | ||||
|         with: | ||||
|           github_token: ${{ secrets.GITHUB_TOKEN }} | ||||
|           publish_dir: ./web/build/distributions | ||||
|           publish_dir: ./core/build/dist/js/productionExecutable | ||||
|           publish_branch: site | ||||
|       - name: Create Release | ||||
|         id: create_release | ||||
| @@ -44,6 +44,6 @@ jobs: | ||||
|           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|         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 | ||||
|           asset_path: "./desktop/build/compose/jars/kmppscriptbuilder.desktop-linux-x64-${{ env.version }}${{ env.additional_version }}.jar" | ||||
|           asset_path: "./core/build/compose/jars/kmppscriptbuilder.core-linux-x64-${{ env.version }}${{ env.additional_version }}.jar" | ||||
|           asset_name: KotlinPublicationScriptsBuilder-linux-x64.jar | ||||
|           asset_content_type: application/java-archive | ||||
|   | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -10,3 +10,4 @@ build/ | ||||
| out/ | ||||
|  | ||||
| local.properties | ||||
| kotlin-js-store/ | ||||
|   | ||||
							
								
								
									
										12
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						| @@ -1,6 +1,5 @@ | ||||
| buildscript { | ||||
|     repositories { | ||||
|         jcenter() | ||||
|         google() | ||||
|         mavenCentral() | ||||
|         mavenLocal() | ||||
| @@ -8,21 +7,18 @@ buildscript { | ||||
|     } | ||||
|  | ||||
|     dependencies { | ||||
|         classpath 'com.android.tools.build:gradle:4.0.2' | ||||
|         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||||
|         classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" | ||||
|         classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version" | ||||
|         classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version" | ||||
|         classpath libs.buildscript.kt.gradle | ||||
|         classpath libs.buildscript.kt.serialization | ||||
|         classpath libs.buildscript.jb.dokka | ||||
|         classpath libs.buildscript.gh.release | ||||
|     } | ||||
| } | ||||
|  | ||||
| allprojects { | ||||
|     repositories { | ||||
|         mavenLocal() | ||||
|         jcenter() | ||||
|         mavenCentral() | ||||
|         google() | ||||
|         maven { url "https://kotlin.bintray.com/kotlinx" } | ||||
|         maven { url "https://maven.pkg.jetbrains.space/public/p/compose/dev" } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,20 +1,46 @@ | ||||
| plugins { | ||||
|     id "org.jetbrains.kotlin.multiplatform" | ||||
|     id "org.jetbrains.kotlin.plugin.serialization" | ||||
|     id "com.android.library" | ||||
|     alias(libs.plugins.jb.compose) | ||||
| } | ||||
|  | ||||
| apply from: "$mppProjectWithSerializationPresetPath" | ||||
|  | ||||
| kotlin { | ||||
|     js (IR) { | ||||
|         binaries.executable() | ||||
|     } | ||||
|     sourceSets { | ||||
|         commonMain { | ||||
|             dependencies { | ||||
|                 api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" | ||||
|                 api "dev.inmo:micro_utils.coroutines:$micro_utils_version" | ||||
|  | ||||
|                 api "io.ktor:ktor-client-core:$ktor_version" | ||||
|                 api libs.kt.coroutines | ||||
|                 api libs.microutils.common | ||||
|                 api libs.microutils.coroutines | ||||
|                 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" | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,48 @@ | ||||
| package dev.inmo.kmppscriptbuilder.core.export | ||||
|  | ||||
| import dev.inmo.kmppscriptbuilder.core.models.MavenConfig | ||||
|  | ||||
| const val generateCentralSonatypeUploadingPartImports = """import java.nio.charset.StandardCharsets | ||||
| import java.net.http.HttpClient | ||||
| import java.net.http.HttpRequest | ||||
| import java.net.http.HttpResponse""" | ||||
| const val generateCentralSonatypeUploadingPart = """if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) { | ||||
|     def taskName = "uploadSonatypePublication" | ||||
|     if (rootProject.tasks.names.contains(taskName) == false) { | ||||
|         rootProject.tasks.register(taskName) { | ||||
|             doLast { | ||||
|                 def username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER') | ||||
|                 def password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD') | ||||
|                 def bearer = Base64.getEncoder().encodeToString("${"$"}username:${"$"}password".getBytes(StandardCharsets.UTF_8)) | ||||
|      | ||||
|                 def client = HttpClient.newHttpClient() | ||||
|                 def request = HttpRequest.newBuilder() | ||||
|                         .uri(URI.create("https://ossrh-staging-api.central.sonatype.com/manual/search/repositories?state=open")) | ||||
|                         .GET() | ||||
|                         .header("Content-Type", "application/json") | ||||
|                         .header("Authorization", "Bearer ${"$"}bearer") | ||||
|                         .build() | ||||
|      | ||||
|                 def response = client.send(request, HttpResponse.BodyHandlers.ofString()) | ||||
|                 def keys = new ArrayList<String>() | ||||
|                 response.body().findAll("\"key\"[\\s]*:[\\s]*\"[^\"]+\"").forEach { | ||||
|                     def key = it.find("[^\"]+\"\$").find("[^\"]+") | ||||
|                     keys.add(key) | ||||
|                 } | ||||
|                 keys.forEach { | ||||
|                     println("Start uploading ${"$"}it") | ||||
|                     def uploadRequest = HttpRequest.newBuilder() | ||||
|                             .uri(URI.create("https://ossrh-staging-api.central.sonatype.com/manual/upload/repository/${"$"}it?publishing_type=user_managed")) | ||||
|                             .POST(HttpRequest.BodyPublishers.ofString("")) | ||||
|                             .header("Content-Type", "application/json") | ||||
|                             .header("Authorization", "Bearer ${"$"}bearer") | ||||
|                             .build() | ||||
|                     def uploadResponse = client.send(uploadRequest, HttpResponse.BodyHandlers.ofString()) | ||||
|                     if (uploadResponse.statusCode() != 200) { | ||||
|                         throw IllegalStateException("Faced error of uploading for repo with key ${"$"}it. Response: ${"$"}uploadResponse") | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| }""" | ||||
| @@ -0,0 +1,87 @@ | ||||
| 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 | ||||
|     } | ||||
|  | ||||
|     task signAll { | ||||
|         tasks.withType(Sign).forEach { | ||||
|             dependsOn(it) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Workaround to make android sign operations depend on signing tasks | ||||
|     project.getTasks().withType(AbstractPublishToMaven.class).configureEach { | ||||
|         def signingTasks = project.getTasks().withType(Sign.class) | ||||
|         mustRunAfter(signingTasks) | ||||
|     } | ||||
|     // Workaround to make test tasks use sign | ||||
|     project.getTasks().withType(Sign.class).configureEach { signTask -> | ||||
|         def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name) | ||||
|         def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign | ||||
|         // These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets | ||||
|  | ||||
|         // Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency | ||||
|         def debugTestTask = tasks.findByName("linkDebugTest${'$'}pubName") | ||||
|         if (debugTestTask != null) { | ||||
|             signTask.mustRunAfter(debugTestTask) | ||||
|         } | ||||
|         // Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency | ||||
|         def testTask = tasks.findByName("compileTestKotlin${'$'}pubName") | ||||
|         if (testTask != null) { | ||||
|             signTask.mustRunAfter(testTask) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| """ | ||||
|     GpgSigning.Enabled -> | ||||
| """ | ||||
| apply plugin: 'signing' | ||||
|  | ||||
| signing { | ||||
|     useGpgCmd() | ||||
|  | ||||
|     sign publishing.publications | ||||
| } | ||||
|  | ||||
| task signAll { | ||||
|     tasks.withType(Sign).forEach { | ||||
|         dependsOn(it) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Workaround to make android sign operations depend on signing tasks | ||||
| project.getTasks().withType(AbstractPublishToMaven.class).configureEach { | ||||
|     def signingTasks = project.getTasks().withType(Sign.class) | ||||
|     mustRunAfter(signingTasks) | ||||
| } | ||||
| // Workaround to make test tasks use sign | ||||
| project.getTasks().withType(Sign.class).configureEach { signTask -> | ||||
|     def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name) | ||||
|     def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign | ||||
|     // These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets | ||||
|  | ||||
|     // Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency | ||||
|     def debugTestTask = tasks.findByName("linkDebugTest${'$'}pubName") | ||||
|     if (debugTestTask != null) { | ||||
|         signTask.mustRunAfter(debugTestTask) | ||||
|     } | ||||
|     // Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency | ||||
|     def testTask = tasks.findByName("compileTestKotlin${'$'}pubName") | ||||
|     if (testTask != null) { | ||||
|         signTask.mustRunAfter(testTask) | ||||
|     } | ||||
| } | ||||
| """ | ||||
| } | ||||
| @@ -0,0 +1,71 @@ | ||||
| package dev.inmo.kmppscriptbuilder.core.export.js_only | ||||
|  | ||||
| import dev.inmo.kmppscriptbuilder.core.export.generateCentralSonatypeUploadingPart | ||||
| import dev.inmo.kmppscriptbuilder.core.export.generateCentralSonatypeUploadingPartImports | ||||
| import dev.inmo.kmppscriptbuilder.core.export.generateMavenConfig | ||||
| import dev.inmo.kmppscriptbuilder.core.models.* | ||||
|  | ||||
| fun MavenConfig.buildJsOnlyMavenConfig(licenses: List<License>): String = """ | ||||
| ${if (includeCentralSonatypeUploadingScript) "$generateCentralSonatypeUploadingPartImports\n" else ""} | ||||
| apply plugin: 'maven-publish' | ||||
| ${if (includeCentralSonatypeUploadingScript) "$generateCentralSonatypeUploadingPart\n" else ""} | ||||
|  | ||||
| task javadocJar(type: Jar) { | ||||
|     archiveClassifier = 'javadoc' | ||||
| } | ||||
| task sourcesJar(type: Jar) { | ||||
|     kotlin.sourceSets.all { | ||||
|         from(kotlin) | ||||
|     } | ||||
|     archiveClassifier = '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() | ||||
| @@ -1,21 +1,25 @@ | ||||
| package dev.inmo.kmppscriptbuilder.core.export.jvm_only | ||||
|  | ||||
| import dev.inmo.kmppscriptbuilder.core.export.generateCentralSonatypeUploadingPart | ||||
| import dev.inmo.kmppscriptbuilder.core.export.generateCentralSonatypeUploadingPartImports | ||||
| import dev.inmo.kmppscriptbuilder.core.export.generateMavenConfig | ||||
| import dev.inmo.kmppscriptbuilder.core.models.* | ||||
|  | ||||
| fun MavenConfig.buildJvmOnlyMavenConfig(licenses: List<License>): String = """ | ||||
|     apply plugin: 'maven-publish' | ||||
|     ${if (includeGpgSigning) "apply plugin: 'signing'\n" else ""} | ||||
| ${if (includeCentralSonatypeUploadingScript) "$generateCentralSonatypeUploadingPartImports\n" else ""} | ||||
| apply plugin: 'maven-publish' | ||||
| ${if (includeCentralSonatypeUploadingScript) "$generateCentralSonatypeUploadingPart\n" else ""} | ||||
|  | ||||
|     task javadocJar(type: Jar) { | ||||
| task javadocJar(type: Jar) { | ||||
|     from javadoc | ||||
|         classifier = 'javadoc' | ||||
|     } | ||||
|     task sourcesJar(type: Jar) { | ||||
|     archiveClassifier = 'javadoc' | ||||
| } | ||||
| task sourcesJar(type: Jar) { | ||||
|     from sourceSets.main.allSource | ||||
|         classifier = 'sources' | ||||
|     } | ||||
|     archiveClassifier = 'sources' | ||||
| } | ||||
|  | ||||
|     publishing { | ||||
| publishing { | ||||
|     publications { | ||||
|         maven(MavenPublication) { | ||||
|             from components.java | ||||
| @@ -59,11 +63,6 @@ fun MavenConfig.buildJvmOnlyMavenConfig(licenses: List<License>): String = """ | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     } | ||||
|     ${if (includeGpgSigning) """ | ||||
|     signing { | ||||
|         useGpgCmd() | ||||
|         sign publishing.publications | ||||
|     } | ||||
|     """ else ""} | ||||
| } | ||||
| ${gpgSigning.generateMavenConfig()} | ||||
| """.trimIndent() | ||||
| @@ -1,15 +1,19 @@ | ||||
| package dev.inmo.kmppscriptbuilder.core.export.mpp | ||||
|  | ||||
| import dev.inmo.kmppscriptbuilder.core.export.generateCentralSonatypeUploadingPart | ||||
| import dev.inmo.kmppscriptbuilder.core.export.generateCentralSonatypeUploadingPartImports | ||||
| import dev.inmo.kmppscriptbuilder.core.export.generateMavenConfig | ||||
| import dev.inmo.kmppscriptbuilder.core.models.* | ||||
|  | ||||
| fun MavenConfig.buildMultiplatformMavenConfig(licenses: List<License>): String = """ | ||||
|     apply plugin: 'maven-publish' | ||||
|     ${if (includeGpgSigning) "apply plugin: 'signing'\n" else ""} | ||||
|     task javadocsJar(type: Jar) { | ||||
|         classifier = 'javadoc' | ||||
|     } | ||||
| ${if (includeCentralSonatypeUploadingScript) "$generateCentralSonatypeUploadingPartImports\n" else ""} | ||||
| apply plugin: 'maven-publish' | ||||
| ${if (includeCentralSonatypeUploadingScript) "$generateCentralSonatypeUploadingPart\n" else ""} | ||||
| task javadocsJar(type: Jar) { | ||||
|     archiveClassifier = 'javadoc' | ||||
| } | ||||
|  | ||||
|     publishing { | ||||
| publishing { | ||||
|     publications.all { | ||||
|         artifact javadocsJar | ||||
|  | ||||
| @@ -23,34 +27,25 @@ fun MavenConfig.buildMultiplatformMavenConfig(licenses: List<License>): String = | ||||
|                 url = "$vcsUrl" | ||||
|             } | ||||
|  | ||||
|                 developers { | ||||
|                     ${developers.joinToString("\n") { """ | ||||
|             developers {${developers.joinToString("\n") { """ | ||||
|                 developer { | ||||
|                     id = "${it.id}" | ||||
|                     name = "${it.name}" | ||||
|                     email = "${it.eMail}" | ||||
|                         } | ||||
|                     """ }} | ||||
|                 }""" }} | ||||
|             } | ||||
|  | ||||
|                 licenses { | ||||
|                     ${licenses.joinToString("\n") { """ | ||||
|             licenses {${licenses.joinToString("\n") { """ | ||||
|                 license { | ||||
|                     name = "${it.title}" | ||||
|                     url = "${it.url}" | ||||
|                         } | ||||
|                     """ }} | ||||
|                 }""" }} | ||||
|             } | ||||
|         } | ||||
|         repositories { | ||||
|             ${repositories.joinToString("\n            ") { it.build("            ") }} | ||||
|         } | ||||
|     } | ||||
|     } | ||||
|     ${if (includeGpgSigning) """ | ||||
|     signing { | ||||
|         useGpgCmd() | ||||
|         sign publishing.publications | ||||
|     } | ||||
|     """ else ""} | ||||
| } | ||||
|     ${gpgSigning.generateMavenConfig()} | ||||
| """.trimIndent() | ||||
| @@ -1,5 +1,6 @@ | ||||
| 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.mpp.buildMultiplatformMavenConfig | ||||
| import kotlinx.serialization.* | ||||
| @@ -32,12 +33,6 @@ object ProjectTypeSerializer : KSerializer<ProjectType> { | ||||
|  | ||||
| object MultiplatformProjectType : ProjectType() { | ||||
|     override val name: String = "Multiplatform" | ||||
| //    override fun buildBintrayGradleConfig( | ||||
| //        bintrayConfig: BintrayConfig, | ||||
| //        licenses: List<License> | ||||
| //    ): String = bintrayConfig.buildMultiplatformGradleConfig( | ||||
| //        licenses | ||||
| //    ) | ||||
|  | ||||
|     override fun buildMavenGradleConfig( | ||||
|         mavenConfig: MavenConfig, | ||||
| @@ -49,12 +44,6 @@ object MultiplatformProjectType : ProjectType() { | ||||
|  | ||||
| object JVMProjectType : ProjectType() { | ||||
|     override val name: String = "JVM" | ||||
| //    override fun buildBintrayGradleConfig( | ||||
| //        bintrayConfig: BintrayConfig, | ||||
| //        licenses: List<License> | ||||
| //    ): String = bintrayConfig.buildJvmOnlyGradleConfig( | ||||
| //        licenses | ||||
| //    ) | ||||
|  | ||||
|     override fun buildMavenGradleConfig( | ||||
|         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 | ||||
| data class Config( | ||||
|     val licenses: List<License>, | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import dev.inmo.kmppscriptbuilder.core.utils.serialFormat | ||||
| import io.ktor.client.HttpClient | ||||
| import io.ktor.client.request.get | ||||
| import io.ktor.client.request.url | ||||
| import io.ktor.client.statement.bodyAsText | ||||
| import kotlinx.serialization.Serializable | ||||
| import kotlinx.serialization.builtins.MapSerializer | ||||
| import kotlinx.serialization.builtins.serializer | ||||
| @@ -20,9 +21,9 @@ private val commonLicensesListDeserializer = MapSerializer(String.serializer(), | ||||
| private var licenses: Map<String, License>? = null | ||||
|  | ||||
| suspend fun HttpClient.getLicenses(): Map<String, License> { | ||||
|     val answer = get<String> { | ||||
|     val answer = get { | ||||
|         url("https://licenses.opendefinition.org/licenses/groups/all.json") | ||||
|     } | ||||
|     }.bodyAsText() | ||||
|     return serialFormat.decodeFromString(commonLicensesListDeserializer, answer).also { gotLicenses -> | ||||
|         licenses = gotLicenses | ||||
|     } | ||||
| @@ -30,19 +31,19 @@ suspend fun HttpClient.getLicenses(): Map<String, License> { | ||||
|  | ||||
| suspend fun HttpClient.searchLicense(name: String): List<License> { | ||||
|     val licenses = licenses ?: getLicenses() | ||||
|     val lowerCase = name.toLowerCase() | ||||
|     val upperCase = name.toUpperCase() | ||||
|     val lowerCase = name.lowercase() | ||||
|     val upperCase = name.uppercase() | ||||
|     return licenses.values.filter { | ||||
|         it.title.toLowerCase().contains(lowerCase) || it.title.toUpperCase().contains(upperCase) || it.title.contains(name) | ||||
|             || it.id.toLowerCase().contains(lowerCase) || it.id.toUpperCase().contains(upperCase) || it.id.contains(name) | ||||
|         it.title.lowercase().contains(lowerCase) || it.title.uppercase().contains(upperCase) || it.title.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> { | ||||
|     val lowerCase = name.toLowerCase() | ||||
|     val upperCase = name.toUpperCase() | ||||
|     val lowerCase = name.lowercase() | ||||
|     val upperCase = name.uppercase() | ||||
|     return values.filter { | ||||
|         it.title.toLowerCase().contains(lowerCase) || it.title.toUpperCase().contains(upperCase) || it.title.contains(name) | ||||
|             || it.id.toLowerCase().contains(lowerCase) || it.id.toUpperCase().contains(upperCase) || it.id.contains(name) | ||||
|         it.title.lowercase().contains(lowerCase) || it.title.uppercase().contains(upperCase) || it.title.contains(name) | ||||
|             || it.id.lowercase().contains(lowerCase) || it.id.uppercase().contains(upperCase) || it.id.contains(name) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,40 +2,120 @@ package dev.inmo.kmppscriptbuilder.core.models | ||||
|  | ||||
| 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 | ||||
| data class MavenConfig( | ||||
|     val name: String, | ||||
|     val description: String, | ||||
|     val url: String, | ||||
|     val vcsUrl: String, | ||||
|     val includeGpgSigning: Boolean = false, | ||||
|     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, | ||||
|     val includeCentralSonatypeUploadingScript: Boolean = false, | ||||
| ) | ||||
|  | ||||
| @Serializable | ||||
| data class MavenPublishingRepository( | ||||
|     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 { | ||||
|         val usernameProperty = "${nameCapitalized}_USER" | ||||
|         val passwordProperty = "${nameCapitalized}_PASSWORD" | ||||
|         return """if ((project.hasProperty('${usernameProperty}') || System.getenv('${usernameProperty}') != null) && (project.hasProperty('${passwordProperty}') || System.getenv('${passwordProperty}') != null)) { | ||||
|     maven { | ||||
|         name = "$name" | ||||
|         url = uri("$url") | ||||
|     @Serializable | ||||
|     sealed interface CredentialsType { | ||||
|         @Serializable | ||||
|         object Nothing: CredentialsType { | ||||
|             override fun buildCheckPart(): String = "true" | ||||
|             override fun buildCredsPart(): String = "" | ||||
|         } | ||||
|         @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 { | ||||
|             username = project.hasProperty('${usernameProperty}') ? project.property('${usernameProperty}') : System.getenv('${usernameProperty}') | ||||
|             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}') | ||||
|         } | ||||
|  | ||||
|         authentication { | ||||
|             header(HttpHeaderAuthentication) | ||||
|         }""" | ||||
|             } | ||||
|  | ||||
|             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") | ||||
|     } | ||||
| } | ||||
|  | ||||
| val SonatypeRepository = MavenPublishingRepository("sonatype", "https://oss.sonatype.org/service/local/staging/deploy/maven2/") | ||||
| val CentralSonatypeRepository = MavenPublishingRepository("sonatype", "https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/") | ||||
|   | ||||
| @@ -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() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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.desktop.utils.* | ||||
| 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( | ||||
|     id: String = "", | ||||
| @@ -22,10 +27,10 @@ class DevelopersView : ListView<DeveloperState>("Developers info") { | ||||
|     var developers: List<Developer> | ||||
|         get() = itemsList.map { it.toDeveloper() } | ||||
|         set(value) { | ||||
|             itemsList.clear() | ||||
|             itemsList.addAll( | ||||
|                 value.map { it.toDeveloperState() } | ||||
|             ) | ||||
|             itemsList.apply { | ||||
|                 clear() | ||||
|                 addAll(value.map { it.toDeveloperState() }) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|     override val addItemText: String = "Add developer" | ||||
| @@ -34,18 +39,19 @@ class DevelopersView : ListView<DeveloperState>("Developers info") { | ||||
|     override fun createItem(): DeveloperState = DeveloperState() | ||||
|     @Composable | ||||
|     override fun buildView(item: DeveloperState) { | ||||
|         CommonText("Developer username") | ||||
|         CommonTextField( | ||||
|             item.id, | ||||
|             "Developer username" | ||||
|         ) { item.id = it } | ||||
|         DefaultSmallVerticalMargin() | ||||
|         CommonText("Developer name") | ||||
|         CommonTextField( | ||||
|             item.name, | ||||
|             "Developer name" | ||||
|         ) { item.name = it } | ||||
|         DefaultSmallVerticalMargin() | ||||
|         CommonText("Developer E-Mail") | ||||
|         CommonTextField( | ||||
|             item.eMail, | ||||
|             "Developer E-Mail" | ||||
|         ) { item.eMail = it } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @@ -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() | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,132 @@ | ||||
| package dev.inmo.kmppscriptbuilder.core.ui | ||||
|  | ||||
| import androidx.compose.runtime.* | ||||
| import dev.inmo.kmppscriptbuilder.core.models.CentralSonatypeRepository | ||||
| 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 var publishToCentralSonatypeProperty by mutableStateOf(false) | ||||
|     internal var includeCentralSonatypeUploadingScriptProperty by mutableStateOf(false) | ||||
|     internal val developersView = DevelopersView() | ||||
|     internal val repositoriesView = RepositoriesView() | ||||
|  | ||||
|     var mavenConfig: MavenConfig | ||||
|         get() = MavenConfig( | ||||
|             name = projectNameProperty.ifBlank { defaultProjectName }, | ||||
|             description = projectDescriptionProperty.ifBlank { defaultProjectDescription }, | ||||
|             url = projectUrlProperty, | ||||
|             vcsUrl = projectVcsUrlProperty, | ||||
|             developers = developersView.developers, | ||||
|             repositories = repositoriesView.repositories + if (publishToMavenCentralProperty) { | ||||
|                 listOf( | ||||
|                     if (publishToCentralSonatypeProperty) { | ||||
|                         CentralSonatypeRepository | ||||
|                     } else { | ||||
|                         SonatypeRepository | ||||
|                     } | ||||
|                 ) | ||||
|             } else { | ||||
|                 emptyList() | ||||
|             }, | ||||
|             gpgSigning = gpgSignProperty, | ||||
|             includeCentralSonatypeUploadingScript = includeCentralSonatypeUploadingScriptProperty | ||||
|         ) | ||||
|         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 || it == CentralSonatypeRepository } | ||||
|             developersView.developers = value.developers | ||||
|             repositoriesView.repositories = value.repositories.filter { it != SonatypeRepository && it != CentralSonatypeRepository } | ||||
|             publishToCentralSonatypeProperty = value.repositories.any { it == CentralSonatypeRepository } | ||||
|             includeCentralSonatypeUploadingScriptProperty = value.includeCentralSonatypeUploadingScript | ||||
|         } | ||||
|  | ||||
|     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 } | ||||
|         if (publishToMavenCentralProperty) { | ||||
|             SwitchWithLabel( | ||||
|                 "Use Central Sonatype instead of OSSRH (OSSRH has been deprecated)", | ||||
|                 publishToCentralSonatypeProperty, | ||||
|                 placeSwitchAtTheStart = true | ||||
|             ) { publishToCentralSonatypeProperty = it } | ||||
|             if (publishToCentralSonatypeProperty) { | ||||
|                 SwitchWithLabel( | ||||
|                     "Add 'uploadSonatypePublication' root project task (required for Central Sonatype publishing)", | ||||
|                     includeCentralSonatypeUploadingScriptProperty, | ||||
|                     placeSwitchAtTheStart = true | ||||
|                 ) { includeCentralSonatypeUploadingScriptProperty = it } | ||||
|             } | ||||
|         } | ||||
|         developersView.build() | ||||
|         repositoriesView.build() | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| package dev.inmo.kmppscriptbuilder.core.ui.utils | ||||
|  | ||||
| import androidx.compose.runtime.Composable | ||||
|  | ||||
| fun interface Drawer<T> { | ||||
|     @Composable | ||||
|     fun T.draw() | ||||
| } | ||||
| @@ -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) | ||||
| @@ -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 | ||||
| @@ -0,0 +1,3 @@ | ||||
| package dev.inmo.kmppscriptbuilder.core.utils | ||||
|  | ||||
| expect fun openLink(link: String): Boolean | ||||
| Before Width: | Height: | Size: 463 B After Width: | Height: | Size: 463 B | 
| Before Width: | Height: | Size: 452 B After Width: | Height: | Size: 452 B | 
| Before Width: | Height: | Size: 744 B After Width: | Height: | Size: 744 B | 
| Before Width: | Height: | Size: 502 B After Width: | Height: | Size: 502 B | 
| @@ -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() | ||||
|         } | ||||
|     }) | ||||
| } | ||||
| @@ -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) | ||||
| @@ -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() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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()) } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
| @@ -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) | ||||
| @@ -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) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
| } | ||||
| @@ -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() | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
| @@ -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 | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
							
								
								
									
										4
									
								
								core/src/jsMain/resources/css/internal.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | ||||
| .no-transform { | ||||
|     text-transform: none; | ||||
| } | ||||
|  | ||||
							
								
								
									
										1
									
								
								core/src/jsMain/resources/css/uikit.min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										73
									
								
								core/src/jsMain/resources/index.html
									
									
									
									
									
										Normal 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> | ||||
							
								
								
									
										1
									
								
								core/src/jsMain/resources/js/uikit-icons.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										1
									
								
								core/src/jsMain/resources/js/uikit.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,68 @@ | ||||
| package dev.inmo.kmppscriptbuilder.core | ||||
|  | ||||
| import androidx.compose.foundation.* | ||||
| import androidx.compose.foundation.layout.* | ||||
| import androidx.compose.material.Colors | ||||
| import androidx.compose.material.MaterialTheme | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.graphics.Color | ||||
| import androidx.compose.ui.window.Window | ||||
| import androidx.compose.ui.window.application | ||||
| import dev.inmo.kmppscriptbuilder.core.ui.BuilderView | ||||
| import dev.inmo.kmppscriptbuilder.core.utils.loadConfigFile | ||||
| import java.io.File | ||||
|  | ||||
| //private val uncaughtExceptionsBC = BroadcastChannel<DefaultErrorHandler.ErrorEvent>(Channel.CONFLATED) | ||||
| //val uncaughtExceptionsFlow: Flow<DefaultErrorHandler.ErrorEvent> = uncaughtExceptionsBC.asFlow() | ||||
|  | ||||
| fun main(args: Array<String>) = application { | ||||
|     Window(onCloseRequest = ::exitApplication, title = "Kotlin Multiplatform Publishing Builder") { | ||||
|         val builder = BuilderView() | ||||
|         MaterialTheme( | ||||
|             Colors( | ||||
|                 primary = Color(0x01, 0x57, 0x9b), | ||||
|                 primaryVariant = Color(0x00, 0x2f, 0x6c), | ||||
|                 secondary = Color(0xb2, 0xeb, 0xf2), | ||||
|                 secondaryVariant = Color(0x81, 0xb9, 0xbf), | ||||
|                 background = Color(0xe1, 0xe2, 0xe1), | ||||
|                 surface = Color(0xf5, 0xf5, 0xf6), | ||||
|                 error = Color(0xb7, 0x1c, 0x1c), | ||||
|                 onPrimary = Color.White, | ||||
|                 onSecondary = Color.Black, | ||||
|                 onBackground = Color.Black, | ||||
|                 onSurface = Color.Black, | ||||
|                 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) | ||||
|                 ) { | ||||
|                     Column { | ||||
|                         builder.build() | ||||
|                     } | ||||
|  | ||||
|                 } | ||||
|  | ||||
|                 VerticalScrollbar( | ||||
|                     modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(), | ||||
|                     adapter = rememberScrollbarAdapter(stateVertical) | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (args.isNotEmpty()) { | ||||
|             val config = loadConfigFile(File(args.first())) | ||||
|             builder.config = config | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
| } | ||||
| @@ -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) | ||||
| @@ -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() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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,) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
| @@ -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) | ||||
| @@ -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) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     ) | ||||
| } | ||||
| @@ -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() | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package dev.inmo.kmppscriptbuilder.desktop.utils | ||||
| package dev.inmo.kmppscriptbuilder.core.ui.utils | ||||
| 
 | ||||
| import java.io.File | ||||
| import javax.swing.filechooser.FileFilter | ||||
| @@ -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.utils.serialFormat | ||||
| import dev.inmo.kmppscriptbuilder.core.ui.utils.FileFilter | ||||
| import dev.inmo.micro_utils.common.MPPFile | ||||
| import java.io.File | ||||
| 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 | ||||
|     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) | ||||
|     fc.addChoosableFileFilter(FileFilter("Kotlin Publication Scripts Builder", Regex(".*\\.$appExtension"))) | ||||
|     fc.addChoosableFileFilter(FileFilter("JSON", Regex(".*\\.json"))) | ||||
|     return when (fc.showOpenDialog(null)) { | ||||
|     when (fc.showOpenDialog(null)) { | ||||
|         JFileChooser.APPROVE_OPTION -> { | ||||
|             val file = fc.selectedFile | ||||
|             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 { | ||||
|         it.writeText(serialFormat.encodeToString(Config.serializer(), config)) | ||||
|     } != null | ||||
| } | ||||
| 
 | ||||
| fun exportGradle(config: Config): Boolean { | ||||
| actual fun exportGradle(config: Config): Boolean { | ||||
|     val fc = JFileChooser(lastFile ?.parent) | ||||
|     fc.fileSelectionMode = JFileChooser.DIRECTORIES_ONLY | ||||
|     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) | ||||
|     fc.addChoosableFileFilter(FileFilter("Kotlin Publication Scripts Builder", Regex(".*\\.$appExtension"))) | ||||
|     fc.addChoosableFileFilter(FileFilter("JSON", Regex(".*\\.json"))) | ||||
| @@ -0,0 +1,17 @@ | ||||
| package dev.inmo.kmppscriptbuilder.core.utils | ||||
|  | ||||
| import java.awt.Desktop | ||||
| import java.net.URI | ||||
|  | ||||
| actual 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 | ||||
| } | ||||
| @@ -0,0 +1,2 @@ | ||||
| package dev.inmo.kmppscriptbuilder.core.utils  | ||||
|  | ||||
| @@ -1 +0,0 @@ | ||||
| <manifest package="dev.inmo.KotlinPublicationScriptsBuilder.core"/> | ||||
| @@ -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' | ||||
|     } | ||||
| } | ||||
| @@ -1,37 +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" | ||||
|             useIR = true | ||||
|         } | ||||
|     } | ||||
|     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" | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -1,62 +0,0 @@ | ||||
| package dev.inmo.kmppscriptbuilder.desktop | ||||
|  | ||||
| import androidx.compose.desktop.Window | ||||
| import androidx.compose.foundation.* | ||||
| import androidx.compose.foundation.layout.* | ||||
| import androidx.compose.material.* | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.graphics.Color | ||||
| import dev.inmo.kmppscriptbuilder.desktop.utils.* | ||||
| import dev.inmo.kmppscriptbuilder.desktop.views.* | ||||
| import java.io.File | ||||
|  | ||||
| //private val uncaughtExceptionsBC = BroadcastChannel<DefaultErrorHandler.ErrorEvent>(Channel.CONFLATED) | ||||
| //val uncaughtExceptionsFlow: Flow<DefaultErrorHandler.ErrorEvent> = uncaughtExceptionsBC.asFlow() | ||||
|  | ||||
| fun main(args: Array<String>) = Window(title = "Kotlin Multiplatform Publishing Builder") { | ||||
|     val builder = BuilderView() | ||||
|     MaterialTheme( | ||||
|         Colors( | ||||
|             primary = Color(0x01, 0x57, 0x9b), | ||||
|             primaryVariant = Color(0x00, 0x2f, 0x6c), | ||||
|             secondary = Color(0xb2, 0xeb, 0xf2), | ||||
|             secondaryVariant = Color(0x81, 0xb9, 0xbf), | ||||
|             background = Color(0xe1, 0xe2, 0xe1), | ||||
|             surface = Color(0xf5, 0xf5, 0xf6), | ||||
|             error = Color(0xb7, 0x1c, 0x1c), | ||||
|             onPrimary = Color.White, | ||||
|             onSecondary = Color.Black, | ||||
|             onBackground = Color.Black, | ||||
|             onSurface = Color.Black, | ||||
|             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) | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (args.isNotEmpty()) { | ||||
|         val config = loadConfigFile(File(args.first())) | ||||
|         builder.config = config | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
| } | ||||
| @@ -1,103 +0,0 @@ | ||||
| package dev.inmo.kmppscriptbuilder.desktop.views | ||||
|  | ||||
| import androidx.compose.foundation.Image | ||||
| import androidx.compose.foundation.clickable | ||||
| import androidx.compose.foundation.layout.* | ||||
| import androidx.compose.material.* | ||||
| import androidx.compose.runtime.* | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.res.svgResource | ||||
| 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 | ||||
|         } | ||||
|  | ||||
|     @Composable | ||||
|     override fun build() { | ||||
|         Box(Modifier.fillMaxSize()) { | ||||
|             Column() { | ||||
|                 TopAppBar( | ||||
|                     @Composable { | ||||
|                         CommonText("Kotlin publication scripts builder", Modifier.clickable { println(config) }) | ||||
|                     }, | ||||
|                     actions = { | ||||
|                         IconButton( | ||||
|                             { | ||||
|                                 loadConfig()?.also { | ||||
|                                     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) { | ||||
|                             IconButton( | ||||
|                                 { | ||||
|                                     exportGradle(config) | ||||
|                                 } | ||||
|                             ) { | ||||
|                                 Image( | ||||
|                                     painter = svgResource("images/export_gradle.svg"), | ||||
|                                     contentDescription = "Export Gradle script" | ||||
|                                 ) | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         IconButton( | ||||
|                             { | ||||
|                                 if (saveAs(config)) { | ||||
|                                     saveAvailableState = true | ||||
|                                 } | ||||
|                             } | ||||
|                         ) { | ||||
|                             Image( | ||||
|                                 painter = svgResource("images/save_as.svg"), | ||||
|                                 contentDescription = "Export Gradle script" | ||||
|                             ) | ||||
|                         } | ||||
|                     } | ||||
|                 ) | ||||
|                 Column(Modifier.padding(8.dp)) { | ||||
|                     projectTypeView.init() | ||||
|                     Divider() | ||||
|                     licensesView.init() | ||||
|                     Divider() | ||||
|                     mavenInfoView.init() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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.toLowerCase() 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") | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,33 +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.Modifier | ||||
| import androidx.compose.ui.unit.dp | ||||
| import dev.inmo.kmppscriptbuilder.desktop.utils.* | ||||
|  | ||||
| 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) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,77 +0,0 @@ | ||||
| package dev.inmo.kmppscriptbuilder.desktop.views | ||||
|  | ||||
| import androidx.compose.foundation.layout.ColumnScope | ||||
| import androidx.compose.runtime.* | ||||
| import dev.inmo.kmppscriptbuilder.core.models.MavenConfig | ||||
| import dev.inmo.kmppscriptbuilder.core.models.SonatypeRepository | ||||
| 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 includeGpgSignProperty by mutableStateOf(true) | ||||
|     private var publishToMavenCentralProperty by mutableStateOf(false) | ||||
|     private val developersView = DevelopersView() | ||||
|     private val repositoriesView = RepositoriesView() | ||||
|  | ||||
|     var mavenConfig: MavenConfig | ||||
|         get() = MavenConfig( | ||||
|             projectNameProperty, | ||||
|             projectDescriptionProperty, | ||||
|             projectUrlProperty, | ||||
|             projectVcsUrlProperty, | ||||
|             includeGpgSignProperty, | ||||
|             developersView.developers, | ||||
|             repositoriesView.repositories + if (publishToMavenCentralProperty) { | ||||
|                 listOf(SonatypeRepository) | ||||
|             } else { | ||||
|                 emptyList() | ||||
|             } | ||||
|         ) | ||||
|         set(value) { | ||||
|             projectNameProperty = value.name | ||||
|             projectDescriptionProperty = value.description | ||||
|             projectUrlProperty = value.url | ||||
|             projectVcsUrlProperty = value.vcsUrl | ||||
|             includeGpgSignProperty = value.includeGpgSigning | ||||
|             publishToMavenCentralProperty = value.repositories.any { it == SonatypeRepository } | ||||
|             developersView.developers = value.developers | ||||
|             repositoriesView.repositories = value.repositories.filter { it != SonatypeRepository } | ||||
| //            developersView.developers = value.developers | ||||
|         } | ||||
|  | ||||
|     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 } | ||||
|  | ||||
|         SwitchWithLabel( | ||||
|             "Include GPG Signing", | ||||
|             includeGpgSignProperty, | ||||
|             placeSwitchAtTheStart = true | ||||
|         ) { includeGpgSignProperty = it } | ||||
|  | ||||
|         SwitchWithLabel( | ||||
|             "Include publication to MavenCentral", | ||||
|             publishToMavenCentralProperty, | ||||
|             placeSwitchAtTheStart = true | ||||
|         ) { publishToMavenCentralProperty = it } | ||||
|         developersView.init() | ||||
|         repositoriesView.init() | ||||
|     } | ||||
| } | ||||
| @@ -1,33 +0,0 @@ | ||||
| package dev.inmo.kmppscriptbuilder.desktop.views | ||||
|  | ||||
| import androidx.compose.foundation.layout.* | ||||
| import androidx.compose.material.Switch | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.runtime.* | ||||
| 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") { | ||||
|     private var projectTypeState by mutableStateOf<Boolean>(false) | ||||
|     private val calculatedProjectType: ProjectType | ||||
|         get() = if (projectTypeState) JVMProjectType else MultiplatformProjectType | ||||
|     var projectType: ProjectType | ||||
|         get() = calculatedProjectType | ||||
|         set(value) { | ||||
|             projectTypeState = value == JVMProjectType | ||||
|         } | ||||
|  | ||||
|     override val content: @Composable ColumnScope.() -> Unit = { | ||||
|         Row(horizontalArrangement = Arrangement.spacedBy(5.dp)) { | ||||
|             Text("Multiplatform", Modifier.alignByBaseline()) | ||||
|             Switch( | ||||
|                 projectTypeState, | ||||
|                 { projectTypeState = it }, | ||||
|                 Modifier.padding(4.dp, 0.dp) | ||||
|             ) | ||||
|             Text("JVM", Modifier.alignByBaseline()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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.* | ||||
|  | ||||
| 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 } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -16,7 +16,6 @@ allprojects { | ||||
|         mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" | ||||
|         mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle" | ||||
|         mppJsProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJsProject.gradle" | ||||
|         mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle" | ||||
|  | ||||
|         defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle" | ||||
|  | ||||
|   | ||||
| @@ -3,30 +3,6 @@ org.gradle.parallel=true | ||||
| kotlin.js.generate.externals=true | ||||
| kotlin.incremental=true | ||||
| kotlin.incremental.js=true | ||||
| android.useAndroidX=true | ||||
| android.enableJetifier=true | ||||
|  | ||||
| kotlin_version=1.4.30 | ||||
| kotlin_coroutines_version=1.4.2 | ||||
| kotlin_serialisation_core_version=1.1.0 | ||||
| ktor_version=1.5.1 | ||||
| micro_utils_version=0.4.27 | ||||
|  | ||||
| compose_version=0.3.0 | ||||
|  | ||||
| # ANDROID | ||||
|  | ||||
| android_minSdkVersion=21 | ||||
| android_compileSdkVersion=30 | ||||
| android_buildToolsVersion=30.0.2 | ||||
| dexcount_version=2.0.0 | ||||
| junit_version=4.12 | ||||
| test_ext_junit_version=1.1.2 | ||||
| espresso_core=3.3.0 | ||||
|  | ||||
| # Dokka | ||||
|  | ||||
| dokka_version=1.4.20 | ||||
|  | ||||
| # Project data | ||||
|  | ||||
|   | ||||
							
								
								
									
										45
									
								
								gradle/libs.versions.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,45 @@ | ||||
| [versions] | ||||
|  | ||||
| kt = "1.9.20" | ||||
| kt-serialization = "1.6.0" | ||||
| kt-coroutines = "1.7.3" | ||||
|  | ||||
| jb-compose = "1.5.10" | ||||
| jb-dokka = "1.9.10" | ||||
| microutils = "0.20.11" | ||||
| kjsuikit = "0.7.3" | ||||
|  | ||||
| ktor = "2.3.5" | ||||
|  | ||||
| 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" } | ||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip | ||||
|   | ||||
| @@ -4,9 +4,7 @@ project.group = "$group" | ||||
| // apply from: "$publishGradlePath" | ||||
|  | ||||
| kotlin { | ||||
|     jvm { | ||||
|         compilations.main.kotlinOptions.useIR = true | ||||
|     } | ||||
|     jvm() | ||||
|  | ||||
|     sourceSets { | ||||
|         commonMain { | ||||
|   | ||||
| @@ -5,17 +5,15 @@ project.group = "$group" | ||||
|  | ||||
| kotlin { | ||||
|     js (IR) { | ||||
|         browser { | ||||
|         browser() | ||||
|         binaries.executable() | ||||
|     } | ||||
|         nodejs() | ||||
|     } | ||||
|  | ||||
|     sourceSets { | ||||
|         commonMain { | ||||
|             dependencies { | ||||
|                 implementation kotlin('stdlib') | ||||
|                 api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version" | ||||
|                 api libs.kt.serialization | ||||
|             } | ||||
|         } | ||||
|         commonTest { | ||||
|   | ||||
| @@ -1,25 +1,20 @@ | ||||
| project.version = "$version" + System.getenv("additional_version") | ||||
| project.version = "$version" + (System.getenv("additional_version") == null ? "" : System.getenv("additional_version")) | ||||
| project.group = "$group" | ||||
|  | ||||
| // apply from: "$publishGradlePath" | ||||
|  | ||||
| kotlin { | ||||
|     jvm { | ||||
|         compilations.main.kotlinOptions.useIR = true | ||||
|     } | ||||
|     jvm() | ||||
|     js (IR) { | ||||
|         browser() | ||||
|         nodejs() | ||||
|     } | ||||
|     android { | ||||
|         publishAllLibraryVariants() | ||||
|     } | ||||
|  | ||||
|     sourceSets { | ||||
|         commonMain { | ||||
|             dependencies { | ||||
|                 implementation kotlin('stdlib') | ||||
|                 api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version" | ||||
|                 api libs.kt.serialization | ||||
|             } | ||||
|         } | ||||
|         commonTest { | ||||
| @@ -39,14 +34,5 @@ kotlin { | ||||
|                 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" | ||||
|   | ||||
| @@ -9,8 +9,8 @@ rootProject.name = 'kmppscriptbuilder' | ||||
|  | ||||
| String[] includes = [ | ||||
|         ":core", | ||||
|         ":desktop", | ||||
|         ":web" | ||||
| //        ":desktop", | ||||
| //        ":web" | ||||
| ] | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -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" | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
|             } | ||||
|         } | ||||
|     ) | ||||
| } | ||||
| @@ -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") | ||||
|         } | ||||
|     } | ||||
| @@ -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() | ||||
| @@ -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 | ||||
|         } | ||||
| } | ||||
| @@ -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 | ||||
|     } | ||||
| } | ||||
| @@ -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.toLowerCase() | ||||
|                         lowercased.all { it in lowercasedTitle } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             onChangeActor | ||||
|         } | ||||
|         private val searchElement = rootElement.createTextField("Quick add", "Type some license name part to find it").apply { | ||||
|             oninput = { | ||||
|                 changeActor.offer(Unit) | ||||
|                 false | ||||
|             } | ||||
|         } | ||||
|         private var searchString: String | ||||
|             get() = searchElement.value.toLowerCase() | ||||
|             set(value) { | ||||
|                 searchElement.value = value | ||||
|             } | ||||
|  | ||||
|         override fun HTMLElement.placeElement(value: License) { | ||||
|             createCommonButton(value.title).onclick = { | ||||
|                 searchString = "" | ||||
|                 licensesView.licenses += value | ||||
|                 changeActor.offer(Unit) | ||||
|                 false | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         override fun HTMLElement.updateElement(from: License, to: License) { | ||||
|             getElementsByTagName("button")[0] ?.remove() | ||||
|             placeElement(to) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private val licensesOffersList = LicenseOfferList( | ||||
|         rootElement.appendElement("div") { classList.add("uk-padding-small") } as HTMLElement, | ||||
|         this, | ||||
|         client, | ||||
|         scope | ||||
|     ) | ||||
|  | ||||
|     var licenses: List<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 ?: "" | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
| } | ||||
| @@ -1,44 +0,0 @@ | ||||
| package dev.inmo.kmppscriptbuilder.web.views | ||||
|  | ||||
| import dev.inmo.kmppscriptbuilder.core.models.MavenConfig | ||||
| import dev.inmo.kmppscriptbuilder.core.models.SonatypeRepository | ||||
| 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 includeGpgElement = document.getElementById("includeGpgSignToggle") as HTMLInputElement | ||||
|     private val includeMavenCentralElement = document.getElementById("includeMavenCentralTargetRepoToggle") as HTMLInputElement | ||||
|     private val developersView = DevelopersView(document.getElementById("developersListDiv") as HTMLElement) | ||||
|     private val repositoriesView = RepositoriesView(document.getElementById("repositoriesListDiv") as HTMLElement) | ||||
|  | ||||
|     var mavenConfig: MavenConfig | ||||
|         get() = MavenConfig( | ||||
|             nameElement.value, | ||||
|             descriptionElement.value, | ||||
|             urlElement.value, | ||||
|             vcsUrlElement.value, | ||||
|             includeGpgElement.checked, | ||||
|             developersView.developers, | ||||
|             repositoriesView.repositories + if (includeMavenCentralElement.checked) { | ||||
|                 listOf(SonatypeRepository) | ||||
|             } else { | ||||
|                 emptyList() | ||||
|             } | ||||
|         ) | ||||
|         set(value) { | ||||
|             nameElement.value = value.name | ||||
|             descriptionElement.value = value.description | ||||
|             urlElement.value = value.url | ||||
|             vcsUrlElement.value = value.vcsUrl | ||||
|             includeGpgElement.checked = value.includeGpgSigning | ||||
|             developersView.developers = value.developers | ||||
|             val reposWithoutSonatype = value.repositories.filter { it != SonatypeRepository } | ||||
|             includeMavenCentralElement.checked = value.repositories.size != reposWithoutSonatype.size | ||||
|             repositoriesView.repositories = value.repositories | ||||
|         } | ||||
| } | ||||
| @@ -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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,33 +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 | ||||
|  | ||||
|     var projectType: ProjectType | ||||
|         get() = if (jvmProjectTypeElement.ukActive) { | ||||
|             JVMProjectType | ||||
|         } else { | ||||
|             MultiplatformProjectType | ||||
|         } | ||||
|         set(value) { | ||||
|             mppProjectTypeElement.ukActive = value == MultiplatformProjectType | ||||
|             jvmProjectTypeElement.ukActive = value == JVMProjectType | ||||
|         } | ||||
|  | ||||
|     init { | ||||
|         mppProjectTypeElement.onclick = { | ||||
|             projectType = MultiplatformProjectType | ||||
|             Unit | ||||
|         } | ||||
|         jvmProjectTypeElement.onclick = { | ||||
|             projectType = JVMProjectType | ||||
|             Unit | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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.name | ||||
|     } | ||||
|  | ||||
|     override fun HTMLElement.updateElement(from: MavenPublishingRepository, to: MavenPublishingRepository) { | ||||
|         nameElement.value = to.name | ||||
|         urlElement.value = to.url | ||||
|     } | ||||
| } | ||||
| @@ -1,3 +0,0 @@ | ||||
| package dev.inmo.kmppscriptbuilder.web.views | ||||
|  | ||||
| interface View | ||||
| @@ -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 | ||||
| } | ||||
| @@ -1,80 +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> | ||||
|         <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> | ||||
|                 </ul> | ||||
|             </div> | ||||
|             <legend class="uk-legend">Licenses</legend> | ||||
|             <div id="licensesListDiv" class="uk-padding-small"> | ||||
| <!--                <div class="uk-margin uk-width-1-1">--> | ||||
| <!--                    <input id="searchFilterInput" class="uk-input uk-width-expand" type="text" placeholder="License search filter">--> | ||||
| <!--                </div>--> | ||||
| <!--                <button class="uk-button uk-button-primary">Add empty license</button>--> | ||||
|             </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><input id="includeGpgSignToggle" class="uk-checkbox" type="checkbox" checked> Include GPG Signing</label> | ||||
|                 </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> | ||||