From c506079fdf3723e781a5a300295ed4a197aa8ae1 Mon Sep 17 00:00:00 2001 From: Martynas Petuska Date: Sun, 16 May 2021 19:54:32 +0100 Subject: [PATCH 001/103] common & scanner modules --- common/build.gradle.kts | 24 +++++++++ .../kotlin/domain/KotlinMPPLibrary.kt | 46 +++++++++++++++++ .../commonMain/kotlin/domain/KotlinTarget.kt | 49 +++++++++++++++++++ .../commonMain/kotlin/domain/MavenArtifact.kt | 31 ++++++++++++ .../src/jvmMain/kotlin}/util/Env.kt | 6 +-- scanner/build.gradle.kts | 3 +- .../kotlin/scanner/client/AnchorClient.kt | 2 +- .../scanner/client/ArtifactoryClient.kt | 2 +- .../main/kotlin/scanner/client/JBossClient.kt | 2 +- .../scanner/client/MavenRepositoryClient.kt | 4 +- .../main/kotlin/scanner/domain/Repository.kt | 2 +- .../processor/GradleModuleProcessor.kt | 2 +- .../scanner/service/MavenScannerService.kt | 4 +- .../service/MavenScannerServiceImpl.kt | 2 +- .../main/kotlin/scanner/util/PrivateEnv.kt | 2 +- .../processor/GradleModuleProcessorTest.kt | 2 +- 16 files changed, 167 insertions(+), 16 deletions(-) create mode 100644 common/build.gradle.kts create mode 100644 common/src/commonMain/kotlin/domain/KotlinMPPLibrary.kt create mode 100644 common/src/commonMain/kotlin/domain/KotlinTarget.kt create mode 100644 common/src/commonMain/kotlin/domain/MavenArtifact.kt rename {src/jvmMain/kotlin/kamp => common/src/jvmMain/kotlin}/util/Env.kt (88%) diff --git a/common/build.gradle.kts b/common/build.gradle.kts new file mode 100644 index 0000000..f101c48 --- /dev/null +++ b/common/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + kotlin("multiplatform") + kotlin("plugin.serialization") +} + +kotlin { + jvm() + js { + browser() + } + + sourceSets { + named("commonMain") { + dependencies { + api("io.ktor:ktor-client-serialization:_") + } + } + named("jvmMain") { + dependencies { + api(kotlin("reflect")) + } + } + } +} diff --git a/common/src/commonMain/kotlin/domain/KotlinMPPLibrary.kt b/common/src/commonMain/kotlin/domain/KotlinMPPLibrary.kt new file mode 100644 index 0000000..f8e62ad --- /dev/null +++ b/common/src/commonMain/kotlin/domain/KotlinMPPLibrary.kt @@ -0,0 +1,46 @@ +package common.domain + +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +@Serializable +data class KotlinMPPLibrary( + override val group: String, + override val name: String, + override val latestVersion: String, + override val releaseVersion: String?, + override val versions: List?, + override val lastUpdated: Long?, + val targets: Set, + val description: String?, + val website: String?, + val scm: String?, +) : MavenArtifact { + constructor( + artifact: MavenArtifact, + targets: Set, + description: String?, + website: String?, + scm: String?, + ) : this( + group = artifact.group, + name = artifact.name, + latestVersion = artifact.latestVersion, + releaseVersion = artifact.releaseVersion, + versions = artifact.versions, + lastUpdated = artifact.lastUpdated, + targets = targets, + description = description, + website = website, + scm = scm + ) + + val _id: String + + @Transient + val isMultiplatform: Boolean = targets.size > 1 + + init { + _id = "$group:$name" + } +} diff --git a/common/src/commonMain/kotlin/domain/KotlinTarget.kt b/common/src/commonMain/kotlin/domain/KotlinTarget.kt new file mode 100644 index 0000000..72c34f7 --- /dev/null +++ b/common/src/commonMain/kotlin/domain/KotlinTarget.kt @@ -0,0 +1,49 @@ +package common.domain + +import kotlinx.serialization.Serializable + +@Serializable +class KotlinTarget private constructor( + val category: String, + val platform: String, +) { + + override fun toString(): String = "$category:$platform" + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is KotlinTarget) return false + + if (platform != other.platform) return false + if (category != other.category) return false + + return true + } + + override fun hashCode(): Int { + var result = platform.hashCode() + result = 31 * result + category.hashCode() + return result + } + + object Common { + const val category: String = "common" + operator fun invoke(): KotlinTarget = KotlinTarget(category, category) + } + + object JS { + const val category: String = "js" + fun Legacy(): KotlinTarget = KotlinTarget(category, "legacy") + fun IR(): KotlinTarget = KotlinTarget(category, "ir") + } + + object JVM { + const val category: String = "jvm" + fun Java(): KotlinTarget = KotlinTarget(category, "jvm") + fun Android(): KotlinTarget = KotlinTarget(category, "android") + } + + object Native { + const val category: String = "native" + operator fun invoke(platform: String): KotlinTarget = KotlinTarget(category, platform) + } +} diff --git a/common/src/commonMain/kotlin/domain/MavenArtifact.kt b/common/src/commonMain/kotlin/domain/MavenArtifact.kt new file mode 100644 index 0000000..5a2f755 --- /dev/null +++ b/common/src/commonMain/kotlin/domain/MavenArtifact.kt @@ -0,0 +1,31 @@ +package common.domain + +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient + +interface MavenArtifact { + val group: String + val name: String + val latestVersion: String + val releaseVersion: String? + val versions: List? + val lastUpdated: Long? + + @Transient + val version: String + get() = releaseVersion ?: latestVersion + + @Transient + val path: String + get() = "$group:$name:$version" +} + +@Serializable +data class MavenArtifactImpl( + override val group: String, + override val name: String, + override val latestVersion: String, + override val releaseVersion: String?, + override val versions: List?, + override val lastUpdated: Long?, +) : MavenArtifact diff --git a/src/jvmMain/kotlin/kamp/util/Env.kt b/common/src/jvmMain/kotlin/util/Env.kt similarity index 88% rename from src/jvmMain/kotlin/kamp/util/Env.kt rename to common/src/jvmMain/kotlin/util/Env.kt index a0e7921..0c4a3cf 100644 --- a/src/jvmMain/kotlin/kamp/util/Env.kt +++ b/common/src/jvmMain/kotlin/util/Env.kt @@ -1,11 +1,11 @@ -package kamp.util +package common.util import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 import kotlin.reflect.full.memberProperties -public abstract class Env { +abstract class Env { protected class EnvDelegate(private val converter: EnvDelegate<*>.(String?) -> T) : ReadOnlyProperty { private val camelSplitRegex = "(?): T { @@ -16,7 +16,7 @@ public abstract class Env { return converter(value) } - public fun findEnv(name: String): String? = System.getenv()[name] + fun findEnv(name: String): String? = System.getenv()[name] } override fun toString(): String = this::class.memberProperties.joinToString("\n") { diff --git a/scanner/build.gradle.kts b/scanner/build.gradle.kts index 97b2a0d..c28c2fa 100644 --- a/scanner/build.gradle.kts +++ b/scanner/build.gradle.kts @@ -5,7 +5,7 @@ plugins { kotlin { dependencies { - implementation(project(rootProject.path)) + implementation(project(":common")) implementation("io.ktor:ktor-client-cio:_") implementation("io.ktor:ktor-client-auth:_") implementation("org.kodein.di:kodein-di:_") @@ -44,6 +44,7 @@ tasks { } jar { val classpath = configurations.runtimeClasspath.get().files.map { if (it.isDirectory) it else zipTree(it) } + duplicatesStrategy = DuplicatesStrategy.WARN from(classpath) { exclude("META-INF/*.SF") exclude("META-INF/*.DSA") diff --git a/scanner/src/main/kotlin/scanner/client/AnchorClient.kt b/scanner/src/main/kotlin/scanner/client/AnchorClient.kt index a84a46a..7bf65f7 100644 --- a/scanner/src/main/kotlin/scanner/client/AnchorClient.kt +++ b/scanner/src/main/kotlin/scanner/client/AnchorClient.kt @@ -1,6 +1,6 @@ package scanner.client -import kamp.domain.MavenArtifact +import common.domain.MavenArtifact import org.jsoup.nodes.Document abstract class AnchorClient( diff --git a/scanner/src/main/kotlin/scanner/client/ArtifactoryClient.kt b/scanner/src/main/kotlin/scanner/client/ArtifactoryClient.kt index 68fa97f..8f94322 100644 --- a/scanner/src/main/kotlin/scanner/client/ArtifactoryClient.kt +++ b/scanner/src/main/kotlin/scanner/client/ArtifactoryClient.kt @@ -1,7 +1,7 @@ package scanner.client +import common.domain.MavenArtifactImpl import io.ktor.client.HttpClient -import kamp.domain.MavenArtifactImpl import kotlinx.serialization.json.Json class ArtifactoryClient( diff --git a/scanner/src/main/kotlin/scanner/client/JBossClient.kt b/scanner/src/main/kotlin/scanner/client/JBossClient.kt index c464a4b..2677272 100644 --- a/scanner/src/main/kotlin/scanner/client/JBossClient.kt +++ b/scanner/src/main/kotlin/scanner/client/JBossClient.kt @@ -1,7 +1,7 @@ package scanner.client +import common.domain.MavenArtifactImpl import io.ktor.client.HttpClient -import kamp.domain.MavenArtifactImpl import kotlinx.serialization.json.Json class JBossClient( diff --git a/scanner/src/main/kotlin/scanner/client/MavenRepositoryClient.kt b/scanner/src/main/kotlin/scanner/client/MavenRepositoryClient.kt index f9a0904..9a2a7b2 100644 --- a/scanner/src/main/kotlin/scanner/client/MavenRepositoryClient.kt +++ b/scanner/src/main/kotlin/scanner/client/MavenRepositoryClient.kt @@ -1,12 +1,12 @@ package scanner.client +import common.domain.MavenArtifact +import common.domain.MavenArtifactImpl import io.ktor.client.HttpClient import io.ktor.client.request.get import io.ktor.util.date.GMTDate import io.ktor.util.date.Month import io.ktor.utils.io.core.Closeable -import kamp.domain.MavenArtifact -import kamp.domain.MavenArtifactImpl import kotlinx.coroutines.coroutineScope import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json diff --git a/scanner/src/main/kotlin/scanner/domain/Repository.kt b/scanner/src/main/kotlin/scanner/domain/Repository.kt index beb5347..7f57b2c 100644 --- a/scanner/src/main/kotlin/scanner/domain/Repository.kt +++ b/scanner/src/main/kotlin/scanner/domain/Repository.kt @@ -1,6 +1,6 @@ package scanner.domain -import kamp.domain.MavenArtifactImpl +import common.domain.MavenArtifactImpl import org.kodein.di.DirectDIAware import org.kodein.di.instance import scanner.client.ArtifactoryClient diff --git a/scanner/src/main/kotlin/scanner/processor/GradleModuleProcessor.kt b/scanner/src/main/kotlin/scanner/processor/GradleModuleProcessor.kt index 983ef70..46adb5b 100644 --- a/scanner/src/main/kotlin/scanner/processor/GradleModuleProcessor.kt +++ b/scanner/src/main/kotlin/scanner/processor/GradleModuleProcessor.kt @@ -1,6 +1,6 @@ package scanner.processor -import kamp.domain.KotlinTarget +import common.domain.KotlinTarget import scanner.domain.GradleModule class GradleModuleProcessor { diff --git a/scanner/src/main/kotlin/scanner/service/MavenScannerService.kt b/scanner/src/main/kotlin/scanner/service/MavenScannerService.kt index 9163a60..3776b51 100644 --- a/scanner/src/main/kotlin/scanner/service/MavenScannerService.kt +++ b/scanner/src/main/kotlin/scanner/service/MavenScannerService.kt @@ -1,8 +1,8 @@ package scanner.service +import common.domain.KotlinMPPLibrary +import common.domain.MavenArtifact import io.ktor.utils.io.core.Closeable -import kamp.domain.KotlinMPPLibrary -import kamp.domain.MavenArtifact import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.flow.Flow diff --git a/scanner/src/main/kotlin/scanner/service/MavenScannerServiceImpl.kt b/scanner/src/main/kotlin/scanner/service/MavenScannerServiceImpl.kt index 3caeb54..eb1c117 100644 --- a/scanner/src/main/kotlin/scanner/service/MavenScannerServiceImpl.kt +++ b/scanner/src/main/kotlin/scanner/service/MavenScannerServiceImpl.kt @@ -1,6 +1,6 @@ package scanner.service -import kamp.domain.MavenArtifactImpl +import common.domain.MavenArtifactImpl import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel diff --git a/scanner/src/main/kotlin/scanner/util/PrivateEnv.kt b/scanner/src/main/kotlin/scanner/util/PrivateEnv.kt index 1ea097a..3d271d6 100644 --- a/scanner/src/main/kotlin/scanner/util/PrivateEnv.kt +++ b/scanner/src/main/kotlin/scanner/util/PrivateEnv.kt @@ -1,6 +1,6 @@ package scanner.util -import kamp.util.Env +import common.util.Env object PrivateEnv : Env() { val API_URL by EnvDelegate { it ?: "http://localhost:8080" } diff --git a/scanner/src/test/kotlin/scanner/processor/GradleModuleProcessorTest.kt b/scanner/src/test/kotlin/scanner/processor/GradleModuleProcessorTest.kt index ab4de71..b662b7c 100644 --- a/scanner/src/test/kotlin/scanner/processor/GradleModuleProcessorTest.kt +++ b/scanner/src/test/kotlin/scanner/processor/GradleModuleProcessorTest.kt @@ -1,9 +1,9 @@ package scanner.processor +import common.domain.KotlinTarget import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotest.matchers.shouldBe -import kamp.domain.KotlinTarget import scanner.domain.GradleModule import scanner.testutil.parseJsonFile From 5a5e24faff9e12b69d98f1819ef1d40019563222 Mon Sep 17 00:00:00 2001 From: Martynas Petuska Date: Sun, 16 May 2021 19:55:12 +0100 Subject: [PATCH 002/103] common & scanner modules --- app/build.gradle.kts | 116 ------------------ app/{ => server}/Dockerfile | 0 app/server/build.gradle.kts | 75 +++++++++++ .../src/main/kotlin}/config/di.kt | 14 +-- .../src/main/kotlin}/config/features.kt | 4 +- .../src/main/kotlin}/config/routing.kt | 19 ++- .../src/main/kotlin/main.kt} | 11 +- .../main/kotlin}/service/LibraryService.kt | 20 ++- .../src/main/kotlin}/util/env.kt | 4 +- .../src/main/kotlin}/util/kodein.kt | 2 +- .../src/main/kotlin}/util/paging.kt | 2 +- .../main}/resources/ApplicationInsights.xml | 0 .../src/main}/resources/application.conf | 2 +- .../src/main}/resources/logback.xml | 0 .../kotlin/app/service/LibraryService.kt | 20 --- app/src/jsMain/kotlin/app/config/di.kt | 39 ------ app/src/jsMain/kotlin/app/config/env.kt | 27 ---- .../kotlin/app/service/LibraryService.kt | 36 ------ app/src/jsMain/kotlin/app/util/general.kt | 33 ----- build.gradle.kts | 49 ++------ buildSrc/build.gradle.kts | 2 - gradle.properties | 3 +- gradle/wrapper/gradle-wrapper.properties | 2 +- scanner/build.gradle.kts | 5 +- .../kotlin/scanner/client/AnchorClient.kt | 2 +- .../scanner/client/ArtifactoryClient.kt | 2 +- .../main/kotlin/scanner/client/JBossClient.kt | 2 +- .../scanner/client/MavenRepositoryClient.kt | 4 +- .../main/kotlin/scanner/domain/Repository.kt | 2 +- .../main/kotlin/scanner/{index.kt => main.kt} | 0 .../processor/GradleModuleProcessor.kt | 2 +- .../scanner/service/MavenScannerService.kt | 4 +- .../service/MavenScannerServiceImpl.kt | 2 +- .../main/kotlin/scanner/util/PrivateEnv.kt | 2 +- .../processor/GradleModuleProcessorTest.kt | 2 +- settings.gradle.kts | 14 ++- {common => shared}/build.gradle.kts | 5 +- .../kotlin/domain/KotlinMPPLibrary.kt | 2 +- .../commonMain/kotlin/domain/KotlinTarget.kt | 2 +- .../commonMain/kotlin/domain/MavenArtifact.kt | 2 +- .../src/commonMain/kotlin}/util/DIModule.kt | 2 +- .../src/jvmMain/kotlin/util/Env.kt | 2 +- .../kotlin/kamp/domain/KotlinMPPLibrary.kt | 46 ------- .../kotlin/kamp/domain/KotlinTarget.kt | 49 -------- .../kotlin/kamp/domain/MavenArtifact.kt | 31 ----- versions.properties | 2 + 46 files changed, 167 insertions(+), 499 deletions(-) delete mode 100644 app/build.gradle.kts rename app/{ => server}/Dockerfile (100%) create mode 100644 app/server/build.gradle.kts rename app/{src/jvmMain/kotlin/app => server/src/main/kotlin}/config/di.kt (69%) rename app/{src/jvmMain/kotlin/app => server/src/main/kotlin}/config/features.kt (94%) rename app/{src/jvmMain/kotlin/app => server/src/main/kotlin}/config/routing.kt (85%) rename app/{src/jvmMain/kotlin/app/index.kt => server/src/main/kotlin/main.kt} (68%) rename app/{src/jvmMain/kotlin/app => server/src/main/kotlin}/service/LibraryService.kt (86%) rename app/{src/jvmMain/kotlin/app => server/src/main/kotlin}/util/env.kt (90%) rename app/{src/jvmMain/kotlin/app => server/src/main/kotlin}/util/kodein.kt (96%) rename app/{src/jvmMain/kotlin/app => server/src/main/kotlin}/util/paging.kt (97%) rename app/{src/jvmMain => server/src/main}/resources/ApplicationInsights.xml (100%) rename app/{src/jvmMain => server/src/main}/resources/application.conf (87%) rename app/{src/jvmMain => server/src/main}/resources/logback.xml (100%) delete mode 100644 app/src/commonMain/kotlin/app/service/LibraryService.kt delete mode 100644 app/src/jsMain/kotlin/app/config/di.kt delete mode 100644 app/src/jsMain/kotlin/app/config/env.kt delete mode 100644 app/src/jsMain/kotlin/app/service/LibraryService.kt delete mode 100644 app/src/jsMain/kotlin/app/util/general.kt rename scanner/src/main/kotlin/scanner/{index.kt => main.kt} (100%) rename {common => shared}/build.gradle.kts (67%) rename {common => shared}/src/commonMain/kotlin/domain/KotlinMPPLibrary.kt (97%) rename {common => shared}/src/commonMain/kotlin/domain/KotlinTarget.kt (98%) rename {common => shared}/src/commonMain/kotlin/domain/MavenArtifact.kt (96%) rename {app/src/commonMain/kotlin/app => shared/src/commonMain/kotlin}/util/DIModule.kt (96%) rename {common => shared}/src/jvmMain/kotlin/util/Env.kt (97%) delete mode 100644 src/commonMain/kotlin/kamp/domain/KotlinMPPLibrary.kt delete mode 100644 src/commonMain/kotlin/kamp/domain/KotlinTarget.kt delete mode 100644 src/commonMain/kotlin/kamp/domain/MavenArtifact.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts deleted file mode 100644 index c736118..0000000 --- a/app/build.gradle.kts +++ /dev/null @@ -1,116 +0,0 @@ -plugins { - kotlin("multiplatform") - kotlin("plugin.serialization") -} - -val mainClassName = "app.IndexKt" -val jsOutputFile = "kamp-$version.js" -kotlin { - jvm {} - js { - useCommonJs() - binaries.executable() - browser { - distribution { - directory = buildDir.resolve("dist/WEB-INF") - } - commonWebpackConfig { - cssSupport.enabled = true - outputFileName = jsOutputFile - devServer = devServer?.copy( - port = 3000, - proxy = mapOf("/api/*" to "http://localhost:8080"), - open = false - ) - } - } - } - sourceSets { - named("commonMain") { - dependencies { - implementation(project(rootProject.path)) - implementation("org.kodein.di:kodein-di:_") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:_") - } - } - named("jvmMain") { - dependencies { - implementation("io.ktor:ktor-server-cio:_") - implementation("io.ktor:ktor-serialization:_") - implementation("io.ktor:ktor-auth:_") - implementation("ch.qos.logback:logback-classic:_") - implementation("org.kodein.di:kodein-di-framework-ktor-server-jvm:_") - implementation("com.microsoft.azure:applicationinsights-web-auto:_") - implementation("org.litote.kmongo:kmongo-coroutine-serialization:_") - } - } - named("jsMain") { - dependencies { - implementation("io.ktor:ktor-client-serialization:_") - implementation("dev.fritz2:components:_") - } - languageSettings.apply { - useExperimentalAnnotation("kotlinx.coroutines.ExperimentalCoroutinesApi") - } - } - named("jvmTest") { - dependencies { - implementation("io.kotest:kotest-runner-junit5:_") - } - } - } -} - -afterEvaluate { - tasks { - named("jsProcessResources", Copy::class) { - eachFile { - if (name == "index.html") { - expand(project.properties + mapOf("jsOutputFileName" to jsOutputFile)) - } - } - } - val jsBrowserDistribution by getting - val compileKotlinJvm by getting - val jvmProcessResources by getting - create("jvmRun") { - group = "run" - main = mainClassName - dependsOn(compileKotlinJvm, jvmProcessResources) - classpath = files( - configurations["jvmRuntimeClasspath"], - compileKotlinJvm.outputs, - jvmProcessResources.outputs, - buildDir.resolve("dist") - ) - } - named("jvmJar", Jar::class) { - dependsOn(jsBrowserDistribution) - into("WEB-INF") { - from(jsBrowserDistribution) - } - val classpath = - configurations.getByName("jvmRuntimeClasspath").map { if (it.isDirectory) it else zipTree(it) } - from(classpath) { - exclude("META-INF/*.SF") - exclude("META-INF/*.DSA") - exclude("META-INF/*.RSA") - } - - manifest { - attributes( - "Main-Class" to mainClassName, - "Built-By" to System.getProperty("user.name"), - "Build-Jdk" to System.getProperty("java.version"), - "Implementation-Version" to project.version, - "Created-By" to "Gradle v${org.gradle.util.GradleVersion.current()}", - "Created-From" to Git.headCommitHash - ) - } - - inputs.property("mainClassName", mainClassName) - inputs.files(classpath) - inputs.files(jsBrowserDistribution.outputs) - } - } -} diff --git a/app/Dockerfile b/app/server/Dockerfile similarity index 100% rename from app/Dockerfile rename to app/server/Dockerfile diff --git a/app/server/build.gradle.kts b/app/server/build.gradle.kts new file mode 100644 index 0000000..cd363a5 --- /dev/null +++ b/app/server/build.gradle.kts @@ -0,0 +1,75 @@ +plugins { + kotlin("jvm") + kotlin("plugin.serialization") +} + +val mainClassName = "app.server.MainKt" +kotlin { + sourceSets { + main { + dependencies { + implementation(project(":app:common")) + implementation("io.ktor:ktor-server-cio:_") + implementation("io.ktor:ktor-serialization:_") + implementation("io.ktor:ktor-auth:_") + implementation("ch.qos.logback:logback-classic:_") + implementation("org.kodein.di:kodein-di-framework-ktor-server-jvm:_") + implementation("com.microsoft.azure:applicationinsights-web-auto:_") + implementation("org.litote.kmongo:kmongo-coroutine-serialization:_") + } + } + test { + dependencies { + implementation("io.kotest:kotest-runner-junit5:_") + } + } + } +} + +afterEvaluate { + tasks { + val jsBrowserDistribution = findByPath(":app:client:jsBrowserDistribution")!! + val compileKotlin by getting + val processResources by getting + create("run") { + group = "run" + main = mainClassName + dependsOn(compileKotlin, processResources) + classpath = files( + configurations["runtimeClasspath"], + compileKotlin.outputs, + processResources.outputs, + buildDir.resolve("dist") + ) + } + jar { + dependsOn(jsBrowserDistribution) + into("WEB-INF") { + from(jsBrowserDistribution) + } + val classpath = + configurations.getByName("runtimeClasspath").map { if (it.isDirectory) it else zipTree(it) } + duplicatesStrategy = DuplicatesStrategy.WARN + from(classpath) { + exclude("META-INF/*.SF") + exclude("META-INF/*.DSA") + exclude("META-INF/*.RSA") + } + + manifest { + attributes( + "Main-Class" to mainClassName, + "Built-By" to System.getProperty("user.name"), + "Build-Jdk" to System.getProperty("java.version"), + "Implementation-Version" to project.version, + "Created-By" to "Gradle v${org.gradle.util.GradleVersion.current()}", + "Created-From" to Git.headCommitHash + ) + } + + inputs.property("mainClassName", mainClassName) + inputs.files(classpath) + inputs.files(jsBrowserDistribution.outputs) + } + } +} diff --git a/app/src/jvmMain/kotlin/app/config/di.kt b/app/server/src/main/kotlin/config/di.kt similarity index 69% rename from app/src/jvmMain/kotlin/app/config/di.kt rename to app/server/src/main/kotlin/config/di.kt index 26a1d50..f2cbfb5 100644 --- a/app/src/jvmMain/kotlin/app/config/di.kt +++ b/app/server/src/main/kotlin/config/di.kt @@ -1,10 +1,8 @@ -package app.config +package app.server.config -import app.service.LibraryService -import app.util.DIModule -import app.util.PrivateEnv +import app.server.service.LibraryService +import app.server.util.PrivateEnv import io.ktor.application.Application -import kamp.domain.KotlinMPPLibrary import org.kodein.di.bind import org.kodein.di.instance import org.kodein.di.ktor.CallScope @@ -14,14 +12,16 @@ import org.kodein.di.singleton import org.litote.kmongo.coroutine.CoroutineClient import org.litote.kmongo.coroutine.CoroutineCollection import org.litote.kmongo.coroutine.coroutine -import org.litote.kmongo.reactivestreams.KMongo +import org.litote.kmongo.reactivestreams.KMongo.createClient +import shared.domain.KotlinMPPLibrary +import shared.util.DIModule fun Application.diConfig() = di { import(services) } private val services by DIModule { - bind() with singleton { KMongo.createClient(PrivateEnv.MONGO_STRING).coroutine } + bind() with singleton { createClient(PrivateEnv.MONGO_STRING).coroutine } bind>() with singleton { instance().getDatabase(PrivateEnv.MONGO_DATABASE).getCollection("libraries") } diff --git a/app/src/jvmMain/kotlin/app/config/features.kt b/app/server/src/main/kotlin/config/features.kt similarity index 94% rename from app/src/jvmMain/kotlin/app/config/features.kt rename to app/server/src/main/kotlin/config/features.kt index 3d1ee3d..e6beffd 100644 --- a/app/src/jvmMain/kotlin/app/config/features.kt +++ b/app/server/src/main/kotlin/config/features.kt @@ -1,6 +1,6 @@ -package app.config +package app.server.config -import app.util.PrivateEnv +import app.server.util.PrivateEnv import io.ktor.application.Application import io.ktor.application.install import io.ktor.auth.Authentication diff --git a/app/src/jvmMain/kotlin/app/config/routing.kt b/app/server/src/main/kotlin/config/routing.kt similarity index 85% rename from app/src/jvmMain/kotlin/app/config/routing.kt rename to app/server/src/main/kotlin/config/routing.kt index 2ab62b6..30c8e06 100644 --- a/app/src/jvmMain/kotlin/app/config/routing.kt +++ b/app/server/src/main/kotlin/config/routing.kt @@ -1,13 +1,12 @@ -package app.config +package app.server.config -import app.service.LibraryService -import app.service.path -import app.util.PublicEnv -import app.util.inject -import app.util.page -import app.util.pageSize -import app.util.search -import app.util.targets +import app.server.service.LibraryService +import app.server.util.PublicEnv +import app.server.util.inject +import app.server.util.page +import app.server.util.pageSize +import app.server.util.search +import app.server.util.targets import io.ktor.application.Application import io.ktor.application.call import io.ktor.auth.authenticate @@ -34,7 +33,7 @@ fun Application.routing() = routing { } private fun Routing.libraries() = - route(LibraryService.path) { + route("/libraries") { get { val service by inject() call.respond( diff --git a/app/src/jvmMain/kotlin/app/index.kt b/app/server/src/main/kotlin/main.kt similarity index 68% rename from app/src/jvmMain/kotlin/app/index.kt rename to app/server/src/main/kotlin/main.kt index 09f20e5..5b3b18e 100644 --- a/app/src/jvmMain/kotlin/app/index.kt +++ b/app/server/src/main/kotlin/main.kt @@ -1,9 +1,9 @@ -package app +package app.server -import app.config.diConfig -import app.config.features -import app.config.routing -import app.util.PublicEnv +import app.server.config.diConfig +import app.server.config.features +import app.server.config.routing +import app.server.util.PublicEnv import io.ktor.application.Application import io.ktor.application.log import io.ktor.server.cio.EngineMain @@ -13,6 +13,7 @@ fun main(args: Array) { EngineMain.main(args) } +@Suppress("unused") fun Application.module() { features() routing() diff --git a/app/src/jvmMain/kotlin/app/service/LibraryService.kt b/app/server/src/main/kotlin/service/LibraryService.kt similarity index 86% rename from app/src/jvmMain/kotlin/app/service/LibraryService.kt rename to app/server/src/main/kotlin/service/LibraryService.kt index 6a31590..dfb7a4e 100644 --- a/app/src/jvmMain/kotlin/app/service/LibraryService.kt +++ b/app/server/src/main/kotlin/service/LibraryService.kt @@ -1,11 +1,10 @@ -package app.service +package app.server.service -import app.domain.LibraryCount -import app.domain.PagedResponse -import app.util.buildNextUrl -import app.util.buildPrevUrl +import app.common.domain.LibraryCount +import app.common.domain.PagedResponse +import app.server.util.buildNextUrl +import app.server.util.buildPrevUrl import io.ktor.application.ApplicationCall -import kamp.domain.KotlinMPPLibrary import org.litote.kmongo.MongoOperator.all import org.litote.kmongo.MongoOperator.and import org.litote.kmongo.MongoOperator.language @@ -14,8 +13,9 @@ import org.litote.kmongo.MongoOperator.search import org.litote.kmongo.MongoOperator.text import org.litote.kmongo.bson import org.litote.kmongo.coroutine.CoroutineCollection +import shared.domain.KotlinMPPLibrary -actual class LibraryService( +class LibraryService( private val call: ApplicationCall, private val collection: CoroutineCollection, ) { @@ -56,7 +56,7 @@ actual class LibraryService( return finalQuery to projection } - actual suspend fun getAll( + suspend fun getAll( page: Int, size: Int, search: String?, @@ -80,13 +80,11 @@ actual class LibraryService( ) } - actual suspend fun getCount(search: String?, targets: Set?): LibraryCount { + suspend fun getCount(search: String?, targets: Set?): LibraryCount { return LibraryCount(collection.countDocuments(buildQuery(search, targets).first ?: "{}")) } suspend fun create(library: KotlinMPPLibrary) { collection.save(library) } - - actual companion object } diff --git a/app/src/jvmMain/kotlin/app/util/env.kt b/app/server/src/main/kotlin/util/env.kt similarity index 90% rename from app/src/jvmMain/kotlin/app/util/env.kt rename to app/server/src/main/kotlin/util/env.kt index dd64881..11ea9f1 100644 --- a/app/src/jvmMain/kotlin/app/util/env.kt +++ b/app/server/src/main/kotlin/util/env.kt @@ -1,6 +1,6 @@ -package app.util +package app.server.util -import kamp.util.Env +import shared.util.Env object PrivateEnv : Env() { val MONGO_STRING by EnvDelegate { it ?: "mongodb://localhost:27017" } diff --git a/app/src/jvmMain/kotlin/app/util/kodein.kt b/app/server/src/main/kotlin/util/kodein.kt similarity index 96% rename from app/src/jvmMain/kotlin/app/util/kodein.kt rename to app/server/src/main/kotlin/util/kodein.kt index 39bd693..19a4144 100644 --- a/app/src/jvmMain/kotlin/app/util/kodein.kt +++ b/app/server/src/main/kotlin/util/kodein.kt @@ -1,4 +1,4 @@ -package app.util +package app.server.util import io.ktor.application.ApplicationCall import io.ktor.util.pipeline.PipelineContext diff --git a/app/src/jvmMain/kotlin/app/util/paging.kt b/app/server/src/main/kotlin/util/paging.kt similarity index 97% rename from app/src/jvmMain/kotlin/app/util/paging.kt rename to app/server/src/main/kotlin/util/paging.kt index 681accb..2219656 100644 --- a/app/src/jvmMain/kotlin/app/util/paging.kt +++ b/app/server/src/main/kotlin/util/paging.kt @@ -1,4 +1,4 @@ -package app.util +package app.server.util import io.ktor.http.URLBuilder import io.ktor.request.ApplicationRequest diff --git a/app/src/jvmMain/resources/ApplicationInsights.xml b/app/server/src/main/resources/ApplicationInsights.xml similarity index 100% rename from app/src/jvmMain/resources/ApplicationInsights.xml rename to app/server/src/main/resources/ApplicationInsights.xml diff --git a/app/src/jvmMain/resources/application.conf b/app/server/src/main/resources/application.conf similarity index 87% rename from app/src/jvmMain/resources/application.conf rename to app/server/src/main/resources/application.conf index db42cde..7d62677 100644 --- a/app/src/jvmMain/resources/application.conf +++ b/app/server/src/main/resources/application.conf @@ -12,6 +12,6 @@ ktor { } application { - modules = [app.IndexKt.module] + modules = [app.server.MainKt.module] } } diff --git a/app/src/jvmMain/resources/logback.xml b/app/server/src/main/resources/logback.xml similarity index 100% rename from app/src/jvmMain/resources/logback.xml rename to app/server/src/main/resources/logback.xml diff --git a/app/src/commonMain/kotlin/app/service/LibraryService.kt b/app/src/commonMain/kotlin/app/service/LibraryService.kt deleted file mode 100644 index 3459ea6..0000000 --- a/app/src/commonMain/kotlin/app/service/LibraryService.kt +++ /dev/null @@ -1,20 +0,0 @@ -package app.service - -import app.domain.LibraryCount -import app.domain.PagedResponse -import kamp.domain.KotlinMPPLibrary - -expect class LibraryService { - suspend fun getAll( - page: Int, - size: Int = 20, - search: String? = null, - targets: Set? = null, - ): PagedResponse - - suspend fun getCount(search: String? = null, targets: Set? = null): LibraryCount - - companion object -} - -val LibraryService.Companion.path get() = "/api/libraries" diff --git a/app/src/jsMain/kotlin/app/config/di.kt b/app/src/jsMain/kotlin/app/config/di.kt deleted file mode 100644 index 52953be..0000000 --- a/app/src/jsMain/kotlin/app/config/di.kt +++ /dev/null @@ -1,39 +0,0 @@ -package app.config - -import app.service.LibraryService -import app.util.DIModule -import io.ktor.client.HttpClient -import io.ktor.client.features.defaultRequest -import io.ktor.client.features.json.JsonFeature -import io.ktor.client.features.json.serializer.KotlinxSerializer -import io.ktor.http.ContentType -import io.ktor.http.contentType -import kotlinx.serialization.json.Json -import org.kodein.di.DI -import org.kodein.di.bind -import org.kodein.di.instance -import org.kodein.di.provider -import org.kodein.di.singleton - -private val services by DIModule { - bind() with provider { LibraryService(instance()) } -} - -val di = DI { - bind { - provider { - Json {} - } - } - bind() with singleton { - HttpClient { - install(JsonFeature) { - serializer = KotlinxSerializer(instance()) - } - defaultRequest { - contentType(ContentType.Application.Json) - } - } - } - import(services) -} diff --git a/app/src/jsMain/kotlin/app/config/env.kt b/app/src/jsMain/kotlin/app/config/env.kt deleted file mode 100644 index 7e4033a..0000000 --- a/app/src/jsMain/kotlin/app/config/env.kt +++ /dev/null @@ -1,27 +0,0 @@ -package app.config - -import kotlinx.browser.window -import kotlinx.coroutines.await -import org.w3c.dom.Window - -external interface AppEnv { - val API_URL: String -} - -inline val Window.env: AppEnv - get() = asDynamic().env.unsafeCast() - -suspend fun loadEnv(): AppEnv { - val env: AppEnv = window.fetch("/application.env").await().text().await() - .split("\n") - .filter(String::isNotBlank) - .joinToString(",", "{", "}") { - val (key, value) = it.split("=", limit = 2).let { c -> - c[0] to c[1] - } - "\"$key\": \"$value\"" - }.let(JSON::parse) - window.asDynamic().env = env - requireNotNull(window.env.API_URL) - return env -} diff --git a/app/src/jsMain/kotlin/app/service/LibraryService.kt b/app/src/jsMain/kotlin/app/service/LibraryService.kt deleted file mode 100644 index dc4d5ac..0000000 --- a/app/src/jsMain/kotlin/app/service/LibraryService.kt +++ /dev/null @@ -1,36 +0,0 @@ -package app.service - -import app.domain.LibraryCount -import app.domain.PagedResponse -import app.util.toApi -import io.ktor.client.HttpClient -import io.ktor.client.request.get -import kamp.domain.KotlinMPPLibrary - -actual class LibraryService(private val client: HttpClient) { - actual suspend fun getAll( - page: Int, - size: Int, - search: String?, - targets: Set?, - ): PagedResponse { - val pagination = "page=$page&size=$size" - val searchQuery = search?.let { "search=$it" } ?: "" - val targetsQuery = targets?.joinToString(prefix = "target=", separator = "&target=") ?: "" - - return client.get("${path}${buildQuery(pagination, searchQuery, targetsQuery)}".toApi()) - } - - actual suspend fun getCount(search: String?, targets: Set?): LibraryCount { - val searchQuery = search?.let { "search=$it" } - val targetsQuery = targets?.joinToString(prefix = "target=", separator = "&target=") - - return client.get("$path/count${buildQuery(searchQuery, targetsQuery)}".toApi()) - } - - private fun buildQuery(vararg query: String?): String { - return query.toSet().filterNotNull().takeIf(List::isNotEmpty)?.joinToString("&", prefix = "?") ?: "" - } - - actual companion object -} diff --git a/app/src/jsMain/kotlin/app/util/general.kt b/app/src/jsMain/kotlin/app/util/general.kt deleted file mode 100644 index 28eeea3..0000000 --- a/app/src/jsMain/kotlin/app/util/general.kt +++ /dev/null @@ -1,33 +0,0 @@ -package app.util - -import app.config.env -import app.view.KampComponent -import dev.fritz2.dom.html.RenderContext -import dev.fritz2.styling.params.BasicComponent -import dev.fritz2.styling.params.BoxParams -import dev.fritz2.styling.params.styled -import kotlinx.browser.window -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch - -external fun require(module: String): dynamic - -inline fun suspending(crossinline block: suspend CoroutineScope.(T1, T2) -> Unit): (T1, T2) -> Unit = - { t1, t2 -> suspending { block(t1, t2) } } - -inline fun suspending(crossinline block: suspend CoroutineScope.(T1) -> Unit): (T1) -> Unit = - { suspending { block(it) } } - -inline fun suspending(crossinline block: suspend CoroutineScope.() -> Unit) { - GlobalScope.launch { block() } -} - -fun String.toApi() = "${window.env.API_URL}/${this.removePrefix("/")}" - -typealias StyledComponent = RenderContext.(style: BoxParams.() -> Unit, block: E.() -> Unit) -> E - -@KampComponent -fun styled(component: BasicComponent): StyledComponent = { style, block -> - (component.styled(styling = style))(block) -} diff --git a/build.gradle.kts b/build.gradle.kts index ff946ec..5d7f28a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,5 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { - kotlin("multiplatform") - id("org.jetbrains.kotlin.plugin.serialization") + kotlin("multiplatform") apply false id("com.github.jakemarsden.git-hooks") id("org.jlleitschuh.gradle.ktlint") idea @@ -18,50 +15,30 @@ gitHooks { ) } -idea { - module { - isDownloadSources = true - isDownloadJavadoc = true - } -} - allprojects { apply(plugin = "org.jlleitschuh.gradle.ktlint") + apply(plugin = "idea") + + idea { + module { + isDownloadSources = true + isDownloadJavadoc = true + } + } repositories { mavenCentral() - maven("https://jitpack.io") - maven("https://kotlin.bintray.com/kotlinx") + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") } + tasks { withType { useJUnitPlatform() } - withType { + withType { kotlinOptions { useIR = true - jvmTarget = "${JavaVersion.VERSION_11}" - } - } - } -} - -kotlin { - explicitApi() - jvm() - js { - browser() - } - - sourceSets { - named("commonMain") { - dependencies { - api("io.ktor:ktor-client-serialization:_") - } - } - named("jvmMain") { - dependencies { - api(kotlin("reflect")) + jvmTarget = "${project.properties["org.gradle.project.targetCompatibility"]}" } } } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index e98dc09..a6217e0 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -3,7 +3,5 @@ plugins { } repositories { - mavenLocal() - jcenter() mavenCentral() } diff --git a/gradle.properties b/gradle.properties index 48007bb..5dc7a06 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,6 +4,7 @@ kotlin.mpp.stability.nowarn=true kotlin.js.generate.externals=false kotlin.js.compiler=ir kotlin.incremental.js=true -org.gradle.project.sourceCompatibility=11 +org.gradle.project.targetCompatibility=11 + version=0.0.0 group=lt.petuska diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d913..f371643 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/scanner/build.gradle.kts b/scanner/build.gradle.kts index c28c2fa..80fb8b6 100644 --- a/scanner/build.gradle.kts +++ b/scanner/build.gradle.kts @@ -5,9 +5,10 @@ plugins { kotlin { dependencies { - implementation(project(":common")) + implementation(project(":shared")) implementation("io.ktor:ktor-client-cio:_") implementation("io.ktor:ktor-client-auth:_") + implementation("io.ktor:ktor-client-serialization:_") implementation("org.kodein.di:kodein-di:_") implementation("org.jsoup:jsoup:_") implementation("ch.qos.logback:logback-classic:_") @@ -27,7 +28,7 @@ kotlin { } } -val mainClassName = "scanner.IndexKt" +val mainClassName = "scanner.MainKt" tasks { val compileKotlin by getting diff --git a/scanner/src/main/kotlin/scanner/client/AnchorClient.kt b/scanner/src/main/kotlin/scanner/client/AnchorClient.kt index 7bf65f7..d20ecd0 100644 --- a/scanner/src/main/kotlin/scanner/client/AnchorClient.kt +++ b/scanner/src/main/kotlin/scanner/client/AnchorClient.kt @@ -1,7 +1,7 @@ package scanner.client -import common.domain.MavenArtifact import org.jsoup.nodes.Document +import shared.domain.MavenArtifact abstract class AnchorClient( val url: String, diff --git a/scanner/src/main/kotlin/scanner/client/ArtifactoryClient.kt b/scanner/src/main/kotlin/scanner/client/ArtifactoryClient.kt index 8f94322..80d4b4d 100644 --- a/scanner/src/main/kotlin/scanner/client/ArtifactoryClient.kt +++ b/scanner/src/main/kotlin/scanner/client/ArtifactoryClient.kt @@ -1,8 +1,8 @@ package scanner.client -import common.domain.MavenArtifactImpl import io.ktor.client.HttpClient import kotlinx.serialization.json.Json +import shared.domain.MavenArtifactImpl class ArtifactoryClient( url: String, diff --git a/scanner/src/main/kotlin/scanner/client/JBossClient.kt b/scanner/src/main/kotlin/scanner/client/JBossClient.kt index 2677272..ec7f093 100644 --- a/scanner/src/main/kotlin/scanner/client/JBossClient.kt +++ b/scanner/src/main/kotlin/scanner/client/JBossClient.kt @@ -1,8 +1,8 @@ package scanner.client -import common.domain.MavenArtifactImpl import io.ktor.client.HttpClient import kotlinx.serialization.json.Json +import shared.domain.MavenArtifactImpl class JBossClient( url: String, diff --git a/scanner/src/main/kotlin/scanner/client/MavenRepositoryClient.kt b/scanner/src/main/kotlin/scanner/client/MavenRepositoryClient.kt index 9a2a7b2..c71b01a 100644 --- a/scanner/src/main/kotlin/scanner/client/MavenRepositoryClient.kt +++ b/scanner/src/main/kotlin/scanner/client/MavenRepositoryClient.kt @@ -1,7 +1,5 @@ package scanner.client -import common.domain.MavenArtifact -import common.domain.MavenArtifactImpl import io.ktor.client.HttpClient import io.ktor.client.request.get import io.ktor.util.date.GMTDate @@ -15,6 +13,8 @@ import scanner.domain.GradleModule import scanner.util.LoggerDelegate import scanner.util.asDocument import scanner.util.supervisedAsync +import shared.domain.MavenArtifact +import shared.domain.MavenArtifactImpl abstract class MavenRepositoryClient( private val defaultRepositoryRootUrl: String, diff --git a/scanner/src/main/kotlin/scanner/domain/Repository.kt b/scanner/src/main/kotlin/scanner/domain/Repository.kt index 7f57b2c..2305e1c 100644 --- a/scanner/src/main/kotlin/scanner/domain/Repository.kt +++ b/scanner/src/main/kotlin/scanner/domain/Repository.kt @@ -1,11 +1,11 @@ package scanner.domain -import common.domain.MavenArtifactImpl import org.kodein.di.DirectDIAware import org.kodein.di.instance import scanner.client.ArtifactoryClient import scanner.client.JBossClient import scanner.client.MavenRepositoryClient +import shared.domain.MavenArtifactImpl enum class Repository( val alias: String, diff --git a/scanner/src/main/kotlin/scanner/index.kt b/scanner/src/main/kotlin/scanner/main.kt similarity index 100% rename from scanner/src/main/kotlin/scanner/index.kt rename to scanner/src/main/kotlin/scanner/main.kt diff --git a/scanner/src/main/kotlin/scanner/processor/GradleModuleProcessor.kt b/scanner/src/main/kotlin/scanner/processor/GradleModuleProcessor.kt index 46adb5b..f90827e 100644 --- a/scanner/src/main/kotlin/scanner/processor/GradleModuleProcessor.kt +++ b/scanner/src/main/kotlin/scanner/processor/GradleModuleProcessor.kt @@ -1,7 +1,7 @@ package scanner.processor -import common.domain.KotlinTarget import scanner.domain.GradleModule +import shared.domain.KotlinTarget class GradleModuleProcessor { val kotlinVersion: String = "1.4.30" diff --git a/scanner/src/main/kotlin/scanner/service/MavenScannerService.kt b/scanner/src/main/kotlin/scanner/service/MavenScannerService.kt index 3776b51..c5984d3 100644 --- a/scanner/src/main/kotlin/scanner/service/MavenScannerService.kt +++ b/scanner/src/main/kotlin/scanner/service/MavenScannerService.kt @@ -1,7 +1,5 @@ package scanner.service -import common.domain.KotlinMPPLibrary -import common.domain.MavenArtifact import io.ktor.utils.io.core.Closeable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.ReceiveChannel @@ -16,6 +14,8 @@ import scanner.processor.GradleModuleProcessor import scanner.processor.PomProcessor import scanner.util.LoggerDelegate import scanner.util.supervisedLaunch +import shared.domain.KotlinMPPLibrary +import shared.domain.MavenArtifact abstract class MavenScannerService : Closeable { protected val logger by LoggerDelegate() diff --git a/scanner/src/main/kotlin/scanner/service/MavenScannerServiceImpl.kt b/scanner/src/main/kotlin/scanner/service/MavenScannerServiceImpl.kt index eb1c117..ba91f25 100644 --- a/scanner/src/main/kotlin/scanner/service/MavenScannerServiceImpl.kt +++ b/scanner/src/main/kotlin/scanner/service/MavenScannerServiceImpl.kt @@ -1,6 +1,5 @@ package scanner.service -import common.domain.MavenArtifactImpl import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel @@ -11,6 +10,7 @@ import scanner.domain.CLIOptions import scanner.processor.GradleModuleProcessor import scanner.processor.PomProcessor import scanner.util.supervisedLaunch +import shared.domain.MavenArtifactImpl import kotlin.time.milliseconds import kotlin.time.seconds diff --git a/scanner/src/main/kotlin/scanner/util/PrivateEnv.kt b/scanner/src/main/kotlin/scanner/util/PrivateEnv.kt index 3d271d6..301da6b 100644 --- a/scanner/src/main/kotlin/scanner/util/PrivateEnv.kt +++ b/scanner/src/main/kotlin/scanner/util/PrivateEnv.kt @@ -1,6 +1,6 @@ package scanner.util -import common.util.Env +import shared.util.Env object PrivateEnv : Env() { val API_URL by EnvDelegate { it ?: "http://localhost:8080" } diff --git a/scanner/src/test/kotlin/scanner/processor/GradleModuleProcessorTest.kt b/scanner/src/test/kotlin/scanner/processor/GradleModuleProcessorTest.kt index b662b7c..ebc82f4 100644 --- a/scanner/src/test/kotlin/scanner/processor/GradleModuleProcessorTest.kt +++ b/scanner/src/test/kotlin/scanner/processor/GradleModuleProcessorTest.kt @@ -1,11 +1,11 @@ package scanner.processor -import common.domain.KotlinTarget import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotest.matchers.shouldBe import scanner.domain.GradleModule import scanner.testutil.parseJsonFile +import shared.domain.KotlinTarget class GradleModuleProcessorTest : FunSpec({ diff --git a/settings.gradle.kts b/settings.gradle.kts index 32bbef9..64712cb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,10 @@ import de.fayard.refreshVersions.bootstrapRefreshVersions +pluginManagement { + repositories { + gradlePluginPortal() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + } +} buildscript { repositories { @@ -13,4 +19,10 @@ buildscript { bootstrapRefreshVersions() rootProject.name = "kamp" -include(":scanner", ":app") +include( + ":shared", + ":scanner", + ":app:common", + ":app:client", + ":app:server", +) diff --git a/common/build.gradle.kts b/shared/build.gradle.kts similarity index 67% rename from common/build.gradle.kts rename to shared/build.gradle.kts index f101c48..063521c 100644 --- a/common/build.gradle.kts +++ b/shared/build.gradle.kts @@ -10,9 +10,10 @@ kotlin { } sourceSets { - named("commonMain") { + commonMain { dependencies { - api("io.ktor:ktor-client-serialization:_") + api("org.kodein.di:kodein-di:_") + api("org.jetbrains.kotlinx:kotlinx-serialization-core:_") } } named("jvmMain") { diff --git a/common/src/commonMain/kotlin/domain/KotlinMPPLibrary.kt b/shared/src/commonMain/kotlin/domain/KotlinMPPLibrary.kt similarity index 97% rename from common/src/commonMain/kotlin/domain/KotlinMPPLibrary.kt rename to shared/src/commonMain/kotlin/domain/KotlinMPPLibrary.kt index f8e62ad..7068018 100644 --- a/common/src/commonMain/kotlin/domain/KotlinMPPLibrary.kt +++ b/shared/src/commonMain/kotlin/domain/KotlinMPPLibrary.kt @@ -1,4 +1,4 @@ -package common.domain +package shared.domain import kotlinx.serialization.Serializable import kotlinx.serialization.Transient diff --git a/common/src/commonMain/kotlin/domain/KotlinTarget.kt b/shared/src/commonMain/kotlin/domain/KotlinTarget.kt similarity index 98% rename from common/src/commonMain/kotlin/domain/KotlinTarget.kt rename to shared/src/commonMain/kotlin/domain/KotlinTarget.kt index 72c34f7..9c5cdb1 100644 --- a/common/src/commonMain/kotlin/domain/KotlinTarget.kt +++ b/shared/src/commonMain/kotlin/domain/KotlinTarget.kt @@ -1,4 +1,4 @@ -package common.domain +package shared.domain import kotlinx.serialization.Serializable diff --git a/common/src/commonMain/kotlin/domain/MavenArtifact.kt b/shared/src/commonMain/kotlin/domain/MavenArtifact.kt similarity index 96% rename from common/src/commonMain/kotlin/domain/MavenArtifact.kt rename to shared/src/commonMain/kotlin/domain/MavenArtifact.kt index 5a2f755..dbcf693 100644 --- a/common/src/commonMain/kotlin/domain/MavenArtifact.kt +++ b/shared/src/commonMain/kotlin/domain/MavenArtifact.kt @@ -1,4 +1,4 @@ -package common.domain +package shared.domain import kotlinx.serialization.Serializable import kotlinx.serialization.Transient diff --git a/app/src/commonMain/kotlin/app/util/DIModule.kt b/shared/src/commonMain/kotlin/util/DIModule.kt similarity index 96% rename from app/src/commonMain/kotlin/app/util/DIModule.kt rename to shared/src/commonMain/kotlin/util/DIModule.kt index 5f4150e..c099cee 100644 --- a/app/src/commonMain/kotlin/app/util/DIModule.kt +++ b/shared/src/commonMain/kotlin/util/DIModule.kt @@ -1,4 +1,4 @@ -package app.util +package shared.util import org.kodein.di.DI import kotlin.properties.ReadOnlyProperty diff --git a/common/src/jvmMain/kotlin/util/Env.kt b/shared/src/jvmMain/kotlin/util/Env.kt similarity index 97% rename from common/src/jvmMain/kotlin/util/Env.kt rename to shared/src/jvmMain/kotlin/util/Env.kt index 0c4a3cf..a155a3d 100644 --- a/common/src/jvmMain/kotlin/util/Env.kt +++ b/shared/src/jvmMain/kotlin/util/Env.kt @@ -1,4 +1,4 @@ -package common.util +package shared.util import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty diff --git a/src/commonMain/kotlin/kamp/domain/KotlinMPPLibrary.kt b/src/commonMain/kotlin/kamp/domain/KotlinMPPLibrary.kt deleted file mode 100644 index 0eb8f78..0000000 --- a/src/commonMain/kotlin/kamp/domain/KotlinMPPLibrary.kt +++ /dev/null @@ -1,46 +0,0 @@ -package kamp.domain - -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient - -@Serializable -public data class KotlinMPPLibrary( - override val group: String, - override val name: String, - override val latestVersion: String, - override val releaseVersion: String?, - override val versions: List?, - override val lastUpdated: Long?, - val targets: Set, - val description: String?, - val website: String?, - val scm: String?, -) : MavenArtifact { - public constructor( - artifact: MavenArtifact, - targets: Set, - description: String?, - website: String?, - scm: String?, - ) : this( - group = artifact.group, - name = artifact.name, - latestVersion = artifact.latestVersion, - releaseVersion = artifact.releaseVersion, - versions = artifact.versions, - lastUpdated = artifact.lastUpdated, - targets = targets, - description = description, - website = website, - scm = scm - ) - - val _id: String - - @Transient - val isMultiplatform: Boolean = targets.size > 1 - - init { - _id = "$group:$name" - } -} diff --git a/src/commonMain/kotlin/kamp/domain/KotlinTarget.kt b/src/commonMain/kotlin/kamp/domain/KotlinTarget.kt deleted file mode 100644 index f7551c2..0000000 --- a/src/commonMain/kotlin/kamp/domain/KotlinTarget.kt +++ /dev/null @@ -1,49 +0,0 @@ -package kamp.domain - -import kotlinx.serialization.Serializable - -@Serializable -public class KotlinTarget private constructor( - public val category: String, - public val platform: String, -) { - - override fun toString(): String = "$category:$platform" - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is KotlinTarget) return false - - if (platform != other.platform) return false - if (category != other.category) return false - - return true - } - - override fun hashCode(): Int { - var result = platform.hashCode() - result = 31 * result + category.hashCode() - return result - } - - public object Common { - public const val category: String = "common" - public operator fun invoke(): KotlinTarget = KotlinTarget(category, category) - } - - public object JS { - public const val category: String = "js" - public fun Legacy(): KotlinTarget = KotlinTarget(category, "legacy") - public fun IR(): KotlinTarget = KotlinTarget(category, "ir") - } - - public object JVM { - public const val category: String = "jvm" - public fun Java(): KotlinTarget = KotlinTarget(category, "jvm") - public fun Android(): KotlinTarget = KotlinTarget(category, "android") - } - - public object Native { - public const val category: String = "native" - public operator fun invoke(platform: String): KotlinTarget = KotlinTarget(category, platform) - } -} diff --git a/src/commonMain/kotlin/kamp/domain/MavenArtifact.kt b/src/commonMain/kotlin/kamp/domain/MavenArtifact.kt deleted file mode 100644 index 12ad2a7..0000000 --- a/src/commonMain/kotlin/kamp/domain/MavenArtifact.kt +++ /dev/null @@ -1,31 +0,0 @@ -package kamp.domain - -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient - -public interface MavenArtifact { - public val group: String - public val name: String - public val latestVersion: String - public val releaseVersion: String? - public val versions: List? - public val lastUpdated: Long? - - @Transient - public val version: String - get() = releaseVersion ?: latestVersion - - @Transient - public val path: String - get() = "$group:$name:$version" -} - -@Serializable -public data class MavenArtifactImpl( - override val group: String, - override val name: String, - override val latestVersion: String, - override val releaseVersion: String?, - override val versions: List?, - override val lastUpdated: Long?, -) : MavenArtifact diff --git a/versions.properties b/versions.properties index 029e419..cc0a8ac 100644 --- a/versions.properties +++ b/versions.properties @@ -9,6 +9,8 @@ plugin.com.github.jakemarsden.git-hooks=0.0.2 plugin.org.jlleitschuh.gradle.ktlint=10.0.0 +plugin.org.jetbrains.compose=0.0.0-web-dev-12 + version.ch.qos.logback..logback-classic=1.2.3 ## # available=1.3.0-alpha0 ## # available=1.3.0-alpha1 From d67ef037d331a542e5c1e07e2510e2f2ee8a4127 Mon Sep 17 00:00:00 2001 From: Martynas Petuska Date: Sun, 16 May 2021 23:23:17 +0100 Subject: [PATCH 003/103] Restructuring --- .github/workflows/release.yml | 12 +- app/client/build.gradle.kts | 64 +++ .../src/commonMain/kotlin/config/AppEnv.kt | 9 + app/client/src/commonMain/kotlin/config/di.kt | 39 ++ app/client/src/commonMain/kotlin/main.kt | 3 + .../kotlin/service/LibraryService.kt | 38 ++ .../src/commonMain/kotlin/store/AppStore.kt | 10 + .../kotlin/store/action/AppAction.kt | 13 + .../kotlin/store/reducer/AppReducer.kt | 19 + .../commonMain/kotlin/store/state/AppState.kt | 11 + .../commonMain/kotlin/store/thunk/AppThunk.kt | 49 +++ .../src/commonMain/kotlin/util/compose.kt | 23 ++ .../src/commonMain/kotlin/util/coroutines.kt | 15 + .../src/commonMain/kotlin/util/general.kt | 5 + .../resources/android-chrome-192x192.png | Bin .../resources/android-chrome-512x512.png | Bin .../resources/apple-touch-icon.png | Bin .../commonMain}/resources/favicon-16x16.png | Bin .../commonMain}/resources/favicon-32x32.png | Bin .../src/commonMain}/resources/favicon.ico | Bin .../src/commonMain}/resources/kamp.ico | Bin .../src/commonMain}/resources/kamp.svg | 0 .../commonMain}/resources/mstile-150x150.png | Bin .../resources/safari-pinned-tab.svg | 0 app/client/src/jsMain/kotlin/config/AppEnv.kt | 30 ++ app/client/src/jsMain/kotlin/main.kt | 12 + app/client/src/jsMain/kotlin/view/App.kt | 39 ++ .../src/jsMain/resources/application.env | 0 .../src/jsMain/resources/browserconfig.xml | 0 app/client/src/jsMain/resources/index.html | 25 ++ .../src/jsMain/resources/site.webmanifest | 0 .../src/jvmMain/kotlin/config/AppEnv.kt | 16 + app/client/src/jvmMain/kotlin/main.kt | 4 + app/common/build.gradle.kts | 27 ++ .../commonMain/kotlin}/domain/LibraryCount.kt | 2 +- .../kotlin}/domain/PagedResponse.kt | 2 +- app/src/jsMain/kotlin/app/index.kt | 20 - .../jsMain/kotlin/app/store/LibraryStore.kt | 30 -- .../jsMain/kotlin/app/store/thunk/appThunk.kt | 35 -- app/src/jsMain/kotlin/app/view/App.kt | 46 --- app/src/jsMain/kotlin/app/view/Content.kt | 58 --- app/src/jsMain/kotlin/app/view/Header.kt | 381 ------------------ .../jsMain/kotlin/app/view/component/Badge.kt | 44 -- .../kotlin/app/view/component/GitHubIcon.kt | 34 -- .../kotlin/app/view/component/KampIcon.kt | 13 - .../kotlin/app/view/component/LibraryCard.kt | 343 ---------------- .../kotlin/app/view/component/NavAnchor.kt | 37 -- app/src/jsMain/resources/index.html | 25 -- versions.properties | 4 + 49 files changed, 463 insertions(+), 1074 deletions(-) create mode 100644 app/client/build.gradle.kts create mode 100644 app/client/src/commonMain/kotlin/config/AppEnv.kt create mode 100644 app/client/src/commonMain/kotlin/config/di.kt create mode 100644 app/client/src/commonMain/kotlin/main.kt create mode 100644 app/client/src/commonMain/kotlin/service/LibraryService.kt create mode 100644 app/client/src/commonMain/kotlin/store/AppStore.kt create mode 100644 app/client/src/commonMain/kotlin/store/action/AppAction.kt create mode 100644 app/client/src/commonMain/kotlin/store/reducer/AppReducer.kt create mode 100644 app/client/src/commonMain/kotlin/store/state/AppState.kt create mode 100644 app/client/src/commonMain/kotlin/store/thunk/AppThunk.kt create mode 100644 app/client/src/commonMain/kotlin/util/compose.kt create mode 100644 app/client/src/commonMain/kotlin/util/coroutines.kt create mode 100644 app/client/src/commonMain/kotlin/util/general.kt rename app/{src/jsMain => client/src/commonMain}/resources/android-chrome-192x192.png (100%) rename app/{src/jsMain => client/src/commonMain}/resources/android-chrome-512x512.png (100%) rename app/{src/jsMain => client/src/commonMain}/resources/apple-touch-icon.png (100%) rename app/{src/jsMain => client/src/commonMain}/resources/favicon-16x16.png (100%) rename app/{src/jsMain => client/src/commonMain}/resources/favicon-32x32.png (100%) rename app/{src/jsMain => client/src/commonMain}/resources/favicon.ico (100%) rename app/{src/jsMain => client/src/commonMain}/resources/kamp.ico (100%) rename app/{src/jsMain => client/src/commonMain}/resources/kamp.svg (100%) rename app/{src/jsMain => client/src/commonMain}/resources/mstile-150x150.png (100%) rename app/{src/jsMain => client/src/commonMain}/resources/safari-pinned-tab.svg (100%) create mode 100644 app/client/src/jsMain/kotlin/config/AppEnv.kt create mode 100644 app/client/src/jsMain/kotlin/main.kt create mode 100644 app/client/src/jsMain/kotlin/view/App.kt rename app/{ => client}/src/jsMain/resources/application.env (100%) rename app/{ => client}/src/jsMain/resources/browserconfig.xml (100%) create mode 100644 app/client/src/jsMain/resources/index.html rename app/{ => client}/src/jsMain/resources/site.webmanifest (100%) create mode 100644 app/client/src/jvmMain/kotlin/config/AppEnv.kt create mode 100644 app/client/src/jvmMain/kotlin/main.kt create mode 100644 app/common/build.gradle.kts rename app/{src/commonMain/kotlin/app => common/src/commonMain/kotlin}/domain/LibraryCount.kt (79%) rename app/{src/commonMain/kotlin/app => common/src/commonMain/kotlin}/domain/PagedResponse.kt (86%) delete mode 100644 app/src/jsMain/kotlin/app/index.kt delete mode 100644 app/src/jsMain/kotlin/app/store/LibraryStore.kt delete mode 100644 app/src/jsMain/kotlin/app/store/thunk/appThunk.kt delete mode 100644 app/src/jsMain/kotlin/app/view/App.kt delete mode 100644 app/src/jsMain/kotlin/app/view/Content.kt delete mode 100644 app/src/jsMain/kotlin/app/view/Header.kt delete mode 100644 app/src/jsMain/kotlin/app/view/component/Badge.kt delete mode 100644 app/src/jsMain/kotlin/app/view/component/GitHubIcon.kt delete mode 100644 app/src/jsMain/kotlin/app/view/component/KampIcon.kt delete mode 100644 app/src/jsMain/kotlin/app/view/component/LibraryCard.kt delete mode 100644 app/src/jsMain/kotlin/app/view/component/NavAnchor.kt delete mode 100644 app/src/jsMain/resources/index.html diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 66b9b23..03a3727 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,17 +32,17 @@ jobs: uses: actions/upload-artifact@v2 id: upload with: - path: app/build/dist/WEB-INF + path: app/client/build/dist/WEB-INF/ name: static-web-app if-no-files-found: error - - name: Build App Docker image & push to GitHub Packages + - name: Build Server Docker image & push to GitHub Packages uses: docker/build-push-action@v1 with: - path: app + path: app/server/ username: ${{ github.actor }} password: ${{ github.token }} registry: docker.pkg.github.com - repository: mpetuska/kamp/app + repository: mpetuska/kamp/server tag_with_ref: true tag_with_sha: true add_git_labels: true @@ -94,7 +94,7 @@ jobs: env: TF_VAR_docker_registry_username: ${{ secrets.GH_PKG_USER }} TF_VAR_docker_registry_password: ${{ secrets.GH_PKG_PASSWORD }} - TF_VAR_docker_image_tag: docker.pkg.github.com/mpetuska/kamp/app:sha-${{ steps.short-sha.outputs.sha }} + TF_VAR_docker_image_tag: docker.pkg.github.com/mpetuska/kamp/server:sha-${{ steps.short-sha.outputs.sha }} TF_VAR_api_admin_user: ${{ secrets.API_ADMIN_USER }} TF_VAR_api_admin_password: ${{ secrets.API_ADMIN_PASSWORD }} @@ -119,6 +119,6 @@ jobs: uses: Azure/static-web-apps-deploy@v0.0.1-preview with: azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APP_API_TOKEN }} - repo_token: ${{ github.token }} # Used for Github integrations (i.e. PR comments) + repo_token: ${{ github.token }} action: "upload" app_location: "/dist" diff --git a/app/client/build.gradle.kts b/app/client/build.gradle.kts new file mode 100644 index 0000000..522f171 --- /dev/null +++ b/app/client/build.gradle.kts @@ -0,0 +1,64 @@ +plugins { + kotlin("multiplatform") + id("org.jetbrains.compose") +} + +val jsOutputFile = "kamp-$version.js" +kotlin { + jvm() + js { + useCommonJs() + binaries.executable() + browser { + distribution { + directory = buildDir.resolve("dist/WEB-INF") + } + commonWebpackConfig { + cssSupport.enabled = true + outputFileName = jsOutputFile + devServer = devServer?.copy( + port = 3000, + proxy = mapOf("/api/*" to "http://localhost:8080"), + open = false + ) + } + } + } + + sourceSets { + commonMain { + dependencies { + implementation(compose.web.web) + implementation(compose.runtime) + implementation(project(":app:common")) + implementation("org.reduxkotlin:redux-kotlin-threadsafe:_") + implementation("org.reduxkotlin:redux-kotlin-thunk:_") + // implementation("io.ktor:ktor-client-cio:_") + implementation("io.ktor:ktor-client-auth:_") + implementation("io.ktor:ktor-client-serialization:_") + } + } + named("jsMain") { + dependencies { + } + } + named("jvmMain") { + dependencies { + implementation(compose.foundation) + implementation(compose.material) + implementation(compose.materialIconsExtended) + implementation(compose.desktop.common) + } + } + } +} + +tasks { + named("jsProcessResources", Copy::class) { + eachFile { + if (name == "index.html") { + expand(project.properties + mapOf("jsOutputFileName" to jsOutputFile)) + } + } + } +} diff --git a/app/client/src/commonMain/kotlin/config/AppEnv.kt b/app/client/src/commonMain/kotlin/config/AppEnv.kt new file mode 100644 index 0000000..f4e9ca2 --- /dev/null +++ b/app/client/src/commonMain/kotlin/config/AppEnv.kt @@ -0,0 +1,9 @@ +package app.client.config + +expect interface AppEnv { + val API_URL: String +} + +expect val env: AppEnv + +expect suspend fun loadEnv() diff --git a/app/client/src/commonMain/kotlin/config/di.kt b/app/client/src/commonMain/kotlin/config/di.kt new file mode 100644 index 0000000..5b4ba83 --- /dev/null +++ b/app/client/src/commonMain/kotlin/config/di.kt @@ -0,0 +1,39 @@ +package app.client.config + +import io.ktor.client.HttpClient +import io.ktor.client.features.defaultRequest +import io.ktor.client.features.json.JsonFeature +import io.ktor.client.features.json.serializer.KotlinxSerializer +import io.ktor.http.ContentType +import io.ktor.http.contentType +import kotlinx.serialization.json.Json +import org.kodein.di.DI +import org.kodein.di.bind +import org.kodein.di.instance +import org.kodein.di.provider +import org.kodein.di.singleton +import service.LibraryService +import shared.util.DIModule + +private val services by DIModule { + bind() with provider { LibraryService(instance()) } +} + +val di = DI { + bind { + provider { + Json {} + } + } + bind() with singleton { + HttpClient { + install(JsonFeature) { + serializer = KotlinxSerializer(instance()) + } + defaultRequest { + contentType(ContentType.Application.Json) + } + } + } + import(services) +} diff --git a/app/client/src/commonMain/kotlin/main.kt b/app/client/src/commonMain/kotlin/main.kt new file mode 100644 index 0000000..8eb7bdd --- /dev/null +++ b/app/client/src/commonMain/kotlin/main.kt @@ -0,0 +1,3 @@ +package app.client + +expect suspend fun main() diff --git a/app/client/src/commonMain/kotlin/service/LibraryService.kt b/app/client/src/commonMain/kotlin/service/LibraryService.kt new file mode 100644 index 0000000..2fd4e1b --- /dev/null +++ b/app/client/src/commonMain/kotlin/service/LibraryService.kt @@ -0,0 +1,38 @@ +package service + +import app.client.util.toApi +import app.common.domain.LibraryCount +import app.common.domain.PagedResponse +import io.ktor.client.HttpClient +import io.ktor.client.request.get +import shared.domain.KotlinMPPLibrary + +class LibraryService(private val client: HttpClient) { + suspend fun getAll( + page: Int, + size: Int, + search: String?, + targets: Set?, + ): PagedResponse { + val pagination = "page=$page&size=$size" + val searchQuery = search?.let { "search=$it" } ?: "" + val targetsQuery = targets?.joinToString(prefix = "target=", separator = "&target=") ?: "" + + return client.get("${path}${buildQuery(pagination, searchQuery, targetsQuery)}".toApi()) + } + + suspend fun getCount(search: String?, targets: Set?): LibraryCount { + val searchQuery = search?.let { "search=$it" } + val targetsQuery = targets?.joinToString(prefix = "target=", separator = "&target=") + + return client.get("$path/count${buildQuery(searchQuery, targetsQuery)}".toApi()) + } + + private fun buildQuery(vararg query: String?): String { + return query.toSet().filterNotNull().takeIf(List::isNotEmpty)?.joinToString("&", prefix = "?") ?: "" + } + + companion object { + const val path = "/libraries" + } +} diff --git a/app/client/src/commonMain/kotlin/store/AppStore.kt b/app/client/src/commonMain/kotlin/store/AppStore.kt new file mode 100644 index 0000000..401463c --- /dev/null +++ b/app/client/src/commonMain/kotlin/store/AppStore.kt @@ -0,0 +1,10 @@ +package app.client.store + +import app.client.store.reducer.rootReducer +import app.client.store.state.AppState +import org.reduxkotlin.Store +import org.reduxkotlin.applyMiddleware +import org.reduxkotlin.createStore +import org.reduxkotlin.createThunkMiddleware + +object AppStore : Store by createStore(rootReducer, AppState(), applyMiddleware(createThunkMiddleware())) diff --git a/app/client/src/commonMain/kotlin/store/action/AppAction.kt b/app/client/src/commonMain/kotlin/store/action/AppAction.kt new file mode 100644 index 0000000..f2346f2 --- /dev/null +++ b/app/client/src/commonMain/kotlin/store/action/AppAction.kt @@ -0,0 +1,13 @@ +package app.client.store.action + +import app.common.domain.PagedResponse +import shared.domain.KotlinMPPLibrary + +sealed class AppAction { + object IncrementCount : AppAction() + object DecrementCount : AppAction() + data class SetLibraries(val libraries: PagedResponse?) : AppAction() + data class SetSearch(val search: String?) : AppAction() + data class SetTargets(val targets: Set?) : AppAction() + data class SetCount(val count: Long?) : AppAction() +} diff --git a/app/client/src/commonMain/kotlin/store/reducer/AppReducer.kt b/app/client/src/commonMain/kotlin/store/reducer/AppReducer.kt new file mode 100644 index 0000000..76d1581 --- /dev/null +++ b/app/client/src/commonMain/kotlin/store/reducer/AppReducer.kt @@ -0,0 +1,19 @@ +package app.client.store.reducer + +import app.client.store.action.AppAction +import app.client.store.state.AppState +import org.reduxkotlin.ReducerForActionType +import org.reduxkotlin.reducerForActionType + +typealias AppReducer = ReducerForActionType + +val rootReducer = reducerForActionType { state, action -> + when (action) { + AppAction.IncrementCount -> state.copy(count = (state.count ?: 0) + 1) + AppAction.DecrementCount -> state.copy(count = (state.count ?: 0) - 1) + is AppAction.SetLibraries -> state.copy(libraries = action.libraries) + is AppAction.SetSearch -> state.copy(search = action.search) + is AppAction.SetTargets -> state.copy(targets = action.targets) + is AppAction.SetCount -> state.copy(count = action.count) + } +} diff --git a/app/client/src/commonMain/kotlin/store/state/AppState.kt b/app/client/src/commonMain/kotlin/store/state/AppState.kt new file mode 100644 index 0000000..b5d4192 --- /dev/null +++ b/app/client/src/commonMain/kotlin/store/state/AppState.kt @@ -0,0 +1,11 @@ +package app.client.store.state + +import app.common.domain.PagedResponse +import shared.domain.KotlinMPPLibrary + +data class AppState( + val count: Long? = null, + val libraries: PagedResponse? = null, + val search: String? = null, + val targets: Set? = null, +) diff --git a/app/client/src/commonMain/kotlin/store/thunk/AppThunk.kt b/app/client/src/commonMain/kotlin/store/thunk/AppThunk.kt new file mode 100644 index 0000000..449f367 --- /dev/null +++ b/app/client/src/commonMain/kotlin/store/thunk/AppThunk.kt @@ -0,0 +1,49 @@ +package app.client.store.thunk + +import app.client.config.di +import app.client.store.action.AppAction +import app.client.store.state.AppState +import app.client.util.suspending +import org.kodein.di.instance +import org.reduxkotlin.Thunk +import service.LibraryService + +typealias AppThunk = Thunk + +fun fetchLibraryPage( + page: Int, + size: Int = 12, + search: String? = null, + targets: Set? = null, +): AppThunk = { dispatch, getState, _ -> + suspending { + val state = getState() + val theSearch = (search ?: state.search)?.takeIf(String::isNotEmpty) + val theTargets = (targets ?: state.targets)?.takeIf(Set::isNotEmpty) + + val service by di.instance() + val theLibraries = service.getAll(page, size, theSearch, theTargets) + // TODO window.scrollTo(0.0, 0.0) + dispatch(AppAction.SetLibraries(theLibraries)) + dispatch(AppAction.SetSearch(theSearch)) + dispatch(AppAction.SetTargets(theTargets)) + } +} + +fun fetchLibraryCount( + search: String? = null, + targets: Set? = null, +): AppThunk = { dispatch, getState, _ -> + suspending { + val state = getState() + val theSearch = (search ?: state.search)?.takeIf(String::isNotEmpty) + val theTargets = (targets ?: state.targets)?.takeIf(Set::isNotEmpty) + + val service by di.instance() + val theCount = service.getCount(theSearch, theTargets).count + + dispatch(AppAction.SetCount(theCount)) + dispatch(AppAction.SetSearch(theSearch)) + dispatch(AppAction.SetTargets(theTargets)) + } +} diff --git a/app/client/src/commonMain/kotlin/util/compose.kt b/app/client/src/commonMain/kotlin/util/compose.kt new file mode 100644 index 0000000..f94ffa7 --- /dev/null +++ b/app/client/src/commonMain/kotlin/util/compose.kt @@ -0,0 +1,23 @@ +package app.client.util + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import org.reduxkotlin.Store + +@Composable +fun Store.select(selector: TState.() -> TSlice): State { + val result = remember { mutableStateOf(state.selector()) } + DisposableEffect(Unit) { + val unsubscribe = subscribe { + result.value = state.selector() + } + onDispose(unsubscribe) + } + return result +} + +@Composable +fun Store.select(): State = select { this } diff --git a/app/client/src/commonMain/kotlin/util/coroutines.kt b/app/client/src/commonMain/kotlin/util/coroutines.kt new file mode 100644 index 0000000..e56dec9 --- /dev/null +++ b/app/client/src/commonMain/kotlin/util/coroutines.kt @@ -0,0 +1,15 @@ +package app.client.util + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch + +inline fun suspending(crossinline block: suspend CoroutineScope.(T1, T2) -> Unit): (T1, T2) -> Unit = + { t1, t2 -> suspending { block(t1, t2) } } + +inline fun suspending(crossinline block: suspend CoroutineScope.(T1) -> Unit): (T1) -> Unit = + { suspending { block(it) } } + +inline fun suspending(crossinline block: suspend CoroutineScope.() -> Unit) { + GlobalScope.launch { block() } +} diff --git a/app/client/src/commonMain/kotlin/util/general.kt b/app/client/src/commonMain/kotlin/util/general.kt new file mode 100644 index 0000000..f4b00e3 --- /dev/null +++ b/app/client/src/commonMain/kotlin/util/general.kt @@ -0,0 +1,5 @@ +package app.client.util + +import app.client.config.env + +fun String.toApi() = "${env.API_URL}/${this.removePrefix("/")}" diff --git a/app/src/jsMain/resources/android-chrome-192x192.png b/app/client/src/commonMain/resources/android-chrome-192x192.png similarity index 100% rename from app/src/jsMain/resources/android-chrome-192x192.png rename to app/client/src/commonMain/resources/android-chrome-192x192.png diff --git a/app/src/jsMain/resources/android-chrome-512x512.png b/app/client/src/commonMain/resources/android-chrome-512x512.png similarity index 100% rename from app/src/jsMain/resources/android-chrome-512x512.png rename to app/client/src/commonMain/resources/android-chrome-512x512.png diff --git a/app/src/jsMain/resources/apple-touch-icon.png b/app/client/src/commonMain/resources/apple-touch-icon.png similarity index 100% rename from app/src/jsMain/resources/apple-touch-icon.png rename to app/client/src/commonMain/resources/apple-touch-icon.png diff --git a/app/src/jsMain/resources/favicon-16x16.png b/app/client/src/commonMain/resources/favicon-16x16.png similarity index 100% rename from app/src/jsMain/resources/favicon-16x16.png rename to app/client/src/commonMain/resources/favicon-16x16.png diff --git a/app/src/jsMain/resources/favicon-32x32.png b/app/client/src/commonMain/resources/favicon-32x32.png similarity index 100% rename from app/src/jsMain/resources/favicon-32x32.png rename to app/client/src/commonMain/resources/favicon-32x32.png diff --git a/app/src/jsMain/resources/favicon.ico b/app/client/src/commonMain/resources/favicon.ico similarity index 100% rename from app/src/jsMain/resources/favicon.ico rename to app/client/src/commonMain/resources/favicon.ico diff --git a/app/src/jsMain/resources/kamp.ico b/app/client/src/commonMain/resources/kamp.ico similarity index 100% rename from app/src/jsMain/resources/kamp.ico rename to app/client/src/commonMain/resources/kamp.ico diff --git a/app/src/jsMain/resources/kamp.svg b/app/client/src/commonMain/resources/kamp.svg similarity index 100% rename from app/src/jsMain/resources/kamp.svg rename to app/client/src/commonMain/resources/kamp.svg diff --git a/app/src/jsMain/resources/mstile-150x150.png b/app/client/src/commonMain/resources/mstile-150x150.png similarity index 100% rename from app/src/jsMain/resources/mstile-150x150.png rename to app/client/src/commonMain/resources/mstile-150x150.png diff --git a/app/src/jsMain/resources/safari-pinned-tab.svg b/app/client/src/commonMain/resources/safari-pinned-tab.svg similarity index 100% rename from app/src/jsMain/resources/safari-pinned-tab.svg rename to app/client/src/commonMain/resources/safari-pinned-tab.svg diff --git a/app/client/src/jsMain/kotlin/config/AppEnv.kt b/app/client/src/jsMain/kotlin/config/AppEnv.kt new file mode 100644 index 0000000..370c37d --- /dev/null +++ b/app/client/src/jsMain/kotlin/config/AppEnv.kt @@ -0,0 +1,30 @@ +package app.client.config + +import kotlinx.browser.window +import kotlinx.coroutines.await +import org.w3c.dom.Window + +inline val Window.env: AppEnv + get() = asDynamic().env.unsafeCast() + +actual val env: AppEnv by lazy { + window.env +} + +actual suspend fun loadEnv() { + val env: AppEnv = window.fetch("/application.env").await().text().await() + .split("\n") + .filter(String::isNotBlank) + .joinToString(",", "{", "}") { + val (key, value) = it.split("=", limit = 2).let { c -> + c[0] to c[1] + } + "\"$key\": \"$value\"" + }.let(JSON::parse) + window.asDynamic().env = env + requireNotNull(window.env.API_URL) +} + +actual external interface AppEnv { + actual val API_URL: String +} diff --git a/app/client/src/jsMain/kotlin/main.kt b/app/client/src/jsMain/kotlin/main.kt new file mode 100644 index 0000000..a913a89 --- /dev/null +++ b/app/client/src/jsMain/kotlin/main.kt @@ -0,0 +1,12 @@ +package app.client + +import androidx.compose.web.renderComposable +import app.client.config.loadEnv +import app.client.view.App + +actual suspend fun main() { + loadEnv() + renderComposable(rootElementId = "root") { + App() + } +} diff --git a/app/client/src/jsMain/kotlin/view/App.kt b/app/client/src/jsMain/kotlin/view/App.kt new file mode 100644 index 0000000..4a6a6f5 --- /dev/null +++ b/app/client/src/jsMain/kotlin/view/App.kt @@ -0,0 +1,39 @@ +package app.client.view + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.web.css.padding +import androidx.compose.web.css.px +import androidx.compose.web.elements.Button +import androidx.compose.web.elements.Div +import androidx.compose.web.elements.Span +import androidx.compose.web.elements.Text +import app.client.store.AppStore +import app.client.store.action.AppAction +import app.client.util.select + +@Composable +fun App() { + val count by AppStore.select { count } + Div(style = { padding(25.px) }) { + Button( + attrs = { + onClick { AppStore.dispatch(AppAction.DecrementCount) } + } + ) { + Text("-") + } + + Span(style = { padding(15.px) }) { + Text("$count") + } + + Button( + attrs = { + onClick { AppStore.dispatch(AppAction.IncrementCount) } + } + ) { + Text("+") + } + } +} diff --git a/app/src/jsMain/resources/application.env b/app/client/src/jsMain/resources/application.env similarity index 100% rename from app/src/jsMain/resources/application.env rename to app/client/src/jsMain/resources/application.env diff --git a/app/src/jsMain/resources/browserconfig.xml b/app/client/src/jsMain/resources/browserconfig.xml similarity index 100% rename from app/src/jsMain/resources/browserconfig.xml rename to app/client/src/jsMain/resources/browserconfig.xml diff --git a/app/client/src/jsMain/resources/index.html b/app/client/src/jsMain/resources/index.html new file mode 100644 index 0000000..15a94ac --- /dev/null +++ b/app/client/src/jsMain/resources/index.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + KAMP + + + + +
+ + \ No newline at end of file diff --git a/app/src/jsMain/resources/site.webmanifest b/app/client/src/jsMain/resources/site.webmanifest similarity index 100% rename from app/src/jsMain/resources/site.webmanifest rename to app/client/src/jsMain/resources/site.webmanifest diff --git a/app/client/src/jvmMain/kotlin/config/AppEnv.kt b/app/client/src/jvmMain/kotlin/config/AppEnv.kt new file mode 100644 index 0000000..916b250 --- /dev/null +++ b/app/client/src/jvmMain/kotlin/config/AppEnv.kt @@ -0,0 +1,16 @@ +package app.client.config + +import kotlinx.serialization.Serializable + +@Serializable +actual interface AppEnv { + actual val API_URL: String +} + +private lateinit var _env: AppEnv + +actual val env: AppEnv by lazy { _env } + +actual suspend fun loadEnv() { + TODO() +} diff --git a/app/client/src/jvmMain/kotlin/main.kt b/app/client/src/jvmMain/kotlin/main.kt new file mode 100644 index 0000000..804f801 --- /dev/null +++ b/app/client/src/jvmMain/kotlin/main.kt @@ -0,0 +1,4 @@ +package app.client + +actual suspend fun main() { +} diff --git a/app/common/build.gradle.kts b/app/common/build.gradle.kts new file mode 100644 index 0000000..e324f73 --- /dev/null +++ b/app/common/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + kotlin("multiplatform") + kotlin("plugin.serialization") +} + +kotlin { + jvm {} + js { + useCommonJs() + browser {} + } + sourceSets { + commonMain { + dependencies { + api(rootProject.project(":shared")) + api("org.kodein.di:kodein-di:_") + api("org.jetbrains.kotlinx:kotlinx-serialization-json:_") + api("io.ktor:ktor-client-serialization:_") + } + } + named("jvmTest") { + dependencies { + implementation("io.kotest:kotest-runner-junit5:_") + } + } + } +} diff --git a/app/src/commonMain/kotlin/app/domain/LibraryCount.kt b/app/common/src/commonMain/kotlin/domain/LibraryCount.kt similarity index 79% rename from app/src/commonMain/kotlin/app/domain/LibraryCount.kt rename to app/common/src/commonMain/kotlin/domain/LibraryCount.kt index 8e04357..a97e810 100644 --- a/app/src/commonMain/kotlin/app/domain/LibraryCount.kt +++ b/app/common/src/commonMain/kotlin/domain/LibraryCount.kt @@ -1,4 +1,4 @@ -package app.domain +package app.common.domain import kotlinx.serialization.Serializable diff --git a/app/src/commonMain/kotlin/app/domain/PagedResponse.kt b/app/common/src/commonMain/kotlin/domain/PagedResponse.kt similarity index 86% rename from app/src/commonMain/kotlin/app/domain/PagedResponse.kt rename to app/common/src/commonMain/kotlin/domain/PagedResponse.kt index a748054..ec70819 100644 --- a/app/src/commonMain/kotlin/app/domain/PagedResponse.kt +++ b/app/common/src/commonMain/kotlin/domain/PagedResponse.kt @@ -1,4 +1,4 @@ -package app.domain +package app.common.domain import kotlinx.serialization.Serializable diff --git a/app/src/jsMain/kotlin/app/index.kt b/app/src/jsMain/kotlin/app/index.kt deleted file mode 100644 index 1b57515..0000000 --- a/app/src/jsMain/kotlin/app/index.kt +++ /dev/null @@ -1,20 +0,0 @@ -package app - -import app.config.loadEnv -import app.store.thunk.fetchLibraryCount -import app.store.thunk.fetchLibraryPage -import app.view.App -import dev.fritz2.dom.html.render -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch - -suspend fun main() = coroutineScope { - loadEnv() - launch { - fetchLibraryPage(1)(Unit) - fetchLibraryCount()(Unit) - } - render("#root") { - App() - } -} diff --git a/app/src/jsMain/kotlin/app/store/LibraryStore.kt b/app/src/jsMain/kotlin/app/store/LibraryStore.kt deleted file mode 100644 index 9857aa1..0000000 --- a/app/src/jsMain/kotlin/app/store/LibraryStore.kt +++ /dev/null @@ -1,30 +0,0 @@ -package app.store - -import app.domain.PagedResponse -import dev.fritz2.binding.RootStore -import kamp.domain.KotlinMPPLibrary - -object LibraryStore : RootStore(LibraryState()) { - data class LibraryState( - val libraries: PagedResponse? = null, - val search: String? = null, - val targets: Set? = null, - val count: Long? = null, - ) - - val setLibraries = - handleAndEmit, PagedResponse> { state, libraries -> - emit(libraries) - state.copy(libraries = libraries) - } - - val setSearch = handleAndEmit { state, search -> - emit(search) - state.copy(search = search) - } - - val setCount = handleAndEmit { state, count -> - emit(count) - state.copy(count = count) - } -} diff --git a/app/src/jsMain/kotlin/app/store/thunk/appThunk.kt b/app/src/jsMain/kotlin/app/store/thunk/appThunk.kt deleted file mode 100644 index 377d10b..0000000 --- a/app/src/jsMain/kotlin/app/store/thunk/appThunk.kt +++ /dev/null @@ -1,35 +0,0 @@ -package app.store.thunk - -import app.config.di -import app.service.LibraryService -import app.store.LibraryStore -import kotlinx.browser.window -import org.kodein.di.instance - -fun fetchLibraryPage(page: Int, size: Int = 12, search: String? = null, targets: Set? = null) = - LibraryStore.handle { state -> - val theSearch = (search ?: state.search)?.takeIf(String::isNotEmpty) - val theTargets = (targets ?: state.targets)?.takeIf(Set::isNotEmpty) - - val service by di.instance() - val libraries = service.getAll(page, size, theSearch, theTargets) - window.scrollTo(0.0, 0.0) - state.copy( - libraries = libraries, - search = theSearch, - targets = theTargets, - ) - } - -fun fetchLibraryCount(search: String? = null, targets: Set? = null) = LibraryStore.handle { state -> - val theSearch = (search ?: state.search)?.takeIf(String::isNotEmpty) - val theTargets = (targets ?: state.targets)?.takeIf(Set::isNotEmpty) - - val service by di.instance() - val count = service.getCount(theSearch, theTargets).count - state.copy( - count = count, - search = theSearch, - targets = theTargets, - ) -} diff --git a/app/src/jsMain/kotlin/app/view/App.kt b/app/src/jsMain/kotlin/app/view/App.kt deleted file mode 100644 index 5a072e6..0000000 --- a/app/src/jsMain/kotlin/app/view/App.kt +++ /dev/null @@ -1,46 +0,0 @@ -package app.view - -import dev.fritz2.components.stackUp -import dev.fritz2.dom.html.RenderContext -import dev.fritz2.styling.staticStyle - -@DslMarker -annotation class KampComponent - -@KampComponent -fun RenderContext.App() { - staticStyle( - """ - /* width */ - ::-webkit-scrollbar { - width: 0.75rem; - } - - /* Track */ - ::-webkit-scrollbar-track { - box-shadow: inset 0 0 5px grey; - border-radius: 0.75rem; - } - - /* Handle */ - ::-webkit-scrollbar-thumb { - background: lightgray; - box-shadow: inset 0 0 5px grey; - border-radius: 0.75rem; - } - - /* Handle on hover */ - ::-webkit-scrollbar-thumb:hover { - background: gray; - } - """.trimIndent() - ) - stackUp({ - height { "100%" } - width { "100%" } - position { relative {} } - }) { - Header() - Content() - } -} diff --git a/app/src/jsMain/kotlin/app/view/Content.kt b/app/src/jsMain/kotlin/app/view/Content.kt deleted file mode 100644 index f9bceed..0000000 --- a/app/src/jsMain/kotlin/app/view/Content.kt +++ /dev/null @@ -1,58 +0,0 @@ -package app.view - -import app.store.LibraryStore -import app.util.styled -import app.view.component.LibraryCard -import dev.fritz2.components.gridBox -import dev.fritz2.components.spinner -import dev.fritz2.components.stackUp -import dev.fritz2.dom.html.RenderContext -import kotlinx.coroutines.flow.map - -@KampComponent -fun RenderContext.Content() { - stackUp({ - alignItems { stretch } - color { gray900 } - minHeight { "100%" } - paddings( - sm = { - left { small } - right { small } - }, - md = { - left { larger } - right { larger } - }, - ) - margins { - vertical { "5rem" } - } - }) { - items { - styled(::h2)({ - textAlign { center } - }) { +"Kotlin Libraries" } - gridBox({ - columns(sm = { "1fr" }, md = { repeat(2) { "1fr" } }, xl = { repeat(3) { "1fr" } }) - gap { small } - width { "max-content" } - css("align-self: center") - }) { - LibraryStore.data.map { it.libraries?.data }.render { libraries -> - if (libraries == null) { - spinner({ - size { large } - }) { - speed("1s") - } - } else { - for (library in libraries) { - LibraryCard(library) - } - } - } - } - } - } -} diff --git a/app/src/jsMain/kotlin/app/view/Header.kt b/app/src/jsMain/kotlin/app/view/Header.kt deleted file mode 100644 index 52604bd..0000000 --- a/app/src/jsMain/kotlin/app/view/Header.kt +++ /dev/null @@ -1,381 +0,0 @@ -package app.view - -import app.store.LibraryStore -import app.store.thunk.fetchLibraryCount -import app.store.thunk.fetchLibraryPage -import app.util.styled -import app.view.component.Badge -import app.view.component.KampIcon -import app.view.component.Link -import dev.fritz2.binding.storeOf -import dev.fritz2.components.box -import dev.fritz2.components.checkbox -import dev.fritz2.components.clickButton -import dev.fritz2.components.gridBox -import dev.fritz2.components.inputField -import dev.fritz2.components.lineUp -import dev.fritz2.components.modal -import dev.fritz2.components.navBar -import dev.fritz2.components.spinner -import dev.fritz2.components.stackUp -import dev.fritz2.dom.html.RenderContext -import dev.fritz2.dom.states -import dev.fritz2.styling.params.BasicParams -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.onEach - -@KampComponent -private fun RenderContext.stackUpClose(style: BasicParams.() -> Unit = {}, children: RenderContext.() -> Unit) = - stackUp(style) { - spacing { none } - items(children) - } - -@KampComponent -private fun RenderContext.SearchModal() = modal({ - maxHeight { "92.5vh" } - overflow { auto } -}) { close -> - val searchStore = storeOf("") - val targetsStore = storeOf(setOf()) - - @KampComponent - fun RenderContext.TargetCheckbox(values: Set, label: RenderContext.() -> Unit = {}) { - checkbox { - checked(targetsStore.data.map { it.containsAll(values) }) - events { - changes.states() handledBy targetsStore.handle { targets, checked -> - if (checked) { - targets + values - } else { - targets - values - } - } - } - label(label) - } - } - - @KampComponent - fun RenderContext.TargetCheckbox(values: Set, label: String) = TargetCheckbox(values) { - label { - sub { +label } - } - } - - @KampComponent - fun RenderContext.TargetCheckboxGroup( - targets: kotlin.collections.Map, - header: RenderContext.() -> Unit, - ) = stackUpClose { - lineUp { - spacing { none } - items { - TargetCheckbox(targets.values.toSet(), header) - } - } - targets.forEach { (name, value) -> - TargetCheckbox(setOf(value), name) - } - } - - size { large } - content { - gridBox({ - margin { auto } - columns { "1fr" } - maxWidth { "100%" } - overflow { auto } - gap { small } - width { minContent } - css("align-self: center") - }) { - box { - h3 { +"Text Search" } - inputField(store = searchStore) { - type("search") - placeholder("Search...") - } - } - stackUpClose { - h3 { +"Targets" } - gridBox({ - columns( - sm = { repeat(3) { "1fr" } }, - ) - gap { small } - margins { - bottom { small } - } - }) { - stackUpClose { - TargetCheckboxGroup( - mapOf( - "common" to "common", - ) - ) { - h4 { +"Metadata" } - } - } - stackUpClose { - TargetCheckboxGroup( - mapOf( - "jvm" to "jvm", - "android" to "android", - ) - ) { - h4 { +"JVM" } - } - } - stackUpClose { - TargetCheckboxGroup( - mapOf( - "legacy" to "legacy", - "ir" to "ir", - ) - ) { - h4 { +"JS" } - } - } - } - h4 { +"Native" } - gridBox({ - columns( - sm = { repeat(2) { "1fr" } }, - md = { repeat(3) { "1fr" } }, - ) - gap { small } - }) { - stackUpClose { - TargetCheckboxGroup( - mapOf( - "linuxArm32Hfp" to "linux_arm32_hfp", - "linuxArm64" to "linux_arm64", - "linuxMips32" to "linux_mips32", - "linuxMipsel32" to "linux_mipsel32", - "linuxX64" to "linux_x64", - ) - ) { - h6 { +"Linux" } - } - } - stackUpClose { - TargetCheckboxGroup( - mapOf( - "mingwX64" to "mingw_x64", - "mingwX86" to "mingw_x86", - ) - ) { - h6 { +"Windows" } - } - } - stackUpClose { - TargetCheckboxGroup( - mapOf( - "androidNativeX64" to "android_x64", - "androidNativeX86" to "android_x86", - "androidNativeArm32" to "android_arm32", - "androidNativeArm64" to "android_arm64", - ) - ) { - h6 { +"Android NDK" } - } - } - stackUpClose { - TargetCheckboxGroup( - mapOf( - "tvosArm64" to "tvos_arm64", - "tvosX64" to "tvos_x64", - ) - ) { - h6 { +"tvOS" } - } - } - stackUpClose { - TargetCheckboxGroup( - mapOf( - "iosArm32" to "ios_arm32", - "iosArm64" to "ios_arm64", - "iosX64" to "ios_x64", - ) - ) { - h6 { +"iOS" } - } - } - stackUpClose { - TargetCheckboxGroup( - mapOf( - "watchosArm32" to "watchos_arm32", - "watchosArm64" to "watchos_arm64", - "watchosX86" to "watchos_x86", - "watchosX64" to "watchos_x64", - ) - ) { - h6 { +"watchOS" } - } - } - stackUpClose { - TargetCheckboxGroup( - mapOf( - "macosX64" to "macos_x64", - ) - ) { - h6 { +"macOS" } - } - } - stackUpClose { - TargetCheckboxGroup( - mapOf( - "wasm32" to "wasm32", - ) - ) { - h6 { +"WebAssembly" } - } - } - } - } - clickButton({ - css("justify-self: center") - width { "100%" } - }) { - text("Search") - icon { fromTheme { search } } - }.map {}.onEach { - fetchLibraryPage( - page = 1, - search = searchStore.current, - targets = targetsStore.current - )() - fetchLibraryCount( - search = searchStore.current, - targets = targetsStore.current - )() - } handledBy close - } - } -} - -@KampComponent -fun RenderContext.Pagination() = lineUp { - spacing { none } - items { - LibraryStore.data.mapLatest { it.libraries }.mapNotNull { it }.render { libs -> - clickButton({ - css("border-top-right-radius: 0") - css("border-bottom-right-radius: 0") - }) { - size { small } - icon { fromTheme { caretLeft } } - disabled(libs.prev == null) - } handledBy fetchLibraryPage(libs.page - 1) - clickButton({ - css("border-radius: 0") - }) { - size { small } - variant { outline } - text("${libs.page}") - } - clickButton({ - css("border-top-left-radius: 0") - css("border-bottom-left-radius: 0") - }) { - size { small } - icon { fromTheme { caretRight } } - disabled(libs.next == null) - } handledBy fetchLibraryPage(libs.page + 1) - } - } -} - -@KampComponent -fun RenderContext.Header() { - styled(::div)({ - }) { - navBar({ - border { width { "0" } } - boxShadow { flat } - }) { - brand { - styled(::a)({ - after { - textAlign { center } - background { color { primary.base } } - color { gray100 } - } - alignItems { end } - }) { - href("/") - KampIcon { - size { "3rem" } - color { primary.base } - display { inline } - css("border-radius: 50%") - } - - styled(::span)({ - margins { left { smaller } } - verticalAlign { sub } - fontSize(sm = { large }, md = { larger }) - fontWeight { lighter } - display(sm = { none }, md = { initial }) - }) { +"KAMP" } - } - LibraryStore.data.map { it.count }.render { count -> - Badge( - { success }, - { - margins { - left { tiny } - } - } - ) { - if (count != null) { - +"$count Lib${if (count > 1) "s" else ""}" - } else { - spinner { - speed("1s") - } - } - } - } - } - - actions { - lineUp({ - display(sm = { none }, md = { flex }) - }) { - items { - Link("https://github.com/mpetuska/kamp", "_new") { - +"GitHub" - } - } - } - lineUp({ - alignItems { center } - margins { - left { tiny } - } - }) { - spacing { tiny } - items { - Pagination() - val modal = SearchModal() - clickButton({ - display(sm = { inlineBlock }, md = { none }) - }) { - icon { fromTheme { search } } - } handledBy modal - clickButton({ - display(sm = { none }, md = { inlineBlock }) - }) { - text("Search") - icon { fromTheme { search } } - } handledBy modal - } - } - } - } - } -} diff --git a/app/src/jsMain/kotlin/app/view/component/Badge.kt b/app/src/jsMain/kotlin/app/view/component/Badge.kt deleted file mode 100644 index c3943de..0000000 --- a/app/src/jsMain/kotlin/app/view/component/Badge.kt +++ /dev/null @@ -1,44 +0,0 @@ -package app.view.component - -import app.util.styled -import app.view.KampComponent -import dev.fritz2.dom.html.RenderContext -import dev.fritz2.dom.html.Span -import dev.fritz2.styling.params.BoxParams -import dev.fritz2.styling.theme.Colors -import dev.fritz2.styling.theme.Property - -@KampComponent -fun RenderContext.Badge( - color: (Colors.() -> Property) = { primary.base }, - style: BoxParams.() -> Unit = {}, - content: Span.() -> Unit = {}, -) = styled(::span)( - { - css("border-radius: 0.75rem") - css("background: none repeat scroll 0% 0%") - boxShadow { flat } - display { inlineFlex } - fontWeight { "500" } - minHeight { large } - minWidth { "1.5rem" } - alignItems { center } - justifyContent { spaceAround } - textAlign { center } - paddings { - horizontal { small } - vertical { tiny } - } - textShadow { flat } - fontSize( - sm = { smaller }, - md = { small } - ) - background { - color(color) - } - color { neutral } - style() - }, - content -) diff --git a/app/src/jsMain/kotlin/app/view/component/GitHubIcon.kt b/app/src/jsMain/kotlin/app/view/component/GitHubIcon.kt deleted file mode 100644 index bf04a1d..0000000 --- a/app/src/jsMain/kotlin/app/view/component/GitHubIcon.kt +++ /dev/null @@ -1,34 +0,0 @@ -package app.view.component - -import app.view.KampComponent -import dev.fritz2.components.icon -import dev.fritz2.dom.html.RenderContext -import dev.fritz2.styling.params.BasicParams -import dev.fritz2.styling.theme.IconDefinition - -private val gitHubSvg = IconDefinition( - displayName = "github", - viewBox = "0 0 496 512", - svg = """ - - """.trimIndent() -) - -@KampComponent -fun RenderContext.GitHubIcon(style: BasicParams.() -> Unit = {}) { - icon(style) { def.value = gitHubSvg } -} diff --git a/app/src/jsMain/kotlin/app/view/component/KampIcon.kt b/app/src/jsMain/kotlin/app/view/component/KampIcon.kt deleted file mode 100644 index 75cd491..0000000 --- a/app/src/jsMain/kotlin/app/view/component/KampIcon.kt +++ /dev/null @@ -1,13 +0,0 @@ -package app.view.component - -import app.util.styled -import app.view.KampComponent -import dev.fritz2.dom.html.RenderContext -import dev.fritz2.styling.params.BoxParams - -@KampComponent -fun RenderContext.KampIcon(style: BoxParams.() -> Unit = {}) { - styled(::img)(style) { - src("/kamp.svg") - } -} diff --git a/app/src/jsMain/kotlin/app/view/component/LibraryCard.kt b/app/src/jsMain/kotlin/app/view/component/LibraryCard.kt deleted file mode 100644 index c936cc5..0000000 --- a/app/src/jsMain/kotlin/app/view/component/LibraryCard.kt +++ /dev/null @@ -1,343 +0,0 @@ -package app.view.component - -import app.util.styled -import app.view.KampComponent -import dev.fritz2.binding.RootStore -import dev.fritz2.binding.storeOf -import dev.fritz2.components.box -import dev.fritz2.components.clickButton -import dev.fritz2.components.flexBox -import dev.fritz2.components.icon -import dev.fritz2.components.lineUp -import dev.fritz2.components.popover -import dev.fritz2.components.stackUp -import dev.fritz2.dom.html.RenderContext -import dev.fritz2.dom.html.Span -import dev.fritz2.styling.params.FlexParams -import dev.fritz2.styling.params.Style -import dev.fritz2.styling.theme.Colors -import dev.fritz2.styling.theme.Property -import io.ktor.http.toHttpDate -import io.ktor.util.date.GMTDate -import kamp.domain.KotlinMPPLibrary -import kamp.domain.KotlinTarget -import kotlinx.coroutines.flow.map - -private val String.badgeColor: Colors.() -> Property - get() = when (this) { - KotlinTarget.Common.category -> ({ "#47d7ff" }) - KotlinTarget.JVM.category -> ({ "#79bf2d" }) - KotlinTarget.JS.category -> ({ "#ffb100" }) - KotlinTarget.Native.category -> ({ "#6d6dff" }) - else -> ({ gray500 }) - } - -private fun targetPriority(target: String) = when (target) { - KotlinTarget.Common.category -> 1 - KotlinTarget.JVM.category -> 2 - KotlinTarget.JS.category -> 3 - KotlinTarget.Native.category -> 4 - else -> 0 -} - -@KampComponent -private fun RenderContext.TargetBadge(category: String, targets: List) { - @KampComponent - fun RenderContext.RenderBadge(content: Span.() -> Unit) { - Badge( - category.badgeColor, - { - css("cursor: pointer") - height { large } - margins { - left { tiny } - top { tiny } - } - }, - content - ) - } - - if (targets.size > 1) { - popover({ - width { minContent } - css("border-radius: 0.5rem") - paddings { - vertical { tiny } - } - minWidth { "5rem" } - textAlign { center } - background { - color(category.badgeColor) - } - }) { - placement { bottom } - hasCloseButton(false) - - toggle { - RenderBadge { - +category - icon { fromTheme { caretDown } } - } - } - content { - for (target in targets) { - styled(::div)({ - fontWeight { "500" } - color { neutral } - textShadow { flat } - }) { - +target.platform - } - } - } - } - } else { - RenderBadge { +(targets.firstOrNull()?.platform ?: category) } - } -} - -@KampComponent -private fun RenderContext.CardHeader(library: KotlinMPPLibrary) { - val groupedTargets = library.targets.groupBy(KotlinTarget::category).entries.sortedWith { (keyA), (keyB) -> - targetPriority(keyA) - targetPriority(keyB) - } - - box { - box({ - display { flex } - alignContent { center } - justifyContent { spaceBetween } - }) { - flexBox({ - direction { column } - }) { - styled(::h4)({ - margins { - top { tiny } - } - }) { +library.name } - styled(::sub)({ - margin { "0" } - }) { +library.group } - } - box({ - margins { - left { small } - } - justifyContent { flexEnd } - css("flex-wrap: wrap") - display { inlineFlex } - }) { - for ((category, targets) in groupedTargets) { - TargetBadge(category, targets) - } - } - } - lineUp({ - justifyContent { spaceBetween } - }) { - items { - box({ - css("align-self: flex-end") - }) { - library.lastUpdated?.let { - sub { - +GMTDate(it).toHttpDate() - } - } - } - lineUp({ - justifyContent { flexEnd } - }) { - spacing { none } - items { - library.website?.let { - Link(it, "_new") { - +"Website" - } - } - library.scm?.let { - Link(it, "_new") { - +"SCM" - } - } - } - } - } - } - } -} - -@KampComponent -private fun RenderContext.CardBody(library: KotlinMPPLibrary, selectedVersion: RootStore) = lineUp({ - margins { - bottom { auto } - } -}) { - val boxStyle: Style = { - paddings { horizontal { tiny } } - overflow { auto } - } - items { - box({ - maxWidth { "7rem" } - }) { - styled(::h5)({ }) { - +"Versions" - } - box({ - css("direction: rtl") - maxHeight { "5rem" } - minHeight { "1rem" } - width { "100%" } - minWidth { "3rem" } - boxStyle() - }) { - flexBox({ - css("direction: ltr") - direction { columnReverse } - }) { - val versions = library.versions ?: listOf(library.version) - for (version in versions) { - selectedVersion.data.map { it == version }.render { isSelected -> - clickButton({ - width { "100%" } - height { minContent } - paddings { vertical { "0.15rem" } } - children("span") { - css("text-overflow: ellipsis") - overflow { hidden } - } - }) { - size { small } - text(version) - variant { if (isSelected) solid else outline } - }.map { version } handledBy selectedVersion.update - } - } - } - } - } - box({ - paddings { vertical { tiny } } - maxHeight { "10rem" } - boxStyle() - }) { - library.description?.let { +it } - } - } -} - -private enum class PackageManager { - GRADLE, MAVEN -} - -@KampComponent -private fun RenderContext.CardFooter(library: KotlinMPPLibrary, selectedVersion: RootStore) = stackUp({ - margins { top { small } } -}) { - val selectedPackageManager = storeOf(PackageManager.GRADLE) - - spacing { none } - items { - lineUp { - spacing { none } - items { - selectedPackageManager.data.render { packageManager -> - for (manager in PackageManager.values()) { - box { - clickButton({ - css("border-bottom-right-radius: 0") - css("border-bottom-left-radius: 0") - css("border-bottom: none") - }) { - variant { if (manager == packageManager) solid else outline } - text(manager.name) - }.map { manager } handledBy selectedPackageManager.update - } - } - } - } - } - box({ - width { "100%" } - border { - color { primary.base } - width { thin } - } - css("border-bottom-left-radius: 0.5rem") - css("border-bottom-right-radius: 0.5rem") - css("border-top-right-radius: 0.5rem") - }) { - for (manager in PackageManager.values()) { - when (manager) { - PackageManager.GRADLE -> styled(::pre)({ - overflowX { auto } - width { inherit } - padding { tiny } - }) { - attr("hidden", selectedPackageManager.data.map { it != manager }) - selectedVersion.data.render { version -> - domNode.innerText = "implementation(\"${library.group}:${library.name}:${version}\")" - } - } - PackageManager.MAVEN -> styled(::pre)({ - overflowX { auto } - width { inherit } - padding { tiny } - }) { - attr("hidden", selectedPackageManager.data.map { it != manager }) - selectedVersion.data.render { version -> - domNode.innerText = """| - | ${library.group} - | ${library.name} - | $version - | - """.trimMargin() - } - } - } - } - } - } -} - -@KampComponent -fun RenderContext.LibraryCard(library: KotlinMPPLibrary) { - val selectedVersionStore = storeOf(library.version) - - box({ - border { - width { "1px" } - } - boxShadow { flat } - css("border-radius: 0.5em") - padding { small } - maxWidth( - sm = { "22.5rem" }, - lg = { "30rem" }, - ) - maxHeight( - sm = { "22.5rem" }, - lg = { "30rem" }, - ) - display { flex } - css("flex-direction: column") - }) { - CardHeader(library) - styled(::hr)({ - borders { - top { - color { gray100 } - style { solid } - width { "0.1rem" } - } - } - css("border-radius: 0.5rem") - margins { vertical { tiny } } - }) {} - CardBody(library, selectedVersionStore) - CardFooter(library, selectedVersionStore) - } -} diff --git a/app/src/jsMain/kotlin/app/view/component/NavAnchor.kt b/app/src/jsMain/kotlin/app/view/component/NavAnchor.kt deleted file mode 100644 index d3826ef..0000000 --- a/app/src/jsMain/kotlin/app/view/component/NavAnchor.kt +++ /dev/null @@ -1,37 +0,0 @@ -package app.view.component - -import app.util.styled -import app.view.KampComponent -import dev.fritz2.dom.html.A -import dev.fritz2.dom.html.RenderContext - -@KampComponent -fun RenderContext.Link(href: String, target: String? = null, block: A.() -> Unit = {}) { - styled(::div)({ - radius { small } - border { - width { none } - } - hover { - background { - color { gray100 } - } - } - paddings { - top { tiny } - bottom { tiny } - left { small } - right { small } - } - }) { - styled(::a)({ - fontSize { normal } - fontWeight { semiBold } - color { gray900 } - }) { - href(href) - target?.let { target(it) } - block() - } - } -} diff --git a/app/src/jsMain/resources/index.html b/app/src/jsMain/resources/index.html deleted file mode 100644 index e66e454..0000000 --- a/app/src/jsMain/resources/index.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - KAMP - - - -
- - - diff --git a/versions.properties b/versions.properties index cc0a8ac..e6b475e 100644 --- a/versions.properties +++ b/versions.properties @@ -42,3 +42,7 @@ version.org.kodein.di..kodein-di=7.5.0 version.org.kodein.di..kodein-di-framework-ktor-server-jvm=7.5.0 version.org.litote.kmongo..kmongo-coroutine-serialization=4.2.5 + +version.org.reduxkotlin..redux-kotlin-threadsafe=0.5.5 + +version.org.reduxkotlin..redux-kotlin-thunk=0.5.5 From 99efad3359f3a3ec915c8c3243eb96801935fc40 Mon Sep 17 00:00:00 2001 From: Martynas Petuska Date: Sun, 23 May 2021 23:04:36 +0100 Subject: [PATCH 004/103] MDC Buttons --- app/client/build.gradle.kts | 1 + app/client/mdc/build.gradle.kts | 23 ++++++ .../mdc/src/jsMain/kotlin/button/MDCButton.kt | 50 ++++++++++++ .../src/jsMain/kotlin/button/MDCButtonIcon.kt | 35 +++++++++ .../jsMain/kotlin/button/MDCButtonLabel.kt | 41 ++++++++++ .../jsMain/kotlin/button/MDCButtonRipple.kt | 29 +++++++ .../kotlin/icon/button/MDCIconButton.kt | 75 ++++++++++++++++++ .../mdc/src/jsMain/kotlin/ripple/MDCRipple.kt | 34 ++++++++ app/client/mdc/src/jsMain/kotlin/util.kt | 8 ++ .../src/commonMain/kotlin/store/AppStore.kt | 12 ++- app/client/src/jsMain/kotlin/config/AppEnv.kt | 17 ++-- app/client/src/jsMain/kotlin/main.kt | 6 +- app/client/src/jsMain/kotlin/view/App.kt | 53 +++++++++---- .../jsMain/kotlin/view/component/Layout.kt | 32 ++++++++ .../jsMain/kotlin/view/component/Navbar.kt | 77 +++++++++++++++++++ .../src/jsMain/kotlin/view/style/AppStyle.kt | 5 ++ .../src/jsMain/kotlin/view/style/Colours.kt | 36 +++++++++ app/client/src/jsMain/resources/index.html | 11 ++- .../src/jvmMain/kotlin/config/AppEnv.kt | 8 +- buildSrc/settings.gradle.kts | 13 ++-- gradle/libs.toml | 7 ++ settings.gradle.kts | 19 ++--- versions.properties | 14 ++-- 23 files changed, 547 insertions(+), 59 deletions(-) create mode 100644 app/client/mdc/build.gradle.kts create mode 100644 app/client/mdc/src/jsMain/kotlin/button/MDCButton.kt create mode 100644 app/client/mdc/src/jsMain/kotlin/button/MDCButtonIcon.kt create mode 100644 app/client/mdc/src/jsMain/kotlin/button/MDCButtonLabel.kt create mode 100644 app/client/mdc/src/jsMain/kotlin/button/MDCButtonRipple.kt create mode 100644 app/client/mdc/src/jsMain/kotlin/icon/button/MDCIconButton.kt create mode 100644 app/client/mdc/src/jsMain/kotlin/ripple/MDCRipple.kt create mode 100644 app/client/mdc/src/jsMain/kotlin/util.kt create mode 100644 app/client/src/jsMain/kotlin/view/component/Layout.kt create mode 100644 app/client/src/jsMain/kotlin/view/component/Navbar.kt create mode 100644 app/client/src/jsMain/kotlin/view/style/AppStyle.kt create mode 100644 app/client/src/jsMain/kotlin/view/style/Colours.kt create mode 100644 gradle/libs.toml diff --git a/app/client/build.gradle.kts b/app/client/build.gradle.kts index 522f171..e8846bb 100644 --- a/app/client/build.gradle.kts +++ b/app/client/build.gradle.kts @@ -40,6 +40,7 @@ kotlin { } named("jsMain") { dependencies { + implementation(project(":app:client:mdc")) } } named("jvmMain") { diff --git a/app/client/mdc/build.gradle.kts b/app/client/mdc/build.gradle.kts new file mode 100644 index 0000000..f7f9f5b --- /dev/null +++ b/app/client/mdc/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + kotlin("multiplatform") + id("org.jetbrains.compose") +} + +kotlin { + js { + useCommonJs() + browser() + } + sourceSets { + named("jsMain") { + dependencies { + val mdcVersion = "11.0.0" + api(npm("@material/ripple", mdcVersion)) + api(npm("@material/button", mdcVersion)) + api(npm("@material/icon-button", mdcVersion)) + api(compose.web.web) + api(compose.runtime) + } + } + } +} diff --git a/app/client/mdc/src/jsMain/kotlin/button/MDCButton.kt b/app/client/mdc/src/jsMain/kotlin/button/MDCButton.kt new file mode 100644 index 0000000..17e47a6 --- /dev/null +++ b/app/client/mdc/src/jsMain/kotlin/button/MDCButton.kt @@ -0,0 +1,50 @@ +package dev.petuska.kmdc.button + +import androidx.compose.runtime.Composable +import androidx.compose.web.attributes.AttrsBuilder +import androidx.compose.web.attributes.Tag +import androidx.compose.web.css.StyleBuilder +import androidx.compose.web.elements.Button +import androidx.compose.web.elements.ElementScope +import dev.petuska.kmdc.MDCDsl +import org.w3c.dom.HTMLHeadingElement + +@PublishedApi +@JsModule("@material/button/dist/mdc.button.css") +internal external val MDCButtonStyle: dynamic + +data class MDCButtonOpts( + var type: Type = Type.Text, + var icon: MDCButtonIconType = MDCButtonIconType.None +) { + enum class Type(vararg val classes: String) { + Text, Outline("mdc-button--outline"), Contained("mdc-button--raised"), ContainedUnelevated("mdc-button--unelevated") + } + + enum class MDCButtonIconType(vararg val classes: String) { + None, Leading("mdc-button--icon-leading"), Trailing("mdc-button--icon-trailing") + } +} + +/** + * [JS API](https://github.com/material-components/material-components-web/tree/v11.0.0/packages/mdc-button) + */ +@MDCDsl +@Composable +inline fun MDCButton( + opts: MDCButtonOpts.() -> Unit = {}, + crossinline attrs: AttrsBuilder.() -> Unit = {}, + crossinline style: (StyleBuilder.() -> Unit) = {}, + content: @Composable ElementScope.() -> Unit +) { + MDCButtonStyle + val options = MDCButtonOpts().apply(opts) + Button( + attrs = { + classes("mdc-button", *options.type.classes, *options.icon.classes) + attrs() + }, + style, + content + ) +} diff --git a/app/client/mdc/src/jsMain/kotlin/button/MDCButtonIcon.kt b/app/client/mdc/src/jsMain/kotlin/button/MDCButtonIcon.kt new file mode 100644 index 0000000..7091b05 --- /dev/null +++ b/app/client/mdc/src/jsMain/kotlin/button/MDCButtonIcon.kt @@ -0,0 +1,35 @@ +package dev.petuska.kmdc.button + +import androidx.compose.runtime.Composable +import androidx.compose.web.attributes.AttrsBuilder +import androidx.compose.web.attributes.Tag +import androidx.compose.web.css.StyleBuilder +import androidx.compose.web.elements.ElementScope +import androidx.compose.web.elements.I +import androidx.compose.web.elements.Text +import dev.petuska.kmdc.MDCDsl +import org.w3c.dom.HTMLElement + +@MDCDsl +@Composable +inline fun ElementScope<*>.MDCButtonIcon( + icon: String, + crossinline attrs: AttrsBuilder.() -> Unit = {}, + crossinline style: (StyleBuilder.() -> Unit) = {}, +) { + I( + attrs = { + classes("material-icons", "mdc-button__icon") + prop( + { e, v -> + e.setAttribute("aria-hidden", v) + }, + "true" + ) + attrs() + }, + style = style + ) { + Text(icon) + } +} diff --git a/app/client/mdc/src/jsMain/kotlin/button/MDCButtonLabel.kt b/app/client/mdc/src/jsMain/kotlin/button/MDCButtonLabel.kt new file mode 100644 index 0000000..266ca06 --- /dev/null +++ b/app/client/mdc/src/jsMain/kotlin/button/MDCButtonLabel.kt @@ -0,0 +1,41 @@ +package dev.petuska.kmdc.button + +import androidx.compose.runtime.Composable +import androidx.compose.web.attributes.AttrsBuilder +import androidx.compose.web.attributes.Tag +import androidx.compose.web.css.StyleBuilder +import androidx.compose.web.elements.ElementScope +import androidx.compose.web.elements.Span +import androidx.compose.web.elements.Text +import dev.petuska.kmdc.MDCDsl +import org.w3c.dom.HTMLSpanElement + +@MDCDsl +@Composable +inline fun MDCButtonLabel( + crossinline attrs: AttrsBuilder.() -> Unit = {}, + crossinline style: (StyleBuilder.() -> Unit) = {}, + content: @Composable ElementScope.() -> Unit = {} +) { + Span( + attrs = { + classes("mdc-button__label") + attrs() + }, + style = style, content + ) +} + +@MDCDsl +@Composable +inline fun MDCButtonLabel( + content: String, + crossinline attrs: AttrsBuilder.() -> Unit = {}, + crossinline style: (StyleBuilder.() -> Unit) = {}, +) { + MDCButtonLabel( + attrs, style + ) { + Text(content) + } +} diff --git a/app/client/mdc/src/jsMain/kotlin/button/MDCButtonRipple.kt b/app/client/mdc/src/jsMain/kotlin/button/MDCButtonRipple.kt new file mode 100644 index 0000000..7779619 --- /dev/null +++ b/app/client/mdc/src/jsMain/kotlin/button/MDCButtonRipple.kt @@ -0,0 +1,29 @@ +package dev.petuska.kmdc.button + +import androidx.compose.runtime.Composable +import androidx.compose.web.attributes.AttrsBuilder +import androidx.compose.web.attributes.Tag +import androidx.compose.web.css.StyleBuilder +import androidx.compose.web.elements.ElementScope +import androidx.compose.web.elements.Span +import dev.petuska.kmdc.MDCDsl +import dev.petuska.kmdc.ripple.MDCRipple +import org.w3c.dom.HTMLSpanElement + +@MDCDsl +@Composable +inline fun ElementScope<*>.MDCButtonRipple( + isUnbounded: Boolean = false, + crossinline attrs: AttrsBuilder.() -> Unit = {}, + crossinline style: (StyleBuilder.() -> Unit) = {}, + content: @Composable ElementScope.() -> Unit = {} +) { + Span( + attrs = { + classes("mdc-button__ripple") + attrs() + }, + style = style, content + ) + MDCRipple(isUnbounded) +} diff --git a/app/client/mdc/src/jsMain/kotlin/icon/button/MDCIconButton.kt b/app/client/mdc/src/jsMain/kotlin/icon/button/MDCIconButton.kt new file mode 100644 index 0000000..bec5ea8 --- /dev/null +++ b/app/client/mdc/src/jsMain/kotlin/icon/button/MDCIconButton.kt @@ -0,0 +1,75 @@ +package dev.petuska.kmdc.icon.button + +import androidx.compose.runtime.Composable +import androidx.compose.web.attributes.AttrsBuilder +import androidx.compose.web.attributes.Tag +import androidx.compose.web.css.StyleBuilder +import androidx.compose.web.elements.Button +import androidx.compose.web.elements.ElementScope +import androidx.compose.web.elements.Text +import dev.petuska.kmdc.MDCDsl +import org.w3c.dom.Element +import org.w3c.dom.HTMLHeadingElement + +@PublishedApi +@JsModule("@material/icon-button/dist/mdc.icon-button.css") +internal external val MDCIconButtonStyle: dynamic + +@PublishedApi +@JsModule("@material/icon-button") +internal external object MDCIconButtonModule { + class MDCIconButtonToggle(element: Element) { + companion object { + fun attachTo(element: Element) + } + } +} + +data class MDCIconButtonOpts( + var on: Boolean = false, +) + +/** + * [JS API](https://github.com/material-components/material-components-web/tree/v11.0.0/packages/mdc-icon-button) + */ +@MDCDsl +@Composable +inline fun MDCIconButton( + opts: MDCIconButtonOpts.() -> Unit = {}, + crossinline attrs: AttrsBuilder.() -> Unit = {}, + crossinline style: (StyleBuilder.() -> Unit) = {}, + content: @Composable ElementScope.() -> Unit +) { + MDCIconButtonStyle + val options = MDCIconButtonOpts().apply(opts) + Button( + attrs = { + classes(*listOfNotNull("mdc-icon-button", if (options.on) "mdc-icon-button--on" else null).toTypedArray()) + attrs() + }, + style, + ) { + DomSideEffect { + MDCIconButtonModule.MDCIconButtonToggle.attachTo(it) + } + content() + } +} + +@MDCDsl +@Composable +inline fun MDCIconButton( + icon: String, + crossinline attrs: AttrsBuilder.() -> Unit = {}, + crossinline style: (StyleBuilder.() -> Unit) = {}, +) { + MDCIconButton( + attrs = { + classes("mdc-icon-button", "material-icons") + attrs() + }, + style = style, + ) { + Text(icon) + } +} diff --git a/app/client/mdc/src/jsMain/kotlin/ripple/MDCRipple.kt b/app/client/mdc/src/jsMain/kotlin/ripple/MDCRipple.kt new file mode 100644 index 0000000..b7240b1 --- /dev/null +++ b/app/client/mdc/src/jsMain/kotlin/ripple/MDCRipple.kt @@ -0,0 +1,34 @@ +package dev.petuska.kmdc.ripple + +import androidx.compose.runtime.Composable +import androidx.compose.web.elements.ElementScope +import dev.petuska.kmdc.MDCDsl +import dev.petuska.kmdc.jsObject +import org.w3c.dom.Element + +@JsModule("@material/ripple") +internal external object MDCRippleModule { + interface MDCRippleAttachOpts { + var isUnbounded: Boolean? + } + + class MDCRipple(element: Element, opts: MDCRippleAttachOpts = definedExternally) { + companion object { + fun attachTo(element: Element, opts: MDCRippleAttachOpts = definedExternally) + } + } +} + +/** + * [JS API](https://github.com/material-components/material-components-web/tree/v11.0.0/packages/mdc-ripple) + */ +@MDCDsl +@Composable +fun ElementScope<*>.MDCRipple(isUnbounded: Boolean = false) = DomSideEffect { + MDCRippleModule.MDCRipple.attachTo( + it, + jsObject { + this.isUnbounded = isUnbounded + } + ) +} diff --git a/app/client/mdc/src/jsMain/kotlin/util.kt b/app/client/mdc/src/jsMain/kotlin/util.kt new file mode 100644 index 0000000..f972429 --- /dev/null +++ b/app/client/mdc/src/jsMain/kotlin/util.kt @@ -0,0 +1,8 @@ +package dev.petuska.kmdc + +internal external fun require(module: String): dynamic + +internal fun jsObject(builder: T.() -> Unit): T = js("{}").unsafeCast().apply(builder) + +@DslMarker +annotation class MDCDsl diff --git a/app/client/src/commonMain/kotlin/store/AppStore.kt b/app/client/src/commonMain/kotlin/store/AppStore.kt index 401463c..13bc6f2 100644 --- a/app/client/src/commonMain/kotlin/store/AppStore.kt +++ b/app/client/src/commonMain/kotlin/store/AppStore.kt @@ -7,4 +7,14 @@ import org.reduxkotlin.applyMiddleware import org.reduxkotlin.createStore import org.reduxkotlin.createThunkMiddleware -object AppStore : Store by createStore(rootReducer, AppState(), applyMiddleware(createThunkMiddleware())) +typealias AppStore = Store + +val appStore: AppStore by lazy { + createStore( + rootReducer, + AppState(), + applyMiddleware( + createThunkMiddleware() + ) + ) +} diff --git a/app/client/src/jsMain/kotlin/config/AppEnv.kt b/app/client/src/jsMain/kotlin/config/AppEnv.kt index 370c37d..4b90e36 100644 --- a/app/client/src/jsMain/kotlin/config/AppEnv.kt +++ b/app/client/src/jsMain/kotlin/config/AppEnv.kt @@ -4,12 +4,15 @@ import kotlinx.browser.window import kotlinx.coroutines.await import org.w3c.dom.Window +actual external interface AppEnv { + actual val API_URL: String +} + inline val Window.env: AppEnv get() = asDynamic().env.unsafeCast() -actual val env: AppEnv by lazy { - window.env -} +actual val env: AppEnv + get() = window.env actual suspend fun loadEnv() { val env: AppEnv = window.fetch("/application.env").await().text().await() @@ -21,10 +24,8 @@ actual suspend fun loadEnv() { } "\"$key\": \"$value\"" }.let(JSON::parse) + with(env) { + requireNotNull(API_URL) + } window.asDynamic().env = env - requireNotNull(window.env.API_URL) -} - -actual external interface AppEnv { - actual val API_URL: String } diff --git a/app/client/src/jsMain/kotlin/main.kt b/app/client/src/jsMain/kotlin/main.kt index a913a89..f335b19 100644 --- a/app/client/src/jsMain/kotlin/main.kt +++ b/app/client/src/jsMain/kotlin/main.kt @@ -1,12 +1,16 @@ package app.client +import androidx.compose.web.css.Style import androidx.compose.web.renderComposable import app.client.config.loadEnv +import app.client.store.appStore import app.client.view.App +import app.client.view.style.AppStyle actual suspend fun main() { loadEnv() renderComposable(rootElementId = "root") { - App() + Style(AppStyle) + appStore.App() } } diff --git a/app/client/src/jsMain/kotlin/view/App.kt b/app/client/src/jsMain/kotlin/view/App.kt index 4a6a6f5..46fd169 100644 --- a/app/client/src/jsMain/kotlin/view/App.kt +++ b/app/client/src/jsMain/kotlin/view/App.kt @@ -11,29 +11,52 @@ import androidx.compose.web.elements.Text import app.client.store.AppStore import app.client.store.action.AppAction import app.client.util.select +import app.client.view.component.FlexColumn +import app.client.view.component.Navbar +import dev.petuska.kmdc.button.MDCButton +import dev.petuska.kmdc.button.MDCButtonIcon +import dev.petuska.kmdc.button.MDCButtonLabel +import dev.petuska.kmdc.button.MDCButtonOpts +import dev.petuska.kmdc.button.MDCButtonRipple +import dev.petuska.kmdc.icon.button.MDCIconButton @Composable -fun App() { - val count by AppStore.select { count } - Div(style = { padding(25.px) }) { - Button( - attrs = { - onClick { AppStore.dispatch(AppAction.DecrementCount) } +fun AppStore.App() { + Navbar() + FlexColumn { + + val count by select { count } + MDCButton( + opts = { + type = MDCButtonOpts.Type.Contained + icon = MDCButtonOpts.MDCButtonIconType.Trailing } ) { - Text("-") + MDCButtonRipple() + MDCButtonLabel("Contained") + MDCButtonIcon("favorite") } + MDCIconButton("favorite") + Div(style = { padding(25.px) }) { + Button( + attrs = { + onClick { dispatch(AppAction.DecrementCount) } + } + ) { + Text("-") + } - Span(style = { padding(15.px) }) { - Text("$count") - } + Span(style = { padding(15.px) }) { + Text("$count") + } - Button( - attrs = { - onClick { AppStore.dispatch(AppAction.IncrementCount) } + Button( + attrs = { + onClick { dispatch(AppAction.IncrementCount) } + } + ) { + Text("+") } - ) { - Text("+") } } } diff --git a/app/client/src/jsMain/kotlin/view/component/Layout.kt b/app/client/src/jsMain/kotlin/view/component/Layout.kt new file mode 100644 index 0000000..57056f3 --- /dev/null +++ b/app/client/src/jsMain/kotlin/view/component/Layout.kt @@ -0,0 +1,32 @@ +package app.client.view.component + +import androidx.compose.runtime.Composable +import androidx.compose.web.css.DisplayStyle +import androidx.compose.web.css.FlexDirection +import androidx.compose.web.css.display +import androidx.compose.web.css.flexDirection +import androidx.compose.web.elements.Div + +@Composable +inline fun FlexColumn(content: @Composable () -> Unit) { + Div( + style = { + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Column) + } + ) { + content() + } +} + +@Composable +inline fun FlexRow(content: @Composable () -> Unit) { + Div( + style = { + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Row) + } + ) { + content() + } +} diff --git a/app/client/src/jsMain/kotlin/view/component/Navbar.kt b/app/client/src/jsMain/kotlin/view/component/Navbar.kt new file mode 100644 index 0000000..d013623 --- /dev/null +++ b/app/client/src/jsMain/kotlin/view/component/Navbar.kt @@ -0,0 +1,77 @@ +package app.client.view.component + +import androidx.compose.runtime.Composable +import androidx.compose.web.attributes.AttrsBuilder +import androidx.compose.web.attributes.Tag +import androidx.compose.web.attributes.href +import androidx.compose.web.css.AlignItems +import androidx.compose.web.css.DisplayStyle +import androidx.compose.web.css.FlexDirection +import androidx.compose.web.css.JustifyContent +import androidx.compose.web.css.StyleSheet +import androidx.compose.web.css.alignItems +import androidx.compose.web.css.borderRadius +import androidx.compose.web.css.display +import androidx.compose.web.css.flexDirection +import androidx.compose.web.css.height +import androidx.compose.web.css.justifyContent +import androidx.compose.web.css.percent +import androidx.compose.web.css.rem +import androidx.compose.web.css.width +import androidx.compose.web.elements.A +import androidx.compose.web.elements.Img +import androidx.compose.web.elements.Nav +import androidx.compose.web.elements.Text +import app.client.store.AppStore +import app.client.view.style.AppStyle + +object NavbarStyle : StyleSheet(AppStyle) + +@Composable +fun AppStore.Navbar() { + Nav( + attrs = { + classes( + NavbarStyle.css { + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Row) + justifyContent(JustifyContent.SpaceBetween) + alignItems(AlignItems.Center) + } + ) + } + ) { + + A( + attrs = { + href("#!") + classes( + NavbarStyle.css { + display(DisplayStyle.Flex) + flexDirection(FlexDirection.Row) + alignItems(AlignItems.Center) + } + ) + } + ) { + KampIcon() + Text("KAMP") + } + } +} + +@Composable +private fun KampIcon(attrs: AttrsBuilder.() -> Unit = {}) { + Img( + src = "/kamp.svg", + attrs = attrs, + style = { + width(3.rem) + height(3.rem) + display(DisplayStyle.Inline) + borderRadius(50.percent) + } + ) { + Text("kamp icon") + } +} diff --git a/app/client/src/jsMain/kotlin/view/style/AppStyle.kt b/app/client/src/jsMain/kotlin/view/style/AppStyle.kt new file mode 100644 index 0000000..7bea30a --- /dev/null +++ b/app/client/src/jsMain/kotlin/view/style/AppStyle.kt @@ -0,0 +1,5 @@ +package app.client.view.style + +import androidx.compose.web.css.StyleSheet + +object AppStyle : StyleSheet() diff --git a/app/client/src/jsMain/kotlin/view/style/Colours.kt b/app/client/src/jsMain/kotlin/view/style/Colours.kt new file mode 100644 index 0000000..accd5b0 --- /dev/null +++ b/app/client/src/jsMain/kotlin/view/style/Colours.kt @@ -0,0 +1,36 @@ +package app.client.view.style + +import androidx.compose.web.css.Color + +interface Colours { + val primary: Color + val secondary: Color + val success: Color + val danger: Color + val warning: Color + val info: Color + val light: Color + val dark: Color +} + +object LightColours : Colours { + override val primary: Color = Color("#2B3F4F") + override val secondary: Color = Color("#95A5A6") + override val success: Color = Color("#00BE9D") + override val danger: Color = Color("#E94241") + override val warning: Color = Color("#F4982F") + override val info: Color = Color("#2E9BD8") + override val light: Color = Color("#ECF0F1") + override val dark: Color = Color("#7B8A8B") +} + +object DarkColours : Colours { + override val primary: Color = Color("#365B7D") + override val secondary: Color = Color("#444444") + override val success: Color = Color("#00BD8E") + override val danger: Color = Color("#E94241") + override val warning: Color = Color("#F4982F") + override val info: Color = Color("#2E9BD8") + override val light: Color = Color("#ADB5BD") + override val dark: Color = Color("#303030") +} diff --git a/app/client/src/jsMain/resources/index.html b/app/client/src/jsMain/resources/index.html index 15a94ac..419dfe3 100644 --- a/app/client/src/jsMain/resources/index.html +++ b/app/client/src/jsMain/resources/index.html @@ -2,8 +2,7 @@ - - + @@ -12,9 +11,13 @@ - KAMP - + + + + + +