├── LICENSE ├── README.md ├── app ├── default │ ├── .gitignore │ ├── .kobweb-template.yaml │ ├── README.md │ ├── gradle.properties │ ├── gradle │ │ ├── libs.versions.toml │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts.ftl │ └── site │ │ ├── .gitignore │ │ ├── .kobweb │ │ └── conf.yaml.ftl │ │ ├── build.gradle.kts.ftl │ │ ├── resources │ │ ├── markdown │ │ │ └── About.md │ │ └── public │ │ │ ├── favicon.ico │ │ │ └── kobweb-logo.png │ │ └── src │ │ └── site │ │ ├── AppEntry.kt.ftl │ │ ├── AppStyles.kt.ftl │ │ ├── SiteTheme.kt.ftl │ │ ├── components │ │ ├── layouts │ │ │ ├── MarkdownLayout.kt.ftl │ │ │ └── PageLayout.kt.ftl │ │ ├── sections │ │ │ ├── Footer.kt.ftl │ │ │ └── NavHeader.kt.ftl │ │ └── widgets │ │ │ └── IconButton.kt.ftl │ │ └── pages │ │ └── Index.kt.ftl └── empty │ ├── .gitignore │ ├── .kobweb-template.yaml │ ├── README.md │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts.ftl │ ├── site │ ├── .gitignore │ ├── .kobweb │ │ └── conf.yaml.ftl │ ├── build.gradle.kts.ftl │ ├── resources │ │ ├── README.md │ │ └── public │ │ │ └── favicon.ico │ └── src │ │ ├── api │ │ └── README.md │ │ └── site │ │ ├── AppEntry.kt.ftl │ │ ├── components │ │ └── README.md │ │ └── pages │ │ ├── Index.kt.ftl │ │ └── README.md │ └── worker │ ├── build.gradle.kts.ftl │ └── src │ └── worker │ └── EchoWorkerFactory.kt.ftl ├── examples ├── chat │ ├── .gitignore │ ├── .kobweb-template.yaml │ ├── README.md │ ├── auth │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── commonMain │ │ │ └── kotlin │ │ │ │ └── chat │ │ │ │ └── auth │ │ │ │ └── model │ │ │ │ └── auth │ │ │ │ ├── Account.kt │ │ │ │ ├── CreateAccountResponse.kt │ │ │ │ └── LoginResponse.kt │ │ │ ├── jsMain │ │ │ └── kotlin │ │ │ │ └── chat │ │ │ │ └── auth │ │ │ │ ├── components │ │ │ │ └── sections │ │ │ │ │ └── LoggedOut.kt │ │ │ │ ├── model │ │ │ │ └── auth │ │ │ │ │ └── LoginState.kt │ │ │ │ └── pages │ │ │ │ └── account │ │ │ │ ├── Create.kt │ │ │ │ └── Login.kt │ │ │ └── jvmMain │ │ │ └── kotlin │ │ │ └── chat │ │ │ └── auth │ │ │ ├── api │ │ │ └── account │ │ │ │ ├── Create.kt │ │ │ │ └── Login.kt │ │ │ └── model │ │ │ └── Accounts.kt │ ├── build.gradle.kts │ ├── chat │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── commonMain │ │ │ └── kotlin │ │ │ │ └── chat │ │ │ │ └── chat │ │ │ │ └── model │ │ │ │ └── Message.kt │ │ │ ├── jsMain │ │ │ └── kotlin │ │ │ │ └── chat │ │ │ │ └── chat │ │ │ │ └── pages │ │ │ │ └── Chat.kt │ │ │ └── jvmMain │ │ │ └── kotlin │ │ │ └── chat │ │ │ └── chat │ │ │ └── api │ │ │ └── Chat.kt │ ├── core │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── jsMain │ │ │ ├── kotlin │ │ │ └── chat │ │ │ │ └── core │ │ │ │ ├── Globals.kt │ │ │ │ ├── components │ │ │ │ ├── layouts │ │ │ │ │ ├── MarkdownLayout.kt │ │ │ │ │ └── PageLayout.kt │ │ │ │ ├── sections │ │ │ │ │ ├── CenteredColumnContent.kt │ │ │ │ │ ├── LoadingMessage.kt │ │ │ │ │ └── NavHeader.kt │ │ │ │ └── widgets │ │ │ │ │ ├── TextButton.kt │ │ │ │ │ └── TitledTextInput.kt │ │ │ │ └── styles │ │ │ │ └── Styles.kt │ │ │ └── resources │ │ │ └── markdown │ │ │ └── About.md │ ├── gradle.properties │ ├── gradle │ │ ├── libs.versions.toml │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── site │ │ ├── .gitignore │ │ ├── .kobweb │ │ └── conf.yaml │ │ ├── build.gradle.kts │ │ └── src │ │ └── jsMain │ │ ├── kotlin │ │ └── chat │ │ │ └── site │ │ │ ├── AppEntry.kt │ │ │ └── pages │ │ │ └── Index.kt │ │ └── resources │ │ └── public │ │ └── favicon.ico ├── clock │ ├── .gitignore │ ├── .kobweb-template.yaml │ ├── README.md │ ├── gradle.properties │ ├── gradle │ │ ├── libs.versions.toml │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── site │ │ ├── .gitignore │ │ ├── .kobweb │ │ └── conf.yaml │ │ ├── build.gradle.kts │ │ └── src │ │ └── jsMain │ │ ├── kotlin │ │ └── clock │ │ │ ├── AppEntry.kt │ │ │ ├── components │ │ │ └── layouts │ │ │ │ └── PageLayout.kt │ │ │ └── pages │ │ │ └── Index.kt │ │ └── resources │ │ └── public │ │ └── favicon.ico ├── imageprocessor │ ├── .gitignore │ ├── .idea │ │ ├── .gitignore │ │ ├── misc.xml │ │ └── vcs.xml │ ├── .kobweb-template.yaml │ ├── README.md │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ │ ├── libs.versions.toml │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ ├── site │ │ ├── .gitignore │ │ ├── .kobweb │ │ │ └── conf.yaml │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── jsMain │ │ │ ├── kotlin │ │ │ └── imageprocessor │ │ │ │ └── site │ │ │ │ ├── AppEntry.kt │ │ │ │ ├── components │ │ │ │ └── widgets │ │ │ │ │ ├── Modal.kt │ │ │ │ │ └── Spinner.kt │ │ │ │ └── pages │ │ │ │ └── Index.kt │ │ │ └── resources │ │ │ └── public │ │ │ ├── default.png │ │ │ └── favicon.ico │ ├── util │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── jsMain │ │ │ └── kotlin │ │ │ └── imageprocessor │ │ │ └── util │ │ │ ├── Image.kt │ │ │ ├── ImageProcessor.kt │ │ │ └── processors │ │ │ ├── BlurProcessor.kt │ │ │ ├── FlipProcessors.kt │ │ │ ├── GrayscaleProcessor.kt │ │ │ └── InvertProcessor.kt │ └── worker │ │ ├── build.gradle.kts │ │ └── src │ │ └── jsMain │ │ └── kotlin │ │ └── imageprocessor │ │ └── worker │ │ └── ImageProcessorWorkerFactory.kt ├── jb │ └── counter │ │ ├── .gitignore │ │ ├── .kobweb-template.yaml │ │ ├── README.md │ │ ├── gradle.properties │ │ ├── gradle │ │ ├── libs.versions.toml │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle.kts │ │ └── site │ │ ├── .gitignore │ │ ├── .kobweb │ │ └── conf.yaml │ │ ├── build.gradle.kts │ │ └── src │ │ └── jsMain │ │ ├── kotlin │ │ └── counter │ │ │ ├── AppEntry.kt │ │ │ └── pages │ │ │ └── Index.kt │ │ └── resources │ │ └── public │ │ └── favicon.ico ├── opengl │ ├── .gitignore │ ├── .kobweb-template.yaml │ ├── README.md │ ├── gradle.properties │ ├── gradle │ │ ├── libs.versions.toml │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── site │ │ ├── .gitignore │ │ ├── .kobweb │ │ └── conf.yaml │ │ ├── build.gradle.kts │ │ └── src │ │ └── jsMain │ │ ├── kotlin │ │ └── opengl │ │ │ ├── AppEntry.kt │ │ │ ├── bindings │ │ │ └── glmatrix.kt │ │ │ ├── components │ │ │ └── layouts │ │ │ │ └── PageLayout.kt │ │ │ └── pages │ │ │ └── Index.kt │ │ └── resources │ │ └── public │ │ ├── favicon.ico │ │ └── images │ │ └── varabyte-face.png └── todo │ ├── .gitignore │ ├── .kobweb-template.yaml │ ├── README.md │ ├── gradle.properties │ ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── site │ ├── .gitignore │ ├── .kobweb │ └── conf.yaml │ ├── build.gradle.kts │ └── src │ ├── commonMain │ └── kotlin │ │ └── todo │ │ └── model │ │ └── TodoItem.kt │ ├── jsMain │ ├── kotlin │ │ └── todo │ │ │ ├── AppEntry.kt │ │ │ ├── components │ │ │ └── widgets │ │ │ │ ├── LoadingSpinner.kt │ │ │ │ ├── TodoCard.kt │ │ │ │ ├── TodoForm.kt │ │ │ │ └── TodoStyles.kt │ │ │ └── pages │ │ │ └── Index.kt │ └── resources │ │ └── public │ │ ├── favicon.ico │ │ └── loader.gif │ └── jvmMain │ └── kotlin │ └── todo │ ├── api │ ├── Add.kt │ ├── Id.kt │ ├── List.kt │ └── Remove.kt │ └── model │ └── TodoStore.kt └── library ├── .gitignore ├── .kobweb-template.yaml ├── README.md ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts.ftl └── sitelib ├── build.gradle.kts.ftl ├── resources └── README.md └── src ├── api └── README.md └── sitelib ├── components └── README.md └── pages └── README.md /README.md: -------------------------------------------------------------------------------- 1 | # kobweb-templates 2 | 3 | Templates which can be used by the Kobweb framework. -------------------------------------------------------------------------------- /app/default/.gitignore: -------------------------------------------------------------------------------- 1 | # General ignores 2 | .DS_Store 3 | build 4 | out 5 | kotlin-js-store 6 | 7 | # IntelliJ ignores 8 | *.iml 9 | /*.ipr 10 | 11 | /.idea/caches 12 | /.idea/libraries 13 | /.idea/modules.xml 14 | /.idea/workspace.xml 15 | /.idea/gradle.xml 16 | /.idea/navEditor.xml 17 | /.idea/assetWizardSettings.xml 18 | /.idea/artifacts 19 | /.idea/compiler.xml 20 | /.idea/jarRepositories.xml 21 | /.idea/*.iml 22 | /.idea/modules 23 | /.idea/libraries-with-intellij-classes.xml 24 | 25 | # Gradle ignores 26 | .gradle 27 | 28 | # Kotlin ignores 29 | .kotlin 30 | -------------------------------------------------------------------------------- /app/default/.kobweb-template.yaml: -------------------------------------------------------------------------------- 1 | metadata: 2 | name: "app" 3 | description: "A template for a minimal web app that demonstrates the basic features of Kobweb" 4 | shouldHighlight: true 5 | 6 | instructions: 7 | # Note: Kobweb provides a "projectFolder" variable for us already 8 | 9 | - ! 10 | name: "projectTitle" 11 | prompt: "What is the user-visible display title for your project?" 12 | default: "${fileToTitle(projectFolder)}" 13 | validation: "isNotEmpty" 14 | - ! 15 | name: "projectName" 16 | value: "${fileToPackage(projectFolder)}" 17 | - ! 18 | message: "Note: The group ID should uniquely identify your project and organization." 19 | - ! 20 | message: "'io.github.(username).(projectname)' can work for a hobby project." 21 | - ! 22 | name: "groupId" 23 | prompt: "What is the group ID for your project?" 24 | default: "org.example.${projectName}" 25 | validation: "isPackage" 26 | # Overwrite projectName, since the user's group ID should be a more accurate indication 27 | # of what the user wants their project name to be than the initial folder. 28 | # Note: the "ensure_starts_with" is kind of a hack, to prevent a crash if someone didn't 29 | # put ANY .'s in their group. 30 | - ! 31 | name: "projectName" 32 | value: "${groupId?ensure_starts_with(\".\")?keep_after_last(\".\")}" 33 | # Copy "groupId" as "package" -- it reads better in some cases. 34 | - ! 35 | name: "package" 36 | value: "${groupId}" 37 | 38 | - ! 39 | 40 | - ! 41 | name: "packagePath" 42 | value: "${packageToPath(package)}" 43 | - ! 44 | from: "site/src/site/*" 45 | to: "site/src/jsMain/kotlin/${packagePath}" 46 | description: "Copying frontend code" 47 | - ! 48 | from: "site/src/api/*" 49 | to: "site/src/jvmMain/kotlin/${packagePath}/api" 50 | description: "Copying backend code" 51 | - ! 52 | from: "site/resources/*" 53 | to: "site/src/jsMain/resources" 54 | description: "Copying resources" 55 | -------------------------------------------------------------------------------- /app/default/README.md: -------------------------------------------------------------------------------- 1 | This is a [Kobweb](https://github.com/varabyte/kobweb) project bootstrapped with the `app` template. 2 | 3 | ## Getting Started 4 | 5 | First, run the development server by typing the following command in a terminal under the `site` folder: 6 | 7 | ```bash 8 | $ cd site 9 | $ kobweb run 10 | ``` 11 | 12 | Open [http://localhost:8080](http://localhost:8080) with your browser to see the result. 13 | 14 | You can use any editor you want for the project, but we recommend using **IntelliJ IDEA Community Edition** downloaded 15 | using the [Toolbox App](https://www.jetbrains.com/toolbox-app/). 16 | 17 | Press `Q` in the terminal to gracefully stop the server. 18 | 19 | ## Navigating the Project 20 | 21 | This simple project has a couple of example files you can learn from. The following list is not exhaustive but should 22 | help you get started finding your way around this relatively small example project: 23 | 24 | * `AppEntry.kt`
25 | This is the entry-point composable called for ALL pages. Note that the method is annotated with `@App` which is how 26 | Kobweb discovers it (the file name and method name don't matter). You will not have to call this composable anywhere 27 | in your site, as this is handled automatically by the framework. 28 | * `SiteTheme.kt`
29 | An example of one approach to extend the theme provided by Silk with your own site-specific colors. 30 | * `components/layouts/PageLayout.kt`
31 | An example layout which, unlike `AppEntry`, won't get called automatically. 32 | Instead, this is a recommended way to organize your high level, shared, layout composables. It is expected that most 33 | pages on your site will share the same layout, but you can create others if different pages have different 34 | requirements. You can find this layout referenced in `pages/Index.kt`. 35 | * `components/layouts/MarkdownLayout.kt`
36 | Additional layout configuration useful for pages generated by markdown. This layout builds on top of `PageLayout` 37 | to extend it, which can be a useful pattern in your own site. You can find this layout is referenced in 38 | `markdown/About.md`. 39 | * `components/sections/NavHeader.kt`
40 | An example of a re-usable composable section. In this case, users will feel that the nav header lives across all 41 | pages, but actually, it's a section shared by them and re-rendered separately per page. 42 | * `pages/Index.kt`
43 | The top level page, which will get rendered if the user visits `(yoursite.com)/` (the name 44 | `index` means it will be a special page that gets visited by default when no explicit page is specified). Note that 45 | the composable is annotated with `@Page` which is how `Kobweb` discovers it. 46 | * `resources/markdown/About.md`
47 | A markdown file which gets converted into a page for you automatically at compile 48 | time. This page will get rendered if the user visits `(yoursite.com)/about` If you are writing a blog, it can be 49 | very convenient to write many of your posts using markdown instead of Kotlin code. 50 | 51 | ### Live Reload 52 | 53 | While Kobweb is running, feel free to modify the code! When you make any changes, Kobweb will notice this 54 | automatically, and the site will indicate the status of the build and automatically reload when ready. 55 | 56 | ## Exporting the Project 57 | 58 | When you are ready to ship, you should shutdown the development server and then export the project using: 59 | 60 | ```bash 61 | $ kobweb export --layout static 62 | ``` 63 | 64 | When finished, you can run a Kobweb server in production mode to test it. 65 | 66 | ```bash 67 | $ kobweb run --env prod --layout static 68 | ``` 69 | 70 | The above export generates a layout which is compatible with any static hosting provider of your choice, such as 71 | GitHub Pages, Netlify, Firebase, etc. There is also a more powerful export option to create a fullstack server, 72 | but as the additional power provided by that approach is not needed by most users and comes with more expensive 73 | hosting costs, it is not demonstrated in this project. 74 | 75 | You can read more about static layouts here: https://bitspittle.dev/blog/2022/staticdeploy 76 | 77 | You can read more about fullstack layouts here: https://bitspittle.dev/blog/2023/clouddeploy 78 | -------------------------------------------------------------------------------- /app/default/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.caching=true 3 | org.gradle.configuration-cache=true -------------------------------------------------------------------------------- /app/default/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | jetbrains-compose = "1.8.0" 3 | kobweb = "0.23.3" 4 | kotlin = "2.2.20" 5 | 6 | [libraries] 7 | compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "jetbrains-compose" } 8 | compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrains-compose" } 9 | kobweb-api = { module = "com.varabyte.kobweb:kobweb-api", version.ref = "kobweb" } 10 | kobweb-core = { module = "com.varabyte.kobweb:kobweb-core ", version.ref = "kobweb" } 11 | kobweb-silk = { module = "com.varabyte.kobweb:kobweb-silk", version.ref = "kobweb" } 12 | kobweb-worker = { module = "com.varabyte.kobweb:kobweb-worker", version.ref = "kobweb" } 13 | kobwebx-markdown = { module = "com.varabyte.kobwebx:kobwebx-markdown", version.ref = "kobweb" } 14 | kobwebx-serialization-kotlinx = { module = "com.varabyte.kobwebx:kobwebx-serialization-kotlinx", version.ref = "kobweb" } 15 | silk-foundation = { module = "com.varabyte.kobweb:silk-foundation", version.ref = "kobweb" } 16 | silk-icons-fa = { module = "com.varabyte.kobwebx:silk-icons-fa", version.ref = "kobweb" } 17 | silk-icons-mdi = { module = "com.varabyte.kobwebx:silk-icons-mdi", version.ref = "kobweb" } 18 | 19 | [plugins] 20 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 21 | kobweb-application = { id = "com.varabyte.kobweb.application", version.ref = "kobweb" } 22 | kobweb-library = { id = "com.varabyte.kobweb.library", version.ref = "kobweb" } 23 | kobweb-worker = { id = "com.varabyte.kobweb.worker", version.ref = "kobweb" } 24 | kobwebx-markdown = { id = "com.varabyte.kobwebx.markdown", version.ref = "kobweb" } 25 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 26 | -------------------------------------------------------------------------------- /app/default/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/app/default/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/default/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /app/default/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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /app/default/settings.gradle.kts.ftl: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | dependencyResolutionManagement { 8 | repositories { 9 | mavenCentral() 10 | google() 11 | } 12 | } 13 | 14 | // The following block registers dependencies to enable Kobweb snapshot support. It is safe to delete or comment out 15 | // this block if you never plan to use them. 16 | gradle.settingsEvaluated { 17 | fun RepositoryHandler.kobwebSnapshots() { 18 | maven("https://central.sonatype.com/repository/maven-snapshots/") { 19 | mavenContent { 20 | includeGroupByRegex("com\\.varabyte\\.kobweb.*") 21 | snapshotsOnly() 22 | } 23 | } 24 | } 25 | 26 | pluginManagement.repositories { kobwebSnapshots() } 27 | dependencyResolutionManagement.repositories { kobwebSnapshots() } 28 | } 29 | 30 | rootProject.name = "${projectName}" 31 | 32 | include(":site") 33 | -------------------------------------------------------------------------------- /app/default/site/.gitignore: -------------------------------------------------------------------------------- 1 | # Kobweb ignores 2 | .kobweb/* 3 | !.kobweb/conf.yaml 4 | -------------------------------------------------------------------------------- /app/default/site/.kobweb/conf.yaml.ftl: -------------------------------------------------------------------------------- 1 | site: 2 | title: "${projectTitle}" 3 | 4 | server: 5 | files: 6 | dev: 7 | contentRoot: "build/processedResources/js/main/public" 8 | script: "build/kotlin-webpack/js/developmentExecutable/${projectName}.js" 9 | api: "build/libs/${projectName}.jar" 10 | prod: 11 | script: "build/kotlin-webpack/js/productionExecutable/${projectName}.js" 12 | siteRoot: ".kobweb/site" 13 | 14 | port: 8080 15 | -------------------------------------------------------------------------------- /app/default/site/build.gradle.kts.ftl: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.application.util.configAsKobwebApplication 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.compose.compiler) 6 | alias(libs.plugins.kobweb.application) 7 | alias(libs.plugins.kobwebx.markdown) 8 | } 9 | 10 | group = "${groupId}" 11 | version = "1.0-SNAPSHOT" 12 | 13 | kobweb { 14 | app { 15 | index { 16 | description.set("Powered by Kobweb") 17 | } 18 | } 19 | } 20 | 21 | kotlin { 22 | // This example is frontend only. However, for a fullstack app, you can uncomment the includeServer parameter 23 | // and the `jvmMain` source set below. 24 | configAsKobwebApplication("${projectName}" /*, includeServer = true*/) 25 | 26 | sourceSets { 27 | // commonMain.dependencies { 28 | // // Add shared dependencies between JS and JVM here if building a fullstack app 29 | // } 30 | 31 | jsMain.dependencies { 32 | implementation(libs.compose.runtime) 33 | implementation(libs.compose.html.core) 34 | implementation(libs.kobweb.core) 35 | implementation(libs.kobweb.silk) 36 | // This default template uses built-in SVG icons, but what's available is limited. 37 | // Uncomment the following if you want access to a large set of font-awesome icons: 38 | // implementation(libs.silk.icons.fa) 39 | implementation(libs.kobwebx.markdown) 40 | } 41 | 42 | // Uncomment the following if you pass `includeServer = true` into the `configAsKobwebApplication` call. 43 | // jvmMain.dependencies { 44 | // compileOnly(libs.kobweb.api) // Provided by Kobweb backend at runtime 45 | // } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/default/site/resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/app/default/site/resources/public/favicon.ico -------------------------------------------------------------------------------- /app/default/site/resources/public/kobweb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/app/default/site/resources/public/kobweb-logo.png -------------------------------------------------------------------------------- /app/default/site/src/site/AppEntry.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${package} 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.LaunchedEffect 5 | import com.varabyte.kobweb.compose.css.ScrollBehavior 6 | import com.varabyte.kobweb.compose.ui.Modifier 7 | import com.varabyte.kobweb.compose.ui.modifiers.minHeight 8 | import com.varabyte.kobweb.compose.ui.modifiers.scrollBehavior 9 | import com.varabyte.kobweb.core.App 10 | import com.varabyte.kobweb.silk.SilkApp 11 | import com.varabyte.kobweb.silk.components.layout.Surface 12 | import com.varabyte.kobweb.silk.init.InitSilk 13 | import com.varabyte.kobweb.silk.init.InitSilkContext 14 | import com.varabyte.kobweb.silk.init.registerStyleBase 15 | import com.varabyte.kobweb.silk.style.common.SmoothColorStyle 16 | import com.varabyte.kobweb.silk.style.toModifier 17 | import com.varabyte.kobweb.silk.theme.colors.ColorMode 18 | import com.varabyte.kobweb.silk.theme.colors.loadFromLocalStorage 19 | import com.varabyte.kobweb.silk.theme.colors.saveToLocalStorage 20 | import com.varabyte.kobweb.silk.theme.colors.systemPreference 21 | import org.jetbrains.compose.web.css.vh 22 | 23 | private const val COLOR_MODE_KEY = "${projectName}:colorMode" 24 | 25 | @InitSilk 26 | fun initColorMode(ctx: InitSilkContext) { 27 | ctx.config.initialColorMode = ColorMode.loadFromLocalStorage(COLOR_MODE_KEY) ?: ColorMode.systemPreference 28 | } 29 | 30 | @InitSilk 31 | fun initStyles(ctx: InitSilkContext) { 32 | ctx.stylesheet.apply { 33 | registerStyleBase("body") { Modifier.scrollBehavior(ScrollBehavior.Smooth) } 34 | } 35 | } 36 | 37 | @App 38 | @Composable 39 | fun AppEntry(content: @Composable () -> Unit) { 40 | SilkApp { 41 | val colorMode = ColorMode.current 42 | LaunchedEffect(colorMode) { 43 | colorMode.saveToLocalStorage(COLOR_MODE_KEY) 44 | } 45 | Surface(SmoothColorStyle.toModifier().minHeight(100.vh)) { 46 | content() 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/default/site/src/site/AppStyles.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${package} 2 | 3 | import com.varabyte.kobweb.compose.css.ScrollBehavior 4 | import com.varabyte.kobweb.compose.css.TextAlign 5 | import com.varabyte.kobweb.compose.ui.Modifier 6 | import com.varabyte.kobweb.compose.ui.graphics.Colors 7 | import com.varabyte.kobweb.compose.ui.modifiers.* 8 | import com.varabyte.kobweb.silk.components.forms.ButtonStyle 9 | import com.varabyte.kobweb.silk.components.forms.ButtonVars 10 | import com.varabyte.kobweb.silk.components.layout.HorizontalDividerStyle 11 | import com.varabyte.kobweb.silk.init.InitSilk 12 | import com.varabyte.kobweb.silk.init.InitSilkContext 13 | import com.varabyte.kobweb.silk.init.registerStyleBase 14 | import com.varabyte.kobweb.silk.style.CssStyle 15 | import com.varabyte.kobweb.silk.style.addVariantBase 16 | import com.varabyte.kobweb.silk.style.base 17 | import com.varabyte.kobweb.silk.theme.colors.palette.color 18 | import com.varabyte.kobweb.silk.theme.colors.palette.toPalette 19 | import com.varabyte.kobweb.silk.theme.modifyStyleBase 20 | import org.jetbrains.compose.web.css.* 21 | 22 | @InitSilk 23 | fun initSiteStyles(ctx: InitSilkContext) { 24 | // This site does not need scrolling itself, but this is a good demonstration for how you might enable this in your 25 | // own site. Note that we only enable smooth scrolling unless the user has requested reduced motion, which is 26 | // considered a best practice. 27 | ctx.stylesheet.registerStyle("html") { 28 | cssRule(CSSMediaQuery.MediaFeature("prefers-reduced-motion", StylePropertyValue("no-preference"))) { 29 | Modifier.scrollBehavior(ScrollBehavior.Smooth) 30 | } 31 | } 32 | 33 | ctx.stylesheet.registerStyleBase("body") { 34 | Modifier 35 | .fontFamily( 36 | "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", 37 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "sans-serif" 38 | ) 39 | .fontSize(18.px) 40 | .lineHeight(1.5) 41 | } 42 | 43 | // Silk dividers only extend 90% by default; we want full width dividers in our site 44 | ctx.theme.modifyStyleBase(HorizontalDividerStyle) { 45 | Modifier.fillMaxWidth() 46 | } 47 | } 48 | 49 | val HeadlineTextStyle = CssStyle.base { 50 | Modifier 51 | .fontSize(3.cssRem) 52 | .textAlign(TextAlign.Start) 53 | .lineHeight(1.2) //1.5x doesn't look as good on very large text 54 | } 55 | 56 | val SubheadlineTextStyle = CssStyle.base { 57 | Modifier 58 | .fontSize(1.cssRem) 59 | .textAlign(TextAlign.Start) 60 | .color(colorMode.toPalette().color.toRgb().copyf(alpha = 0.8f)) 61 | } 62 | 63 | val CircleButtonVariant = ButtonStyle.addVariantBase { 64 | Modifier.padding(0.px).borderRadius(50.percent) 65 | } 66 | 67 | val UncoloredButtonVariant = ButtonStyle.addVariantBase { 68 | Modifier.setVariable(ButtonVars.BackgroundDefaultColor, Colors.Transparent) 69 | } 70 | -------------------------------------------------------------------------------- /app/default/site/src/site/SiteTheme.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${package} 2 | 3 | import com.varabyte.kobweb.compose.ui.graphics.Color 4 | import com.varabyte.kobweb.compose.ui.graphics.Colors 5 | import com.varabyte.kobweb.silk.init.InitSilk 6 | import com.varabyte.kobweb.silk.init.InitSilkContext 7 | import com.varabyte.kobweb.silk.theme.colors.ColorMode 8 | import com.varabyte.kobweb.silk.theme.colors.palette.background 9 | import com.varabyte.kobweb.silk.theme.colors.palette.color 10 | 11 | /** 12 | * @property nearBackground A useful color to apply to a container that should differentiate itself from the background 13 | * but just a little. 14 | */ 15 | class SitePalette( 16 | val nearBackground: Color, 17 | val cobweb: Color, 18 | val brand: Brand, 19 | ) { 20 | class Brand( 21 | val primary: Color = Color.rgb(0x3C83EF), 22 | val accent: Color = Color.rgb(0xF3DB5B), 23 | ) 24 | } 25 | 26 | object SitePalettes { 27 | val light = SitePalette( 28 | nearBackground = Color.rgb(0xF4F6FA), 29 | cobweb = Colors.LightGray, 30 | brand = SitePalette.Brand( 31 | primary = Color.rgb(0x3C83EF), 32 | accent = Color.rgb(0xFCBA03), 33 | ) 34 | ) 35 | val dark = SitePalette( 36 | nearBackground = Color.rgb(0x13171F), 37 | cobweb = Colors.LightGray.inverted(), 38 | brand = SitePalette.Brand( 39 | primary = Color.rgb(0x3C83EF), 40 | accent = Color.rgb(0xF3DB5B), 41 | ) 42 | ) 43 | } 44 | 45 | fun ColorMode.toSitePalette(): SitePalette { 46 | return when (this) { 47 | ColorMode.LIGHT -> SitePalettes.light 48 | ColorMode.DARK -> SitePalettes.dark 49 | } 50 | } 51 | 52 | @InitSilk 53 | fun initTheme(ctx: InitSilkContext) { 54 | ctx.theme.palettes.light.background = Color.rgb(0xFAFAFA) 55 | ctx.theme.palettes.light.color = Colors.Black 56 | ctx.theme.palettes.dark.background = Color.rgb(0x06080B) 57 | ctx.theme.palettes.dark.color = Colors.White 58 | } 59 | -------------------------------------------------------------------------------- /app/default/site/src/site/components/layouts/MarkdownLayout.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${package}.components.layouts 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.varabyte.kobweb.compose.css.FontWeight 5 | import com.varabyte.kobweb.compose.css.Overflow 6 | import com.varabyte.kobweb.compose.css.OverflowWrap 7 | import com.varabyte.kobweb.compose.ui.Modifier 8 | import com.varabyte.kobweb.compose.ui.modifiers.* 9 | import com.varabyte.kobweb.core.data.add 10 | import com.varabyte.kobweb.core.init.InitRoute 11 | import com.varabyte.kobweb.core.init.InitRouteContext 12 | import com.varabyte.kobweb.core.layout.Layout 13 | import com.varabyte.kobweb.silk.style.CssStyle 14 | import com.varabyte.kobweb.silk.style.toAttrs 15 | import com.varabyte.kobweb.silk.theme.colors.palette.color 16 | import com.varabyte.kobweb.silk.theme.colors.palette.toPalette 17 | import com.varabyte.kobwebx.markdown.markdown 18 | import org.jetbrains.compose.web.css.DisplayStyle 19 | import org.jetbrains.compose.web.css.LineStyle 20 | import org.jetbrains.compose.web.css.cssRem 21 | import org.jetbrains.compose.web.css.px 22 | import org.jetbrains.compose.web.dom.Div 23 | import ${package}.toSitePalette 24 | 25 | val MarkdownStyle = CssStyle { 26 | base { Modifier.fillMaxSize() } 27 | 28 | cssRule("h1") { 29 | Modifier 30 | .fontSize(3.cssRem) 31 | .fontWeight(400) 32 | .margin(bottom = 2.5.cssRem) 33 | .lineHeight(1.2) //1.5x doesn't look as good on very large text 34 | } 35 | 36 | cssRule("h2") { 37 | Modifier 38 | .fontSize(3.cssRem) 39 | .fontWeight(300) 40 | .margin(topBottom = 2.cssRem) 41 | } 42 | 43 | cssRule("h3") { 44 | Modifier 45 | .fontSize(2.4.cssRem) 46 | .fontWeight(300) 47 | .margin(topBottom = 1.5.cssRem) 48 | } 49 | 50 | cssRule("h4") { 51 | Modifier 52 | .fontSize(1.2.cssRem) 53 | .fontWeight(FontWeight.Bolder) 54 | .margin(top = 1.cssRem, bottom = 0.5.cssRem) 55 | } 56 | 57 | cssRule("ul") { 58 | Modifier.fillMaxWidth().overflowWrap(OverflowWrap.BreakWord) 59 | } 60 | 61 | cssRule(" :is(li,ol,ul)") { 62 | Modifier.margin(bottom = 0.25.cssRem) 63 | } 64 | 65 | cssRule("code") { 66 | Modifier 67 | .color(colorMode.toPalette().color.toRgb().copyf(alpha = 0.8f)) 68 | .fontWeight(FontWeight.Bolder) 69 | } 70 | 71 | cssRule("pre") { 72 | Modifier 73 | .margin(top = 0.5.cssRem, bottom = 2.cssRem) 74 | .fillMaxWidth() 75 | } 76 | cssRule("pre > code") { 77 | Modifier 78 | .display(DisplayStyle.Block) 79 | .fillMaxWidth() 80 | .backgroundColor(colorMode.toSitePalette().nearBackground) 81 | .border(1.px, LineStyle.Solid, colorMode.toPalette().color) 82 | .borderRadius(0.25.cssRem) 83 | .padding(0.5.cssRem) 84 | .fontSize(1.cssRem) 85 | .overflow { x(Overflow.Auto) } 86 | } 87 | } 88 | 89 | @InitRoute 90 | fun initMarkdownLayout(ctx: InitRouteContext) { 91 | val title = ctx.markdown!!.frontMatter["title"]?.singleOrNull() 92 | require(title != null) { "Markdown file must set \"title\" in frontmatter" } 93 | 94 | ctx.data.add(PageLayoutData(title)) 95 | } 96 | 97 | @Composable 98 | @Layout(".components.layouts.PageLayout") 99 | fun MarkdownLayout(content: @Composable () -> Unit) { 100 | Div(MarkdownStyle.toAttrs()) { 101 | content() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/default/site/src/site/components/sections/Footer.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${package}.components.sections 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.varabyte.kobweb.compose.css.TextAlign 5 | import com.varabyte.kobweb.compose.css.WhiteSpace 6 | import com.varabyte.kobweb.compose.foundation.layout.Box 7 | import com.varabyte.kobweb.compose.ui.Alignment 8 | import com.varabyte.kobweb.compose.ui.Modifier 9 | import com.varabyte.kobweb.compose.ui.modifiers.* 10 | import com.varabyte.kobweb.compose.ui.toAttrs 11 | import com.varabyte.kobweb.silk.components.navigation.Link 12 | import com.varabyte.kobweb.silk.components.navigation.UncoloredLinkVariant 13 | import com.varabyte.kobweb.silk.components.text.SpanText 14 | import com.varabyte.kobweb.silk.style.CssStyle 15 | import com.varabyte.kobweb.silk.style.base 16 | import com.varabyte.kobweb.silk.style.toModifier 17 | import com.varabyte.kobweb.silk.style.vars.color.ColorVar 18 | import com.varabyte.kobweb.silk.theme.colors.ColorMode 19 | import org.jetbrains.compose.web.css.cssRem 20 | import org.jetbrains.compose.web.css.percent 21 | import org.jetbrains.compose.web.dom.Span 22 | import ${package}.toSitePalette 23 | 24 | val FooterStyle = CssStyle.base { 25 | Modifier 26 | .backgroundColor(colorMode.toSitePalette().nearBackground) 27 | .padding(topBottom = 1.5.cssRem, leftRight = 10.percent) 28 | } 29 | 30 | @Composable 31 | fun Footer(modifier: Modifier = Modifier) { 32 | Box(FooterStyle.toModifier().then(modifier), contentAlignment = Alignment.Center) { 33 | Span(Modifier.textAlign(TextAlign.Center).toAttrs()) { 34 | val sitePalette = ColorMode.current.toSitePalette() 35 | SpanText("Built with ") 36 | Link( 37 | "https://github.com/varabyte/kobweb", 38 | "Kobweb", 39 | Modifier.setVariable(ColorVar, sitePalette.brand.primary), 40 | variant = UncoloredLinkVariant 41 | ) 42 | SpanText(", template designed by ") 43 | 44 | // Huge thanks to UI Rocket (https://ui-rocket.com) for putting this great template design together for us! 45 | // If you like what you see here and want help building your own site, consider checking out their services. 46 | Link( 47 | "https://ui-rocket.com", 48 | "UI Rocket", 49 | Modifier.setVariable(ColorVar, sitePalette.brand.accent).whiteSpace(WhiteSpace.NoWrap), 50 | variant = UncoloredLinkVariant 51 | ) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/default/site/src/site/components/widgets/IconButton.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${package}.components.widgets 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.varabyte.kobweb.compose.ui.Modifier 5 | import com.varabyte.kobweb.compose.ui.modifiers.setVariable 6 | import com.varabyte.kobweb.silk.components.forms.Button 7 | import com.varabyte.kobweb.silk.components.forms.ButtonVars 8 | import org.jetbrains.compose.web.css.em 9 | import ${package}.CircleButtonVariant 10 | import ${package}.UncoloredButtonVariant 11 | 12 | @Composable 13 | fun IconButton(onClick: () -> Unit, content: @Composable () -> Unit) { 14 | Button( 15 | onClick = { onClick() }, 16 | Modifier.setVariable(ButtonVars.FontSize, 1.em), // Make button icon size relative to parent container font size 17 | variant = CircleButtonVariant.then(UncoloredButtonVariant) 18 | ) { 19 | content() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/empty/.gitignore: -------------------------------------------------------------------------------- 1 | # General ignores 2 | .DS_Store 3 | build 4 | out 5 | kotlin-js-store 6 | 7 | # IntelliJ ignores 8 | *.iml 9 | /*.ipr 10 | 11 | /.idea/caches 12 | /.idea/libraries 13 | /.idea/modules.xml 14 | /.idea/workspace.xml 15 | /.idea/gradle.xml 16 | /.idea/navEditor.xml 17 | /.idea/assetWizardSettings.xml 18 | /.idea/artifacts 19 | /.idea/compiler.xml 20 | /.idea/jarRepositories.xml 21 | /.idea/*.iml 22 | /.idea/modules 23 | /.idea/libraries-with-intellij-classes.xml 24 | 25 | # Gradle ignores 26 | .gradle 27 | 28 | # Kotlin ignores 29 | .kotlin 30 | -------------------------------------------------------------------------------- /app/empty/.kobweb-template.yaml: -------------------------------------------------------------------------------- 1 | metadata: 2 | description: "A template for building a Kobweb app from an empty skeleton" 3 | shouldHighlight: true 4 | 5 | instructions: 6 | # Note: Kobweb provides a "projectFolder" variable for us already 7 | 8 | - ! 9 | name: "projectTitle" 10 | prompt: "What is the user-visible display title for your project?" 11 | default: "${fileToTitle(projectFolder)}" 12 | validation: "isNotEmpty" 13 | - ! 14 | name: "projectName" 15 | value: "${fileToPackage(projectFolder)}" 16 | - ! 17 | message: "Note: The group ID should uniquely identify your project and organization." 18 | - ! 19 | message: "'io.github.(username).(projectname)' can work for a hobby project." 20 | - ! 21 | name: "groupId" 22 | prompt: "What is the group ID for your project?" 23 | default: "org.example.${projectName}" 24 | validation: "isPackage" 25 | # Overwrite projectName, since the user's group ID should be a more accurate source 26 | # of what the user wants their project name to be than the initial folder. 27 | # Note: the "ensure_starts_with" is kind of a hack, to prevent a crash if someone 28 | # didn't put ANY .'s in their group. 29 | - ! 30 | name: "projectName" 31 | value: "${groupId?ensure_starts_with(\".\")?keep_after_last(\".\")}" 32 | # Copy "groupId" as "package" -- it reads better in some cases. 33 | - ! 34 | name: "package" 35 | value: "${groupId}" 36 | 37 | - ! 38 | message: "🪡 To learn more: https://kobweb.varabyte.com/docs/concepts/presentation/silk" 39 | - ! 40 | name: "useSilk" 41 | prompt: "Would you like to use Silk, Kobweb's powerful UI layer built on top of Compose for Web?" 42 | default: "yes" 43 | validation: "isYesNo" 44 | transform: "${yesNoToBool(value)}" 45 | 46 | - ! 47 | message: "🌐 To learn more: https://kobweb.varabyte.com/docs/concepts/server/fullstack" 48 | - ! 49 | name: "useServer" 50 | prompt: "Would you like to include support for defining server behavior via API routes and API streams?" 51 | default: "no" 52 | validation: "isYesNo" 53 | transform: "${yesNoToBool(value)}" 54 | 55 | - ! 56 | message: "📝 To learn more: https://kobweb.varabyte.com/docs/concepts/foundation/markdown" 57 | - ! 58 | name: "useMarkdown" 59 | prompt: "Would you like to include support for handling Markdown files?" 60 | default: "yes" 61 | validation: "isYesNo" 62 | transform: "${yesNoToBool(value)}" 63 | 64 | - ! 65 | message: "⚙️ To learn more: https://kobweb.varabyte.com/docs/concepts/foundation/workers" 66 | - ! 67 | name: "useWorker" 68 | prompt: "Would you like to include support for a Kobweb worker?" 69 | default: "no" 70 | validation: "isYesNo" 71 | transform: "${yesNoToBool(value)}" 72 | 73 | - ! 74 | 75 | - ! 76 | name: "packagePath" 77 | value: "${packageToPath(package)}" 78 | - ! 79 | from: "site/src/site/*" 80 | to: "site/src/jsMain/kotlin/${packagePath}" 81 | description: "Rearranging site source to conform to the user's package" 82 | - ! 83 | condition: "${useServer}" 84 | from: "site/src/api/*" 85 | to: "site/src/jvmMain/kotlin/${packagePath}/api" 86 | description: "Rearranging API source to conform to the user's package" 87 | - ! 88 | files: "site/src/api/*" 89 | description: "Removing unused server files (if any)" 90 | - ! 91 | condition: "${useWorker}" 92 | from: "worker/src/worker/*" 93 | to: "worker/src/jsMain/kotlin/${packagePath}/worker" 94 | description: "Rearranging worker source to conform to the user's package" 95 | - ! 96 | condition: "${useWorker}" 97 | from: "worker/*" 98 | to: "donotdeleteworker" 99 | description: "Moving worker files to a safe location before cleanup" 100 | - ! 101 | files: "worker/*" 102 | description: "Removing unused worker files (if any)" 103 | - ! 104 | condition: "${useWorker}" 105 | from: "donotdeleteworker/*" 106 | to: "worker" 107 | description: "Finalizing worker files" 108 | - ! 109 | from: "site/resources/*" 110 | to: "site/src/jsMain/resources" 111 | description: "Rearranging resources" 112 | -------------------------------------------------------------------------------- /app/empty/README.md: -------------------------------------------------------------------------------- 1 | This is a [Kobweb](https://github.com/varabyte/kobweb) project bootstrapped with the `app/empty` template. 2 | 3 | This template is useful if you already know what you're doing and just want a clean slate. By default, it 4 | just creates a blank home page (which prints to the console so you can confirm it's working) 5 | 6 | If you are still learning, consider instantiating the `app` template (or one of the examples) to see actual, 7 | working projects. 8 | 9 | ## Getting Started 10 | 11 | First, run the development server by typing the following command in a terminal under the `site` folder: 12 | 13 | ```bash 14 | $ cd site 15 | $ kobweb run 16 | ``` 17 | 18 | Open [http://localhost:8080](http://localhost:8080) with your browser to see the result. 19 | 20 | You can use any editor you want for the project, but we recommend using **IntelliJ IDEA Community Edition** downloaded 21 | using the [Toolbox App](https://www.jetbrains.com/toolbox-app/). 22 | 23 | Press `Q` in the terminal to gracefully stop the server. 24 | 25 | ### Live Reload 26 | 27 | Feel free to edit / add / delete new components, pages, and API endpoints! When you make any changes, the site will 28 | indicate the status of the build and automatically reload when ready. 29 | 30 | ## Exporting the Project 31 | 32 | When you are ready to ship, you should shutdown the development server and then export the project using: 33 | 34 | ```bash 35 | kobweb export 36 | ``` 37 | 38 | When finished, you can run a Kobweb server in production mode: 39 | 40 | ```bash 41 | kobweb run --env prod 42 | ``` 43 | 44 | If you want to run this command in the Cloud provider of your choice, consider disabling interactive mode since nobody 45 | is sitting around watching the console in that case anyway. To do that, use: 46 | 47 | ```bash 48 | kobweb run --env prod --notty 49 | ``` 50 | 51 | Kobweb also supports exporting to a static layout which is compatible with static hosting providers, such as GitHub 52 | Pages, Netlify, Firebase, any presumably all the others. You can read more about that approach here: 53 | https://bitspittle.dev/blog/2022/staticdeploy -------------------------------------------------------------------------------- /app/empty/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.multiplatform) apply false 3 | } 4 | -------------------------------------------------------------------------------- /app/empty/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.caching=true 3 | org.gradle.configuration-cache=true -------------------------------------------------------------------------------- /app/empty/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | jetbrains-compose = "1.8.0" 3 | kobweb = "0.23.3" 4 | kotlin = "2.2.20" 5 | 6 | [libraries] 7 | compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "jetbrains-compose" } 8 | compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrains-compose" } 9 | kobweb-api = { module = "com.varabyte.kobweb:kobweb-api", version.ref = "kobweb" } 10 | kobweb-core = { module = "com.varabyte.kobweb:kobweb-core ", version.ref = "kobweb" } 11 | kobweb-silk = { module = "com.varabyte.kobweb:kobweb-silk", version.ref = "kobweb" } 12 | kobweb-worker = { module = "com.varabyte.kobweb:kobweb-worker", version.ref = "kobweb" } 13 | kobwebx-markdown = { module = "com.varabyte.kobwebx:kobwebx-markdown", version.ref = "kobweb" } 14 | kobwebx-serialization-kotlinx = { module = "com.varabyte.kobwebx:kobwebx-serialization-kotlinx", version.ref = "kobweb" } 15 | silk-foundation = { module = "com.varabyte.kobweb:silk-foundation", version.ref = "kobweb" } 16 | silk-icons-fa = { module = "com.varabyte.kobwebx:silk-icons-fa", version.ref = "kobweb" } 17 | silk-icons-mdi = { module = "com.varabyte.kobwebx:silk-icons-mdi", version.ref = "kobweb" } 18 | 19 | [plugins] 20 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 21 | kobweb-application = { id = "com.varabyte.kobweb.application", version.ref = "kobweb" } 22 | kobweb-library = { id = "com.varabyte.kobweb.library", version.ref = "kobweb" } 23 | kobweb-worker = { id = "com.varabyte.kobweb.worker", version.ref = "kobweb" } 24 | kobwebx-markdown = { id = "com.varabyte.kobwebx.markdown", version.ref = "kobweb" } 25 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 26 | -------------------------------------------------------------------------------- /app/empty/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/app/empty/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/empty/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /app/empty/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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /app/empty/settings.gradle.kts.ftl: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | dependencyResolutionManagement { 8 | repositories { 9 | mavenCentral() 10 | google() 11 | } 12 | } 13 | 14 | // The following block registers dependencies to enable Kobweb snapshot support. It is safe to delete or comment out 15 | // this block if you never plan to use them. 16 | gradle.settingsEvaluated { 17 | fun RepositoryHandler.kobwebSnapshots() { 18 | maven("https://central.sonatype.com/repository/maven-snapshots/") { 19 | mavenContent { 20 | includeGroupByRegex("com\\.varabyte\\.kobweb.*") 21 | snapshotsOnly() 22 | } 23 | } 24 | } 25 | 26 | pluginManagement.repositories { kobwebSnapshots() } 27 | dependencyResolutionManagement.repositories { kobwebSnapshots() } 28 | } 29 | 30 | rootProject.name = "${projectName}" 31 | 32 | include(":site") 33 | <#if useWorker?boolean>include(":worker") 34 | -------------------------------------------------------------------------------- /app/empty/site/.gitignore: -------------------------------------------------------------------------------- 1 | # Kobweb ignores 2 | .kobweb/* 3 | !.kobweb/conf.yaml 4 | -------------------------------------------------------------------------------- /app/empty/site/.kobweb/conf.yaml.ftl: -------------------------------------------------------------------------------- 1 | site: 2 | title: "${projectTitle}" 3 | 4 | server: 5 | files: 6 | dev: 7 | contentRoot: "build/processedResources/js/main/public" 8 | script: "build/kotlin-webpack/js/developmentExecutable/${projectName}.js" 9 | api: "build/libs/${projectName}.jar" 10 | prod: 11 | script: "build/kotlin-webpack/js/productionExecutable/${projectName}.js" 12 | siteRoot: ".kobweb/site" 13 | 14 | port: 8080 15 | -------------------------------------------------------------------------------- /app/empty/site/build.gradle.kts.ftl: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.application.util.configAsKobwebApplication 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.compose.compiler) 6 | alias(libs.plugins.kobweb.application) 7 | <#if !useMarkdown?boolean>// alias(libs.plugins.kobwebx.markdown) 8 | } 9 | 10 | group = "${groupId}" 11 | version = "1.0-SNAPSHOT" 12 | 13 | kobweb { 14 | app { 15 | index { 16 | description.set("Powered by Kobweb") 17 | } 18 | } 19 | } 20 | 21 | kotlin { 22 | configAsKobwebApplication("${projectName}"<#if useServer?boolean>, includeServer = true) 23 | 24 | sourceSets { 25 | <#if useServer?boolean> 26 | // commonMain.dependencies { 27 | // // Add shared dependencies between JS and JVM here 28 | // } 29 | 30 | jsMain.dependencies { 31 | implementation(libs.compose.runtime) 32 | implementation(libs.compose.html.core) 33 | implementation(libs.kobweb.core) 34 | <#if !useSilk?boolean>// implementation(libs.kobweb.silk) 35 | <#if !useSilk?boolean>// implementation(libs.silk.icons.fa) 36 | <#if !useMarkdown?boolean>// implementation(libs.kobwebx.markdown) 37 | <#if useWorker?boolean>implementation(project(":worker")) 38 | } 39 | <#if useServer?boolean> 40 | jvmMain.dependencies { 41 | compileOnly(libs.kobweb.api) // Provided by Kobweb backend at runtime 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/empty/site/resources/README.md: -------------------------------------------------------------------------------- 1 | Define markdown pages under a markdown/ folder, and 2 | they will get picked up and converted to html. 3 | 4 | You must have enabled markdown on this project for this 5 | feature to work. 6 | 7 | See also: https://kobweb.varabyte.com/docs/concepts/foundation/markdown 8 | -------------------------------------------------------------------------------- /app/empty/site/resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/app/empty/site/resources/public/favicon.ico -------------------------------------------------------------------------------- /app/empty/site/src/api/README.md: -------------------------------------------------------------------------------- 1 | Define API routes in here. 2 | 3 | See also: https://kobweb.varabyte.com/docs/concepts/server/fullstack#define-api-routes 4 | -------------------------------------------------------------------------------- /app/empty/site/src/site/AppEntry.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${package} 2 | 3 | <#if useSilk?boolean> 4 | import androidx.compose.runtime.* 5 | import com.varabyte.kobweb.compose.ui.Modifier 6 | import com.varabyte.kobweb.compose.ui.modifiers.* 7 | import com.varabyte.kobweb.core.App 8 | import com.varabyte.kobweb.silk.SilkApp 9 | import com.varabyte.kobweb.silk.components.layout.Surface 10 | import com.varabyte.kobweb.silk.init.InitSilk 11 | import com.varabyte.kobweb.silk.init.InitSilkContext 12 | import com.varabyte.kobweb.silk.init.registerStyleBase 13 | import com.varabyte.kobweb.silk.style.common.SmoothColorStyle 14 | import com.varabyte.kobweb.silk.style.toModifier 15 | import org.jetbrains.compose.web.css.* 16 | 17 | @InitSilk 18 | fun initStyles(ctx: InitSilkContext) { 19 | ctx.stylesheet.registerStyleBase("html, body") { Modifier.fillMaxHeight() } 20 | } 21 | 22 | @App 23 | @Composable 24 | fun AppEntry(content: @Composable () -> Unit) { 25 | SilkApp { 26 | Surface(SmoothColorStyle.toModifier().fillMaxHeight()) { 27 | content() 28 | } 29 | } 30 | } 31 | <#else> 32 | import androidx.compose.runtime.* 33 | import com.varabyte.kobweb.core.App 34 | import com.varabyte.kobweb.core.KobwebApp 35 | 36 | @App 37 | @Composable 38 | fun AppEntry(content: @Composable () -> Unit) { 39 | KobwebApp { 40 | content() 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /app/empty/site/src/site/components/README.md: -------------------------------------------------------------------------------- 1 | Define layouts, sections, and widgets in here. 2 | 3 | See also: https://kobweb.varabyte.com/docs/concepts/foundation/project-structure#components-and-pages 4 | -------------------------------------------------------------------------------- /app/empty/site/src/site/pages/Index.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${package}.pages 2 | 3 | <#macro worker_imports> 4 | <#if useWorker?boolean> 5 | import com.varabyte.kobweb.worker.rememberWorker 6 | import ${package}.worker.EchoWorker 7 | 8 | 9 | <#macro worker> 10 | <#if useWorker?boolean> 11 | val worker = rememberWorker { EchoWorker { output -> console.log("Echoed: $output") } } 12 | LaunchedEffect(Unit) { 13 | worker.postInput("Hello, worker!") 14 | } 15 | 16 | 17 | 18 | <#if useSilk?boolean> 19 | import androidx.compose.runtime.* 20 | import com.varabyte.kobweb.compose.foundation.layout.Box 21 | import com.varabyte.kobweb.compose.ui.Alignment 22 | import com.varabyte.kobweb.compose.ui.Modifier 23 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize 24 | import com.varabyte.kobweb.core.Page 25 | import org.jetbrains.compose.web.css.vh 26 | import org.jetbrains.compose.web.dom.Text 27 | <@worker_imports/> 28 | 29 | @Page 30 | @Composable 31 | fun HomePage() { 32 | <@worker/> 33 | // TODO: Replace the following with your own content 34 | Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { 35 | Text("THIS PAGE INTENTIONALLY LEFT BLANK") 36 | } 37 | } 38 | <#else> 39 | import androidx.compose.runtime.* 40 | import com.varabyte.kobweb.core.Page 41 | import org.jetbrains.compose.web.css.* 42 | import org.jetbrains.compose.web.dom.Div 43 | import org.jetbrains.compose.web.dom.Text 44 | <@worker_imports/> 45 | 46 | @Page 47 | @Composable 48 | fun HomePage() { 49 | <@worker/> 50 | // TODO: Replace the following with your own content 51 | Div(attrs = { 52 | style { 53 | display(DisplayStyle.Flex) 54 | width(100.percent) 55 | minHeight(100.vh) 56 | alignItems(AlignItems.Center) 57 | justifyContent(JustifyContent.Center) 58 | } 59 | }) { 60 | Text("THIS PAGE INTENTIONALLY LEFT BLANK") 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /app/empty/site/src/site/pages/README.md: -------------------------------------------------------------------------------- 1 | Define pages in here. 2 | 3 | See also: https://kobweb.varabyte.com/docs/concepts/foundation/routing#page 4 | -------------------------------------------------------------------------------- /app/empty/worker/build.gradle.kts.ftl: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.worker.util.configAsKobwebWorker 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.kobweb.worker) 6 | } 7 | 8 | group = "${groupId}.worker" 9 | version = "1.0-SNAPSHOT" 10 | 11 | kotlin { 12 | configAsKobwebWorker() 13 | 14 | sourceSets { 15 | jsMain.dependencies { 16 | implementation(libs.kobweb.worker) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/empty/worker/src/worker/EchoWorkerFactory.kt.ftl: -------------------------------------------------------------------------------- 1 | package ${package}.worker 2 | 3 | import com.varabyte.kobweb.worker.OutputDispatcher 4 | import com.varabyte.kobweb.worker.WorkerFactory 5 | import com.varabyte.kobweb.worker.WorkerStrategy 6 | import com.varabyte.kobweb.worker.createPassThroughSerializer 7 | 8 | // TODO: Worker checklist 9 | // - Review https://kobweb.varabyte.com/docs/concepts/foundation/workers 10 | // - Rename this class to a more appropriate worker for your project 11 | // - Choose appropriate input/output generic types for WorkerFactory 12 | // - Consider using Kotlinx serialization for rich I/O types 13 | // - Write strategy implementation logic 14 | // - Update IO serialization override if I/O types changed 15 | // - Delete this checklist! 16 | 17 | internal class EchoWorkerFactory : WorkerFactory { 18 | override fun createStrategy(postOutput: OutputDispatcher) = WorkerStrategy { input -> 19 | postOutput(input) // Add real worker logic here 20 | } 21 | 22 | override fun createIOSerializer() = createPassThroughSerializer() 23 | } 24 | -------------------------------------------------------------------------------- /examples/chat/.gitignore: -------------------------------------------------------------------------------- 1 | # General ignores 2 | .DS_Store 3 | build 4 | out 5 | kotlin-js-store 6 | 7 | # IntelliJ ignores 8 | *.iml 9 | /*.ipr 10 | 11 | /.idea 12 | 13 | # Gradle ignores 14 | .gradle 15 | 16 | # Kobweb ignores 17 | .kobweb/* 18 | !.kobweb/conf.yaml 19 | 20 | # Kotlin ignores 21 | .kotlin 22 | -------------------------------------------------------------------------------- /examples/chat/.kobweb-template.yaml: -------------------------------------------------------------------------------- 1 | metadata: 2 | description: "An example chat app, showcasing a multimodule project layout" 3 | 4 | # No need for any instructions - we just copy the demo as is 5 | 6 | -------------------------------------------------------------------------------- /examples/chat/README.md: -------------------------------------------------------------------------------- 1 | This is an example of how to break up a Kobweb project into multiple modules. 2 | 3 | As a concrete use-case, we create a chat server which requires logging in with an account to use it. 4 | 5 | ## Motivation 6 | 7 | Two common reasons for splitting up a project are: 8 | 9 | ### Project organization 10 | 11 | It can be easier to break major features up into their own separate module areas, to reduce the amount of code you have 12 | to worry about at one time, or (if on a team) to give people different areas that they own. 13 | 14 | ### Publishing a library 15 | 16 | You can create reusable libraries that you can publish to Maven. While this may not be useful for pages and API routes, 17 | it can be a great way to share custom widgets. 18 | 19 | ## Structure 20 | 21 | In this project, the `site` module represents the entire application. It is in this project that you'll trigger 22 | `kobweb run`. 23 | 24 | Then, there are three library modules: `core` (a bunch of general components, shared by multiple modules), `auth` 25 | (which provides pages and server support for creating an account and logging in), and `chat` (which handles posting and 26 | fetching messages). 27 | 28 | While you can put components, pages, and API routes in both library *and* application modules, it is recommended that if 29 | you do decide to split up your project, you should strive to keep the application module relatively thin. The main thing 30 | that should go in there is the `@App` composable (if you declare one) and maybe the site root (i.e. `pages/Index.kt`). 31 | The rest, it should delegate to the other modules. 32 | 33 | ### Compared to a monolith project 34 | 35 | Some things to notice about this multimodule app (vs. how it differs from a Kobweb monolith project): 36 | 37 | * The `core`, `auth`, and `chat` modules apply the `com.varabyte.kobweb.library` plugin in their build script, while 38 | the `site` module applies the `com.varabyte.kobweb.application` plugin. 39 | 40 | * The `.kobweb/conf.yaml` file lives under `site` and not at the root level. 41 | * Note that its dev script path points into the *root* build folder, not the *site* build folder. 42 | * There is a `.gitignore` file in the `site` subfolder in addition to the one at the root level. This is because it is 43 | responsible for ignoring the `.kobweb` folder, which moved into the `site` folder. 44 | 45 | `core`, `auth`, and `chat` each get processed by the Kobweb library plugin at build time, generating some intermediate 46 | artifacts that the Kobweb application plugin looks for and consumes. 47 | 48 | The application plugin is responsible for bundling everything together into a final, cohesive, single site. 49 | 50 | ## Run 51 | 52 | To run the demo: 53 | 54 | ```bash 55 | cd site 56 | kobweb run 57 | ``` -------------------------------------------------------------------------------- /examples/chat/auth/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.library.util.configAsKobwebLibrary 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.compose.compiler) 6 | alias(libs.plugins.kotlinx.serialization) 7 | alias(libs.plugins.kobweb.library) 8 | } 9 | 10 | group = "chat.auth" 11 | version = "1.0-SNAPSHOT" 12 | 13 | kotlin { 14 | configAsKobwebLibrary(includeServer = true) 15 | 16 | sourceSets { 17 | commonMain.dependencies { 18 | implementation(libs.kotlinx.serialization.json) 19 | implementation(project(":core")) 20 | } 21 | jsMain.dependencies { 22 | implementation(libs.compose.runtime) 23 | implementation(libs.compose.html.core) 24 | implementation(libs.kobweb.core) 25 | implementation(libs.kobweb.silk) 26 | implementation(libs.silk.icons.fa) 27 | } 28 | jvmMain.dependencies { 29 | compileOnly(libs.kobweb.api) // Provided by Kobweb backend at runtime 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/chat/auth/src/commonMain/kotlin/chat/auth/model/auth/Account.kt: -------------------------------------------------------------------------------- 1 | package chat.auth.model.auth 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Account( 7 | val username: String, 8 | val password: String, 9 | ) -------------------------------------------------------------------------------- /examples/chat/auth/src/commonMain/kotlin/chat/auth/model/auth/CreateAccountResponse.kt: -------------------------------------------------------------------------------- 1 | package chat.auth.model.auth 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class CreateAccountResponse( 7 | val succeeded: Boolean 8 | ) -------------------------------------------------------------------------------- /examples/chat/auth/src/commonMain/kotlin/chat/auth/model/auth/LoginResponse.kt: -------------------------------------------------------------------------------- 1 | package chat.auth.model.auth 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class LoginResponse( 7 | val succeeded: Boolean 8 | ) -------------------------------------------------------------------------------- /examples/chat/auth/src/jsMain/kotlin/chat/auth/components/sections/LoggedOut.kt: -------------------------------------------------------------------------------- 1 | package chat.auth.components.sections 2 | 3 | import androidx.compose.runtime.Composable 4 | import chat.core.components.sections.CenteredColumnContent 5 | import chat.core.components.widgets.TextButton 6 | import com.varabyte.kobweb.core.rememberPageContext 7 | import com.varabyte.kobweb.navigation.UpdateHistoryMode 8 | import org.jetbrains.compose.web.dom.Text 9 | 10 | @Composable 11 | fun LoggedOutMessage() { 12 | CenteredColumnContent { 13 | val ctx = rememberPageContext() 14 | Text("You are not logged in and cannot access this page.") 15 | TextButton("Go Home") { 16 | ctx.router.navigateTo("/", UpdateHistoryMode.REPLACE) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/chat/auth/src/jsMain/kotlin/chat/auth/model/auth/LoginState.kt: -------------------------------------------------------------------------------- 1 | package chat.auth.model.auth 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.mutableStateOf 5 | import chat.core.components.sections.ExtraNavHeaderAction 6 | import chat.core.components.sections.NavHeaderAction 7 | import com.varabyte.kobweb.navigation.Router 8 | import com.varabyte.kobweb.silk.components.icons.fa.FaRightFromBracket 9 | 10 | sealed class LoginState { 11 | companion object { 12 | private val mutableLoginState by lazy { mutableStateOf(LoggedOut) } 13 | 14 | var current 15 | get() = mutableLoginState.value 16 | set(value) { 17 | mutableLoginState.value = value 18 | 19 | when (value) { 20 | is LoggedIn -> { 21 | ExtraNavHeaderAction.current = object : NavHeaderAction() { 22 | @Composable 23 | override fun render() { 24 | FaRightFromBracket() 25 | } 26 | 27 | override fun onActionClicked(router: Router) { 28 | LoginState.current = LoggedOut 29 | router.navigateTo("/") 30 | } 31 | } 32 | } 33 | 34 | LoggedOut -> { 35 | ExtraNavHeaderAction.current = null 36 | } 37 | } 38 | } 39 | } 40 | 41 | object LoggedOut : LoginState() 42 | class LoggedIn(val account: Account) : LoginState() 43 | } 44 | -------------------------------------------------------------------------------- /examples/chat/auth/src/jsMain/kotlin/chat/auth/pages/account/Create.kt: -------------------------------------------------------------------------------- 1 | package chat.auth.pages.account 2 | 3 | import androidx.compose.runtime.* 4 | import chat.auth.model.auth.Account 5 | import chat.auth.model.auth.CreateAccountResponse 6 | import chat.auth.model.auth.LoginState 7 | import chat.core.components.layouts.PageLayoutData 8 | import chat.core.components.sections.CenteredColumnContent 9 | import chat.core.components.widgets.TextButton 10 | import chat.core.components.widgets.TitledTextInput 11 | import chat.core.styles.ErrorTextStyle 12 | import com.varabyte.kobweb.browser.api 13 | import com.varabyte.kobweb.compose.dom.ref 14 | import com.varabyte.kobweb.core.Page 15 | import com.varabyte.kobweb.core.PageContext 16 | import com.varabyte.kobweb.core.data.add 17 | import com.varabyte.kobweb.core.init.InitRoute 18 | import com.varabyte.kobweb.core.init.InitRouteContext 19 | import com.varabyte.kobweb.core.layout.Layout 20 | import com.varabyte.kobweb.navigation.UpdateHistoryMode 21 | import com.varabyte.kobweb.silk.components.text.SpanText 22 | import com.varabyte.kobweb.silk.style.toModifier 23 | import kotlinx.browser.window 24 | import kotlinx.coroutines.launch 25 | import kotlinx.serialization.json.Json 26 | 27 | @InitRoute 28 | fun initCreateAccountPage(ctx: InitRouteContext) { 29 | ctx.data.add(PageLayoutData("Create Account")) 30 | } 31 | 32 | @Page 33 | @Layout("chat.core.components.layouts.PageLayout") 34 | @Composable 35 | fun CreateAccountPage(ctx: PageContext) { 36 | CenteredColumnContent { 37 | val coroutine = rememberCoroutineScope() 38 | var username by remember { mutableStateOf("") } 39 | var password1 by remember { mutableStateOf("") } 40 | var password2 by remember { mutableStateOf("") } 41 | var errorText by remember { mutableStateOf("") } 42 | 43 | errorText = when { 44 | username.any { it.isWhitespace() } -> "Username cannot contain whitespace." 45 | password1.any { it.isWhitespace() } -> "Password cannot contain whitespace." 46 | password1 != password2 -> "Passwords don't match." 47 | else -> errorText 48 | } 49 | 50 | fun isValid() = username.isNotEmpty() && password1.isNotEmpty() && errorText.isEmpty() 51 | 52 | fun tryCreate() { 53 | if (!isValid()) return 54 | 55 | val account = Account(username, password1) 56 | val accountBytes = Json.encodeToString(account).encodeToByteArray() 57 | coroutine.launch { 58 | val response = window.api.post("/account/create", body = accountBytes) 59 | .decodeToString().let { Json.decodeFromString(CreateAccountResponse.serializer(), it) } 60 | 61 | if (response.succeeded) { 62 | LoginState.current = LoginState.LoggedIn(account) 63 | ctx.router.navigateTo("/chat", UpdateHistoryMode.REPLACE) 64 | } else { 65 | errorText = "Could not create account. Username is already taken." 66 | } 67 | } 68 | } 69 | 70 | TitledTextInput( 71 | "Username", 72 | username, 73 | { errorText = ""; username = it }, 74 | ref = ref { it.focus() }, 75 | onCommit = ::tryCreate 76 | ) 77 | TitledTextInput( 78 | "Password", 79 | password1, 80 | { errorText = ""; password1 = it }, 81 | masked = true, 82 | onCommit = ::tryCreate 83 | ) 84 | TitledTextInput( 85 | "Confirm Password", 86 | password2, 87 | { errorText = ""; password2 = it }, 88 | masked = true, 89 | onCommit = ::tryCreate 90 | ) 91 | 92 | TextButton("Create Account", enabled = isValid(), onClick = ::tryCreate) 93 | 94 | if (errorText.isNotBlank()) { 95 | SpanText(errorText, ErrorTextStyle.toModifier()) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /examples/chat/auth/src/jsMain/kotlin/chat/auth/pages/account/Login.kt: -------------------------------------------------------------------------------- 1 | package chat.auth.pages.account 2 | 3 | import androidx.compose.runtime.* 4 | import chat.auth.model.auth.Account 5 | import chat.auth.model.auth.LoginResponse 6 | import chat.auth.model.auth.LoginState 7 | import chat.core.components.layouts.PageLayoutData 8 | import chat.core.components.sections.CenteredColumnContent 9 | import chat.core.components.widgets.TextButton 10 | import chat.core.components.widgets.TitledTextInput 11 | import chat.core.styles.ErrorTextStyle 12 | import com.varabyte.kobweb.browser.api 13 | import com.varabyte.kobweb.compose.dom.ref 14 | import com.varabyte.kobweb.core.Page 15 | import com.varabyte.kobweb.core.PageContext 16 | import com.varabyte.kobweb.core.data.add 17 | import com.varabyte.kobweb.core.init.InitRoute 18 | import com.varabyte.kobweb.core.init.InitRouteContext 19 | import com.varabyte.kobweb.core.layout.Layout 20 | import com.varabyte.kobweb.navigation.UpdateHistoryMode 21 | import com.varabyte.kobweb.silk.components.text.SpanText 22 | import com.varabyte.kobweb.silk.style.toModifier 23 | import kotlinx.browser.window 24 | import kotlinx.coroutines.launch 25 | import kotlinx.serialization.json.Json 26 | 27 | @InitRoute 28 | fun initLoginPage(ctx: InitRouteContext) { 29 | ctx.data.add(PageLayoutData("Login")) 30 | } 31 | 32 | @Page 33 | @Layout("chat.core.components.layouts.PageLayout") 34 | @Composable 35 | fun LoginPage(ctx: PageContext) { 36 | CenteredColumnContent { 37 | val coroutine = rememberCoroutineScope() 38 | var username by remember { mutableStateOf("") } 39 | var password by remember { mutableStateOf("") } 40 | var errorText by remember { mutableStateOf("") } 41 | 42 | errorText = when { 43 | username.any { it.isWhitespace() } -> "Username cannot contain whitespace." 44 | password.any { it.isWhitespace() } -> "Password cannot contain whitespace." 45 | else -> errorText 46 | } 47 | 48 | fun isValid() = username.isNotEmpty() && username.none { it.isWhitespace() } 49 | && password.isNotEmpty() && password.none { it.isWhitespace() } 50 | 51 | fun tryLogin() { 52 | if (!isValid()) return 53 | 54 | val account = Account(username, password) 55 | val accountBytes = Json.encodeToString(account).encodeToByteArray() 56 | coroutine.launch { 57 | val response = window.api.post("/account/login", body = accountBytes) 58 | .decodeToString().let { Json.decodeFromString(LoginResponse.serializer(), it) } 59 | 60 | if (response.succeeded) { 61 | LoginState.current = LoginState.LoggedIn(account) 62 | ctx.router.navigateTo("/chat", UpdateHistoryMode.REPLACE) 63 | } else { 64 | errorText = "Login failed. Invalid username / password?" 65 | } 66 | } 67 | } 68 | 69 | TitledTextInput( 70 | "Username", 71 | username, 72 | { errorText = ""; username = it }, 73 | ref = ref { it.focus() }, 74 | onCommit = ::tryLogin 75 | ) 76 | TitledTextInput( 77 | "Password", 78 | password, 79 | { errorText = ""; password = it }, 80 | masked = true, 81 | onCommit = ::tryLogin 82 | ) 83 | 84 | TextButton("Login", enabled = isValid(), onClick = ::tryLogin) 85 | 86 | if (errorText.isNotBlank()) { 87 | SpanText(errorText, ErrorTextStyle.toModifier()) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /examples/chat/auth/src/jvmMain/kotlin/chat/auth/api/account/Create.kt: -------------------------------------------------------------------------------- 1 | package chat.auth.api.account 2 | 3 | import chat.auth.model.Accounts 4 | import chat.auth.model.auth.Account 5 | import chat.auth.model.auth.CreateAccountResponse 6 | import com.varabyte.kobweb.api.Api 7 | import com.varabyte.kobweb.api.ApiContext 8 | import com.varabyte.kobweb.api.data.getValue 9 | import com.varabyte.kobweb.api.http.HttpMethod 10 | import com.varabyte.kobweb.api.http.readBodyText 11 | import com.varabyte.kobweb.api.http.setBodyText 12 | import kotlinx.serialization.encodeToString 13 | import kotlinx.serialization.json.Json 14 | 15 | @Api 16 | fun create(ctx: ApiContext) { 17 | if (ctx.req.method != HttpMethod.POST) return 18 | val account = Json.decodeFromString(Account.serializer(), ctx.req.readBodyText()!!) 19 | val accounts = ctx.data.getValue() 20 | val result = CreateAccountResponse(accounts.set.none { it.username == account.username }) 21 | if (result.succeeded) { 22 | accounts.set.add(account) 23 | } 24 | 25 | ctx.res.setBodyText(Json.encodeToString(result)) 26 | } 27 | -------------------------------------------------------------------------------- /examples/chat/auth/src/jvmMain/kotlin/chat/auth/api/account/Login.kt: -------------------------------------------------------------------------------- 1 | package chat.auth.api.account 2 | 3 | import chat.auth.model.Accounts 4 | import chat.auth.model.auth.Account 5 | import chat.auth.model.auth.LoginResponse 6 | import com.varabyte.kobweb.api.Api 7 | import com.varabyte.kobweb.api.ApiContext 8 | import com.varabyte.kobweb.api.data.getValue 9 | import com.varabyte.kobweb.api.http.HttpMethod 10 | import com.varabyte.kobweb.api.http.readBodyText 11 | import com.varabyte.kobweb.api.http.setBodyText 12 | import kotlinx.serialization.encodeToString 13 | import kotlinx.serialization.json.Json 14 | 15 | @Api 16 | fun login(ctx: ApiContext) { 17 | if (ctx.req.method != HttpMethod.POST) return 18 | val account = Json.decodeFromString(Account.serializer(), ctx.req.readBodyText()!!) 19 | val accounts = ctx.data.getValue() 20 | ctx.res.setBodyText(Json.encodeToString(LoginResponse(succeeded = accounts.set.contains(account)))) 21 | } 22 | -------------------------------------------------------------------------------- /examples/chat/auth/src/jvmMain/kotlin/chat/auth/model/Accounts.kt: -------------------------------------------------------------------------------- 1 | package chat.auth.model 2 | 3 | import chat.auth.model.auth.Account 4 | import com.varabyte.kobweb.api.data.add 5 | import com.varabyte.kobweb.api.init.InitApi 6 | import com.varabyte.kobweb.api.init.InitApiContext 7 | 8 | @InitApi 9 | fun initAccounts(ctx: InitApiContext) { 10 | ctx.data.add(Accounts()) 11 | } 12 | 13 | class Accounts { 14 | val set = mutableSetOf() 15 | } -------------------------------------------------------------------------------- /examples/chat/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.multiplatform) apply false 3 | alias(libs.plugins.compose.compiler) apply false 4 | alias(libs.plugins.kobweb.application) apply false 5 | alias(libs.plugins.kobweb.library) apply false 6 | alias(libs.plugins.kobwebx.markdown) apply false 7 | } 8 | -------------------------------------------------------------------------------- /examples/chat/chat/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.library.util.configAsKobwebLibrary 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.compose.compiler) 6 | alias(libs.plugins.kotlinx.serialization) 7 | alias(libs.plugins.kobweb.library) 8 | } 9 | 10 | group = "chat.chat" 11 | version = "1.0-SNAPSHOT" 12 | 13 | kotlin { 14 | configAsKobwebLibrary(includeServer = true) 15 | 16 | sourceSets { 17 | commonMain.dependencies { 18 | implementation(libs.kotlinx.serialization.json) 19 | implementation(project(":core")) 20 | implementation(project(":auth")) 21 | } 22 | jsMain.dependencies { 23 | implementation(libs.compose.runtime) 24 | implementation(libs.compose.html.core) 25 | implementation(libs.kobweb.core) 26 | implementation(libs.kobweb.silk) 27 | implementation(libs.silk.icons.fa) 28 | } 29 | jvmMain.dependencies { 30 | compileOnly(libs.kobweb.api) // Provided by Kobweb backend at runtime 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/chat/chat/src/commonMain/kotlin/chat/chat/model/Message.kt: -------------------------------------------------------------------------------- 1 | package chat.chat.model 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | class Message(val username: String, val text: String) 7 | -------------------------------------------------------------------------------- /examples/chat/chat/src/jsMain/kotlin/chat/chat/pages/Chat.kt: -------------------------------------------------------------------------------- 1 | package chat.chat.pages 2 | 3 | import androidx.compose.runtime.* 4 | import chat.auth.components.sections.LoggedOutMessage 5 | import chat.auth.model.auth.LoginState 6 | import chat.chat.model.Message 7 | import chat.core.G 8 | import chat.core.components.layouts.PageLayoutData 9 | import chat.core.components.sections.CenteredColumnContent 10 | import chat.core.components.widgets.TextButton 11 | import com.varabyte.kobweb.compose.css.Overflow 12 | import com.varabyte.kobweb.compose.dom.ref 13 | import com.varabyte.kobweb.compose.foundation.layout.Box 14 | import com.varabyte.kobweb.compose.foundation.layout.Column 15 | import com.varabyte.kobweb.compose.ui.Alignment 16 | import com.varabyte.kobweb.compose.ui.Modifier 17 | import com.varabyte.kobweb.compose.ui.modifiers.* 18 | import com.varabyte.kobweb.core.Page 19 | import com.varabyte.kobweb.core.data.add 20 | import com.varabyte.kobweb.core.init.InitRoute 21 | import com.varabyte.kobweb.core.init.InitRouteContext 22 | import com.varabyte.kobweb.core.layout.Layout 23 | import com.varabyte.kobweb.silk.components.forms.TextInput 24 | import com.varabyte.kobweb.silk.style.CssStyle 25 | import com.varabyte.kobweb.silk.style.base 26 | import com.varabyte.kobweb.silk.style.toModifier 27 | import com.varabyte.kobweb.streams.ApiStream 28 | import com.varabyte.kobweb.streams.connect 29 | import kotlinx.serialization.json.Json 30 | import org.jetbrains.compose.web.css.LineStyle 31 | import org.jetbrains.compose.web.css.percent 32 | import org.jetbrains.compose.web.css.px 33 | import org.jetbrains.compose.web.dom.Br 34 | import org.jetbrains.compose.web.dom.Text 35 | 36 | val ChatBoxStyle = CssStyle.base { 37 | Modifier 38 | .padding(5.px) 39 | .borderRadius(5.px) 40 | .border { style(LineStyle.Solid) } 41 | .overflow { y(Overflow.Auto) } 42 | } 43 | 44 | private fun Message.toChatLine() = "${this.username}: ${this.text}" 45 | 46 | @InitRoute 47 | fun initChatPage(ctx: InitRouteContext) { 48 | ctx.data.add(PageLayoutData("Chat")) 49 | } 50 | 51 | @Page 52 | @Layout("chat.core.components.layouts.PageLayout") 53 | @Composable 54 | fun ChatPage() { 55 | val account = (LoginState.current as? LoginState.LoggedIn)?.account ?: run { 56 | LoggedOutMessage() 57 | return 58 | } 59 | 60 | val messages = remember { mutableStateListOf() } 61 | val chatStream = remember { ApiStream("chat") } 62 | LaunchedEffect(Unit) { 63 | chatStream.connect { ctx -> 64 | messages.add(Json.decodeFromString(ctx.text)) 65 | } 66 | } 67 | 68 | CenteredColumnContent { 69 | Column( 70 | ChatBoxStyle.toModifier().height(80.percent).width(G.Ui.Width.Large).fontSize(G.Ui.Text.MediumSmall) 71 | ) { 72 | messages.forEach { entry -> 73 | Text(entry.toChatLine()) 74 | Br() 75 | } 76 | } 77 | Box(Modifier.width(G.Ui.Width.Large).height(60.px)) { 78 | var message by remember { mutableStateOf("") } 79 | 80 | fun sendMessage() { 81 | val messageCopy = Message(account.username, message.trim()) 82 | messages.add(messageCopy) 83 | message = "" 84 | chatStream.send(Json.encodeToString(messageCopy)) 85 | } 86 | TextInput( 87 | message, 88 | { message = it }, 89 | Modifier.width(70.percent).align(Alignment.BottomStart), 90 | ref = ref { it.focus() }, 91 | onCommit = ::sendMessage 92 | ) 93 | TextButton( 94 | "Send", 95 | Modifier.width(20.percent).align(Alignment.BottomEnd), 96 | enabled = message.isNotBlank(), 97 | onClick = ::sendMessage 98 | ) 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /examples/chat/chat/src/jvmMain/kotlin/chat/chat/api/Chat.kt: -------------------------------------------------------------------------------- 1 | package chat.chat.api 2 | 3 | import com.varabyte.kobweb.api.stream.ApiStream 4 | import com.varabyte.kobweb.api.stream.broadcastExcluding 5 | 6 | val chat = ApiStream { ctx -> 7 | ctx.stream.broadcastExcluding(ctx.text, ctx.stream.id) 8 | } 9 | -------------------------------------------------------------------------------- /examples/chat/core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.library.util.configAsKobwebLibrary 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.compose.compiler) 6 | alias(libs.plugins.kobweb.library) 7 | alias(libs.plugins.kobwebx.markdown) 8 | } 9 | 10 | group = "chat.core" 11 | version = "1.0-SNAPSHOT" 12 | 13 | kotlin { 14 | // Even though this module doesn't actually define any server routes itself, 'includeServer = true' allows us to 15 | // depened on ":core" as a commonMain dependency from other modules, instead of a JS-only dependency. 16 | configAsKobwebLibrary(includeServer = true) 17 | 18 | sourceSets { 19 | jsMain.dependencies { 20 | implementation(libs.compose.runtime) 21 | implementation(libs.compose.html.core) 22 | implementation(libs.kobweb.core) 23 | implementation(libs.kobweb.silk) 24 | implementation(libs.kobwebx.markdown) 25 | implementation(libs.silk.icons.fa) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/chat/core/src/jsMain/kotlin/chat/core/Globals.kt: -------------------------------------------------------------------------------- 1 | package chat.core 2 | 3 | import org.jetbrains.compose.web.css.px 4 | 5 | object G { 6 | object Ui { 7 | object Width { 8 | val Small = 200.px 9 | val Medium = 400.px 10 | val Large = 600.px 11 | } 12 | 13 | object Text { 14 | val Small = 18.px 15 | val MediumSmall = 22.px 16 | val Medium = 28.px 17 | val Large = 38.px 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/chat/core/src/jsMain/kotlin/chat/core/components/layouts/MarkdownLayout.kt: -------------------------------------------------------------------------------- 1 | package chat.core.components.layouts 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.LaunchedEffect 5 | import chat.core.components.sections.NavHeader 6 | import com.varabyte.kobweb.compose.foundation.layout.Column 7 | import com.varabyte.kobweb.compose.ui.Alignment 8 | import com.varabyte.kobweb.compose.ui.Modifier 9 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize 10 | import com.varabyte.kobweb.compose.ui.modifiers.padding 11 | import com.varabyte.kobweb.core.PageContext 12 | import com.varabyte.kobweb.core.data.add 13 | import com.varabyte.kobweb.core.data.addIfAbsent 14 | import com.varabyte.kobweb.core.data.getValue 15 | import com.varabyte.kobweb.core.init.InitRoute 16 | import com.varabyte.kobweb.core.init.InitRouteContext 17 | import com.varabyte.kobweb.core.layout.Layout 18 | import com.varabyte.kobwebx.markdown.markdown 19 | import kotlinx.browser.document 20 | import org.jetbrains.compose.web.css.px 21 | 22 | @InitRoute 23 | fun initMarkdownLayout(ctx: InitRouteContext) { 24 | ctx.markdown!!.frontMatter["title"]?.singleOrNull()?.let { title -> 25 | ctx.data.add(PageLayoutData(title)) 26 | } 27 | } 28 | 29 | @Composable 30 | @Layout(".components.layouts.PageLayout") 31 | fun MarkdownLayout(content: @Composable () -> Unit) { 32 | content() 33 | } 34 | -------------------------------------------------------------------------------- /examples/chat/core/src/jsMain/kotlin/chat/core/components/layouts/PageLayout.kt: -------------------------------------------------------------------------------- 1 | package chat.core.components.layouts 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.LaunchedEffect 5 | import chat.core.components.sections.NavHeader 6 | import com.varabyte.kobweb.compose.foundation.layout.Column 7 | import com.varabyte.kobweb.compose.ui.Alignment 8 | import com.varabyte.kobweb.compose.ui.Modifier 9 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize 10 | import com.varabyte.kobweb.compose.ui.modifiers.padding 11 | import com.varabyte.kobweb.core.PageContext 12 | import com.varabyte.kobweb.core.data.addIfAbsent 13 | import com.varabyte.kobweb.core.data.getValue 14 | import com.varabyte.kobweb.core.init.InitRoute 15 | import com.varabyte.kobweb.core.init.InitRouteContext 16 | import com.varabyte.kobweb.core.layout.Layout 17 | import kotlinx.browser.document 18 | import org.jetbrains.compose.web.css.px 19 | 20 | class PageLayoutData(val title: String) 21 | 22 | @InitRoute 23 | fun initPageLayout(ctx: InitRouteContext) { 24 | ctx.data.addIfAbsent { PageLayoutData("Chat") } 25 | } 26 | 27 | @Composable 28 | @Layout 29 | fun PageLayout(ctx: PageContext, content: @Composable () -> Unit) { 30 | val data = ctx.data.getValue() 31 | LaunchedEffect(data.title) { 32 | document.title = data.title 33 | } 34 | 35 | Column( 36 | modifier = Modifier.fillMaxSize(), 37 | horizontalAlignment = Alignment.CenterHorizontally 38 | ) { 39 | NavHeader() 40 | Column( 41 | modifier = Modifier.fillMaxSize().padding(top = 10.px, left = 50.px, right = 50.px), 42 | horizontalAlignment = Alignment.CenterHorizontally 43 | ) { 44 | content() 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/chat/core/src/jsMain/kotlin/chat/core/components/sections/CenteredColumnContent.kt: -------------------------------------------------------------------------------- 1 | package chat.core.components.sections 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.varabyte.kobweb.compose.foundation.layout.Column 5 | import com.varabyte.kobweb.compose.foundation.layout.ColumnScope 6 | import com.varabyte.kobweb.compose.ui.Alignment 7 | import com.varabyte.kobweb.compose.ui.Modifier 8 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize 9 | import com.varabyte.kobweb.compose.ui.modifiers.padding 10 | import org.jetbrains.compose.web.css.px 11 | 12 | @Composable 13 | fun CenteredColumnContent(content: @Composable ColumnScope.() -> Unit) { 14 | Column( 15 | Modifier.fillMaxSize().padding(top = 50.px), 16 | horizontalAlignment = Alignment.CenterHorizontally, 17 | content = content 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /examples/chat/core/src/jsMain/kotlin/chat/core/components/sections/LoadingMessage.kt: -------------------------------------------------------------------------------- 1 | package chat.core.components.sections 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.varabyte.kobweb.compose.foundation.layout.Box 5 | import com.varabyte.kobweb.compose.ui.Alignment 6 | import com.varabyte.kobweb.compose.ui.Modifier 7 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxSize 8 | import org.jetbrains.compose.web.dom.Text 9 | 10 | @Composable 11 | fun LoadingMessage() { 12 | Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { 13 | Text("Please wait. Loading...") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/chat/core/src/jsMain/kotlin/chat/core/components/widgets/TextButton.kt: -------------------------------------------------------------------------------- 1 | package chat.core.components.widgets 2 | 3 | import androidx.compose.runtime.Composable 4 | import chat.core.G 5 | import com.varabyte.kobweb.compose.ui.Modifier 6 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize 7 | import com.varabyte.kobweb.compose.ui.modifiers.margin 8 | import com.varabyte.kobweb.compose.ui.modifiers.padding 9 | import com.varabyte.kobweb.compose.ui.modifiers.width 10 | import com.varabyte.kobweb.silk.components.forms.Button 11 | import com.varabyte.kobweb.silk.style.CssStyle 12 | import com.varabyte.kobweb.silk.style.base 13 | import com.varabyte.kobweb.silk.style.toModifier 14 | import org.jetbrains.compose.web.css.px 15 | import org.jetbrains.compose.web.dom.Text 16 | 17 | val TextButtonStyle = CssStyle.base { 18 | Modifier 19 | .width(G.Ui.Width.Medium) 20 | .margin(10.px) 21 | .padding(4.px) 22 | .fontSize(G.Ui.Text.Medium) 23 | } 24 | 25 | @Composable 26 | fun TextButton( 27 | text: String, 28 | modifier: Modifier = Modifier, 29 | enabled: Boolean = true, 30 | onClick: () -> Unit 31 | ) { 32 | Button(onClick = { onClick() }, TextButtonStyle.toModifier().then(modifier), enabled = enabled) { 33 | Text(text) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/chat/core/src/jsMain/kotlin/chat/core/components/widgets/TitledTextInput.kt: -------------------------------------------------------------------------------- 1 | package chat.core.components.widgets 2 | 3 | import androidx.compose.runtime.Composable 4 | import chat.core.G 5 | import com.varabyte.kobweb.compose.dom.ElementRefScope 6 | import com.varabyte.kobweb.compose.foundation.layout.Column 7 | import com.varabyte.kobweb.compose.ui.Modifier 8 | import com.varabyte.kobweb.compose.ui.graphics.Colors 9 | import com.varabyte.kobweb.compose.ui.modifiers.color 10 | import com.varabyte.kobweb.compose.ui.modifiers.fontSize 11 | import com.varabyte.kobweb.compose.ui.modifiers.margin 12 | import com.varabyte.kobweb.compose.ui.modifiers.width 13 | import com.varabyte.kobweb.silk.components.forms.TextInput 14 | import com.varabyte.kobweb.silk.components.text.SpanText 15 | import com.varabyte.kobweb.silk.style.CssStyle 16 | import com.varabyte.kobweb.silk.style.base 17 | import com.varabyte.kobweb.silk.style.toModifier 18 | import org.jetbrains.compose.web.css.px 19 | import org.w3c.dom.HTMLInputElement 20 | 21 | val TitledTextInputLabelStyle = CssStyle.base { 22 | Modifier 23 | .fontSize(G.Ui.Text.Small) 24 | .color(Colors.Grey) 25 | } 26 | 27 | val TitledTextInputStyle = CssStyle.base { 28 | Modifier 29 | .width(G.Ui.Width.Medium) 30 | .margin(bottom = 10.px) 31 | } 32 | 33 | 34 | /** A text input box with a descriptive label above it. */ 35 | @Composable 36 | fun TitledTextInput( 37 | title: String, 38 | text: String, 39 | onTextChange: (String) -> Unit, 40 | masked: Boolean = false, 41 | onCommit: () -> Unit = {}, 42 | ref: ElementRefScope? = null, 43 | ) { 44 | Column { 45 | SpanText(title, TitledTextInputLabelStyle.toModifier()) 46 | TextInput(text, onTextChange, TitledTextInputStyle.toModifier(), password = masked, onCommit = onCommit, ref = ref) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/chat/core/src/jsMain/kotlin/chat/core/styles/Styles.kt: -------------------------------------------------------------------------------- 1 | package chat.core.styles 2 | 3 | import com.varabyte.kobweb.compose.ui.Modifier 4 | import com.varabyte.kobweb.compose.ui.graphics.Colors 5 | import com.varabyte.kobweb.compose.ui.modifiers.color 6 | import com.varabyte.kobweb.silk.style.CssStyle 7 | import com.varabyte.kobweb.silk.style.base 8 | 9 | val ErrorTextStyle = CssStyle.base { 10 | Modifier.color(Colors.Red) 11 | } 12 | -------------------------------------------------------------------------------- /examples/chat/core/src/jsMain/resources/markdown/About.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: .components.layouts.PageLayout 3 | title: About 4 | --- 5 | 6 | This project is created with the [Kobweb](https://varabyte.com/kobweb) framework and used to demonstrate a way to 7 | organize your project by splitting pieces up into multiple modules. -------------------------------------------------------------------------------- /examples/chat/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.caching=true 3 | org.gradle.configuration-cache=true -------------------------------------------------------------------------------- /examples/chat/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | jetbrains-compose = "1.8.0" 3 | kobweb = "0.23.3" 4 | kotlin = "2.2.20" 5 | kotlinx-serialization = "1.9.0" 6 | 7 | [libraries] 8 | compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "jetbrains-compose" } 9 | compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrains-compose" } 10 | kobweb-api = { module = "com.varabyte.kobweb:kobweb-api", version.ref = "kobweb" } 11 | kobweb-core = { module = "com.varabyte.kobweb:kobweb-core", version.ref = "kobweb" } 12 | kobweb-silk = { module = "com.varabyte.kobweb:kobweb-silk", version.ref = "kobweb" } 13 | kobwebx-markdown = { module = "com.varabyte.kobwebx:kobwebx-markdown", version.ref = "kobweb" } 14 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } 15 | silk-icons-fa = { module = "com.varabyte.kobwebx:silk-icons-fa", version.ref = "kobweb" } 16 | 17 | [plugins] 18 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 19 | kobweb-application = { id = "com.varabyte.kobweb.application", version.ref = "kobweb" } 20 | kobweb-library = { id = "com.varabyte.kobweb.library", version.ref = "kobweb" } 21 | kobwebx-markdown = { id = "com.varabyte.kobwebx.markdown", version.ref = "kobweb" } 22 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 23 | kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 24 | -------------------------------------------------------------------------------- /examples/chat/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/chat/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/chat/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /examples/chat/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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /examples/chat/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | dependencyResolutionManagement { 8 | repositories { 9 | mavenCentral() 10 | google() 11 | } 12 | } 13 | 14 | // The following block registers dependencies to enable Kobweb snapshot support. It is safe to delete or comment out 15 | // this block if you never plan to use them. 16 | gradle.settingsEvaluated { 17 | fun RepositoryHandler.kobwebSnapshots() { 18 | maven("https://central.sonatype.com/repository/maven-snapshots/") { 19 | mavenContent { 20 | includeGroupByRegex("com\\.varabyte\\.kobweb.*") 21 | snapshotsOnly() 22 | } 23 | } 24 | } 25 | 26 | pluginManagement.repositories { kobwebSnapshots() } 27 | dependencyResolutionManagement.repositories { kobwebSnapshots() } 28 | } 29 | 30 | rootProject.name = "chat" 31 | 32 | include(":core") 33 | include(":auth") 34 | include(":chat") 35 | include(":site") 36 | -------------------------------------------------------------------------------- /examples/chat/site/.gitignore: -------------------------------------------------------------------------------- 1 | # Kobweb ignores 2 | .kobweb/* 3 | !.kobweb/conf.yaml 4 | -------------------------------------------------------------------------------- /examples/chat/site/.kobweb/conf.yaml: -------------------------------------------------------------------------------- 1 | site: 2 | title: "Kobweb Chat" 3 | 4 | server: 5 | files: 6 | dev: 7 | contentRoot: "build/processedResources/js/main/public" 8 | script: "build/kotlin-webpack/js/developmentExecutable/chat-site.js" 9 | api: "build/libs/chat-site.jar" 10 | prod: 11 | script: "build/kotlin-webpack/js/productionExecutable/chat-site.js" 12 | siteRoot: ".kobweb/site" 13 | 14 | port: 8080 15 | -------------------------------------------------------------------------------- /examples/chat/site/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.application.util.configAsKobwebApplication 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.compose.compiler) 6 | id(libs.plugins.kobweb.application.get().pluginId) 7 | } 8 | 9 | group = "chat.site" 10 | version = "1.0-SNAPSHOT" 11 | 12 | kotlin { 13 | configAsKobwebApplication(includeServer = true) 14 | 15 | sourceSets { 16 | commonMain.dependencies { 17 | implementation(project(":core")) 18 | implementation(project(":auth")) 19 | implementation(project(":chat")) 20 | } 21 | jsMain.dependencies { 22 | implementation(libs.compose.runtime) 23 | implementation(libs.compose.html.core) 24 | implementation(libs.kobweb.core) 25 | implementation(libs.kobweb.silk) 26 | } 27 | jvmMain.dependencies { 28 | compileOnly(libs.kobweb.api) // Provided by Kobweb backend at runtime 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/chat/site/src/jsMain/kotlin/chat/site/AppEntry.kt: -------------------------------------------------------------------------------- 1 | package chat 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.LaunchedEffect 5 | import com.varabyte.kobweb.compose.ui.Modifier 6 | import com.varabyte.kobweb.compose.ui.modifiers.fillMaxHeight 7 | import com.varabyte.kobweb.compose.ui.modifiers.fontFamily 8 | import com.varabyte.kobweb.compose.ui.modifiers.lineHeight 9 | import com.varabyte.kobweb.core.App 10 | import com.varabyte.kobweb.silk.SilkApp 11 | import com.varabyte.kobweb.silk.components.layout.Surface 12 | import com.varabyte.kobweb.silk.init.InitSilk 13 | import com.varabyte.kobweb.silk.init.InitSilkContext 14 | import com.varabyte.kobweb.silk.init.registerStyleBase 15 | import com.varabyte.kobweb.silk.style.common.SmoothColorStyle 16 | import com.varabyte.kobweb.silk.style.toModifier 17 | import com.varabyte.kobweb.silk.theme.colors.ColorMode 18 | import com.varabyte.kobweb.silk.theme.colors.loadFromLocalStorage 19 | import com.varabyte.kobweb.silk.theme.colors.saveToLocalStorage 20 | import com.varabyte.kobweb.silk.theme.colors.systemPreference 21 | 22 | private const val COLOR_MODE_KEY = "chat:app:colorMode" 23 | 24 | @InitSilk 25 | fun updateTheme(ctx: InitSilkContext) { 26 | ctx.config.initialColorMode = ColorMode.loadFromLocalStorage(COLOR_MODE_KEY) ?: ColorMode.systemPreference 27 | } 28 | 29 | @InitSilk 30 | fun registerGlobalStyles(ctx: InitSilkContext) = ctx.stylesheet.apply { 31 | registerStyleBase("html, body") { Modifier.fillMaxHeight() } 32 | registerStyleBase("body") { 33 | Modifier 34 | .fontFamily( 35 | "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", 36 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "sans-serif" 37 | ) 38 | .lineHeight(1.4) 39 | } 40 | } 41 | 42 | @App 43 | @Composable 44 | fun AppEntry(content: @Composable () -> Unit) { 45 | SilkApp { 46 | val colorMode = ColorMode.current 47 | LaunchedEffect(colorMode) { 48 | colorMode.saveToLocalStorage(COLOR_MODE_KEY) 49 | } 50 | 51 | Surface(SmoothColorStyle.toModifier().fillMaxHeight()) { 52 | content() 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/chat/site/src/jsMain/kotlin/chat/site/pages/Index.kt: -------------------------------------------------------------------------------- 1 | package chat.site.pages 2 | 3 | import androidx.compose.runtime.Composable 4 | import chat.auth.model.auth.LoginState 5 | import chat.core.components.layouts.PageLayout 6 | import chat.core.components.layouts.PageLayoutData 7 | import chat.core.components.sections.CenteredColumnContent 8 | import chat.core.components.widgets.TextButton 9 | import com.varabyte.kobweb.core.Page 10 | import com.varabyte.kobweb.core.PageContext 11 | import com.varabyte.kobweb.core.data.add 12 | import com.varabyte.kobweb.core.init.InitRoute 13 | import com.varabyte.kobweb.core.init.InitRouteContext 14 | import com.varabyte.kobweb.core.layout.Layout 15 | import com.varabyte.kobweb.core.rememberPageContext 16 | 17 | @InitRoute 18 | fun initHomePage(ctx: InitRouteContext) { 19 | ctx.data.add(PageLayoutData("Kobweb Chat")) 20 | } 21 | 22 | @Page 23 | @Layout("chat.core.components.layouts.PageLayout") 24 | @Composable 25 | fun HomePage(ctx: PageContext) { 26 | CenteredColumnContent { 27 | if (LoginState.current is LoginState.LoggedIn) { 28 | TextButton("Go to Chat") { ctx.router.navigateTo("/chat") } 29 | } 30 | TextButton("Create Account") { ctx.router.navigateTo("/account/create") } 31 | TextButton("Login") { ctx.router.navigateTo("/account/login") } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/chat/site/src/jsMain/resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/chat/site/src/jsMain/resources/public/favicon.ico -------------------------------------------------------------------------------- /examples/clock/.gitignore: -------------------------------------------------------------------------------- 1 | # General ignores 2 | .DS_Store 3 | build 4 | out 5 | kotlin-js-store 6 | 7 | # IntelliJ ignores 8 | *.iml 9 | /*.ipr 10 | 11 | /.idea 12 | 13 | # Gradle ignores 14 | .gradle 15 | 16 | # Kotlin ignores 17 | .kotlin 18 | -------------------------------------------------------------------------------- /examples/clock/.kobweb-template.yaml: -------------------------------------------------------------------------------- 1 | metadata: 2 | description: "A canvas demo using the Mozilla animated clock canvas tutorial" 3 | 4 | # No need for any instructions - we just copy the demo as is 5 | 6 | -------------------------------------------------------------------------------- /examples/clock/README.md: -------------------------------------------------------------------------------- 1 | This is a [Kobweb](https://github.com/varabyte/kobweb) project instantiated from the `examples/clock` template. 2 | 3 | It is a Kotlin implementation of the tutorial 4 | [shared by Mozilla](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#an_animated_clock). 5 | 6 | The purpose of this project is to demonstrate the `Canvas2d` composable as well as color mode awareness. 7 | 8 | --- 9 | 10 | To run the sample, simply enter the following command in the terminal: 11 | 12 | ```bash 13 | kobweb run 14 | ``` 15 | 16 | and open [http://localhost:8080](http://localhost:8080) with your browser to see the result. 17 | -------------------------------------------------------------------------------- /examples/clock/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.caching=true 3 | org.gradle.configuration-cache=true -------------------------------------------------------------------------------- /examples/clock/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | jetbrains-compose = "1.8.0" 3 | kobweb = "0.23.3" 4 | kotlin = "2.2.20" 5 | 6 | [libraries] 7 | compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "jetbrains-compose" } 8 | compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrains-compose" } 9 | kobweb-core = { module = "com.varabyte.kobweb:kobweb-core", version.ref = "kobweb" } 10 | kobweb-silk = { module = "com.varabyte.kobweb:kobweb-silk", version.ref = "kobweb" } 11 | silk-icons-fa = { module = "com.varabyte.kobwebx:silk-icons-fa", version.ref = "kobweb" } 12 | 13 | [plugins] 14 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 15 | kobweb-application = { id = "com.varabyte.kobweb.application", version.ref = "kobweb" } 16 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 17 | -------------------------------------------------------------------------------- /examples/clock/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/clock/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/clock/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /examples/clock/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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /examples/clock/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | dependencyResolutionManagement { 8 | repositories { 9 | mavenCentral() 10 | google() 11 | } 12 | } 13 | 14 | // The following block registers dependencies to enable Kobweb snapshot support. It is safe to delete or comment out 15 | // this block if you never plan to use them. 16 | gradle.settingsEvaluated { 17 | fun RepositoryHandler.kobwebSnapshots() { 18 | maven("https://central.sonatype.com/repository/maven-snapshots/") { 19 | mavenContent { 20 | includeGroupByRegex("com\\.varabyte\\.kobweb.*") 21 | snapshotsOnly() 22 | } 23 | } 24 | } 25 | 26 | pluginManagement.repositories { kobwebSnapshots() } 27 | dependencyResolutionManagement.repositories { kobwebSnapshots() } 28 | } 29 | 30 | rootProject.name = "clock" 31 | 32 | include(":site") 33 | -------------------------------------------------------------------------------- /examples/clock/site/.gitignore: -------------------------------------------------------------------------------- 1 | # Kobweb ignores 2 | .kobweb/* 3 | !.kobweb/conf.yaml 4 | -------------------------------------------------------------------------------- /examples/clock/site/.kobweb/conf.yaml: -------------------------------------------------------------------------------- 1 | site: 2 | title: "Clock" 3 | 4 | server: 5 | files: 6 | dev: 7 | contentRoot: "build/processedResources/js/main/public" 8 | script: "build/kotlin-webpack/js/developmentExecutable/clock.js" 9 | api: "build/libs/clock.jar" 10 | prod: 11 | script: "build/kotlin-webpack/js/productionExecutable/clock.js" 12 | siteRoot: ".kobweb/site" 13 | 14 | port: 8080 15 | -------------------------------------------------------------------------------- /examples/clock/site/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.application.util.configAsKobwebApplication 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.compose.compiler) 6 | alias(libs.plugins.kobweb.application) 7 | } 8 | 9 | group = "clock" 10 | version = "1.0-SNAPSHOT" 11 | 12 | kotlin { 13 | configAsKobwebApplication() 14 | 15 | sourceSets { 16 | jsMain.dependencies { 17 | implementation(libs.compose.runtime) 18 | implementation(libs.compose.html.core) 19 | implementation(libs.kobweb.core) 20 | implementation(libs.kobweb.silk) 21 | implementation(libs.silk.icons.fa) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/clock/site/src/jsMain/kotlin/clock/AppEntry.kt: -------------------------------------------------------------------------------- 1 | package clock 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.varabyte.kobweb.compose.ui.Modifier 5 | import com.varabyte.kobweb.compose.ui.modifiers.* 6 | import com.varabyte.kobweb.core.App 7 | import com.varabyte.kobweb.silk.init.InitSilk 8 | import com.varabyte.kobweb.silk.init.InitSilkContext 9 | import com.varabyte.kobweb.silk.SilkApp 10 | import com.varabyte.kobweb.silk.components.layout.Surface 11 | import com.varabyte.kobweb.silk.style.common.SmoothColorStyle 12 | import com.varabyte.kobweb.silk.style.toModifier 13 | import com.varabyte.kobweb.silk.init.registerStyleBase 14 | 15 | @InitSilk 16 | fun registerGlobalStyles(ctx: InitSilkContext) = ctx.stylesheet.apply { 17 | registerStyleBase("html, body") { Modifier.fillMaxHeight() } 18 | registerStyleBase("body") { 19 | Modifier 20 | .fontFamily( 21 | "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", 22 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "sans-serif" 23 | ) 24 | } 25 | } 26 | 27 | @App 28 | @Composable 29 | fun AppEntry(content: @Composable () -> Unit) { 30 | SilkApp { 31 | Surface(SmoothColorStyle.toModifier().fillMaxHeight()) { 32 | content() 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/clock/site/src/jsMain/kotlin/clock/components/layouts/PageLayout.kt: -------------------------------------------------------------------------------- 1 | package clock.components.layouts 2 | 3 | import androidx.compose.runtime.* 4 | import com.varabyte.kobweb.compose.css.TextAlign 5 | import com.varabyte.kobweb.compose.css.WhiteSpace 6 | import com.varabyte.kobweb.compose.foundation.layout.Arrangement 7 | import com.varabyte.kobweb.compose.foundation.layout.Box 8 | import com.varabyte.kobweb.compose.foundation.layout.BoxScope 9 | import com.varabyte.kobweb.compose.foundation.layout.Column 10 | import com.varabyte.kobweb.compose.foundation.layout.Row 11 | import com.varabyte.kobweb.compose.foundation.layout.Spacer 12 | import com.varabyte.kobweb.compose.ui.Alignment 13 | import com.varabyte.kobweb.compose.ui.Modifier 14 | import com.varabyte.kobweb.compose.ui.modifiers.* 15 | import com.varabyte.kobweb.compose.ui.toAttrs 16 | import com.varabyte.kobweb.silk.components.forms.Button 17 | import com.varabyte.kobweb.silk.components.icons.fa.FaMoon 18 | import com.varabyte.kobweb.silk.components.icons.fa.FaSun 19 | import com.varabyte.kobweb.silk.components.navigation.Link 20 | import com.varabyte.kobweb.silk.components.text.SpanText 21 | import com.varabyte.kobweb.silk.theme.SilkTheme 22 | import com.varabyte.kobweb.silk.theme.colors.ColorMode 23 | import com.varabyte.kobweb.silk.theme.colors.palette.color 24 | import org.jetbrains.compose.web.css.* 25 | import org.jetbrains.compose.web.dom.Span 26 | import org.jetbrains.compose.web.dom.Text 27 | 28 | @Composable 29 | fun PageLayout(content: @Composable BoxScope.() -> Unit) { 30 | Column( 31 | Modifier.fillMaxWidth().minHeight(100.vh), 32 | horizontalAlignment = Alignment.CenterHorizontally 33 | ) { 34 | Column(Modifier.fillMaxSize()) { 35 | Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { 36 | var colorMode by ColorMode.currentState 37 | Button( 38 | onClick = { colorMode = colorMode.opposite }, 39 | Modifier.margin(10.px).padding(0.px).borderRadius(50.percent) 40 | ) { 41 | if (colorMode.isLight) FaMoon() else FaSun() 42 | } 43 | } 44 | Box(Modifier.fillMaxSize()) { 45 | content() 46 | } 47 | } 48 | 49 | val borderColor = SilkTheme.palette.color 50 | Spacer() 51 | Box( 52 | Modifier 53 | .fillMaxWidth() 54 | .borderTop(1.px, LineStyle.Solid, borderColor) 55 | .fontSize(1.5.cssRem), 56 | Alignment.Center 57 | ) { 58 | // Use PreWrap to preserve trailing space in text 59 | Span(Modifier.margin(topBottom = 1.cssRem).whiteSpace(WhiteSpace.PreWrap).textAlign(TextAlign.Center).toAttrs()) { 60 | Text("This project is built using ") 61 | Link( 62 | "https://github.com/varabyte/kobweb", 63 | "Kobweb", 64 | ) 65 | Text(", a full-stack Kotlin web framework.") 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples/clock/site/src/jsMain/resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/clock/site/src/jsMain/resources/public/favicon.ico -------------------------------------------------------------------------------- /examples/imageprocessor/.gitignore: -------------------------------------------------------------------------------- 1 | # General ignores 2 | .DS_Store 3 | build 4 | out 5 | kotlin-js-store 6 | 7 | # IntelliJ ignores 8 | *.iml 9 | /*.ipr 10 | 11 | /.idea/caches 12 | /.idea/libraries 13 | /.idea/modules.xml 14 | /.idea/workspace.xml 15 | /.idea/gradle.xml 16 | /.idea/navEditor.xml 17 | /.idea/assetWizardSettings.xml 18 | /.idea/artifacts 19 | /.idea/compiler.xml 20 | /.idea/jarRepositories.xml 21 | /.idea/*.iml 22 | /.idea/modules 23 | /.idea/libraries-with-intellij-classes.xml 24 | 25 | # Gradle ignores 26 | .gradle 27 | 28 | # Kotlin ignores 29 | .kotlin 30 | -------------------------------------------------------------------------------- /examples/imageprocessor/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /examples/imageprocessor/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/imageprocessor/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/imageprocessor/.kobweb-template.yaml: -------------------------------------------------------------------------------- 1 | metadata: 2 | description: "An image processing demo that showcases Kobweb Workers" 3 | 4 | # No need for any instructions - we just copy the demo as is 5 | 6 | -------------------------------------------------------------------------------- /examples/imageprocessor/README.md: -------------------------------------------------------------------------------- 1 | This is a [Kobweb](https://github.com/varabyte/kobweb) project instantiated from the `examples/imageprocessor` template. 2 | 3 | The purpose of this project is to showcase a reasonably sophisticated web worker implementation. 4 | 5 | Normally, most web worker articles discuss something very simple -- a worker that receives a numeric value, does some 6 | calculation on it, and returns a new value. 7 | 8 | However, using sealed classes, you can create a worker that can receive a variety of commands, and return a variety of 9 | results. (Of course, Kobweb Workers support communicating via simple, direct value types as well). 10 | 11 | In this site, we create a simple "Image Processor" worker that can apply effects onto an image. An advantage of this 12 | approach is it provides a strong guarantee that the logic inside the worker cannot ever affect the UI directly. 13 | 14 | In a real project, such a separation of responsibilities could be a good way to divide responsibilities across multiple 15 | developers. The fact this approach lets you write complex, computationally intensive logic without worrying about 16 | affecting the responsiveness of your site is just icing on the cake. 17 | 18 | ## Project organization 19 | 20 | The project is divided up into three modules: `site`, `worker`, and `util`. 21 | 22 | In the `worker` module, we defined the `ImageProcessorWorkerFactory` class. The Kobweb Worker Gradle plugin will 23 | automatically generate an `ImageProcessorWorker` class from this factory. 24 | 25 | We separated the implementation details of the `worker` module out into a separate `util` module to ensure that those 26 | classes won't leak into the final site. (You can of course put this logic inside the `worker` module and tag everything 27 | internal, but that can be easy to forget). 28 | 29 | Finally, you can find `ImageProcessorWorker` used by the `site` module in `Index.kt`. 30 | 31 | ## Running 32 | 33 | --- 34 | 35 | To run the sample, simply enter the following commands in the terminal: 36 | 37 | ```bash 38 | $ cd site 39 | $ kobweb run 40 | ``` 41 | 42 | and open [http://localhost:8080](http://localhost:8080) with your browser to see the result. 43 | -------------------------------------------------------------------------------- /examples/imageprocessor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.multiplatform) apply false 3 | alias(libs.plugins.kotlin.serialization) apply false 4 | } 5 | -------------------------------------------------------------------------------- /examples/imageprocessor/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.caching=true 3 | org.gradle.configuration-cache=true -------------------------------------------------------------------------------- /examples/imageprocessor/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | jetbrains-compose = "1.8.0" 3 | kobweb = "0.23.3" 4 | kotlin = "2.2.20" 5 | kotlinx-serialization = "1.9.0" 6 | 7 | [libraries] 8 | compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "jetbrains-compose" } 9 | compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrains-compose" } 10 | kobweb-api = { module = "com.varabyte.kobweb:kobweb-api", version.ref = "kobweb" } 11 | kobweb-core = { module = "com.varabyte.kobweb:kobweb-core", version.ref = "kobweb" } 12 | kobweb-silk = { module = "com.varabyte.kobweb:kobweb-silk", version.ref = "kobweb" } 13 | kobweb-worker = { module = "com.varabyte.kobweb:kobweb-worker", version.ref = "kobweb" } 14 | kobwebx-serialization-kotlinx = { module = "com.varabyte.kobwebx:kobwebx-serialization-kotlinx", version.ref = "kobweb" } 15 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } 16 | silk-icons-fa = { module = "com.varabyte.kobwebx:silk-icons-fa", version.ref = "kobweb" } 17 | 18 | [plugins] 19 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 20 | kobweb-application = { id = "com.varabyte.kobweb.application", version.ref = "kobweb" } 21 | kobweb-worker = { id = "com.varabyte.kobweb.worker", version.ref = "kobweb" } 22 | kobwebx-markdown = { id = "com.varabyte.kobwebx.markdown", version.ref = "kobweb" } 23 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 24 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 25 | -------------------------------------------------------------------------------- /examples/imageprocessor/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/imageprocessor/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/imageprocessor/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /examples/imageprocessor/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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /examples/imageprocessor/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | dependencyResolutionManagement { 8 | repositories { 9 | mavenCentral() 10 | google() 11 | } 12 | } 13 | 14 | // The following block registers dependencies to enable Kobweb snapshot support. It is safe to delete or comment out 15 | // this block if you never plan to use them. 16 | gradle.settingsEvaluated { 17 | fun RepositoryHandler.kobwebSnapshots() { 18 | maven("https://central.sonatype.com/repository/maven-snapshots/") { 19 | mavenContent { 20 | includeGroupByRegex("com\\.varabyte\\.kobweb.*") 21 | snapshotsOnly() 22 | } 23 | } 24 | } 25 | 26 | pluginManagement.repositories { kobwebSnapshots() } 27 | dependencyResolutionManagement.repositories { kobwebSnapshots() } 28 | } 29 | 30 | rootProject.name = "imageprocessor" 31 | 32 | include(":site") 33 | include(":util") 34 | include(":worker") 35 | -------------------------------------------------------------------------------- /examples/imageprocessor/site/.gitignore: -------------------------------------------------------------------------------- 1 | # Kobweb ignores 2 | .kobweb/* 3 | !.kobweb/conf.yaml 4 | 5 | -------------------------------------------------------------------------------- /examples/imageprocessor/site/.kobweb/conf.yaml: -------------------------------------------------------------------------------- 1 | site: 2 | title: "Image Processor" 3 | 4 | server: 5 | files: 6 | dev: 7 | contentRoot: "build/processedResources/js/main/public" 8 | script: "build/kotlin-webpack/js/developmentExecutable/imageprocessor.js" 9 | prod: 10 | script: "build/kotlin-webpack/js/productionExecutable/imageprocessor.js" 11 | siteRoot: ".kobweb/site" 12 | 13 | port: 8080 14 | -------------------------------------------------------------------------------- /examples/imageprocessor/site/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.application.util.configAsKobwebApplication 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.compose.compiler) 6 | alias(libs.plugins.kobweb.application) 7 | } 8 | 9 | group = "imageprocessor.site" 10 | version = "1.0-SNAPSHOT" 11 | 12 | kotlin { 13 | configAsKobwebApplication("imageprocessor") 14 | 15 | sourceSets { 16 | jsMain.dependencies { 17 | implementation(libs.compose.runtime) 18 | implementation(libs.compose.html.core) 19 | implementation(libs.kobweb.core) 20 | implementation(libs.kobweb.silk) 21 | implementation(project(":worker")) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/imageprocessor/site/src/jsMain/kotlin/imageprocessor/site/AppEntry.kt: -------------------------------------------------------------------------------- 1 | package imageprocessor.site 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.varabyte.kobweb.compose.ui.Modifier 5 | import com.varabyte.kobweb.compose.ui.graphics.Colors 6 | import com.varabyte.kobweb.compose.ui.modifiers.* 7 | import com.varabyte.kobweb.core.App 8 | import com.varabyte.kobweb.silk.SilkApp 9 | import com.varabyte.kobweb.silk.components.layout.Surface 10 | import com.varabyte.kobweb.silk.init.InitSilk 11 | import com.varabyte.kobweb.silk.init.InitSilkContext 12 | import com.varabyte.kobweb.silk.init.registerStyleBase 13 | import org.jetbrains.compose.web.css.* 14 | 15 | @InitSilk 16 | fun initSiteStyles(ctx: InitSilkContext) = ctx.stylesheet.apply { 17 | registerStyleBase("html, body") { Modifier.fillMaxHeight() } 18 | registerStyleBase("body") { 19 | Modifier.fontFamily( 20 | "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", 21 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "sans-serif" 22 | ) 23 | } 24 | 25 | registerStyleBase("footer") { 26 | Modifier 27 | .width(100.percent) 28 | .height(100.px) 29 | .fontSize(1.5.cssRem) 30 | .borderTop(1.px, LineStyle.Solid, Colors.Black) 31 | .display(DisplayStyle.Flex) 32 | .justifyContent(JustifyContent.Center) 33 | .alignItems(AlignItems.Center) 34 | } 35 | } 36 | 37 | @App 38 | @Composable 39 | fun AppEntry(content: @Composable () -> Unit) { 40 | SilkApp { 41 | Surface(Modifier.fillMaxHeight()) { 42 | content() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/imageprocessor/site/src/jsMain/kotlin/imageprocessor/site/components/widgets/Modal.kt: -------------------------------------------------------------------------------- 1 | package imageprocessor.site.components.widgets 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.varabyte.kobweb.compose.css.Overflow 5 | import com.varabyte.kobweb.compose.foundation.layout.* 6 | import com.varabyte.kobweb.compose.ui.Modifier 7 | import com.varabyte.kobweb.compose.ui.modifiers.* 8 | import com.varabyte.kobweb.silk.components.overlay.Overlay 9 | import com.varabyte.kobweb.silk.style.CssStyle 10 | import com.varabyte.kobweb.silk.style.base 11 | import com.varabyte.kobweb.silk.style.toModifier 12 | import com.varabyte.kobweb.silk.theme.colors.palette.background 13 | import com.varabyte.kobweb.silk.theme.colors.palette.toPalette 14 | import org.jetbrains.compose.web.css.cssRem 15 | import org.jetbrains.compose.web.css.percent 16 | import org.jetbrains.compose.web.css.px 17 | import org.jetbrains.compose.web.css.vh 18 | 19 | val ModalStyle = CssStyle.base { 20 | Modifier 21 | .minWidth(300.px) 22 | .maxWidth(500.px) 23 | .backgroundColor(colorMode.toPalette().background) 24 | .margin(top = 6.vh) 25 | .padding(20.px) 26 | .gap(10.px) 27 | .borderRadius(2.percent) 28 | } 29 | 30 | val ModalContentStyle = CssStyle { 31 | base { 32 | Modifier 33 | .fillMaxWidth() 34 | .gap(10.px) 35 | .padding(5.px) // Avoid outlines clipping against the side / add space between buttons and scrollbar 36 | .maxHeight(60.vh) 37 | .overflow { y(Overflow.Auto) } 38 | } 39 | } 40 | 41 | val ModalButtonRowStyle = CssStyle { 42 | base { Modifier.fillMaxWidth().margin(top = 1.cssRem).gap(1.cssRem) } 43 | cssRule(" *") { Modifier.flexGrow(1) } 44 | } 45 | 46 | @Composable 47 | fun Modal( 48 | bottomRow: (@Composable RowScope.() -> Unit)? = null, 49 | content: (@Composable BoxScope.() -> Unit)? = null, 50 | ) { 51 | Overlay { 52 | Column(ModalStyle.toModifier()) { 53 | content?.let { content -> 54 | Box(ModalContentStyle.toModifier()) { 55 | content() 56 | } 57 | } 58 | if (bottomRow != null) { 59 | Row(ModalButtonRowStyle.toModifier()) { 60 | bottomRow() 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/imageprocessor/site/src/jsMain/kotlin/imageprocessor/site/components/widgets/Spinner.kt: -------------------------------------------------------------------------------- 1 | package imageprocessor.site.components.widgets 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.varabyte.kobweb.compose.css.AnimationIterationCount 5 | import com.varabyte.kobweb.compose.dom.svg.Circle 6 | import com.varabyte.kobweb.compose.dom.svg.SVGFillType 7 | import com.varabyte.kobweb.compose.dom.svg.SVGStrokeLineCap 8 | import com.varabyte.kobweb.compose.dom.svg.Svg 9 | import com.varabyte.kobweb.compose.foundation.layout.Box 10 | import com.varabyte.kobweb.compose.ui.Modifier 11 | import com.varabyte.kobweb.compose.ui.graphics.Colors 12 | import com.varabyte.kobweb.compose.ui.modifiers.animation 13 | import com.varabyte.kobweb.compose.ui.modifiers.padding 14 | import com.varabyte.kobweb.compose.ui.modifiers.rotate 15 | import com.varabyte.kobweb.compose.ui.modifiers.size 16 | import com.varabyte.kobweb.compose.ui.styleModifier 17 | import com.varabyte.kobweb.compose.ui.toAttrs 18 | import com.varabyte.kobweb.silk.style.animation.Keyframes 19 | import com.varabyte.kobweb.silk.style.animation.toAnimation 20 | import com.varabyte.kobweb.silk.style.CssStyle 21 | import com.varabyte.kobweb.silk.style.base 22 | import com.varabyte.kobweb.silk.style.toAttrs 23 | import com.varabyte.kobweb.silk.style.toModifier 24 | import org.jetbrains.compose.web.css.* 25 | 26 | // Source: https://codepen.io/supah/pen/BjYLdW 27 | 28 | val SpinnerRotate = Keyframes { 29 | 100.percent { Modifier.rotate(360.deg) } 30 | } 31 | 32 | val SpinnerDash = Keyframes { 33 | 0.percent { 34 | Modifier.styleModifier { 35 | property("stroke-dasharray", "1, 150") 36 | property("stroke-dashoffset", "0") 37 | } 38 | } 39 | 40 | 50.percent { 41 | Modifier.styleModifier { 42 | property("stroke-dasharray", "90, 150") 43 | property("stroke-dashoffset", "-35") 44 | } 45 | } 46 | 47 | 100.percent { 48 | Modifier.styleModifier { 49 | property("stroke-dasharray", "90, 150") 50 | property("stroke-dashoffset", "-124") 51 | } 52 | } 53 | } 54 | 55 | 56 | val SpinnerContainerStyle = CssStyle.base { 57 | // We put some margin inside the container so that the spinner SVG element doesn't keep messing with the width / 58 | // height of the current layout as it rotates. Even though the spinner itself is technically a circle, the SVG 59 | // element is a square, so as it rotates, its sharp corners keep escaping the current layout bounds. A parent 60 | // container prevents this from causing layout problems. 61 | Modifier.size(70.px).padding(10.px) 62 | } 63 | 64 | val SpinnerStyle = CssStyle.base { 65 | Modifier.animation( 66 | SpinnerRotate.toAnimation( 67 | colorMode, 68 | 2.s, 69 | AnimationTimingFunction.Linear, 70 | iterationCount = AnimationIterationCount.Infinite 71 | ) 72 | ) 73 | } 74 | 75 | val SpinnerPathStyle = CssStyle.base { 76 | Modifier.animation( 77 | SpinnerDash.toAnimation( 78 | colorMode, 79 | 1.5.s, 80 | AnimationTimingFunction.EaseIn, 81 | iterationCount = AnimationIterationCount.Infinite 82 | ) 83 | ) 84 | } 85 | 86 | @Composable 87 | fun Spinner() { 88 | Box(SpinnerContainerStyle.toModifier()) { 89 | Svg( 90 | attrs = SpinnerStyle.toAttrs { 91 | viewBox(0, 0, 50, 50) 92 | } 93 | ) { 94 | Circle(attrs = SpinnerPathStyle.toModifier().toAttrs { 95 | classes("spinner-path") 96 | cx(25) 97 | cy(25) 98 | r(20) 99 | fill(SVGFillType.None) 100 | stroke(Colors.Black) 101 | strokeLineCap(SVGStrokeLineCap.Round) 102 | strokeWidth(5) 103 | }) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /examples/imageprocessor/site/src/jsMain/resources/public/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/imageprocessor/site/src/jsMain/resources/public/default.png -------------------------------------------------------------------------------- /examples/imageprocessor/site/src/jsMain/resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/imageprocessor/site/src/jsMain/resources/public/favicon.ico -------------------------------------------------------------------------------- /examples/imageprocessor/util/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.kotlin.multiplatform) 3 | } 4 | 5 | group = "imageprocessor.util" 6 | version = "1.0-SNAPSHOT" 7 | 8 | kotlin { 9 | js { browser() } 10 | 11 | sourceSets { 12 | jsMain.dependencies { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/imageprocessor/util/src/jsMain/kotlin/imageprocessor/util/Image.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnsafeCastFromDynamic") // Necessary for working with Uint8ClampedArray, otherwise values get truncated 2 | 3 | package imageprocessor.util 4 | 5 | import org.khronos.webgl.Uint8ClampedArray 6 | import org.khronos.webgl.get 7 | import org.khronos.webgl.set 8 | 9 | data class Pixel(val r: Int, val g: Int, val b: Int, val a: Int = 255) { 10 | init { 11 | check(r in 0..255) { "Expected r to be in range [0, 255], got $r" } 12 | check(g in 0..255) { "Expected g to be in range [0, 255], got $g" } 13 | check(b in 0..255) { "Expected b to be in range [0, 255], got $b" } 14 | check(a in 0..255) { "Expected a to be in range [0, 255], got $a" } 15 | } 16 | 17 | fun alphaNormalized(): Pixel { 18 | if (this.a == 255) return this 19 | val r = this.r * this.a / 255 20 | val g = this.g * this.a / 255 21 | val b = this.b * this.a / 255 22 | return Pixel(r, g, b, 255) 23 | } 24 | } 25 | 26 | interface Image { 27 | val w: Int 28 | val h: Int 29 | val pixels: List 30 | 31 | fun copy(): Image = MutableImage(w, h, pixels) 32 | } 33 | 34 | operator fun Image.get(x: Int, y: Int) = pixels[y * w + x] 35 | 36 | private fun Uint8ClampedArray.toPixels(): List { 37 | check(length % 4 == 0) { "Expected a multiple of 4 bytes, got $length" } 38 | return List(length / 4) { i -> 39 | Pixel( 40 | this[i * 4 + 0].asDynamic(), 41 | this[i * 4 + 1].asDynamic(), 42 | this[i * 4 + 2].asDynamic(), 43 | this[i * 4 + 3].asDynamic(), 44 | ) 45 | } 46 | } 47 | 48 | private fun List.toUint8ClampedArray(w: Int, h: Int): Uint8ClampedArray { 49 | val pixels = this 50 | return Uint8ClampedArray(w * h * 4).apply { 51 | var byteIndex = 0 52 | for (pixel in pixels) { 53 | this[byteIndex++] = pixel.r.asDynamic() 54 | this[byteIndex++] = pixel.g.asDynamic() 55 | this[byteIndex++] = pixel.b.asDynamic() 56 | this[byteIndex++] = pixel.a.asDynamic() 57 | } 58 | } 59 | } 60 | 61 | fun Image.toUint8ClampedArray() = pixels.toUint8ClampedArray(w, h) 62 | 63 | class MutableImage(override var w: Int, override var h: Int, pixels: List) : Image { 64 | constructor(w: Int, h: Int, bytes: Uint8ClampedArray) : this(w, h, bytes.toPixels()) 65 | 66 | init { 67 | check(pixels.size == w * h) { "Given width $w and height $h, so expected ${w * h} pixels, but got ${pixels.size}" } 68 | } 69 | 70 | override val pixels = pixels.toMutableList() 71 | 72 | override fun copy(): MutableImage = super.copy() as MutableImage 73 | } 74 | 75 | operator fun MutableImage.set(x: Int, y: Int, pixel: Pixel) { 76 | pixels[y * w + x] = pixel 77 | } 78 | -------------------------------------------------------------------------------- /examples/imageprocessor/util/src/jsMain/kotlin/imageprocessor/util/ImageProcessor.kt: -------------------------------------------------------------------------------- 1 | package imageprocessor.util 2 | 3 | interface ImageProcessor { 4 | fun process(image: MutableImage) 5 | } 6 | 7 | abstract class ImageProcessorAdapter : ImageProcessor { 8 | interface Context { 9 | val image: Image 10 | val x: Int 11 | val y: Int 12 | } 13 | 14 | private class MutableContext(override val image: MutableImage) : Context { 15 | override var x: Int = 0 16 | override var y: Int = 0 17 | } 18 | 19 | protected open fun preProcess(image: MutableImage) {} 20 | 21 | protected abstract fun Context.convertPixel(pixel: Pixel): Pixel 22 | 23 | override fun process(image: MutableImage) { 24 | preProcess(image) 25 | 26 | val ctx = MutableContext(image) 27 | for (y in 0 until image.h) { 28 | for (x in 0 until image.w) { 29 | ctx.x = x 30 | ctx.y = y 31 | image[x, y] = ctx.convertPixel(image[x, y]) 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/imageprocessor/util/src/jsMain/kotlin/imageprocessor/util/processors/BlurProcessor.kt: -------------------------------------------------------------------------------- 1 | package imageprocessor.util.processors 2 | 3 | import imageprocessor.util.* 4 | 5 | class BlurProcessor(val radius: Int) : ImageProcessorAdapter() { 6 | private lateinit var originalImageWithAlphaNormalized: Image 7 | 8 | override fun preProcess(image: MutableImage) { 9 | originalImageWithAlphaNormalized = image.copy().apply { 10 | pixels.forEachIndexed { index, pixel -> pixels[index] = pixel.alphaNormalized() } 11 | } 12 | } 13 | 14 | override fun Context.convertPixel(pixel: Pixel): Pixel { 15 | var sumR = 0 16 | var sumG = 0 17 | var sumB = 0 18 | var count = 0 19 | 20 | // Calculate the sum of the color values of the neighboring pixels, weighted by their alpha values 21 | for (dy in -radius..radius) { 22 | for (dx in -radius..radius) { 23 | val nx = x + dx 24 | val ny = y + dy 25 | 26 | // Check if the neighbor is within the image boundaries 27 | if (nx in 0 until image.w && ny in 0 until image.h) { 28 | val neighborPixel = originalImageWithAlphaNormalized[nx, ny] 29 | 30 | sumR += neighborPixel.r 31 | sumG += neighborPixel.g 32 | sumB += neighborPixel.b 33 | count++ 34 | } 35 | } 36 | } 37 | 38 | // Calculate the average color value 39 | val avgR = sumR / count 40 | val avgG = sumG / count 41 | val avgB = sumB / count 42 | 43 | // Return the new blurred pixel with the original alpha 44 | return pixel.copy(r = avgR, g = avgG, b = avgB) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/imageprocessor/util/src/jsMain/kotlin/imageprocessor/util/processors/FlipProcessors.kt: -------------------------------------------------------------------------------- 1 | package imageprocessor.util.processors 2 | 3 | import imageprocessor.util.* 4 | 5 | class FlipXProcessor : ImageProcessorAdapter() { 6 | private lateinit var originalImage: Image 7 | 8 | override fun preProcess(image: MutableImage) { 9 | originalImage = image.copy() 10 | } 11 | 12 | override fun Context.convertPixel(pixel: Pixel): Pixel { 13 | return originalImage[image.w - x - 1, y] 14 | } 15 | } 16 | 17 | class FlipYProcessor : ImageProcessorAdapter() { 18 | private lateinit var originalImage: Image 19 | 20 | override fun preProcess(image: MutableImage) { 21 | originalImage = image.copy() 22 | } 23 | 24 | override fun Context.convertPixel(pixel: Pixel): Pixel { 25 | return originalImage[x, image.h - y - 1] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/imageprocessor/util/src/jsMain/kotlin/imageprocessor/util/processors/GrayscaleProcessor.kt: -------------------------------------------------------------------------------- 1 | package imageprocessor.util.processors 2 | 3 | import imageprocessor.util.ImageProcessorAdapter 4 | import imageprocessor.util.Pixel 5 | 6 | class GrayscaleProcessor : ImageProcessorAdapter() { 7 | override fun Context.convertPixel(pixel: Pixel): Pixel { 8 | val avg = (pixel.r + pixel.g + pixel.b) / 3 9 | return pixel.copy(r = avg, g = avg, b = avg) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/imageprocessor/util/src/jsMain/kotlin/imageprocessor/util/processors/InvertProcessor.kt: -------------------------------------------------------------------------------- 1 | package imageprocessor.util.processors 2 | 3 | import imageprocessor.util.ImageProcessorAdapter 4 | import imageprocessor.util.Pixel 5 | 6 | class InvertProcessor : ImageProcessorAdapter() { 7 | override fun Context.convertPixel(pixel: Pixel): Pixel { 8 | return pixel.copy(r = 255 - pixel.r, g = 255 - pixel.g, b = 255 - pixel.b) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/imageprocessor/worker/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.worker.util.configAsKobwebWorker 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.kotlin.serialization) 6 | alias(libs.plugins.kobweb.worker) 7 | } 8 | 9 | group = "imageprocessor.worker" 10 | version = "1.0-SNAPSHOT" 11 | 12 | kotlin { 13 | configAsKobwebWorker("imageprocessor-worker") 14 | 15 | sourceSets { 16 | jsMain.dependencies { 17 | implementation(libs.kotlinx.serialization.json) 18 | implementation(libs.kobweb.worker) 19 | implementation(libs.kobwebx.serialization.kotlinx) 20 | implementation(project(":util")) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/imageprocessor/worker/src/jsMain/kotlin/imageprocessor/worker/ImageProcessorWorkerFactory.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnsafeCastFromDynamic") // Necessary for working with Uint8ClampedArray, otherwise values get truncated 2 | 3 | package imageprocessor.worker 4 | 5 | import com.varabyte.kobweb.serialization.createIOSerializer 6 | import com.varabyte.kobweb.worker.Attachments 7 | import com.varabyte.kobweb.worker.OutputDispatcher 8 | import com.varabyte.kobweb.worker.WorkerFactory 9 | import com.varabyte.kobweb.worker.WorkerStrategy 10 | import imageprocessor.util.Image 11 | import imageprocessor.util.ImageProcessor 12 | import imageprocessor.util.MutableImage 13 | import imageprocessor.util.processors.* 14 | import imageprocessor.util.toUint8ClampedArray 15 | import kotlinx.serialization.Serializable 16 | import kotlinx.serialization.json.Json 17 | import org.w3c.dom.ImageData 18 | 19 | @Serializable 20 | class ProcessImageRequest( 21 | val commands: List 22 | ) 23 | 24 | @Suppress("CanSealedSubClassBeObject") // Using classes as we may add parameters in the future 25 | @Serializable 26 | sealed class ImageProcessorCommand { 27 | @Serializable 28 | class Blur(val radius: Int) : ImageProcessorCommand() 29 | 30 | @Serializable 31 | class Grayscale : ImageProcessorCommand() 32 | 33 | @Serializable 34 | class Invert : ImageProcessorCommand() 35 | 36 | @Serializable 37 | class Flip(val x: Boolean = false, val y: Boolean = false) : ImageProcessorCommand() 38 | } 39 | 40 | @Suppress("CanSealedSubClassBeObject") // Using classes as we may add parameters in the future 41 | @Serializable 42 | sealed class ProcessImageResponse { 43 | @Serializable 44 | class Finished : ProcessImageResponse() 45 | 46 | @Serializable 47 | class Error(val message: String) : ProcessImageResponse() 48 | } 49 | 50 | private fun Image.toImageData() = ImageData(this.toUint8ClampedArray(), w, h) 51 | 52 | internal class ImageProcessorWorkerFactory : WorkerFactory { 53 | override fun createIOSerializer() = Json.createIOSerializer() 54 | 55 | override fun createStrategy(postOutput: OutputDispatcher)= 56 | WorkerStrategy { input -> 57 | val processors = mutableListOf() 58 | for (command in input.commands) { 59 | when (command) { 60 | is ImageProcessorCommand.Blur -> processors.add(BlurProcessor(command.radius)) 61 | is ImageProcessorCommand.Grayscale -> processors.add(GrayscaleProcessor()) 62 | is ImageProcessorCommand.Invert -> processors.add(InvertProcessor()) 63 | is ImageProcessorCommand.Flip -> { 64 | if (command.x) processors.add(FlipXProcessor()) 65 | if (command.y) processors.add(FlipYProcessor()) 66 | } 67 | } 68 | } 69 | 70 | try { 71 | val imageData = attachments.getImageData("imageData")!! 72 | val image = MutableImage(imageData.width, imageData.height, imageData.data) 73 | 74 | processors.forEach { it.process(image) } 75 | postOutput( 76 | ProcessImageResponse.Finished(), 77 | Attachments { add("imageData", image.toImageData()) } 78 | ) 79 | } catch (e: Exception) { 80 | postOutput(ProcessImageResponse.Error(e.message ?: "Unknown error (${e::class.simpleName})")) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /examples/jb/counter/.gitignore: -------------------------------------------------------------------------------- 1 | # General ignores 2 | .DS_Store 3 | build 4 | out 5 | kotlin-js-store 6 | 7 | # IntelliJ ignores 8 | *.iml 9 | /*.ipr 10 | 11 | /.idea 12 | 13 | # Gradle ignores 14 | .gradle 15 | 16 | # Kotlin ignores 17 | .kotlin 18 | -------------------------------------------------------------------------------- /examples/jb/counter/.kobweb-template.yaml: -------------------------------------------------------------------------------- 1 | metadata: 2 | description: "A very minimal site with just a counter (based on the Jetbrains tutorial)" 3 | 4 | # No need for any instructions - we just copy the demo as is 5 | 6 | -------------------------------------------------------------------------------- /examples/jb/counter/README.md: -------------------------------------------------------------------------------- 1 | This is a [Kobweb](https://github.com/varabyte/kobweb) project instantiated from the `examples/jb/counter` template. 2 | 3 | It is based of the "Getting Started" tutorial provided by JetBrains 4 | [here](https://github.com/JetBrains/compose-jb/tree/master/tutorials/Web/Getting_Started). 5 | 6 | Even though Kobweb provides a extra functionality that you can use, this project doesn't use most of them (note the lack 7 | of dependencies in the project's `build.gradle.kts` file), and that's OK! Being a thin shim around Compose for Web is a 8 | perfectly acceptable use-case for this framework. 9 | 10 | --- 11 | 12 | To run the sample, simply enter the following command in the terminal: 13 | 14 | ```bash 15 | kobweb run 16 | ``` 17 | 18 | and open [http://localhost:8080](http://localhost:8080) with your browser to see the result. -------------------------------------------------------------------------------- /examples/jb/counter/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.caching=true 3 | org.gradle.configuration-cache=true -------------------------------------------------------------------------------- /examples/jb/counter/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | jetbrains-compose = "1.8.0" 3 | kobweb = "0.23.3" 4 | kotlin = "2.2.20" 5 | 6 | [libraries] 7 | compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "jetbrains-compose" } 8 | compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrains-compose" } 9 | kobweb-core = { module = "com.varabyte.kobweb:kobweb-core", version.ref = "kobweb" } 10 | 11 | [plugins] 12 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 13 | kobweb-application = { id = "com.varabyte.kobweb.application", version.ref = "kobweb" } 14 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 15 | -------------------------------------------------------------------------------- /examples/jb/counter/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/jb/counter/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/jb/counter/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /examples/jb/counter/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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /examples/jb/counter/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | dependencyResolutionManagement { 8 | repositories { 9 | mavenCentral() 10 | google() 11 | } 12 | } 13 | 14 | // The following block registers dependencies to enable Kobweb snapshot support. It is safe to delete or comment out 15 | // this block if you never plan to use them. 16 | gradle.settingsEvaluated { 17 | fun RepositoryHandler.kobwebSnapshots() { 18 | maven("https://central.sonatype.com/repository/maven-snapshots/") { 19 | mavenContent { 20 | includeGroupByRegex("com\\.varabyte\\.kobweb.*") 21 | snapshotsOnly() 22 | } 23 | } 24 | } 25 | 26 | pluginManagement.repositories { kobwebSnapshots() } 27 | dependencyResolutionManagement.repositories { kobwebSnapshots() } 28 | } 29 | 30 | rootProject.name = "counter" 31 | 32 | include(":site") 33 | -------------------------------------------------------------------------------- /examples/jb/counter/site/.gitignore: -------------------------------------------------------------------------------- 1 | # Kobweb ignores 2 | .kobweb/* 3 | !.kobweb/conf.yaml 4 | -------------------------------------------------------------------------------- /examples/jb/counter/site/.kobweb/conf.yaml: -------------------------------------------------------------------------------- 1 | site: 2 | title: "Counter" 3 | 4 | server: 5 | files: 6 | dev: 7 | contentRoot: "build/processedResources/js/main/public" 8 | script: "build/kotlin-webpack/js/developmentExecutable/counter.js" 9 | prod: 10 | script: "build/kotlin-webpack/js/productionExecutable/counter.js" 11 | siteRoot: ".kobweb/site" 12 | 13 | port: 8080 14 | -------------------------------------------------------------------------------- /examples/jb/counter/site/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.application.util.configAsKobwebApplication 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.compose.compiler) 6 | alias(libs.plugins.kobweb.application) 7 | } 8 | 9 | group = "counter" 10 | version = "1.0-SNAPSHOT" 11 | 12 | kotlin { 13 | configAsKobwebApplication() 14 | 15 | sourceSets { 16 | jsMain.dependencies { 17 | implementation(libs.compose.runtime) 18 | implementation(libs.compose.html.core) 19 | implementation(libs.kobweb.core) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/jb/counter/site/src/jsMain/kotlin/counter/AppEntry.kt: -------------------------------------------------------------------------------- 1 | package counter 2 | 3 | import androidx.compose.runtime.* 4 | import com.varabyte.kobweb.core.App 5 | import com.varabyte.kobweb.core.KobwebApp 6 | 7 | @App 8 | @Composable 9 | fun AppEntry(content: @Composable () -> Unit) { 10 | KobwebApp { 11 | content() 12 | } 13 | } -------------------------------------------------------------------------------- /examples/jb/counter/site/src/jsMain/kotlin/counter/pages/Index.kt: -------------------------------------------------------------------------------- 1 | package counter.pages 2 | 3 | import androidx.compose.runtime.* 4 | import com.varabyte.kobweb.core.Page 5 | import org.jetbrains.compose.web.css.padding 6 | import org.jetbrains.compose.web.css.px 7 | import org.jetbrains.compose.web.dom.Button 8 | import org.jetbrains.compose.web.dom.Div 9 | import org.jetbrains.compose.web.dom.Span 10 | import org.jetbrains.compose.web.dom.Text 11 | 12 | // See also: https://github.com/JetBrains/compose-jb/tree/master/tutorials/Web/Getting_Started 13 | @Page 14 | @Composable 15 | fun MainPage() { 16 | var count by remember { mutableStateOf(0) } 17 | Div({ style { padding(25.px) } }) { 18 | Button(attrs = { 19 | onClick { count -= 1 } 20 | }) { 21 | Text("-") 22 | } 23 | 24 | Span({ style { padding(15.px) } }) { 25 | Text("$count") 26 | } 27 | 28 | Button(attrs = { 29 | onClick { count += 1 } 30 | }) { 31 | Text("+") 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /examples/jb/counter/site/src/jsMain/resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/jb/counter/site/src/jsMain/resources/public/favicon.ico -------------------------------------------------------------------------------- /examples/opengl/.gitignore: -------------------------------------------------------------------------------- 1 | # General ignores 2 | .DS_Store 3 | build 4 | out 5 | kotlin-js-store 6 | 7 | # IntelliJ ignores 8 | *.iml 9 | /*.ipr 10 | 11 | /.idea 12 | 13 | # Gradle ignores 14 | .gradle 15 | 16 | # Kotlin ignores 17 | .kotlin 18 | -------------------------------------------------------------------------------- /examples/opengl/.kobweb-template.yaml: -------------------------------------------------------------------------------- 1 | metadata: 2 | description: "An OpenGL demo based on the Mozilla textured cube tutorial" 3 | 4 | # No need for any instructions - we just copy the demo as is 5 | 6 | -------------------------------------------------------------------------------- /examples/opengl/README.md: -------------------------------------------------------------------------------- 1 | This is a [Kobweb](https://github.com/varabyte/kobweb) project instantiated from the `examples/opengl` template. 2 | 3 | The purpose of this project is to demonstrate the `CanvasGl` composable to drive 3D, OpenGL content. 4 | 5 | This sample was built starting from the tutorials at 6 | https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/ 7 | 8 | In addition to converting the tutorial's JavaScript to Kotlin, the other two interesting bits was the creation of 9 | bindings into a JavaScript library it uses for dealing with matrices called 10 | [glMatrix](https://github.com/toji/gl-matrix). 11 | 12 | To support this, I both created the file `src/jsMain/kotlin/opengl/bindings/glmatrix.kt` and also added a link to the 13 | library via an html script tag in this project's generated `index.html` file. This is done in the `build.gradle.kts` 14 | file: 15 | 16 | ```kotlin 17 | /*...*/ 18 | 19 | kobweb.index.head.add { 20 | script { 21 | src = "https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/3.4.2/gl-matrix-min.js" 22 | } 23 | } 24 | 25 | /*...*/ 26 | ``` 27 | 28 | which generates html that looks like: 29 | 30 | ```html 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | ``` 39 | 40 | --- 41 | 42 | To run the sample, simply enter the following command in the terminal: 43 | 44 | ```bash 45 | kobweb run 46 | ``` 47 | 48 | and open [http://localhost:8080](http://localhost:8080) with your browser to see the result. -------------------------------------------------------------------------------- /examples/opengl/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.caching=true 3 | org.gradle.configuration-cache=true -------------------------------------------------------------------------------- /examples/opengl/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | jetbrains-compose = "1.8.0" 3 | kobweb = "0.23.3" 4 | kotlin = "2.2.20" 5 | 6 | [libraries] 7 | compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "jetbrains-compose" } 8 | compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrains-compose" } 9 | kobweb-core = { module = "com.varabyte.kobweb:kobweb-core", version.ref = "kobweb" } 10 | kobweb-silk = { module = "com.varabyte.kobweb:kobweb-silk", version.ref = "kobweb" } 11 | silk-icons-fa = { module = "com.varabyte.kobwebx:silk-icons-fa", version.ref = "kobweb" } 12 | 13 | [plugins] 14 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 15 | kobweb-application = { id = "com.varabyte.kobweb.application", version.ref = "kobweb" } 16 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 17 | -------------------------------------------------------------------------------- /examples/opengl/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/opengl/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/opengl/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /examples/opengl/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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /examples/opengl/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | dependencyResolutionManagement { 8 | repositories { 9 | mavenCentral() 10 | google() 11 | } 12 | } 13 | 14 | // The following block registers dependencies to enable Kobweb snapshot support. It is safe to delete or comment out 15 | // this block if you never plan to use them. 16 | gradle.settingsEvaluated { 17 | fun RepositoryHandler.kobwebSnapshots() { 18 | maven("https://central.sonatype.com/repository/maven-snapshots/") { 19 | mavenContent { 20 | includeGroupByRegex("com\\.varabyte\\.kobweb.*") 21 | snapshotsOnly() 22 | } 23 | } 24 | } 25 | 26 | pluginManagement.repositories { kobwebSnapshots() } 27 | dependencyResolutionManagement.repositories { kobwebSnapshots() } 28 | } 29 | 30 | rootProject.name = "opengl" 31 | 32 | include(":site") 33 | -------------------------------------------------------------------------------- /examples/opengl/site/.gitignore: -------------------------------------------------------------------------------- 1 | # Kobweb ignores 2 | .kobweb/* 3 | !.kobweb/conf.yaml 4 | -------------------------------------------------------------------------------- /examples/opengl/site/.kobweb/conf.yaml: -------------------------------------------------------------------------------- 1 | site: 2 | title: "OpenGL" 3 | 4 | server: 5 | files: 6 | dev: 7 | contentRoot: "build/processedResources/js/main/public" 8 | script: "build/kotlin-webpack/js/developmentExecutable/opengl.js" 9 | api: "build/libs/opengl.jar" 10 | prod: 11 | script: "build/kotlin-webpack/js/productionExecutable/opengl.js" 12 | siteRoot: ".kobweb/site" 13 | 14 | port: 8080 15 | -------------------------------------------------------------------------------- /examples/opengl/site/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.application.extensions.index 2 | import com.varabyte.kobweb.gradle.application.util.configAsKobwebApplication 3 | import kotlinx.html.script 4 | 5 | plugins { 6 | alias(libs.plugins.kotlin.multiplatform) 7 | alias(libs.plugins.compose.compiler) 8 | alias(libs.plugins.kobweb.application) 9 | } 10 | 11 | group = "opengl" 12 | version = "1.0-SNAPSHOT" 13 | 14 | kobweb { 15 | app { 16 | index { 17 | head.add { 18 | script { 19 | src = "https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/3.4.2/gl-matrix-min.js" 20 | } 21 | } 22 | } 23 | } 24 | } 25 | 26 | kotlin { 27 | configAsKobwebApplication() 28 | 29 | sourceSets { 30 | jsMain.dependencies { 31 | implementation(libs.compose.runtime) 32 | implementation(libs.compose.html.core) 33 | implementation(libs.kobweb.core) 34 | implementation(libs.kobweb.silk) 35 | implementation(libs.silk.icons.fa) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/opengl/site/src/jsMain/kotlin/opengl/AppEntry.kt: -------------------------------------------------------------------------------- 1 | package opengl 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.varabyte.kobweb.compose.ui.Modifier 5 | import com.varabyte.kobweb.compose.ui.modifiers.* 6 | import com.varabyte.kobweb.core.App 7 | import com.varabyte.kobweb.silk.init.InitSilk 8 | import com.varabyte.kobweb.silk.init.InitSilkContext 9 | import com.varabyte.kobweb.silk.SilkApp 10 | import com.varabyte.kobweb.silk.components.layout.Surface 11 | import com.varabyte.kobweb.silk.style.common.SmoothColorStyle 12 | import com.varabyte.kobweb.silk.style.toModifier 13 | import com.varabyte.kobweb.silk.init.registerStyleBase 14 | import org.jetbrains.compose.web.css.vh 15 | import org.jetbrains.compose.web.css.vw 16 | 17 | @InitSilk 18 | fun registerGlobalStyles(ctx: InitSilkContext) = ctx.stylesheet.apply { 19 | registerStyleBase("html, body") { Modifier.fillMaxHeight() } 20 | registerStyleBase("body") { 21 | Modifier 22 | .fontFamily( 23 | "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", 24 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "sans-serif" 25 | ) 26 | } 27 | } 28 | 29 | @App 30 | @Composable 31 | fun AppEntry(content: @Composable () -> Unit) { 32 | SilkApp { 33 | Surface(SmoothColorStyle.toModifier().fillMaxHeight()) { 34 | content() 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/opengl/site/src/jsMain/kotlin/opengl/bindings/glmatrix.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("ClassName") 2 | 3 | package opengl.bindings 4 | 5 | import org.khronos.webgl.Float32Array 6 | 7 | open external class ReadonlyVec3 : Float32Array 8 | open external class ReadonlyMat4 : Float32Array 9 | 10 | fun Array.toReadonlyVec3() = unsafeCast() 11 | 12 | /** 13 | * The minimal bindings needed to get glMatrix to work with this sample project. 14 | */ 15 | external class glMatrix { 16 | // See https://glmatrix.net/docs/module-mat4.html for the full API for this class 17 | class mat4 : ReadonlyMat4 { 18 | companion object { 19 | fun create(): mat4 20 | fun perspective(matrixOut: mat4, fov: Number, aspect: Number, zNear: Number, zFar: Number) 21 | 22 | fun translate(matrixOut: mat4, matrixIn: ReadonlyMat4, translateBy: ReadonlyVec3) 23 | fun rotate(matrixOut: mat4, matrixIn: ReadonlyMat4, rotateRad: Number, rotateAxis: ReadonlyVec3) 24 | 25 | fun invert(matrixOut: mat4, matrixIn: ReadonlyMat4) 26 | fun transpose(matrixOut: mat4, matrixIn: ReadonlyMat4) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /examples/opengl/site/src/jsMain/kotlin/opengl/components/layouts/PageLayout.kt: -------------------------------------------------------------------------------- 1 | package opengl.components.layouts 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.setValue 6 | import com.varabyte.kobweb.compose.css.TextAlign 7 | import com.varabyte.kobweb.compose.css.WhiteSpace 8 | import com.varabyte.kobweb.compose.foundation.layout.* 9 | import com.varabyte.kobweb.compose.ui.Alignment 10 | import com.varabyte.kobweb.compose.ui.Modifier 11 | import com.varabyte.kobweb.compose.ui.modifiers.* 12 | import com.varabyte.kobweb.compose.ui.toAttrs 13 | import com.varabyte.kobweb.core.layout.Layout 14 | import com.varabyte.kobweb.silk.components.forms.Button 15 | import com.varabyte.kobweb.silk.components.icons.fa.FaMoon 16 | import com.varabyte.kobweb.silk.components.icons.fa.FaSun 17 | import com.varabyte.kobweb.silk.components.navigation.Link 18 | import com.varabyte.kobweb.silk.theme.SilkTheme 19 | import com.varabyte.kobweb.silk.theme.colors.ColorMode 20 | import com.varabyte.kobweb.silk.theme.colors.palette.color 21 | import org.jetbrains.compose.web.css.* 22 | import org.jetbrains.compose.web.dom.Span 23 | import org.jetbrains.compose.web.dom.Text 24 | 25 | @Composable 26 | @Layout 27 | fun PageLayout(content: @Composable BoxScope.() -> Unit) { 28 | Column( 29 | Modifier.fillMaxWidth().minHeight(100.vh), 30 | horizontalAlignment = Alignment.CenterHorizontally 31 | ) { 32 | Column(Modifier.fillMaxSize()) { 33 | Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { 34 | var colorMode by ColorMode.currentState 35 | Button( 36 | onClick = { colorMode = colorMode.opposite }, 37 | Modifier.margin(10.px).padding(0.px).borderRadius(50.percent) 38 | ) { 39 | if (colorMode.isLight) FaSun() else FaMoon() 40 | } 41 | } 42 | Box(Modifier.fillMaxSize()) { 43 | content() 44 | } 45 | } 46 | 47 | val borderColor = SilkTheme.palette.color 48 | Spacer() 49 | Box( 50 | Modifier 51 | .fillMaxWidth() 52 | .borderTop(1.px, LineStyle.Solid, borderColor) 53 | .fontSize(1.5.cssRem), 54 | Alignment.Center 55 | ) { 56 | // Use PreWrap to preserve trailing space in text 57 | Span(Modifier.margin(topBottom = 1.cssRem).whiteSpace(WhiteSpace.PreWrap).textAlign(TextAlign.Center).toAttrs()) { 58 | Text("This project is built using ") 59 | Link( 60 | "https://github.com/varabyte/kobweb", 61 | "Kobweb", 62 | ) 63 | Text(", a full-stack Kotlin web framework.") 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/opengl/site/src/jsMain/resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/opengl/site/src/jsMain/resources/public/favicon.ico -------------------------------------------------------------------------------- /examples/opengl/site/src/jsMain/resources/public/images/varabyte-face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/opengl/site/src/jsMain/resources/public/images/varabyte-face.png -------------------------------------------------------------------------------- /examples/todo/.gitignore: -------------------------------------------------------------------------------- 1 | # General ignores 2 | .DS_Store 3 | build 4 | out 5 | kotlin-js-store 6 | 7 | # IntelliJ ignores 8 | *.iml 9 | /*.ipr 10 | 11 | /.idea/caches 12 | /.idea/libraries 13 | /.idea/modules.xml 14 | /.idea/workspace.xml 15 | /.idea/gradle.xml 16 | /.idea/navEditor.xml 17 | /.idea/assetWizardSettings.xml 18 | /.idea/artifacts 19 | /.idea/compiler.xml 20 | /.idea/jarRepositories.xml 21 | /.idea/*.iml 22 | /.idea/modules 23 | /.idea/libraries-with-intellij-classes.xml 24 | 25 | # Gradle ignores 26 | .gradle 27 | 28 | # Kotlin ignores 29 | .kotlin 30 | -------------------------------------------------------------------------------- /examples/todo/.kobweb-template.yaml: -------------------------------------------------------------------------------- 1 | metadata: 2 | description: "An example TODO app, showcasing client / server interactions" 3 | 4 | # No need for any instructions - we just copy the demo as is 5 | 6 | -------------------------------------------------------------------------------- /examples/todo/README.md: -------------------------------------------------------------------------------- 1 | This is a [Kobweb](https://github.com/varabyte/kobweb) project instantiated from the `examples/todo` template. 2 | 3 | The purpose of this project is to showcase a minimal Todo app, demonstrating: 4 | 5 | * a simple, reactive, single-page web app, making use of both Silk UI and Compose for Web 6 | * API endpoints (e.g. for adding, removing, and fetching items) 7 | * how to share types across client and server (see `TodoItem` which has text and an ID value) 8 | 9 | I'd like to give credit to https://blog.upstash.com/nextjs-todo for sharing the Next.js version. 10 | 11 | --- 12 | 13 | To run the sample, simply enter the following commands in the terminal: 14 | 15 | ```bash 16 | $ cd site 17 | $ kobweb run 18 | ``` 19 | 20 | and open [http://localhost:8080](http://localhost:8080) with your browser to see the result. 21 | -------------------------------------------------------------------------------- /examples/todo/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.caching=true 3 | org.gradle.configuration-cache=true -------------------------------------------------------------------------------- /examples/todo/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | jetbrains-compose = "1.8.0" 3 | kobweb = "0.23.3" 4 | kotlin = "2.2.20" 5 | kotlinx-serialization = "1.9.0" 6 | 7 | [libraries] 8 | compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "jetbrains-compose" } 9 | compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrains-compose" } 10 | kobweb-api = { module = "com.varabyte.kobweb:kobweb-api", version.ref = "kobweb" } 11 | kobweb-core = { module = "com.varabyte.kobweb:kobweb-core", version.ref = "kobweb" } 12 | kobweb-silk = { module = "com.varabyte.kobweb:kobweb-silk", version.ref = "kobweb" } 13 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } 14 | silk-icons-fa = { module = "com.varabyte.kobwebx:silk-icons-fa", version.ref = "kobweb" } 15 | 16 | [plugins] 17 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 18 | kobweb-application = { id = "com.varabyte.kobweb.application", version.ref = "kobweb" } 19 | kobwebx-markdown = { id = "com.varabyte.kobwebx.markdown", version.ref = "kobweb" } 20 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 21 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 22 | -------------------------------------------------------------------------------- /examples/todo/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/todo/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/todo/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /examples/todo/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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /examples/todo/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | dependencyResolutionManagement { 8 | repositories { 9 | mavenCentral() 10 | google() 11 | } 12 | } 13 | 14 | // The following block registers dependencies to enable Kobweb snapshot support. It is safe to delete or comment out 15 | // this block if you never plan to use them. 16 | gradle.settingsEvaluated { 17 | fun RepositoryHandler.kobwebSnapshots() { 18 | maven("https://central.sonatype.com/repository/maven-snapshots/") { 19 | mavenContent { 20 | includeGroupByRegex("com\\.varabyte\\.kobweb.*") 21 | snapshotsOnly() 22 | } 23 | } 24 | } 25 | 26 | pluginManagement.repositories { kobwebSnapshots() } 27 | dependencyResolutionManagement.repositories { kobwebSnapshots() } 28 | } 29 | 30 | rootProject.name = "todo" 31 | 32 | include(":site") 33 | 34 | -------------------------------------------------------------------------------- /examples/todo/site/.gitignore: -------------------------------------------------------------------------------- 1 | # Kobweb ignores 2 | .kobweb/* 3 | !.kobweb/conf.yaml 4 | 5 | -------------------------------------------------------------------------------- /examples/todo/site/.kobweb/conf.yaml: -------------------------------------------------------------------------------- 1 | site: 2 | title: "Todo" 3 | 4 | server: 5 | files: 6 | dev: 7 | contentRoot: "build/processedResources/js/main/public" 8 | script: "build/kotlin-webpack/js/developmentExecutable/todo.js" 9 | api: "build/libs/todo.jar" 10 | prod: 11 | script: "build/kotlin-webpack/js/productionExecutable/todo.js" 12 | siteRoot: ".kobweb/site" 13 | 14 | port: 8080 15 | -------------------------------------------------------------------------------- /examples/todo/site/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.application.util.configAsKobwebApplication 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.kotlin.serialization) 6 | alias(libs.plugins.compose.compiler) 7 | alias(libs.plugins.kobweb.application) 8 | } 9 | 10 | group = "todo" 11 | version = "1.0-SNAPSHOT" 12 | 13 | kotlin { 14 | configAsKobwebApplication(includeServer = true) 15 | 16 | sourceSets { 17 | commonMain.dependencies { 18 | implementation(libs.kotlinx.serialization.json) 19 | } 20 | 21 | jsMain.dependencies { 22 | implementation(libs.compose.runtime) 23 | implementation(libs.compose.html.core) 24 | implementation(libs.kobweb.core) 25 | implementation(libs.kobweb.silk) 26 | } 27 | 28 | jvmMain.dependencies { 29 | compileOnly(libs.kobweb.api) // Provided by Kobweb backend at runtime 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/todo/site/src/commonMain/kotlin/todo/model/TodoItem.kt: -------------------------------------------------------------------------------- 1 | package todo.model 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | class TodoItem( 7 | val id: String, 8 | val text: String, 9 | ) -------------------------------------------------------------------------------- /examples/todo/site/src/jsMain/kotlin/todo/AppEntry.kt: -------------------------------------------------------------------------------- 1 | package todo 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.varabyte.kobweb.compose.ui.Modifier 5 | import com.varabyte.kobweb.compose.ui.graphics.Color 6 | import com.varabyte.kobweb.compose.ui.modifiers.* 7 | import com.varabyte.kobweb.core.App 8 | import com.varabyte.kobweb.silk.SilkApp 9 | import com.varabyte.kobweb.silk.components.layout.Surface 10 | import com.varabyte.kobweb.silk.init.InitSilk 11 | import com.varabyte.kobweb.silk.init.InitSilkContext 12 | import com.varabyte.kobweb.silk.init.registerStyleBase 13 | import org.jetbrains.compose.web.css.* 14 | 15 | val BORDER_COLOR = Color.rgb(0xea, 0xea, 0xea) 16 | 17 | @InitSilk 18 | fun initSiteStyles(ctx: InitSilkContext) = ctx.stylesheet.apply { 19 | registerStyleBase("html, body") { Modifier.fillMaxHeight() } 20 | registerStyleBase("body") { 21 | Modifier.fontFamily( 22 | "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "Oxygen", "Ubuntu", 23 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "sans-serif" 24 | ) 25 | } 26 | 27 | registerStyleBase("footer") { 28 | Modifier 29 | .width(100.percent) 30 | .height(100.px) 31 | .fontSize(1.5.cssRem) 32 | .borderTop(1.px, LineStyle.Solid, BORDER_COLOR) 33 | .display(DisplayStyle.Flex) 34 | .justifyContent(JustifyContent.Center) 35 | .alignItems(AlignItems.Center) 36 | } 37 | } 38 | 39 | @App 40 | @Composable 41 | fun AppEntry(content: @Composable () -> Unit) { 42 | SilkApp { 43 | Surface(Modifier.fillMaxHeight()) { 44 | content() 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/todo/site/src/jsMain/kotlin/todo/components/widgets/LoadingSpinner.kt: -------------------------------------------------------------------------------- 1 | package todo.components.widgets 2 | 3 | import androidx.compose.runtime.Composable 4 | import org.jetbrains.compose.web.dom.Img 5 | 6 | @Composable 7 | fun LoadingSpinner() = Img("/loader.gif") 8 | -------------------------------------------------------------------------------- /examples/todo/site/src/jsMain/kotlin/todo/components/widgets/TodoCard.kt: -------------------------------------------------------------------------------- 1 | package todo.components.widgets 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.varabyte.kobweb.silk.style.toAttrs 5 | import org.jetbrains.compose.web.dom.A 6 | 7 | @Composable 8 | fun TodoCard(onClick: (() -> Unit)? = null, content: @Composable () -> Unit) { 9 | val styles = mutableListOf(TodoStyle, TodoContainerStyle, TodoTextStyle) 10 | if (onClick != null) { 11 | styles.add(TodoClickableStyle) 12 | } 13 | 14 | // Use "A" so the item supports a11y (you can tab on it and press enter to click it) 15 | A(href = "#", attrs = styles.toAttrs { 16 | tabIndex(0) // Make this item tabbable 17 | 18 | onClick { evt -> 19 | evt.preventDefault() // We are using "A" for its a11y side effects but don't want its actual behavior 20 | onClick?.invoke() 21 | } 22 | }) { 23 | content() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/todo/site/src/jsMain/kotlin/todo/components/widgets/TodoForm.kt: -------------------------------------------------------------------------------- 1 | package todo.components.widgets 2 | 3 | import androidx.compose.runtime.* 4 | import com.varabyte.kobweb.silk.style.toAttrs 5 | import org.jetbrains.compose.web.attributes.InputType 6 | import org.jetbrains.compose.web.attributes.name 7 | import org.jetbrains.compose.web.attributes.onSubmit 8 | import org.jetbrains.compose.web.attributes.placeholder 9 | import org.jetbrains.compose.web.dom.Form 10 | import org.jetbrains.compose.web.dom.Input 11 | 12 | @Composable 13 | fun TodoForm(placeholder: String, loading: Boolean, submitTodo: (String) -> Unit) { 14 | if (loading) { 15 | TodoCard { 16 | LoadingSpinner() 17 | } 18 | } else { 19 | var todo by remember { mutableStateOf("") } 20 | Form(attrs = listOf(TodoStyle, TodoContainerStyle).toAttrs { 21 | onSubmit { evt -> 22 | evt.preventDefault() 23 | submitTodo(todo) 24 | } 25 | }) { 26 | Input( 27 | InputType.Text, 28 | attrs = listOf(TodoStyle, TodoTextStyle, TodoInputStyle) 29 | .toAttrs { 30 | placeholder(placeholder) 31 | name("todo") 32 | onChange { todo = it.value } 33 | } 34 | ) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /examples/todo/site/src/jsMain/kotlin/todo/components/widgets/TodoStyles.kt: -------------------------------------------------------------------------------- 1 | package todo.components.widgets 2 | 3 | import com.varabyte.kobweb.compose.css.* 4 | import com.varabyte.kobweb.compose.css.AlignItems 5 | import com.varabyte.kobweb.compose.css.Transition 6 | import com.varabyte.kobweb.compose.ui.Modifier 7 | import com.varabyte.kobweb.compose.ui.graphics.Color 8 | import com.varabyte.kobweb.compose.ui.graphics.Colors 9 | import com.varabyte.kobweb.compose.ui.modifiers.* 10 | import com.varabyte.kobweb.silk.style.CssStyle 11 | import com.varabyte.kobweb.silk.style.base 12 | import com.varabyte.kobweb.silk.style.selectors.hover 13 | import com.varabyte.kobweb.silk.style.selectors.placeholderShown 14 | import org.jetbrains.compose.web.css.* 15 | import todo.BORDER_COLOR 16 | 17 | private val INTERACT_COLOR = Color.rgb(0x00, 0x70, 0xf3) 18 | 19 | /** Common styles for all todo widgets */ 20 | val TodoStyle = CssStyle.base { 21 | Modifier 22 | .width(85.percent) 23 | .height(5.cssRem) 24 | .border(1.px, LineStyle.Solid, BORDER_COLOR) 25 | .borderRadius(10.px) 26 | .transition(Transition.group(listOf("color", "border-color"), 0.15.s, TransitionTimingFunction.Ease)) 27 | .textDecorationLine(TextDecorationLine.None) 28 | } 29 | 30 | /** Styles for the bordered, outer container (the form component has an inner and outer layer) */ 31 | val TodoContainerStyle = CssStyle.base { 32 | Modifier 33 | .margin(0.5.cssRem) 34 | .border(1.px, LineStyle.Solid, BORDER_COLOR) 35 | .display(DisplayStyle.Flex) 36 | .textAlign(TextAlign.Left) 37 | .alignItems(AlignItems.Center) 38 | } 39 | 40 | /** Styles for the text parts of todo widgets */ 41 | val TodoTextStyle = CssStyle.base { 42 | Modifier 43 | .padding(1.5.cssRem) 44 | .fontSize(1.25.cssRem) 45 | // We use "A" tags for accessibility, but we want our colors to come from our container 46 | .color(Color("inherit")) 47 | } 48 | 49 | /** Styles for the input element which handles user input */ 50 | val TodoInputStyle = CssStyle { 51 | base { 52 | Modifier 53 | .fillMaxWidth() 54 | .backgroundColor(Colors.Transparent) 55 | .border(0.px) 56 | } 57 | 58 | placeholderShown { 59 | Modifier.fontStyle(FontStyle.Italic) 60 | } 61 | } 62 | 63 | /** Styles for mouse interaction with todo widgets */ 64 | val TodoClickableStyle = CssStyle { 65 | hover { 66 | Modifier 67 | .color(INTERACT_COLOR) 68 | .cursor(Cursor.Pointer) 69 | .border { color(INTERACT_COLOR) } 70 | .textDecorationLine(TextDecorationLine.LineThrough) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /examples/todo/site/src/jsMain/resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/todo/site/src/jsMain/resources/public/favicon.ico -------------------------------------------------------------------------------- /examples/todo/site/src/jsMain/resources/public/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/examples/todo/site/src/jsMain/resources/public/loader.gif -------------------------------------------------------------------------------- /examples/todo/site/src/jvmMain/kotlin/todo/api/Add.kt: -------------------------------------------------------------------------------- 1 | package todo.api 2 | 3 | import com.varabyte.kobweb.api.Api 4 | import com.varabyte.kobweb.api.ApiContext 5 | import com.varabyte.kobweb.api.data.getValue 6 | import com.varabyte.kobweb.api.http.HttpMethod 7 | import todo.model.TodoStore 8 | 9 | @Api 10 | fun addTodo(ctx: ApiContext) { 11 | if (ctx.req.method != HttpMethod.POST) return 12 | 13 | val ownerId = ctx.req.params["owner"] 14 | val todo = ctx.req.params["todo"] 15 | if (ownerId == null || todo == null) { 16 | return 17 | } 18 | 19 | ctx.data.getValue().add(ownerId, todo) 20 | ctx.res.status = 200 21 | } -------------------------------------------------------------------------------- /examples/todo/site/src/jvmMain/kotlin/todo/api/Id.kt: -------------------------------------------------------------------------------- 1 | package todo.api 2 | 3 | import com.varabyte.kobweb.api.Api 4 | import com.varabyte.kobweb.api.ApiContext 5 | import com.varabyte.kobweb.api.http.HttpMethod 6 | import com.varabyte.kobweb.api.http.setBodyText 7 | import java.util.* 8 | 9 | /** 10 | * Instead of generating an ID on the client (where there's no UUID library by default), we just ask the server to do 11 | * it. It's doing it anyways for its own stuff! 12 | */ 13 | @Api 14 | fun generateId(ctx: ApiContext) { 15 | if (ctx.req.method != HttpMethod.GET) return 16 | ctx.res.setBodyText(UUID.randomUUID().toString()) 17 | } -------------------------------------------------------------------------------- /examples/todo/site/src/jvmMain/kotlin/todo/api/List.kt: -------------------------------------------------------------------------------- 1 | package todo.api 2 | 3 | import com.varabyte.kobweb.api.Api 4 | import com.varabyte.kobweb.api.ApiContext 5 | import com.varabyte.kobweb.api.data.getValue 6 | import com.varabyte.kobweb.api.http.HttpMethod 7 | import com.varabyte.kobweb.api.http.setBodyText 8 | import kotlinx.serialization.encodeToString 9 | import kotlinx.serialization.json.Json 10 | import todo.model.TodoStore 11 | 12 | @Api 13 | fun listTodos(ctx: ApiContext) { 14 | if (ctx.req.method != HttpMethod.GET) return 15 | val ownerId = ctx.req.params["owner"] ?: return 16 | 17 | val todos = ctx.data.getValue() 18 | ctx.res.setBodyText(Json.encodeToString(todos[ownerId])) 19 | } 20 | -------------------------------------------------------------------------------- /examples/todo/site/src/jvmMain/kotlin/todo/api/Remove.kt: -------------------------------------------------------------------------------- 1 | package todo.api 2 | 3 | import com.varabyte.kobweb.api.Api 4 | import com.varabyte.kobweb.api.ApiContext 5 | import com.varabyte.kobweb.api.data.getValue 6 | import com.varabyte.kobweb.api.http.HttpMethod 7 | import todo.model.TodoStore 8 | 9 | @Api 10 | fun removeTodo(ctx: ApiContext) { 11 | if (ctx.req.method != HttpMethod.POST) return 12 | 13 | val ownerId = ctx.req.params["owner"] 14 | val todoId = ctx.req.params["todo"] 15 | if (ownerId == null || todoId == null) { 16 | return 17 | } 18 | 19 | ctx.data.getValue().remove(ownerId, todoId) 20 | ctx.res.status = 200 21 | } -------------------------------------------------------------------------------- /examples/todo/site/src/jvmMain/kotlin/todo/model/TodoStore.kt: -------------------------------------------------------------------------------- 1 | package todo.model 2 | 3 | import com.varabyte.kobweb.api.data.add 4 | import com.varabyte.kobweb.api.init.InitApi 5 | import com.varabyte.kobweb.api.init.InitApiContext 6 | import java.util.* 7 | import java.util.concurrent.locks.ReentrantLock 8 | import kotlin.concurrent.withLock 9 | 10 | // NOTE: This is a simple demo, so we create an in-memory store (which will get reset everytime server code is live 11 | // reloaded). However, in a production app, you should use a real database instead, maybe a redis backend, or possibly 12 | // even something that lives on a different server which you access via network calls. 13 | 14 | @InitApi 15 | fun initTodoStore(ctx: InitApiContext) { 16 | ctx.data.add(TodoStore()) 17 | } 18 | 19 | class TodoStore { 20 | private val lock = ReentrantLock() 21 | private val todos = mutableMapOf>() 22 | 23 | fun add(ownerId: String, todo: String) { 24 | lock.withLock { 25 | todos.computeIfAbsent(ownerId) { mutableListOf() }.add(TodoItem(UUID.randomUUID().toString(), todo)) 26 | } 27 | } 28 | 29 | fun remove(ownerId: String, id: String) { 30 | lock.withLock { todos[ownerId]?.removeIf { it.id == id } } 31 | } 32 | 33 | operator fun get(ownerId: String): List = lock.withLock { todos[ownerId] } ?: emptyList() 34 | } -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | # General ignores 2 | .DS_Store 3 | build 4 | out 5 | kotlin-js-store 6 | 7 | # IntelliJ ignores 8 | *.iml 9 | /*.ipr 10 | 11 | /.idea/caches 12 | /.idea/libraries 13 | /.idea/modules.xml 14 | /.idea/workspace.xml 15 | /.idea/gradle.xml 16 | /.idea/navEditor.xml 17 | /.idea/assetWizardSettings.xml 18 | /.idea/artifacts 19 | /.idea/compiler.xml 20 | /.idea/jarRepositories.xml 21 | /.idea/*.iml 22 | /.idea/modules 23 | /.idea/libraries-with-intellij-classes.xml 24 | 25 | # Gradle ignores 26 | .gradle 27 | 28 | # Kotlin ignores 29 | .kotlin 30 | -------------------------------------------------------------------------------- /library/.kobweb-template.yaml: -------------------------------------------------------------------------------- 1 | metadata: 2 | description: "A template for creating a Kobweb library skeleton" 3 | shouldHighlight: true 4 | 5 | instructions: 6 | # Note: Kobweb provides a "projectFolder" variable for us already 7 | 8 | - ! 9 | name: "projectName" 10 | value: "${fileToPackage(projectFolder)}" 11 | - ! 12 | message: "Note: The group ID should uniquely identify your project and organization." 13 | - ! 14 | message: "'io.github.(username).(projectname)' can work for a hobby project." 15 | - ! 16 | name: "groupId" 17 | prompt: "What is the group ID for your project?" 18 | default: "org.example.${projectName}" 19 | validation: "isPackage" 20 | # Overwrite projectName, since the user's group ID should be a more accurate source 21 | # of what the user wants their project name to be than the initial folder. 22 | # Note: the "ensure_starts_with" is kind of a hack, to prevent a crash if someone 23 | # didn't put ANY .'s in their group. 24 | - ! 25 | name: "projectName" 26 | value: "${groupId?ensure_starts_with(\".\")?keep_after_last(\".\")}" 27 | # Copy "groupId" as "package" -- it reads better in some cases. 28 | - ! 29 | name: "package" 30 | value: "${groupId}" 31 | 32 | - ! 33 | message: "To learn more: https://kobweb.varabyte.com/docs/concepts/presentation/silk" 34 | - ! 35 | name: "useSilk" 36 | prompt: "Would you like to use Silk, Kobweb's powerful UI layer built on top of Compose for Web?" 37 | default: "yes" 38 | validation: "isYesNo" 39 | transform: "${yesNoToBool(value)}" 40 | 41 | - ! 42 | message: "To learn more: https://kobweb.varabyte.com/docs/concepts/server/fullstack" 43 | - ! 44 | name: "useServer" 45 | prompt: "Would you like to include support for defining backend logic, such as API routes and API streams?" 46 | default: "no" 47 | validation: "isYesNo" 48 | transform: "${yesNoToBool(value)}" 49 | 50 | - ! 51 | message: "To learn more: https://kobweb.varabyte.com/docs/concepts/foundation/markdown" 52 | - ! 53 | name: "useMarkdown" 54 | prompt: "Would you like to include support for Markdown files?" 55 | default: "no" 56 | validation: "isYesNo" 57 | transform: "${yesNoToBool(value)}" 58 | 59 | - ! 60 | 61 | - ! 62 | name: "packagePath" 63 | value: "${packageToPath(package)}" 64 | - ! 65 | from: "sitelib/build.gradle.kts" 66 | to: "${projectName}" 67 | description: "Copying build script" 68 | - ! 69 | from: "sitelib/src/sitelib/*" 70 | to: "${projectName}/src/jsMain/kotlin/${packagePath}" 71 | description: "Rearranging frontend source to conform to the user's package" 72 | - ! 73 | condition: "${useServer}" 74 | from: "sitelib/src/api/*" 75 | to: "${projectName}/src/jvmMain/kotlin/${packagePath}/api" 76 | description: "Rearranging backend source to conform to the user's package" 77 | - ! 78 | files: "sitelib/src/api/*" 79 | description: "Removing unused server files (if any)" 80 | - ! 81 | from: "sitelib/resources/*" 82 | to: "${projectName}/src/jsMain/resources" 83 | description: "Rearranging resources" 84 | 85 | -------------------------------------------------------------------------------- /library/README.md: -------------------------------------------------------------------------------- 1 | This is a [Kobweb](https://github.com/varabyte/kobweb) project bootstrapped with the `library` template. 2 | 3 | This template is useful if you want to create a re-usable library that can be consumed by other Kobweb projects. The 4 | biggest difference between a Kobweb library and a Kobweb application is that the library applies the 5 | `com.varabyte.kobweb.library` Gradle plugin instead in its build script. 6 | 7 | A very easy way to share your Kobweb library with the world is to use [JitPack](https://jitpack.io/). You can read more 8 | about that approach on their site, but essentially, your steps will be: 9 | 10 | * Edit your library's build script, adding the `maven-publish` plugin. 11 | * Make sure the build script group and versions are set to what you want (or, optionally, configure a publishing block). 12 | * Double check that this is working by running `publishToMavenLocal` from the command line. 13 | * Commit your changes and push them to GitHub. 14 | * In a different project, which will consume your library, add the `jitpack.io` repository and then add a dependency 15 | to your library's group and artifact: 16 | ```kotlin 17 | repositories { 18 | maven(url = "https://jitpack.io") 19 | } 20 | dependencies { 21 | implementation("group.path.here:project-name-here:") 22 | } 23 | ``` 24 | 25 | For a concrete example, you can refer to 26 | this [Kotlin Boostrap library build script](https://github.com/stevdza-san/KotlinBootstrap/blob/master/bootstrap/build.gradle.kts) 27 | which results in the following [JitPack artifact entry](https://jitpack.io/#stevdza-san/KotlinBootstrap). 28 | 29 | This above project opts to provide its own publishing block for more control over the artifact name and version, but if 30 | you omit it, it will try to use reasonable defaults from your project's build script settings instead. The group and 31 | version will come directly from the build script values themselves, and the artifact name will be the name of the 32 | project (usually the folder name, but whatever you set in `settings.gradle.kts`). 33 | -------------------------------------------------------------------------------- /library/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.caching=true 3 | org.gradle.configuration-cache=true -------------------------------------------------------------------------------- /library/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | jetbrains-compose = "1.8.0" 3 | kobweb = "0.23.3" 4 | kotlin = "2.2.20" 5 | 6 | [libraries] 7 | compose-html-core = { module = "org.jetbrains.compose.html:html-core", version.ref = "jetbrains-compose" } 8 | compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrains-compose" } 9 | kobweb-api = { module = "com.varabyte.kobweb:kobweb-api", version.ref = "kobweb" } 10 | kobweb-core = { module = "com.varabyte.kobweb:kobweb-core ", version.ref = "kobweb" } 11 | kobweb-silk = { module = "com.varabyte.kobweb:kobweb-silk", version.ref = "kobweb" } 12 | kobweb-worker = { module = "com.varabyte.kobweb:kobweb-worker", version.ref = "kobweb" } 13 | kobwebx-markdown = { module = "com.varabyte.kobwebx:kobwebx-markdown", version.ref = "kobweb" } 14 | kobwebx-serialization-kotlinx = { module = "com.varabyte.kobwebx:kobwebx-serialization-kotlinx", version.ref = "kobweb" } 15 | silk-foundation = { module = "com.varabyte.kobweb:silk-foundation", version.ref = "kobweb" } 16 | silk-icons-fa = { module = "com.varabyte.kobwebx:silk-icons-fa", version.ref = "kobweb" } 17 | silk-icons-mdi = { module = "com.varabyte.kobwebx:silk-icons-mdi", version.ref = "kobweb" } 18 | 19 | [plugins] 20 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } 21 | kobweb-application = { id = "com.varabyte.kobweb.application", version.ref = "kobweb" } 22 | kobweb-library = { id = "com.varabyte.kobweb.library", version.ref = "kobweb" } 23 | kobweb-worker = { id = "com.varabyte.kobweb.worker", version.ref = "kobweb" } 24 | kobwebx-markdown = { id = "com.varabyte.kobwebx.markdown", version.ref = "kobweb" } 25 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 26 | -------------------------------------------------------------------------------- /library/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/varabyte/kobweb-templates/45dd60aea66afede567588497cec622e9d602721/library/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /library/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /library/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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /library/settings.gradle.kts.ftl: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | dependencyResolutionManagement { 8 | repositories { 9 | mavenCentral() 10 | google() 11 | } 12 | } 13 | 14 | // The following block registers dependencies to enable Kobweb snapshot support. It is safe to delete or comment out 15 | // this block if you never plan to use them. 16 | gradle.settingsEvaluated { 17 | fun RepositoryHandler.kobwebSnapshots() { 18 | maven("https://central.sonatype.com/repository/maven-snapshots/") { 19 | mavenContent { 20 | includeGroupByRegex("com\\.varabyte\\.kobweb.*") 21 | snapshotsOnly() 22 | } 23 | } 24 | } 25 | 26 | pluginManagement.repositories { kobwebSnapshots() } 27 | dependencyResolutionManagement.repositories { kobwebSnapshots() } 28 | } 29 | 30 | rootProject.name = "${projectName}" 31 | 32 | include(":${projectName}") 33 | -------------------------------------------------------------------------------- /library/sitelib/build.gradle.kts.ftl: -------------------------------------------------------------------------------- 1 | import com.varabyte.kobweb.gradle.library.util.configAsKobwebLibrary 2 | 3 | plugins { 4 | alias(libs.plugins.kotlin.multiplatform) 5 | alias(libs.plugins.compose.compiler) 6 | alias(libs.plugins.kobweb.library) 7 | <#if !useMarkdown?boolean>// alias(libs.plugins.kobwebx.markdown) 8 | } 9 | 10 | group = "${groupId}" 11 | version = "1.0-SNAPSHOT" 12 | 13 | kotlin { 14 | configAsKobwebLibrary(<#if useServer?boolean>includeServer = true) 15 | 16 | sourceSets { 17 | <#if useServer?boolean> 18 | // commonMain.dependencies { 19 | // // Add shared dependencies between JS and JVM here 20 | // } 21 | 22 | jsMain.dependencies { 23 | implementation(libs.compose.runtime) 24 | implementation(libs.compose.html.core) 25 | implementation(libs.kobweb.core) 26 | <#if !useSilk?boolean>// implementation(libs.kobweb.silk) 27 | <#if !useMarkdown?boolean>// implementation(libs.kobwebx.markdown) 28 | } 29 | <#if useServer?boolean> 30 | jvmMain.dependencies { 31 | compileOnly(libs.kobweb.api) // Provided by Kobweb backend at runtime 32 | } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /library/sitelib/resources/README.md: -------------------------------------------------------------------------------- 1 | Define markdown pages under a markdown/ folder, and 2 | they will get picked up and converted to html. 3 | 4 | You must have enabled markdown on this project for this 5 | feature to work. 6 | 7 | See also: https://kobweb.varabyte.com/docs/concepts/foundation/markdown 8 | -------------------------------------------------------------------------------- /library/sitelib/src/api/README.md: -------------------------------------------------------------------------------- 1 | Define API routes in here. 2 | 3 | See also: https://kobweb.varabyte.com/docs/concepts/server/fullstack#define-api-routes 4 | -------------------------------------------------------------------------------- /library/sitelib/src/sitelib/components/README.md: -------------------------------------------------------------------------------- 1 | Define layouts, sections, and widgets in here. 2 | 3 | See also: https://kobweb.varabyte.com/docs/concepts/foundation/project-structure#components-and-pages 4 | -------------------------------------------------------------------------------- /library/sitelib/src/sitelib/pages/README.md: -------------------------------------------------------------------------------- 1 | Define pages in here. 2 | 3 | See also: https://kobweb.varabyte.com/docs/concepts/foundation/routing#page 4 | --------------------------------------------------------------------------------