Skip to content

Commit

Permalink
basic web support (#204)
Browse files Browse the repository at this point in the history
  • Loading branch information
sargunv authored Dec 31, 2024
1 parent 723e692 commit e91613b
Show file tree
Hide file tree
Showing 52 changed files with 1,238 additions and 93 deletions.
8 changes: 8 additions & 0 deletions .fleet/run.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
":demo-app:run"
]
},
{
"name": "Js-App",
"type": "gradle",
"workingDir": "$PROJECT_DIR$",
"tasks": [
":demo-app:jsRun"
]
},
{
"name": "Reformat",
"type": "gradle",
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ Android and iOS support is implemented with
[MapLibre Native](https://github.com/maplibre/maplibre-native). A broad set of
features are supported.

Desktop support is implemented with
[MapLibre GL JS](https://github.com/maplibre/maplibre-gl-js) and
[KCEF](https://github.com/DatL4g/KCEF). It's currently **very** limited and
experimental.
Web support is implemented with
[MapLibre GL JS](https://github.com/maplibre/maplibre-gl-js).

Desktop support is also implemented with MapLibre GL JS and
[KCEF](https://github.com/DatL4g/KCEF) for now, though we'd like to switch to
MapLibre Native.

Web is not yet supported.
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ dependencies {
dokka(project(":lib:maplibre-compose:"))
dokka(project(":lib:maplibre-compose-expressions:"))
dokka(project(":lib:maplibre-compose-material3:"))
dokka(project(":lib:kotlin-maplibre-js"))
dokka(project(":lib:compose-html-interop:"))
}

spotless {
Expand Down
11 changes: 11 additions & 0 deletions demo-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ kotlin {
iosSimulatorArm64()
iosX64()
jvm("desktop")
js(IR) {
browser { commonWebpackConfig { outputFileName = "app.js" } }
binaries.executable()
}

cocoapods {
summary = "MapLibre Compose demo app"
Expand Down Expand Up @@ -86,6 +90,7 @@ kotlin {

androidMain.dependencies {
implementation(libs.androidx.activity.compose)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.ktor.client.okhttp)
}

Expand All @@ -94,6 +99,12 @@ kotlin {
desktopMain.dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.ktor.client.okhttp)
}

jsMain.dependencies {
implementation(compose.html.core)
implementation(libs.ktor.client.js)
}

commonTest.dependencies {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,10 @@ internal class FrameRateState(private val spinner: String = "◐◓◑◒") {
}

@Composable expect fun getDefaultColorScheme(isDark: Boolean = false): ColorScheme

expect object Platform {
val supportsBlending: Boolean
val supportsFps: Boolean
val supportsCamera: Boolean
val supportsLayers: Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,10 @@ fun main() {
actual fun getDefaultColorScheme(isDark: Boolean): ColorScheme {
return if (isDark) darkColorScheme() else lightColorScheme()
}

actual object Platform {
actual val supportsBlending = false
actual val supportsFps = false
actual val supportsCamera = false
actual val supportsLayers = false
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,10 @@ fun MainViewController() = ComposeUIViewController { DemoApp() }
actual fun getDefaultColorScheme(isDark: Boolean): ColorScheme {
return if (isDark) darkColorScheme() else lightColorScheme()
}

actual object Platform {
actual val supportsBlending = true
actual val supportsFps = true
actual val supportsCamera = true
actual val supportsLayers = true
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dev.sargunv.maplibrecompose.demoapp

import androidx.compose.material3.ColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.window.ComposeViewport
import kotlinx.browser.document
import org.jetbrains.skiko.wasm.onWasmReady

@OptIn(ExperimentalComposeUiApi::class)
fun main() {
onWasmReady { ComposeViewport(document.body!!) { DemoApp() } }
}

@Composable
actual fun getDefaultColorScheme(isDark: Boolean): ColorScheme {
return if (isDark) darkColorScheme() else lightColorScheme()
}

actual object Platform {
actual val supportsBlending = false
actual val supportsFps = false
actual val supportsCamera = false
actual val supportsLayers = false
}
23 changes: 23 additions & 0 deletions demo-app/src/jsMain/resources/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!doctype html>
<!--suppress HtmlUnknownTarget -->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MapLibre Compose demo app</title>
<link rel='stylesheet' href='https://unpkg.com/[email protected]/dist/maplibre-gl.css'/>
<style>
html,
body {
width: 100vw;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
<script src="skiko.js"></script>
</head>
<body></body>
<script src="app.js"></script>
</html>
7 changes: 7 additions & 0 deletions demo-app/src/jsMain/resources/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
48 changes: 25 additions & 23 deletions docs/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,31 @@ to express an interactive map API in Compose.

Android and iOS support is implemented with [MapLibre Native][maplibre-native].

Desktop support is implemented with [MapLibre GL JS][maplibre-js] and
[KCEF][kcef].

| Feature | Android | iOS | Desktop | Web |
| ------------------------------------------------- | ------------------ | ------------------ | ------------------ | --- |
| Render a map | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
| Overlay Compose UI over the map | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Load Compose resource URIs | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
| Configure ornaments (compass, logo, attribution) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
| Configure gestures (pan, zoom, rotate, pitch) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
| Respond to a map click or long click | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Query visible map features | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Get, set, and animate the camera position | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Convert between screen and geographic coordinates | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Get the currently visible region and bounding box | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Insert, remove, and replace layers | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Configure layers with expressions | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Add data sources by URI or GeoJSON | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Add images to the style | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Add annotations | :x: | :x: | :x: | :x: |
| Snapshot the map as an image | :x: | :x: | :x: | :x: |
| Configure the offline cache | :x: | :x: | :x: | :x: |
| Configure layer transitions | :x: | :x: | :x: | :x: |
Web support is implemented with [MapLibre GL JS][maplibre-js].

Desktop support is also implemented with MapLibre GL JS in [KCEF][kcef] for now,
though we'd like to switch to MapLibre Native.

| Feature | Android | iOS | Desktop | Web |
| ------------------------------------------------- | ------------------ | ------------------ | ------------------ | ------------------ |
| Render a map | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Overlay Compose UI over the map | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Load Compose resource URIs | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
| Configure ornaments (compass, logo, attribution) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
| Configure gestures (pan, zoom, rotate, pitch) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: |
| Respond to a map click or long click | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Query visible map features | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Get, set, and animate the camera position | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Convert between screen and geographic coordinates | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Get the currently visible region and bounding box | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Insert, remove, and replace layers | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Configure layers with expressions | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Add data sources by URI or GeoJSON | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Add images to the style | :white_check_mark: | :white_check_mark: | :x: | :x: |
| Add annotations | :x: | :x: | :x: | :x: |
| Snapshot the map as an image | :x: | :x: | :x: | :x: |
| Configure the offline cache | :x: | :x: | :x: | :x: |
| Configure layer transitions | :x: | :x: | :x: | :x: |

[compose]: https://www.jetbrains.com/compose-multiplatform/
[maplibre]: https://maplibre.org/
Expand Down
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ androidTargetSdk=35
iosDeploymentTarget=12.0
kotlin.code.style=official
kotlin.daemon.jvmargs=-Xmx2048M
kotlin.incremental.wasm=true
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
# Blocker: https://github.com/diffplug/spotless/issues/2347
Expand Down
6 changes: 5 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
androidx-activity = "1.9.3"
androidx-composeUi = "1.7.6"
androidx-navigation = "2.8.0-alpha11"
webview = "1.9.40"
kermit = "2.0.5"
kotlinx-browser = "0.3"
kotlinx-coroutines = "1.9.0"
ktor = "3.0.3"
maplibre-android-sdk = "11.7.1"
Expand All @@ -13,6 +13,7 @@ maplibre-js = "4.7.1"
spatialk = "0.3.0"
webpack-html = "5.6.3"
webpack-htmlInlineScript = "3.2.1"
webview = "1.9.40"

gradle-android = "8.7.3"
gradle-compose = "1.7.3"
Expand All @@ -29,11 +30,14 @@ tool-prettier = "3.4.2"
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
androidx-composeUi-testManifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-composeUi" }
androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "androidx-navigation" }
kotlinx-browser = { module = "org.jetbrains.kotlinx:kotlinx-browser", version.ref = "kotlinx-browser" }
webview = { module = "io.github.kevinnzou:compose-webview-multiplatform", version.ref = "webview" }
kermit = { group = "co.touchlab", name = "kermit", version.ref = "kermit" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-contentNegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-serialization-kotlinxJson = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
Expand Down
32 changes: 16 additions & 16 deletions iosApp/iosApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
B92378962B6B1156000C7307 /* Frameworks */,
7555FF79242A565900829871 /* Resources */,
FB29095E4E4AE33FC6A4F030 /* [CP] Embed Pods Frameworks */,
E8A0EF8E3B4DDB502C870BB0 /* [CP] Copy Pods Resources */,
1DBF57571B44936782348BCD /* [CP] Copy Pods Resources */,
);
buildRules = (
);
Expand Down Expand Up @@ -170,43 +170,43 @@
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
81EA16B41DBA59C4915ABE3F /* [CP] Check Pods Manifest.lock */ = {
1DBF57571B44936782348BCD /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt",
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n";
showEnvVarsInLog = 0;
};
E8A0EF8E3B4DDB502C870BB0 /* [CP] Copy Pods Resources */ = {
81EA16B41DBA59C4915ABE3F /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
FB29095E4E4AE33FC6A4F030 /* [CP] Embed Pods Frameworks */ = {
Expand Down
7 changes: 6 additions & 1 deletion kotlin-js-store/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"

"@js-joda/[email protected]":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273"
integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg==

"@leichtgewicht/ip-codec@^2.0.1":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1"
Expand Down Expand Up @@ -3288,7 +3293,7 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==

ws@^8.13.0:
ws@8.18.0, ws@^8.13.0:
version "8.18.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
Expand Down
3 changes: 3 additions & 0 deletions lib/compose-html-interop/MODULE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Module compose-html-interop

Include an HTML element in a Compose Web UI.
Loading

0 comments on commit e91613b

Please sign in to comment.