refactor web-map to be SPA
This commit is contained in:
parent
de419aeae8
commit
a84c4a2f76
17 changed files with 329 additions and 267 deletions
|
|
@ -173,3 +173,8 @@ val installThemeCss by tasks.registering(Copy::class) {
|
||||||
tasks.assemble {
|
tasks.assemble {
|
||||||
dependsOn(installThemeCss)
|
dependsOn(installThemeCss)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
tasks.withType<io.kvision.gradle.tasks.KVConvertPoTask>().configureEach {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,5 @@
|
||||||
FROM nginx
|
FROM nginx
|
||||||
|
|
||||||
|
COPY ./nginx.conf /etc/nginx/nginx.conf
|
||||||
|
|
||||||
COPY ./build/distributions /usr/share/nginx/html
|
COPY ./build/distributions /usr/share/nginx/html
|
||||||
|
|
|
||||||
25
modules/web-map/docker/nginx.conf
Normal file
25
modules/web-map/docker/nginx.conf
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
http {
|
||||||
|
# what times to include
|
||||||
|
# include /etc/nginx/mime.types;
|
||||||
|
# what is the default one
|
||||||
|
#default_type application/octet-stream;
|
||||||
|
|
||||||
|
# Sets the path, format, and configuration for a buffered log write
|
||||||
|
#log_format compression '$remote_addr - $remote_user [$time_local] '
|
||||||
|
#'"$request" $status $upstream_addr '
|
||||||
|
#'"$http_referer" "$http_user_agent"';
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# First attempt to serve request as file, then
|
||||||
|
# as directory, then fall back to redirecting to index.html
|
||||||
|
try_files $uri $uri/ $uri.html /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,15 +4,19 @@ import dev.adamko.kafkatorio.schema.packets.EventServerPacket
|
||||||
import dev.adamko.kafkatorio.webmap.layout.headerNav
|
import dev.adamko.kafkatorio.webmap.layout.headerNav
|
||||||
import dev.adamko.kafkatorio.webmap.layout.homePage
|
import dev.adamko.kafkatorio.webmap.layout.homePage
|
||||||
import dev.adamko.kafkatorio.webmap.layout.servers.serverPage
|
import dev.adamko.kafkatorio.webmap.layout.servers.serverPage
|
||||||
|
import dev.adamko.kafkatorio.webmap.routing.SiteRouter
|
||||||
|
import dev.adamko.kafkatorio.webmap.routing.SiteView
|
||||||
import dev.adamko.kafkatorio.webmap.services.WebsocketService
|
import dev.adamko.kafkatorio.webmap.services.WebsocketService
|
||||||
|
import dev.adamko.kafkatorio.webmap.state.SiteAction
|
||||||
|
import dev.adamko.kafkatorio.webmap.state.SiteState
|
||||||
import io.kvision.Application
|
import io.kvision.Application
|
||||||
import io.kvision.BootstrapIconsModule
|
import io.kvision.BootstrapIconsModule
|
||||||
import io.kvision.BootstrapModule
|
import io.kvision.BootstrapModule
|
||||||
import io.kvision.ChartModule
|
import io.kvision.ChartModule
|
||||||
import io.kvision.CoreModule
|
import io.kvision.CoreModule
|
||||||
import io.kvision.FontAwesomeModule
|
import io.kvision.FontAwesomeModule
|
||||||
|
import io.kvision.html.Link
|
||||||
import io.kvision.html.div
|
import io.kvision.html.div
|
||||||
import io.kvision.html.h3
|
|
||||||
import io.kvision.html.header
|
import io.kvision.html.header
|
||||||
import io.kvision.html.main
|
import io.kvision.html.main
|
||||||
import io.kvision.module
|
import io.kvision.module
|
||||||
|
|
@ -20,7 +24,6 @@ import io.kvision.panel.root
|
||||||
import io.kvision.redux.ReduxStore
|
import io.kvision.redux.ReduxStore
|
||||||
import io.kvision.redux.createReduxStore
|
import io.kvision.redux.createReduxStore
|
||||||
import io.kvision.require
|
import io.kvision.require
|
||||||
import io.kvision.routing.Routing
|
|
||||||
import io.kvision.startApplication
|
import io.kvision.startApplication
|
||||||
import io.kvision.state.bind
|
import io.kvision.state.bind
|
||||||
import kotlinx.coroutines.CoroutineName
|
import kotlinx.coroutines.CoroutineName
|
||||||
|
|
@ -51,6 +54,7 @@ object App : Application() {
|
||||||
var siteStateStore: ReduxStore<SiteState, SiteAction> by appState
|
var siteStateStore: ReduxStore<SiteState, SiteAction> by appState
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.siteStateStore = createReduxStore(SiteState::plus, SiteState())
|
this.siteStateStore = createReduxStore(SiteState::plus, SiteState())
|
||||||
|
|
||||||
|
|
@ -65,27 +69,17 @@ object App : Application() {
|
||||||
}.launchIn(coroutineScope)
|
}.launchIn(coroutineScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
// private val gameState: FactorioGameState
|
|
||||||
// get() = reduxStore.getState()
|
|
||||||
|
|
||||||
// private val kvMaps: Maps
|
|
||||||
// get() = gameState.map.kvMap
|
|
||||||
|
|
||||||
override fun start(state: Map<String, Any>) {
|
override fun start(state: Map<String, Any>) {
|
||||||
Routing.init(useHash = false)
|
SiteRouter.init()
|
||||||
|
|
||||||
this.appState = state.toMutableMap()
|
this.appState = state.toMutableMap()
|
||||||
|
|
||||||
SiteRouting.init()
|
|
||||||
|
|
||||||
root("kvapp") {
|
root("kvapp") {
|
||||||
|
|
||||||
header().bind(siteStateStore) { state ->
|
header().bind(siteStateStore) { state ->
|
||||||
headerNav(state)
|
headerNav(state)
|
||||||
}
|
}
|
||||||
|
|
||||||
h3("Kafkatorio Web Map")
|
|
||||||
|
|
||||||
main().bind(siteStateStore) { state ->
|
main().bind(siteStateStore) { state ->
|
||||||
div(className = "container-fluid") {
|
div(className = "container-fluid") {
|
||||||
when (state.view) {
|
when (state.view) {
|
||||||
|
|
@ -93,6 +87,7 @@ object App : Application() {
|
||||||
SiteView.SERVER -> serverPage(state)
|
SiteView.SERVER -> serverPage(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SiteRouter.updatePageLinks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,6 +102,7 @@ object App : Application() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun dispose(): Map<String, Any> {
|
override fun dispose(): Map<String, Any> {
|
||||||
rootJob.cancelChildren()
|
rootJob.cancelChildren()
|
||||||
rootJob.cancel("app dispose")
|
rootJob.cancel("app dispose")
|
||||||
|
|
@ -119,7 +115,10 @@ object App : Application() {
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
startApplication(
|
startApplication(
|
||||||
{ App },
|
{
|
||||||
|
Link.useDataNavigoForLinks = true
|
||||||
|
App
|
||||||
|
},
|
||||||
module.hot,
|
module.hot,
|
||||||
BootstrapModule,
|
BootstrapModule,
|
||||||
// BootstrapCssModule,
|
// BootstrapCssModule,
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
package dev.adamko.kafkatorio.webmap.layout
|
package dev.adamko.kafkatorio.webmap.layout
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.webmap.SiteState
|
import dev.adamko.kafkatorio.webmap.routing.SiteView
|
||||||
import dev.adamko.kafkatorio.webmap.SiteView
|
import dev.adamko.kafkatorio.webmap.state.SiteState
|
||||||
import io.kvision.core.Container
|
import io.kvision.core.Container
|
||||||
import io.kvision.navbar.nav
|
import io.kvision.navbar.nav
|
||||||
import io.kvision.navbar.navLink
|
|
||||||
import io.kvision.navbar.navbar
|
import io.kvision.navbar.navbar
|
||||||
|
|
||||||
|
|
||||||
fun Container.headerNav(state: SiteState) {
|
fun Container.headerNav(state: SiteState) {
|
||||||
navbar {
|
navbar(label = "Kafkatorio", link = SiteView.HOME.path) {
|
||||||
nav {
|
nav {
|
||||||
navLink("Home", SiteView.HOME.path)
|
// navLink("Home", SiteView.HOME.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,55 +1,111 @@
|
||||||
package dev.adamko.kafkatorio.webmap.layout
|
package dev.adamko.kafkatorio.webmap.layout
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.webmap.SiteRouting
|
import dev.adamko.kafkatorio.webmap.state.SiteState
|
||||||
import dev.adamko.kafkatorio.webmap.SiteState
|
|
||||||
import io.kvision.core.Container
|
import io.kvision.core.Container
|
||||||
import io.kvision.html.ButtonSize
|
import io.kvision.html.ListType
|
||||||
import io.kvision.html.ButtonStyle
|
|
||||||
import io.kvision.html.button
|
|
||||||
import io.kvision.html.div
|
import io.kvision.html.div
|
||||||
import io.kvision.html.h5
|
import io.kvision.html.h5
|
||||||
import io.kvision.html.span
|
import io.kvision.html.li
|
||||||
|
import io.kvision.html.link
|
||||||
|
import io.kvision.html.listTag
|
||||||
|
import io.kvision.html.p
|
||||||
|
|
||||||
|
|
||||||
fun Container.homePage(state: SiteState) {
|
fun Container.homePage(state: SiteState) {
|
||||||
div(className = "home-page") {
|
div(className = "home-page") {
|
||||||
|
introduction()
|
||||||
servers(state)
|
servers(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun Container.introduction() {
|
||||||
|
div {
|
||||||
|
h5("Introduction")
|
||||||
|
// <a target="_blank" href="https://github.com/adamko-dev/kafkatorio">Kafkatorio</a>
|
||||||
|
p(
|
||||||
|
"""
|
||||||
|
Kafkatorio is a platform used for creating
|
||||||
|
<a target="_blank" href="https://www.factorio.com/">Factorio</a> mods that require communication with an external server.
|
||||||
|
""".trimIndent(),
|
||||||
|
rich = true,
|
||||||
|
)
|
||||||
|
p(
|
||||||
|
"""
|
||||||
|
It was created to explore the possibilities of using
|
||||||
|
<a target="_blank" href="https://kafka.apache.org/">Apache Kafka</a>
|
||||||
|
to process updates from a Factorio server.
|
||||||
|
""".trimIndent(),
|
||||||
|
rich = true,
|
||||||
|
)
|
||||||
|
p("To keep up to date on progress, check out:")
|
||||||
|
listTag(ListType.UL) {
|
||||||
|
link(
|
||||||
|
label = "Factorio Mod Portal",
|
||||||
|
url = "https://mods.factorio.com/mod/kafkatorio-events",
|
||||||
|
target = "_blank"
|
||||||
|
)
|
||||||
|
link(
|
||||||
|
label = "Factorio forum discussion",
|
||||||
|
url = "https://forums.factorio.com/viewtopic.php?f=190&t=102841&p=570598#p570598",
|
||||||
|
target = "_blank"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
p(
|
||||||
|
"""
|
||||||
|
Development is ongoing. Presently it used to create a live-updating web-map of a Factorio
|
||||||
|
server.
|
||||||
|
""".trimIndent(),
|
||||||
|
rich = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun Container.servers(state: SiteState) {
|
private fun Container.servers(state: SiteState) {
|
||||||
div(className = "servers") {
|
div(className = "servers") {
|
||||||
h5("Servers")
|
h5("Factorio Servers Live maps")
|
||||||
if (state.serverIds != null) {
|
if (state.serverIds != null) {
|
||||||
div {
|
p("View live-updating maps of the following Factorio servers")
|
||||||
span("Available servers: ${state.serverIds.size}")
|
p("Available servers: ${state.serverIds.size}")
|
||||||
}
|
listTag(ListType.UL) {
|
||||||
|
|
||||||
div {
|
|
||||||
state.serverIds.forEach { serverId ->
|
state.serverIds.forEach { serverId ->
|
||||||
// div {
|
li {
|
||||||
// link(
|
div {
|
||||||
// label = "$serverId",
|
link(
|
||||||
// dataNavigo = true,
|
label = "$serverId",
|
||||||
// url = "/servers/$serverId",
|
url = "/servers/$serverId",
|
||||||
// ) {
|
// url = routing.link("servers/$serverId"),
|
||||||
//// onClick {
|
)
|
||||||
//// SiteRouting.serverView(serverId)
|
|
||||||
//// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
button(text = "$serverId", className = "btn btn-link") {
|
|
||||||
style = ButtonStyle.PRIMARY
|
|
||||||
size = ButtonSize.SMALL
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
SiteRouting.serverView(serverId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// div {
|
||||||
|
// state.serverIds.forEach { serverId ->
|
||||||
|
// div {
|
||||||
|
// link(
|
||||||
|
// label = "$serverId",
|
||||||
|
// dataNavigo = true,
|
||||||
|
// url = "/servers/$serverId",
|
||||||
|
// ) {
|
||||||
|
//// onClick {
|
||||||
|
//// SiteRouting.serverView(serverId)
|
||||||
|
//// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// button(text = "$serverId", className = "btn btn-link") {
|
||||||
|
// style = ButtonStyle.PRIMARY
|
||||||
|
// size = ButtonSize.SMALL
|
||||||
|
//
|
||||||
|
// onClick {
|
||||||
|
// SiteRouting.serverView(serverId)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
} else {
|
} else {
|
||||||
div("no servers available")
|
div("no servers available")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package dev.adamko.kafkatorio.webmap.layout.servers
|
package dev.adamko.kafkatorio.webmap.layout.servers
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.webmap.SiteState
|
import dev.adamko.kafkatorio.webmap.state.SiteState
|
||||||
import io.kvision.core.Container
|
import io.kvision.core.Container
|
||||||
import io.kvision.html.div
|
import io.kvision.html.div
|
||||||
import io.kvision.maps.externals.leaflet.layer.tile.GridLayer
|
import io.kvision.maps.externals.leaflet.layer.tile.GridLayer
|
||||||
|
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
package dev.adamko.kafkatorio.webmap
|
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
|
||||||
import dev.adamko.kafkatorio.schema.common.Tick
|
|
||||||
import dev.adamko.kafkatorio.schema.packets.EventServerPacket
|
|
||||||
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
|
||||||
import dev.adamko.kafkatorio.webmap.services.EventsServerClient
|
|
||||||
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.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
|
|
||||||
|
|
||||||
|
|
||||||
object SiteRouting : CoroutineScope {
|
|
||||||
|
|
||||||
override val coroutineContext: CoroutineContext =
|
|
||||||
Dispatchers.Default + SupervisorJob(rootJob) + CoroutineName("SiteRouting")
|
|
||||||
|
|
||||||
|
|
||||||
fun init() {
|
|
||||||
routing
|
|
||||||
.on(SiteView.HOME) { homepageView() }
|
|
||||||
.on(SiteView.SERVER) { match ->
|
|
||||||
val serverId = match.data[0] as? String ?: error("invalid server id")
|
|
||||||
serverView(FactorioServerId(serverId))
|
|
||||||
}
|
|
||||||
.resolve()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun homepageView() {
|
|
||||||
App.siteStateStore.dispatch(SiteAction.HomePage)
|
|
||||||
launch {
|
|
||||||
val serverIds = EventsServerClient.serverIds()
|
|
||||||
App.siteStateStore.dispatch(SiteAction.ServerIdsLoaded(serverIds))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun serverView(serverId: FactorioServerId) {
|
|
||||||
App.siteStateStore.dispatch(SiteAction.ServerPage(serverId))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
data class SiteState(
|
|
||||||
val view: SiteView = SiteView.HOME,
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
is SiteAction.ServerPage -> copy(
|
|
||||||
view = SiteView.SERVER,
|
|
||||||
factorioGameState = FactorioGameState(action.serverId),
|
|
||||||
)
|
|
||||||
|
|
||||||
is SiteAction.ServerIdsLoaded -> copy(serverIds = action.serverIds)
|
|
||||||
|
|
||||||
is SiteAction.FactorioUpdate -> copy(
|
|
||||||
factorioGameState = factorioGameState?.let { it + action }
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sealed interface SiteAction : RAction {
|
|
||||||
object HomePage : SiteAction
|
|
||||||
data class ServerPage(val serverId: FactorioServerId) : SiteAction
|
|
||||||
|
|
||||||
|
|
||||||
data class ServerIdsLoaded(val serverIds: List<FactorioServerId>) : SiteAction
|
|
||||||
|
|
||||||
|
|
||||||
/** Updates from `events-server` */
|
|
||||||
sealed class FactorioUpdate : SiteAction {
|
|
||||||
data class Player(val tick: Tick, val data: PlayerUpdate) : FactorioUpdate()
|
|
||||||
}
|
|
||||||
|
|
||||||
data class EventServerUpdate(val packet: EventServerPacket) : SiteAction
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sealed interface SiteView {
|
|
||||||
|
|
||||||
sealed interface StaticPath : SiteView {
|
|
||||||
val path: String
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface DynamicPath : SiteView {
|
|
||||||
val pathMatcher: RegExp
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
object HOME : StaticPath {
|
|
||||||
override val path: String = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
object SERVER : DynamicPath {
|
|
||||||
override val pathMatcher: RegExp = RegExp("^servers/${FactorioServerId.validIdRegex}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun Navigo.on(view: SiteView, handler: (Match) -> Unit): Navigo {
|
|
||||||
return when (view) {
|
|
||||||
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,
|
|
||||||
// after: AfterHook? = null,
|
|
||||||
// leave: LeaveHook? = null,
|
|
||||||
// already: AlreadyHook? = null,
|
|
||||||
// handler: (Match) -> Unit,
|
|
||||||
//): Navigo {
|
|
||||||
//
|
|
||||||
// val hooks: RouteHooks? =
|
|
||||||
// if (before != null || after != null || leave != null || already != null) {
|
|
||||||
// jso {
|
|
||||||
// if (before != null) this@jso.before = before
|
|
||||||
// if (after != null) this@jso.after = after
|
|
||||||
// if (leave != null) this@jso.leave = leave
|
|
||||||
// if (already != null) this@jso.already = already
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// null
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return when (view) {
|
|
||||||
// is SiteView.StaticPath -> when (hooks) {
|
|
||||||
// null -> on(view.pathMatcher, { match: Match -> handler(match) })
|
|
||||||
// else -> on(view.pathMatcher, { match: Match -> handler(match) }, hooks)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// is SiteView.DynamicPath -> when (hooks) {
|
|
||||||
// null -> on(view.pathMatcher, { match: Match -> handler(match) })
|
|
||||||
// else -> on(view.pathMatcher, { match: Match -> handler(match) }, hooks)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
package dev.adamko.kafkatorio.webmap.routing
|
||||||
|
|
||||||
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
|
import dev.adamko.kafkatorio.webmap.App
|
||||||
|
import dev.adamko.kafkatorio.webmap.state.SiteAction
|
||||||
|
import dev.adamko.kafkatorio.webmap.rootJob
|
||||||
|
import dev.adamko.kafkatorio.webmap.services.EventsServerClient
|
||||||
|
import io.kvision.navigo.Match
|
||||||
|
import io.kvision.navigo.Navigo
|
||||||
|
import io.kvision.routing.Routing
|
||||||
|
import io.kvision.routing.Strategy
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlinx.browser.window
|
||||||
|
import kotlinx.coroutines.CoroutineName
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
object SiteRouter : CoroutineScope {
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext =
|
||||||
|
Dispatchers.Default + SupervisorJob(rootJob) + CoroutineName("SiteRouting")
|
||||||
|
|
||||||
|
|
||||||
|
private val navigo: Routing = Routing(
|
||||||
|
root = "/",
|
||||||
|
useHash = false,
|
||||||
|
strategy = Strategy.ONE
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
fun init() {
|
||||||
|
navigo
|
||||||
|
.on(SiteView.HOME) {
|
||||||
|
log("routing.on HOME")
|
||||||
|
homepageView()
|
||||||
|
}
|
||||||
|
.on(SiteView.SERVER) { match ->
|
||||||
|
println("routing.on SERVER ${JSON.stringify(match)}")
|
||||||
|
val serverId = match.data["serverId"] as? String ?: error("invalid server id")
|
||||||
|
println("routing.on SERVER serverId:$serverId")
|
||||||
|
serverView(FactorioServerId(serverId))
|
||||||
|
}
|
||||||
|
.resolve()
|
||||||
|
|
||||||
|
log("routing.lastResolved(): " + JSON.stringify(navigo.lastResolved()))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun updatePageLinks() {
|
||||||
|
window.setTimeout(
|
||||||
|
handler = {
|
||||||
|
log("updating page links")
|
||||||
|
navigo.updatePageLinks()
|
||||||
|
},
|
||||||
|
timeout = 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun homepageView() {
|
||||||
|
App.siteStateStore.dispatch(SiteAction.HomePage)
|
||||||
|
launch {
|
||||||
|
val serverIds = EventsServerClient.serverIds()
|
||||||
|
log("loaded server IDs $serverIds")
|
||||||
|
App.siteStateStore.dispatch(SiteAction.ServerIdsLoaded(serverIds))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun serverView(serverId: FactorioServerId) {
|
||||||
|
App.siteStateStore.dispatch(SiteAction.ServerPage(serverId))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun log(message: String) = println("[SiteRouting] $message")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun Navigo.on(view: SiteView, handler: (Match) -> Unit): Navigo {
|
||||||
|
return when (view) {
|
||||||
|
is SiteView.StaticPath -> on(view.path, { match: Match -> handler(match) })
|
||||||
|
is SiteView.DynamicPath -> on(view.path, { match: Match -> handler(match) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package dev.adamko.kafkatorio.webmap.routing
|
||||||
|
|
||||||
|
|
||||||
|
sealed interface SiteView {
|
||||||
|
|
||||||
|
sealed interface StaticPath : SiteView {
|
||||||
|
val path: String
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface DynamicPath : SiteView {
|
||||||
|
val path: String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
object HOME : StaticPath {
|
||||||
|
override val path: String = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
object SERVER : DynamicPath {
|
||||||
|
override val path: String = "/servers/:serverId/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
||||||
import dev.adamko.kafkatorio.schema.packets.PrototypesUpdate
|
import dev.adamko.kafkatorio.schema.packets.PrototypesUpdate
|
||||||
import dev.adamko.kafkatorio.schema.packets.SurfaceUpdate
|
import dev.adamko.kafkatorio.schema.packets.SurfaceUpdate
|
||||||
import dev.adamko.kafkatorio.webmap.App
|
import dev.adamko.kafkatorio.webmap.App
|
||||||
import dev.adamko.kafkatorio.webmap.SiteAction
|
import dev.adamko.kafkatorio.webmap.state.SiteAction
|
||||||
import dev.adamko.kafkatorio.webmap.config.ApplicationProperties
|
import dev.adamko.kafkatorio.webmap.config.ApplicationProperties
|
||||||
import dev.adamko.kafkatorio.webmap.config.jsonMapper
|
import dev.adamko.kafkatorio.webmap.config.jsonMapper
|
||||||
import dev.adamko.kafkatorio.webmap.rootJob
|
import dev.adamko.kafkatorio.webmap.rootJob
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package dev.adamko.kafkatorio.webmap.state
|
||||||
|
|
||||||
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
|
import dev.adamko.kafkatorio.schema.common.Tick
|
||||||
|
import dev.adamko.kafkatorio.schema.packets.EventServerPacket
|
||||||
|
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
||||||
|
import io.kvision.redux.RAction
|
||||||
|
|
||||||
|
|
||||||
|
sealed interface SiteAction : RAction {
|
||||||
|
object HomePage : SiteAction
|
||||||
|
data class ServerPage(val serverId: FactorioServerId) : SiteAction
|
||||||
|
|
||||||
|
|
||||||
|
data class ServerIdsLoaded(val serverIds: List<FactorioServerId>) : SiteAction
|
||||||
|
|
||||||
|
|
||||||
|
/** Updates from `events-server` */
|
||||||
|
sealed class FactorioUpdate : SiteAction {
|
||||||
|
data class Player(val tick: Tick, val data: PlayerUpdate) : FactorioUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class EventServerUpdate(val packet: EventServerPacket) : SiteAction
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
package dev.adamko.kafkatorio.webmap.state
|
||||||
|
|
||||||
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
|
import dev.adamko.kafkatorio.schema.packets.EventServerPacket
|
||||||
|
import dev.adamko.kafkatorio.webmap.rootJob
|
||||||
|
import dev.adamko.kafkatorio.webmap.routing.SiteView
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlinx.coroutines.CoroutineName
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
data class SiteState(
|
||||||
|
val view: SiteView = SiteView.HOME,
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
is SiteAction.ServerPage -> copy(
|
||||||
|
view = SiteView.SERVER,
|
||||||
|
factorioGameState = FactorioGameState(action.serverId),
|
||||||
|
)
|
||||||
|
|
||||||
|
is SiteAction.ServerIdsLoaded -> copy(serverIds = action.serverIds)
|
||||||
|
|
||||||
|
is SiteAction.FactorioUpdate -> copy(
|
||||||
|
factorioGameState = factorioGameState?.let { it + action }
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,6 @@ package dev.adamko.kafkatorio.webmap.state
|
||||||
import dev.adamko.kafkatorio.schema.common.PlayerIndex
|
import dev.adamko.kafkatorio.schema.common.PlayerIndex
|
||||||
import dev.adamko.kafkatorio.schema.common.Tick
|
import dev.adamko.kafkatorio.schema.common.Tick
|
||||||
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
||||||
import dev.adamko.kafkatorio.webmap.SiteAction
|
|
||||||
import io.kvision.maps.Maps
|
import io.kvision.maps.Maps
|
||||||
import io.kvision.maps.externals.leaflet.layer.vector.CircleMarker
|
import io.kvision.maps.externals.leaflet.layer.vector.CircleMarker
|
||||||
import kotlinx.js.jso
|
import kotlinx.js.jso
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package dev.adamko.kafkatorio.webmap.state
|
||||||
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
import dev.adamko.kafkatorio.schema.common.PlayerIndex
|
import dev.adamko.kafkatorio.schema.common.PlayerIndex
|
||||||
import dev.adamko.kafkatorio.webmap.FactorioMap
|
import dev.adamko.kafkatorio.webmap.FactorioMap
|
||||||
import dev.adamko.kafkatorio.webmap.SiteAction
|
|
||||||
import io.kvision.maps.externals.leaflet.layer.LayerGroup
|
import io.kvision.maps.externals.leaflet.layer.LayerGroup
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,11 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Kafkatorio Web Map</title>
|
<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="/css/bootstrap.min.css">
|
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="/css/kafkatorio.css">
|
<link rel="stylesheet" href="/css/kafkatorio.css">
|
||||||
<link rel="icon" type="image/png" href="/img/kafkatorio-logo.png"/>
|
<link rel="icon" type="image/png" href="/img/kafkatorio-logo.png"/>
|
||||||
<!--suppress HtmlUnknownTarget -->
|
<!--suppress HtmlUnknownTarget -->
|
||||||
<script type="text/javascript" src="main.bundle.js"></script>
|
<script type="text/javascript" src="/main.bundle.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="kvapp"></div>
|
<div id="kvapp"></div>
|
||||||
|
|
|
||||||
|
|
@ -15,41 +15,5 @@ config.performance = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// required for single-page app
|
||||||
// // dev server
|
config.devServer.historyApiFallback = true
|
||||||
// config.devServer = {
|
|
||||||
// open: false,
|
|
||||||
// // devMiddleware: {
|
|
||||||
// // index: false, // specify to enable root proxying
|
|
||||||
// // },
|
|
||||||
// port: 3000,
|
|
||||||
// proxy: {
|
|
||||||
// // context: () => true,
|
|
||||||
// // target: "http://localhost:12080",
|
|
||||||
// // secure: false,
|
|
||||||
// // ws: true,
|
|
||||||
// // changeOrigin: true,
|
|
||||||
//
|
|
||||||
// // "/tiles": {
|
|
||||||
// // target: "http://localhost:12080",
|
|
||||||
// // // "pathRewrite": { "^/tiles": "" },
|
|
||||||
// // // "logger": "console",
|
|
||||||
// // // "logLevel": "debug",
|
|
||||||
// // secure: false,
|
|
||||||
// // changeOrigin: true,
|
|
||||||
// // },
|
|
||||||
// // "/ws": {
|
|
||||||
// // target: "http://localhost:12080",
|
|
||||||
// // // pathRewrite: { "^/ws": "" },
|
|
||||||
// // // "logLevel": "debug",
|
|
||||||
// // // "logger": "console",
|
|
||||||
// // secure: false,
|
|
||||||
// // ws: true,
|
|
||||||
// // changeOrigin: true,
|
|
||||||
// // },
|
|
||||||
// },
|
|
||||||
// "static": [
|
|
||||||
// // "../../processedResources/js/main",
|
|
||||||
// "D:\\Users\\Adam\\Projects\\games\\kafkatorio\\modules\\web-map\\build\\processedResources\\frontend\\main",
|
|
||||||
// ],
|
|
||||||
// };
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue