From d1ef2956d861d664ef548ac236cde3b0b93a307d Mon Sep 17 00:00:00 2001 From: Trevor Merritt Date: Tue, 25 Aug 2026 10:10:47 -0400 Subject: [PATCH] Web admin app loads list of players --- build.gradle.kts | 1 + .../packages/dopewars-web/kotlin/index.html | 21 -------- .../geekback/dopewars/desktop/ClientState.kt | 1 + shared/build.gradle.kts | 4 ++ .../geekback/dopewars/shared}/ApiClient.kt | 7 +-- shared_ui/build.gradle.kts | 3 ++ web/build.gradle.kts | 16 ++++-- .../dev/geekback/dopewars/web/AdminApi.kt | 38 +++++++++++++ .../geekback/dopewars/web/ApiControlBox.kt | 37 +++++++++++++ .../kotlin/dev/geekback/dopewars/web/App.kt | 53 +++++++++++++++++++ .../dev/geekback/dopewars/web/PlayersList.kt | 28 ++++++++++ .../kotlin/dev/geekback/dopewars/web/main.kt | 13 ----- 12 files changed, 178 insertions(+), 44 deletions(-) delete mode 100644 build/wasm/packages/dopewars-web/kotlin/index.html rename {desktop/src/main/kotlin/dev/geekback/dopewars/desktop => shared/src/commonMain/kotlin/dev/geekback/dopewars/shared}/ApiClient.kt (92%) create mode 100644 web/src/webMain/kotlin/dev/geekback/dopewars/web/AdminApi.kt create mode 100644 web/src/webMain/kotlin/dev/geekback/dopewars/web/ApiControlBox.kt create mode 100644 web/src/webMain/kotlin/dev/geekback/dopewars/web/App.kt create mode 100644 web/src/webMain/kotlin/dev/geekback/dopewars/web/PlayersList.kt diff --git a/build.gradle.kts b/build.gradle.kts index 27ab617..2845e94 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,3 +5,4 @@ plugins { alias(libs.plugins.ktor) apply false alias(libs.plugins.kotlin.serialization) apply false } + diff --git a/build/wasm/packages/dopewars-web/kotlin/index.html b/build/wasm/packages/dopewars-web/kotlin/index.html deleted file mode 100644 index be5bedc..0000000 --- a/build/wasm/packages/dopewars-web/kotlin/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - KotlinProject - - - -

Web Client HTML

- - - - - - - - - \ No newline at end of file diff --git a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/ClientState.kt b/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/ClientState.kt index 7c5854b..a433ce8 100644 --- a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/ClientState.kt +++ b/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/ClientState.kt @@ -3,6 +3,7 @@ package dev.geekback.dopewars.desktop import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import dev.geekback.dopewars.shared.ApiClient import dev.geekback.dopewars.shared.Player import dev.geekback.dopewars.shared.api.TravelRequest diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index e8ad46f..4730c28 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -18,6 +18,10 @@ kotlin { commonMain.dependencies { implementation(libs.kotlinx.serialization) 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 { diff --git a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/ApiClient.kt b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/ApiClient.kt similarity index 92% rename from desktop/src/main/kotlin/dev/geekback/dopewars/desktop/ApiClient.kt rename to shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/ApiClient.kt index 04397e9..06c7fd7 100644 --- a/desktop/src/main/kotlin/dev/geekback/dopewars/desktop/ApiClient.kt +++ b/shared/src/commonMain/kotlin/dev/geekback/dopewars/shared/ApiClient.kt @@ -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.TravelRequest -import dev.geekback.dopewars.shared.api.TravelResult import io.ktor.client.HttpClient import io.ktor.client.call.body 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.post import io.ktor.client.request.setBody -import io.ktor.client.statement.bodyAsText import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.http.contentType diff --git a/shared_ui/build.gradle.kts b/shared_ui/build.gradle.kts index 91ffa78..f66f6a8 100644 --- a/shared_ui/build.gradle.kts +++ b/shared_ui/build.gradle.kts @@ -4,12 +4,15 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl plugins { alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose) + alias(libs.plugins.kotlin.compose) } kotlin { jvm() wasmJs { + browser() } sourceSets { diff --git a/web/build.gradle.kts b/web/build.gradle.kts index 83718c9..bf7b35d 100644 --- a/web/build.gradle.kts +++ b/web/build.gradle.kts @@ -16,11 +16,19 @@ kotlin { sourceSets { commonMain.dependencies { implementation(project(":shared")) + implementation(project(":shared_ui")) - implementation(compose.runtime) - implementation(compose.foundation) - implementation(compose.material3) - implementation(compose.ui) + // UI Elements + implementation(libs.compose.runtime) + implementation(libs.compose.foundation) + 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 { diff --git a/web/src/webMain/kotlin/dev/geekback/dopewars/web/AdminApi.kt b/web/src/webMain/kotlin/dev/geekback/dopewars/web/AdminApi.kt new file mode 100644 index 0000000..8ade445 --- /dev/null +++ b/web/src/webMain/kotlin/dev/geekback/dopewars/web/AdminApi.kt @@ -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 { + val response = this.client.get("${this.baseUrl}/players") { + contentType(ContentType.Application.Json) + } + + return response.body() + } +} \ No newline at end of file diff --git a/web/src/webMain/kotlin/dev/geekback/dopewars/web/ApiControlBox.kt b/web/src/webMain/kotlin/dev/geekback/dopewars/web/ApiControlBox.kt new file mode 100644 index 0000000..1a2926f --- /dev/null +++ b/web/src/webMain/kotlin/dev/geekback/dopewars/web/ApiControlBox.kt @@ -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") + } + } + } +} diff --git a/web/src/webMain/kotlin/dev/geekback/dopewars/web/App.kt b/web/src/webMain/kotlin/dev/geekback/dopewars/web/App.kt new file mode 100644 index 0000000..44242a0 --- /dev/null +++ b/web/src/webMain/kotlin/dev/geekback/dopewars/web/App.kt @@ -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()) + 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") + } + } + } + ) + } +} + diff --git a/web/src/webMain/kotlin/dev/geekback/dopewars/web/PlayersList.kt b/web/src/webMain/kotlin/dev/geekback/dopewars/web/PlayersList.kt new file mode 100644 index 0000000..67a84f9 --- /dev/null +++ b/web/src/webMain/kotlin/dev/geekback/dopewars/web/PlayersList.kt @@ -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, 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) }) + } + } + } + } +} \ No newline at end of file diff --git a/web/src/webMain/kotlin/dev/geekback/dopewars/web/main.kt b/web/src/webMain/kotlin/dev/geekback/dopewars/web/main.kt index 5de362a..7cb1770 100644 --- a/web/src/webMain/kotlin/dev/geekback/dopewars/web/main.kt +++ b/web/src/webMain/kotlin/dev/geekback/dopewars/web/main.kt @@ -1,12 +1,6 @@ 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.material3.Text -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp import androidx.compose.ui.window.ComposeViewport @OptIn(ExperimentalComposeUiApi::class) @@ -15,10 +9,3 @@ fun main() { App() } } - -@Composable -fun App() { - MaterialTheme { - Text(modifier = Modifier.padding(16.dp), text = "Taxation is theft") - } -}