diff options
author | Robbie Cronin <robert.cronin@uqconnect.edu.au> | 2019-06-21 13:17:30 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-21 13:17:30 +0800 |
commit | 2f8e0060308b0f5b1eeac9541600bfed3ad5acc9 (patch) | |
tree | 80d40364c5f5957da8c82578dd1152adac06912e | |
parent | 9038555f9e46a32a0a725112304e64cdd408bf8e (diff) | |
parent | d9f1a90c772719d14540eb2bf7bc3b8384fa7a72 (diff) | |
download | kvision-2f8e0060308b0f5b1eeac9541600bfed3ad5acc9.tar.gz kvision-2f8e0060308b0f5b1eeac9541600bfed3ad5acc9.tar.bz2 kvision-2f8e0060308b0f5b1eeac9541600bfed3ad5acc9.zip |
Merge pull request #1 from rjaros/master
merge
159 files changed, 3717 insertions, 289 deletions
@@ -49,13 +49,14 @@ allows to build full-stack, multiplatform applications with shared common code. - Innovative integration interface for [Ktor](https://ktor.io), [Jooby](https://jooby.org) and [Spring Boot](https://spring.io/projects/spring-boot) frameworks on the server side, including support for type-safe websockets connections. - Support for building cross-platform, desktop applications with [Electron](https://electronjs.org). -- Ready to explore [KVision examples](https://github.com/rjaros/kvision-examples) are available, -built with [Gradle](https://gradle.org/) and supporting Webpack's [Hot Module Replacement (HMR)](https://webpack.js.org/concepts/hot-module-replacement/) and +- KVision applications are built with [Gradle](https://gradle.org/) with support for Webpack's [Hot Module Replacement (HMR)](https://webpack.js.org/concepts/hot-module-replacement/) and [Kotlin JavaScript DCE (dead code elimination)](https://kotlinlang.org/docs/reference/javascript-dce.html). - [Karma](https://karma-runner.github.io/) testing framework support. - IDE support (IntelliJ IDEA Community Edition). -## Documentation +## Examples and documentation + +Ready to explore, rich set of [KVision examples](https://github.com/rjaros/kvision-examples) is available in the separate project. The comprehensive [KVision guide](https://kvision.gitbook.io/kvision-guide/) is published on GitBook. diff --git a/build.gradle b/build.gradle index cb32f60f..1c77ff6e 100644 --- a/build.gradle +++ b/build.gradle @@ -138,6 +138,7 @@ if (!project.gradle.startParameter.taskNames.contains("dokka")) { dependency("navigo", "7.0.0") devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } @@ -178,6 +179,7 @@ dokka { 'kvision-modules/kvision-server-jooby/src/main/kotlin', 'kvision-modules/kvision-server-ktor/src/main/kotlin', 'kvision-modules/kvision-server-spring-boot/src/main/kotlin', + 'kvision-modules/kvision-cordova/src/main/kotlin', 'kvision-modules/kvision-electron/src/main/kotlin') classpath = [new File("dokka/kvision-dokka-helper.jar")] outputFormat = 'html' diff --git a/gradle.properties b/gradle.properties index 3d8b64ac..e51a8eb4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ group=pl.treksoft -version=0.0.35 -kotlinVersion=1.3.30 +version=0.0.36 +kotlinVersion=1.3.31 javaVersion=1.8 -coroutinesVersion=1.2.0 +coroutinesVersion=1.2.1 serializationVersion=0.11.0 frontendPluginVersion=0.0.45 dokkaVersion=0.9.18 diff --git a/kvision-modules/kvision-bootstrap/build.gradle b/kvision-modules/kvision-bootstrap/build.gradle index 5f5d0c49..6c504c17 100644 --- a/kvision-modules/kvision-bootstrap/build.gradle +++ b/kvision-modules/kvision-bootstrap/build.gradle @@ -11,6 +11,7 @@ kotlinFrontend { dependency("bootstrap-vertical-tabs", "1.2.2") devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css index a0314ced..7633ed6c 100644 --- a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css +++ b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css @@ -163,3 +163,55 @@ ul.tabs-top > li { float:none; flex-shrink: 0; } + +.kv-tab-close { + margin-left: 10px; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: 0.2; +} + +.kv-tab-close:hover, .kv-tab-close:focus { + cursor: pointer; + filter: alpha(opacity=50); + opacity: 0.5; +} + +select.form-control, .tabulator-row .tabulator-cell.tabulator-editing select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: transparent none no-repeat; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAKCAYAAABblxXYAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wUKFyIn4IjqJgAAAENJREFUKM/l0LERACEQQlGsiTa2px1aokGugNNAx8wfMy8AeLoBALYjaTqoKkga2+gKPgF/2Q7JkEx359oftu+C7/UBCUIcVQz0PvcAAAAASUVORK5CYII='); + background-position: right center; + cursor: pointer; +} + +select.form-control:hover { + background-color: #e6e6e6; +} + +select.form-control option { + background-color: white; +} + +select.input-sm { + line-height: inherit; +} + +.tabulator-row .tabulator-cell.tabulator-editing input, .tabulator-row .tabulator-cell.tabulator-editing select { + border: 1px solid #ccc; + border-radius: 4px; +} + +.tabulator-row .tabulator-cell.tabulator-editing input:focus, .tabulator-row .tabulator-cell.tabulator-editing select:focus { + border-color: #66afe9; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6); +} + +.tabulator-row .tabulator-cell.tabulator-editing { + border-right: 1px solid #1d68cd !important; + padding: 2px !important; +} diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt index 2fc0320a..1ad97acc 100644 --- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -88,7 +88,7 @@ interface WSpec : DomSpec { fun runW(code: (widget: Widget, element: Element?) -> Unit) { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() widget.id = "test_id" root.add(widget) diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt index ff4daea7..af35fa51 100644 --- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt @@ -34,7 +34,7 @@ class DropDownSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag") root.add(dd) dd.toggle() @@ -51,7 +51,7 @@ class DropDownSpec : DomSpec { @Test fun renderDropUp() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag").apply { dropup = true } root.add(dd) dd.toggle() @@ -68,7 +68,7 @@ class DropDownSpec : DomSpec { @Test fun renderHeaderElement() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val dd = DropDown("Dropdown", listOf("abc" to DD.HEADER.option), "flag") root.add(dd) dd.toggle() @@ -85,7 +85,7 @@ class DropDownSpec : DomSpec { @Test fun renderSeparatorElement() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val dd = DropDown("Dropdown", listOf("abc" to DD.SEPARATOR.option), "flag") root.add(dd) dd.toggle() @@ -102,7 +102,7 @@ class DropDownSpec : DomSpec { @Test fun renderDisabledElement() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val dd = DropDown("Dropdown", listOf("abc" to DD.DISABLED.option), "flag") root.add(dd) dd.toggle() @@ -119,7 +119,7 @@ class DropDownSpec : DomSpec { @Test fun toggle() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag") root.add(dd) val visible = dd.getElementJQuery()?.hasClass("open") ?: false diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt index 807f837a..c3d17de9 100644 --- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt @@ -36,7 +36,7 @@ class AlertSpec : DomSpec { @Test fun render() { run { - Root("test", true) + Root("test", fixed = true) Alert.show("Alert caption", "Alert content") val alert = document.getElementById("test")?.let { jQuery(it).find(".modal")[0] } assertNotNull(alert, "Should show alert window") diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt index 2e3ea3ef..1893ce81 100644 --- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt @@ -32,7 +32,7 @@ class CloseIconSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ci = CloseIcon() root.add(ci) val element = document.getElementById("test") diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt index dc734e10..875bf9e2 100644 --- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt @@ -36,7 +36,7 @@ class ConfirmSpec : DomSpec { @Test fun render() { run { - Root("test", true) + Root("test", fixed = true) Confirm.show("Confirm caption", "Confirm content") val confirm = document.getElementById("test")?.let { jQuery(it).find(".modal")[0] } assertNotNull(confirm, "Should show confirm window") diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt index 523abfd5..7149b163 100644 --- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt @@ -35,7 +35,7 @@ class ModalSpec : DomSpec { @Test fun render() { run { - Root("test", true) + Root("test", fixed = true) val modal = Modal("Modal") modal.show() val content = document.getElementById("test")?.let { jQuery(it).find(".modal-title").html() } @@ -47,7 +47,7 @@ class ModalSpec : DomSpec { @Test fun toggle() { run { - Root("test", true) + Root("test", fixed = true) val modal = Modal("Modal") modal.toggle() val content = document.getElementById("test")?.let { jQuery(it).find(".modal-title").html() } diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt index e87626ca..c79b9d32 100644 --- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt +++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt @@ -32,7 +32,7 @@ class WindowSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val window = Window("Window title", isResizable = false) root.add(window) val id = window.id diff --git a/kvision-modules/kvision-chart/build.gradle b/kvision-modules/kvision-chart/build.gradle index 4d4ba4df..d90de6ce 100644 --- a/kvision-modules/kvision-chart/build.gradle +++ b/kvision-modules/kvision-chart/build.gradle @@ -6,6 +6,7 @@ kotlinFrontend { dependency("chart.js", "2.7.3") devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt index 37d7a9df..13c8531b 100644 --- a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt +++ b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -86,7 +86,7 @@ interface WSpec : DomSpec { fun runW(code: (widget: Widget, element: Element?) -> Unit) { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() widget.id = "test_id" root.add(widget) diff --git a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt index 6fae54ad..da83b989 100644 --- a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt +++ b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt @@ -36,7 +36,7 @@ class ChartCanvasSpec : DomSpec { @Test fun renderResponsive() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val chart = ChartCanvas( configuration = Configuration( ChartType.SCATTER, @@ -56,7 +56,7 @@ class ChartCanvasSpec : DomSpec { @Test fun renderNotResponsive() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val chart = ChartCanvas( 300, 600, configuration = Configuration( diff --git a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt index 7974ea78..1fe73608 100644 --- a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt +++ b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt @@ -37,7 +37,7 @@ class ChartSpec : DomSpec { @Test fun renderResponsive() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val chart = Chart( Configuration( ChartType.SCATTER, @@ -60,7 +60,7 @@ class ChartSpec : DomSpec { @Test fun renderNotResponsive() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val chart = Chart( Configuration( ChartType.SCATTER, diff --git a/kvision-modules/kvision-cordova/build.gradle b/kvision-modules/kvision-cordova/build.gradle new file mode 100644 index 00000000..4aaef76d --- /dev/null +++ b/kvision-modules/kvision-cordova/build.gradle @@ -0,0 +1,5 @@ +apply from: "../shared.gradle" + +dependencies { + compile "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion" +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt new file mode 100644 index 00000000..656a519a --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Battery status. + */ +external class BatteryStatus { + val level: Int + val isPlugged: Boolean +} + +/** + * Main object for Cordova battery. + */ +object Battery { + + /** + * Battery status event types. + */ + enum class BatteryEvent(internal val type: String) { + BATTERY_STATUS("batterystatus"), + BATTERY_LOW("batterylow"), + BATTERY_CRITICAL("batterycritical") + } + + /** + * Add listeners for battery status Cordova events. + */ + fun addStatusListener(event: BatteryEvent, listener: (BatteryStatus) -> Unit) { + addDeviceReadyListener { + window.addEventListener(event.type, { status -> + @Suppress("UnsafeCastFromDynamic") + listener(status.asDynamic()) + }, false) + } + } + + /** + * Get battery status. + */ + suspend fun getStatus(): BatteryStatus { + return suspendCoroutine { continuation -> + addStatusListener(Battery.BatteryEvent.BATTERY_STATUS) { status -> + continuation.resume(status) + } + } + } +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt new file mode 100644 index 00000000..40c731c7 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import pl.treksoft.kvision.utils.obj +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Exception class for camera errors. + */ +class CameraException(message: String) : Exception(message) + +/** + * Main object for Cordova camera. + */ +object Camera { + + private const val CAMERA_ACTIVE_STORAGE_KEY = "kv_camera_active_storage_key" + private const val CAMERA_STATUS_OK = "OK" + + /** + * Camera destination types. + */ + enum class DestinationType { + DATA_URL, + FILE_URI, + NATIVE_URI + } + + /** + * Picture encoding types. + */ + enum class EncodingType { + JPEG, + PNG + } + + /** + * Picture/video media types. + */ + enum class MediaType { + PICTURE, + VIDEO, + ALLMEDIA + } + + /** + * Camera picture/video sources. + */ + enum class PictureSourceType { + PHOTOLIBRARY, + CAMERA, + SAVEDPHOTOALBUM + } + + /** + * Camera facing types. + */ + enum class Direction { + BACK, + FRONT + } + + /** + * iOS popover arrow directions. + */ + enum class PopoverArrowDirection { + ARROW_UP, + ARROW_DOWN, + ARROW_LEFT, + ARROW_RIGHT, + ARROW_ANY + } + + /** + * iOS popover options. + */ + data class CameraPopoverOptions( + val x: Int, + val y: Int, + val width: Int, + val height: Int, + val arrowDir: PopoverArrowDirection, + val popoverWidth: Int, + val popoverHeight: Int + ) + + /** + * Suspending function to get picture from the camera. + * + * Note: On Android platform you must also use [addCameraResultCallback] listener. + * + * @param options camera options + * @return a [Result] class containing the picture or the exception + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun getPicture(options: CameraOptions): Result<String, CameraException> { + return suspendCoroutine { continuation -> + getPicture(options) { + continuation.resume(it) + } + } + } + + /** + * A function to get picture from the camera. + * + * Note: On Android platform you must also use [addCameraResultCallback] listener. + * + * @param options camera options + * @param resultCallback a callback function to get the [Result], containing the picture or the exception + */ + @Suppress("UnsafeCastFromDynamic") + fun getPicture(options: CameraOptions, resultCallback: (Result<String, CameraException>) -> Unit) { + window.localStorage.setItem(CAMERA_ACTIVE_STORAGE_KEY, "true") + addDeviceReadyListener { + window.navigator.asDynamic().camera.getPicture({ image -> + window.localStorage.removeItem(CAMERA_ACTIVE_STORAGE_KEY) + resultCallback(Result.success(image)) + }, { message -> + window.localStorage.removeItem(CAMERA_ACTIVE_STORAGE_KEY) + resultCallback(Result.error(CameraException(message))) + }, options.toJs()) + } + } + + /** + * An Android specific function to get picture from the camera after resume when the application + * webview intent is killed. + * + * @param resultCallback a callback function to get the [Result], containing the picture or the exception + */ + fun addCameraResultCallback(resultCallback: (Result<String, CameraException>) -> Unit) { + addResumeListener { resumeEvent -> + val isCameraActive = window.localStorage.getItem(CAMERA_ACTIVE_STORAGE_KEY) == "true" + if (isCameraActive && resumeEvent.pendingResult != null) { + window.localStorage.removeItem(CAMERA_ACTIVE_STORAGE_KEY) + if (resumeEvent.pendingResult.pluginStatus == CAMERA_STATUS_OK) { + resultCallback(Result.success(resumeEvent.pendingResult.result)) + } else { + resultCallback(Result.error(CameraException(resumeEvent.pendingResult.result))) + } + } + } + } + + /** + * Removes intermediate image files that are kept in the temporary storage after calling [getPicture]. + * + * @param resultCallback an optional callback function to get the [Result] of the cleanup operation. + */ + @Suppress("UnsafeCastFromDynamic") + fun cleanup(resultCallback: ((Result<String, CameraException>) -> Unit)? = null) { + addDeviceReadyListener { + window.navigator.asDynamic().camera.cleanup({ + resultCallback?.invoke(Result.success(CAMERA_STATUS_OK)) + }, { message -> + resultCallback?.invoke(Result.error(CameraException(message))) + }) + } + } +} + +internal fun Camera.DestinationType.toJs(): dynamic = when (this) { + Camera.DestinationType.DATA_URL -> js("Camera.DestinationType.DATA_URL") + Camera.DestinationType.FILE_URI -> js("Camera.DestinationType.FILE_URI") + Camera.DestinationType.NATIVE_URI -> js("Camera.DestinationType.NATIVE_URI") +} + +internal fun Camera.EncodingType.toJs(): dynamic = when (this) { + Camera.EncodingType.JPEG -> js("Camera.EncodingType.JPEG") + Camera.EncodingType.PNG -> js("Camera.EncodingType.PNG") +} + +internal fun Camera.MediaType.toJs(): dynamic = when (this) { + Camera.MediaType.PICTURE -> js("Camera.MediaType.PICTURE") + Camera.MediaType.VIDEO -> js("Camera.MediaType.VIDEO") + Camera.MediaType.ALLMEDIA -> js("Camera.MediaType.ALLMEDIA") +} + +internal fun Camera.PictureSourceType.toJs(): dynamic = when (this) { + Camera.PictureSourceType.PHOTOLIBRARY -> js("Camera.PictureSourceType.PHOTOLIBRARY") + Camera.PictureSourceType.CAMERA -> js("Camera.PictureSourceType.CAMERA") + Camera.PictureSourceType.SAVEDPHOTOALBUM -> js("Camera.PictureSourceType.SAVEDPHOTOALBUM") +} + +internal fun Camera.PopoverArrowDirection.toJs(): dynamic = when (this) { + Camera.PopoverArrowDirection.ARROW_UP -> js("Camera.PopoverArrowDirection.ARROW_UP") + Camera.PopoverArrowDirection.ARROW_DOWN -> js("Camera.PopoverArrowDirection.ARROW_DOWN") + Camera.PopoverArrowDirection.ARROW_LEFT -> js("Camera.PopoverArrowDirection.ARROW_LEFT") + Camera.PopoverArrowDirection.ARROW_RIGHT -> js("Camera.PopoverArrowDirection.ARROW_RIGHT") + Camera.PopoverArrowDirection.ARROW_ANY -> js("Camera.PopoverArrowDirection.ARROW_ANY") +} + +internal fun Camera.Direction.toJs(): dynamic = when (this) { + Camera.Direction.BACK -> js("Camera.Direction.BACK") + Camera.Direction.FRONT -> js("Camera.Direction.FRONT") +} + +internal external class CameraPopoverOptions( + x: Int, + y: Int, + width: Int, + height: Int, + arrowDir: dynamic, + popoverWidth: Int, + popoverHeight: Int +) + +internal fun Camera.CameraPopoverOptions.toJs(): dynamic { + return CameraPopoverOptions(x, y, width, height, arrowDir.toJs(), popoverWidth, popoverHeight) +} + +/** + * Camera options. + */ +data class CameraOptions( + val quality: Int? = null, + val destinationType: Camera.DestinationType? = null, + val sourceType: Camera.PictureSourceType? = null, + val allowEdit: Boolean? = null, + val encodingType: Camera.EncodingType? = null, + val targetWidth: Int? = null, + val targetHeight: Int? = null, + val mediaType: Camera.MediaType? = null, + val correctOrientation: Boolean? = null, + val saveToPhotoAlbum: Boolean? = null, + val popoverOptions: Camera.CameraPopoverOptions? = null, + val cameraDirection: Camera.Direction? = null +) + +@Suppress("ComplexMethod") +internal fun CameraOptions.toJs(): dynamic { + return obj { + if (quality != null) this.quality = quality + if (destinationType != null) this.destinationType = destinationType.toJs() + if (sourceType != null) this.sourceType = sourceType.toJs() + if (allowEdit != null) this.allowEdit = allowEdit + if (encodingType != null) this.encodingType = encodingType.toJs() + if (targetWidth != null) this.targetWidth = targetWidth + if (targetHeight != null) this.targetHeight = targetHeight + if (mediaType != null) this.mediaType = mediaType.toJs() + if (correctOrientation != null) this.correctOrientation = correctOrientation + if (saveToPhotoAlbum != null) this.saveToPhotoAlbum = saveToPhotoAlbum + if (popoverOptions != null) this.popoverOptions = popoverOptions.toJs() + if (cameraDirection != null) this.cameraDirection = cameraDirection.toJs() + } +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt new file mode 100644 index 00000000..d83b79d4 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import org.w3c.dom.events.Event +import kotlin.browser.document +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Device information class. + */ +external class Device { + val cordova: String + val model: String + val platform: String + val uuid: String + val version: String + val manufacturer: String + val isVirtual: Boolean + val serial: String +} + +/** + * Pending result class. + */ +external class PendingResult { + val pluginServiceName: String + val pluginStatus: String + val result: dynamic +} + +/** + * Resume event class. + */ +external class ResumeEvent { + val action: String? + val pendingResult: PendingResult? +} + +/** + * Cordova event types. + */ +enum class CordovaEvent(internal val type: String) { + DEVICEREADY("deviceready"), + PAUSE("pause"), + RESUME("resume"), + BACKBUTTON("backbutton"), + MENUBUTTON("menubutton"), + SEARCHBUTTON("searchbutton"), + STARTCALLBUTTON("startcallbutton"), + ENDCALLBUTTON("endcallbutton"), + VOLUMEDOWNBUTTON("volumedownbutton"), + VOLUMEUPBUTTON("volumeupbutton"), + ACTIVATED("activated") +} + +private external val device: Device + +/** + * Cordova device information object. + */ +var cordovaDevice: Device? = null + private set + +/** + * Add listeners for 'deviceready' Cordova event. + */ +fun addDeviceReadyListener(listener: (Device) -> Unit) { + document.addEventListener(CordovaEvent.DEVICEREADY.type, { + listener(device) + }, false) +} + +/** + * Add listeners for 'pause' Cordova event. + */ +fun addPauseListener(listener: () -> Unit) { + addDeviceReadyListener { + document.addEventListener(CordovaEvent.PAUSE.type, { + listener() + }, false) + } +} + +/** + * Add listeners for 'resume' Cordova event. + */ +fun addResumeListener(listener: (ResumeEvent) -> Unit) { + addDeviceReadyListener { + document.addEventListener(CordovaEvent.RESUME.type, { e -> + @Suppress("UnsafeCastFromDynamic") + listener(e.asDynamic()) + }, false) + } +} + +/** + * Add listeners for a Cordova events. + */ +fun addCordovaEventListener(event: CordovaEvent, listener: (Event) -> Unit) { + addDeviceReadyListener { + document.addEventListener(event.type, { e -> + listener(e) + }, false) + } +} + +/** + * Suspending function to return device information object. + */ +suspend fun getDevice(): Device { + return cordovaDevice ?: suspendCoroutine { continuation -> + addDeviceReadyListener { + cordovaDevice = device + continuation.resume(device) + } + } +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt new file mode 100644 index 00000000..6e7ba732 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt @@ -0,0 +1,542 @@ +@file:Suppress( + "TooManyFunctions", "TooGenericExceptionCaught" +) + +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import org.khronos.webgl.ArrayBuffer +import org.khronos.webgl.Uint8Array +import org.khronos.webgl.set +import org.w3c.files.Blob +import org.w3c.files.BlobPropertyBag +import org.w3c.files.FileReader +import pl.treksoft.kvision.utils.obj +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Exception class for file errors. + */ +class FileException(code: Int) : Exception("Error: $code") + +/** + * System directories class. + */ +external class SystemDirs { + val applicationDirectory: String + val applicationStorageDirectory: String + val dataDirectory: String + val cacheDirectory: String + val externalApplicationStorageDirectory: String? + val externalDataDirectory: String? + val externalCacheDirectory: String? + val externalRootDirectory: String? + val tempDirectory: String? + val syncedDataDirectory: String? + val documentsDirectory: String? + val sharedDirectory: String? +} + +/** + * Main object for Cordova file. + */ +object File { + + const val NOT_FOUND_ERR = 1 + const val SECURITY_ERR = 2 + const val ABORT_ERR = 3 + const val NOT_READABLE_ERR = 4 + const val ENCODING_ERR = 5 + const val NO_MODIFICATION_ALLOWED_ERR = 6 + const val INVALID_STATE_ERR = 7 + const val SYNTAX_ERR = 8 + const val INVALID_MODIFICATION_ERR = 9 + const val QUOTA_EXCEEDED_ERR = 10 + const val TYPE_MISMATCH_ERR = 11 + const val PATH_EXISTS_ERR = 12 + + /** + * File system types. + */ + enum class FileSystemType { + TEMPORARY, + PERSISTENT + } + + /** + * Get system directories. + */ + suspend fun getSystemDirectories(): SystemDirs { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + continuation.resume(window.asDynamic().cordova.file) + } + } + } + + /** + * Resolve given path to a file or directory entry. + * @param url directory path + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun resolveLocalFileSystemURL(url: String): Result<Entry, FileException> { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + window.asDynamic().resolveLocalFileSystemURL(url, { entry -> + continuation.resume(Result.success(entry)) + }, { error -> + continuation.resume(Result.error(FileException(error.code))) + }) + } + } + } + + /** + * Resolve given path to a directory entry. + * @param url directory path + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun resolveLocalFileSystemURLForDir(url: String): Result<DirectoryEntry, FileException> { + return when (val result = resolveLocalFileSystemURL(url)) { + is Result.Success -> { + if (result.value.isDirectory) { + result.map { + @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") + it as DirectoryEntry + } + } else { + Result.error(FileException(TYPE_MISMATCH_ERR)) + } + } + is Result.Failure -> result + } + } + + /** + * Resolve given path to a file entry. + * @param url file path + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun resolveLocalFileSystemURLForFile(url: String): Result<FileEntry, FileException> { + return when (val result = resolveLocalFileSystemURL(url)) { + is Result.Success -> { + if (result.value.isFile) { + result.map { + @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") + it as FileEntry + } + } else { + Result.error(FileException(TYPE_MISMATCH_ERR)) + } + } + is Result.Failure -> result + } + } + + /** + * Request a file system of a given type. + * @param fileSystemType file system type (temporary or persistent) + * @param size file system size (quota) + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun requestFileSystem( + fileSystemType: File.FileSystemType, + size: Long = 0 + ): Result<FileSystem, FileException> { + return suspendCoroutine { continuation -> + val type = when (fileSystemType) { + File.FileSystemType.TEMPORARY -> LocalFileSystem.TEMPORARY + File.FileSystemType.PERSISTENT -> LocalFileSystem.PERSISTENT + } + addDeviceReadyListener { + window.asDynamic().requestFileSystem(type, size, { fs -> + continuation.resume(Result.success(fs)) + }, { error -> + continuation.resume(Result.error(FileException(error.code))) + }) + } + } + } + + internal fun dataURLtoBlob(dataURI: String): Blob { + val byteString = window.atob(dataURI.split(',')[1]) + val mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] + val ab = ArrayBuffer(byteString.length) + val ia = Uint8Array(ab) + for (i in 0 until byteString.length) { + ia[i] = byteString[i].toByte() + } + return Blob(arrayOf(ab), BlobPropertyBag(type = mimeString)) + } + +} + +/** + * Extension function to convert String to a directory entry. + */ +suspend fun String.toDirectoryEntry(): Result<DirectoryEntry, FileException> { + return File.resolveLocalFileSystemURLForDir(this) +} + +/** + * Extension function to convert String to a file entry. + */ +suspend fun String.toFileEntry(): Result<FileEntry, FileException> { + return File.resolveLocalFileSystemURLForFile(this) +} + +/** + * Get file or directory metadata. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun Entry.getMetadata(): Result<Metadata, FileException> { + return suspendCoroutine { continuation -> + this.getMetadata({ metadata: Metadata -> + continuation.resume(Result.success(metadata)) + } as MetadataCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Get file or directory parent directory entry. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun Entry.getParent(): Result<DirectoryEntry, FileException> { + return suspendCoroutine { continuation -> + this.getParent({ directoryEntry: DirectoryEntry -> + continuation.resume(Result.success(directoryEntry)) + } as DirectoryEntryCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Remove given file or directory. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun Entry.remove(): Result<Entry, FileException> { + return suspendCoroutine { continuation -> + this.remove({ + continuation.resume(Result.success(this)) + } as VoidCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Move given file or directory to a new location. + * @param parent new location parent directory entry + * @param newName new location name + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun Entry.moveTo(parent: DirectoryEntry, newName: String? = null): Result<Entry, FileException> { + return suspendCoroutine { continuation -> + this.moveTo(parent, newName, { entry: Entry -> + continuation.resume(Result.success(entry)) + } as EntryCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Copy given file or directory to a new location. + * @param parent new location parent directory entry + * @param newName new location name + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun Entry.copyTo(parent: DirectoryEntry, newName: String? = null): Result<Entry, FileException> { + return suspendCoroutine { continuation -> + this.copyTo(parent, newName, { entry: Entry -> + continuation.resume(Result.success(entry)) + } as EntryCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Create a FileWriter object for a given file entry. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun FileEntry.createWriter(): Result<FileWriter, FileException> { + return suspendCoroutine { continuation -> + this.createWriter({ fileWriter: FileWriter -> + continuation.resume(Result.success(fileWriter)) + } as FileWriterCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Get a File object for a given file entry. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun FileEntry.file(): Result<org.w3c.files.File, FileException> { + return suspendCoroutine { continuation -> + this.file({ file: org.w3c.files.File -> + continuation.resume(Result.success(file)) + } as FileCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Get or create a file in a given parent directory. + * @param path target file path + * @param create Used to indicate that the user wants to create a file if it was not previously there. + * @param exclusive Fail if the target path file already exists. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun DirectoryEntry.getFile( + path: String, + create: Boolean = true, + exclusive: Boolean = false +): Result<FileEntry, FileException> { + return suspendCoroutine { continuation -> + this.getFile(path, obj { + this.create = create + this.exclusive = exclusive + }, { fileEntry: FileEntry -> + continuation.resume(Result.success(fileEntry)) + } as FileEntryCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Get or create a directory in a given parent directory. + * @param path target directory path + * @param create Used to indicate that the user wants to create a directory if it was not previously there. + * @param exclusive Fail if the target path directory already exists. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun DirectoryEntry.getDirectory( + path: String, + create: Boolean = true, + exclusive: Boolean = false +): Result<DirectoryEntry, FileException> { + return suspendCoroutine { continuation -> + this.getDirectory(path, obj { + this.create = create + this.exclusive = exclusive + }, { directoryEntry: DirectoryEntry -> + continuation.resume(Result.success(directoryEntry)) + } as DirectoryEntryCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * Remove given directory recursively. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun DirectoryEntry.removeRecursively(): Result<DirectoryEntry, FileException> { + return suspendCoroutine { continuation -> + this.removeRecursively({ + continuation.resume(Result.success(this)) + } as VoidCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * List directory entries for a given DirectoryReader. + */ +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic") +suspend fun DirectoryReader.readEntries(): Result<List<Entry>, FileException> { + return suspendCoroutine { continuation -> + this.readEntries({ entries: Array<Entry> -> + continuation.resume(Result.success(entries.toList())) + } as EntriesCallback, { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } as ErrorCallback) + } +} + +/** + * List directory entries for a given parent directory entry. + */ +suspend fun DirectoryEntry.readEntries(): Result<List<Entry>, FileException> { + return this.createReader().readEntries() +} + +/** + * Read file content as a plain string. + */ +@Suppress("UnsafeCastFromDynamic") +suspend fun FileEntry.readAsText(): Result<String, FileException> { + return this.file().flatMap { file -> + suspendCoroutine<Result<String, FileException>> { continuation -> + val reader = FileReader() + reader.onloadend = { e -> + continuation.resume(Result.success(e.target.asDynamic().result)) + } + reader.onerror = { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } + reader.readAsText(file) + } + } +} + +/** + * Read file content as a data url. + */ +@Suppress("UnsafeCastFromDynamic") +suspend fun FileEntry.readAsDataURL(): Result<String, FileException> { + return this.file().flatMap { file -> + suspendCoroutine<Result<String, FileException>> { continuation -> + val reader = FileReader() + reader.onloadend = { e -> + continuation.resume(Result.success(e.target.asDynamic().result)) + } + reader.onerror = { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } + reader.readAsDataURL(file) + } + } +} + +/** + * Read file content as an array buffer. + */ +@Suppress("UnsafeCastFromDynamic") +suspend fun FileEntry.readAsArrayBuffer(): Result<ArrayBuffer, FileException> { + return this.file().flatMap { file -> + suspendCoroutine<Result<ArrayBuffer, FileException>> { continuation -> + val reader = FileReader() + reader.onloadend = { e -> + continuation.resume(Result.success(e.target.asDynamic().result)) + } + reader.onerror = { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } + reader.onabort = { _ -> + continuation.resume(Result.error(FileException(File.ABORT_ERR))) + } + reader.readAsArrayBuffer(file) + } + } +} + +/** + * Write file content from a Blob. + * @param data a data Blob to be written. + */ +@Suppress("UnsafeCastFromDynamic") +suspend fun FileEntry.write(data: Blob): Result<FileEntry, FileException> { + return this.createWriter().flatMap { writer -> + suspendCoroutine<Result<FileEntry, FileException>> { continuation -> + writer.onwriteend = { + continuation.resume(Result.success(this)) + } + writer.onerror = { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } + writer.onabort = { _ -> + continuation.resume(Result.error(FileException(File.ABORT_ERR))) + } + writer.write(data) + } + } +} + +/** + * Write file content from a plain string. + * @param data a data string to be written. + */ +suspend fun FileEntry.write(data: String): Result<FileEntry, FileException> { + return this.write(Blob(arrayOf(data))) +} + +/** + * Write file content from a data url. + * @param dataUrl a data url to be written. + */ +suspend fun FileEntry.writeDataUrL(dataUrl: String): Result<FileEntry, FileException> { + return this.write(File.dataURLtoBlob(dataUrl)) +} + +/** + * Write file content from an array buffer. + * @param data an array buffer to be written. + */ +suspend fun FileEntry.write(data: ArrayBuffer): Result<FileEntry, FileException> { + return this.write(data.toBlob()) +} + +/** + * Append file content from a Blob. + * @param data a data Blob to be appended. + */ +@Suppress("UnsafeCastFromDynamic") +suspend fun FileEntry.append(data: Blob): Result<FileEntry, FileException> { + return this.createWriter().flatMap { writer -> + try { + writer.seek(writer.length) + } catch (e: Exception) { + console.log("File doesn't exist!") + } + suspendCoroutine<Result<FileEntry, FileException>> { continuation -> + writer.onwriteend = { + continuation.resume(Result.success(this)) + } + writer.onerror = { error: dynamic -> + continuation.resume(Result.error(FileException(error.code))) + } + writer.onabort = { _ -> + continuation.resume(Result.error(FileException(File.ABORT_ERR))) + } + writer.write(data) + } + } +} + +/** + * Append file content from a plain string. + * @param data a string to be appended. + */ +suspend fun FileEntry.append(data: String): Result<FileEntry, FileException> { + return this.append(Blob(arrayOf(data))) +} + +/** + * Convert array buffer to a blob. + */ +fun ArrayBuffer.toBlob(): Blob { + return Blob(arrayOf(Uint8Array(this))) +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt new file mode 100644 index 00000000..48f4f3d2 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt @@ -0,0 +1,221 @@ +@file:Suppress( + "INTERFACE_WITH_SUPERCLASS", + "OVERRIDING_FINAL_MEMBER", + "RETURN_TYPE_MISMATCH_ON_OVERRIDE", + "CONFLICTING_OVERLOADS", + "EXTERNAL_DELEGATION", + "NESTED_CLASS_IN_EXTERNAL_INTERFACE", + "unused", "PropertyName", "TooManyFunctions", "VariableNaming", "MaxLineLength" +) + +package pl.treksoft.kvision.cordova + +import org.w3c.files.File +import kotlin.js.Date + +external object LocalFileSystem { + var TEMPORARY: Number + var PERSISTENT: Number + fun requestFileSystem( + type: Number, + size: Number, + successCallback: FileSystemCallback, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) + + fun resolveLocalFileSystemURL( + url: String, + successCallback: EntryCallback, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) + + fun webkitRequestFileSystem( + type: Number, + size: Number, + successCallback: FileSystemCallback, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) +} + +external object LocalFileSystemSync { + var TEMPORARY: Number + var PERSISTENT: Number + fun requestFileSystemSync(type: Number, size: Number): FileSystemSync + fun resolveLocalFileSystemSyncURL(url: String): EntrySync + fun webkitRequestFileSystemSync(type: Number, size: Number): FileSystemSync +} + +external interface Metadata { + var modificationTime: Date + var size: Number +} + +external interface Flags { + var create: Boolean? get() = definedExternally; set(value) = definedExternally + var exclusive: Boolean? get() = definedExternally; set(value) = definedExternally +} + +external interface FileSystem { + var name: String + var root: DirectoryEntry +} + +external interface Entry { + var isFile: Boolean + var isDirectory: Boolean + fun getMetadata(successCallback: MetadataCallback, errorCallback: ErrorCallback? = definedExternally /* null */) + var name: String + var fullPath: String + var filesystem: FileSystem + fun moveTo( + parent: DirectoryEntry, + newName: String? = definedExternally /* null */, + successCallback: EntryCallback? = definedExternally /* null */, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) + + fun copyTo( + parent: DirectoryEntry, + newName: String? = definedExternally /* null */, + successCallback: EntryCallback? = definedExternally /* null */, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) + + fun toURL(): String + fun toInternalURL(): String + fun remove(successCallback: VoidCallback, errorCallback: ErrorCallback? = definedExternally /* null */) + fun getParent(successCallback: DirectoryEntryCallback, errorCallback: ErrorCallback? = definedExternally /* null */) +} + +external interface DirectoryEntry : Entry { + fun createReader(): DirectoryReader + fun getFile( + path: String, + options: Flags? = definedExternally /* null */, + successCallback: FileEntryCallback?, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) + + fun getDirectory( + path: String, + options: Flags? = definedExternally /* null */, + successCallback: DirectoryEntryCallback?, + errorCallback: ErrorCallback? = definedExternally /* null */ + ) + + fun removeRecursively(successCallback: VoidCallback, errorCallback: ErrorCallback? = definedExternally /* null */) +} + +external interface DirectoryReader { + fun readEntries(successCallback: EntriesCallback, errorCallback: ErrorCallback? = definedExternally /* null */) +} + +external interface FileEntry : Entry { + fun createWriter(successCallback: FileWriterCallback, errorCallback: ErrorCallback? = definedExternally /* null */) + fun file(successCallback: FileCallback, errorCallback: ErrorCallback? = definedExternally /* null */) +} + +external interface FileSystemCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun FileSystemCallback.invoke(filesystem: FileSystem) { + asDynamic()(filesystem) +} + +external interface EntryCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun EntryCallback.invoke(entry: Entry) { + asDynamic()(entry) +} + +external interface FileEntryCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun FileEntryCallback.invoke(entry: FileEntry) { + asDynamic()(entry) +} + +external interface DirectoryEntryCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun DirectoryEntryCallback.invoke(entry: DirectoryEntry) { + asDynamic()(entry) +} + +external interface EntriesCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun EntriesCallback.invoke(entries: Array<Entry>) { + asDynamic()(entries) +} + +external interface MetadataCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun MetadataCallback.invoke(metadata: Metadata) { + asDynamic()(metadata) +} + +external interface FileWriterCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun FileWriterCallback.invoke(fileWriter: FileWriter) { + asDynamic()(fileWriter) +} + +external interface FileCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun FileCallback.invoke(file: File) { + asDynamic()(file) +} + +external interface VoidCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun VoidCallback.invoke() { + asDynamic()() +} + +external interface ErrorCallback + +@Suppress("NOTHING_TO_INLINE") +inline operator fun ErrorCallback.invoke(err: Error) { + asDynamic()(err) +} + +external interface FileSystemSync { + var name: String + var root: DirectoryEntrySync +} + +external interface EntrySync { + var isFile: Boolean + var isDirectory: Boolean + fun getMetadata(): Metadata + var name: String + var fullPath: String + var filesystem: FileSystemSync + fun moveTo(parent: DirectoryEntrySync, newName: String? = definedExternally /* null */): EntrySync + fun copyTo(parent: DirectoryEntrySync, newName: String? = definedExternally /* null */): EntrySync + fun toURL(): String + fun remove() + fun getParent(): DirectoryEntrySync +} + +external interface DirectoryEntrySync : EntrySync { + fun createReader(): DirectoryReaderSync + fun getFile(path: String, options: Flags? = definedExternally /* null */): FileEntrySync + fun getDirectory(path: String, options: Flags? = definedExternally /* null */): DirectoryEntrySync + fun removeRecursively() +} + +external interface DirectoryReaderSync { + fun readEntries(): Array<EntrySync> +} + +external interface FileEntrySync : EntrySync { + fun createWriter(): FileWriterSync + fun file(): File +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt new file mode 100644 index 00000000..26215aa8 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt @@ -0,0 +1,47 @@ +@file:Suppress( + "INTERFACE_WITH_SUPERCLASS", + "OVERRIDING_FINAL_MEMBER", + "RETURN_TYPE_MISMATCH_ON_OVERRIDE", + "CONFLICTING_OVERLOADS", + "EXTERNAL_DELEGATION", + "NESTED_CLASS_IN_EXTERNAL_INTERFACE", + "unused", "PropertyName", "TooManyFunctions", "VariableNaming", "MaxLineLength" +) + +package pl.treksoft.kvision.cordova + +import org.w3c.dom.events.EventTarget +import org.w3c.files.Blob +import org.w3c.xhr.ProgressEvent + +external interface FileSaver : EventTarget { + fun abort() + var INIT: Number + var WRITING: Number + var DONE: Number + var readyState: Number + var error: Error + var onwritestart: (event: ProgressEvent) -> Unit + var onprogress: (event: ProgressEvent) -> Unit + var onwrite: (event: ProgressEvent) -> Unit + var onabort: (event: ProgressEvent) -> Unit + var onerror: (event: ProgressEvent) -> Unit + var onwriteend: (event: ProgressEvent) -> Unit + +} + +external interface FileWriter : FileSaver { + var position: Number + var length: Number + fun write(data: Blob) + fun seek(offset: Number) + fun truncate(size: Number) +} + +external interface FileWriterSync { + var position: Number + var length: Number + fun write(data: Blob) + fun seek(offset: Number) + fun truncate(size: Number) +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt new file mode 100644 index 00000000..c4ccd354 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import pl.treksoft.kvision.utils.obj +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Geolocation coordinates values. + */ +external class Coordinates { + val latitude: Number + val longitude: Number + val altitude: Number + val accuracy: Number + val altitudeAccuracy: Number? + val heading: Number? + val speed: Number +} + +/** + * Geolocation position value. + */ +external class Position { + val coords: Coordinates + val timestamp: Number +} + +/** + * Geolocaton error codes. + */ +enum class PositionError { + PERMISSION_DENIED, + POSITION_UNAVAILABLE, + TIMEOUT +} + +private fun codeToEnum(code: Int): PositionError { + return when (code) { + 1 -> PositionError.PERMISSION_DENIED + 2 -> PositionError.POSITION_UNAVAILABLE + else -> PositionError.TIMEOUT + } +} + +/** + * Exception class for geolocation errors. + */ +class GeolocationException(val code: PositionError, message: String) : Exception(message) + +/** + * Main geolocation object based on webview api. + */ +object Geolocation { + + /** + * Get current location. + * @param enableHighAccuracy provides a hint that the application needs the best possible results + * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result + * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun getCurrentPosition( + enableHighAccuracy: Boolean = true, + timeout: Number? = null, + maximumAge: Number? = null + ): Result<Position, GeolocationException> { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + window.navigator.asDynamic().geolocation.getCurrentPosition({ position: Position -> + continuation.resume(Result.success(position)) + }, { error -> + continuation.resume(Result.error(GeolocationException(codeToEnum(error.code), error.message))) + }, obj { + this.enableHighAccuracy = enableHighAccuracy + if (timeout != null) this.timeout = timeout + if (maximumAge != null) this.maximumAge = maximumAge + }) + } + } + } + + /** + * Watch location changes. + * @param enableHighAccuracy provides a hint that the application needs the best possible results + * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result + * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds + * @param resultCallback a callback function receiving the result + * @return watch identifier (can be removed with a [clearWatch] function) + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun watchPosition( + enableHighAccuracy: Boolean = true, + timeout: Number? = null, + maximumAge: Number? = null, + resultCallback: (Result<Position, GeolocationException>) -> Unit + ): String? { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + val watchId = window.navigator.asDynamic().geolocation.watchPosition({ position: Position -> + resultCallback(Result.success(position)) + }, { error -> + resultCallback(Result.error(GeolocationException(codeToEnum(error.code), error.message))) + }, obj { + this.enableHighAccuracy = enableHighAccuracy + if (timeout != null) this.timeout = timeout + if (maximumAge != null) this.maximumAge = maximumAge + }) + continuation.resume(watchId) + } + } + } + + /** + * Clear the given watch. + * @param watchId watch identifier returned from [watchPosition] function + */ + @Suppress("UnsafeCastFromDynamic") + fun clearWatch(watchId: String) { + if (window.navigator.asDynamic().geolocation != null) { + window.navigator.asDynamic().geolocation.clearWatch(watchId) + } + } + +} + +/** + * Main geolocation object based on Google location services api. + */ +object Locationservices { + + /** + * Location services priority. + */ + enum class Priority { + PRIORITY_HIGH_ACCURACY, + PRIORITY_BALANCED_POWER_ACCURACY, + PRIORITY_LOW_POWER, + PRIORITY_NO_POWER + } + + /** + * Get current location. + * @param enableHighAccuracy provides a hint that the application needs the best possible results + * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result + * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds + * @param priority the priority of the request is a strong hint for which location sources to use + * @param interval set the desired interval for active location updates, in milliseconds + * @param fastInterval explicitly set the fastest interval for location updates, in milliseconds + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun getCurrentPosition( + enableHighAccuracy: Boolean = true, + timeout: Number? = null, + maximumAge: Number? = null, + priority: Priority? = null, + interval: Number? = null, + fastInterval: Number? = null + ): Result<Position, GeolocationException> { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + window.asDynamic() + .cordova.plugins.locationServices.geolocation.getCurrentPosition({ position: Position -> + continuation.resume(Result.success(position)) + }, { error -> + continuation.resume(Result.error(GeolocationException(codeToEnum(error.code), error.message))) + }, obj { + this.enableHighAccuracy = enableHighAccuracy + if (timeout != null) this.timeout = timeout + if (maximumAge != null) this.maximumAge = maximumAge + if (priority != null) this.priority = getJsPriority(priority) + if (interval != null) this.interval = interval + if (fastInterval != null) this.fastInterval = fastInterval + }) + } + } + } + + /** + * Watch location changes. + * @param enableHighAccuracy provides a hint that the application needs the best possible results + * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result + * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds + * @param priority the priority of the request is a strong hint for which location sources to use + * @param interval set the desired interval for active location updates, in milliseconds + * @param fastInterval explicitly set the fastest interval for location updates, in milliseconds + * @param resultCallback a callback function receiving the result + * @return watch identifier (can be removed with a [clearWatch] function) + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun watchPosition( + enableHighAccuracy: Boolean = true, + timeout: Number? = null, + maximumAge: Number? = null, + priority: Priority? = null, + interval: Number? = null, + fastInterval: Number? = null, + resultCallback: (Result<Position, GeolocationException>) -> Unit + ): String? { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + val watchId = + window.asDynamic() + .cordova.plugins.locationServices.geolocation.watchPosition({ position: Position -> + resultCallback(Result.success(position)) + }, { error -> + resultCallback(Result.error(GeolocationException(codeToEnum(error.code), error.message))) + }, obj { + this.enableHighAccuracy = enableHighAccuracy + if (timeout != null) this.timeout = timeout + if (maximumAge != null) this.maximumAge = maximumAge + if (priority != null) this.priority = getJsPriority(priority) + if (interval != null) this.interval = interval + if (fastInterval != null) this.fastInterval = fastInterval + }) + continuation.resume(watchId) + } + } + } + + /** + * Clear the given watch. + * @param watchId watch identifier returned from [watchPosition] function + */ + @Suppress("UnsafeCastFromDynamic") + fun clearWatch(watchId: String) { + if (window.asDynamic().cordova.plugins.locationServices.geolocation != null) { + window.asDynamic().cordova.plugins.locationServices.geolocation.clearWatch(watchId) + } + } + + @Suppress("MagicNumber") + private fun getJsPriority(priority: Priority): dynamic { + return when (priority) { + Priority.PRIORITY_HIGH_ACCURACY -> 100 + Priority.PRIORITY_BALANCED_POWER_ACCURACY -> 102 + Priority.PRIORITY_LOW_POWER -> 104 + Priority.PRIORITY_NO_POWER -> 105 + } + } + +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt new file mode 100644 index 00000000..c0c8f0b4 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * InAppBrowser event type. + */ +external class InAppBrowserEvent { + val type: String + val url: String + val code: Number? + val message: String? + val data: String? +} + +/** + * InAppBrowser reference object. + */ +external class Browser { + fun addEventListener(eventname: String, callback: (InAppBrowserEvent) -> Unit) + fun removeEventListener(eventname: String, callback: (InAppBrowserEvent) -> Unit) + fun close() + fun show() + fun hide() + fun executeScript(details: dynamic, callback: ((dynamic) -> Unit) = definedExternally) + fun insertCSS(details: dynamic, callback: (() -> Unit) = definedExternally) +} + +/** + * Main object for Cordova InAppBrowser api. + */ +object InAppBrowser { + + /** + * Open new browser window. + * @param url an URL address + * @param target a target window + * @param options a string with window options + * @return a browser window reference + */ + suspend fun open(url: String, target: String = "_blank", options: String? = null): Browser { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + val ref = window.asDynamic().cordova.InAppBrowser.open(url, target, options) + continuation.resume(ref) + } + } + } + +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt new file mode 100644 index 00000000..a126941b --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Media error class. + */ +external class MediaError { + val code: Int + val message: String + + companion object { + val MEDIA_ERR_ABORTED: Int + val MEDIA_ERR_NETWORK: Int + val MEDIA_ERR_DECODE: Int + val MEDIA_ERR_NONE_SUPPORTED: Int + } +} + +/** + * Cordova media class. + */ +@Suppress("TooManyFunctions") +external class Media( + src: String, + mediaSuccess: (() -> Unit)? = definedExternally, + mediaError: ((MediaError) -> Unit)? = definedExternally, + mediaStatus: ((Int) -> Unit)? = definedExternally +) { + + fun play() + fun pause() + fun stop() + fun startRecord() + fun pauseRecord() + fun resumeRecord() + fun stopRecord() + fun seekTo(millis: Number) + fun setRate(rate: Number) + fun setVolume(volume: Number) + fun getDuration(): Number + fun getCurrentAmplitude(mediaSuccess: (Number) -> Unit, mediaError: ((Int) -> Unit)? = definedExternally) + fun getCurrentPosition(mediaSuccess: (Number) -> Unit, mediaError: ((Int) -> Unit)? = definedExternally) + fun release() + + val position: Number + val duration: Number + + companion object { + val MEDIA_NONE: Int + val MEDIA_STARTING: Int + val MEDIA_RUNNING: Int + val MEDIA_PAUSED: Int + val MEDIA_STOPPED: Int + } +} + +/** + * Returns the current amplitude within an audio file. + */ +suspend fun Media.getCurrentAmplitude(): Number { + return suspendCoroutine { continuation -> + this.getCurrentAmplitude({ amp -> + continuation.resume(amp) + }, { + continuation.resume(-1) + }) + } +} + +/** + * Returns the current position within an audio file. + */ +suspend fun Media.getCurrentPosition(): Number { + return suspendCoroutine { continuation -> + this.getCurrentPosition({ pos -> + continuation.resume(pos) + }, { + continuation.resume(-1) + }) + } +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt new file mode 100644 index 00000000..fda81da7 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import pl.treksoft.kvision.utils.obj +import kotlin.browser.document +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Media error class. + */ +internal external class CaptureError { + val code: Int + + companion object { + val CAPTURE_INTERNAL_ERR: Int + val CAPTURE_APPLICATION_BUSY: Int + val CAPTURE_INVALID_ARGUMENT: Int + val CAPTURE_NO_MEDIA_FILES: Int + val CAPTURE_PERMISSION_DENIED: Int + val CAPTURE_NOT_SUPPORTED: Int + } +} + +/** + * Media file details class. + */ +external class MediaFileData { + val codecs: String? + val bitrate: Number + val height: Number + val width: Number + val duration: Number +} + +/** + * Media file information class. + */ +external class MediaFile { + val name: String + val fullPath: String + val type: String + val lastModifiedDate: Number + val size: Number + + fun getFormatData(successCallback: (MediaFileData) -> Unit, errorCallback: (() -> Unit)? = definedExternally) +} + +/** + * Get details for the given file. + */ +suspend fun MediaFile.getFormatData(): MediaFileData? { + return suspendCoroutine { continuation -> + this.getFormatData({ + continuation.resume(it) + }, { + continuation.resume(null) + }) + } +} + +/** + * Exception class for media capture errors. + */ +class CaptureException(val code: MediaCapture.CaptureErrorCode) : Exception("Capture exception: $code") + +/** + * Main media capture object. + */ +object MediaCapture { + + enum class CaptureErrorCode { + CAPTURE_INTERNAL_ERR, + CAPTURE_APPLICATION_BUSY, + CAPTURE_INVALID_ARGUMENT, + CAPTURE_NO_MEDIA_FILES, + CAPTURE_PERMISSION_DENIED, + CAPTURE_NOT_SUPPORTED + } + + @Suppress("MagicNumber") + private fun codeToEnum(code: Int): CaptureErrorCode { + return when (code) { + 0 -> CaptureErrorCode.CAPTURE_INTERNAL_ERR + 1 -> CaptureErrorCode.CAPTURE_APPLICATION_BUSY + 2 -> CaptureErrorCode.CAPTURE_INVALID_ARGUMENT + 3 -> CaptureErrorCode.CAPTURE_NO_MEDIA_FILES + 4 -> CaptureErrorCode.CAPTURE_PERMISSION_DENIED + else -> CaptureErrorCode.CAPTURE_NOT_SUPPORTED + } + } + + /** + * Capture audio file(s). + * @param limit The maximum number of audio clips the user can capture in a single capture operation. + * @param duration The maximum duration of an audio clip, in seconds. + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun captureAudio(limit: Number = 1, duration: Number? = null): Result<List<MediaFile>, CaptureException> { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + window.navigator.asDynamic().device.capture.captureAudio({ result: Array<MediaFile> -> + continuation.resume(Result.success(result.asList())) + }, { err: CaptureError -> + continuation.resume(Result.error(CaptureException(codeToEnum(err.code)))) + }, obj { + this.limit = limit + if (duration != null) this.duration = duration + }) + } + } + } + + /** + * Capture image file(s). + * @param limit The maximum number of image files the user can capture in a single capture operation. + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun captureImage(limit: Number = 1): Result<List<MediaFile>, CaptureException> { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + window.navigator.asDynamic().device.capture.captureImage({ result: Array<MediaFile> -> + continuation.resume(Result.success(result.asList())) + }, { err: CaptureError -> + continuation.resume(Result.error(CaptureException(codeToEnum(err.code)))) + }, obj { + this.limit = limit + }) + } + } + } + + /** + * Capture video file(s). + * @param limit The maximum number of video clips the user can capture in a single capture operation. + * @param duration The maximum duration of an video clip, in seconds. + * @param lowQuality Capture video with a lower quality (Android only) + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun captureVideo( + limit: Number = 1, + duration: Number? = null, + lowQuality: Boolean = false + ): Result<List<MediaFile>, CaptureException> { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + window.navigator.asDynamic().device.capture.captureVideo({ result: Array<MediaFile> -> + continuation.resume(Result.success(result.asList())) + }, { err: CaptureError -> + continuation.resume(Result.error(CaptureException(codeToEnum(err.code)))) + }, obj { + this.limit = limit + if (duration != null) this.duration = duration + if (lowQuality) this.quality = 0 + }) + } + } + } + + /** + * Add listeners for pending capture Cordova events. + */ + fun addPendingCaptureListener(listener: (Result<List<MediaFile>, CaptureException>) -> Unit) { + addDeviceReadyListener { + document.addEventListener("pendingcaptureresult", { result -> + @Suppress("CAST_NEVER_SUCCEEDS") + listener(Result.success((result as? Array<MediaFile>)?.asList() ?: listOf())) + }, false) + document.addEventListener("pendingcaptureerror", { error -> + @Suppress("CAST_NEVER_SUCCEEDS") + listener(Result.error(CaptureException(codeToEnum((error as? CaptureError)?.code ?: 0)))) + }, false) + } + } +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt new file mode 100644 index 00000000..eb98f8a4 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.document +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Main object for Cordova network. + */ +object Network { + + /** + * Connection types + */ + enum class ConnectionType { + UNKNOWN, + ETHERNET, + WIFI, + CELL_2G, + CELL_3G, + CELL_4G, + CELL, + NONE + } + + /** + * Network status events + */ + enum class NetworkEvent(internal val type: String) { + OFFLINE("offline"), + ONLINE("online") + } + + /** + * Get network connection type. + */ + suspend fun getConnectionType(): ConnectionType { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + val connectionType = when (window.navigator.asDynamic().connection.type) { + js("Connection.ETHERNET") -> ConnectionType.ETHERNET + js("Connection.WIFI") -> ConnectionType.WIFI + js("Connection.CELL_2G") -> ConnectionType.CELL_2G + js("Connection.CELL_3G") -> ConnectionType.CELL_3G + js("Connection.CELL_4G") -> ConnectionType.CELL_4G + js("Connection.CELL") -> ConnectionType.CELL + js("Connection.NONE") -> ConnectionType.NONE + else -> ConnectionType.UNKNOWN + } + continuation.resume(connectionType) + } + } + } + + /** + * Add listeners for network status Cordova events. + */ + fun addStatusListener(event: NetworkEvent, listener: () -> Unit) { + addDeviceReadyListener { + document.addEventListener(event.type, { + listener() + }, false) + } + } + +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt new file mode 100644 index 00000000..5afddb41 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.window + +/** + * A response object for prompt function callback. + */ +external class PromptResponse { + val buttonIndex: Int + val input1: String +} + +/** + * Main object for Cordova notifications. + */ +object Notification { + + /** + * Show alert dialog. + * @param message a message + * @param title an optional dialog title (defaults to Alert) + * @param buttonLabel an optional button label (defaults to OK) + * @param callback a callback function + */ + @Suppress("UnsafeCastFromDynamic") + fun alert(message: String, title: String? = null, buttonLabel: String? = null, callback: () -> Unit) { + addDeviceReadyListener { + window.navigator.asDynamic().notification.alert(message, callback, title, buttonLabel) + } + } + + /** + * Show confirm dialog. + * @param message a message + * @param title an optional dialog title (defaults to Confirm) + * @param buttonLabels an optional list of button labels (defaults to [OK, Cancel]) + * @param callback a callback function + */ + @Suppress("UnsafeCastFromDynamic") + fun confirm(message: String, title: String? = null, buttonLabels: List<String>? = null, callback: (Int) -> Unit) { + addDeviceReadyListener { + window.navigator.asDynamic().notification.confirm(message, callback, title, buttonLabels?.toTypedArray()) + } + } + + /** + * Show prompt dialog. + * @param message a message + * @param title an optional dialog title (defaults to Prompt) + * @param buttonLabels an optional list of button labels (defaults to [OK, Cancel]) + * @param defaultText default input value (default to empty string) + * @param callback a callback function + */ + @Suppress("UnsafeCastFromDynamic") + fun prompt( + message: String, + title: String? = null, + buttonLabels: List<String>? = null, + defaultText: String? = null, + callback: (PromptResponse) -> Unit + ) { + addDeviceReadyListener { + window.navigator.asDynamic().notification.prompt( + message, + callback, + title, + buttonLabels?.toTypedArray(), + defaultText + ) + } + } + + /** + * Play a beep sound. + * @param times the number of times to repeat the beep (defaults to 1) + */ + @Suppress("UnsafeCastFromDynamic") + fun beep(times: Int = 1) { + addDeviceReadyListener { + window.navigator.asDynamic().notification.beep(times) + } + } + +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt new file mode 100644 index 00000000..83a1987e --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt @@ -0,0 +1,154 @@ +@file:Suppress( + "TooManyFunctions", "TooGenericExceptionCaught" +) +/* + * Source: https://github.com/kittinunf/Result + * + * MIT License + * + * Copyright (c) 2017 Kittinun Vantasin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +inline fun <reified X> Result<*, *>.getAs() = when (this) { + is Result.Success -> value as? X + is Result.Failure -> error as? X +} + +inline fun <V : Any> Result<V, *>.success(f: (V) -> Unit) = fold(f, {}) + +inline fun <E : Exception> Result<*, E>.failure(f: (E) -> Unit) = fold({}, f) + +infix fun <V : Any, E : Exception> Result<V, E>.or(fallback: V) = when (this) { + is Result.Success -> this + else -> Result.Success(fallback) +} + +infix fun <V : Any, E : Exception> Result<V, E>.getOrElse(fallback: V) = when (this) { + is Result.Success -> value + else -> fallback +} + +inline fun <V : Any, U : Any, E : Exception> Result<V, E>.map(transform: (V) -> U): Result<U, E> = try { + when (this) { + is Result.Success -> Result.Success(transform(value)) + is Result.Failure -> Result.Failure(error) + } +} catch (ex: Exception) { + @Suppress("UNCHECKED_CAST") + Result.error(ex as E) +} + +inline fun <V : Any, U : Any, E : Exception> Result<V, E>.flatMap(transform: (V) -> Result<U, E>): Result<U, E> = try { + when (this) { + is Result.Success -> transform(value) + is Result.Failure -> Result.Failure(error) + } +} catch (ex: Exception) { + @Suppress("UNCHECKED_CAST") + Result.error(ex as E) +} + +fun <V : Any, E : Exception, E2 : Exception> Result<V, E>.mapError(transform: (E) -> E2) = when (this) { + is Result.Success -> Result.Success(value) + is Result.Failure -> Result.Failure(transform(error)) +} + +fun <V : Any, E : Exception, E2 : Exception> Result<V, E>.flatMapError(transform: (E) -> Result<V, E2>) = when (this) { + is Result.Success -> Result.Success(value) + is Result.Failure -> transform(error) +} + +fun <V : Any, E : Exception> Result<V, E>.any(predicate: (V) -> Boolean): Boolean = try { + when (this) { + is Result.Success -> predicate(value) + is Result.Failure -> false + } +} catch (ex: Exception) { + false +} + +fun <V : Any, U : Any> Result<V, *>.fanout(other: () -> Result<U, *>): Result<Pair<V, U>, *> = + flatMap { outer -> other().map { outer to it } } + +sealed class Result<out V : Any, out E : Exception> { + + open operator fun component1(): V? = null + open operator fun component2(): E? = null + + inline fun <X> fold(success: (V) -> X, failure: (E) -> X): X = when (this) { + is Success -> success(this.value) + is Failure -> failure(this.error) + } + + abstract fun get(): V + + class Success<out V : Any>(val value: V) : Result<V, Nothing>() { + override fun component1(): V? = value + + override fun get(): V = value + + override fun toString() = "[Success: $value]" + + override fun hashCode(): Int = value.hashCode() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + return other is Success<*> && value == other.value + } + } + + class Failure<out E : Exception>(val error: E) : Result<Nothing, E>() { + override fun component2(): E? = error + + override fun get() = throw error + + fun getException(): E = error + + override fun toString() = "[Failure: $error]" + + override fun hashCode(): Int = error.hashCode() + + override fun equals(other: Any?): Boolean { + if (this === other) return true + return other is Failure<*> && error == other.error + } + } + + companion object { + // Factory methods + fun <E : Exception> error(ex: E) = Failure(ex) + + fun <V : Any> success(v: V) = Success(v) + + fun <V : Any> of(value: V?, fail: (() -> Exception) = { Exception() }): Result<V, Exception> = + value?.let { success(it) } ?: error(fail()) + + fun <V : Any, E : Exception> of(f: () -> V): Result<V, E> = try { + success(f()) + } catch (ex: Exception) { + @Suppress("UNCHECKED_CAST") + error(ex as E) + } + } + +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt new file mode 100644 index 00000000..6765b44e --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.window + +/** + * Main object for Cordova screen. + */ +object Screen { + + /** + * Screen orientations + */ + enum class Orientation(internal val type: String) { + PORTRAIT_PRIMARY("portrait-primary"), + PORTRAIT_SECONDARY("portrait-secondary"), + LANDSCAPE_PRIMARY("landscape-primary"), + LANDSCAPE_SECONDARY("landscape-secondary"), + PORTRAIT("portrait"), + LANDSCAPE("landscape"), + ANY("any") + } + + /** + * Lock screen orientation. + * @param orientation selected orientation + */ + fun lock(orientation: Orientation) { + window.screen.asDynamic().orientation.lock(orientation.type) + } + + /** + * Unlock screen orientation. + */ + fun unlock() { + window.screen.asDynamic().orientation.unlock() + } + + /** + * Get screen orientation. + */ + fun getOrientation(): Orientation { + val type = window.screen.asDynamic().orientation.type + return Orientation.values().find { it.type == type } ?: Screen.Orientation.ANY + } + + /** + * Add listeners for screen orientation change Cordova events. + */ + fun addOrientationChangeListener(listener: (Orientation) -> Unit) { + window.addEventListener("orientationchange", { + listener(getOrientation()) + }, false) + } +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt new file mode 100644 index 00000000..03157534 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.window + +/** + * Main object for Cordova splashscreen. + */ +object Splashscreen { + + /** + * Show splashscreen. + */ + fun show() { + addDeviceReadyListener { + window.navigator.asDynamic().splashscreen.show() + } + } + + /** + * Hide splashscreen. + */ + fun hide() { + addDeviceReadyListener { + window.navigator.asDynamic().splashscreen.hide() + } + } + +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt new file mode 100644 index 00000000..e3de6af2 --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import pl.treksoft.kvision.utils.toHexString +import kotlin.browser.window +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +@Suppress("TopLevelPropertyNaming") +@JsName("StatusBar") +internal external val StatusBarJs: dynamic + +/** + * Main object for Cordova status bar. + */ +@Suppress("TooManyFunctions") +object StatusBar { + + /** + * Show status bar. + */ + fun show() { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.show() + } + } + + /** + * Hide status bar. + */ + fun hide() { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.hide() + } + } + + /** + * Set overlay. + * @param overlays determines if the status bar overlays the web view + */ + fun overlaysWebView(overlays: Boolean) { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.overlaysWebView(overlays) + } + } + + /** + * Use the default status bar (dark text, for light backgrounds). + */ + fun styleDefault() { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.styleDefault() + } + } + + /** + * Use the lightContent status bar (light text, for dark backgrounds). + */ + fun styleLightContent() { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.styleLightContent() + } + } + + /** + * Use the blackTranslucent status bar (light text, for dark backgrounds). + */ + fun styleBlackTranslucent() { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.styleBlackTranslucent() + } + } + + /** + * Use the blackOpaque status bar (light text, for dark backgrounds). + */ + fun styleBlackOpaque() { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.styleBlackOpaque() + } + } + + /** + * Sets the background color of the status bar by a hexadecimal Int. + */ + fun setBackgroundColor(color: Int) { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.backgroundColorByHexString("#" + color.toHexString()) + } + } + + /** + * Sets the background color of the status bar by a hex string. + */ + fun setBackgroundColorByHexString(color: String) { + addDeviceReadyListener { + @Suppress("UnsafeCastFromDynamic") + StatusBarJs.backgroundColorByHexString(color) + } + } + + /** + * Returns if the status bar is visible. + */ + @Suppress("UnsafeCastFromDynamic") + suspend fun isVisible(): Boolean { + return suspendCoroutine { continuation -> + addDeviceReadyListener { + continuation.resume(StatusBarJs.isVisible) + } + } + } + + /** + * Add listeners for the status bar tap event. + */ + fun addTapListener(listener: () -> Unit) { + addDeviceReadyListener { + window.addEventListener("statusTap", { + listener() + }) + } + } + +} diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt new file mode 100644 index 00000000..6d973bbc --- /dev/null +++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package pl.treksoft.kvision.cordova + +import kotlin.browser.window + +/** + * Main object for Cordova vibration. + */ +object Vibration { + + /** + * Generate a vibration with a given pattern. + * @param pattern a pattern - sequence of durations (in milliseconds) for which to turn on or off the vibrator. + */ + fun vibrate(vararg pattern: Int) { + val patternArray = if (pattern.isEmpty()) arrayOf(0) else pattern.toTypedArray() + addDeviceReadyListener { + window.navigator.vibrate(patternArray) + } + } + +} diff --git a/kvision-modules/kvision-datacontainer/build.gradle b/kvision-modules/kvision-datacontainer/build.gradle index 7ad09e67..9337698c 100644 --- a/kvision-modules/kvision-datacontainer/build.gradle +++ b/kvision-modules/kvision-datacontainer/build.gradle @@ -9,6 +9,7 @@ kotlinFrontend { npm { devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt index 37d7a9df..13c8531b 100644 --- a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt +++ b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -86,7 +86,7 @@ interface WSpec : DomSpec { fun runW(code: (widget: Widget, element: Element?) -> Unit) { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() widget.id = "test_id" root.add(widget) diff --git a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt index 931294d5..556d3991 100644 --- a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt +++ b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt @@ -36,7 +36,7 @@ class DataContainerSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) class Model(value: String) : BaseDataComponent() { var value: String by obs(value) diff --git a/kvision-modules/kvision-datetime/build.gradle b/kvision-modules/kvision-datetime/build.gradle index 610b1d9c..bbd4bb79 100644 --- a/kvision-modules/kvision-datetime/build.gradle +++ b/kvision-modules/kvision-datetime/build.gradle @@ -6,6 +6,7 @@ kotlinFrontend { dependency("bootstrap-datetime-picker", "2.4.4") devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt b/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt index 7c1c8d71..5858d04b 100644 --- a/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt +++ b/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt @@ -282,14 +282,14 @@ open class DateTimeInput( /** * Makes the input element focused. */ - open fun focus() { + override fun focus() { getElementJQuery()?.focus() } /** * Makes the input element blur. */ - open fun blur() { + override fun blur() { getElementJQuery()?.blur() } diff --git a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt index 37d7a9df..13c8531b 100644 --- a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt +++ b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -86,7 +86,7 @@ interface WSpec : DomSpec { fun runW(code: (widget: Widget, element: Element?) -> Unit) { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() widget.id = "test_id" root.add(widget) diff --git a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt index 69ab46ec..877cf650 100644 --- a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt +++ b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt @@ -34,7 +34,7 @@ class DateTimeInputSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val data = Date() val dti = DateTimeInput(value = data).apply { placeholder = "place" diff --git a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt index 19cefd86..b5e393bb 100644 --- a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt +++ b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt @@ -34,7 +34,7 @@ class DateTimeSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val data = Date() val ti = DateTime(value = data, label = "Label").apply { placeholder = "place" diff --git a/kvision-modules/kvision-dialog/build.gradle b/kvision-modules/kvision-dialog/build.gradle index 16c2720a..292a228f 100644 --- a/kvision-modules/kvision-dialog/build.gradle +++ b/kvision-modules/kvision-dialog/build.gradle @@ -9,6 +9,7 @@ kotlinFrontend { npm { devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-handlebars/build.gradle b/kvision-modules/kvision-handlebars/build.gradle index de48816a..a971427e 100644 --- a/kvision-modules/kvision-handlebars/build.gradle +++ b/kvision-modules/kvision-handlebars/build.gradle @@ -7,6 +7,7 @@ kotlinFrontend { dependency("handlebars-loader", "1.7.1") devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-i18n/build.gradle b/kvision-modules/kvision-i18n/build.gradle index 88147bbe..72870169 100644 --- a/kvision-modules/kvision-i18n/build.gradle +++ b/kvision-modules/kvision-i18n/build.gradle @@ -6,6 +6,7 @@ kotlinFrontend { dependency("jed", "1.1.1") devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt b/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt index 9eb9d9a7..39e7c2dc 100644 --- a/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt +++ b/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt @@ -23,7 +23,7 @@ package pl.treksoft.kvision.moment import kotlin.js.Date -@Suppress("unused") +@Suppress("unused", "TooManyFunctions") @JsNonModule @JsModule("moment") open external class Moment { @@ -79,7 +79,9 @@ open external class Moment { open fun isSameOrAfter(moment: dynamic): Boolean = definedExternally open fun isSameOrAfter(moment: dynamic, key: String): Boolean = definedExternally open fun isBetween(firstMoment: dynamic, secondMoment: dynamic): Boolean = definedExternally - open fun isBetween(firstMoment: dynamic, secondMoment: dynamic, key: String, inclusivity: String): Boolean = definedExternally + open fun isBetween(firstMoment: dynamic, secondMoment: dynamic, key: String, inclusivity: String): Boolean = + definedExternally + open fun isBetween(firstMoment: dynamic, secondMoment: dynamic, key: String): Boolean = definedExternally open fun isDST(): Boolean = definedExternally open fun isDSTShifted(): Boolean = definedExternally diff --git a/kvision-modules/kvision-redux/build.gradle b/kvision-modules/kvision-redux/build.gradle index db0610bd..3394095f 100644 --- a/kvision-modules/kvision-redux/build.gradle +++ b/kvision-modules/kvision-redux/build.gradle @@ -14,6 +14,7 @@ kotlinFrontend { dependency("core-js", "3.0.0") devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt index 37d7a9df..13c8531b 100644 --- a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt +++ b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -86,7 +86,7 @@ interface WSpec : DomSpec { fun runW(code: (widget: Widget, element: Element?) -> Unit) { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() widget.id = "test_id" root.add(widget) diff --git a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt index 0271a956..5204402d 100644 --- a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt +++ b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt @@ -55,7 +55,7 @@ class StateBindingSpec : DomSpec { @Test fun stateBinding() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val store = createReduxStore(::stateReducer, State(10)) val container = SimplePanel() @@ -81,7 +81,7 @@ class StateBindingSpec : DomSpec { @Test fun stateUpdate() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val store = createReduxStore(::stateReducer, State(10)) val container = SimplePanel() diff --git a/kvision-modules/kvision-richtext/build.gradle b/kvision-modules/kvision-richtext/build.gradle index 86be8e7f..fde9ff17 100644 --- a/kvision-modules/kvision-richtext/build.gradle +++ b/kvision-modules/kvision-richtext/build.gradle @@ -6,6 +6,7 @@ kotlinFrontend { dependency("trix", "1.1.0") devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt index 37d7a9df..13c8531b 100644 --- a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt +++ b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -86,7 +86,7 @@ interface WSpec : DomSpec { fun runW(code: (widget: Widget, element: Element?) -> Unit) { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() widget.id = "test_id" root.add(widget) diff --git a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt index 21b7dc39..4ebf7b45 100644 --- a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt +++ b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt @@ -34,7 +34,7 @@ class RichTextInputSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val hai = RichTextInput(value = "abc").apply { placeholder = "place" id = "idti" diff --git a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt index 844b7e94..1ea79790 100644 --- a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt +++ b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt @@ -34,7 +34,7 @@ class RichTextSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val hai = RichText(value = "abc", label = "Field").apply { placeholder = "place" id = "idti" diff --git a/kvision-modules/kvision-select/build.gradle b/kvision-modules/kvision-select/build.gradle index 65b8a83c..7525f037 100644 --- a/kvision-modules/kvision-select/build.gradle +++ b/kvision-modules/kvision-select/build.gradle @@ -7,6 +7,7 @@ kotlinFrontend { dependency("ajax-bootstrap-select", "1.4.3") devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt index f19081e1..db8a5b3b 100644 --- a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt +++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt @@ -39,7 +39,7 @@ import pl.treksoft.kvision.utils.SnOn * [SelectOption] or [SelectOptGroup] components to the container. * * @constructor - * @param options an optional list of options (label to value pairs) for the select control + * @param options an optional list of options (value to label pairs) for the select control * @param value selected value * @param name the name attribute of the generated HTML input element * @param multiple allows multiple value selection (multiple values are comma delimited) @@ -55,7 +55,7 @@ open class Select( ) : SimplePanel(setOf("form-group")), StringFormControl { /** - * A list of options (label to value pairs) for the select control. + * A list of options (value to label pairs) for the select control. */ var options get() = input.options diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt index 7ffdf2c2..9ae1a4e9 100644 --- a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt +++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt @@ -50,7 +50,7 @@ enum class SelectWidthType(internal val value: String) { * [SelectOption] or [SelectOptGroup] components to the container. * * @constructor - * @param options an optional list of options (label to value pairs) for the select control + * @param options an optional list of options (value to label pairs) for the select control * @param value selected value * @param multiple allows multiple value selection (multiple values are comma delimited) * @param ajaxOptions additional options for remote (AJAX) data source @@ -64,9 +64,9 @@ open class SelectInput( ) : SimplePanel(classes), FormInput { /** - * A list of options (label to value pairs) for the select control. + * A list of options (value to label pairs) for the select control. */ - internal var options by refreshOnUpdate(options) { setChildrenFromOptions() } + var options by refreshOnUpdate(options) { setChildrenFromOptions() } /** * A value of the selected option. */ @@ -337,14 +337,14 @@ open class SelectInput( /** * Makes the input element focused. */ - open fun focus() { + override fun focus() { getElementJQuery()?.focus() } /** * Makes the input element blur. */ - open fun blur() { + override fun blur() { getElementJQuery()?.blur() } diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt index 37d7a9df..13c8531b 100644 --- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt +++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -86,7 +86,7 @@ interface WSpec : DomSpec { fun runW(code: (widget: Widget, element: Element?) -> Unit) { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() widget.id = "test_id" root.add(widget) diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt index 30f42e9c..bfd93900 100644 --- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt +++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt @@ -34,7 +34,7 @@ class SelectInputSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val selectInput = SelectInput(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", true).apply { liveSearch = true placeholder = "Choose ..." diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt index bd88f560..33ccc843 100644 --- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt +++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt @@ -33,7 +33,7 @@ class SelectOptGroupSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val selectOptGroup = SelectOptGroup("Group", listOf("test1" to "Test 1", "test2" to "Test 2"), 2) root.add(selectOptGroup) val element = document.getElementById("test") diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt index f7e07d42..33c36576 100644 --- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt +++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt @@ -32,7 +32,7 @@ class SelectOptionSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val selectOption = SelectOption("testValue", "testLabel") root.add(selectOption) val element = document.getElementById("test") diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt index eaccd551..9eddff81 100644 --- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt +++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt @@ -34,7 +34,7 @@ class SelectSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val select = Select(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", null, true, null, "Label").apply { liveSearch = true placeholder = "Choose ..." diff --git a/kvision-modules/kvision-spinner/build.gradle b/kvision-modules/kvision-spinner/build.gradle index e7600e5a..4569b8bc 100644 --- a/kvision-modules/kvision-spinner/build.gradle +++ b/kvision-modules/kvision-spinner/build.gradle @@ -6,6 +6,7 @@ kotlinFrontend { dependency("bootstrap-touchspin", "4.2.5") devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt b/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt index c60aab2f..66e98ab5 100644 --- a/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt +++ b/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt @@ -293,14 +293,14 @@ open class SpinnerInput( /** * Makes the input element focused. */ - open fun focus() { + override fun focus() { getElementJQuery()?.focus() } /** * Makes the input element blur. */ - open fun blur() { + override fun blur() { getElementJQuery()?.blur() } diff --git a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt index 37d7a9df..13c8531b 100644 --- a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt +++ b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -86,7 +86,7 @@ interface WSpec : DomSpec { fun runW(code: (widget: Widget, element: Element?) -> Unit) { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() widget.id = "test_id" root.add(widget) diff --git a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt index a240bfd8..467e48db 100644 --- a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt +++ b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt @@ -32,7 +32,7 @@ class SpinnerInputSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val si = SpinnerInput(value = 13).apply { placeholder = "place" id = "idti" @@ -46,7 +46,7 @@ class SpinnerInputSpec : DomSpec { @Test fun spinUp() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val si = SpinnerInput(value = 13).apply { placeholder = "place" id = "idti" @@ -61,7 +61,7 @@ class SpinnerInputSpec : DomSpec { @Test fun spinDown() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val si = SpinnerInput(value = 13).apply { placeholder = "place" id = "idti" diff --git a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt index 30f12a93..928fe0b1 100644 --- a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt +++ b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt @@ -33,7 +33,7 @@ class SpinnerSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ti = Spinner(value = 13, label = "Label").apply { placeholder = "place" name = "name" @@ -59,7 +59,7 @@ class SpinnerSpec : DomSpec { @Test fun spinUp() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val si = Spinner(value = 13) root.add(si) assertEquals(13, si.value, "Should return initial value before spinUp") @@ -71,7 +71,7 @@ class SpinnerSpec : DomSpec { @Test fun spinDown() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val si = Spinner(value = 13) root.add(si) assertEquals(13, si.value, "Should return initial value before spinDown") diff --git a/kvision-modules/kvision-tabulator/build.gradle b/kvision-modules/kvision-tabulator/build.gradle index d461e542..5ea97e50 100644 --- a/kvision-modules/kvision-tabulator/build.gradle +++ b/kvision-modules/kvision-tabulator/build.gradle @@ -11,6 +11,7 @@ kotlinFrontend { dependency("tabulator-tables", "4.2.5") devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt index dac29ab9..08f2603b 100644 --- a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt +++ b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt @@ -22,8 +22,19 @@ package pl.treksoft.kvision.tabulator +import kotlinx.serialization.KSerializer +import org.w3c.dom.HTMLElement +import pl.treksoft.kvision.core.Component +import pl.treksoft.kvision.form.FormControl +import pl.treksoft.kvision.form.FormInput +import pl.treksoft.kvision.panel.Root +import pl.treksoft.kvision.tabulator.EditorRoot.disposeTimer +import pl.treksoft.kvision.tabulator.EditorRoot.root import pl.treksoft.kvision.tabulator.js.Tabulator +import pl.treksoft.kvision.utils.JSON import pl.treksoft.kvision.utils.obj +import kotlin.browser.document +import kotlin.browser.window import kotlin.js.Promise /** @@ -248,7 +259,7 @@ fun DownloadConfig.toJs(): Tabulator.DownloadConfig { /** * Column definition options. */ -data class ColumnDefinition( +data class ColumnDefinition<T : Any>( val title: String, val field: String? = null, val visible: Boolean? = null, @@ -265,18 +276,36 @@ data class ColumnDefinition( val rowHandle: Boolean? = null, val hideInHtml: Boolean? = null, val sorter: Sorter? = null, + val sorterFunction: (( + a: dynamic, b: dynamic, aRow: Tabulator.RowComponent, bRow: Tabulator.RowComponent, + column: Tabulator.ColumnComponent, dir: SortingDir, sorterParams: dynamic + ) -> Number)? = null, val sorterParams: dynamic = null, val formatter: Formatter? = null, val formatterFunction: (( cell: Tabulator.CellComponent, formatterParams: dynamic, onRendered: (callback: () -> Unit) -> Unit ) -> dynamic)? = null, + val formatterComponentFunction: (( + cell: Tabulator.CellComponent, onRendered: (callback: () -> Unit) -> Unit, data: T + ) -> Component)? = null, val formatterParams: dynamic = null, val variableHeight: Boolean? = null, val editable: ((cell: Tabulator.CellComponent) -> Boolean)? = null, val editor: Editor? = null, + val editorFunction: (( + cell: Tabulator.CellComponent, + onRendered: (callback: () -> Unit) -> Unit, + success: (value: dynamic) -> Unit, cancel: (value: dynamic) -> Unit, editorParams: dynamic + ) -> dynamic)? = null, + val editorComponentFunction: (( + cell: Tabulator.CellComponent, + onRendered: (callback: () -> Unit) -> Unit, + success: (value: dynamic) -> Unit, cancel: (value: dynamic) -> Unit, data: T + ) -> Component)? = null, val editorParams: dynamic = null, val validator: Validator? = null, + val validatorFunction: dynamic = null, val validatorParams: String? = null, val download: Boolean? = null, val downloadTitle: String? = null, @@ -325,11 +354,78 @@ data class ColumnDefinition( val cellEditCancelled: ((cell: Tabulator.CellComponent) -> Unit)? = null ) +internal object EditorRoot { + internal var root: Root? = null + internal var disposeTimer: Int? = null +} + /** * An extension function to convert column definition class to JS object. */ -@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "ComplexMethod") -fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.ColumnDefinition { +@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "ComplexMethod", "MagicNumber") +fun <T : Any> ColumnDefinition<T>.toJs( + i18nTranslator: (String) -> (String), + dataSerializer: KSerializer<T>? = null +): Tabulator.ColumnDefinition { + val tmpEditorFunction = editorComponentFunction?.let { + { cell: Tabulator.CellComponent, + onRendered: (callback: () -> Unit) -> Unit, + success: (value: dynamic) -> Unit, cancel: (value: dynamic) -> Unit, _: dynamic -> + var onRenderedCallback: (() -> Unit)? = null + val str = kotlin.js.JSON.stringify(cell.getData()) + @Suppress("UNCHECKED_CAST") val data = dataSerializer?.let { + JSON.plain.parse(it, str) + } ?: cell.getData() as T + val component = it(cell, { callback -> + onRenderedCallback = callback + }, { value -> + success(value) + disposeTimer = window.setTimeout({ + root?.dispose() + disposeTimer = null + root = null + }, 500) + }, cancel, data) + val rootElement = document.createElement("div") as HTMLElement + onRendered { + if (root != null) { + disposeTimer?.let { window.clearTimeout(it) } + root?.dispose() + } + root = Root(element = rootElement) + @Suppress("UnsafeCastFromDynamic") + root?.add(component) + (component as? FormControl)?.focus() + (component as? FormInput)?.focus() + cell.checkHeight() + onRenderedCallback?.invoke() + } + rootElement + } + } + + val tmpFormatterFunction = formatterComponentFunction?.let { + { cell: Tabulator.CellComponent, _: dynamic, + onRendered: (callback: () -> Unit) -> Unit -> + var onRenderedCallback: (() -> Unit)? = null + val str = kotlin.js.JSON.stringify(cell.getData()) + @Suppress("UNCHECKED_CAST") val data = + dataSerializer?.let { JSON.plain.parse(it, str) } ?: cell.getData() as T + val component = it(cell, { callback -> + onRenderedCallback = callback + }, data) + val rootElement = document.createElement("div") as HTMLElement + onRendered { + val root = Root(element = rootElement) + @Suppress("UnsafeCastFromDynamic") + root.add(component) + cell.checkHeight() + onRenderedCallback?.invoke() + } + rootElement + } + } + return obj { this.title = i18nTranslator(title) if (field != null) this.field = field @@ -346,17 +442,25 @@ fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.Colum if (cssClass != null) this.cssClass = cssClass if (rowHandle != null) this.rowHandle = rowHandle if (hideInHtml != null) this.hideInHtml = hideInHtml - if (sorter != null) this.sorter = sorter.sorter + if (sorterFunction != null) { + this.sorter = sorterFunction + } else if (sorter != null) { + this.sorter = sorter.sorter + } if (sorterParams != null) this.sorterParams = sorterParams - if (formatterFunction != null) { - this.formatter = formatterFunction - } else if (formatter != null) { - this.formatter = formatter.formatter + when { + tmpFormatterFunction != null -> this.formatter = tmpFormatterFunction + formatterFunction != null -> this.formatter = formatterFunction + formatter != null -> this.formatter = formatter.formatter } if (formatterParams != null) this.formatterParams = formatterParams if (variableHeight != null) this.variableHeight = variableHeight if (editable != null) this.editable = editable - if (editor != null) this.editor = editor.editor + when { + tmpEditorFunction != null -> this.editor = tmpEditorFunction + editorFunction != null -> this.editor = editorFunction + editor != null -> this.editor = editor.editor + } if (editorParams != null) this.editorParams = editorParams if (validator != null) this.validator = validator.validator if (validatorParams != null) this.validatorParams = validatorParams @@ -404,14 +508,20 @@ fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.Colum if (cellMouseMove != null) this.cellMouseMove = cellMouseMove if (cellEditing != null) this.cellEditing = cellEditing if (cellEdited != null) this.cellEdited = cellEdited - if (cellEditCancelled != null) this.cellEditCancelled = cellEditCancelled + if (cellEditCancelled != null) { + this.cellEditCancelled = cellEditCancelled + } else if (tmpEditorFunction != null) { + this.cellEditCancelled = { cell: Tabulator.CellComponent -> + cell.checkHeight() + } + } } as Tabulator.ColumnDefinition } /** * Tabulator options. */ -data class TabulatorOptions( +data class TabulatorOptions<T : Any>( val height: String? = null, val virtualDom: Boolean? = null, val virtualDomBuffer: Int? = null, @@ -425,7 +535,7 @@ data class TabulatorOptions( val downloadConfig: DownloadConfig? = null, val reactiveData: Boolean? = null, val autoResize: Boolean? = null, - val columns: List<ColumnDefinition>? = null, + val columns: List<ColumnDefinition<T>>? = null, val autoColumns: Boolean? = null, val layout: Layout? = null, val layoutColumnsOnNewData: Boolean? = null, @@ -572,7 +682,17 @@ data class TabulatorOptions( * An extension function to convert tabulator options class to JS object. */ @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "ComplexMethod") -fun TabulatorOptions.toJs(i18nTranslator: (String) -> (String)): Tabulator.Options { +fun <T : Any> TabulatorOptions<T>.toJs( + i18nTranslator: (String) -> (String), + dataSerializer: KSerializer<T>? = null +): Tabulator.Options { + val tmpCellEditCancelled = this.columns?.find { it.editorComponentFunction != null }?.let { + { cell: Tabulator.CellComponent -> + cellEditCancelled?.invoke(cell) + cell.getTable().redraw(true) + } + } ?: cellEditCancelled + return obj { if (height != null) this.height = height if (virtualDom != null) this.virtualDom = virtualDom @@ -587,7 +707,7 @@ fun TabulatorOptions.toJs(i18nTranslator: (String) -> (String)): Tabulator.Optio if (downloadConfig != null) this.downloadConfig = downloadConfig.toJs() if (reactiveData != null) this.reactiveData = reactiveData if (autoResize != null) this.autoResize = autoResize - if (columns != null) this.columns = columns.map { it.toJs(i18nTranslator) }.toTypedArray() + if (columns != null) this.columns = columns.map { it.toJs(i18nTranslator, dataSerializer) }.toTypedArray() if (autoColumns != null) { this.autoColumns = autoColumns } else if (columns == null) { @@ -700,7 +820,9 @@ fun TabulatorOptions.toJs(i18nTranslator: (String) -> (String)): Tabulator.Optio if (cellMouseMove != null) this.cellMouseMove = cellMouseMove if (cellEditing != null) this.cellEditing = cellEditing if (cellEdited != null) this.cellEdited = cellEdited - if (cellEditCancelled != null) this.cellEditCancelled = cellEditCancelled + if (tmpCellEditCancelled != null) { + this.cellEditCancelled = tmpCellEditCancelled + } if (columnMoved != null) this.columnMoved = columnMoved if (columnResized != null) this.columnResized = columnResized if (columnVisibilityChanged != null) this.columnVisibilityChanged = columnVisibilityChanged diff --git a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt index 249c578f..72a2809a 100644 --- a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt +++ b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt @@ -38,6 +38,7 @@ import pl.treksoft.kvision.table.TableType import pl.treksoft.kvision.utils.JSON import pl.treksoft.kvision.utils.createInstance import pl.treksoft.kvision.utils.obj +import pl.treksoft.kvision.utils.syncWithList import redux.RAction import pl.treksoft.kvision.tabulator.js.Tabulator as JsTabulator @@ -47,14 +48,17 @@ import pl.treksoft.kvision.tabulator.js.Tabulator as JsTabulator * @constructor * @param T serializable type * @param data a list of serializable objects + * @param dataUpdateOnEdit determines if the data model is automatically updated after tabulator edit action * @param options tabulator options + * @param types a set of table types * @param classes a set of CSS class names * @param dataSerializer a serializer for class T */ @Suppress("LargeClass", "TooManyFunctions") open class Tabulator<T : Any>( protected val data: List<T>? = null, - val options: TabulatorOptions = TabulatorOptions(), + protected val dataUpdateOnEdit: Boolean = true, + val options: TabulatorOptions<T> = TabulatorOptions(), types: Set<TableType> = setOf(), classes: Set<String> = setOf(), protected val dataSerializer: KSerializer<T>? = null @@ -191,6 +195,9 @@ open class Tabulator<T : Any>( val d = nativeToData(data, dataSerializer) @Suppress("UnsafeCastFromDynamic") this.dispatchEvent("tabulatorDataEdited", obj { detail = d }) + if (dataUpdateOnEdit && this.data is MutableList<T>) { + this.data.syncWithList(d) + } } } counter++ @@ -228,7 +235,7 @@ open class Tabulator<T : Any>( (this.getElement() as? HTMLElement)?.let { jsTabulator = KVManagerTabulator.getConstructor() - .createInstance(it, options.toJs(this::translate)) + .createInstance(it, options.toJs(this::translate, dataSerializer)) if (currentPage != null) { jsTabulator?.setPageSize(pageSize ?: 0) jsTabulator?.setPage(currentPage) @@ -561,12 +568,13 @@ open class Tabulator<T : Any>( */ inline fun <reified T : Any> Container.tabulator( data: List<T>? = null, - options: TabulatorOptions = TabulatorOptions(), + dataUpdateOnEdit: Boolean = true, + options: TabulatorOptions<T> = TabulatorOptions(), types: Set<TableType> = setOf(), classes: Set<String> = setOf(), noinline init: (Tabulator<T>.() -> Unit)? = null ): Tabulator<T> { - val tabulator = create(data, options, types, classes) + val tabulator = create(data, dataUpdateOnEdit, options, types, classes) init?.invoke(tabulator) this.add(tabulator) return tabulator @@ -578,7 +586,7 @@ open class Tabulator<T : Any>( inline fun <reified T : Any, S : Any, A : RAction> Container.tabulator( store: ReduxStore<S, A>, noinline dataFactory: (S) -> List<T>, - options: TabulatorOptions = TabulatorOptions(), + options: TabulatorOptions<T> = TabulatorOptions(), types: Set<TableType> = setOf(), classes: Set<String> = setOf(), noinline init: (Tabulator<T>.() -> Unit)? = null @@ -594,7 +602,7 @@ open class Tabulator<T : Any>( */ inline fun <reified T : Any, A : RAction> Container.tabulator( store: ReduxStore<List<T>, A>, - options: TabulatorOptions = TabulatorOptions(), + options: TabulatorOptions<T> = TabulatorOptions(), types: Set<TableType> = setOf(), classes: Set<String> = setOf(), noinline init: (Tabulator<T>.() -> Unit)? = null @@ -609,12 +617,12 @@ open class Tabulator<T : Any>( * DSL builder extension function for dynamic data (send within options parameter). */ fun <T : Any> Container.tabulator( - options: TabulatorOptions = TabulatorOptions(), + options: TabulatorOptions<T> = TabulatorOptions(), types: Set<TableType> = setOf(), classes: Set<String> = setOf(), init: (Tabulator<T>.() -> Unit)? = null ): Tabulator<T> { - val tabulator = Tabulator<T>(options = options, types = types, classes = classes) + val tabulator = Tabulator(dataUpdateOnEdit = false, options = options, types = types, classes = classes) init?.invoke(tabulator) this.add(tabulator) return tabulator @@ -625,12 +633,14 @@ open class Tabulator<T : Any>( */ @UseExperimental(ImplicitReflectionSerializer::class) inline fun <reified T : Any> create( - data: List<T>? = null, options: TabulatorOptions = TabulatorOptions(), + data: List<T>? = null, + dataUpdateOnEdit: Boolean = true, + options: TabulatorOptions<T> = TabulatorOptions(), types: Set<TableType> = setOf(), classes: Set<String> = setOf(), noinline init: (Tabulator<T>.() -> Unit)? = null ): Tabulator<T> { - val tabulator = Tabulator(data, options, types, classes, T::class.serializer()) + val tabulator = Tabulator(data, dataUpdateOnEdit, options, types, classes, T::class.serializer()) init?.invoke(tabulator) return tabulator } @@ -642,13 +652,13 @@ open class Tabulator<T : Any>( inline fun <reified T : Any, S : Any, A : RAction> create( store: ReduxStore<S, A>, noinline dataFactory: (S) -> List<T>, - options: TabulatorOptions = TabulatorOptions(), + options: TabulatorOptions<T> = TabulatorOptions(), types: Set<TableType> = setOf(), classes: Set<String> = setOf(), noinline init: (Tabulator<T>.() -> Unit)? = null ): Tabulator<T> { val data = dataFactory(store.getState()) - val tabulator = Tabulator(data, options, types, classes, T::class.serializer()) + val tabulator = Tabulator(data, false, options, types, classes, T::class.serializer()) init?.invoke(tabulator) store.subscribe { s -> tabulator.replaceData(dataFactory(s)) @@ -662,13 +672,13 @@ open class Tabulator<T : Any>( @UseExperimental(ImplicitReflectionSerializer::class) inline fun <reified T : Any, A : RAction> create( store: ReduxStore<List<T>, A>, - options: TabulatorOptions = TabulatorOptions(), + options: TabulatorOptions<T> = TabulatorOptions(), types: Set<TableType> = setOf(), classes: Set<String> = setOf(), noinline init: (Tabulator<T>.() -> Unit)? = null ): Tabulator<T> { val data = store.getState() - val tabulator = Tabulator(data, options, types, classes, T::class.serializer()) + val tabulator = Tabulator(data, false, options, types, classes, T::class.serializer()) init?.invoke(tabulator) store.subscribe { s -> tabulator.replaceData(s) diff --git a/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt index 37d7a9df..13c8531b 100644 --- a/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt +++ b/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -86,7 +86,7 @@ interface WSpec : DomSpec { fun runW(code: (widget: Widget, element: Element?) -> Unit) { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() widget.id = "test_id" root.add(widget) diff --git a/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt b/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt index d6b33a78..1f49ee93 100644 --- a/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt +++ b/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt @@ -34,7 +34,7 @@ class TabulatorSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val tabulator = Tabulator<Any>(options = TabulatorOptions(data = arrayOf(obj { id = 1 name = "Name" diff --git a/kvision-modules/kvision-upload/build.gradle b/kvision-modules/kvision-upload/build.gradle index 21ac7eab..2ac20c32 100644 --- a/kvision-modules/kvision-upload/build.gradle +++ b/kvision-modules/kvision-upload/build.gradle @@ -10,6 +10,7 @@ kotlinFrontend { dependency("bootstrap-fileinput", "4.5.2") devDependency("karma", "3.1.4") devDependency("karma-chrome-launcher", "2.2.0") + devDependency("karma-webpack", "3.0.5") devDependency("qunit", "2.8.0") } diff --git a/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt b/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt index a250df42..39c4c4c6 100644 --- a/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt +++ b/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt @@ -278,14 +278,14 @@ open class UploadInput(uploadUrl: String? = null, multiple: Boolean = false, cla /** * Makes the input element focused. */ - open fun focus() { + override fun focus() { getElementJQuery()?.focus() } /** * Makes the input element blur. */ - open fun blur() { + override fun blur() { getElementJQuery()?.blur() } diff --git a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt index 37d7a9df..13c8531b 100644 --- a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt +++ b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -86,7 +86,7 @@ interface WSpec : DomSpec { fun runW(code: (widget: Widget, element: Element?) -> Unit) { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() widget.id = "test_id" root.add(widget) diff --git a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt index 626b70e4..de7a9315 100644 --- a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt +++ b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt @@ -33,7 +33,7 @@ class UploadInputSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val upi = UploadInput(multiple = true).apply { id = "idti" } diff --git a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt index bea4ddee..92078153 100644 --- a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt +++ b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt @@ -33,7 +33,7 @@ class UploadSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val upi = Upload(multiple = true) val id = upi.input.id root.add(upi) diff --git a/settings.gradle b/settings.gradle index c7d58bfd..20498616 100644 --- a/settings.gradle +++ b/settings.gradle @@ -23,4 +23,5 @@ include 'kvision-modules:kvision-base', 'kvision-modules:kvision-server-jooby', 'kvision-modules:kvision-server-ktor', 'kvision-modules:kvision-server-spring-boot', - 'kvision-modules:kvision-electron' + 'kvision-modules:kvision-electron', + 'kvision-modules:kvision-cordova' diff --git a/src/main/kotlin/pl/treksoft/kvision/KVManager.kt b/src/main/kotlin/pl/treksoft/kvision/KVManager.kt index faa0b77c..d1a4a8be 100644 --- a/src/main/kotlin/pl/treksoft/kvision/KVManager.kt +++ b/src/main/kotlin/pl/treksoft/kvision/KVManager.kt @@ -29,6 +29,7 @@ import com.github.snabbdom.datasetModule import com.github.snabbdom.eventListenersModule import com.github.snabbdom.propsModule import com.github.snabbdom.styleModule +import org.w3c.dom.HTMLElement import pl.treksoft.kvision.core.Component import pl.treksoft.kvision.utils.isIE11 import kotlin.browser.document @@ -72,6 +73,10 @@ internal object KVManager { return sdPatch(container, vnode) } + internal fun patch(element: HTMLElement, vnode: VNode): VNode { + return sdPatch(element, vnode) + } + internal fun patch(oldVNode: VNode, newVNode: VNode): VNode { return sdPatch(oldVNode, newVNode) } diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Component.kt b/src/main/kotlin/pl/treksoft/kvision/core/Component.kt index 411eae8d..fe5569d4 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Component.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Component.kt @@ -97,6 +97,26 @@ interface Component { fun removeSurroundingCssClass(css: Style): Component /** + * Returns the value of an additional attribute. + * @param name the name of the attribute + * @return the value of the attribute + */ + fun getAttribute(name: String): String? + + /** + * Sets the value of additional attribute. + * @param name the name of the attribute + * @param value the value of the attribute + */ + fun setAttribute(name: String, value: String): Component + + /** + * Removes the value of additional attribute. + * @param name the name of the attribute + */ + fun removeAttribute(name: String): Component + + /** * @suppress * Internal function * Renders current component as a Snabbdom vnode. diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Style.kt b/src/main/kotlin/pl/treksoft/kvision/core/Style.kt index 282d2e7e..ff91c429 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Style.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Style.kt @@ -21,11 +21,6 @@ */ package pl.treksoft.kvision.core -import com.github.snabbdom.VNode -import com.github.snabbdom.h -import org.w3c.dom.Node -import pl.treksoft.jquery.JQuery -import pl.treksoft.kvision.panel.Root import kotlin.reflect.KProperty /** @@ -41,8 +36,6 @@ open class Style(className: String? = null, parentStyle: Style? = null, init: (S StyledComponent() { private val propertyValues: MutableMap<String, Any?> = mutableMapOf() - override var parent: Container? = Root.getFirstRoot() - private val newClassName: String = if (parentStyle == null) { className ?: "kv_styleclass_${counter++}" } else { @@ -55,86 +48,19 @@ open class Style(className: String? = null, parentStyle: Style? = null, init: (S var className: String by refreshOnUpdate(newClassName) init { + @Suppress("LeakingThis") styles.add(this) @Suppress("LeakingThis") init?.invoke(this) } - override var visible: Boolean = true - set(value) { - val oldField = field - field = value - if (oldField != field) refresh() - } - - override fun addCssClass(css: String): Component { - return this - } - - override fun removeCssClass(css: String): Component { - return this - } - - override fun addSurroundingCssClass(css: String): Component { - return this - } - - override fun removeSurroundingCssClass(css: String): Component { - return this - } - - override fun addCssClass(css: Style): Component { - return this - } - - override fun removeCssClass(css: Style): Component { - return this - } - - override fun addSurroundingCssClass(css: Style): Component { - return this - } - - override fun removeSurroundingCssClass(css: Style): Component { - return this - } - - override fun renderVNode(): VNode { - return h("style", arrayOf(generateStyle())) - } - internal fun generateStyle(): String { - val styles = getSnStyle() + val styles = getSnStyleInternal() return ".$className {\n" + styles.joinToString("\n") { "${it.first}: ${it.second};" } + "\n}" } - override fun getElement(): Node? { - return null - } - - override fun getElementJQuery(): JQuery? { - return null - } - - override fun getElementJQueryD(): dynamic { - return null - } - - override fun clearParent(): Component { - this.parent = null - return this - } - - override fun getRoot(): Root? { - return this.parent?.getRoot() - } - - override fun dispose() { - styles.remove(this) - } - protected fun <T> refreshOnUpdate(refreshFunction: ((T) -> Unit) = { this.refresh() }) = RefreshDelegateProvider<T>(null, refreshFunction) diff --git a/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt b/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt index aa3f26bb..d9fa63fc 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt @@ -29,7 +29,7 @@ import kotlin.reflect.KProperty * Base class for components supporting CSS styling. */ @Suppress("LargeClass") -abstract class StyledComponent : Component { +abstract class StyledComponent { private val propertyValues: MutableMap<String, Any?> = mutableMapOf() /** diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt index 3388a011..fbcd89da 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt @@ -53,11 +53,12 @@ import kotlin.reflect.KProperty * @param classes Set of CSS class names */ @Suppress("TooManyFunctions", "LargeClass") -open class Widget(classes: Set<String> = setOf()) : StyledComponent() { +open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component { private val propertyValues: MutableMap<String, Any?> = mutableMapOf() internal val classes = classes.toMutableSet() internal val surroundingClasses: MutableSet<String> = mutableSetOf() + internal val attributes: MutableMap<String, String> = mutableMapOf() internal val internalListeners = mutableListOf<SnOn<Widget>.() -> Unit>() internal val listeners = mutableListOf<SnOn<Widget>.() -> Unit>() @@ -243,6 +244,9 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent() { if (draggable == true) { snattrs.add("draggable" to "true") } + if (attributes.isNotEmpty()) { + snattrs += attributes.toList() + } return snattrs } @@ -565,6 +569,22 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent() { return removeSurroundingCssClass(css.className) } + override fun getAttribute(name: String): String? { + return this.attributes[name] + } + + override fun setAttribute(name: String, value: String): Widget { + this.attributes[name] = value + refresh() + return this + } + + override fun removeAttribute(name: String): Widget { + this.attributes.remove(name) + refresh() + return this + } + override fun getElement(): Node? { return this.vnode?.elm } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt b/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt index 57ce88eb..3019fb6f 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt @@ -47,6 +47,16 @@ interface FormInput : Component { * The name attribute of the generated HTML input element. */ var name: String? + + /** + * Makes the input element focused. + */ + fun focus() + + /** + * Makes the input element blur. + */ + fun blur() } /** diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt index 6973efe9..07b86b4c 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt @@ -152,14 +152,14 @@ abstract class CheckInput( /** * Makes the input element focused. */ - open fun focus() { + override fun focus() { getElementJQuery()?.focus() } /** * Makes the input element blur. */ - open fun blur() { + override fun blur() { getElementJQuery()?.blur() } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt index 98839982..fca681f6 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt @@ -150,11 +150,11 @@ open class RadioGroupInput( } } - fun focus() { + override fun focus() { getChildren().filterIsInstance<Radio>().firstOrNull()?.focus() } - fun blur() { + override fun blur() { getChildren().filterIsInstance<Radio>().firstOrNull()?.blur() } diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt new file mode 100644 index 00000000..4d278ad2 --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.form.select + +import pl.treksoft.kvision.core.Component +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.StringBoolPair +import pl.treksoft.kvision.core.StringPair +import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.form.FieldLabel +import pl.treksoft.kvision.form.HelpBlock +import pl.treksoft.kvision.form.StringFormControl +import pl.treksoft.kvision.panel.SimplePanel +import pl.treksoft.kvision.utils.SnOn + +/** + * The form field component for SimpleSelect control. + * + * @constructor + * @param options an optional list of options (value to label pairs) for the select control + * @param value selected value + * @param emptyOption determines if an empty option is automatically generated + * @param name the name attribute of the generated HTML input element + * @param label label text bound to the input element + * @param rich determines if [label] can contain HTML code + */ +@Suppress("TooManyFunctions") +open class SimpleSelect( + options: List<StringPair>? = null, value: String? = null, emptyOption: Boolean = false, + name: String? = null, label: String? = null, rich: Boolean = false +) : SimplePanel(setOf("form-group")), StringFormControl { + + /** + * A list of options (value to label pairs) for the select control. + */ + var options + get() = input.options + set(value) { + input.options = value + } + /** + * A value of the selected option. + */ + override var value + get() = input.value + set(value) { + input.value = value + } + /** + * The value of the selected child option. + * + * This value is placed directly in the generated HTML code, while the [value] property is dynamically + * bound to the select component. + */ + var startValue + get() = input.startValue + set(value) { + input.startValue = value + } + /** + * Determines if an empty option is automatically generated. + */ + var emptyOption + get() = input.emptyOption + set(value) { + input.emptyOption = value + } + /** + * Determines if the select is automatically focused. + */ + var autofocus + get() = input.autofocus + set(value) { + input.autofocus = value + } + /** + * The label text bound to the select element. + */ + var label + get() = flabel.content + set(value) { + flabel.content = value + } + /** + * Determines if [label] can contain HTML code. + */ + var rich + get() = flabel.rich + set(value) { + flabel.rich = value + } + + private val idc = "kv_form_simpleselect_$counter" + final override val input: SimpleSelectInput = SimpleSelectInput( + options, value, emptyOption, setOf("form-control") + ).apply { + this.id = idc + this.name = name + } + final override val flabel: FieldLabel = FieldLabel(idc, label, rich) + final override val validationInfo: HelpBlock = HelpBlock().apply { visible = false } + + init { + @Suppress("LeakingThis") + input.eventTarget = this + this.addInternal(flabel) + this.addInternal(input) + this.addInternal(validationInfo) + counter++ + } + + override fun getSnClass(): List<StringBoolPair> { + val cl = super.getSnClass().toMutableList() + if (validatorError != null) { + cl.add("has-error" to true) + } + return cl + } + + @Suppress("UNCHECKED_CAST") + override fun <T : Widget> setEventListener(block: SnOn<T>.() -> Unit): Widget { + input.setEventListener(block) + return this + } + + override fun setEventListener(block: SnOn<Widget>.() -> Unit): Widget { + input.setEventListener(block) + return this + } + + override fun removeEventListeners(): Widget { + input.removeEventListeners() + return this + } + + override fun add(child: Component): SimplePanel { + input.add(child) + return this + } + + override fun addAll(children: List<Component>): SimplePanel { + input.addAll(children) + return this + } + + override fun remove(child: Component): SimplePanel { + input.remove(child) + return this + } + + override fun removeAll(): SimplePanel { + input.removeAll() + return this + } + + override fun getChildren(): List<Component> { + return input.getChildren() + } + + override fun focus() { + input.focus() + } + + override fun blur() { + input.blur() + } + + companion object { + internal var counter = 0 + + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Container.simpleSelect( + options: List<StringPair>? = null, + value: String? = null, + emptyOption: Boolean = false, + name: String? = null, + label: String? = null, + rich: Boolean = false, + init: (SimpleSelect.() -> Unit)? = null + ): SimpleSelect { + val simpleSelect = SimpleSelect(options, value, emptyOption, name, label, rich).apply { init?.invoke(this) } + this.add(simpleSelect) + return simpleSelect + } + } +} diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt new file mode 100644 index 00000000..df334c1c --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package pl.treksoft.kvision.form.select + +import com.github.snabbdom.VNode +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.StringBoolPair +import pl.treksoft.kvision.core.StringPair +import pl.treksoft.kvision.form.FormInput +import pl.treksoft.kvision.form.InputSize +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag +import pl.treksoft.kvision.panel.SimplePanel + +internal const val KVNULL = "#kvnull" + +/** + * Simple select component. + * + * @constructor + * @param options an optional list of options (value to label pairs) for the select control + * @param value text input value + * @param emptyOption determines if an empty option is automatically generated + * @param classes a set of CSS class names + */ +open class SimpleSelectInput( + options: List<StringPair>? = null, value: String? = null, emptyOption: Boolean = false, + classes: Set<String> = setOf() +) : SimplePanel(classes + "form-control"), FormInput { + + /** + * A list of options (value to label pairs) for the select control. + */ + var options by refreshOnUpdate(options) { setChildrenFromOptions() } + + /** + * Text input value. + */ + var value by refreshOnUpdate(value) { refreshState() } + /** + * The value of the selected child option. + * + * This value is placed directly in the generated HTML code, while the [value] property is dynamically + * bound to the select component. + */ + var startValue by refreshOnUpdate(value) { this.value = it; selectOption() } + /** + * The name attribute of the generated HTML input element. + */ + override var name: String? by refreshOnUpdate() + /** + * Determines if the field is disabled. + */ + override var disabled by refreshOnUpdate(false) + /** + * Determines if the text input is automatically focused. + */ + var autofocus: Boolean? by refreshOnUpdate() + /** + * Determines if an empty option is automatically generated. + */ + var emptyOption by refreshOnUpdate(emptyOption) { setChildrenFromOptions() } + /** + * The size of the input. + */ + override var size: InputSize? by refreshOnUpdate() + + init { + this.vnkey = "kv_simpleselectinput_${counter++}" + setChildrenFromOptions() + this.setInternalEventListener<SimpleSelectInput> { + change = { + self.changeValue() + } + } + } + + override fun render(): VNode { + return render("select", childrenVNodes()) + } + + private fun setChildrenFromOptions() { + super.removeAll() + if (emptyOption) { + super.add(Tag(TAG.OPTION, "", attributes = mapOf("value" to KVNULL))) + } + options?.let { + val c = it.map { + val attributes = if (it.first == value) { + mapOf("value" to it.first, "selected" to "selected") + } else { + mapOf("value" to it.first) + } + Tag(TAG.OPTION, it.second, attributes = attributes) + } + super.addAll(c) + } + } + + private fun selectOption() { + children.forEach { child -> + if (child is Tag && child.type == TAG.OPTION) { + if (value != null && child.getAttribute("value") == value) { + child.setAttribute("selected", "selected") + } else { + child.removeAttribute("selected") + } + } + } + } + + override fun getSnClass(): List<StringBoolPair> { + val cl = super.getSnClass().toMutableList() + size?.let { + cl.add(it.className to true) + } + return cl + } + + override fun getSnAttrs(): List<StringPair> { + val sn = super.getSnAttrs().toMutableList() + name?.let { + sn.add("name" to it) + } + autofocus?.let { + if (it) { + sn.add("autofocus" to "autofocus") + } + } + if (disabled) { + sn.add("disabled" to "disabled") + } + return sn + } + + override fun afterInsert(node: VNode) { + refreshState() + } + + /** + * @suppress + * Internal function + */ + protected open fun refreshState() { + value?.let { + getElementJQuery()?.`val`(it) + } ?: getElementJQueryD()?.`val`(null) + } + + /** + * @suppress + * Internal function + */ + protected open fun changeValue() { + val v = getElementJQuery()?.`val`() as String? + if (v != null && v.isNotEmpty() && v != KVNULL) { + this.value = v + } else { + this.value = null + } + } + + /** + * Makes the input element focused. + */ + override fun focus() { + getElementJQuery()?.focus() + } + + /** + * Makes the input element blur. + */ + override fun blur() { + getElementJQuery()?.blur() + } + + companion object { + internal var counter = 0 + + /** + * DSL builder extension function. + * + * It takes the same parameters as the constructor of the built component. + */ + fun Container.simpleSelectInput( + options: List<StringPair>? = null, value: String? = null, emptyOption: Boolean = false, + classes: Set<String> = setOf(), init: (SimpleSelectInput.() -> Unit)? = null + ): SimpleSelectInput { + val simpleSelectInput = SimpleSelectInput(options, value, emptyOption, classes).apply { init?.invoke(this) } + this.add(simpleSelectInput) + return simpleSelectInput + } + } +} diff --git a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt index c9ea2dba..393ae63f 100644 --- a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt +++ b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt @@ -157,14 +157,14 @@ abstract class AbstractTextInput( /** * Makes the input element focused. */ - open fun focus() { + override fun focus() { getElementJQuery()?.focus() } /** * Makes the input element blur. */ - open fun blur() { + override fun blur() { getElementJQuery()?.blur() } diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt index 32119a90..68e43da0 100644 --- a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt +++ b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt @@ -69,6 +69,7 @@ enum class TAG(internal val tagName: String) { SAMP("samp"), SPAN("span"), LI("li"), + HR("hr"), CAPTION("caption"), THEAD("thead"), @@ -78,7 +79,10 @@ enum class TAG(internal val tagName: String) { TD("td"), FORM("form"), - INPUT("input") + INPUT("input"), + SELECT("select"), + OPTION("option"), + BUTTON("button") } /** @@ -101,11 +105,13 @@ enum class Align(val className: String) { * @param rich determines if [content] can contain HTML code * @param align content align * @param classes a set of CSS class names + * @param attributes a map of additional attributes * @param init an initializer extension function */ open class Tag( type: TAG, content: String? = null, rich: Boolean = false, align: Align? = null, - classes: Set<String> = setOf(), init: (Tag.() -> Unit)? = null + classes: Set<String> = setOf(), attributes: Map<String, String> = mapOf(), + init: (Tag.() -> Unit)? = null ) : SimplePanel(classes), Template { /** @@ -139,6 +145,7 @@ open class Tag( override var templates: Map<String, (Any?) -> String> by refreshOnUpdate(mapOf()) init { + this.attributes += attributes @Suppress("LeakingThis") init?.invoke(this) } @@ -187,9 +194,10 @@ open class Tag( */ fun Container.tag( type: TAG, content: String? = null, rich: Boolean = false, align: Align? = null, - classes: Set<String> = setOf(), init: (Tag.() -> Unit)? = null + classes: Set<String> = setOf(), attributes: Map<String, String> = mapOf(), + init: (Tag.() -> Unit)? = null ): Tag { - val tag = Tag(type, content, rich, align, classes, init) + val tag = Tag(type, content, rich, align, classes, attributes, init) this.add(tag) return tag } diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt index 310d4d49..010f7cba 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt @@ -24,7 +24,7 @@ package pl.treksoft.kvision.panel import pl.treksoft.kvision.core.Component import pl.treksoft.kvision.core.Container import pl.treksoft.kvision.core.StringPair -import pl.treksoft.kvision.core.StyledComponent +import pl.treksoft.kvision.core.Widget import pl.treksoft.kvision.core.WidgetWrapper import pl.treksoft.kvision.utils.px @@ -152,10 +152,10 @@ open class FlexPanel( } private fun refreshSpacing() { - getChildren().filterIsInstance<StyledComponent>().map { applySpacing(it) } + getChildren().filterIsInstance<Widget>().map { applySpacing(it) } } - private fun applySpacing(wrapper: StyledComponent): StyledComponent { + private fun applySpacing(wrapper: Widget): Widget { wrapper.marginTop = null wrapper.marginRight = null wrapper.marginBottom = null diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt index c17ea1a4..2d9dcc46 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt @@ -23,6 +23,7 @@ package pl.treksoft.kvision.panel import com.github.snabbdom.VNode import com.github.snabbdom.h +import org.w3c.dom.HTMLElement import pl.treksoft.kvision.KVManager import pl.treksoft.kvision.core.StringBoolPair import pl.treksoft.kvision.core.Style @@ -45,7 +46,12 @@ import pl.treksoft.kvision.utils.snOpt * @param init an initializer extension function */ @Suppress("TooManyFunctions") -class Root(id: String, private val fixed: Boolean = false, init: (Root.() -> Unit)? = null) : SimplePanel() { +class Root( + id: String? = null, + element: HTMLElement? = null, + private val fixed: Boolean = false, + init: (Root.() -> Unit)? = null +) : SimplePanel() { private val contextMenus: MutableList<ContextMenu> = mutableListOf() private var rootVnode: VNode = renderVNode() @@ -54,11 +60,17 @@ class Root(id: String, private val fixed: Boolean = false, init: (Root.() -> Uni val isFirstRoot = roots.isEmpty() init { - rootVnode = KVManager.patch(id, this.renderVNode()) - this.id = id + if (id != null) { + rootVnode = KVManager.patch(id, this.renderVNode()) + this.id = id + } else if (element != null) { + rootVnode = KVManager.patch(element, this.renderVNode()) + this.id = "kv_root_${counter++}" + } else { + throw IllegalArgumentException("No root element specified!") + } roots.add(this) if (isFirstRoot) { - Style.styles.forEach { it.parent = this } Modal.modals.forEach { it.parent = this } } @Suppress("LeakingThis") @@ -89,9 +101,8 @@ class Root(id: String, private val fixed: Boolean = false, init: (Root.() -> Uni private fun stylesVNodes(): Array<VNode> { return if (isFirstRoot) { - val visibleStyles = Style.styles.filter { it.visible } - if (visibleStyles.isNotEmpty()) { - val stylesDesc = visibleStyles.joinToString("\n") { it.generateStyle() } + if (Style.styles.isNotEmpty()) { + val stylesDesc = Style.styles.joinToString("\n") { it.generateStyle() } arrayOf(h("style", arrayOf(stylesDesc))) } else { arrayOf() @@ -144,6 +155,8 @@ class Root(id: String, private val fixed: Boolean = false, init: (Root.() -> Uni } companion object { + internal var counter = 0 + /** * @suppress internal function */ diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt index 3b045fa6..37dd449b 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt @@ -55,6 +55,8 @@ open class StackPanel( activeIndex = children.indexOf(value) } + internal val childrenMap = mutableMapOf<Int, Component>() + init { @Suppress("LeakingThis") init?.invoke(this) @@ -76,8 +78,11 @@ open class StackPanel( */ open fun add(panel: Component, route: String): StackPanel { add(panel) - val currentIndex = children.size - 1 - routing.on(route, { _ -> activeIndex = currentIndex }).resolve() + val currentIndex = counter++ + childrenMap[currentIndex] = panel + routing.on(route, { _ -> + activeChild = childrenMap[currentIndex]!! + }).resolve() return this } @@ -97,17 +102,23 @@ open class StackPanel( override fun remove(child: Component): StackPanel { super.remove(child) + childrenMap.filter { it.value == child }.keys.firstOrNull()?.let { + childrenMap.remove(it) + } if (activeIndex > children.size - 1) activeIndex = children.size - 1 return this } override fun removeAll(): StackPanel { super.removeAll() + childrenMap.clear() if (activeIndex > children.size - 1) activeIndex = children.size - 1 return this } companion object { + internal var counter = 0 + /** * DSL builder extension function. * diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt index 5a4fac0c..ae8360b5 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt @@ -25,10 +25,13 @@ import pl.treksoft.kvision.core.Component import pl.treksoft.kvision.core.Container import pl.treksoft.kvision.core.ResString import pl.treksoft.kvision.core.WidgetWrapper -import pl.treksoft.kvision.html.Link +import pl.treksoft.kvision.html.Icon +import pl.treksoft.kvision.html.Link.Companion.link import pl.treksoft.kvision.html.TAG import pl.treksoft.kvision.html.Tag import pl.treksoft.kvision.routing.routing +import pl.treksoft.kvision.utils.obj +import pl.treksoft.kvision.html.Icon.Companion.icon as cicon /** * Tab position. @@ -95,6 +98,8 @@ open class TabPanel( private var nav = Tag(TAG.UL, classes = navClasses) private var content = StackPanel(false) + internal val childrenMap = mutableMapOf<Int, Component>() + init { when (tabPosition) { TabPosition.TOP -> { @@ -135,23 +140,46 @@ open class TabPanel( * @param panel child component * @param icon icon of the tab * @param image image of the tab + * @param closable determines if this tab is closable * @param route JavaScript route to activate given child * @return current container */ open fun addTab( title: String, panel: Component, icon: String? = null, - image: ResString? = null, route: String? = null + image: ResString? = null, closable: Boolean = false, route: String? = null ): TabPanel { - val tag = Tag(TAG.LI) - tag.role = "presentation" - tag.add(Link(title, "#", icon, image)) - val index = nav.children.size - tag.setEventListener { - click = { e -> - activeIndex = index - e.preventDefault() - if (route != null) { - routing.navigate(route) + val currentIndex = counter++ + childrenMap[currentIndex] = panel + val tag = Tag(TAG.LI) { + role = "presentation" + link(title, "#", icon, image) { + if (closable) { + cicon("remove") { + addCssClass("kv-tab-close") + setEventListener<Icon> { + click = { e -> + val actIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex]) + e.asDynamic().data = actIndex + if (this@TabPanel.dispatchEvent( + "tabClosing", + obj { detail = e; cancelable = true }) != false + ) { + this@TabPanel.removeTab(actIndex) + this@TabPanel.dispatchEvent("tabClosed", obj { detail = e }) + } + e.stopPropagation() + } + } + } + } + } + setEventListener { + click = { e -> + activeIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex]) + e.preventDefault() + if (route != null) { + routing.navigate(route) + } } } } @@ -162,7 +190,8 @@ open class TabPanel( } content.add(panel) if (route != null) { - routing.on(route, { _ -> activeIndex = index }).resolve() + routing.on(route, { _ -> activeIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex]) }) + .resolve() } return this } @@ -172,6 +201,9 @@ open class TabPanel( */ open fun removeTab(index: Int): TabPanel { nav.remove(nav.children[index]) + childrenMap.filter { it.value == content.children[index] }.keys.firstOrNull()?.let { + childrenMap.remove(it) + } content.remove(content.children[index]) activeIndex = content.activeIndex return this @@ -191,14 +223,32 @@ open class TabPanel( return removeTab(index) } + /** + * Returns child component by tab index. + * @param index tab index + */ + open fun getChildComponent(index: Int): Component? { + return content.children[index] + } + + /** + * Returns tab header component by tab index. + * @param index tab index + */ + open fun getNavComponent(index: Int): Tag? { + return nav.children[index] as? Tag + } + override fun removeAll(): TabPanel { content.removeAll() nav.removeAll() + childrenMap.clear() refresh() return this } companion object { + internal var counter = 0 /** * DSL builder extension function. * diff --git a/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt b/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt index 950dfe1c..4628d989 100644 --- a/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt +++ b/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt @@ -48,4 +48,4 @@ open class Routing : Navigo(null, true, "#!") { /** * Default JavaScript router. */ -var routing = Routing() +var routing = Routing().also { it.resolve() } diff --git a/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt b/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt index d2177a97..cb48cfd1 100644 --- a/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt +++ b/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt @@ -130,6 +130,8 @@ interface BtOn : On { var tabulatorDataLoading: ((KvEvent) -> Unit)? var tabulatorDataLoaded: ((KvEvent) -> Unit)? var tabulatorDataEdited: ((KvEvent) -> Unit)? + var tabClosing: ((KvEvent) -> Unit)? + var tabClosed: ((KvEvent) -> Unit)? } /** diff --git a/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt index 37d7a9df..13c8531b 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt @@ -86,7 +86,7 @@ interface WSpec : DomSpec { fun runW(code: (widget: Widget, element: Element?) -> Unit) { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() widget.id = "test_id" root.add(widget) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt index 3e16fff8..960a18b6 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt @@ -34,7 +34,7 @@ class ContainerSpec : DomSpec { @Test fun add() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val container = SimplePanel() val child1 = Widget() child1.id = "child1" @@ -52,7 +52,7 @@ class ContainerSpec : DomSpec { @Test fun addAll() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val container = SimplePanel() val child1 = Widget() child1.id = "child1" @@ -69,7 +69,7 @@ class ContainerSpec : DomSpec { @Test fun remove() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val container = SimplePanel() val child1 = Widget() child1.id = "child1" @@ -88,7 +88,7 @@ class ContainerSpec : DomSpec { @Test fun removeAll() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val container = SimplePanel() val child1 = Widget() child1.id = "child1" @@ -107,7 +107,7 @@ class ContainerSpec : DomSpec { @Test fun getChildren() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val container = SimplePanel() val child1 = Widget() child1.id = "child1" diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt index bdb93744..4fbe3d59 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt @@ -37,7 +37,7 @@ class StyleSpec : DomSpec { @Test fun render() { run { - Root("test", true) { + Root("test", fixed = true) { widget { style { margin = 2.px @@ -58,7 +58,7 @@ class StyleSpec : DomSpec { @Test fun renderCustomClass() { run { - Root("test", true) { + Root("test", fixed = true) { widget { style("customclass") { margin = 2.px @@ -79,7 +79,7 @@ class StyleSpec : DomSpec { @Test fun renderSubclass() { run { - Root("test", true) { + Root("test", fixed = true) { widget { style("customclass") { margin = 2.px diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt index fc1cc761..6b9be23d 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt @@ -130,7 +130,7 @@ class WidgetSpec : WSpec { @Test fun getRoot() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() root.add(widget) val r = widget.getRoot() diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt index aac92c50..0c28c327 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt @@ -35,7 +35,7 @@ class WidgetWrapperSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val widget = Widget() val wrapper = WidgetWrapper(widget) wrapper.width = 100 to UNIT.em diff --git a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt index 7d320e2e..35172267 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt @@ -34,7 +34,7 @@ class ContextMenuSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val m = ContextMenu { link("a", "b") link("c", "d") @@ -54,7 +54,7 @@ class ContextMenuSpec : DomSpec { @Test fun positionMenu() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val m = ContextMenu { link("a", "b") link("c", "d") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt index 5a9a050c..e75baf9e 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt @@ -32,7 +32,7 @@ class HeaderSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val h = Header("Test") root.add(h) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt index 2f2d22bf..86607ec7 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt @@ -32,7 +32,7 @@ class SeparatorSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val s = Separator() root.add(s) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt index d13bc920..5319d4bc 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt @@ -32,7 +32,7 @@ class FieldLabelSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val fl = FieldLabel("input", "Label") root.add(fl) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt index c7d0b0da..c7c4ede5 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt @@ -32,7 +32,7 @@ class HelpBlockSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val fl = HelpBlock("Form Error") root.add(fl) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt index 8a9f86d5..677a2b8e 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt @@ -32,7 +32,7 @@ class CheckBoxInputSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ci = CheckBoxInput(value = true).apply { name = "name" id = "idti" diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt index 9a178abb..16da0c70 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt @@ -33,7 +33,7 @@ class CheckBoxSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ci = CheckBox(value = true, label = "Label").apply { name = "name" style = CheckBoxStyle.DANGER diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt index f74a76f7..55788c84 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt @@ -33,7 +33,7 @@ class RadioGroupInputSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ci = RadioGroupInput(options = listOf("a" to "A", "b" to "B"), value = "a").apply { disabled = true inline = true diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt index e55e9913..2ed52b67 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt @@ -33,7 +33,7 @@ class RadioGroupSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ci = RadioGroup(options = listOf("a" to "A", "b" to "B"), value = "a", label = "Label").apply { disabled = true inline = true diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt index 55d4108a..5b4fe836 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt @@ -32,7 +32,7 @@ class RadioInputSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ci = RadioInput(value = true).apply { name = "name" id = "idti" diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt index 09763cc7..a8fbbcc5 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt @@ -33,7 +33,7 @@ class RadioSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ci = Radio(value = true, label = "Label", extraValue = "abc").apply { name = "name" style = RadioStyle.DANGER diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectInputSpec.kt new file mode 100644 index 00000000..b2c77d10 --- /dev/null +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectInputSpec.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package test.pl.treksoft.kvision.form.select + +import pl.treksoft.kvision.form.select.SimpleSelectInput +import pl.treksoft.kvision.panel.Root +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class SimpleSelectInputSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val si = SimpleSelectInput(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", true).apply { + name = "name" + id = "idti" + disabled = true + } + root.add(si) + val element = document.getElementById("test") + assertEqualsHtml( + "<select class=\"form-control\" id=\"idti\" name=\"name\" disabled=\"disabled\"><option value=\"#kvnull\"></option><option value=\"test1\" selected=\"selected\">Test 1</option><option value=\"test2\">Test 2</option></select>", + element?.innerHTML, + "Should render correct simple select input control" + ) + } + } + +} diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectSpec.kt new file mode 100644 index 00000000..db1c36f0 --- /dev/null +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectSpec.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2017-present Robert Jaros + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package test.pl.treksoft.kvision.form.select + +import pl.treksoft.kvision.form.select.SimpleSelect +import pl.treksoft.kvision.panel.Root +import test.pl.treksoft.kvision.DomSpec +import kotlin.browser.document +import kotlin.test.Test + +class SimpleSelectSpec : DomSpec { + + @Test + fun render() { + run { + val root = Root("test", fixed = true) + val select = + SimpleSelect(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", true, "select", "Label").apply { + name = "name" + id = "idti" + disabled = true + } + root.add(select) + val element = document.getElementById("test") + val id = select.input.id + assertEqualsHtml( + "<div class=\"form-group\" id=\"idti\"><label class=\"control-label\" for=\"$id\">Label</label><select class=\"form-control\" id=\"$id\" name=\"name\" disabled=\"disabled\"><option value=\"#kvnull\"></option><option value=\"test1\" selected=\"selected\">Test 1</option><option value=\"test2\">Test 2</option></select></div>", + element?.innerHTML, + "Should render correct simple select form control" + ) + } + } + +} diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt index dc46782b..5d6c2738 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt @@ -32,7 +32,7 @@ class PasswordSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ti = Password(value = "abc", label = "Label").apply { placeholder = "place" name = "name" diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt index 0e8257ff..815f12e7 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt @@ -32,7 +32,7 @@ class TextAreaInputSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ti = TextAreaInput(cols = 5, rows = 2, value = "abc").apply { placeholder = "place" name = "name" diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt index c948628b..31b4baf2 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt @@ -32,7 +32,7 @@ class TextAreaSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ti = TextArea(cols = 5, rows = 2, value = "abc", label = "Label").apply { placeholder = "place" name = "name" diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt index bd8c9786..6d21ab14 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt @@ -33,7 +33,7 @@ class TextInputSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ti = TextInput(type = TextInputType.PASSWORD, value = "abc").apply { placeholder = "place" name = "name" diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt index 071bf35b..7ce811fa 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt @@ -32,7 +32,7 @@ class TextSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ti = Text(value = "abc", label = "Label").apply { placeholder = "place" name = "name" diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt index de2ea036..512c3bc2 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt @@ -34,7 +34,7 @@ class ButtonSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val button = Button("Cancel", "fa-bars", ButtonStyle.PRIMARY) button.size = ButtonSize.LARGE button.block = true diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt index a896d9e5..f30872ca 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt @@ -32,7 +32,7 @@ class CanvasSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val canvas = Canvas(800, 600) root.add(canvas) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt index 4fd64478..528fc0ae 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt @@ -32,7 +32,7 @@ class DivSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val div = Div("This is a div") root.add(div) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt index 6398d95c..69819203 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt @@ -32,7 +32,7 @@ class FooterSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val footer = Footer("This is a footer") root.add(footer) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt index 6f22b89f..bd0c69b6 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt @@ -32,7 +32,7 @@ class H1Spec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val h1 = H1("This is h1 header") root.add(h1) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt index 1ca74077..5ec2a666 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt @@ -32,7 +32,7 @@ class H2Spec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val h2 = H2("This is h2 header") root.add(h2) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt index a9f58473..f85ede34 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt @@ -32,7 +32,7 @@ class H3Spec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val h3 = H3("This is h3 header") root.add(h3) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt index 47550459..ce52282b 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt @@ -32,7 +32,7 @@ class H4Spec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val h4 = H4("This is h4 header") root.add(h4) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt index 6b3fc647..029a692f 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt @@ -32,7 +32,7 @@ class H5Spec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val h5 = H5("This is h5 header") root.add(h5) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt index e22bd401..06a851e0 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt @@ -32,7 +32,7 @@ class H6Spec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val div = H1("This is h1 header") root.add(div) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt index e5ea8679..1b33adcc 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt @@ -32,7 +32,7 @@ class HeaderSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val header = Header("This is a header") root.add(header) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt index fde07413..ea3425c5 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt @@ -32,7 +32,7 @@ class IconSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val icon = Icon("fa-check") root.add(icon) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt index 60a380b7..69051f92 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt @@ -33,7 +33,7 @@ class IframeSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val iframe = Iframe("https://www.google.com", null, "test", 800, 600, setOf(Sandbox.ALLOWSAMEORIGIN)) root.add(iframe) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt index fa32a24f..169575fc 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt @@ -34,7 +34,7 @@ class ImageSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val res = require("./img/placeholder.png") @Suppress("UnsafeCastFromDynamic") val image = Image(res, "Image", true, ImageShape.ROUNDED, true) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt index 6a4434ae..88869cf4 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt @@ -32,7 +32,7 @@ class LinkSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val link = Link("Google", "http://www.google.com") root.add(link) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt index 5f0aac69..bf52f1dc 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt @@ -35,7 +35,7 @@ class ListSpec : DomSpec { @Test fun renderElements() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val list = ListTag(ListType.DL_HORIZ, listOf("a1", "a2", "b1", "b2")) root.add(list) val element = document.getElementById("test") @@ -50,7 +50,7 @@ class ListSpec : DomSpec { @Test fun renderAsContainer() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val list = ListTag(ListType.UL) list.add(Tag(TAG.PRE, "pre")) list.add(Tag(TAG.DEL, "del")) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt index d5af81e3..0ab9c5bc 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt @@ -32,7 +32,7 @@ class PSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val par = P("This is a paragraph") root.add(par) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt index 4fb7a490..fa987d05 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt @@ -32,7 +32,7 @@ class SectionSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val section = Section("This is a section") root.add(section) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt index b1f49b45..88604b5e 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt @@ -32,7 +32,7 @@ class SpanSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val span = Span("This is a label") root.add(span) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt index 29a5c079..b6017804 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt @@ -35,7 +35,7 @@ class TagSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val tag = Tag(TAG.H1, "This is <b>h1</b>", rich = false, align = Align.CENTER) root.add(tag) val element = document.getElementById("test") @@ -50,7 +50,7 @@ class TagSpec : DomSpec { @Test fun renderRich() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val tag = Tag(TAG.H1, "This is <b>h1</b>", rich = true, align = Align.RIGHT) root.add(tag) val element = document.getElementById("test") @@ -65,7 +65,7 @@ class TagSpec : DomSpec { @Test fun renderAsContainer() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val tag = Tag(TAG.P, align = Align.RIGHT) tag.add(Tag(TAG.DEL, "This is test")) tag.add(Link("abc", "/x")) @@ -82,7 +82,7 @@ class TagSpec : DomSpec { @Test fun renderUnaryPlus() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val tag = Tag(TAG.H1, rich = true) { +"This is <b>h1</b>" } diff --git a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt index aeea7a3b..40720bcb 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt @@ -32,7 +32,7 @@ class NavFormSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val navf = NavForm() root.add(navf) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt index 2ecad124..988a706d 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt @@ -32,7 +32,7 @@ class NavSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val nav = Nav() root.add(nav) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt index 90af4450..f38a05f9 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt @@ -37,7 +37,7 @@ class NavbarSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val navbar = Navbar("TEST", NavbarType.FIXEDTOP) root.add(navbar) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt index d387be56..5684120d 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt @@ -34,7 +34,7 @@ class DockPanelSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val dockPanel = DockPanel() root.add(dockPanel) dockPanel.add(Span("abc"), Side.UP) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt index 5bcc68f6..b897fef2 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt @@ -35,7 +35,7 @@ class FlexPanelSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val flexPanel = FlexPanel(FlexDir.ROWREV, justify = FlexJustify.SPACEEVENLY) root.add(flexPanel) flexPanel.add(Span("abc"), 1) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt index add3638f..d7d9beb7 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt @@ -33,7 +33,7 @@ class GridPanelSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val gridPanel = GridPanel() root.add(gridPanel) gridPanel.add(Span("abc"), 1, 1) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt index a185420c..c53b2e57 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt @@ -34,7 +34,7 @@ class HPanelSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val hPanel = HPanel(justify = FlexJustify.SPACEBETWEEN) root.add(hPanel) hPanel.add(Span("abc"), 1) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt index eb710cef..fcdf9860 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt @@ -33,7 +33,7 @@ class ResponsiveGridPanelSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val rgPanel = ResponsiveGridPanel() root.add(rgPanel) rgPanel.add(Span("abc"), 1, 1) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt index 659dac9a..474b1d6a 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt @@ -50,7 +50,7 @@ class RootSpec : DomSpec { @Test fun getRoot() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val r = root.getRoot() assertTrue("Should return self") { r == root } } diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt index 09c397ce..94ed165a 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt @@ -34,7 +34,7 @@ class SplitPanelSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val splitPanel = SplitPanel(Direction.VERTICAL) root.add(splitPanel) val label1 = Span("abc") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt index f52b6486..8a1ce795 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt @@ -33,7 +33,7 @@ class StackPanelSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val stackPanel = StackPanel() root.add(stackPanel) val label1 = Span("abc") @@ -48,7 +48,7 @@ class StackPanelSpec : DomSpec { @Test fun renderNotActivateLast() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val stackPanel = StackPanel(activateLast = false) root.add(stackPanel) val label1 = Span("abc") @@ -67,7 +67,7 @@ class StackPanelSpec : DomSpec { @Test fun remove() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val stackPanel = StackPanel(activateLast = false) root.add(stackPanel) val label1 = Span("abc") @@ -83,7 +83,7 @@ class StackPanelSpec : DomSpec { @Test fun removeAll() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val stackPanel = StackPanel(activateLast = false) root.add(stackPanel) val label1 = Span("abc") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt index 900a7268..35620818 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt @@ -21,6 +21,7 @@ */ package test.pl.treksoft.kvision.panel +import pl.treksoft.jquery.jQuery import pl.treksoft.kvision.html.Span import pl.treksoft.kvision.panel.Root import pl.treksoft.kvision.panel.TabPanel @@ -33,7 +34,7 @@ class TabPanelSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val tabs = TabPanel() root.add(tabs) val label1 = Span("abc") @@ -52,7 +53,7 @@ class TabPanelSpec : DomSpec { @Test fun setActiveIndex() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val tabs = TabPanel() root.add(tabs) val label1 = Span("abc") @@ -72,7 +73,7 @@ class TabPanelSpec : DomSpec { @Test fun removeTab() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val tabs = TabPanel() root.add(tabs) val label1 = Span("abc") @@ -89,4 +90,28 @@ class TabPanelSpec : DomSpec { ) } } + + + @Test + fun tabClick() { + run { + val root = Root("test", fixed = true) + val tabs = TabPanel() + root.add(tabs) + val label1 = Span("abc") + val label2 = Span("def") + tabs.addTab("ABC", label1) + tabs.addTab("DEF", label2) + tabs.removeTab(0) + val label3 = Span("ghi") + tabs.addTab("GHI", label3) + jQuery("#test a")[0]?.click() + val element = document.getElementById("test") + assertEqualsHtml( + "<div><ul class=\"nav nav-tabs\"><li role=\"presentation\" class=\"\"><a href=\"#\">DEF</a></li><li role=\"presentation\"><a href=\"#\">GHI</a></li></ul><div><span>def</span></div></div>", + element?.innerHTML, + "Should select correct tab by clicking" + ) + } + } }
\ No newline at end of file diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt index 81ea720d..5bed89f1 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt @@ -34,7 +34,7 @@ class VPanelSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val vPanel = VPanel(justify = FlexJustify.SPACEBETWEEN) root.add(vPanel) vPanel.add(Span("abc"), 1) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt index d520f0b6..2f044987 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt @@ -33,7 +33,7 @@ class ProgressBarSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val progressBar = ProgressBar(50, style = ProgressBarStyle.SUCCESS, striped = true, content = "Processing ...") root.add(progressBar) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt index 83892ed9..4aa14230 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt @@ -33,7 +33,7 @@ class ProgressIndicatorSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val ind = ProgressIndicator(50, style = ProgressBarStyle.SUCCESS, striped = true) root.add(ind) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt index 435e9b22..582212bd 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt @@ -32,7 +32,7 @@ class CellSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val cell = Cell("This is a cell") root.add(cell) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt index 8c210ae0..40f25a66 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt @@ -32,7 +32,7 @@ class HeaderCellSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val cell = HeaderCell("This is a header cell") root.add(cell) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt index 6c2f3c1c..d9a6fdfa 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt @@ -33,7 +33,7 @@ class RowSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val row = Row { cell("A") } diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt index 637f8f28..997da597 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt @@ -35,7 +35,7 @@ class TableSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val table = Table(listOf("a", "b")) { row { cell("A") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt index 16b352d8..b324b8ab 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt @@ -34,7 +34,7 @@ class ButtonGroupSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val group = ButtonGroup() root.add(group) val element = document.getElementById("test") diff --git a/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt index 4d296e03..d41ef05e 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt @@ -32,7 +32,7 @@ class ToolbarSpec : DomSpec { @Test fun render() { run { - val root = Root("test", true) + val root = Root("test", fixed = true) val toolbar = Toolbar() root.add(toolbar) val element = document.getElementById("test") |