KobWeb integration
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
site:
|
||||
title: "Meldestelle Kobweb Application"
|
||||
|
||||
server:
|
||||
files:
|
||||
dev:
|
||||
contentRoot: ".kobweb/server/dev"
|
||||
prod:
|
||||
contentRoot: ".kobweb/server/prod"
|
||||
siteRoot: "/"
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user