fix webmap after multi-server refactor, and add styling

This commit is contained in:
Adam 2022-09-02 23:22:18 +02:00
parent 8a6c49a74b
commit 3976c3cc1b
14 changed files with 12461 additions and 96 deletions

View file

@ -1490,7 +1490,7 @@ http-parser-js@>=0.5.1:
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3"
integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==
"http-proxy-middleware@>=2.0.6 <3.0.0", http-proxy-middleware@^2.0.3, http-proxy-middleware@^2.0.6:
http-proxy-middleware@^2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f"
integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==

View file

@ -158,3 +158,18 @@ val dockerEnvUpdate by tasks.registering(DockerEnvUpdateTask::class) {
)
}
tasks.assemble { dependsOn(dockerEnvUpdate) }
val installThemeCss by tasks.registering(Copy::class) {
// val bootswatchThemeCss = resources.text.fromUri("https://bootswatch.com/5/darkly/bootstrap.css")
val bootswatchThemeMinCss =
resources.text.fromUri("https://bootswatch.com/5/darkly/bootstrap.min.css")
from(bootswatchThemeMinCss) {
rename { "bootstrap.min.css" }
}
into(layout.projectDirectory.dir("src/main/resources/css"))
}
tasks.assemble {
dependsOn(installThemeCss)
}

View file

@ -3,16 +3,16 @@ package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.schema.packets.EventServerPacket
import dev.adamko.kafkatorio.webmap.layout.headerNav
import dev.adamko.kafkatorio.webmap.layout.homePage
import dev.adamko.kafkatorio.webmap.layout.serverPage
import dev.adamko.kafkatorio.webmap.layout.servers.serverPage
import dev.adamko.kafkatorio.webmap.services.WebsocketService
import io.kvision.Application
import io.kvision.BootstrapCssModule
import io.kvision.BootstrapIconsModule
import io.kvision.BootstrapModule
import io.kvision.ChartModule
import io.kvision.CoreModule
import io.kvision.FontAwesomeModule
import io.kvision.html.div
import io.kvision.html.h3
import io.kvision.html.header
import io.kvision.html.main
import io.kvision.module
@ -38,7 +38,7 @@ import kotlinx.coroutines.flow.onEach
val rootJob = Job()
class App : Application() {
object App : Application() {
private val coroutineScope: CoroutineScope = CoroutineScope(
Dispatchers.Default
@ -48,15 +48,23 @@ class App : Application() {
private var appState: MutableMap<String, Any> = mutableMapOf()
var reduxStore: ReduxStore<SiteState, SiteAction> by appState
var siteStateStore: ReduxStore<SiteState, SiteAction> by appState
private set
// var wsService: WebsocketService by appState
// private set
init {
this.reduxStore = createReduxStore(SiteState::plus, SiteState())
this.siteStateStore = createReduxStore(SiteState::plus, SiteState())
// this.wsService = wsService
require("./css/kafkatorio.css")
WebsocketService.packetsFlow
.filterIsInstance<EventServerPacket.ChunkTileSaved>()
.onEach { tileSavedEvent ->
// println("[packetsFlow] triggering tile refresh ${tileSavedEvent.filename.value}")
siteStateStore.dispatch(SiteAction.EventServerUpdate(tileSavedEvent))
// reduxStore.map.refreshUpdatedTilePng(tileSavedEvent.filename)
}.launchIn(coroutineScope)
}
// private val gameState: FactorioGameState
@ -70,25 +78,27 @@ class App : Application() {
this.appState = state.toMutableMap()
val siteRouting = SiteRouting(reduxStore)
val websocketService = WebsocketService(reduxStore)
// val siteRouting = SiteRouting(reduxStore)
// val websocketService = WebsocketService(siteStateStore)
siteRouting.init()
SiteRouting.init()
root("kvapp") {
header().bind(siteRouting.siteStateStore) { state ->
header().bind(siteStateStore) { state ->
headerNav(state)
}
div("Kafkatorio Web Map")
h3("Kafkatorio Web Map")
main().bind(siteRouting.siteStateStore) { state ->
main().bind(siteStateStore) { state ->
div(className = "container-fluid") {
when (state.view) {
SiteView.HOME -> homePage(state)
SiteView.SERVER -> serverPage(state)
}
}
}
// div("Kafkatorio Web Map")
// add(kvMaps)
@ -103,14 +113,6 @@ class App : Application() {
// })
// }
websocketService.packetsFlow
.filterIsInstance<EventServerPacket.ChunkTileSaved>()
.onEach { tileSavedEvent ->
// println("[packetsFlow] triggering tile refresh ${tileSavedEvent.filename.value}")
reduxStore.dispatch(SiteAction.EventServerUpdate(tileSavedEvent))
// reduxStore.map.refreshUpdatedTilePng(tileSavedEvent.filename)
}.launchIn(coroutineScope)
}
override fun dispose(): Map<String, Any> {
@ -129,14 +131,16 @@ fun main() {
// val wsService = WebsocketService(
// reduxStore = reduxStore,
// )
App(
// App(
// reduxStore = reduxStore,
// wsService = wsService,
)
// )
App
},
module.hot,
BootstrapModule,
BootstrapCssModule,
// BootstrapCssModule,
BootstrapIconsModule,
FontAwesomeModule,
ChartModule,

View file

@ -1,12 +1,13 @@
package dev.adamko.kafkatorio.webmap
package dev.adamko.kafkatorio.webmap.config
import kotlinx.browser.window
object Props {
object ApplicationProperties {
@Suppress("HttpUrlsUsage")
val websocketServerUrl: String
get() = window.location.href
get() = window.location.origin
.replace("http://", "ws://")
.replace("https://", "wss://") +
"kafkatorio/ws"
"/kafkatorio/ws"
}

View file

@ -10,7 +10,7 @@ import io.kvision.html.nav
fun Container.headerNav(state: SiteState) {
nav(className = "navbar") {
div(className = "container") {
link("home", "#${SiteView.HOME.pathMatcher}")
link("home", SiteView.HOME.path)
}
}
}

View file

@ -1,10 +1,14 @@
package dev.adamko.kafkatorio.webmap.layout
import dev.adamko.kafkatorio.webmap.SiteRouting
import dev.adamko.kafkatorio.webmap.SiteState
import io.kvision.core.Container
import io.kvision.html.ButtonSize
import io.kvision.html.ButtonStyle
import io.kvision.html.button
import io.kvision.html.div
import io.kvision.html.h5
import io.kvision.html.link
import io.kvision.html.span
fun Container.homePage(state: SiteState) {
@ -18,9 +22,32 @@ private fun Container.servers(state: SiteState) {
div(className = "servers") {
h5("Servers")
if (state.serverIds != null) {
state.serverIds.forEach { serverId ->
div {
link("server", "/servers/$serverId")
span("Available servers: ${state.serverIds.size}")
}
div {
state.serverIds.forEach { serverId ->
// div {
// link(
// label = "$serverId",
// dataNavigo = true,
// url = "/servers/$serverId",
// ) {
//// onClick {
//// SiteRouting.serverView(serverId)
//// }
// }
// }
button(text = "$serverId") {
style = ButtonStyle.PRIMARY
size = ButtonSize.SMALL
onClick {
SiteRouting.serverView(serverId)
}
}
}
}
} else {

View file

@ -1,4 +1,4 @@
package dev.adamko.kafkatorio.webmap.layout
package dev.adamko.kafkatorio.webmap.layout.servers
import dev.adamko.kafkatorio.webmap.SiteState
import io.kvision.core.Container
@ -8,19 +8,23 @@ import io.kvision.maps.externals.leaflet.layer.tile.GridLayer
fun Container.serverPage(state: SiteState) {
val factorioGameState = state.factorioGameState
div(className = "server-page") {
if (factorioGameState != null) {
add(factorioGameState.map.kvMap)
}
}
factorioGameState.map.kvMap.leafletMap {
invalidateSize(true)
eachLayer({
if (it is GridLayer<*>) {
it.redraw()
}
})
}
}
}
// TODO figure out how to run this after adding kvMap to the container...
// (doing it now causes an NPE because kvMap isn't added to the DOM yet
// factorioGameState?.map?.kvMap?.leafletMap {
// invalidateSize(true)
// eachLayer({
// if (it is GridLayer<*>) {
// it.redraw()
// }
// })
// }
}

View file

@ -37,8 +37,10 @@ class FactorioMap(
) {
val kvMap: Maps = Maps {
width = 800.px
height = 800.px
minHeight = 800.px
minWidth = 800.px
// width = 100.vh
// height = 100.vh
margin = 10.px
}
@ -164,12 +166,5 @@ class FactorioMap(
companion object {
const val DYNAMIC_RELOAD_ATT = "dynamic-reload"
// private val serverName: String?
// get() = window.location.pathname
// .substringAfter("servers")
// .substringBefore("/")
// .ifBlank { null }
}
}

View file

@ -9,40 +9,44 @@ import dev.adamko.kafkatorio.webmap.state.FactorioGameState
import io.kvision.navigo.Match
import io.kvision.navigo.Navigo
import io.kvision.redux.RAction
import io.kvision.redux.ReduxStore
import io.kvision.routing.routing
import kotlin.coroutines.CoroutineContext
import kotlin.js.RegExp
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class SiteRouting(
val siteStateStore: ReduxStore<SiteState, SiteAction>
) : CoroutineScope by CoroutineScope(Dispatchers.Default + SupervisorJob()) {
object SiteRouting : CoroutineScope {
override val coroutineContext: CoroutineContext =
Dispatchers.Default + SupervisorJob(rootJob) + CoroutineName("SiteRouting")
fun init() {
routing
.on(SiteView.HOME) { homepageView() }
.on(SiteView.SERVER) { match ->
serverView(match)
val serverId = match.data[0] as? String ?: error("invalid server id")
serverView(FactorioServerId(serverId))
}
.resolve()
}
private fun homepageView() {
siteStateStore.dispatch(SiteAction.HomePage)
fun homepageView() {
App.siteStateStore.dispatch(SiteAction.HomePage)
launch {
val serverIds = EventsServerClient.serverIds()
siteStateStore.dispatch(SiteAction.ServerIdsLoaded(serverIds))
App.siteStateStore.dispatch(SiteAction.ServerIdsLoaded(serverIds))
}
}
private fun serverView(match: Match) {
val serverId = match.data[0] as? String ?: error("invalid server id")
siteStateStore.dispatch(SiteAction.ServerPage(FactorioServerId(serverId)))
fun serverView(serverId: FactorioServerId) {
App.siteStateStore.dispatch(SiteAction.ServerPage(serverId))
}
}
@ -52,7 +56,10 @@ data class SiteState(
val serverIds: List<FactorioServerId>? = null,
val factorioGameState: FactorioGameState? = null,
) {
) : CoroutineScope {
override val coroutineContext: CoroutineContext =
Dispatchers.Default + SupervisorJob(rootJob) + CoroutineName("SiteState")
operator fun plus(action: SiteAction): SiteState = when (action) {
is SiteAction.HomePage -> copy(view = SiteView.HOME)
@ -68,9 +75,20 @@ data class SiteState(
factorioGameState = factorioGameState?.let { it + action }
)
is SiteAction.EventServerUpdate -> copy(
is SiteAction.EventServerUpdate -> when (action.packet) {
is EventServerPacket.ChunkTileSaved -> {
if (factorioGameState?.map != null) {
launch {
factorioGameState.map.refreshUpdatedTilePng(action.packet.filename)
}
}
this
}
)
is EventServerPacket.Kafkatorio -> {
this
}
}
}
}
@ -93,19 +111,18 @@ sealed interface SiteAction : RAction {
sealed interface SiteView {
val pathMatcher: Any
sealed interface StaticPath : SiteView {
override val pathMatcher: String
val path: String
}
sealed interface DynamicPath : SiteView {
override val pathMatcher: RegExp
val pathMatcher: RegExp
}
object HOME : StaticPath {
override val pathMatcher: String = "/"
override val path: String = "/"
}
object SERVER : DynamicPath {
@ -113,19 +130,15 @@ sealed interface SiteView {
}
}
//val SiteView.urlMatcher: String
// get() = when (this) {
// SiteView.HOME -> "/"
// SiteView.SERVER -> "servers/${FactorioServerId.validIdRegex}"
// }
private fun Navigo.on(view: SiteView, handler: (Match) -> Unit): Navigo {
return when (view) {
is SiteView.StaticPath -> on(view.pathMatcher, { match: Match -> handler(match) })
is SiteView.StaticPath -> on(view.path, { match: Match -> handler(match) })
is SiteView.DynamicPath -> on(view.pathMatcher, { match: Match -> handler(match) })
}
}
//private fun Navigo.on(
// view: SiteView,
// before: BeforeHook? = null,

View file

@ -12,12 +12,11 @@ import dev.adamko.kafkatorio.schema.packets.MapChunkTileUpdate
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
import dev.adamko.kafkatorio.schema.packets.PrototypesUpdate
import dev.adamko.kafkatorio.schema.packets.SurfaceUpdate
import dev.adamko.kafkatorio.webmap.App
import dev.adamko.kafkatorio.webmap.SiteAction
import dev.adamko.kafkatorio.webmap.SiteState
import dev.adamko.kafkatorio.webmap.config.ApplicationProperties
import dev.adamko.kafkatorio.webmap.config.jsonMapper
import dev.adamko.kafkatorio.webmap.rootJob
import io.kvision.redux.ReduxStore
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
@ -31,9 +30,7 @@ import org.w3c.dom.WebSocket
import org.w3c.dom.events.Event
class WebsocketService(
private val siteStateStore: ReduxStore<SiteState, SiteAction>
) : CoroutineScope {
object WebsocketService : CoroutineScope {
override val coroutineContext: CoroutineContext =
CoroutineName("WebsocketService") + Job(rootJob)
@ -80,7 +77,7 @@ class WebsocketService(
is EventServerPacket.Kafkatorio ->
when (val packetData = packet.packet.data) {
is PlayerUpdate ->
siteStateStore.dispatch(
App.siteStateStore.dispatch(
SiteAction.FactorioUpdate.Player(packet.packet.tick, packetData)
)

File diff suppressed because it is too large Load diff

View file

@ -7,14 +7,16 @@
image-rendering: pixelated;
}
html {
background: #2B2B2BFF;
}
/*html {*/
/* background: #2B2B2BFF;*/
/*}*/
body {
background: #2B2B2BFF;
}
/*body {*/
/* background: #2B2B2BFF;*/
/*}*/
.leaflet-container {
background-color: rgba(255, 0, 0, 0.0);
background-color: rgba(255, 0, 0, 0.0) !important;
/*background: rgb(34, 34, 34) !important;*/
/*background: var(--bs-body-bg-rgb) !important;*/
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View file

@ -4,7 +4,11 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kafkatorio Web Map</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.1.0/dist/superhero/bootstrap.min.css" integrity="sha256-T8h7oW5tHp4MhFhmRoDI8xjnB2ld89qSgc3s9g8T0uU=" crossorigin="anonymous">
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@5.1.0/dist/superhero/bootstrap.min.css" integrity="sha256-T8h7oW5tHp4MhFhmRoDI8xjnB2ld89qSgc3s9g8T0uU=" crossorigin="anonymous">-->
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/kafkatorio.css">
<link rel="icon" type="image/png" href="/img/kafkatorio-logo.png"/>
<!--suppress HtmlUnknownTarget -->
<script type="text/javascript" src="main.bundle.js"></script>
</head>
<body>