- Frontend-Aufbau für Meldestelle KMP
- Network Layer - Shared Foundation - Service Layer and API Integration - Test-Fix und Development Screen - WASM-Js Test-Implementation - Build-Konfiguration reparieren
This commit is contained in:
@@ -0,0 +1,65 @@
|
|||||||
|
package at.mocode.clients.app
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import at.mocode.clients.shared.commonui.components.AppHeader
|
||||||
|
import at.mocode.clients.shared.commonui.components.AppScaffold
|
||||||
|
import at.mocode.clients.shared.commonui.theme.AppTheme
|
||||||
|
import at.mocode.clients.shared.navigation.AppScreen
|
||||||
|
import at.mocode.clients.pingfeature.PingScreen
|
||||||
|
import at.mocode.clients.pingfeature.PingViewModel
|
||||||
|
import at.mocode.clients.authfeature.LoginScreen
|
||||||
|
import at.mocode.clients.authfeature.AuthTokenManager
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun App() {
|
||||||
|
var currentScreen: AppScreen by remember { mutableStateOf(AppScreen.Home) }
|
||||||
|
// Create a single PingViewModel instance for the lifetime of the App composition.
|
||||||
|
val pingViewModel: PingViewModel = remember { PingViewModel() }
|
||||||
|
// Create a single AuthTokenManager instance for the lifetime of the App composition.
|
||||||
|
val authTokenManager: AuthTokenManager = remember { AuthTokenManager() }
|
||||||
|
// Observe authentication state
|
||||||
|
val authState by authTokenManager.authState.collectAsState()
|
||||||
|
|
||||||
|
AppTheme {
|
||||||
|
AppScaffold(
|
||||||
|
header = {
|
||||||
|
AppHeader(
|
||||||
|
title = "Meldestelle",
|
||||||
|
onNavigateToPing = { currentScreen = AppScreen.Ping },
|
||||||
|
onNavigateToLogin = { currentScreen = AppScreen.Login },
|
||||||
|
onLogout = {
|
||||||
|
authTokenManager.clearToken()
|
||||||
|
currentScreen = AppScreen.Home
|
||||||
|
},
|
||||||
|
isAuthenticated = authState.isAuthenticated,
|
||||||
|
username = authState.username,
|
||||||
|
userPermissions = authState.permissions.map { it.name }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{ paddingValues ->
|
||||||
|
Box(modifier = Modifier.padding(paddingValues)) {
|
||||||
|
when (currentScreen) {
|
||||||
|
is AppScreen.Home -> {
|
||||||
|
LandingScreen(authTokenManager = authTokenManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
is AppScreen.Login -> {
|
||||||
|
LoginScreen(
|
||||||
|
authTokenManager = authTokenManager,
|
||||||
|
onLoginSuccess = { currentScreen = AppScreen.Home }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is AppScreen.Ping -> {
|
||||||
|
PingScreen(viewModel = pingViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
package at.mocode.clients.app
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import at.mocode.clients.authfeature.AuthTokenManager
|
||||||
|
import at.mocode.clients.authfeature.Permission
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LandingScreen(
|
||||||
|
authTokenManager: AuthTokenManager? = null
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(32.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Top
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Willkommen bei Meldestelle",
|
||||||
|
style = MaterialTheme.typography.headlineLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(24.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Eine moderne, skalierbare Frontend-Architektur",
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Diese Anwendung demonstriert eine \"Shell + Feature-Module\"-Architektur " +
|
||||||
|
"basierend auf Kotlin Multiplatform. Sie spiegelt die DDD-Struktur des Backends " +
|
||||||
|
"wider und ist als native Desktop-Anwendung (JVM) und Web-Anwendung (JS/Wasm) lauffähig.",
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
lineHeight = MaterialTheme.typography.bodyLarge.lineHeight * 1.2
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "🚀 Technologien:",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
TechItem("Kotlin Multiplatform")
|
||||||
|
TechItem("Jetpack Compose Multiplatform")
|
||||||
|
TechItem("Material Design 3")
|
||||||
|
TechItem("Ktor Client")
|
||||||
|
TechItem("Domain-Driven Design")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "Verwenden Sie das Ping Service Menü oben, um die API-Funktionalität zu testen.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
|
||||||
|
// Permission-based UI demonstration
|
||||||
|
authTokenManager?.let { tokenManager ->
|
||||||
|
val authState by tokenManager.authState.collectAsState()
|
||||||
|
|
||||||
|
if (authState.isAuthenticated && authState.permissions.isNotEmpty()) {
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(24.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "🔐 Verfügbare Funktionen",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// Admin features (visible only to users with delete permissions)
|
||||||
|
if (tokenManager.isAdmin()) {
|
||||||
|
PermissionCard(
|
||||||
|
title = "👑 Administrator-Bereich",
|
||||||
|
description = "Vollzugriff auf alle System-Funktionen",
|
||||||
|
permissions = listOf("Alle Berechtigungen", "System-Verwaltung", "Benutzer-Management"),
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.errorContainer,
|
||||||
|
textColor = MaterialTheme.colorScheme.onErrorContainer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Management features (visible to users with create/update permissions)
|
||||||
|
if (tokenManager.canCreate() || tokenManager.canUpdate()) {
|
||||||
|
PermissionCard(
|
||||||
|
title = "✏️ Verwaltung",
|
||||||
|
description = "Erstellen und bearbeiten von Daten",
|
||||||
|
permissions = buildList {
|
||||||
|
if (tokenManager.hasPermission(Permission.PERSON_CREATE)) add("Personen erstellen")
|
||||||
|
if (tokenManager.hasPermission(Permission.PERSON_UPDATE)) add("Personen bearbeiten")
|
||||||
|
if (tokenManager.hasPermission(Permission.VEREIN_CREATE)) add("Vereine erstellen")
|
||||||
|
if (tokenManager.hasPermission(Permission.VEREIN_UPDATE)) add("Vereine bearbeiten")
|
||||||
|
if (tokenManager.hasPermission(Permission.PFERD_CREATE)) add("Pferde erstellen")
|
||||||
|
if (tokenManager.hasPermission(Permission.PFERD_UPDATE)) add("Pferde bearbeiten")
|
||||||
|
if (tokenManager.hasPermission(Permission.VERANSTALTUNG_CREATE)) add("Veranstaltungen erstellen")
|
||||||
|
if (tokenManager.hasPermission(Permission.VERANSTALTUNG_UPDATE)) add("Veranstaltungen bearbeiten")
|
||||||
|
},
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
textColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read-only features (visible to all authenticated users)
|
||||||
|
if (tokenManager.canRead()) {
|
||||||
|
PermissionCard(
|
||||||
|
title = "👁️ Ansicht",
|
||||||
|
description = "Nur-Lese-Zugriff auf Daten",
|
||||||
|
permissions = buildList {
|
||||||
|
if (tokenManager.hasPermission(Permission.PERSON_READ)) add("Personen anzeigen")
|
||||||
|
if (tokenManager.hasPermission(Permission.VEREIN_READ)) add("Vereine anzeigen")
|
||||||
|
if (tokenManager.hasPermission(Permission.PFERD_READ)) add("Pferde anzeigen")
|
||||||
|
if (tokenManager.hasPermission(Permission.VERANSTALTUNG_READ)) add("Veranstaltungen anzeigen")
|
||||||
|
},
|
||||||
|
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
textColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun TechItem(text: String) {
|
||||||
|
Text(
|
||||||
|
text = "• $text",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier.padding(vertical = 2.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PermissionCard(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
permissions: List<String>,
|
||||||
|
backgroundColor: androidx.compose.ui.graphics.Color,
|
||||||
|
textColor: androidx.compose.ui.graphics.Color
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = backgroundColor
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = textColor
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = description,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = textColor
|
||||||
|
)
|
||||||
|
|
||||||
|
if (permissions.isNotEmpty()) {
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
permissions.forEach { permission ->
|
||||||
|
Text(
|
||||||
|
text = "✓ $permission",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = textColor,
|
||||||
|
modifier = Modifier.padding(vertical = 2.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
package screens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import at.mocode.clients.shared.presentation.store.AppStore
|
||||||
|
import at.mocode.clients.shared.presentation.state.AppState
|
||||||
|
import at.mocode.clients.pingfeature.PingViewModel
|
||||||
|
import at.mocode.ping.api.HealthResponse
|
||||||
|
import at.mocode.ping.api.PingResponse
|
||||||
|
import at.mocode.ping.api.EnhancedPingResponse
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DevelopmentScreen(appStore: AppStore) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxSize().padding(16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"🚀 Meldestelle Development Mode",
|
||||||
|
style = MaterialTheme.typography.headlineMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
// Backend Connectivity Tests
|
||||||
|
BackendTestSection()
|
||||||
|
|
||||||
|
// Ping Service Test
|
||||||
|
PingTestSection()
|
||||||
|
|
||||||
|
// State Debugging
|
||||||
|
StateDebugSection(appStore)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BackendTestSection() {
|
||||||
|
Card(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text("🌐 Backend Connectivity", style = MaterialTheme.typography.titleMedium)
|
||||||
|
|
||||||
|
var testStatus by remember { mutableStateOf("Not tested") }
|
||||||
|
var isLoading by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
// TODO: Test Gateway Connection
|
||||||
|
isLoading = true
|
||||||
|
testStatus = "Testing..."
|
||||||
|
},
|
||||||
|
enabled = !isLoading
|
||||||
|
) {
|
||||||
|
Text("Test Gateway")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
// TODO: Test Ping Service Direct
|
||||||
|
isLoading = true
|
||||||
|
testStatus = "Testing direct connection..."
|
||||||
|
},
|
||||||
|
enabled = !isLoading
|
||||||
|
) {
|
||||||
|
Text("Test Ping Service")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.padding(8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("Status: $testStatus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PingTestSection() {
|
||||||
|
val pingViewModel = remember { PingViewModel() }
|
||||||
|
val uiState = pingViewModel.uiState
|
||||||
|
|
||||||
|
Card(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text("🏓 Ping Service Integration", style = MaterialTheme.typography.titleMedium)
|
||||||
|
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Button(
|
||||||
|
onClick = { pingViewModel.performHealthCheck() },
|
||||||
|
enabled = !uiState.isLoading
|
||||||
|
) {
|
||||||
|
Text("Health Check")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = { pingViewModel.performSimplePing() },
|
||||||
|
enabled = !uiState.isLoading
|
||||||
|
) {
|
||||||
|
Text("Simple Ping")
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = { pingViewModel.performEnhancedPing(true) },
|
||||||
|
enabled = !uiState.isLoading
|
||||||
|
) {
|
||||||
|
Text("Test Circuit Breaker")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uiState.isLoading) {
|
||||||
|
LinearProgressIndicator(modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Results Display
|
||||||
|
uiState.healthResponse?.let { health ->
|
||||||
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(8.dp)) {
|
||||||
|
Text("✅ Health Check Result:")
|
||||||
|
Text("Status: ${health.status}")
|
||||||
|
Text("Service: ${health.service}")
|
||||||
|
Text("Healthy: ${health.healthy}")
|
||||||
|
Text("Timestamp: ${health.timestamp}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uiState.simplePingResponse?.let { ping ->
|
||||||
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(8.dp)) {
|
||||||
|
Text("🏓 Simple Ping Result:")
|
||||||
|
Text("Status: ${ping.status}")
|
||||||
|
Text("Service: ${ping.service}")
|
||||||
|
Text("Timestamp: ${ping.timestamp}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uiState.enhancedPingResponse?.let { ping ->
|
||||||
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.tertiaryContainer
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(8.dp)) {
|
||||||
|
Text("⚡ Enhanced Ping Result:")
|
||||||
|
Text("Status: ${ping.status}")
|
||||||
|
Text("Circuit Breaker: ${ping.circuitBreakerState}")
|
||||||
|
Text("Response Time: ${ping.responseTime}ms")
|
||||||
|
Text("Service: ${ping.service}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uiState.errorMessage?.let { error ->
|
||||||
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.errorContainer
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"❌ Error: $error",
|
||||||
|
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||||
|
modifier = Modifier.padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StateDebugSection(appStore: AppStore) {
|
||||||
|
val appState by appStore.state.collectAsState()
|
||||||
|
|
||||||
|
Card(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text("🔍 App State Debug", style = MaterialTheme.typography.titleMedium)
|
||||||
|
|
||||||
|
Text("Auth State: ${if(appState.auth.isAuthenticated) "✅ Authenticated" else "❌ Not Authenticated"}")
|
||||||
|
Text("Current Route: ${appState.navigation.currentRoute}")
|
||||||
|
Text("Dark Mode: ${if(appState.ui.isDarkMode) "🌙 Enabled" else "☀️ Disabled"}")
|
||||||
|
Text("Online: ${if(appState.network.isOnline) "🟢 Online" else "🔴 Offline"}")
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
appStore.dispatch(at.mocode.clients.shared.presentation.actions.AppAction.UI.ToggleDarkMode)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Toggle Dark Mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,39 +1,19 @@
|
|||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
// HTML template will be handled by Kotlin/JS build system
|
||||||
const path = require('path');
|
// No need for custom HtmlWebpackPlugin configuration
|
||||||
|
|
||||||
// Template-Pfad für deine index.html
|
// Bundle-Analyse für Development (optional, only if package is available)
|
||||||
const templatePath = path.resolve(__dirname, '../../../../clients/app/src/jsMain/resources/index.html');
|
|
||||||
|
|
||||||
// Erweitere die bestehende Kotlin/JS Webpack-Konfiguration
|
|
||||||
config.plugins.push(new HtmlWebpackPlugin({
|
|
||||||
template: templatePath,
|
|
||||||
filename: 'index.html',
|
|
||||||
inject: 'body',
|
|
||||||
scriptLoading: 'blocking',
|
|
||||||
// Optimierung hinzufügen
|
|
||||||
minify: false
|
|
||||||
/*{
|
|
||||||
removeComments: true,
|
|
||||||
collapseWhitespace: true,
|
|
||||||
removeRedundantAttributes: true,
|
|
||||||
removeEmptyAttributes: true,
|
|
||||||
useShortDoctype: true,
|
|
||||||
removeStyleLinkTypeAttributes: true,
|
|
||||||
keepClosingSlash: true,
|
|
||||||
minifyJS: true,
|
|
||||||
minifyCSS: true,
|
|
||||||
minifyURLs: true,
|
|
||||||
}*/
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Bundle-Analyse für Development
|
|
||||||
if (process.env.ANALYZE_BUNDLE === 'true') {
|
if (process.env.ANALYZE_BUNDLE === 'true') {
|
||||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
try {
|
||||||
config.plugins.push(new BundleAnalyzerPlugin({
|
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
analyzerMode: 'static',
|
config.plugins.push(new BundleAnalyzerPlugin({
|
||||||
openAnalyzer: false,
|
analyzerMode: 'static',
|
||||||
reportFilename: 'bundle-report.html'
|
openAnalyzer: false,
|
||||||
}));
|
reportFilename: 'bundle-report.html'
|
||||||
|
}));
|
||||||
|
console.log('Bundle analyzer enabled');
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Bundle analyzer not available (webpack-bundle-analyzer not installed)');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Weitere Optimierungen hinzufügen (erweitert bestehende config)
|
// Weitere Optimierungen hinzufügen (erweitert bestehende config)
|
||||||
|
|||||||
Reference in New Issue
Block a user