├── .gitignore ├── LICENSE ├── README.md ├── android ├── .gitignore ├── README.md ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── kweb │ │ │ └── demo │ │ │ └── android │ │ │ ├── MainActivity.kt │ │ │ └── ToDoState.kt │ │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── main_activity.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── formValidation ├── .gitignore ├── README.md ├── build.gradle ├── gradlew ├── gradlew.bat └── src │ ├── forms.kt │ ├── main.kt │ └── ui_utils.kt ├── helloWorld ├── .gitignore ├── README.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── helloWorld.kt ├── https ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── HttpsApp.kt ├── ktorFeature ├── .gitignore ├── README.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── FeatureApp.kt ├── modalDialog ├── .gitignore ├── README.md ├── build.gradle ├── gradlew ├── gradlew.bat ├── preview.gif └── src │ ├── main.kt │ └── modal.kt ├── sessionAndAuth ├── .gitignore ├── README.md ├── build.gradle ├── gradlew.bat └── src │ ├── main.kt │ ├── pages │ ├── indexPage.kt │ ├── loginPage.kt │ ├── loginRequiredPage.kt │ ├── logoutPage.kt │ └── publicPage.kt │ └── sessions.kt └── todoList ├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src └── main ├── kotlin ├── ToDoState.kt └── TodoApp.kt └── resources └── logback.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/** 2 | **.iml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kweb demos 2 | Simple projects that demonstrate [kweb's](https://kweb.io) capabilities 🦆 3 | 4 | Each subfolder is a standalone project. 5 | 6 | - android: shows how to run kweb in an android app 7 | - helloWorld: very simple "hello world" app 8 | - https: demo with https enabled 9 | - ktorFeature: shows kweb being used as a [ktor feature](https://ktor.io/servers/features.html) 10 | - todoList: a todo-list app showing off some more advanced features 11 | 12 | See their readme files for more info. -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/** 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | -------------------------------------------------------------------------------- /android/README.md: -------------------------------------------------------------------------------- 1 | This project is an android app with embedded [kweb](https://github.com/kwebio/kweb-core) server. 2 | The server is running on port 7659. This app contains a webview which loads that page, but you 3 | can also load the page by navigating to http://localhost:7659 in your phone's browser. 4 | 5 | # How to run Kweb in an android app 6 | 7 | 1) add the [Jitpack](https://jitpack.io/) repo to the 8 | project's gradle file 9 | ([Jump to code](https://github.com/kwebio/kweb-demos/blob/master/android/build.gradle#L22)): 10 | `maven { url 'https://jitpack.io' }` 11 | 2) add the following config options to the `android` section of the app's gradle file 12 | ([jump to code](https://github.com/kwebio/kweb-demos/blob/master/android/app/build.gradle#L25)): 13 | ```groovy 14 | packagingOptions { 15 | exclude 'META-INF/*' 16 | } 17 | 18 | compileOptions { 19 | sourceCompatibility = 1.8 20 | targetCompatibility = 1.8 21 | } 22 | 23 | kotlinOptions { 24 | jvmTarget = "1.8" 25 | } 26 | ``` 27 | 3) add Kweb as a dependency ([jump to code](https://github.com/kwebio/kweb-demos/blob/master/android/app/build.gradle#L47)): 28 | ```groovy 29 | implementation('com.github.kwebio:core:0.7.5') 30 | ``` 31 | 4) Add the internet permission to the manifest 32 | ([jump to code](https://github.com/kwebio/kweb-demos/blob/master/android/app/src/main/AndroidManifest.xml#L4)): 33 | `` 34 | 5) Run the Kweb server in your main activity: 35 | ```kotlin 36 | Kweb(port = 7659, buildPage = { 37 | doc.body.new { 38 | h1().text("Hello World!") 39 | } 40 | }) 41 | ``` 42 | 43 | That's all you need in order for kweb to be running inside your app. If you want to also 44 | embed the webview, you will need to: 45 | 46 | 1) Add a [WebView](https://developer.android.com/reference/android/webkit/WebView) somewhere 47 | in your app, and have it load the server's url: 48 | ```kotlin 49 | val webView: WebView = findViewById(R.id.webViewWidget) 50 | 51 | webView.settings.javaScriptEnabled = true 52 | webView.webViewClient = WebViewClient() 53 | webView.loadUrl("http://localhost:16097") 54 | ``` 55 | 2) allow the app to use cleartext http by adding this attribute to the `` 56 | tag in the manifest: `android:usesCleartextTraffic="true"` 57 | 58 | 59 | Thanks to the following sources: 60 | - [Diamantidis Guide to running Ktor on Android](https://diamantidis.github.io/2019/11/10/running-an-http-server-on-an-android-app) 61 | 62 | # Caveats 63 | 64 | This app has been tested against SDK version 26 (Android 8.0). However, according to 65 | [some sources](https://caniuse.com/#feat=websockets), websockets are supported in Android 4.4 and higher. 66 | YMMV. -------------------------------------------------------------------------------- /android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion 28 7 | 8 | defaultConfig { 9 | applicationId "kweb.demos.android" 10 | minSdkVersion 26 11 | targetSdkVersion 28 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | packagingOptions { 26 | exclude 'META-INF/*' 27 | } 28 | 29 | compileOptions { 30 | sourceCompatibility = 1.8 31 | targetCompatibility = 1.8 32 | } 33 | 34 | kotlinOptions { 35 | jvmTarget = "1.8" 36 | } 37 | } 38 | 39 | dependencies { 40 | implementation fileTree(dir: 'libs', include: ['*.jar']) 41 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 42 | implementation 'androidx.appcompat:appcompat:1.1.0' 43 | implementation 'androidx.core:core-ktx:1.2.0' 44 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 45 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' 46 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' 47 | testImplementation 'junit:junit:4.12' 48 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 49 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 50 | 51 | implementation('com.github.kwebio:core:0.7.5') 52 | } 53 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/main/java/kweb/demo/android/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package kweb.demo.android 2 | 3 | import android.content.Context 4 | import androidx.appcompat.app.AppCompatActivity 5 | import android.os.Bundle 6 | import android.webkit.WebView 7 | import android.webkit.WebViewClient 8 | import kotlinx.coroutines.GlobalScope 9 | import kotlinx.coroutines.future.await 10 | import kotlinx.coroutines.launch 11 | import kweb.* 12 | import kweb.demos.todo.ToDoState 13 | import kweb.plugins.fomanticUI.fomantic 14 | import kweb.plugins.fomanticUI.fomanticUIPlugin 15 | import kweb.state.* 16 | import mu.KotlinLogging 17 | import java.nio.file.Path 18 | import java.nio.file.Paths 19 | import java.time.Instant 20 | 21 | class MainActivity : AppCompatActivity() { 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | setContentView(R.layout.main_activity) 26 | 27 | TodoApp(this.baseContext) 28 | 29 | val webView: WebView = findViewById(R.id.webViewWidget) 30 | 31 | webView.settings.javaScriptEnabled = true 32 | webView.webViewClient = WebViewClient() 33 | webView.loadUrl("http://localhost:7659") 34 | } 35 | 36 | class TodoApp (context:Context){ 37 | 38 | private val logger = KotlinLogging.logger {} 39 | 40 | val state = ToDoState(context.filesDir.toPath()) 41 | val plugins = listOf(fomanticUIPlugin) 42 | val server: Kweb 43 | 44 | init { 45 | 46 | /** Create a Kweb instance, and configure it to use the Fomantic 47 | * UI framework. Build a simple to-do list app listening on 48 | * http://localhost:7659/ 49 | * */ 50 | server = Kweb(port = 7659, debug = true, plugins = plugins, buildPage = { 51 | doc.head.new { 52 | // Not required, but recommended by HTML spec 53 | meta( 54 | name = "Description", 55 | content = "A simple To Do list app to demonstrate Kweb" 56 | ) 57 | } 58 | 59 | doc.body.new { 60 | /** Kweb allows you to modularize your code however suits your needs 61 | best. Here I use an extension function defined elsewhere to 62 | draw some util outer page DOM elements */ 63 | pageBorderAndTitle("To do List") { 64 | div(fomantic.content).new { 65 | 66 | route { 67 | 68 | path("/") { 69 | val newListId = createNewList() 70 | /** 71 | * This will cause the page to switch to the newly created list automatically, and 72 | * without a page refresh. 73 | */ 74 | url.value = "/lists/$newListId" 75 | } 76 | 77 | path("/lists/{id}") { params -> 78 | doc.head.new { 79 | title().text("To Do List #${params.getValue("id").value}") 80 | } 81 | render(params.getValue("id")) { listId -> 82 | logger.info("Rendering list id $listId") 83 | 84 | try { 85 | /** Here I use the same render mechanism to tie DOM 86 | state to persistent state stored in Shoebox, a simple but powerful 87 | key-value store with observer pattern support. */ 88 | val list: KVar = 89 | toVar(state.lists, listId) 90 | 91 | renderList(list) 92 | } catch (e: NoSuchElementException) { 93 | throw NotFoundException("Can't find list with id $listId") 94 | } 95 | } 96 | } 97 | 98 | /* 99 | * It's not necessary, but we can also define a custom 404 handler: 100 | */ 101 | notFound { 102 | div(fomantic.ui.negative.message).new { 103 | div(fomantic.header).text("Not Found :(") 104 | p().text(url.map { "Unable to find path $it" }) 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | }) 112 | } 113 | 114 | private fun ElementCreator<*>.pageBorderAndTitle( 115 | title: String, 116 | content: ElementCreator.() -> Unit 117 | ) { 118 | div(fomantic.ui.main.container).new { 119 | div(fomantic.column).new { 120 | div(fomantic.ui.vertical.segment).new { 121 | div(fomantic.ui.message).new { 122 | p().innerHTML( 123 | """ 124 | A simple demo of Kweb, add and delete items from a 125 | to do list. 126 |

127 | Try visiting this URL in another browser window and make some changes. 128 |

129 | You may find the source code for this app 130 | here. 131 | """ 132 | .trimIndent() 133 | ) 134 | } 135 | } 136 | 137 | div(fomantic.ui.vertical.segment).new { 138 | h1(fomantic.ui.dividing.header).text(title) 139 | content(this) 140 | } 141 | } 142 | } 143 | } 144 | 145 | private fun createNewList(): String { 146 | val newListId = generateNewUid() 147 | state.lists[newListId] = ToDoState.List(newListId, "") 148 | return newListId 149 | } 150 | 151 | private fun ElementCreator<*>.renderList(list: KVar) { 152 | h3().text(list.property(ToDoState.List::title)) 153 | div(fomantic.ui.middle.aligned.divided.list).new { 154 | renderEach(state.itemsByList(list.value.uid)) { item -> 155 | div(fomantic.item).new { 156 | div(fomantic.right.floated.content).new { 157 | renderRemoveButton(item) 158 | } 159 | div(fomantic.content).text(item.map(ToDoState.Item::text)) 160 | } 161 | } 162 | } 163 | div(fomantic.ui.action.input).new { 164 | val input = input(InputType.text, placeholder = "Add Item") 165 | input.on.keypress { ke -> 166 | if (ke.code == "Enter") { 167 | handleAddItem(input, list) 168 | } 169 | } 170 | button(fomantic.ui.button).text("Add").apply { 171 | on.click { 172 | handleAddItem(input, list) 173 | } 174 | } 175 | } 176 | } 177 | 178 | private fun handleAddItem(input: InputElement, list: KVar) { 179 | GlobalScope.launch { 180 | val newItemText = input.getValue().await() 181 | input.setValue("") 182 | val newItem = 183 | ToDoState.Item(generateNewUid(), Instant.now(), list.value.uid, newItemText) 184 | state.items[newItem.uid] = newItem 185 | } 186 | } 187 | 188 | private fun ElementCreator.renderRemoveButton(item: KVar) { 189 | val button = button(fomantic.mini.ui.icon.button) 190 | button.new { 191 | i(fomantic.trash.icon) 192 | } 193 | button.on.click { 194 | state.items.remove(item.value.uid) 195 | } 196 | } 197 | 198 | private fun generateNewUid() = random.nextInt(100_000_000).toString(16) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /android/app/src/main/java/kweb/demo/android/ToDoState.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.todo 2 | 3 | import kweb.shoebox.Shoebox 4 | import java.nio.file.Files 5 | import java.nio.file.Path 6 | import java.time.Instant 7 | 8 | /** 9 | * Stores all persistent state for this app in a directory, creating it if necessary 10 | */ 11 | 12 | class ToDoState(dir: Path) { 13 | init { 14 | if (Files.notExists(dir)) { 15 | Files.createDirectory(dir) 16 | } 17 | } 18 | 19 | data class List(val uid: String, val title: String) 20 | 21 | data class Item(val uid: String, val created: Instant, val listUid: String, val text: String) 22 | 23 | val lists = Shoebox(dir.resolve("lists")) 24 | 25 | val items = Shoebox(dir.resolve("items")) 26 | 27 | private val itemsByList = items.view("itemsByList", Item::listUid) 28 | 29 | fun itemsByList(listUid: String) = itemsByList.orderedSet(listUid, compareBy(Item::created)) 30 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /android/app/src/main/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #6200EE 4 | #3700B3 5 | #03DAC5 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | kweb-android-demo 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.4.0' 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:4.0.0' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | maven { url 'https://jitpack.io' } 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 22 20:04:41 EDT 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name='kweb-android-demo' 2 | include ':app' 3 | -------------------------------------------------------------------------------- /formValidation/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .gradle 3 | -------------------------------------------------------------------------------- /formValidation/README.md: -------------------------------------------------------------------------------- 1 | This folder contains a simple form demo. You can start the server 2 | by running `gradlew run`. You can then view the app at http://localhost:16097 -------------------------------------------------------------------------------- /formValidation/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jetbrains.kotlin.jvm' version '1.4.0' 3 | id 'application' 4 | } 5 | 6 | mainClassName = "kweb.demos.helloWorld.MainKt" 7 | 8 | sourceSets { 9 | main.kotlin.srcDirs += 'src' 10 | } 11 | 12 | repositories { 13 | jcenter() 14 | maven { url 'https://jitpack.io' } 15 | } 16 | 17 | dependencies { 18 | implementation 'com.github.kwebio:kweb-core:0.7.21' 19 | implementation 'org.slf4j:slf4j-simple:1.7.30' 20 | } 21 | -------------------------------------------------------------------------------- /formValidation/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /formValidation/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /formValidation/src/forms.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld 2 | 3 | import com.github.salomonbrys.kotson.fromJson 4 | import kweb.* 5 | import kweb.plugins.fomanticUI.fomantic 6 | import kweb.state.KVar 7 | import kweb.state.render 8 | import kweb.util.gson 9 | import kweb.util.random 10 | import kotlin.math.abs 11 | 12 | class FormControl{ 13 | 14 | private val inputs: MutableList = mutableListOf() 15 | val errors: KVar> = KVar(listOf()) 16 | val formLevelValidations = mutableListOf<()->String?>() 17 | 18 | fun add(fi:FormInput){ 19 | inputs.add(fi) 20 | } 21 | 22 | private fun validate(){ 23 | logger.info("Running validations") 24 | errors.value = inputs.mapNotNull { it.checkInput() }.plus(formLevelValidations.mapNotNull { it.invoke() }).also { 25 | it.forEach { error-> 26 | logger.warn(error) 27 | } 28 | logger.info("Done. Found ${it.size} errors") 29 | } 30 | } 31 | 32 | val isValid: Boolean 33 | get() { 34 | validate() 35 | return errors.value.isEmpty() 36 | } 37 | 38 | fun withValidation(func: ()->String?){ 39 | formLevelValidations.add(func) 40 | } 41 | } 42 | 43 | fun ElementCreator<*>.formControl(block: ElementCreator<*>.(form:FormControl)->Unit) : FormControl { 44 | val fc = FormControl() 45 | 46 | form(fomantic.ui.form).new(){ 47 | fc.errors.addListener { old, new -> 48 | logger.info("Form Error state did change.") 49 | 50 | if(old.isNotEmpty() && new.isEmpty()){ 51 | this.parent.removeClasses("error") 52 | logger.info("Removing Error class") 53 | } 54 | if(old.isEmpty() && new.isNotEmpty()){ 55 | this.parent.addClasses("error") 56 | logger.info("Adding Error class") 57 | } 58 | } 59 | 60 | block(fc) 61 | } 62 | 63 | return fc 64 | } 65 | 66 | typealias ValidationFunc = (String?)->String? 67 | 68 | interface FormInput { 69 | val actualValue: KVar 70 | val isRequired: Boolean 71 | val label: String? 72 | val errorMessage: KVar 73 | val inputElement: Element 74 | 75 | fun runValidation(): String? 76 | } 77 | 78 | inline fun T.with(fc: FormControl) : T{ 79 | fc.add(this) 80 | return this 81 | } 82 | 83 | fun FormInput.checkInput() : String?{ 84 | errorMessage.value = runValidation() 85 | return errorMessage.value 86 | } 87 | 88 | class BasicFormInput( 89 | override val actualValue: KVar, 90 | override val isRequired:Boolean, 91 | override val label:String?, 92 | private var validator: ValidationFunc?=null, 93 | override val errorMessage: KVar = KVar(null), 94 | private var inputMissingErrorMessage: String? = null) : FormInput{ 95 | 96 | private lateinit var _inputElement: Element 97 | 98 | override val inputElement: Element 99 | get() = _inputElement 100 | 101 | 102 | fun setInputElement(e: Element){ 103 | this._inputElement = e 104 | } 105 | 106 | 107 | fun withInputMissingErrorMessage(message:String) : FormInput{ 108 | inputMissingErrorMessage = message 109 | return this 110 | } 111 | 112 | override fun runValidation(): String? { 113 | if(isRequired && actualValue.value.isBlank()){ 114 | return inputMissingErrorMessage ?: "The Field '${label}' is required" 115 | } 116 | return validator?.invoke(actualValue.value) 117 | } 118 | 119 | fun validate(func: ValidationFunc): FormInput{ 120 | validator = func 121 | return this 122 | } 123 | } 124 | 125 | fun ElementCreator<*>.formInput(label: String?=null, placeholder:String?=null, required:Boolean=false, bindTo: KVar, inputType: InputType = InputType.text): BasicFormInput = absFormInput(label, required, bindTo){ 126 | lateinit var input: InputElement 127 | div(fomantic.ui.input).new() { 128 | input = input(inputType, placeholder = placeholder).apply { value=bindTo } 129 | } 130 | input 131 | } 132 | 133 | fun ElementCreator<*>.checkBoxInput(label:String, bindTo: KVar) { 134 | val bindToStr = KVar(bindTo.toString()) 135 | bindToStr.addListener { oldVal, newVal -> 136 | val currentVal = newVal.toBoolean() 137 | bindTo.value = currentVal 138 | } 139 | div(fomantic.field).new { 140 | render(bindTo){isChecked-> 141 | div(fomantic.ui.checkbox.checked(bindTo.value)) 142 | .apply { 143 | on.click { 144 | bindTo.value = !bindTo.value 145 | } 146 | } 147 | .new { 148 | input(InputType.checkbox,attributes = mapOf("class" to "hidden")).apply { 149 | if(isChecked) { 150 | checked(true) 151 | } 152 | } 153 | label().text(label) 154 | } 155 | } 156 | } 157 | } 158 | 159 | fun ElementCreator<*>.absFormInput(label: String?=null, required:Boolean=false, bindTo: KVar, inputElementFunc: ElementCreator<*>.()-> InputElement) : BasicFormInput{ 160 | val formInput = BasicFormInput(bindTo, required, label) 161 | 162 | div(fomantic.ui.field).new { 163 | 164 | formInput.errorMessage.addListener { old, newError -> 165 | if(newError!=null){ 166 | this.parent.addClasses("error") 167 | } 168 | else{ 169 | this.parent.removeClasses("error") 170 | } 171 | } 172 | 173 | label?.let { 174 | label().text(label) 175 | } 176 | 177 | val input = inputElementFunc() 178 | formInput.setInputElement(input) 179 | } 180 | return formInput 181 | } 182 | 183 | class DropdownValueSelectEvent(val selectedValue: String?, val selectedText: String?) 184 | class DropdownElement { 185 | internal val callbacks: MutableList<(key: String?)->Unit> = mutableListOf() 186 | fun onSelect(callback:(key: String?)->Unit) 187 | { 188 | callbacks.add(callback) 189 | } 190 | } 191 | 192 | fun ElementCreator<*>.dropdownField(label: String, options: Map, currentValue: KVar = KVar(null)) : DropdownElement{ 193 | lateinit var element : DropdownElement 194 | div(fomantic.ui.field).new { 195 | label().text(label) 196 | 197 | element = dropdown(options, currentValue) 198 | } 199 | return element 200 | } 201 | 202 | fun ElementCreator<*>.dropdown(options: Map, currentValue: KVar = KVar(null)):DropdownElement { 203 | 204 | val result = DropdownElement() 205 | 206 | val dropdown = div(fomantic.ui.selection.dropdown) 207 | dropdown.new { 208 | input(type=InputType.hidden, name="dropdown", initialValue = currentValue.value) 209 | i(fomantic.icon.dropdown) 210 | div(fomantic.text.default).text("Select") 211 | div(fomantic.menu).new{ 212 | options.forEach { (key, displayText) -> 213 | div(fomantic.item).apply { this.setAttributeRaw("data-value", key) }.text(displayText) 214 | } 215 | } 216 | } 217 | 218 | val callbackId = abs(random.nextInt()) 219 | browser.executeWithCallback(""" 220 | $('#${dropdown.id}').dropdown({ 221 | action: 'activate', 222 | onChange: function(value, text) { 223 | callbackWs($callbackId,{selectedValue: value, selectedText: text}); 224 | } 225 | }); 226 | """.trimIndent(), callbackId) {inputData-> 227 | val selectedData : DropdownValueSelectEvent = gson.fromJson(inputData.toString()) 228 | if(currentValue != null) { 229 | currentValue.value = selectedData.selectedValue ?: "" 230 | } 231 | result.callbacks.forEach{cb->cb.invoke(selectedData.selectedValue)} 232 | } 233 | 234 | return result 235 | } 236 | 237 | fun ElementCreator<*>.radioInput(label:String?=null, options: Map, required: Boolean=false, isInline:Boolean=false, bindTo: KVar) : BasicFormInput { 238 | val formInput = BasicFormInput(bindTo, required, label) 239 | 240 | render(bindTo){ 241 | div(fomantic.ui.fields.inline(isInline).grouped).new { 242 | formInput.errorMessage.addListener { old, newError -> 243 | if(newError!=null){ 244 | this.parent.addClasses("error") 245 | } 246 | else{ 247 | this.parent.removeClasses("error") 248 | } 249 | } 250 | label?.let { 251 | label().text(label) 252 | } 253 | options.forEach{ (labelName, labelValue) -> 254 | div(fomantic.ui.field).new { 255 | div(fomantic.ui.radio.checkbox.checked(labelValue == bindTo.value)).apply { 256 | on.click { 257 | bindTo.value = labelValue 258 | } 259 | }.new { 260 | input(type = InputType.radio, name = labelValue, attributes = mapOf("class" to "hidden")).apply { 261 | if(labelValue == bindTo.value){ 262 | checked(true) 263 | } 264 | } 265 | label().text(labelName) 266 | } 267 | } 268 | } 269 | } 270 | } 271 | 272 | return formInput 273 | } 274 | -------------------------------------------------------------------------------- /formValidation/src/main.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld 2 | 3 | import kweb.* 4 | import kweb.plugins.fomanticUI.fomantic 5 | import kweb.plugins.fomanticUI.fomanticUIPlugin 6 | import kweb.state.KVar 7 | import kweb.state.render 8 | 9 | fun main(args: Array) { 10 | formDemo() 11 | } 12 | 13 | fun formDemo() { 14 | Kweb(port = 16097, plugins = listOf(fomanticUIPlugin)) { 15 | doc.body.new { 16 | centeredBox { 17 | h1().text("Form Demo") 18 | 19 | val successMessage = KVar(null) 20 | 21 | 22 | val firstName = KVar("") 23 | val agreed = KVar(false) 24 | val bike = KVar("A") 25 | val color = KVar(null) 26 | 27 | formControl { form-> 28 | formInput("First Name", "Jane", true, firstName) 29 | .validate { input-> 30 | when{ 31 | input == null -> "Please enter a name!" 32 | input.length < 3 -> "Please enter a longer name!. It shall have at least three chars." 33 | else -> null 34 | } 35 | } 36 | .with(form) 37 | 38 | checkBoxInput("Please agree to nothing", agreed) 39 | 40 | radioInput("Which bicycle do you ride?", mapOf("Racing Bike" to "RB", "Mountain Bike" to "MTB", "Dutch Bike" to "FIETS", "None" to "X"), false, false, bike) 41 | 42 | dropdownField("Color", mapOf("red" to "Red", "yellow" to "Yellow", "green" to "Green"), color) 43 | 44 | render(form.errors){foundErrors-> 45 | ul().new { 46 | foundErrors.forEach { error-> 47 | li().text(error) 48 | } 49 | } 50 | } 51 | 52 | render(successMessage){msg -> 53 | if(msg != null) { 54 | p().new{ 55 | i(fomantic.icon.check) 56 | span().text(msg) 57 | } 58 | } 59 | } 60 | 61 | 62 | button(fomantic.ui.button).text("Submit").on.click { 63 | if(form.isValid){ 64 | logger.info("Input is valid!") 65 | successMessage.value = "Greetings ${firstName.value}. Will you agree? ${agreed.value}. You selected option ${bike.value}. Selected color: ${color.value}" 66 | } 67 | else { 68 | logger.error("Input is not valid!") 69 | } 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /formValidation/src/ui_utils.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld 2 | 3 | import kweb.* 4 | import kweb.plugins.fomanticUI.FomanticUIClasses 5 | import kweb.plugins.fomanticUI.fomantic 6 | 7 | val FomanticUIClasses.server : FomanticUIClasses 8 | get() { 9 | classes("server") 10 | return this 11 | } 12 | 13 | fun ElementCreator<*>.centeredBox(contentBlock: ElementCreator.()->Unit){ 14 | 15 | this.browser.doc.head.new(){ 16 | element("style", mapOf("type" to "text/css")).innerHTML(""" 17 | body { 18 | background-color: #DADADA; 19 | } 20 | #K1 { 21 | height: 100%; 22 | } 23 | .grid{ 24 | height: 100%; 25 | } 26 | .image { 27 | margin-top: -100px; 28 | } 29 | .column { 30 | max-width: 450px; 31 | } 32 | .footer { 33 | clear: both; 34 | position: relative; 35 | height: 20px; 36 | margin-top: -15px; 37 | width: 100%; 38 | display: table; 39 | text-align: center; 40 | } 41 | .footerContent { 42 | display: table-cell; 43 | vertical-align: middle; 44 | } 45 | """.trimIndent()) 46 | } 47 | 48 | div(fomantic.ui.middle.aligned.center.aligned.grid).new { 49 | 50 | div(fomantic.left.aligned.column).new(){ 51 | div(fomantic.ui.segment).new(){ 52 | contentBlock(this) 53 | } 54 | } 55 | } 56 | 57 | div(attributes = mapOf("class" to "footer")).new{ 58 | div(attributes = mapOf("class" to "footerContent")).new{ 59 | div(fomantic.ui.label).new { 60 | i(fomantic.ui.icon.server) 61 | span().text("Powered by KWeb") 62 | } 63 | } 64 | } 65 | } 66 | 67 | fun FomanticUIClasses.checked(isChecked: Boolean): FomanticUIClasses { 68 | if(isChecked){ 69 | classes("checked") 70 | } 71 | return this 72 | } 73 | 74 | fun FomanticUIClasses.inline(isInline: Boolean):FomanticUIClasses{ 75 | if(isInline){ 76 | classes("inline") 77 | } 78 | return this 79 | } 80 | 81 | val FomanticUIClasses.default : FomanticUIClasses 82 | get() { 83 | classes("default") 84 | return this 85 | } -------------------------------------------------------------------------------- /helloWorld/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .gradle 3 | -------------------------------------------------------------------------------- /helloWorld/README.md: -------------------------------------------------------------------------------- 1 | This folder contains a simple "Hello World" demo. You can start the server 2 | by running `gradlew run`. You can then view the app at http://localhost:16097 -------------------------------------------------------------------------------- /helloWorld/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jetbrains.kotlin.jvm' version '1.4.0' 3 | id 'application' 4 | } 5 | 6 | mainClassName = "kweb.demos.helloWorld.HelloWorldKt" 7 | 8 | sourceSets { 9 | main.kotlin.srcDirs += 'src' 10 | } 11 | 12 | repositories { 13 | jcenter() 14 | maven { url 'https://jitpack.io' } 15 | } 16 | 17 | dependencies { 18 | implementation 'com.github.kwebio:kweb-core:0.7.21' 19 | implementation 'org.slf4j:slf4j-simple:1.7.30' 20 | } -------------------------------------------------------------------------------- /helloWorld/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/helloWorld/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /helloWorld/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /helloWorld/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /helloWorld/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /helloWorld/src/helloWorld.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld 2 | 3 | import kotlinx.coroutines.* 4 | import kotlinx.coroutines.future.await 5 | import kweb.* 6 | import java.lang.Exception 7 | import java.util.concurrent.CompletableFuture 8 | import kotlin.random.Random 9 | 10 | fun main(args: Array) { 11 | helloWorld() 12 | } 13 | 14 | fun helloWorld() { 15 | Kweb(port = 16097) { 16 | doc.body.new { 17 | h1().text("Hello Worlds!") 18 | 19 | GlobalScope.launch { 20 | try { 21 | var x = 0 22 | while (true) { 23 | x += 1 24 | li().text("Hello World $x!") 25 | 26 | 27 | val callbackId = Random.nextInt() 28 | 29 | 30 | withTimeout(2000){ 31 | val waiter = CompletableFuture() 32 | GlobalScope.launch(Dispatchers.IO){ 33 | browser.executeWithCallback("callbackWs($callbackId, {alive: 1});", callbackId) { 34 | waiter.complete(true) 35 | } 36 | } 37 | waiter.await() 38 | } 39 | 40 | delay(500) 41 | } 42 | } 43 | catch (ex:Exception){ 44 | logger.info(ex.message) 45 | } 46 | finally { 47 | logger.info("I'm gone") 48 | } 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /https/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .gradle 3 | -------------------------------------------------------------------------------- /https/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jetbrains.kotlin.jvm' version '1.3.70' 3 | id 'application' 4 | } 5 | 6 | mainClassName = "kweb.demos.https.HttpsAppKt" 7 | 8 | sourceSets { 9 | main.kotlin.srcDirs += 'src' 10 | } 11 | 12 | repositories { 13 | jcenter() 14 | maven { url 'https://jitpack.io' } 15 | } 16 | 17 | dependencies { 18 | implementation 'com.github.kwebio:kweb-core:0.7.21' 19 | implementation 'org.slf4j:slf4j-simple:1.7.30' 20 | } -------------------------------------------------------------------------------- /https/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/https/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /https/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /https/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /https/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /https/src/HttpsApp.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.https 2 | 3 | import kweb.Kweb 4 | import kweb.div 5 | import kweb.h1 6 | import kweb.https.SSLConfig 7 | import kweb.new 8 | import kweb.plugins.fomanticUI.fomantic 9 | import kweb.plugins.fomanticUI.fomanticUIPlugin 10 | 11 | fun main() { 12 | Kweb(9090, plugins = listOf(fomanticUIPlugin), httpsConfig = SSLConfig()) { 13 | doc.body.new { 14 | div(fomantic.ui.main.container).new { 15 | div(fomantic.column).new { 16 | div(fomantic.ui.vertical.segment).new { 17 | div(fomantic.ui.message).new { 18 | h1(fomantic.ui.header.center.aligned).text("Welcome to Https Kweb!") 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /ktorFeature/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .gradle 3 | -------------------------------------------------------------------------------- /ktorFeature/README.md: -------------------------------------------------------------------------------- 1 | This demo shows how kweb can be used as a 2 | [ktor feature](https://ktor.io/servers/features.html). 3 | -------------------------------------------------------------------------------- /ktorFeature/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jetbrains.kotlin.jvm' version '1.4.0' 3 | id 'application' 4 | } 5 | 6 | mainClassName = "kweb.demos.feature.FeatureAppKt" 7 | 8 | sourceSets { 9 | main.kotlin.srcDirs += 'src' 10 | } 11 | 12 | repositories { 13 | jcenter() 14 | maven { url 'https://jitpack.io' } 15 | } 16 | 17 | dependencies { 18 | implementation 'com.github.kwebio:kweb-core:0.8.1' 19 | implementation 'org.slf4j:slf4j-simple:1.7.30' 20 | } -------------------------------------------------------------------------------- /ktorFeature/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/ktorFeature/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ktorFeature/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /ktorFeature/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /ktorFeature/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /ktorFeature/src/FeatureApp.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.feature 2 | 3 | import io.ktor.application.* 4 | import io.ktor.features.Compression 5 | import io.ktor.features.DefaultHeaders 6 | import io.ktor.http.cio.websocket.pingPeriod 7 | import io.ktor.http.cio.websocket.timeout 8 | import io.ktor.response.* 9 | import io.ktor.routing.* 10 | import io.ktor.server.engine.embeddedServer 11 | import io.ktor.server.jetty.Jetty 12 | import io.ktor.websocket.WebSockets 13 | import kweb.* 14 | import kweb.state.KVar 15 | import java.time.Duration 16 | 17 | fun main() { 18 | embeddedServer(Jetty, port = 16097, module = Application::kwebFeature).start() 19 | } 20 | 21 | private fun Application.kwebFeature() { 22 | install(DefaultHeaders) 23 | install(Compression) 24 | install(WebSockets) { 25 | pingPeriod = Duration.ofSeconds(10) 26 | timeout = Duration.ofSeconds(30) 27 | } 28 | 29 | install(Kweb) 30 | 31 | routing { 32 | get("/staticpage") { 33 | call.respondText("This is a static page and it won't be routed via Kweb") 34 | } 35 | } 36 | 37 | // You can also do: 38 | // routing { 39 | // get("/{visitedUrl...}") { 40 | // call.respondKweb(buildPage) 41 | // } 42 | // } 43 | installKwebOnRemainingRoutes { 44 | doc.body.new { 45 | val greeting = url.map { it.removePrefix("/") }.map { "Hello " + if (it.isNotBlank()) it else "World" } 46 | 47 | val next = KVar("") 48 | 49 | h1().text(greeting) 50 | span().text("Where to next?") 51 | input().setValue(next) 52 | button().text("Go!").on.click { 53 | url.value = next.value 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /modalDialog/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .gradle 3 | -------------------------------------------------------------------------------- /modalDialog/README.md: -------------------------------------------------------------------------------- 1 | Description 2 | ----- 3 | 4 | This folder contains a simple demo showing how to use a formatic ui dialog. You can start the server 5 | by running `gradlew run`. You can then view the app at http://localhost:16097 6 | 7 | 8 | Preview 9 | ---- 10 | 11 | ![Preview of Modal](preview.gif) -------------------------------------------------------------------------------- /modalDialog/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jetbrains.kotlin.jvm' version '1.4.0' 3 | id 'application' 4 | } 5 | 6 | mainClassName = "kweb.demos.helloWorld.MainKt" 7 | 8 | sourceSets { 9 | main.kotlin.srcDirs += 'src' 10 | } 11 | 12 | repositories { 13 | jcenter() 14 | maven { url 'https://jitpack.io' } 15 | } 16 | 17 | dependencies { 18 | implementation 'com.github.kwebio:kweb-core:0.7.21' 19 | implementation 'org.slf4j:slf4j-simple:1.7.30' 20 | } 21 | -------------------------------------------------------------------------------- /modalDialog/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /modalDialog/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /modalDialog/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/modalDialog/preview.gif -------------------------------------------------------------------------------- /modalDialog/src/main.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld 2 | 3 | import kweb.* 4 | import kweb.plugins.fomanticUI.fomantic 5 | import kweb.plugins.fomanticUI.fomanticUIPlugin 6 | import kweb.state.KVar 7 | import kweb.state.render 8 | 9 | fun main(args: Array) { 10 | modalDemo() 11 | } 12 | 13 | fun ElementCreator<*>.doSomethingModal(whenDone:(input:String)->Unit) = modal("Enter a number"){modal-> 14 | val userInput = KVar("") 15 | 16 | form(fomantic.ui.form).new { 17 | div(fomantic.ui.field).new { 18 | label().text("Input a number") 19 | input(InputType.text, placeholder = "111").apply { value=userInput } 20 | } 21 | 22 | button(fomantic.ui.button).text("Save").on.click { 23 | whenDone(userInput.value) 24 | modal.close() 25 | } 26 | } 27 | } 28 | 29 | fun modalDemo() { 30 | Kweb(port = 16097, plugins = listOf(fomanticUIPlugin)) { 31 | 32 | val modalResult = KVar("") 33 | 34 | doc.body.new { 35 | div(fomantic.ui.main.container).new { 36 | 37 | div(fomantic.column).new { 38 | div(fomantic.ui.vertical.segment).new { 39 | 40 | h1(fomantic.ui.header).text("Modal demo") 41 | div(fomantic.ui.content).new { 42 | val modal = doSomethingModal { userInput-> 43 | logger.info("User entered ${userInput}") 44 | modalResult.value = userInput 45 | } 46 | 47 | button(fomantic.ui.button).text("Modal 1").on.click { 48 | modal.open() 49 | } 50 | 51 | div(fomantic.ui.divider.hidden) 52 | 53 | render(modalResult){result-> 54 | if(!result.isBlank()) { 55 | p().text("User choice is $result") 56 | } 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /modalDialog/src/modal.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld 2 | 3 | import kweb.DivElement 4 | import kweb.ElementCreator 5 | import kweb.div 6 | import kweb.new 7 | import kweb.plugins.fomanticUI.fomantic 8 | import kweb.state.KVar 9 | import kweb.state.render 10 | import kweb.util.gson 11 | 12 | class ModalViewOptions( 13 | val autofocus: Boolean=true 14 | ) 15 | 16 | class ModalView(val ec: ElementCreator<*>, val id: String = (modalCounter++).toString(), var isOpen: KVar = KVar(false), val autoFocus: Boolean=true) { 17 | companion object { 18 | var modalCounter: Int = 0 19 | } 20 | val options = ModalViewOptions(autofocus=autoFocus) 21 | 22 | 23 | fun close(){ 24 | ec.browser.evaluate(""" 25 | console.log("Hiding Modal " + ${this.id}) 26 | $('#${this.id}').modal(${gson.toJson(options)}).modal('hide'); 27 | """.trimIndent()) 28 | 29 | isOpen.value = false 30 | } 31 | 32 | fun open(){ 33 | isOpen.value = true 34 | ec.browser.evaluate(""" 35 | $('#${this.id}').modal(${gson.toJson(options)}).modal('show',${gson.toJson(options)}); 36 | """.trimIndent()) 37 | } 38 | } 39 | 40 | fun ElementCreator<*>.modal(header: String, autoFocus: Boolean=true, content: ElementCreator.(modal: ModalView) -> Unit) : ModalView { 41 | val mv = ModalView(this, autoFocus = autoFocus) 42 | 43 | val classes = fomantic.ui.modal 44 | render(mv.isOpen){isOpen -> 45 | if(isOpen) { 46 | div(classes.plus("id" to mv.id)).new { 47 | div(fomantic.ui.header).text(header) 48 | div(fomantic.ui.content).new { 49 | content(mv) 50 | } 51 | } 52 | } 53 | } 54 | 55 | return mv 56 | } -------------------------------------------------------------------------------- /sessionAndAuth/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | .gradle 3 | -------------------------------------------------------------------------------- /sessionAndAuth/README.md: -------------------------------------------------------------------------------- 1 | This folder contains a simple authentication demo. You can start the server 2 | by running `gradlew run`. You can then view the app at http://localhost:16097 -------------------------------------------------------------------------------- /sessionAndAuth/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jetbrains.kotlin.jvm' version '1.4.0' 3 | id 'application' 4 | } 5 | 6 | mainClassName = "kweb.demos.helloWorld.MainKt" 7 | 8 | sourceSets { 9 | main.kotlin.srcDirs += 'src' 10 | } 11 | 12 | repositories { 13 | jcenter() 14 | maven { url 'https://jitpack.io' } 15 | } 16 | 17 | dependencies { 18 | implementation 'com.github.kwebio:kweb-core:0.10.3' 19 | implementation 'org.slf4j:slf4j-simple:1.7.30' 20 | } 21 | -------------------------------------------------------------------------------- /sessionAndAuth/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /sessionAndAuth/src/main.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld 2 | 3 | import kweb.* 4 | import kweb.demos.helloWorld.pages.* 5 | import org.slf4j.LoggerFactory 6 | 7 | fun main(args: Array) { 8 | sessionAndAuthDemo() 9 | } 10 | 11 | val logger = LoggerFactory.getLogger("auth") 12 | 13 | 14 | fun sessionAndAuthDemo() { 15 | Kweb(port = 16097) { 16 | doc.body.new { 17 | route { 18 | 19 | path("/login"){ 20 | loginPage() 21 | } 22 | 23 | path("/publicPage"){ 24 | publicPage() 25 | } 26 | 27 | path("/privatePageWhereLoginIsRequried"){ 28 | privatePageWhereLoginIsRequired() 29 | } 30 | 31 | path("/logout"){ 32 | logoutPage() 33 | } 34 | 35 | path("/"){ 36 | indexPage() 37 | } 38 | 39 | } 40 | 41 | 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /sessionAndAuth/src/pages/indexPage.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld.pages 2 | 3 | import kweb.ElementCreator 4 | import kweb.a 5 | import kweb.br 6 | import kweb.h4 7 | 8 | fun ElementCreator<*>.indexPage(){ 9 | h4().text("Welcome. Where do you want to go?") 10 | a(href = "/publicPage").text("Go to Public area") 11 | br() 12 | a(href = "/privatePageWhereLoginIsRequried").text("Go to Private area. Login required") 13 | } -------------------------------------------------------------------------------- /sessionAndAuth/src/pages/loginPage.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld.pages 2 | 3 | import kotlinx.serialization.json.JsonPrimitive 4 | import kweb.* 5 | import kweb.demos.helloWorld.Sessions 6 | import kweb.demos.helloWorld.UserDatabase 7 | import kweb.demos.helloWorld.getOrCreateSessionId 8 | import kweb.demos.helloWorld.navigateTo 9 | import kweb.state.KVar 10 | import org.slf4j.LoggerFactory 11 | 12 | val logger = LoggerFactory.getLogger("loginPage") 13 | 14 | 15 | fun ElementCreator<*>.loginPage(){ 16 | h4().text("Please authenticate") 17 | p().text("If you are unsure, log in using test / test as username and password") 18 | 19 | val username = KVar("") 20 | val password = KVar("") 21 | val error = KVar("") 22 | 23 | 24 | div(attributes = mapOf("style" to JsonPrimitive("color: red;"))).text(error) 25 | 26 | 27 | input(type = InputType.text,placeholder = "user").apply { this.value = username }.on.focusin { error.value = "" } 28 | br() 29 | input(type = InputType.password,placeholder = "password").apply { this.value = password }.on.focusin { error.value = "" } 30 | br() 31 | button { text("Log in") }.on.click { 32 | 33 | logger.info("Logging in as ${username.value} with password ${"*".repeat(password.value.length)}") 34 | 35 | val user = UserDatabase.checkLoginAndReturnUser(username.value, password.value) 36 | if(user != null){ 37 | Sessions.start(this.browser.getOrCreateSessionId()!!, user) 38 | this.browser.navigateTo("/") 39 | } 40 | else { 41 | error.value = "Check username and password" 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /sessionAndAuth/src/pages/loginRequiredPage.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld.pages 2 | 3 | import kweb.ElementCreator 4 | import kweb.a 5 | import kweb.demos.helloWorld.authRequired 6 | import kweb.demos.helloWorld.authenticatedUser 7 | import kweb.h4 8 | import kweb.p 9 | 10 | fun ElementCreator<*>.privatePageWhereLoginIsRequired(){ 11 | authRequired { 12 | h4().text("Member area") 13 | p().text("Hello ${this.browser.authenticatedUser?.username}") 14 | p().text("This area is only available after logging in.") 15 | a(href = "/logout").text("Log out") 16 | } 17 | } -------------------------------------------------------------------------------- /sessionAndAuth/src/pages/logoutPage.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld.pages 2 | 3 | import kotlinx.coroutines.GlobalScope 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | import kweb.ElementCreator 7 | import kweb.demos.helloWorld.clearSession 8 | import kweb.demos.helloWorld.navigateTo 9 | import kweb.h3 10 | import kweb.p 11 | 12 | fun ElementCreator<*>.logoutPage(){ 13 | h3().text("Logout") 14 | p().text("You were logged out. Redirect in 5 sek...") 15 | 16 | browser.clearSession() 17 | 18 | GlobalScope.launch { 19 | delay(5000) 20 | browser.navigateTo("/") 21 | } 22 | } -------------------------------------------------------------------------------- /sessionAndAuth/src/pages/publicPage.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld.pages 2 | 3 | import kweb.ElementCreator 4 | import kweb.h4 5 | 6 | fun ElementCreator<*>.publicPage(){ 7 | h4().text("Everyone can visit this page. No auth requrired...") 8 | } -------------------------------------------------------------------------------- /sessionAndAuth/src/sessions.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.helloWorld 2 | 3 | import kweb.ElementCreator 4 | import kweb.WebBrowser 5 | import java.math.BigInteger 6 | import java.security.SecureRandom 7 | import java.time.Duration 8 | import java.util.* 9 | import javax.crypto.SecretKeyFactory 10 | import javax.crypto.spec.PBEKeySpec 11 | 12 | 13 | 14 | data class User( 15 | val id: Int?, 16 | val username: String, 17 | val passwordHash: String, 18 | val sessionId: String?=null 19 | ) 20 | 21 | fun ElementCreator<*>.authRequired(block: ElementCreator<*>.()->Unit) { 22 | if(this.browser.authenticatedUser!=null){ 23 | block() 24 | } 25 | else { 26 | if(this.browser.url.value != "/") { 27 | this.browser.navigateTo("/login?next=${this.browser.url.value}") 28 | } 29 | else { 30 | this.browser.navigateTo("/login") 31 | } 32 | } 33 | } 34 | 35 | fun WebBrowser.navigateTo(path:String){ 36 | this.callJsFunction("window.location = \"${path}\"") 37 | } 38 | 39 | fun WebBrowser.getOrCreateSessionId() : String? { 40 | val sessionCookie = this.httpRequestInfo.cookies.get("SESSION") 41 | if(sessionCookie == null){ 42 | val sessionId = UUID.randomUUID().toString() 43 | doc.cookie.set("SESSION", sessionId, expires = Duration.ofDays(14)) 44 | logger.info("SessionID ${sessionId}") 45 | return sessionId 46 | } 47 | logger.info("SessionID ${sessionCookie}") 48 | return sessionCookie 49 | } 50 | 51 | val WebBrowser.authenticatedUser : User? 52 | get() = getOrCreateSessionId()?.let{Sessions.get(it)} 53 | 54 | fun WebBrowser.clearSession() { 55 | val sessionId = getOrCreateSessionId() 56 | doc.cookie.set("SESSION", UUID.randomUUID().toString(), expires = Duration.ofDays(14)) 57 | sessionId?.let{Sessions.remove( sessionId)} 58 | 59 | } 60 | 61 | object Sessions { 62 | 63 | fun start(sessionId: String, user: User){ 64 | openSessions[sessionId] = user 65 | UserDatabase.save(user.copy(sessionId = sessionId)) 66 | } 67 | 68 | fun get(sessionId: String) : User? { 69 | if(openSessions[sessionId]!=null) 70 | return openSessions[sessionId] 71 | val user = UserDatabase.findUserBySessionId(sessionId) 72 | 73 | if(user!=null){ 74 | openSessions[sessionId] = user 75 | } 76 | 77 | return user 78 | } 79 | 80 | fun remove(sessionId: String){ 81 | this.openSessions.remove(sessionId) 82 | 83 | UserDatabase.findUserBySessionId(sessionId)?.let { user-> 84 | UserDatabase.save(user.copy(sessionId = null)) 85 | } 86 | 87 | } 88 | private val openSessions = mutableMapOf() 89 | } 90 | 91 | object UserDatabase { 92 | private val users = mutableListOf( 93 | User(2, "frnk", generatePasswordHash("youWillNeverGuessIt\uD83D\uDE08")), 94 | User(1,"sanity", generatePasswordHash("kweb")), 95 | User(3,"june", generatePasswordHash("niceMonth")), 96 | User(4, "test", generatePasswordHash("test")) 97 | ) 98 | 99 | fun findUserBySessionId(sessionId: String):User? = users.find { it.sessionId == sessionId } 100 | fun save(user: User){ 101 | if(user.id == null){ 102 | users.add(user.copy(id = users.size)) 103 | }else { 104 | val idx = users.indexOfFirst { it.id == user.id } 105 | users[idx] = user 106 | } 107 | } 108 | fun checkLoginAndReturnUser(username:String, pass: String): User?{ 109 | val user = users.find { it.username == username } 110 | if(checkPasswordHash(user?.passwordHash, pass)){ 111 | return user 112 | } 113 | return null 114 | } 115 | } 116 | 117 | fun checkPasswordHash(currentPasswordHash:String?, password:String):Boolean{ 118 | currentPasswordHash ?: return false 119 | 120 | val (format, iterations, salt, hash) = currentPasswordHash.split(":") 121 | 122 | val actualHash = generatePasswordHash(password, salt, iterations.toInt()) 123 | 124 | return actualHash == currentPasswordHash 125 | } 126 | 127 | fun String.hexStringToByteArray(): ByteArray { 128 | val len = this.length 129 | val data = ByteArray(len / 2) 130 | var i = 0 131 | while (i < len) { 132 | data[i / 2] = ((Character.digit(this[i], 16) shl 4) 133 | + Character.digit(this[i + 1], 16)).toByte() 134 | i += 2 135 | } 136 | return data 137 | } 138 | 139 | fun toHex(array: ByteArray): String { 140 | val bi = BigInteger(1, array) 141 | val hex = bi.toString(16) 142 | val paddingLength = array.size * 2 - hex.length 143 | return if (paddingLength > 0) { 144 | String.format("%0" + paddingLength + "d", 0) + hex 145 | } else { 146 | hex 147 | } 148 | } 149 | 150 | fun generatePasswordHash(password: String, initialSalt:String? = null, iterations: Int = 1000): String { 151 | val chars = password.toCharArray() 152 | val salt = initialSalt?.hexStringToByteArray() ?: getSalt() 153 | val spec = PBEKeySpec(chars, salt, iterations, 64 * 8) 154 | val skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") 155 | val hash = skf.generateSecret(spec).encoded 156 | return "pbkdf2:"+iterations.toString() + ":" + toHex(salt) + ":" + toHex(hash) 157 | } 158 | 159 | fun getSalt(): ByteArray { 160 | val sr: SecureRandom = SecureRandom.getInstance("SHA1PRNG") 161 | val salt = ByteArray(16) 162 | sr.nextBytes(salt) 163 | return salt 164 | } -------------------------------------------------------------------------------- /todoList/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /data 3 | /build -------------------------------------------------------------------------------- /todoList/README.md: -------------------------------------------------------------------------------- 1 | # Todo List App 2 | 3 | This app showcases many of kweb's features. Use `gradlew run` to start 4 | the server, and then open http://localhost:7659 in your browser. Try 5 | visiting the same list URL in two different browser windows and notice 6 | how they synchronize in real time. 7 | 8 | This project also demonstrates how to use [shoebox](https://github.com/kwebio/shoebox) 9 | for reactive persistence. -------------------------------------------------------------------------------- /todoList/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jetbrains.kotlin.jvm' version '1.3.70' 3 | id 'application' 4 | } 5 | 6 | group 'kweb.demos.todo' 7 | version '0.1' 8 | mainClassName = "kweb.demos.todo.TodoAppKt" 9 | 10 | repositories { 11 | mavenLocal() 12 | jcenter() 13 | maven { url 'https://jitpack.io' } 14 | } 15 | 16 | dependencies { 17 | implementation 'com.github.kwebio:kweb-core:0.7.21' 18 | implementation "ch.qos.logback:logback-classic:1.2.3" 19 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 20 | } 21 | 22 | compileKotlin { 23 | kotlinOptions.jvmTarget = "1.8" 24 | } 25 | compileTestKotlin { 26 | kotlinOptions.jvmTarget = "1.8" 27 | } -------------------------------------------------------------------------------- /todoList/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kwebio/kweb-demos/c4f120b7d0c49924927f1b5ba341740f9027cc56/todoList/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /todoList/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /todoList/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /todoList/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /todoList/src/main/kotlin/ToDoState.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.todo 2 | 3 | import kweb.shoebox.Shoebox 4 | import java.nio.file.Files 5 | import java.nio.file.Path 6 | import java.time.Instant 7 | 8 | /** 9 | * Stores all persistent state for this app in a directory, creating it if necessary 10 | */ 11 | 12 | class ToDoState(dir: Path) { 13 | init { 14 | if (Files.notExists(dir)) { 15 | Files.createDirectory(dir) 16 | } 17 | } 18 | 19 | data class List(val uid: String, val title: String) 20 | 21 | data class Item(val uid: String, val created: Instant, val listUid: String, val text: String) 22 | 23 | val lists = Shoebox(dir.resolve("lists")) 24 | 25 | val items = Shoebox(dir.resolve("items")) 26 | 27 | private val itemsByList = items.view("itemsByList", Item::listUid) 28 | 29 | fun itemsByList(listUid: String) = itemsByList.orderedSet(listUid, compareBy(Item::created)) 30 | } -------------------------------------------------------------------------------- /todoList/src/main/kotlin/TodoApp.kt: -------------------------------------------------------------------------------- 1 | package kweb.demos.todo 2 | 3 | import kotlinx.coroutines.GlobalScope 4 | import kotlinx.coroutines.future.await 5 | import kotlinx.coroutines.launch 6 | import kweb.* 7 | import kweb.plugins.fomanticUI.fomantic 8 | import kweb.plugins.fomanticUI.fomanticUIPlugin 9 | import kweb.state.* 10 | import mu.KotlinLogging 11 | import java.nio.file.Paths 12 | import java.time.Instant 13 | import java.util.* 14 | 15 | fun main() { 16 | TodoApp() 17 | } 18 | 19 | class TodoApp { 20 | 21 | private val logger = KotlinLogging.logger {} 22 | 23 | val state = ToDoState(Paths.get("data")) 24 | val plugins = listOf(fomanticUIPlugin) 25 | val server: Kweb 26 | 27 | init { 28 | 29 | /** Create a Kweb instance, and configure it to use the Fomantic 30 | * UI framework. Build a simple to-do list app listening on 31 | * http://localhost:7659/ 32 | * */ 33 | server = Kweb(port = 7659, debug = true, plugins = plugins) { 34 | doc.head.new { 35 | // Not required, but recommended by HTML spec 36 | meta(name = "Description", content = "A simple To Do list app to demonstrate Kweb") 37 | } 38 | 39 | doc.body.new { 40 | /** Kweb allows you to modularize your code however suits your needs 41 | best. Here I use an extension function defined elsewhere to 42 | draw some util outer page DOM elements */ 43 | pageBorderAndTitle("To do List") { 44 | div(fomantic.content).new { 45 | 46 | route { 47 | 48 | path("/") { 49 | val newListId = createNewList() 50 | /** 51 | * This will cause the page to switch to the newly created list automatically, and 52 | * without a page refresh. 53 | */ 54 | url.value = "/lists/$newListId" 55 | } 56 | 57 | path("/lists/{id}") { params -> 58 | doc.head.new { 59 | title().text("To Do List #${params.getValue("id").value}") 60 | } 61 | render(params.getValue("id")) { listId -> 62 | logger.info("Rendering list id $listId") 63 | 64 | try { 65 | /** Here I use the same render mechanism to tie DOM 66 | state to persistent state stored in Shoebox, a simple but powerful 67 | key-value store with observer pattern support. */ 68 | val list: KVar = toVar(state.lists, listId) 69 | 70 | renderList(list) 71 | } catch (e: NoSuchElementException) { 72 | throw NotFoundException("Can't find list with id $listId") 73 | } 74 | } 75 | } 76 | 77 | /* 78 | * It's not necessary, but we can also define a custom 404 handler: 79 | */ 80 | notFound { 81 | div(fomantic.ui.negative.message).new { 82 | div(fomantic.header).text("Not Found :(") 83 | p().text(url.map { "Unable to find path $it" }) 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | } 92 | 93 | private fun ElementCreator<*>.pageBorderAndTitle(title: String, content: ElementCreator.() -> Unit) { 94 | div(fomantic.ui.main.container).new { 95 | div(fomantic.column).new { 96 | div(fomantic.ui.vertical.segment).new { 97 | div(fomantic.ui.message).new { 98 | p().innerHTML( 99 | """ 100 | A simple demo of Kweb, add and delete items from a 101 | to do list. 102 |

103 | Try visiting this URL in another browser window and make some changes. 104 |

105 | You may find the source code for this app 106 | here. 107 | """ 108 | .trimIndent() 109 | ) 110 | } 111 | } 112 | 113 | div(fomantic.ui.vertical.segment).new { 114 | h1(fomantic.ui.dividing.header).text(title) 115 | content(this) 116 | } 117 | } 118 | } 119 | } 120 | 121 | private fun createNewList(): String { 122 | val newListId = generateNewUid() 123 | state.lists[newListId] = ToDoState.List(newListId, "") 124 | return newListId 125 | } 126 | 127 | private fun ElementCreator<*>.renderList(list: KVar) { 128 | h3().text(list.property(ToDoState.List::title)) 129 | div(fomantic.ui.middle.aligned.divided.list).new { 130 | renderEach(state.itemsByList(list.value.uid)) { item -> 131 | div(fomantic.item).new { 132 | div(fomantic.right.floated.content).new { 133 | renderRemoveButton(item) 134 | } 135 | div(fomantic.content).text(item.map(ToDoState.Item::text)) 136 | } 137 | } 138 | } 139 | div(fomantic.ui.action.input).new { 140 | val input = input(InputType.text, placeholder = "Add Item") 141 | input.on.keypress { ke -> 142 | if (ke.code == "Enter") { 143 | handleAddItem(input, list) 144 | } 145 | } 146 | button(fomantic.ui.button).text("Add").apply { 147 | on.click { 148 | handleAddItem(input, list) 149 | } 150 | } 151 | } 152 | } 153 | 154 | private fun handleAddItem(input: InputElement, list: KVar) { 155 | GlobalScope.launch { 156 | val newItemText = input.getValue().await() 157 | input.setValue("") 158 | val newItem = ToDoState.Item(generateNewUid(), Instant.now(), list.value.uid, newItemText) 159 | state.items[newItem.uid] = newItem 160 | } 161 | } 162 | 163 | private fun ElementCreator.renderRemoveButton(item: KVar) { 164 | val button = button(fomantic.mini.ui.icon.button) 165 | button.new { 166 | i(fomantic.trash.icon) 167 | } 168 | button.on.click { 169 | state.items.remove(item.value.uid) 170 | } 171 | } 172 | 173 | private fun generateNewUid() = random.nextInt(100_000_000).toString(16) 174 | } -------------------------------------------------------------------------------- /todoList/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | --------------------------------------------------------------------------------