fixed inital Config
This commit is contained in:
1 parent
cd57e97cff
commit
e8d2408c8e
6 files changed
+132
-56
No files matched your search
@@ -19,6 +19,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(compose.materialIconsExtended)
|
||||
|
||||
// Global hotkeys
|
||||
implementation("com.github.kwhat:jnativehook:2.2.2")
|
||||
|
||||
@@ -65,19 +65,6 @@ fun main() {
|
||||
onSettings = { isSettingsVisible = true },
|
||||
onExit = { exitApplication() },
|
||||
)
|
||||
client.getSpaces()
|
||||
.onSuccess { spaces ->
|
||||
logger.info("Available spaces:")
|
||||
spaces.forEach { logger.info("Space: ${it.id} ${it.name}") }
|
||||
val space = spaces.find { space -> space.id == configResult.config.spaceId }
|
||||
if (space == null) {
|
||||
logger.error("Selected space not found: ${configResult.config.spaceId}")
|
||||
exitProcess(-1)
|
||||
} else {
|
||||
logger.info("Selected space: ${space.name} (${space.id})")
|
||||
}
|
||||
}
|
||||
.onFailure { logger.error("Failed to load spaces: ${it.message}") }
|
||||
}
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
@@ -129,7 +116,6 @@ fun main() {
|
||||
}
|
||||
SettingsWindow(
|
||||
config = configResult.config,
|
||||
client = client,
|
||||
onSave = { newConfig ->
|
||||
saveConfig(newConfig, configResult.sourceFile)
|
||||
configResult = ConfigResult(newConfig, configResult.sourceFile)
|
||||
|
||||
@@ -1,32 +1,19 @@
|
||||
package ch.ergon.anytask.api
|
||||
|
||||
import ch.ergon.anytask.api.dto.CreateObjectRequest
|
||||
import ch.ergon.anytask.api.dto.CreateObjectResponse
|
||||
import ch.ergon.anytask.api.dto.ObjectType
|
||||
import ch.ergon.anytask.api.dto.SearchObject
|
||||
import ch.ergon.anytask.api.dto.SearchObjectsResponse
|
||||
import ch.ergon.anytask.api.dto.Space
|
||||
import ch.ergon.anytask.api.dto.SpacesResponse
|
||||
import ch.ergon.anytask.api.dto.TypesResponse
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.engine.cio.CIO
|
||||
import io.ktor.client.plugins.HttpSend
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.client.plugins.plugin
|
||||
import io.ktor.client.request.bearerAuth
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.parameter
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.contentType
|
||||
import io.ktor.http.isSuccess
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import ch.ergon.anytask.api.dto.*
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.call.*
|
||||
import io.ktor.client.engine.cio.*
|
||||
import io.ktor.client.plugins.*
|
||||
import io.ktor.client.plugins.contentnegotiation.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.statement.*
|
||||
import io.ktor.http.*
|
||||
import io.ktor.http.content.OutgoingContent
|
||||
import io.ktor.http.content.TextContent
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import kotlin.time.DurationUnit.MILLISECONDS
|
||||
import kotlin.time.toDuration
|
||||
@@ -52,6 +39,14 @@ class AnyTypeApi(
|
||||
val method = request.method.value
|
||||
val url = request.url.buildString()
|
||||
logger.info("→ $method $url")
|
||||
if (logger.isTraceEnabled) {
|
||||
val bodyText = when (val body = request.body) {
|
||||
is TextContent -> body.text
|
||||
is OutgoingContent.ByteArrayContent -> body.bytes().decodeToString()
|
||||
else -> null
|
||||
}
|
||||
if (bodyText != null) logger.trace("Request body: $bodyText")
|
||||
}
|
||||
val start = System.currentTimeMillis()
|
||||
val call = execute(request)
|
||||
val duration = (System.currentTimeMillis() - start).toDuration(MILLISECONDS)
|
||||
|
||||
@@ -30,6 +30,7 @@ import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -40,12 +41,14 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ch.ergon.anytask.api.AnyTypeApi
|
||||
import ch.ergon.anytask.api.dto.ObjectType
|
||||
import ch.ergon.anytask.api.dto.Property
|
||||
import ch.ergon.anytask.api.dto.Space
|
||||
import ch.ergon.anytask.config.Configuration
|
||||
import ch.ergon.anytask.config.Connection
|
||||
import ch.ergon.anytask.config.PropertyConfig
|
||||
@@ -54,16 +57,19 @@ import kotlinx.coroutines.launch
|
||||
@Composable
|
||||
fun SettingsWindow(
|
||||
config: Configuration,
|
||||
client: AnyTypeApi,
|
||||
onSave: (Configuration) -> Unit,
|
||||
onCancel: () -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var apiKey by remember { mutableStateOf(config.connection.apiKey) }
|
||||
var loadedApiKey by remember { mutableStateOf(config.connection.apiKey) }
|
||||
var selectedSpaceId by remember { mutableStateOf(config.spaceId) }
|
||||
var selectedTypeKey by remember { mutableStateOf(config.taskTypeKey) }
|
||||
val propertyConfigs = remember { mutableStateListOf<PropertyConfig>().also { it.addAll(config.properties) } }
|
||||
|
||||
var spaces by remember { mutableStateOf<List<Space>>(emptyList()) }
|
||||
var isLoadingSpaces by remember { mutableStateOf(false) }
|
||||
var types by remember { mutableStateOf<List<ObjectType>>(emptyList()) }
|
||||
var isLoadingTypes by remember { mutableStateOf(false) }
|
||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
||||
@@ -71,21 +77,48 @@ fun SettingsWindow(
|
||||
// Property edit state
|
||||
var editingIndex by remember { mutableStateOf<Int?>(null) } // null=none, -1=new
|
||||
|
||||
// Load types on start
|
||||
LaunchedEffect(Unit) {
|
||||
suspend fun loadSpaces(key: String) {
|
||||
spaces = emptyList()
|
||||
isLoadingSpaces = true
|
||||
val tempClient = AnyTypeApi(baseUrl = config.connection.baseUrl, apiKey = key)
|
||||
try {
|
||||
tempClient.getSpaces()
|
||||
.onSuccess { spaces = it }
|
||||
.onFailure { errorMessage = "Failed to load spaces: ${it.message}" }
|
||||
} finally {
|
||||
tempClient.close()
|
||||
}
|
||||
isLoadingSpaces = false
|
||||
}
|
||||
|
||||
suspend fun loadTypes(key: String, spaceId: String) {
|
||||
types = emptyList()
|
||||
isLoadingTypes = true
|
||||
client.getTypes()
|
||||
.onSuccess { types = it }
|
||||
.onFailure { errorMessage = "Failed to load types: ${it.message}" }
|
||||
val tempClient = AnyTypeApi(baseUrl = config.connection.baseUrl, apiKey = key, spaceId = spaceId)
|
||||
try {
|
||||
tempClient.getTypes()
|
||||
.onSuccess { types = it }
|
||||
.onFailure { errorMessage = "Failed to load types: ${it.message}" }
|
||||
} finally {
|
||||
tempClient.close()
|
||||
}
|
||||
isLoadingTypes = false
|
||||
}
|
||||
|
||||
// Initial load
|
||||
LaunchedEffect(Unit) {
|
||||
loadSpaces(apiKey)
|
||||
if (selectedSpaceId.isNotEmpty()) {
|
||||
loadTypes(apiKey, selectedSpaceId)
|
||||
}
|
||||
}
|
||||
|
||||
val selectedType = types.find { it.key == selectedTypeKey }
|
||||
val availableProperties = selectedType?.properties.orEmpty()
|
||||
|
||||
fun buildConfig(): Configuration = Configuration(
|
||||
connection = Connection(apiKey = apiKey, baseUrl = config.connection.baseUrl),
|
||||
spaceId = config.spaceId,
|
||||
spaceId = selectedSpaceId,
|
||||
taskTypeKey = selectedTypeKey,
|
||||
properties = propertyConfigs.toList(),
|
||||
)
|
||||
@@ -96,13 +129,56 @@ fun SettingsWindow(
|
||||
Text("Settings", style = MaterialTheme.typography.h6)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// ApiKey
|
||||
OutlinedTextField(
|
||||
value = apiKey,
|
||||
onValueChange = { apiKey = it },
|
||||
label = { Text("API Key") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
// ApiKey + reload button
|
||||
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
OutlinedTextField(
|
||||
value = apiKey,
|
||||
onValueChange = { apiKey = it },
|
||||
label = { Text("API Key") },
|
||||
singleLine = true,
|
||||
modifier = Modifier.weight(1f).onFocusChanged { focusState ->
|
||||
if (!focusState.isFocused && apiKey != loadedApiKey) {
|
||||
scope.launch {
|
||||
loadedApiKey = apiKey
|
||||
selectedSpaceId = ""
|
||||
selectedTypeKey = ""
|
||||
propertyConfigs.clear()
|
||||
editingIndex = null
|
||||
types = emptyList()
|
||||
loadSpaces(apiKey)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
IconButton(onClick = {
|
||||
scope.launch {
|
||||
loadedApiKey = apiKey
|
||||
selectedSpaceId = ""
|
||||
selectedTypeKey = ""
|
||||
propertyConfigs.clear()
|
||||
editingIndex = null
|
||||
types = emptyList()
|
||||
loadSpaces(apiKey)
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = "Reload spaces")
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// Space dropdown
|
||||
DropdownSelector(
|
||||
label = "Space",
|
||||
selectedValue = spaces.find { it.id == selectedSpaceId }?.name ?: selectedSpaceId,
|
||||
options = spaces.map { it.id to it.name },
|
||||
onSelect = { id ->
|
||||
selectedSpaceId = id
|
||||
selectedTypeKey = ""
|
||||
propertyConfigs.clear()
|
||||
editingIndex = null
|
||||
scope.launch { loadTypes(apiKey, id) }
|
||||
},
|
||||
isLoading = isLoadingSpaces,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="WARN">
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
@@ -1,12 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="WARN">
|
||||
<Properties>
|
||||
<Property name="CLR">{FATAL=bright red, ERROR=red, WARN=bright yellow, INFO=Normal, DEBUG=white, TRACE=white}</Property>
|
||||
<Property name="MSG_CLR">{FATAL=bright red, ERROR=Normal, WARN=Normal, INFO=Normal, DEBUG=Normal, TRACE=white}</Property>
|
||||
<Property name="LOG_PATTERN">%d{yy-MM-dd HH:mm:ss.SSS} %style{[%t]}{magenta} %highlight{%-5level}${CLR} %style{%logger{36}}{cyan} %highlight{: %msg%n}${MSG_CLR}</Property>
|
||||
</Properties>
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
|
||||
<PatternLayout pattern="${LOG_PATTERN}" disableAnsi="false"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
<Root level="trace">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
|
||||