├── gradle.properties ├── .gitignore ├── libs ├── embedded.jar └── androidx │ └── compose │ └── runtime │ └── runtime-js │ └── 1.0.0-beta02 │ ├── runtime-js-1.0.0-beta02.klib │ ├── runtime-js-1.0.0-beta02-sources.jar │ ├── runtime-js-1.0.0-beta02.pom │ └── runtime-js-1.0.0-beta02.module ├── settings.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ ├── resources │ └── index.html │ └── kotlin │ ├── main.kt │ ├── App.kt │ └── Router1.kt ├── prelude ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── compose │ └── web │ ├── Properties.kt │ ├── Events.kt │ ├── internal │ ├── JsApplier.kt │ ├── NodeWrapper.kt │ └── GlobalSnapshotManager.kt │ ├── Base.kt │ ├── renderComposable.kt │ ├── HtmlModifiers.kt │ ├── Tags.kt │ ├── component │ └── Input.kt │ └── Modifier.kt ├── README.md ├── gradlew.bat └── gradlew /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .gradle/ 3 | .idea/ 4 | *.iml 5 | -------------------------------------------------------------------------------- /libs/embedded.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShikaSD/compose-testbed/HEAD/libs/embedded.jar -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "compose-browser-demo" 2 | 3 | include(":prelude") 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShikaSD/compose-testbed/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /libs/androidx/compose/runtime/runtime-js/1.0.0-beta02/runtime-js-1.0.0-beta02.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShikaSD/compose-testbed/HEAD/libs/androidx/compose/runtime/runtime-js/1.0.0-beta02/runtime-js-1.0.0-beta02.klib -------------------------------------------------------------------------------- /libs/androidx/compose/runtime/runtime-js/1.0.0-beta02/runtime-js-1.0.0-beta02-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShikaSD/compose-testbed/HEAD/libs/androidx/compose/runtime/runtime-js/1.0.0-beta02/runtime-js-1.0.0-beta02-sources.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-rc-4-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | compose-browser-demo 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /prelude/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("js") 3 | } 4 | 5 | kotlin { 6 | js(IR) { 7 | browser() 8 | } 9 | } 10 | 11 | configureComposeCompiler() 12 | 13 | dependencies { 14 | implementation(composeRuntimeJs()) 15 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1") 16 | } 17 | -------------------------------------------------------------------------------- /prelude/src/main/kotlin/compose/web/Properties.kt: -------------------------------------------------------------------------------- 1 | package compose.web 2 | 3 | import org.w3c.dom.HTMLCanvasElement 4 | import org.w3c.dom.HTMLInputElement 5 | 6 | fun Modifier.value(value: String): Modifier = 7 | property { this.value = value } 8 | 9 | fun Modifier.width(value: Int): Modifier = 10 | property { this.width = value } 11 | 12 | fun Modifier.height(value: Int): Modifier = 13 | property { this.height = value } 14 | -------------------------------------------------------------------------------- /prelude/src/main/kotlin/compose/web/Events.kt: -------------------------------------------------------------------------------- 1 | package compose.web 2 | 3 | import org.w3c.dom.events.Event 4 | import org.w3c.dom.events.EventListener 5 | import org.w3c.dom.events.InputEvent 6 | import org.w3c.dom.events.KeyboardEvent 7 | 8 | fun Modifier.onClick(action: (Event) -> Unit): Modifier = 9 | event("click", EventListener(action)) 10 | 11 | fun Modifier.onInput(action: (InputEvent) -> Unit): Modifier = 12 | event("input", EventListener { action(it as InputEvent) }) 13 | 14 | fun Modifier.onKeyUp(action: (KeyboardEvent) -> Unit): Modifier = 15 | event("keyup", EventListener { action(it as KeyboardEvent) }) 16 | -------------------------------------------------------------------------------- /prelude/src/main/kotlin/compose/web/internal/JsApplier.kt: -------------------------------------------------------------------------------- 1 | package compose.web.internal 2 | 3 | import androidx.compose.runtime.AbstractApplier 4 | import org.w3c.dom.Node 5 | 6 | internal class JsApplier(root: Node) : AbstractApplier(NodeWrapper(root)) { 7 | override fun onClear() { 8 | // no-op 9 | } 10 | 11 | override fun insertBottomUp(index: Int, instance: NodeWrapper) { 12 | current.insert(index, instance) 13 | } 14 | 15 | override fun insertTopDown(index: Int, instance: NodeWrapper) {} 16 | 17 | override fun move(from: Int, to: Int, count: Int) { 18 | current.move(from, to, count) 19 | } 20 | 21 | override fun remove(index: Int, count: Int) { 22 | current.remove(index, count) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /prelude/src/main/kotlin/compose/web/Base.kt: -------------------------------------------------------------------------------- 1 | package compose.web 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.ComposeNode 5 | import compose.web.internal.JsApplier 6 | import compose.web.internal.NodeWrapper 7 | import kotlinx.browser.document 8 | import org.w3c.dom.Text 9 | 10 | @Composable 11 | fun tag(modifier: Modifier, tagName: String, content: @Composable () -> Unit) { 12 | ComposeNode( 13 | factory = { NodeWrapper(tagName) }, 14 | update = { 15 | set(modifier) { this.modifier = modifier } 16 | }, 17 | content = content 18 | ) 19 | } 20 | 21 | @Composable 22 | fun text(value: String) { 23 | ComposeNode( 24 | factory = { NodeWrapper(document.createTextNode("")) }, 25 | update = { 26 | set(value) { value -> 27 | (realNode as Text).data = value 28 | } 29 | }, 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /prelude/src/main/kotlin/compose/web/renderComposable.kt: -------------------------------------------------------------------------------- 1 | package compose.web 2 | 3 | import androidx.compose.runtime.* 4 | import compose.web.internal.GlobalSnapshotManager 5 | import compose.web.internal.JsApplier 6 | import kotlinx.coroutines.CoroutineScope 7 | import kotlinx.coroutines.CoroutineStart 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.launch 10 | import org.w3c.dom.HTMLElement 11 | 12 | @OptIn(ExperimentalComposeApi::class) 13 | fun renderComposable(root: HTMLElement, content: @Composable () -> Unit): Composition { 14 | GlobalSnapshotManager.ensureStarted() 15 | 16 | val context = DefaultMonotonicFrameClock + Dispatchers.Main 17 | val recomposer = Recomposer(context) 18 | 19 | CoroutineScope(context).launch(start = CoroutineStart.UNDISPATCHED) { 20 | recomposer.runRecomposeAndApplyChanges() 21 | } 22 | 23 | val composition = Composition( 24 | applier = JsApplier(root), 25 | parent = recomposer 26 | ) 27 | composition.setContent(content) 28 | 29 | return composition 30 | } 31 | -------------------------------------------------------------------------------- /src/main/kotlin/main.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalComposeApi::class, ExperimentalTime::class) 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.ExperimentalComposeApi 5 | import compose.web.Modifier 6 | import compose.web.renderComposable 7 | import compose.web.tag 8 | import compose.web.text 9 | import kotlinx.browser.document 10 | import kotlin.time.Duration 11 | import kotlin.time.ExperimentalTime 12 | import kotlin.time.measureTime 13 | 14 | fun main() { 15 | renderComposable(document.body!!) { 16 | App() 17 | } 18 | // val duration = Benchmark(content = { 19 | // tag(Modifier, "div") { 20 | // for (i in 0 until 1000) { 21 | // tag(Modifier, "div") { 22 | // text("$it") 23 | // } 24 | // } 25 | // } 26 | // }) 27 | // 28 | // println(duration) 29 | } 30 | 31 | @Composable 32 | fun TestRange(to: Int) { 33 | for (i in 0 until to) { 34 | tag(Modifier, "div") { 35 | text("$i") 36 | } 37 | } 38 | } 39 | 40 | @OptIn(ExperimentalTime::class) 41 | fun Benchmark(content: @Composable (round: Int) -> Unit, rounds: Int = 100): Duration { 42 | val composition = renderComposable(document.body!!) {} 43 | 44 | var duration = Duration.ZERO 45 | for (i in 0 until rounds) { 46 | duration += measureTime { 47 | composition.setContent { content(i) } 48 | composition.setContent { } 49 | } 50 | } 51 | return duration / rounds 52 | } 53 | -------------------------------------------------------------------------------- /prelude/src/main/kotlin/compose/web/HtmlModifiers.kt: -------------------------------------------------------------------------------- 1 | package compose.web 2 | 3 | import org.w3c.dom.HTMLElement 4 | import org.w3c.dom.css.CSSStyleDeclaration 5 | import org.w3c.dom.events.EventListener 6 | 7 | /** 8 | * Adds an event listener for the target node which is applied on node recomposition 9 | */ 10 | internal class EventModifier(val eventName: String, val listener: EventListener) : Modifier.Element 11 | 12 | /** 13 | * Adds a css style configuration which would be applied when node is updated 14 | */ 15 | internal class CssModifier(val configure: CSSStyleDeclaration.() -> Unit) : Modifier.Element 16 | 17 | /** 18 | * Updates target node properties on recomposition 19 | * Properties are not cleared automatically (yet) when modifier is removed 20 | */ 21 | internal class PropertyModifier(val configure: HTMLElement.() -> Unit): Modifier.Element 22 | 23 | /** 24 | * Provides access to underlying HTML element 25 | */ 26 | internal class RefModifier(val configure: HTMLElement.() -> Unit): Modifier.Element 27 | 28 | fun Modifier.css(configure: CSSStyleDeclaration.() -> Unit): Modifier = 29 | this.then(CssModifier(configure)) 30 | 31 | fun Modifier.event(eventName: String, listener: EventListener) : Modifier = 32 | this.then(EventModifier(eventName, listener)) 33 | 34 | @Suppress("UNCHECKED_CAST") 35 | fun Modifier.property(configure: R.() -> Unit) : Modifier = 36 | this.then(PropertyModifier(configure as HTMLElement.() -> Unit)) 37 | 38 | fun Modifier.ref(block: (HTMLElement) -> Unit): Modifier = 39 | this.then(RefModifier(block)) 40 | -------------------------------------------------------------------------------- /src/main/kotlin/App.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.Composable 2 | import androidx.compose.runtime.getValue 3 | import androidx.compose.runtime.mutableStateOf 4 | import androidx.compose.runtime.remember 5 | import androidx.compose.runtime.setValue 6 | import compose.web.Modifier 7 | import compose.web.button 8 | import compose.web.canvas 9 | import compose.web.component.Checkbox 10 | import compose.web.component.CheckboxState 11 | import compose.web.component.InputState 12 | import compose.web.component.TextInput 13 | import compose.web.css 14 | import compose.web.h1 15 | import compose.web.h2 16 | import compose.web.onClick 17 | import compose.web.ref 18 | import compose.web.text 19 | import org.w3c.dom.HTMLElement 20 | 21 | @Composable 22 | fun App() { 23 | h1(Modifier.css { color = "red" }) { 24 | text("Hello, Compose/JS!") 25 | } 26 | 27 | var counter by remember { mutableStateOf(0) } 28 | 29 | h2 { 30 | text("Counter: ${counter}") 31 | } 32 | 33 | button(Modifier.onClick { counter++ }) { 34 | text("Increment!") 35 | } 36 | 37 | h1 { 38 | text("Input something below") 39 | } 40 | 41 | val inputState = remember { InputState("") } 42 | TextInput(state = inputState) 43 | 44 | val checkboxState = remember { CheckboxState(false) } 45 | 46 | Checkbox(state = checkboxState) 47 | 48 | text("input value: ${inputState.value}, checkbox checked: ${checkboxState.value}") 49 | 50 | var canvas: HTMLElement? by remember { mutableStateOf(null) } 51 | canvas(Modifier.ref { canvas = it }, width = 300, height = 150) 52 | } 53 | -------------------------------------------------------------------------------- /prelude/src/main/kotlin/compose/web/Tags.kt: -------------------------------------------------------------------------------- 1 | package compose.web 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | @Composable 6 | fun h1(modifier: Modifier = Modifier, content: @Composable () -> Unit) { 7 | tag(modifier, "h1", content = content) 8 | } 9 | 10 | @Composable 11 | fun h2(modifier: Modifier = Modifier, content: @Composable () -> Unit) { 12 | tag(modifier,"h2", content = content) 13 | } 14 | 15 | @Composable 16 | fun button(modifier: Modifier = Modifier, content: @Composable () -> Unit) { 17 | tag(modifier,"button", content = content) 18 | } 19 | 20 | @Composable 21 | fun input(modifier: Modifier = Modifier, content: @Composable () -> Unit = {}) { 22 | tag(modifier, "input", content = content) 23 | } 24 | 25 | @Composable 26 | fun canvas(modifier: Modifier = Modifier, width: Int, height: Int) { 27 | tag(modifier = modifier.width(width).height(height), tagName = "canvas", content = {}) 28 | } 29 | 30 | @Composable 31 | fun br() { 32 | tag(modifier = Modifier, tagName = "br", content = {}) 33 | } 34 | 35 | @Composable 36 | fun hr() { 37 | tag(modifier = Modifier, tagName = "hr", content = {}) 38 | } 39 | 40 | @Composable 41 | fun ul(modifier: Modifier = Modifier, content: @Composable () -> Unit) { 42 | tag(modifier = modifier, tagName = "ul", content) 43 | } 44 | 45 | @Composable 46 | fun li(modifier: Modifier = Modifier, content: @Composable () -> Unit) { 47 | tag(modifier = modifier, tagName = "li", content) 48 | } 49 | 50 | @Composable 51 | fun a(modifier: Modifier = Modifier, content: @Composable() () -> Unit) { 52 | tag(modifier = modifier, tagName = "a", content) 53 | } 54 | -------------------------------------------------------------------------------- /prelude/src/main/kotlin/compose/web/component/Input.kt: -------------------------------------------------------------------------------- 1 | package compose.web.component 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.MutableState 5 | import androidx.compose.runtime.mutableStateOf 6 | import compose.web.Modifier 7 | import compose.web.event 8 | import compose.web.onInput 9 | import compose.web.property 10 | import compose.web.tag 11 | import org.w3c.dom.HTMLInputElement 12 | import org.w3c.dom.events.EventListener 13 | 14 | class CheckboxState(initialValue: Boolean) : MutableState by mutableStateOf(initialValue) 15 | 16 | @Composable 17 | fun Checkbox(modifier: Modifier = Modifier, state: CheckboxState) { 18 | // TODO: tbh, setting properties through the modifier feels super inefficient 19 | tag( 20 | modifier = modifier 21 | .property { type = "checkbox" } 22 | .property { checked = state.value } 23 | .event("change", EventListener { 24 | state.value = (it.target as HTMLInputElement).checked 25 | }), 26 | tagName = "input", 27 | content = {} 28 | ) 29 | } 30 | 31 | class InputState(initialValue: String) : MutableState by mutableStateOf(initialValue) 32 | 33 | @Composable 34 | fun TextInput(modifier: Modifier = Modifier, state: InputState) { 35 | // TODO: tbh, setting properties through the modifier feels super inefficient 36 | tag( 37 | modifier = modifier 38 | .property { type = "text" } 39 | .property { value = state.value } 40 | .onInput { 41 | state.value = (it.target as HTMLInputElement).value 42 | }, 43 | tagName = "input", 44 | content = {} 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## If you are here, most probably you are looking for [official Compose for Web implementation](https://github.com/JetBrains/compose-jb/tree/master/tutorials/Web/Getting_Started). This repo is only used for my own testing purposes now :) 2 | 3 | This is a demo of Compose running in the browser using Kotlin/JS and original compiler. 4 | The API here is not representative of anything final (actually, almost non-existent), so feel free to play with it and come up with anything on your own. 5 | Compiler plugin is **NOT QUITE STABLE** yet, if you encounter any bugs/crashes, please report :). 6 | 7 | **NOTE:** this is not an "official" Compose/JS implementation from JB/Google, but just an example to play/experiment with. 8 | 9 | ## Where to look? 10 | 11 | - [`.`](./src/main/kotlin) - Playground 12 | - [`prelude`](./prelude/src/main/kotlin/compose/web) - Some basic HTML definitions 13 | - [`libs`](./libs/) - Prebuilt artifacts of runtime and compiler plugin 14 | 15 | Inside of prelude, see [`JsApplier`](./prelude/src/main/kotlin/compose/web/internal/JsApplier.kt) for an example of how DOM elements are created. 16 | 17 | ## Where's the prebuilts coming from? 18 | 19 | Prebuilt artifacts are generated from CRs in aosp repo ( 20 | [one](https://android-review.googlesource.com/c/platform/frameworks/support/+/1535138), 21 | [two](https://android-review.googlesource.com/c/platform/frameworks/support/+/1535139) 22 | ). I will try to keep them in sync while it makes sense. 23 | 24 | Currently these artifacts are not published anywhere, but you can use [this template](https://github.com/ShikaSD/compose-browser-external-template) to include it in other projects. 25 | 26 | ## See also 27 | 28 | - [Compose/JS from JB hackathon](https://github.com/JetBrains/compose-for-web-demos) 29 | - [Compose/Web branch in Jetbrains fork of Compose](https://github.com/JetBrains/androidx/tree/compose-web-main) 30 | -------------------------------------------------------------------------------- /prelude/src/main/kotlin/compose/web/internal/NodeWrapper.kt: -------------------------------------------------------------------------------- 1 | package compose.web.internal 2 | 3 | import compose.web.CssModifier 4 | import compose.web.EventModifier 5 | import compose.web.Modifier 6 | import compose.web.PropertyModifier 7 | import compose.web.RefModifier 8 | import kotlinx.browser.document 9 | import org.w3c.dom.Element 10 | import org.w3c.dom.HTMLElement 11 | import org.w3c.dom.Node 12 | import org.w3c.dom.get 13 | 14 | internal class NodeWrapper internal constructor(internal val realNode: Node) { 15 | internal constructor(tagName: String) : this(document.createElement(tagName)) 16 | 17 | internal var modifier: Modifier = Modifier 18 | set(value) { 19 | updateModifier(previous = field, next = value) 20 | field = value 21 | } 22 | 23 | private fun updateModifier(previous: Modifier, next: Modifier) { 24 | val element = realNode as HTMLElement 25 | 26 | previous.foldOut(Unit) { mod, _ -> 27 | if (mod is EventModifier) { 28 | element.removeEventListener(mod.eventName, mod.listener) 29 | } 30 | } 31 | if (element.style.length > 0) { 32 | element.style.cssText = "" 33 | } 34 | 35 | next.foldOut(Unit) { mod, _ -> 36 | when (mod) { 37 | is CssModifier -> element.style.apply(mod.configure) 38 | is EventModifier -> element.addEventListener(mod.eventName, mod.listener) 39 | is PropertyModifier -> element.apply(mod.configure) 40 | is RefModifier -> mod.configure(element) 41 | } 42 | } 43 | } 44 | 45 | fun insert(index: Int, instance: NodeWrapper) { 46 | if (realNode !is Element) throw IllegalStateException("Cannot add elements to $realNode") 47 | 48 | realNode.insertBefore(instance.realNode, realNode.childNodes[index]) 49 | } 50 | 51 | fun remove(index: Int, count: Int) { 52 | if (realNode !is Element) throw IllegalStateException("Cannot remove elements from $realNode") 53 | 54 | repeat(count) { 55 | realNode.removeChild(realNode.childNodes[index]!!) 56 | } 57 | } 58 | 59 | fun move(from: Int, to: Int, count: Int) { 60 | if (realNode !is Element) throw IllegalStateException("Cannot move elements in $realNode") 61 | 62 | if (from > to) { 63 | repeat(count) { 64 | realNode.insertBefore(realNode.childNodes[from + it]!!, realNode.childNodes[to + it]) 65 | } 66 | } else { 67 | repeat(count) { 68 | realNode.insertBefore(realNode.childNodes[from]!!, realNode.childNodes[to]) 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /libs/androidx/compose/runtime/runtime-js/1.0.0-beta02/runtime-js-1.0.0-beta02.pom: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 4.0.0 9 | androidx.compose.runtime 10 | runtime-js 11 | 1.0.0-beta02 12 | klib 13 | Compose Runtime 14 | Tree composition support for code generated by the Compose compiler plugin and corresponding public API 15 | https://developer.android.com/jetpack/androidx/releases/compose-runtime#1.0.0-beta02 16 | 2019 17 | 18 | 19 | The Apache Software License, Version 2.0 20 | http://www.apache.org/licenses/LICENSE-2.0.txt 21 | repo 22 | 23 | 24 | 25 | 26 | The Android Open Source Project 27 | 28 | 29 | 30 | scm:git:https://android.googlesource.com/platform/frameworks/support 31 | https://cs.android.com/androidx/platform/frameworks/support 32 | 33 | 34 | 35 | org.jetbrains.kotlin 36 | kotlin-stdlib-js 37 | 1.4.31 38 | compile 39 | 40 | 41 | org.jetbrains.kotlin 42 | kotlin-stdlib-common 43 | 1.4.31 44 | compile 45 | 46 | 47 | org.jetbrains.kotlinx 48 | kotlinx-coroutines-core-js 49 | 1.4.1 50 | compile 51 | 52 | 53 | org.jetbrains.kotlinx 54 | kotlinx-collections-immutable-js 55 | 0.3.3 56 | compile 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /prelude/src/main/kotlin/compose/web/internal/GlobalSnapshotManager.kt: -------------------------------------------------------------------------------- 1 | package compose.web.internal 2 | 3 | import androidx.compose.runtime.ExperimentalComposeApi 4 | import androidx.compose.runtime.snapshots.ObserverHandle 5 | import androidx.compose.runtime.snapshots.Snapshot 6 | import compose.web.internal.GlobalSnapshotManager.ensureStarted 7 | import kotlinx.coroutines.CoroutineScope 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.SupervisorJob 10 | import kotlinx.coroutines.launch 11 | 12 | /** 13 | * Platform-specific mechanism for starting a monitor of global snapshot state writes 14 | * in order to schedule the periodic dispatch of snapshot apply notifications. 15 | * This process should remain platform-specific; it is tied to the threading and update model of 16 | * a particular platform and framework target. 17 | * 18 | * Composition bootstrapping mechanisms for a particular platform/framework should call 19 | * [ensureStarted] during setup to initialize periodic global snapshot notifications. 20 | */ 21 | internal object GlobalSnapshotManager { 22 | private var started = false 23 | private var commitPending = false 24 | private var removeWriteObserver: (ObserverHandle)? = null 25 | 26 | private val scheduleScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) 27 | 28 | @OptIn(ExperimentalComposeApi::class) 29 | fun ensureStarted() { 30 | if (!started) { 31 | started = true 32 | removeWriteObserver = Snapshot.registerGlobalWriteObserver(globalWriteObserver) 33 | } 34 | } 35 | 36 | @OptIn(ExperimentalComposeApi::class) 37 | private val globalWriteObserver: (Any) -> Unit = { 38 | // Race, but we don't care too much if we end up with multiple calls scheduled. 39 | if (!commitPending) { 40 | commitPending = true 41 | schedule { 42 | commitPending = false 43 | Snapshot.sendApplyNotifications() 44 | } 45 | } 46 | } 47 | 48 | /** 49 | * List of deferred callbacks to run serially. Guarded by its own monitor lock. 50 | */ 51 | private val scheduledCallbacks = mutableListOf<() -> Unit>() 52 | 53 | /** 54 | * Guarded by [scheduledCallbacks]'s monitor lock. 55 | */ 56 | private var isSynchronizeScheduled = false 57 | 58 | /** 59 | * Synchronously executes any outstanding callbacks and brings snapshots into a 60 | * consistent, updated state. 61 | */ 62 | private fun synchronize() { 63 | scheduledCallbacks.forEach { it.invoke() } 64 | scheduledCallbacks.clear() 65 | isSynchronizeScheduled = false 66 | } 67 | 68 | private fun schedule(block: () -> Unit) { 69 | scheduledCallbacks.add(block) 70 | if (!isSynchronizeScheduled) { 71 | isSynchronizeScheduled = true 72 | scheduleScope.launch { synchronize() } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | 88 | @rem Execute Gradle 89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 90 | 91 | :end 92 | @rem End local scope for the variables with windows NT shell 93 | if "%ERRORLEVEL%"=="0" goto mainEnd 94 | 95 | :fail 96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 97 | rem the _cmd.exe /c_ return code! 98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 99 | exit /b 1 100 | 101 | :mainEnd 102 | if "%OS%"=="Windows_NT" endlocal 103 | 104 | :omega 105 | -------------------------------------------------------------------------------- /src/main/kotlin/Router1.kt: -------------------------------------------------------------------------------- 1 | import androidx.compose.runtime.* 2 | import compose.web.* 3 | 4 | class Router { 5 | val backStack: BackStack = BackStack(mutableStateListOf(Root)) 6 | 7 | companion object { 8 | val local: ProvidableCompositionLocal = staticCompositionLocalOf { Router() } 9 | val currentIndex: ProvidableCompositionLocal = compositionLocalOf { 1 } 10 | } 11 | } 12 | 13 | @Composable 14 | fun Router(default: T, block: @Composable (T) -> Unit) { 15 | val router = Router.local.current 16 | val stackIndex = Router.currentIndex.current 17 | val stack = router.backStack.entries.last().stack 18 | 19 | val current = if (stackIndex < stack.size) stack[stackIndex] else default 20 | 21 | CompositionLocalProvider(Router.currentIndex provides stackIndex + 1) { 22 | block(current as T) 23 | } 24 | } 25 | 26 | fun Router.push(value: Route) { 27 | backStack.entries += value 28 | } 29 | 30 | fun Router.pop() { 31 | backStack.entries.removeLast() 32 | } 33 | 34 | interface Route { 35 | fun then(value: R): Route = 36 | Entry(value, parent = stack) 37 | 38 | val stack: List<*> 39 | 40 | val value: T 41 | 42 | class Entry( 43 | override val value: T, 44 | parent: List<*> 45 | ) : Route { 46 | override val stack: List<*> = 47 | parent + value 48 | } 49 | } 50 | 51 | operator fun Route<*>.div(value: T): Route = 52 | then(value) 53 | 54 | object Root : Route { 55 | override val stack: List> = listOf(Root) 56 | override val value: Root = Root 57 | } 58 | 59 | class BackStack(val entries: MutableList>) 60 | 61 | sealed class Page { 62 | object Initial : Page() 63 | object Message : Page() 64 | object Profile : Page() 65 | } 66 | 67 | @Composable 68 | fun testRouter1() { 69 | Router(Page.Initial) { value: Page -> 70 | val current = Router.local.current.backStack.entries.last() 71 | console.log(current.stack) 72 | 73 | when (value) { 74 | Page.Initial -> InitialScreen() 75 | Page.Message -> Messages() 76 | Page.Profile -> Profile() 77 | } 78 | } 79 | } 80 | 81 | @Composable 82 | fun InitialScreen() { 83 | h1 { 84 | text("Root") 85 | } 86 | 87 | Button("Messages", navigateTo = MessagesRoute) 88 | Button("Profile", navigateTo = ProfileRoute) 89 | } 90 | 91 | val ProfileRoute = Root / Page.Profile 92 | @Composable 93 | fun Profile() { 94 | h1 { 95 | text("Profile") 96 | } 97 | 98 | Button("Root", navigateTo = Root) 99 | } 100 | 101 | val MessagesRoute = Root / Page.Message 102 | @Composable 103 | fun Messages() { 104 | h1 { 105 | text("Messages") 106 | } 107 | 108 | Router(null) { 109 | if (it != null) { 110 | CurrentMessage(it.messageId) 111 | } 112 | 113 | val nextId = it?.messageId?.plus(1) ?: 0 114 | Button("Message #$nextId", navigateTo = messageRoute(nextId)) 115 | } 116 | 117 | Button("Root", navigateTo = Root) 118 | } 119 | 120 | data class Message(val messageId: Int) 121 | fun messageRoute(id: Int): Route<*> = MessagesRoute / Message(id) 122 | 123 | @Composable 124 | fun CurrentMessage(id: Int) { 125 | text("Currently displaying message with id=$id") 126 | } 127 | 128 | @Composable 129 | fun Button(text: String, navigateTo: Route<*>) { 130 | val router = Router.local.current 131 | button(Modifier.onClick { router.push(navigateTo) }) { 132 | text(text) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /libs/androidx/compose/runtime/runtime-js/1.0.0-beta02/runtime-js-1.0.0-beta02.module: -------------------------------------------------------------------------------- 1 | { 2 | "formatVersion": "1.1", 3 | "component": { 4 | "url": "../../runtime/1.0.0-beta02/runtime-1.0.0-beta02.module", 5 | "group": "androidx.compose.runtime", 6 | "module": "runtime", 7 | "version": "1.0.0-beta02", 8 | "attributes": { 9 | "org.gradle.status": "release" 10 | } 11 | }, 12 | "createdBy": { 13 | "gradle": { 14 | "version": "6.8.1", 15 | "buildId:": "0" 16 | } 17 | }, 18 | "variants": [ 19 | { 20 | "name": "jsApiElements-published", 21 | "attributes": { 22 | "org.gradle.usage": "kotlin-api", 23 | "org.jetbrains.kotlin.js.compiler": "ir", 24 | "org.jetbrains.kotlin.platform.type": "js" 25 | }, 26 | "dependencies": [ 27 | { 28 | "group": "org.jetbrains.kotlin", 29 | "module": "kotlin-stdlib-js", 30 | "version": { 31 | "requires": "1.4.31" 32 | } 33 | }, 34 | { 35 | "group": "org.jetbrains.kotlin", 36 | "module": "kotlin-stdlib-common", 37 | "version": { 38 | "requires": "1.4.31" 39 | } 40 | }, 41 | { 42 | "group": "org.jetbrains.kotlinx", 43 | "module": "kotlinx-coroutines-core", 44 | "version": { 45 | "requires": "1.4.1" 46 | } 47 | }, 48 | { 49 | "group": "org.jetbrains.kotlinx", 50 | "module": "kotlinx-collections-immutable", 51 | "version": { 52 | "requires": "0.3.3" 53 | } 54 | } 55 | ], 56 | "files": [ 57 | { 58 | "name": "runtime-js-1.0.0-beta02.klib", 59 | "url": "runtime-js-1.0.0-beta02.klib", 60 | "size": 568233, 61 | "sha512": "b92c668183eb112cc4c49bb2514f5d990385a5aef448dddfc7b6acc984f497f5424f80c387bbecb16081454c67940457dac9e26130e166f7aa468674f7bd6f2e", 62 | "sha256": "14fa5502ccc587159945435ddf776bee78e82cfc985f4bd8dbf532bdd7bdfda5", 63 | "sha1": "6b4b26de2cf8071f1de0f1b0b59d680673bfb8f9", 64 | "md5": "9d6dbdad61464286917a32b4685ff870" 65 | } 66 | ] 67 | }, 68 | { 69 | "name": "jsRuntimeElements-published", 70 | "attributes": { 71 | "org.gradle.usage": "kotlin-runtime", 72 | "org.jetbrains.kotlin.js.compiler": "ir", 73 | "org.jetbrains.kotlin.platform.type": "js" 74 | }, 75 | "dependencies": [ 76 | { 77 | "group": "org.jetbrains.kotlin", 78 | "module": "kotlin-stdlib-js", 79 | "version": { 80 | "requires": "1.4.31" 81 | } 82 | }, 83 | { 84 | "group": "org.jetbrains.kotlin", 85 | "module": "kotlin-stdlib-common", 86 | "version": { 87 | "requires": "1.4.31" 88 | } 89 | }, 90 | { 91 | "group": "org.jetbrains.kotlinx", 92 | "module": "kotlinx-coroutines-core", 93 | "version": { 94 | "requires": "1.4.1" 95 | } 96 | }, 97 | { 98 | "group": "org.jetbrains.kotlinx", 99 | "module": "kotlinx-collections-immutable", 100 | "version": { 101 | "requires": "0.3.3" 102 | } 103 | } 104 | ], 105 | "files": [ 106 | { 107 | "name": "runtime-js-1.0.0-beta02.klib", 108 | "url": "runtime-js-1.0.0-beta02.klib", 109 | "size": 568233, 110 | "sha512": "b92c668183eb112cc4c49bb2514f5d990385a5aef448dddfc7b6acc984f497f5424f80c387bbecb16081454c67940457dac9e26130e166f7aa468674f7bd6f2e", 111 | "sha256": "14fa5502ccc587159945435ddf776bee78e82cfc985f4bd8dbf532bdd7bdfda5", 112 | "sha1": "6b4b26de2cf8071f1de0f1b0b59d680673bfb8f9", 113 | "md5": "9d6dbdad61464286917a32b4685ff870" 114 | } 115 | ] 116 | } 117 | ] 118 | } 119 | -------------------------------------------------------------------------------- /prelude/src/main/kotlin/compose/web/Modifier.kt: -------------------------------------------------------------------------------- 1 | package compose.web 2 | 3 | import androidx.compose.runtime.Stable 4 | 5 | // taken from android version 6 | @Stable 7 | interface Modifier { 8 | 9 | /** 10 | * Accumulates a value starting with [initial] and applying [operation] to the current value 11 | * and each element from outside in. 12 | * 13 | * Elements wrap one another in a chain from left to right; an [Element] that appears to the 14 | * left of another in a `+` expression or in [operation]'s parameter order affects all 15 | * of the elements that appear after it. [foldIn] may be used to accumulate a value starting 16 | * from the parent or head of the modifier chain to the final wrapped child. 17 | */ 18 | fun foldIn(initial: R, operation: (R, Element) -> R): R 19 | 20 | /** 21 | * Accumulates a value starting with [initial] and applying [operation] to the current value 22 | * and each element from inside out. 23 | * 24 | * Elements wrap one another in a chain from left to right; an [Element] that appears to the 25 | * left of another in a `+` expression or in [operation]'s parameter order affects all 26 | * of the elements that appear after it. [foldOut] may be used to accumulate a value starting 27 | * from the child or tail of the modifier chain up to the parent or head of the chain. 28 | */ 29 | fun foldOut(initial: R, operation: (Element, R) -> R): R 30 | 31 | /** 32 | * Returns `true` if [predicate] returns true for any [Element] in this [Modifier]. 33 | */ 34 | fun any(predicate: (Element) -> Boolean): Boolean 35 | 36 | /** 37 | * Returns `true` if [predicate] returns true for all [Element]s in this [Modifier] or if 38 | * this [Modifier] contains no [Element]s. 39 | */ 40 | fun all(predicate: (Element) -> Boolean): Boolean 41 | 42 | /** 43 | * Concatenates this modifier with another. 44 | * 45 | * Returns a [Modifier] representing this modifier followed by [other] in sequence. 46 | */ 47 | infix fun then(other: Modifier): Modifier = 48 | if (other === Modifier) this else CombinedModifier(this, other) 49 | 50 | /** 51 | * A single element contained within a [Modifier] chain. 52 | */ 53 | interface Element : Modifier { 54 | override fun foldIn(initial: R, operation: (R, Element) -> R): R = 55 | operation(initial, this) 56 | 57 | override fun foldOut(initial: R, operation: (Element, R) -> R): R = 58 | operation(this, initial) 59 | 60 | override fun any(predicate: (Element) -> Boolean): Boolean = predicate(this) 61 | 62 | override fun all(predicate: (Element) -> Boolean): Boolean = predicate(this) 63 | } 64 | 65 | /** 66 | * The companion object `Modifier` is the empty, default, or starter [Modifier] 67 | * that contains no [elements][Element]. Use it to create a new [Modifier] using 68 | * modifier extension factory functions: 69 | * 70 | * @sample androidx.compose.ui.samples.ModifierUsageSample 71 | * 72 | * or as the default value for [Modifier] parameters: 73 | * 74 | * @sample androidx.compose.ui.samples.ModifierParameterSample 75 | */ 76 | // The companion object implements `Modifier` so that it may be used as the start of a 77 | // modifier extension factory expression. 78 | companion object : Modifier { 79 | override fun foldIn(initial: R, operation: (R, Element) -> R): R = initial 80 | override fun foldOut(initial: R, operation: (Element, R) -> R): R = initial 81 | override fun any(predicate: (Element) -> Boolean): Boolean = false 82 | override fun all(predicate: (Element) -> Boolean): Boolean = true 83 | override infix fun then(other: Modifier): Modifier = other 84 | override fun toString() = "Modifier" 85 | } 86 | } 87 | 88 | /** 89 | * A node in a [Modifier] chain. A CombinedModifier always contains at least two elements; 90 | * a Modifier [outer] that wraps around the Modifier [inner]. 91 | */ 92 | class CombinedModifier( 93 | private val outer: Modifier, 94 | private val inner: Modifier 95 | ) : Modifier { 96 | override fun foldIn(initial: R, operation: (R, Modifier.Element) -> R): R = 97 | inner.foldIn(outer.foldIn(initial, operation), operation) 98 | 99 | override fun foldOut(initial: R, operation: (Modifier.Element, R) -> R): R = 100 | outer.foldOut(inner.foldOut(initial, operation), operation) 101 | 102 | override fun any(predicate: (Modifier.Element) -> Boolean): Boolean = 103 | outer.any(predicate) || inner.any(predicate) 104 | 105 | override fun all(predicate: (Modifier.Element) -> Boolean): Boolean = 106 | outer.all(predicate) && inner.all(predicate) 107 | 108 | override fun equals(other: Any?): Boolean = 109 | other is CombinedModifier && outer == other.outer && inner == other.inner 110 | 111 | override fun hashCode(): Int = outer.hashCode() + 31 * inner.hashCode() 112 | 113 | override fun toString() = "[" + foldIn("") { acc, element -> 114 | if (acc.isEmpty()) element.toString() else "$acc, $element" 115 | } + "]" 116 | } 117 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | --------------------------------------------------------------------------------