From 01a30c96457102913f7bfdcc63a0b969c3fc26f5 Mon Sep 17 00:00:00 2001 From: rnentjes Date: Fri, 1 Jan 2021 19:45:05 +0100 Subject: [PATCH] Testing state komponent --- build.gradle.kts | 11 +- komp.commonMain.iml | 39 +++++++ komp.commonTest.iml | 45 ++++++++ komp.iml | 2 +- komp.ipr | 103 ++++++++++++++--- komp.jsMain.iml | 60 ++++++++++ komp.jsTest.iml | 88 +++++++++++++++ komp_main.iml | 48 -------- komp_test.iml | 67 ----------- .../kotlin/nl/astraeus/komp/HtmlBuilder.kt | 8 +- .../kotlin/nl/astraeus/komp/Komponent.kt | 104 +++++++++++++++--- 11 files changed, 419 insertions(+), 156 deletions(-) create mode 100644 komp.commonMain.iml create mode 100644 komp.commonTest.iml create mode 100644 komp.jsMain.iml create mode 100644 komp.jsTest.iml delete mode 100644 komp_main.iml delete mode 100644 komp_test.iml diff --git a/build.gradle.kts b/build.gradle.kts index 99db642..cc0e7f5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,11 @@ + plugins { - kotlin("multiplatform") version "1.3.71" + kotlin("multiplatform") version "1.4.30-M1" `maven-publish` } group = "nl.astraeus" -version = "0.1.21-SNAPSHOT" +version = "0.2.3-SNAPSHOT" repositories { mavenCentral() @@ -15,7 +16,7 @@ kotlin { /* Targets configuration omitted. * To find out how to configure the targets, please follow the link: * https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets */ - js { + js(BOTH) { browser { //produceKotlinLibrary() testTask { @@ -31,14 +32,12 @@ kotlin { dependencies { implementation(kotlin("stdlib-common")) - //implementation("org.jetbrains.kotlinx:kotlinx-html:0.7.2-build-1711") + api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2") } } val jsMain by getting { dependencies { implementation(kotlin("stdlib-js")) - - api("org.jetbrains.kotlinx:kotlinx-html-js:0.7.1") } } val jsTest by getting { diff --git a/komp.commonMain.iml b/komp.commonMain.iml new file mode 100644 index 0000000..e043417 --- /dev/null +++ b/komp.commonMain.iml @@ -0,0 +1,39 @@ + + + + + + SOURCE_SET_HOLDER + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/komp.commonTest.iml b/komp.commonTest.iml new file mode 100644 index 0000000..1044866 --- /dev/null +++ b/komp.commonTest.iml @@ -0,0 +1,45 @@ + + + + + + SOURCE_SET_HOLDER + + jsLegacyBrowserTest|komp:jsTest|jsLegacy + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/komp.iml b/komp.iml index b4be323..bb96ad8 100644 --- a/komp.iml +++ b/komp.iml @@ -1,5 +1,5 @@ - + diff --git a/komp.ipr b/komp.ipr index 46cb162..e3ed064 100644 --- a/komp.ipr +++ b/komp.ipr @@ -13,6 +13,36 @@ + + $PROJECT_DIR$/build/libs + + + + + + $PROJECT_DIR$/build/libs + + + + + + $PROJECT_DIR$/build/libs + + + + + + $PROJECT_DIR$/build/libs + + + + + + $PROJECT_DIR$/build/libs + + + + @@ -216,9 +245,11 @@ - - - + + + + + @@ -254,6 +285,11 @@ @@ -317,41 +353,76 @@ + + + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/komp.jsMain.iml b/komp.jsMain.iml new file mode 100644 index 0000000..c4fc494 --- /dev/null +++ b/komp.jsMain.iml @@ -0,0 +1,60 @@ + + + + + + komp:commonMain + + komp.commonMain + + COMPILATION_AND_SOURCE_SET_HOLDER + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/komp.jsTest.iml b/komp.jsTest.iml new file mode 100644 index 0000000..52f9ec0 --- /dev/null +++ b/komp.jsTest.iml @@ -0,0 +1,88 @@ + + + + + + komp:commonTest + + komp.commonMain + komp.jsMain + komp.commonTest + + COMPILATION_AND_SOURCE_SET_HOLDER + + jsLegacyBrowserTest|komp:jsTest|jsLegacy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/komp_main.iml b/komp_main.iml deleted file mode 100644 index c3b60db..0000000 --- a/komp_main.iml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - COMPILATION_AND_SOURCE_SET_HOLDER - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/komp_test.iml b/komp_test.iml deleted file mode 100644 index ccc6890..0000000 --- a/komp_test.iml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - komp_main - - COMPILATION_AND_SOURCE_SET_HOLDER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt index d9ce14b..a0abe19 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/HtmlBuilder.kt @@ -1,13 +1,13 @@ package nl.astraeus.komp -import kotlinx.html.* import org.w3c.dom.* import org.w3c.dom.css.CSSStyleDeclaration import org.w3c.dom.events.Event -import kotlin.browser.document +import kotlinx.browser.document +import kotlinx.html.* @Suppress("NOTHING_TO_INLINE") -inline fun HTMLElement.setEvent(name: String, noinline callback: (Event) -> Unit): Unit { +inline fun HTMLElement.setEvent(name: String, noinline callback: (Event) -> Unit) { val eventName = if (name.startsWith("on")) { name.substring(2) } else { @@ -185,7 +185,7 @@ class HtmlBuilder( throw IllegalStateException("No current DOM node") } - // stupid hack as browsers doesn't support createEntityReference + // stupid hack as browsers don't support createEntityReference val s = document.createElement("span") as HTMLElement s.innerHTML = entity.text path.last().appendChild(s.childNodes.asList().first { it.nodeType == Node.TEXT_NODE }) diff --git a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt index d9baf82..e19cbed 100644 --- a/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt +++ b/src/jsMain/kotlin/nl/astraeus/komp/Komponent.kt @@ -1,14 +1,36 @@ package nl.astraeus.komp -import kotlinx.html.div import org.w3c.dom.HTMLDivElement import org.w3c.dom.HTMLElement import org.w3c.dom.Node import org.w3c.dom.css.CSSStyleDeclaration -import kotlin.browser.document +import kotlinx.browser.document +import kotlinx.browser.window +import kotlinx.html.div +import kotlin.reflect.KProperty typealias CssStyle = CSSStyleDeclaration.() -> Unit +class StateDelegate( + val komponent: Komponent, + initialValue: T +) { + var value: T = initialValue + + operator fun getValue(thisRef: Any?, property: KProperty<*>): T { + return value + } + + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { + if (this.value?.equals(value) != true) { + this.value = value + komponent.requestUpdate() + } + } +} + +inline fun Komponent.state(initialValue: T): StateDelegate = StateDelegate(this, initialValue) + fun HtmlConsumer.include(component: Komponent) { if (Komponent.updateStrategy == UpdateStrategy.REPLACE) { if (component.element != null) { @@ -39,6 +61,9 @@ enum class UpdateStrategy { } abstract class Komponent { + private var createIndex = getNextCreateIndex() + private var dirty: Boolean = true + var element: Node? = null val declaredStyles: MutableMap = HashMap() @@ -49,11 +74,18 @@ abstract class Komponent { element = result + dirty = false + return result } abstract fun HtmlBuilder.render() + fun requestUpdate() { + dirty = true + scheduleForUpdate(this) + } + open fun style(className: String, vararg imports: CssStyle, block: CssStyle = {}) { val style = (document.createElement("div") as HTMLDivElement).style for (imp in imports) { @@ -63,9 +95,11 @@ abstract class Komponent { declaredStyles[className] = style } - open fun update() = refresh() + open fun update() { + refresh() + } - open fun refresh() { + internal fun refresh() { val oldElement = element if (logRenderEvent) { console.log("Rendering", this) @@ -73,19 +107,21 @@ abstract class Komponent { val newElement = create() if (oldElement != null) { - if (updateStrategy == UpdateStrategy.REPLACE) { - if (logReplaceEvent) { - console.log("Replacing", oldElement, newElement) - } - oldElement.parentNode?.replaceChild(newElement, oldElement) - element = newElement + element = if (updateStrategy == UpdateStrategy.REPLACE) { + if (logReplaceEvent) { + console.log("Replacing", oldElement, newElement) + } + oldElement.parentNode?.replaceChild(newElement, oldElement) + newElement } else { - if (logReplaceEvent) { - console.log("DomDiffing", oldElement, newElement) - } - element = DiffPatch.updateNode(oldElement, newElement) + if (logReplaceEvent) { + console.log("DomDiffing", oldElement, newElement) + } + DiffPatch.updateNode(oldElement, newElement) } } + + dirty = false } @JsName("remove") @@ -102,6 +138,10 @@ abstract class Komponent { } companion object { + private var nextCreateIndex: Int = 1 + private var updateCallback: Int? = null + private var scheduledForUpdate = mutableSetOf() + var logRenderEvent = false var logReplaceEvent = false var updateStrategy = UpdateStrategy.DOM_DIFF @@ -115,6 +155,42 @@ abstract class Komponent { parent.appendChild(element) } } + + private fun getNextCreateIndex() = nextCreateIndex++ + + private fun scheduleForUpdate(komponent: Komponent) { + scheduledForUpdate.add(komponent) + + if (updateCallback == null) { + window.setTimeout({ + runUpdate() + }, 0) + } + } + + private fun runUpdate() { + val todo = scheduledForUpdate.sortedBy { komponent -> komponent.createIndex } + + if (logRenderEvent) { + console.log("runUpdate") + } + + todo.forEach { next -> + if (next.dirty) { + if (logRenderEvent) { + console.log("Update dirty ${next.createIndex}") + } + next.update() + } else { + if (logRenderEvent) { + console.log("Skip ${next.createIndex}") + } + } + } + + scheduledForUpdate.clear() + updateCallback = null + } } }