KobWeb integration

This commit is contained in:
stefan
2025-09-09 17:43:31 +02:00
parent 599c1e8bcb
commit 0ba27e7e87
29 changed files with 990 additions and 2011 deletions
+10
View File
@@ -0,0 +1,10 @@
site:
title: "Meldestelle Kobweb Application"
server:
files:
dev:
contentRoot: ".kobweb/server/dev"
prod:
contentRoot: ".kobweb/server/prod"
siteRoot: "/"
+51
View File
@@ -0,0 +1,51 @@
plugins {
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.compose.multiplatform)
alias(libs.plugins.compose.compiler)
}
group = "at.mocode.client.kobweb"
version = "1.0-SNAPSHOT"
kotlin {
js(IR) {
outputModuleName.set("kobweb-app")
browser {
commonWebpackConfig {
outputFileName = "kobweb-app.js"
}
}
binaries.executable()
}
@Suppress("UNUSED_VARIABLE") // Suppress spurious warnings about the outputs not being used anywhere
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
}
}
val jsMain by getting {
dependencies {
// Kobweb dependencies
implementation(libs.kobweb.core)
implementation(libs.kobweb.silk.core)
implementation(libs.kobwebx.markdown)
// Compose HTML (CSS, DOM)
implementation(libs.compose.html.core)
// Common UI module (preserving business logic)
implementation(project(":client:common-ui"))
// Additional web-specific dependencies
implementation(libs.kotlinx.coroutines.core)
implementation(libs.ktor.client.js)
}
}
}
}
@@ -0,0 +1,30 @@
package at.mocode.client.kobweb
import androidx.compose.runtime.*
import com.varabyte.kobweb.core.App
import com.varabyte.kobweb.silk.SilkApp
import com.varabyte.kobweb.silk.components.layout.Surface
import com.varabyte.kobweb.silk.init.InitSilk
import com.varabyte.kobweb.silk.init.InitSilkContext
import com.varabyte.kobweb.compose.ui.Modifier
import com.varabyte.kobweb.compose.ui.modifiers.minHeight
import org.jetbrains.compose.web.css.vh
@InitSilk
fun initSilk(ctx: InitSilkContext) {
// You can configure your app here.
// This will be called once when your app starts up.
//
// As an example, you can use `ctx.stylesheet` to add styles,
// or `ctx.theme` to modify colors, fonts, etc.
}
@App
@Composable
fun MyApp(content: @Composable () -> Unit) {
SilkApp {
Surface(modifier = Modifier.minHeight(100.vh)) {
content()
}
}
}
@@ -0,0 +1,41 @@
package at.mocode.client.kobweb.components
import androidx.compose.runtime.*
import com.varabyte.kobweb.compose.foundation.layout.Box
import com.varabyte.kobweb.compose.ui.Alignment
import com.varabyte.kobweb.compose.ui.Modifier
import com.varabyte.kobweb.compose.ui.modifiers.*
import com.varabyte.kobweb.silk.components.text.SpanText
import kotlinx.coroutines.delay
/**
* A simple loading indicator component using only Kobweb/Silk components.
*/
@Composable
fun LoadingIndicator(
message: String = "Loading",
modifier: Modifier = Modifier
) {
var dots by remember { mutableStateOf("") }
LaunchedEffect(Unit) {
while (true) {
delay(500)
dots = when (dots.length) {
0 -> "."
1 -> ".."
2 -> "..."
else -> ""
}
}
}
Box(
modifier = modifier,
contentAlignment = Alignment.Center
) {
SpanText(
text = "$message$dots"
)
}
}
@@ -0,0 +1,48 @@
package at.mocode.client.kobweb.config
/**
* Application configuration for the Kobweb client.
* Provides centralized configuration management to avoid hardcoded values.
*/
object AppConfig {
/**
* Base URL for the backend services.
* Can be overridden via environment variables or build configuration.
*/
val baseUrl: String = getBaseUrl()
/**
* Application title
*/
const val APP_TITLE = "Meldestelle Kobweb Application"
/**
* Default timeout for network requests in milliseconds
*/
const val DEFAULT_TIMEOUT = 10_000L
/**
* Gets the base URL from various sources with fallback hierarchy:
* 1. Runtime environment variable
* 2. Build-time configuration
* 3. Default localhost for development
*/
private fun getBaseUrl(): String {
// Check for runtime configuration (if available in browser environment)
val runtimeUrl = js("typeof window !== 'undefined' ? window.location.origin : null") as? String
// For development, use localhost backend
// In production, this should be configured during build or deployment
return when {
!runtimeUrl.isNullOrBlank() && runtimeUrl != "null" -> {
// In production, backend might be on same origin or configured path
if (runtimeUrl.contains("localhost") || runtimeUrl.contains("127.0.0.1")) {
"http://localhost:8081" // Development backend
} else {
"$runtimeUrl/api" // Production backend on same origin
}
}
else -> "http://localhost:8081" // Fallback for development
}
}
}
@@ -0,0 +1,53 @@
package at.mocode.client.kobweb.di
import at.mocode.client.data.service.PingService
import at.mocode.client.kobweb.config.AppConfig
import at.mocode.client.ui.viewmodel.PingViewModel
/**
* Simple dependency injection container for the Kobweb application.
* Provides centralized service management and lifecycle handling.
*/
object ServiceProvider {
// Lazy initialization of services
private val _pingService by lazy {
PingService(AppConfig.baseUrl)
}
// Track created ViewModels for cleanup
private val createdViewModels = mutableListOf<PingViewModel>()
/**
* Get the singleton PingService instance
*/
fun getPingService(): PingService = _pingService
/**
* Create a new PingViewModel instance.
* Note: ViewModels should typically be created per screen/component
* to maintain proper state isolation.
*/
fun createPingViewModel(): PingViewModel {
val viewModel = PingViewModel(_pingService)
createdViewModels.add(viewModel)
return viewModel
}
/**
* Cleanup a specific ViewModel
*/
fun cleanupViewModel(viewModel: PingViewModel) {
viewModel.dispose()
createdViewModels.remove(viewModel)
}
/**
* Cleanup all resources when the application is shutting down.
* Should be called when the app is being destroyed.
*/
fun cleanup() {
createdViewModels.forEach { it.dispose() }
createdViewModels.clear()
}
}
@@ -0,0 +1,99 @@
package at.mocode.client.kobweb.pages
import androidx.compose.runtime.*
import at.mocode.client.data.service.PingService
import at.mocode.client.ui.viewmodel.PingUiState
import at.mocode.client.ui.viewmodel.PingViewModel
import com.varabyte.kobweb.core.Page
import com.varabyte.kobweb.silk.components.forms.Button
import com.varabyte.kobweb.compose.foundation.layout.Box
import com.varabyte.kobweb.compose.foundation.layout.Column
import com.varabyte.kobweb.compose.foundation.layout.Spacer
import com.varabyte.kobweb.silk.components.text.SpanText
import com.varabyte.kobweb.compose.ui.Modifier
import com.varabyte.kobweb.compose.ui.Alignment
import com.varabyte.kobweb.compose.ui.modifiers.*
import org.jetbrains.compose.web.css.*
import org.jetbrains.compose.web.css.rgb
import org.jetbrains.compose.web.dom.Div
import org.jetbrains.compose.web.dom.H1
import org.jetbrains.compose.web.dom.Text
import at.mocode.client.kobweb.config.AppConfig
import at.mocode.client.kobweb.di.ServiceProvider
import at.mocode.client.kobweb.components.LoadingIndicator
@Page
@Composable
fun HomePage() {
// Use dependency injection for better service management
val viewModel = remember { ServiceProvider.createPingViewModel() }
// Proper lifecycle management with ServiceProvider cleanup
DisposableEffect(viewModel) {
onDispose {
ServiceProvider.cleanupViewModel(viewModel)
}
}
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.px)
) {
H1 {
Text(AppConfig.APP_TITLE)
}
Spacer()
H1 {
Text("Ping Backend Service")
}
Spacer()
// Status display area
Box(
modifier = Modifier
.fillMaxWidth()
.height(100.px),
contentAlignment = Alignment.Center
) {
when (val state = viewModel.uiState) {
is PingUiState.Initial -> {
SpanText(
text = "Klicke auf den Button, um das Backend zu testen",
modifier = Modifier.color(rgb(0, 0, 0))
)
}
is PingUiState.Loading -> {
LoadingIndicator(
message = "Pinge Backend",
modifier = Modifier.fillMaxWidth()
)
}
is PingUiState.Success -> {
SpanText(
text = "Antwort vom Backend: ${state.response.status}",
modifier = Modifier.color(rgb(0, 150, 0))
)
}
is PingUiState.Error -> {
SpanText(
text = "Fehler: ${state.message}",
modifier = Modifier.color(rgb(180, 0, 0))
)
}
}
}
Spacer()
Button(
onClick = { viewModel.pingBackend() },
enabled = viewModel.uiState !is PingUiState.Loading
) {
SpanText("Ping Backend")
}
}
}