Web admin app loads list of players

This commit is contained in:
2026-08-25 10:10:47 -04:00
parent d62575df16
commit d1ef2956d8
12 changed files with 178 additions and 44 deletions
+1
View File
@@ -5,3 +5,4 @@ plugins {
alias(libs.plugins.ktor) apply false alias(libs.plugins.ktor) apply false
alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.kotlin.serialization) apply false
} }
@@ -1,21 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KotlinProject</title>
<link type="text/css" rel="stylesheet" href="styles.css">
</head>
<body style="text-align: center; align-content: center">
<h1>Web Client HTML</h1>
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 50 50" role="presentation">
<circle cx="25" cy="25" r="20" stroke="#ccc" stroke-width="4" fill="none"/>
<circle cx="25" cy="25" r="20" stroke="#333" stroke-width="4" fill="none" stroke-linecap="round"
stroke-dasharray="90 125">
<animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="1s"
repeatCount="indefinite"/>
</circle>
</svg>
<script type="application/javascript" src="web.js"></script>
</body>
</html>
@@ -3,6 +3,7 @@ package dev.geekback.dopewars.desktop
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import dev.geekback.dopewars.shared.ApiClient
import dev.geekback.dopewars.shared.Player import dev.geekback.dopewars.shared.Player
import dev.geekback.dopewars.shared.api.TravelRequest import dev.geekback.dopewars.shared.api.TravelRequest
+4
View File
@@ -18,6 +18,10 @@ kotlin {
commonMain.dependencies { commonMain.dependencies {
implementation(libs.kotlinx.serialization) implementation(libs.kotlinx.serialization)
implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.datetime)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.cio)
implementation(libs.ktor.client.content.negotiation)
} }
wasmJsMain.dependencies { wasmJsMain.dependencies {
@@ -1,11 +1,7 @@
package dev.geekback.dopewars.desktop package dev.geekback.dopewars.shared
import dev.geekback.dopewars.shared.Location
import dev.geekback.dopewars.shared.Market
import dev.geekback.dopewars.shared.Player
import dev.geekback.dopewars.shared.api.PlayerNameUpdateRequest import dev.geekback.dopewars.shared.api.PlayerNameUpdateRequest
import dev.geekback.dopewars.shared.api.TravelRequest import dev.geekback.dopewars.shared.api.TravelRequest
import dev.geekback.dopewars.shared.api.TravelResult
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO import io.ktor.client.engine.cio.CIO
@@ -13,7 +9,6 @@ import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.setBody 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.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType import io.ktor.http.contentType
+3
View File
@@ -4,12 +4,15 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins { plugins {
alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.compose)
alias(libs.plugins.kotlin.compose)
} }
kotlin { kotlin {
jvm() jvm()
wasmJs { wasmJs {
browser()
} }
sourceSets { sourceSets {
+12 -4
View File
@@ -16,11 +16,19 @@ kotlin {
sourceSets { sourceSets {
commonMain.dependencies { commonMain.dependencies {
implementation(project(":shared")) implementation(project(":shared"))
implementation(project(":shared_ui"))
implementation(compose.runtime) // UI Elements
implementation(compose.foundation) implementation(libs.compose.runtime)
implementation(compose.material3) implementation(libs.compose.foundation)
implementation(compose.ui) implementation(libs.compose.material3)
implementation(libs.compose.ui)
// HTTP Client
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.ktor.client.core)
// implementation(libs.ktor.client.cio)
implementation(libs.ktor.client.content.negotiation)
} }
wasmJsMain.dependencies { wasmJsMain.dependencies {
@@ -0,0 +1,38 @@
package dev.geekback.dopewars.web
import dev.geekback.dopewars.shared.Player
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
object AdminApi {
var baseUrl: String = "http://localhost:8080"
private val client = HttpClient {
install(ContentNegotiation) {
json(
Json {
allowSpecialFloatingPointValues = true
ignoreUnknownKeys = true
prettyPrint = true
isLenient = true
})
}
}
fun setBaseUrl(baseUrl: String) {
this.baseUrl = baseUrl
}
suspend fun loadPlayers(): List<Player> {
val response = this.client.get("${this.baseUrl}/players") {
contentType(ContentType.Application.Json)
}
return response.body()
}
}
@@ -0,0 +1,37 @@
package dev.geekback.dopewars.web
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@Composable
fun ApiControlBox(onUrlUpdate: (String) -> Unit) {
var localUrl by remember { mutableStateOf("http://localhost:8080") }
var visible by remember { mutableStateOf(true) }
Card {
Column {
Button(onClick = {
visible != visible
}) {
Text("API Config")
}
TextField(value = AdminApi.baseUrl, onValueChange = {
localUrl = it
})
Button(onClick = {
onUrlUpdate(localUrl)
}) {
Text("Apply")
}
}
}
}
@@ -0,0 +1,53 @@
package dev.geekback.dopewars.web
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dev.geekback.dopewars.shared.Player
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun App() {
val scope = rememberCoroutineScope()
var players by mutableStateOf (listOf<Player>())
MaterialTheme {
Scaffold(
modifier = Modifier.padding(16.dp),
content = {
Column {
// List of current Players
Button(onClick = {
println("Reload Players Now")
scope.launch {
players = AdminApi.loadPlayers()
}
}) {
Text("Reload Players")
}
ApiControlBox {
println("Setting API BaseUrl to $it")
AdminApi.setBaseUrl(it)
}
PlayersList(players) {
println("Click on $it")
}
}
}
)
}
}
@@ -0,0 +1,28 @@
package dev.geekback.dopewars.web
import androidx.compose.foundation.clickable
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import dev.geekback.dopewars.shared.Player
@Composable
fun PlayersList(players: List<Player>, onPLayerSelected: (Player) -> Unit) {
Card {
Text("Current Players")
LazyColumn {
if (players.isEmpty()) {
items(1) {
Text("No Players.")
}
} else {
items(players.size) { i ->
val item = players[i];
Text("Player ${item.name}", modifier = Modifier.clickable { onPLayerSelected(item) })
}
}
}
}
}
@@ -1,12 +1,6 @@
package dev.geekback.dopewars.web package dev.geekback.dopewars.web
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.ComposeViewport import androidx.compose.ui.window.ComposeViewport
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
@@ -15,10 +9,3 @@ fun main() {
App() App()
} }
} }
@Composable
fun App() {
MaterialTheme {
Text(modifier = Modifier.padding(16.dp), text = "Taxation is theft")
}
}