From f171cb11460c5b5aeccb9b6f0e6abcf7f1de63ab Mon Sep 17 00:00:00 2001 From: Ian Clarke Date: Wed, 4 Jul 2018 11:15:37 -0500 Subject: [PATCH] bump version to 0.3.2 to try to fix a bug possibly related to dependency problems --- build.gradle | 6 +- src/main/kotlin/io/kweb/Kweb.kt | 8 +- src/main/kotlin/io/kweb/WebBrowser.kt | 1 + .../browserConnection/KwebClientConnection.kt | 6 +- src/main/kotlin/io/kweb/demos/todo/todoApp.kt | 2 +- .../dom/element/creation/ElementCreator.kt | 2 +- .../kweb/dom/element/creation/tags/other.kt | 4 + .../plugins/semanticUI/SemanticUIClasses.kt | 103 +++++++++++++++++- .../plugins/semanticUI/SemanticUIPlugin.kt | 1 + .../io/kweb/state/persistent/persistent.kt | 54 +++++++-- src/main/kotlin/io/kweb/state/state.kt | 6 +- .../resources/io/kweb/kweb_bootstrap.html | 16 ++- .../kweb/state/persistent/PersistentSpec.kt | 3 +- 13 files changed, 184 insertions(+), 28 deletions(-) diff --git a/build.gradle b/build.gradle index 5df1f3a6ed..840d0a47e6 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ allprojects { buildscript { ext.kotlin_version = '1.2.50' ext.dokka_version = '0.9.15' - ext.ktor_version = '0.9.3-alpha-3' + ext.ktor_version = '0.9.3' repositories { jcenter() @@ -32,7 +32,7 @@ buildscript { } group 'io.kweb' -version '0.3.1' +version '0.3.2' apply plugin: 'java' apply plugin: 'kotlin' @@ -119,7 +119,7 @@ dependencies { compile 'io.github.microutils:kotlin-logging:1.4.9' compile("org.reflections:reflections:0.9.11") - compile 'com.github.kwebio:shoebox:0.2.16' + compile 'com.github.kwebio:shoebox:0.2.17' compile 'com.github.yamamotoj:cached-property-kotlin:0.1.0' diff --git a/src/main/kotlin/io/kweb/Kweb.kt b/src/main/kotlin/io/kweb/Kweb.kt index 3cabad218f..5ff3eb07c9 100644 --- a/src/main/kotlin/io/kweb/Kweb.kt +++ b/src/main/kotlin/io/kweb/Kweb.kt @@ -57,7 +57,7 @@ class Kweb(val port: Int, val plugins: List = java.util.Collections.emptyList(), val appServerConfigurator: (io.ktor.routing.Routing) -> Unit = {}, val onError: ((List, io.kweb.JavaScriptError) -> io.kweb.LogError) = { _, _ -> true }, - val maxPageBuildTimeMS: Long = 500, + val maxPageBuildTimeMS: Long = 1000, val clientStateTimeout : Duration = Duration.ofHours(1), val buildPage: WebBrowser.() -> Unit ) : Closeable { @@ -108,6 +108,12 @@ class Kweb(val port: Int, .replace("", endHeadBuilder.toString()) // Setup default KWeb routing. + + get("/robots.txt") { + call.response.status(HttpStatusCode.NotFound) + call.respondText("robots.txt not currently supported by kweb") + } + get("/favicon.ico") { call.response.status(HttpStatusCode.NotFound) call.respondText("favicons not currently supported by kweb") diff --git a/src/main/kotlin/io/kweb/WebBrowser.kt b/src/main/kotlin/io/kweb/WebBrowser.kt index bb0e11e469..54351db7ba 100644 --- a/src/main/kotlin/io/kweb/WebBrowser.kt +++ b/src/main/kotlin/io/kweb/WebBrowser.kt @@ -86,6 +86,7 @@ class WebBrowser(private val sessionId: String, val httpRequestInfo: HttpRequest val url = KVar(httpRequestInfo.requestedUrl) url.addListener { old, new -> + logger.info("URL updated from $old to $new") pushState(new) } diff --git a/src/main/kotlin/io/kweb/browserConnection/KwebClientConnection.kt b/src/main/kotlin/io/kweb/browserConnection/KwebClientConnection.kt index df42ef70ed..1fa7fd89f4 100644 --- a/src/main/kotlin/io/kweb/browserConnection/KwebClientConnection.kt +++ b/src/main/kotlin/io/kweb/browserConnection/KwebClientConnection.kt @@ -2,7 +2,7 @@ package io.kweb.browserConnection import io.ktor.http.cio.websocket.Frame.Text import io.ktor.http.cio.websocket.WebSocketSession -import kotlinx.coroutines.experimental.* +import kotlinx.coroutines.experimental.launch import mu.KotlinLogging import java.util.concurrent.ConcurrentLinkedQueue @@ -13,10 +13,8 @@ sealed class KwebClientConnection { class WebSocket(private val channel: WebSocketSession) : KwebClientConnection() { - val socketContext = newSingleThreadContext("outbound-websocket-queue") - override fun send(message: String) { - launch(socketContext) { + launch { channel.send(Text(message)) } } diff --git a/src/main/kotlin/io/kweb/demos/todo/todoApp.kt b/src/main/kotlin/io/kweb/demos/todo/todoApp.kt index bf53e3b300..a3f05045ed 100644 --- a/src/main/kotlin/io/kweb/demos/todo/todoApp.kt +++ b/src/main/kotlin/io/kweb/demos/todo/todoApp.kt @@ -26,7 +26,7 @@ fun main(args: Array) { pageBorderAndTitle("Todo List") { - val url = url(simpleUrlParser) + val url = doc.receiver.url(simpleUrlParser) div(s.content).new { render(url.path[0]) { entityType -> diff --git a/src/main/kotlin/io/kweb/dom/element/creation/ElementCreator.kt b/src/main/kotlin/io/kweb/dom/element/creation/ElementCreator.kt index 355f39e0cf..ab22b418a4 100644 --- a/src/main/kotlin/io/kweb/dom/element/creation/ElementCreator.kt +++ b/src/main/kotlin/io/kweb/dom/element/creation/ElementCreator.kt @@ -53,7 +53,7 @@ open class ElementCreator( plugin.elementCreationHook(newElement) } onCleanup(withParent = false) { - logger.debug("Deleting element ${newElement.id}") + logger.debug("Deleting element ${newElement.id}", RuntimeException()) newElement.delete() } return newElement diff --git a/src/main/kotlin/io/kweb/dom/element/creation/tags/other.kt b/src/main/kotlin/io/kweb/dom/element/creation/tags/other.kt index eefaa5b011..864010641a 100644 --- a/src/main/kotlin/io/kweb/dom/element/creation/tags/other.kt +++ b/src/main/kotlin/io/kweb/dom/element/creation/tags/other.kt @@ -51,6 +51,10 @@ fun ElementCreator.textArea(rows : Int? = null, attributes: Map.select(attributes: Map = attr) = SelectElement(element("select", attributes)) +open class OptionElement(parent: Element) : Element(parent) +fun ElementCreator.option(attributes: Map = attr) = OptionElement(element("option", attributes)) + + open class H1Element(parent: Element) : Element(parent) fun ElementCreator.h1(attributes: Map = attr) = H1Element(element("h1", attributes)) diff --git a/src/main/kotlin/io/kweb/plugins/semanticUI/SemanticUIClasses.kt b/src/main/kotlin/io/kweb/plugins/semanticUI/SemanticUIClasses.kt index b9b4a2829d..fb8e66643f 100644 --- a/src/main/kotlin/io/kweb/plugins/semanticUI/SemanticUIClasses.kt +++ b/src/main/kotlin/io/kweb/plugins/semanticUI/SemanticUIClasses.kt @@ -50,7 +50,12 @@ class SemanticUIClasses : AttributeBuilder() { classes("attached") return this } - + + val balance : SemanticUIClasses get() { + classes("balance") + return this + } + val banner : SemanticUIClasses get() { classes("banner") return this @@ -156,6 +161,11 @@ class SemanticUIClasses : AttributeBuilder() { return this } + val compact : SemanticUIClasses get() { + classes("compact") + return this + } + val container : SemanticUIClasses get() { classes("container") return this @@ -196,6 +206,11 @@ class SemanticUIClasses : AttributeBuilder() { return this } + val dropdown : SemanticUIClasses get() { + classes("dropdown") + return this + } + val edit : SemanticUIClasses get() { classes("edit") return this @@ -212,6 +227,11 @@ class SemanticUIClasses : AttributeBuilder() { return this } + val error : SemanticUIClasses get() { + classes("error") + return this + } + val equal : SemanticUIClasses get() { classes("equal") return this @@ -324,11 +344,21 @@ class SemanticUIClasses : AttributeBuilder() { return this } + val indeterminate : SemanticUIClasses get() { + classes("indeterminate") + return this + } + val info : SemanticUIClasses get() { classes("info") return this } + val inline : SemanticUIClasses get() { + classes("inline") + return this + } + val input : SemanticUIClasses get() { classes("input") return this @@ -390,6 +420,12 @@ class SemanticUIClasses : AttributeBuilder() { return this } + val loader : SemanticUIClasses get() { + classes("loader") + return this + } + + val loading : SemanticUIClasses get() { classes("loading") return this @@ -451,6 +487,12 @@ class SemanticUIClasses : AttributeBuilder() { return this } + val notched : SemanticUIClasses get() { + classes("notched") + return this + } + + val olive : SemanticUIClasses get() { classes("olive") return this @@ -513,11 +555,27 @@ class SemanticUIClasses : AttributeBuilder() { return this } + + val scale : SemanticUIClasses get() { + classes("scale") + return this + } + val secondary : SemanticUIClasses get() { classes("secondary") return this } + val selection : SemanticUIClasses get() { + classes("selection") + return this + } + + val sortable : SemanticUIClasses get() { + classes("sortable") + return this + } + val success : SemanticUIClasses get() { classes("success") return this @@ -613,6 +671,22 @@ class SemanticUIClasses : AttributeBuilder() { return this } + val stackable : SemanticUIClasses get() { + classes("stackable") + return this + } + + val statistic : SemanticUIClasses get() { + classes("statistic") + return this + } + + val statistics : SemanticUIClasses get() { + classes("statistics") + return this + } + + val striped : SemanticUIClasses get() { classes("striped") return this @@ -628,6 +702,16 @@ class SemanticUIClasses : AttributeBuilder() { return this } + val table: SemanticUIClasses get() { + classes("table") + return this + } + + val teal: SemanticUIClasses get() { + classes("teal") + return this + } + val three : SemanticUIClasses get() { classes("three") return this @@ -687,6 +771,18 @@ class SemanticUIClasses : AttributeBuilder() { return this } + val yellow : SemanticUIClasses get() { + classes("yellow") + return this + } + + + val value : SemanticUIClasses get() { + classes("value") + return this + } + + val very : SemanticUIClasses get() { classes("very") return this @@ -702,6 +798,11 @@ class SemanticUIClasses : AttributeBuilder() { return this } + val warning : SemanticUIClasses get() { + classes("warning") + return this + } + val wide : SemanticUIClasses get() { classes("wide") return this diff --git a/src/main/kotlin/io/kweb/plugins/semanticUI/SemanticUIPlugin.kt b/src/main/kotlin/io/kweb/plugins/semanticUI/SemanticUIPlugin.kt index 0804ad03fe..ca91c20fbe 100644 --- a/src/main/kotlin/io/kweb/plugins/semanticUI/SemanticUIPlugin.kt +++ b/src/main/kotlin/io/kweb/plugins/semanticUI/SemanticUIPlugin.kt @@ -11,6 +11,7 @@ class SemanticUIPlugin : KWebPlugin(dependsOn = setOf(jqueryCore)) { startHead.append(""" + """.trimIndent()) } diff --git a/src/main/kotlin/io/kweb/state/persistent/persistent.kt b/src/main/kotlin/io/kweb/state/persistent/persistent.kt index 150ded78c8..98c107eb44 100644 --- a/src/main/kotlin/io/kweb/state/persistent/persistent.kt +++ b/src/main/kotlin/io/kweb/state/persistent/persistent.kt @@ -6,15 +6,40 @@ import io.kweb.dom.element.creation.ElementCreator import io.kweb.dom.element.creation.tags.* import io.kweb.shoebox.* import io.kweb.state.* +import kotlinx.coroutines.experimental.* import mu.KotlinLogging -import java.util.* -import kotlin.NoSuchElementException +import java.util.concurrent.CopyOnWriteArrayList /** * Created by ian on 6/18/17. */ -private val logger = KotlinLogging.logger {} +val logger = KotlinLogging.logger {} + +fun main(args: Array) { + + data class D(val a: Int, val b: Int) + + val dv = KVar(D(1, 1)) + + Kweb(port = 13513) { + doc.body.new { + div().new { + render(dv) { + div().text(it.a.toString()) + div().text(it.b.toString()) + } + } + } + } + + launch { + while(true) { + dv.value = dv.value.copy(a = dv.value.a + 1) + delay(20000) + } + } +} fun ElementCreator<*>.render(kval : KVal, renderer : ElementCreator.(T) -> Unit) { var childEC = ElementCreator(this.addToElement, this) @@ -50,7 +75,7 @@ data class IndexedItem(val index : Int, val total : Int, val item : I) * @sample ordered_view_set_sample */ fun ElementCreator.renderEach(orderedViewSet: OrderedViewSet, renderer : ElementCreator.(KVar) -> Unit) { - val items = ArrayList>() + val items = CopyOnWriteArrayList>() for (keyValue in orderedViewSet.keyValueEntries) { items += createItem(orderedViewSet, keyValue, renderer, insertAtPosition = null) } @@ -60,10 +85,14 @@ fun ElementCreator.renderEach(orderedViewSet: Ord } this.onCleanup(true) { orderedViewSet.deleteInsertListener(onInsertHandler) } - val onRemoveHandler = orderedViewSet.onRemove { index, _ -> - val removed = items.removeAt(index) - removed.creator.cleanup() - removed.KVar.close() + val onRemoveHandler = orderedViewSet.onRemove { index, keyValue -> + if (index >= items.size) { + logger.warn("Invalid index $index to retrieve item from items list of size ${items.size} for key ${keyValue.key} and item ${keyValue.value}", RuntimeException()) + } else { + val removed = items.removeAt(index) + removed.creator.cleanup() + removed.KVar.close() + } } this.onCleanup(true) { orderedViewSet.deleteRemoveListener(onRemoveHandler) } @@ -77,8 +106,13 @@ private fun ElementCreator.createItem( : ItemInfo { val itemElementCreator = ElementCreator(this.addToElement, this, insertAtPosition) val itemVar = itemElementCreator.toVar(orderedViewSet.view.viewOf, keyValue.key) - renderer(itemElementCreator, itemVar) - if (itemElementCreator.elementsCreated != 1) { + try { + renderer(itemElementCreator, itemVar) + } catch (e: Exception) { + logger.error("Error rendering item", e) + } + + if (itemElementCreator.elementsCreated > 1) { /* * Only one element may be created per-item because otherwise it would be much more complicated to figure * out where new items should be inserted by the onInsert handler below. onRemove would be easier because diff --git a/src/main/kotlin/io/kweb/state/state.kt b/src/main/kotlin/io/kweb/state/state.kt index 542dd54ad5..54ebc5ab9f 100644 --- a/src/main/kotlin/io/kweb/state/state.kt +++ b/src/main/kotlin/io/kweb/state/state.kt @@ -52,7 +52,11 @@ open class KVal(value: T) { val mappedValue = mapper(new) newObservable.pValue = mappedValue newObservable.listeners.values.forEach { - it(mapper(old), mappedValue) + try { + it(mapper(old), mappedValue) + } catch (e : Exception) { + logger.warn("Exception thrown by listener", e) + } } } diff --git a/src/main/resources/io/kweb/kweb_bootstrap.html b/src/main/resources/io/kweb/kweb_bootstrap.html index bf489934f8..3392f587f9 100644 --- a/src/main/resources/io/kweb/kweb_bootstrap.html +++ b/src/main/resources/io/kweb/kweb_bootstrap.html @@ -87,13 +87,15 @@ } console.warn("WebSocket was closed", explanation, evt); - // websocketEstablished = false - // connectWs() + websocketEstablished = false + location.reload(true); + setTimeout(function() { location.reload(true); }, 5000); }; socket.onerror = function(evt) { console.error("WebSocket error", evt); websocketEstablished = false - // connectWs() + location.reload(true); + setTimeout(function() { location.reload(true); }, 5000); } } } @@ -148,7 +150,7 @@ } } var docCookies = { - getTasks: function (sKey) { + getItems: function (sKey) { if (!sKey || !this.hasItem(sKey)) { return "__COOKIE_NOT_FOUND_TOKEN__"; } return unescape(document.cookie.replace(new RegExp("(?:^|.*;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*((?:[^;](?!;))*[^;]?).*"), "$1")); }, @@ -186,6 +188,12 @@ hasItem: function (sKey) { return (new RegExp("(?:^|;\\s*)" + escape(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie); } }; + window.addEventListener( "pageshow", function ( event ) { + if(performance.navigation.type == 2){ + location.reload(true); + } + }); + function buildPage() { connectWs(); diff --git a/src/test/kotlin/io/kweb/state/persistent/PersistentSpec.kt b/src/test/kotlin/io/kweb/state/persistent/PersistentSpec.kt index dd65cbaa53..6b227b613c 100644 --- a/src/test/kotlin/io/kweb/state/persistent/PersistentSpec.kt +++ b/src/test/kotlin/io/kweb/state/persistent/PersistentSpec.kt @@ -2,11 +2,10 @@ package io.kweb.state.persistent /** * Created by ian on 6/29/17. - +* object PersistentSpec : Spek({ val webClient: WebClient = autoClose(ACWebClient()) - init { // htmlUnitInit(webClient) "Given a shoebox and a ordered set of tan colored dogs" - {