diff --git a/modules/web-map/build.gradle.kts b/modules/web-map/build.gradle.kts index 284b406..667e1ed 100644 --- a/modules/web-map/build.gradle.kts +++ b/modules/web-map/build.gradle.kts @@ -1,10 +1,13 @@ +import dev.adamko.kafkatorio.gradle.taskProvider import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler +import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig +import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension plugins { -// dev.adamko.kafkatorio.lang.`kotlin-js` - dev.adamko.kafkatorio.lang.`kotlin-multiplatform` + dev.adamko.kafkatorio.lang.`kotlin-js` +// dev.adamko.kafkatorio.lang.`kotlin-multiplatform` kotlin("plugin.serialization") id("io.kvision") } @@ -19,13 +22,42 @@ project.ext.set("io.kvision.plugin.enableBackendTasks", false) -kotlin { - js(IR) { - browser() - binaries.executable() +rootProject.extensions.configure { + // https://github.com/rjaros/kvision/issues/410 + versions.webpackCli.version = "4.10.0" + versions.webpackDevServer.version = "4.9.2" +} + +rootProject.extensions.configure { + resolution("http-proxy-middleware", "^2.0.6") +} + +//rootProject.afterEvaluate { +// extensions.configure { +// // https://github.com/rjaros/kvision/issues/410 +// versions.webpackCli.version = "4.10.0" +// } +//} + +afterEvaluate { + rootProject.extensions.configure { + // https://github.com/rjaros/kvision/issues/410 + versions.webpackCli.version = "4.10.0" + versions.webpackDevServer.version = "4.9.2" } - js("frontend", IR) { + rootProject.extensions.configure { + resolution("http-proxy-middleware", "^2.0.6") + } +} + +kotlin { + js(IR) { +// browser() +// binaries.executable() +// } +// +// js("frontend", IR) { // nodejs() browser { runTask { @@ -35,13 +67,20 @@ kotlin { open = false, port = 3000, proxy = mutableMapOf( - "/kv/*" to "http://localhost:8080", - "/kvws/*" to mapOf( - "target" to "ws://localhost:8080", + "/tiles" to mapOf( + "target" to "http://localhost:12080", + "secure" to false, + "changeOrigin" to true, + ), + "/ws/foo" to mapOf( + "target" to "ws://localhost:12080", +// "secure" to false, "ws" to true, - ) +// "changeOrigin" to true, + ), ), - static = mutableListOf("$buildDir/processedResources/frontend/main") +// static = mutableListOf("$buildDir/processedResources/frontend/main") + static = mutableListOf("$buildDir/processedResources/js/main") ) } webpackTask { @@ -54,11 +93,14 @@ kotlin { } } binaries.executable() +// compilations["main"].packageJson { +// this.devDependencies["webpack-dev-server"] = "4.9.1" +// } } - jvm("backend") { - - } +// jvm("backend") { +// +// } sourceSets { @@ -69,7 +111,8 @@ kotlin { } } - val commonMain by getting { +// val commonMain by getting { + main { dependencies { implementation(dependencies.platform(libs.kotlinx.coroutines.bom)) implementation(dependencies.platform(libs.kotlin.jsWrappers.bom)) @@ -79,13 +122,13 @@ kotlin { implementation(projects.modules.eventsSchema) implementation(libs.kotlinx.html) - } - } +// } +// } - val jsMain by getting { - dependsOn(commonMain) - - dependencies { +// val jsMain by getting { +// dependsOn(commonMain) +// +// dependencies { implementation(projects.modules.eventsSchema) @@ -108,36 +151,52 @@ kotlin { implementation(libs.kotlinx.coroutines.core) + implementation(devNpm("http-proxy-middleware", "^2.0.6")) + implementation(dependencies.platform(devNpm("http-proxy-middleware", "^2.0.6"))) + // implementation(dependencies.platform(npm("follow-redirects", "^1.14.8"))) // implementation(dependencies.platform(npm("nanoid", "^3.1.31"))) // implementation(dependencies.platform(npm("minimist", "^1.2.6"))) // implementation(dependencies.platform(npm("async", "^2.6.4"))) // implementation(dependencies.platform(npm("node-forge", "^1.3.0"))) +// implementation(dependencies.platform(npm("socket.io", "^4.5.1"))) +// implementation(dependencies.platform(devNpm("http-proxy-middleware", "^3.0.0-beta.0"))) +// implementation(dependencies.platform(devNpm("webpack-dev-server", "4.9.1"))) } // val webDir = file("src/main/web") resources.srcDir("src/main/web") - resources.srcDir("src/main/resources") - - kotlin.srcDir("src/main/kotlin") - } - - val frontendMain by getting { - dependsOn(jsMain) -// resources.srcDir("src/main/web") // resources.srcDir("src/main/resources") + +// kotlin.srcDir("src/main/kotlin") } - val jsTest by getting { +// val frontendMain by getting { +// dependsOn(jsMain) +//// resources.srcDir("src/main/web") +//// resources.srcDir("src/main/resources") +// } +// +// val jsTest by getting { +// dependencies { +// implementation(kotlin("test-js")) +// +// kvision("kvision-testutils") +// +//// implementation(npm("karma", "^6.3.16")) +// } +// +// kotlin.srcDir("src/test/kotlin") +// } + + test { dependencies { implementation(kotlin("test-js")) kvision("kvision-testutils") -// implementation(npm("karma", "^6.3.16")) + // implementation(npm("karma", "^6.3.16")) } - - kotlin.srcDir("src/test/kotlin") } } } @@ -152,17 +211,42 @@ fun KotlinDependencyHandler.kvision( implementation("io.kvision:$module:${version.get()}", configure) } -// https://youtrack.jetbrains.com/issue/KT-42420 +////// https://youtrack.jetbrains.com/issue/KT-42420 //afterEvaluate { // yarn { -// resolution("mocha", "9.2.2") -// resolution("follow-redirects", "1.14.8") -// resolution("nanoid", "3.1.31") -// resolution("minimist", "1.2.6") -// resolution("async", "2.6.4") -// resolution("node-forge", "1.3.0") +//// resolution("mocha", "9.2.2") +//// resolution("follow-redirects", "1.14.8") +//// resolution("nanoid", "3.1.31") +//// resolution("minimist", "1.2.6") +//// resolution("async", "2.6.4") +//// resolution("node-forge", "1.3.0") +//// resolution("socket.io", "^4.5.1") +//// resolution("http-proxy-middleware", "^3.0.0-beta.0") +// resolution("webpack-dev-server", "4.9.1") // } //} -evaluationDependsOn(rootProject.path) -evaluationDependsOn(projects.modules.eventsSchema.dependencyProject.path) + +//evaluationDependsOn(rootProject.path) +//evaluationDependsOn(projects.modules.eventsSchema.dependencyProject.path) + + +// stop warnings when building +tasks.configureEach { + when (name) { + "productionExecutableCompileSync" -> + dependsOn(taskProvider("compileProductionExecutableKotlinJs")) + "testTestDevelopmentExecutableCompileSync" -> + dependsOn(taskProvider("compileTestDevelopmentExecutableKotlinJs")) +// "jsTestTestDevelopmentExecutableCompileSync" -> +// dependsOn(taskProvider("compileTestDevelopmentExecutableKotlinFrontend")) +// "jsProductionExecutableCompileSync" -> +// dependsOn(taskProvider("compileProductionExecutableKotlinFrontend")) + "browserDevelopmentRun" -> + dependsOn(taskProvider("browserDevelopmentWebpack")) + "browserDevelopmentWebpack" -> + dependsOn(taskProvider("assemble")) + "browserProductionWebpack" -> + dependsOn(taskProvider("developmentExecutableCompileSync")) + } +} diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/App.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/App.kt index f1de35a..6d09996 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/App.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/App.kt @@ -38,8 +38,7 @@ class App( wsService: WebsocketService, ) : Application(), CoroutineScope { - override val coroutineContext: CoroutineContext = - CoroutineName("WebMapApp") + Job(rootJob) + override val coroutineContext: CoroutineContext = CoroutineName("WebMapApp") private var appState: MutableMap = mutableMapOf() @@ -62,7 +61,7 @@ class App( override fun start(state: Map) { this.appState = state.toMutableMap() - val root = root("kvapp") { + root("kvapp") { div("Kafkatorio Web Map") add(kvMaps) } @@ -74,18 +73,16 @@ class App( it.redraw() } }) - -// this.asDynamic()._fadeAnimated = false } - val doc = root.getElement()?.ownerDocument - val window = doc!!.defaultView!! - wsService.packetsFlow .filterIsInstance() .onEach { println("[packetsFlow] triggering tile refresh ${it.filename.value}") - gameState.map.refreshUpdatedTilePng(doc, window, it.filename) + gameState.map.refreshUpdatedTilePng( +// doc, window, + it.filename + ) }.launchIn(this) } @@ -104,8 +101,8 @@ fun main() { { val reduxStore = createFactorioReduxStore() val wsService = WebsocketService( - "ws://localhost:9073/ws", - reduxStore, +// "ws://localhost:9073/ws", // TODO load from config + reduxStore = reduxStore, ) App( reduxStore = reduxStore, diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/ApplicationProperties.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/ApplicationProperties.kt new file mode 100644 index 0000000..38d4a7e --- /dev/null +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/ApplicationProperties.kt @@ -0,0 +1,12 @@ +package dev.adamko.kafkatorio.webmap + +import kotlinx.browser.window + +object Props { + @Suppress("HttpUrlsUsage") + val websocketServerUrl: String + get() = window.location.href + .replace("http://", "ws://") + .replace("https://", "wss://") + + "ws/foo" +} diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/externals/leaflet.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/externals/leaflet.kt new file mode 100644 index 0000000..d7a3195 --- /dev/null +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/externals/leaflet.kt @@ -0,0 +1,33 @@ +package dev.adamko.kafkatorio.webmap.externals + +import io.kvision.maps.externals.leaflet.DoneCallback +import io.kvision.maps.externals.leaflet.geo.LatLng +import io.kvision.maps.externals.leaflet.layer.tile.TileLayer +import io.kvision.maps.externals.leaflet.layer.vector.Polyline +import org.w3c.dom.HTMLElement + + +inline var TileLayer.tileOnLoad: TileOnLoadFn? + get() = asDynamic()._tileOnLoad as TileOnLoadFn + set(value) { + asDynamic()._tileOnLoad = value + } + + +typealias TileOnLoadFn = (done: DoneCallback, tile: HTMLElement) -> Unit + + +sealed interface PolylinePathPoints { + value class D1(val points: Array) : PolylinePathPoints + value class D2(val points: Array>) : PolylinePathPoints + value class D3(val points: Array>>) : PolylinePathPoints +} + +inline fun Polyline<*>.getLatLngs2(): PolylinePathPoints? { + return when (val points = this.getLatLngs()) { + is Array -> PolylinePathPoints.D1(points as Array) + is Array> -> PolylinePathPoints.D2(points as Array>) + is Array>> -> PolylinePathPoints.D3(points as Array>>) + else -> null + } +} diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/map.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/map.kt index a0e82f1..a124582 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/map.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/map.kt @@ -1,6 +1,8 @@ package dev.adamko.kafkatorio.webmap import dev.adamko.kafkatorio.schema.common.TilePngFilename +import dev.adamko.kafkatorio.webmap.externals.TileOnLoadFn +import dev.adamko.kafkatorio.webmap.externals.tileOnLoad import io.kvision.maps.Maps import io.kvision.maps.Maps.Companion.L import io.kvision.maps.externals.leaflet.DoneCallback @@ -11,15 +13,15 @@ import io.kvision.maps.externals.leaflet.geo.CRS import io.kvision.maps.externals.leaflet.geo.LatLng import io.kvision.maps.externals.leaflet.layer.LayerGroup import io.kvision.maps.externals.leaflet.layer.tile.TileLayer -import io.kvision.utils.obj import io.kvision.utils.px +import kotlinx.browser.document +import kotlinx.browser.window import kotlinx.coroutines.await import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch -import org.w3c.dom.Document +import kotlinx.js.jso import org.w3c.dom.HTMLElement import org.w3c.dom.Image -import org.w3c.dom.Window import org.w3c.dom.asList import org.w3c.fetch.NO_CORS import org.w3c.fetch.RELOAD @@ -29,7 +31,7 @@ import org.w3c.fetch.RequestMode class FactorioMap( - private val tileUrlTemplate: String = """http://localhost:9073/tiles/s1/z{z}/x{x}/y{y}.png""", + private val tileUrlTemplate: String = """/tiles/s1/z{z}/x{x}/y{y}.png""", ) { val kvMap: Maps = Maps { @@ -38,21 +40,24 @@ class FactorioMap( margin = 10.px } - val factorioTerrainLayer: TileLayer<*> = buildFactorioTerrainLayer() + private val factorioTerrainLayer: TileLayer<*> = buildFactorioTerrainLayer() val playerIconsLayer: LayerGroup = LayerGroup() init { - val baseLayers: Control.LayersObject = obj {} - baseLayers["Terrain"] = factorioTerrainLayer - val overlays: Control.LayersObject = obj {} - overlays["Players"] = playerIconsLayer + val baseLayers: Control.LayersObject = jso { + set("Terrain", factorioTerrainLayer) + } + + val overlays: Control.LayersObject = jso { + set("Players", playerIconsLayer) + } val layersControl = Layers( baseLayers = baseLayers, overlays = overlays, - options = obj { + options = jso { sortLayers = true } ) @@ -78,21 +83,19 @@ class FactorioMap( suspend fun refreshUpdatedTilePng( - document: Document, - window: Window, tilePngFilename: TilePngFilename, ): Unit = coroutineScope { - launch { - document - .querySelectorAll("img.leaflet-tile-loaded") - .asList() - .filterIsInstance() - .filter { img -> - tilePngFilename.value in img.src - } - .forEach { img -> - val imgSrc = img.src //.substringBeforeLast('?') + document + .querySelectorAll("img.leaflet-tile-loaded") + .asList() + .filterIsInstance() + .filter { img -> + tilePngFilename.value in img.src + } + .forEach { img -> + val imgSrc = img.src + launch { window.fetch( imgSrc, RequestInit( @@ -100,24 +103,14 @@ class FactorioMap( mode = RequestMode.NO_CORS, ) ).then { - println("[refreshUpdatedTilePng] updating $imgSrc") - img.setAttribute(DYNAMIC_RELOAD_ATT, "true") - img.src = imgSrc + window.requestAnimationFrame { + println("[refreshUpdatedTilePng] updating $imgSrc") + img.setAttribute(DYNAMIC_RELOAD_ATT, "true") + img.src = imgSrc + } }.await() } - } - -//// println("fetching $imgSrc") -// val newImg = Image() -// newImg.onload = { -//// window.requestAnimationFrame { -//// println("image loaded ${newImg!!.src}") -// img.setAttribute(DYNAMIC_RELOAD_ATT, "true") -// img.src = newImg.src -// Unit -//// } -// } -// newImg.src = imgSrc //+ "?t=${currentTimeMillis()}" + } } private fun buildFactorioTerrainLayer(): TileLayer { @@ -132,7 +125,7 @@ class FactorioMap( minZoom = -2 maxZoom = 6 - maxNativeZoom = 3 + maxNativeZoom = -1 minNativeZoom = -1 noWrap = true @@ -140,13 +133,14 @@ class FactorioMap( updateWhenZooming = true } - val currentTileOnLoad: (DoneCallback, HTMLElement) -> Unit = - baseTileLayer.asDynamic()._tileOnLoad as (DoneCallback, HTMLElement) -> Unit - baseTileLayer.asDynamic()._tileOnLoad = { done: DoneCallback, tile: HTMLElement -> - if (tile.hasAttribute(DYNAMIC_RELOAD_ATT)) { + val currentTileOnLoad: TileOnLoadFn? = baseTileLayer.tileOnLoad + + if (currentTileOnLoad != null) { + baseTileLayer.tileOnLoad = { done: DoneCallback, tile: HTMLElement -> + if (!tile.hasAttribute(DYNAMIC_RELOAD_ATT)) { + currentTileOnLoad(done, tile) + } tile.removeAttribute(DYNAMIC_RELOAD_ATT) - } else { - currentTileOnLoad(done, tile) } } diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/state/playerState.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/state/playerState.kt index 1488696..7cd71ab 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/state/playerState.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/state/playerState.kt @@ -10,9 +10,8 @@ import dev.adamko.kafkatorio.schema.packets.PlayerUpdate import dev.adamko.kafkatorio.webmap.latLng import io.kvision.maps.Maps import io.kvision.maps.externals.leaflet.geo.LatLng -import io.kvision.maps.externals.leaflet.layer.overlay.Tooltip import io.kvision.maps.externals.leaflet.layer.vector.CircleMarker -import io.kvision.utils.obj +import kotlinx.js.jso fun handlePlayerUpdate( @@ -100,7 +99,7 @@ data class PlayerState( className = "player-marker" // interactive = false }.also { circle -> - circle.bindTooltip("", obj { + circle.bindTooltip("", jso { // interactive = false }) } diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/websockets.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/websockets.kt index fc9ca41..7c0ca27 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/websockets.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/websockets.kt @@ -22,10 +22,11 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import org.w3c.dom.MessageEvent import org.w3c.dom.WebSocket +import org.w3c.dom.events.Event class WebsocketService( - wsUrl: String, + wsUrl: String = Props.websocketServerUrl, private val reduxStore: ReduxStore, ) : CoroutineScope { @@ -33,6 +34,7 @@ class WebsocketService( CoroutineName("WebsocketService") + Job(rootJob) private val ws = WebSocket(wsUrl) +// private val ws = WebSocket("ws://localhost:12080/ws/foo") private val _packetsFlow = MutableSharedFlow() val packetsFlow: SharedFlow @@ -40,6 +42,9 @@ class WebsocketService( init { ws.onmessage = ::handleMessageEvent + ws.onerror = ::handleError + + println("[WebsocketService] init ${ws.url}, ${ws.protocol}") } private fun handleMessageEvent(msg: MessageEvent) { @@ -52,35 +57,46 @@ class WebsocketService( && data.endsWith("}") ) { - println(data.replace('\n', ' ')) + println("[WebsocketService.handleMessageEvent] ${data.replace('\n', ' ')}") - val packet = jsonMapper.decodeFromString(EventServerPacket.serializer(), data) - - launch { - _packetsFlow.emit(packet) + val packet: EventServerPacket? = runCatching { + jsonMapper.decodeFromString(EventServerPacket.serializer(), data) + }.getOrElse { + null } - when (packet) { - is EventServerPacket.ChunkTileSaved -> {} + if (packet != null) { - is EventServerPacket.Kafkatorio -> - when (val packetData = packet.packet.data) { - is PlayerUpdate -> reduxStore.dispatch( - FactorioUpdate.Player(packet.packet.tick, packetData) - ) - is ConfigurationUpdate, - is ConsoleChatUpdate, - is ConsoleCommandUpdate, - is PrototypesUpdate, - is SurfaceUpdate, - is EntityUpdate, - is MapChunkUpdate -> { - // to be continued... + launch { _packetsFlow.emit(packet) } + + when (packet) { + is EventServerPacket.ChunkTileSaved -> {} + + is EventServerPacket.Kafkatorio -> + when (val packetData = packet.packet.data) { + is PlayerUpdate -> reduxStore.dispatch( + FactorioUpdate.Player(packet.packet.tick, packetData) + ) + is ConfigurationUpdate, + is ConsoleChatUpdate, + is ConsoleCommandUpdate, + is PrototypesUpdate, + is SurfaceUpdate, + is EntityUpdate, + is MapChunkUpdate -> { + // to be continued... + } } - } + } + } else { + println("[WebsocketService.handleMessageEvent] unknown json message ${msg.data}") } } else { - println(data) + println("[WebsocketService.handleMessageEvent] non-json message ${msg.data}") } } + + private fun handleError(error: Event) { + println("ws error: ${JSON.stringify(error)}") + } } diff --git a/modules/web-map/src/main/resources/css/kafkatorio.css b/modules/web-map/src/main/resources/css/kafkatorio.css index 54b756e..db87d4c 100644 --- a/modules/web-map/src/main/resources/css/kafkatorio.css +++ b/modules/web-map/src/main/resources/css/kafkatorio.css @@ -1,8 +1,10 @@ -/* https://github.com/Leaflet/Leaflet/issues/3575 */ .leaflet-tile { - width: 257px !important; - height: 257px !important; - box-shadow: 0 0 1px rgba(0, 0, 0, 0.05); + /* https://github.com/Leaflet/Leaflet/issues/3575 */ + /*width: 257px !important;*/ + /*height: 257px !important;*/ + /*box-shadow: 0 0 1px rgba(0, 0, 0, 0.05);*/ + + image-rendering: pixelated; } html { diff --git a/modules/web-map/webpack.config.d/webpack.js b/modules/web-map/webpack.config.d/webpack.js index 4ce4481..0491c5c 100644 --- a/modules/web-map/webpack.config.d/webpack.js +++ b/modules/web-map/webpack.config.d/webpack.js @@ -1,15 +1,55 @@ config.resolve.modules.push("../../processedResources/js/main"); if (config.devServer) { - config.devServer.hot = true; - config.devtool = 'eval-cheap-source-map'; + config.devServer.hot = true; + config.devtool = 'eval-cheap-source-map'; + config.devServer.host = "0.0.0.0" } else { - config.devtool = undefined; + config.devtool = undefined; } // disable bundle size warning config.performance = { - assetFilter: function (assetFilename) { - return !assetFilename.endsWith('.js'); - }, + assetFilter: function (assetFilename) { + return !assetFilename.endsWith('.js'); + }, }; + + +// // dev server +// 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", +// ], +// };