better homepage samples

This commit is contained in:
Sergey Chernov 2026-01-18 19:09:37 +03:00
parent d0230c5b89
commit 338dc00573
4 changed files with 213 additions and 57 deletions

View File

@ -1,7 +1,6 @@
#!/bin/env lyng #!/bin/env lyng
import lyng.io.fs import lyng.io.fs
import lyng.stdlib
val files = Path("../..").list().toList() val files = Path("../..").list().toList()
// most long is longest? // most long is longest?

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -118,7 +118,7 @@ fun App() {
Div({ classes("col-12", if (isDocsRoute) "col-lg-9" else "col-lg-12") }) { Div({ classes("col-12", if (isDocsRoute) "col-lg-9" else "col-lg-12") }) {
when { when {
route.isBlank() -> HomePage() route.isBlank() -> HomePage()
route == "tryling" -> TryLyngPage() route.startsWith("tryling") -> TryLyngPage(route)
route.startsWith("search") -> SearchPage(route) route.startsWith("search") -> SearchPage(route)
!isDocsRoute -> ReferencePage() !isDocsRoute -> ReferencePage()
else -> DocsPage( else -> DocsPage(

View File

@ -15,7 +15,9 @@
* *
*/ */
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import kotlinx.browser.window
import kotlinx.coroutines.delay
import net.sergeych.lyngweb.ensureBootstrapCodeBlocks import net.sergeych.lyngweb.ensureBootstrapCodeBlocks
import net.sergeych.lyngweb.highlightLyngHtml import net.sergeych.lyngweb.highlightLyngHtml
import net.sergeych.lyngweb.htmlEscape import net.sergeych.lyngweb.htmlEscape
@ -23,6 +25,141 @@ import org.jetbrains.compose.web.dom.*
@Composable @Composable
fun HomePage() { fun HomePage() {
val samples = remember {
listOf(
"""
// Everything is an expression
val x = 10
val status = if (x > 0) "Positive" else "Zero or Negative"
// Even loops return values!
val result = for (i in 1..5) {
if (i == 3) break "Found 3!"
} else "Not found"
println("Result: " + result)
""".trimIndent(),
"""
// Functional power with ranges and collections
val squares = (1..10)
.filter { it % 2 == 0 }
.map { it * it }
println("Even squares: " + squares)
// Output: [4, 16, 36, 64, 100]
""".trimIndent(),
"""
// Flexible map literals and shorthands
val id = 101
val name = "Lyng"
val base = { id:, name: } // Shorthand for id: id, name: name
val full = { ...base, version: "1.0", status: "active" }
println(full)
""".trimIndent(),
"""
// Modern null safety
var config = null
config ?= { timeout: 30 } // Assign only if null
val timeout = config?.timeout ?? 60
println("Timeout is: " + timeout)
""".trimIndent(),
"""
// Destructuring with splat operator
val [first, middle..., last] = [1, 2, 3, 4, 5, 6]
println("First: " + first)
println("Middle: " + middle)
println("Last: " + last)
""".trimIndent(),
"""
// Diamond-safe Multiple Inheritance (C3 MRO)
interface Logger {
fun log(m) = println("[LOG] " + m)
}
interface Auth {
fun login(u) = println("Login: " + u)
}
class App() : Logger, Auth {
fun run() {
log("Starting...")
login("admin")
}
}
App().run()
""".trimIndent(),
"""
// Extension functions and properties
fun String.shout() = this.toUpperCase() + "!!!"
println("hello".shout())
val List.second get = this[1]
println([10, 20, 30].second)
""".trimIndent(),
"""
// Non-local returns from closures
fun findFirst(list, predicate) {
list.forEach {
if (predicate(it)) return@findFirst it
}
null
}
val found = findFirst([1, 5, 8, 12]) { it > 10 }
println("Found: " + found)
""".trimIndent(),
"""
// Easy operator overloading
class Vector(val x, val y) {
override fun plus(other) = Vector(x + other.x, y + other.y)
override fun toString() = "Vector(%g, %g)"(x, y)
}
val v1 = Vector(1, 2)
val v2 = Vector(3, 4)
println(v1 + v2)
""".trimIndent(),
"""
// Property delegation to Map
class User() {
var name by Map()
}
val u = User()
u.name = "Sergey"
println("User name: " + u.name)
""".trimIndent(),
"""
// Implicit coroutines: parallelism without ceremony
import lyng.time
val d1 = launch {
delay(100.milliseconds)
"Task A finished"
}
val d2 = launch {
delay(50.milliseconds)
"Task B finished"
}
println(d1.await())
println(d2.await())
""".trimIndent()
)
}
var currentSlide by remember { mutableStateOf((samples.indices).random()) }
LaunchedEffect(Unit) {
ensureSlideshowStyles()
while (true) {
delay(7000)
currentSlide = (currentSlide + 1) % samples.size
}
}
// Hero section // Hero section
Section({ classes("py-4", "py-lg-5") }) { Section({ classes("py-4", "py-lg-5") }) {
Div({ classes("text-center") }) { Div({ classes("text-center") }) {
@ -44,7 +181,7 @@ fun HomePage() {
} }
} }
// CTA buttons // CTA buttons
Div({ classes("d-flex", "justify-content-center", "gap-2", "mb-4") }) { Div({ classes("d-flex", "justify-content-center", "gap-2", "mb-2") }) {
A(attrs = { A(attrs = {
classes("btn", "btn-primary", "btn-lg") classes("btn", "btn-primary", "btn-lg")
attr("href", "#/docs/tutorial.md") attr("href", "#/docs/tutorial.md")
@ -72,48 +209,26 @@ fun HomePage() {
} }
} }
// Code sample // Code sample slideshow
val code = """ Div({
// Create, transform, and verify — the Lyng way classes("markdown-body", "mt-0", "slide-container", "position-relative")
import lyng.stdlib onClick {
window.location.hash = "#tryling?code=" + encodeURIComponent(samples[currentSlide])
fun findFirstPositive(list) {
list.forEach {
if (it > 0) return@findFirstPositive it
} }
null }) {
Small({
classes("position-absolute", "top-0", "end-0", "m-2", "text-muted", "fw-light", "try-hint")
}) {
Text("click to try")
} }
assertEquals(42, findFirstPositive([-1, 42, -5])) key(currentSlide) {
val slideCode = samples[currentSlide]
val data = 1..5 // or [1,2,3,4,5] val mapHtml = "<pre><code>" + htmlEscape(slideCode) + "</code></pre>"
val evens2 = data.filter { it % 2 == 0 }.map { it * it } Div({ classes("slide-animation") }) {
assertEquals([4, 16], evens2)
// Map literal with identifier keys, shorthand, and spread
val base = { a: 1, b: 2 }
val patch = { b: 3, c: }
val m = { "a": 0, ...base, ...patch, d: 4 }
assertEquals(1, m["a"]) // base overwrites 0
// Object expressions: anonymous classes on the fly
val worker = object : Runnable {
override fun run() = println("Working...")
}
worker.run()
// User-defined exceptions: real classes with custom fields
class MyError(val code, m) : Exception(m)
try {
throw MyError(500, "Something failed")
} catch (e: MyError) {
println("Error " + e.code + ": " + e.message)
}
>>> void
""".trimIndent()
val mapHtml = "<pre><code>" + htmlEscape(code) + "</code></pre>"
Div({ classes("markdown-body", "mt-3") }) {
UnsafeRawHtml(highlightLyngHtml(ensureBootstrapCodeBlocks(mapHtml))) UnsafeRawHtml(highlightLyngHtml(ensureBootstrapCodeBlocks(mapHtml)))
} }
}
}
// Short features list // Short features list
Div({ classes("row", "g-4", "mt-1") }) { Div({ classes("row", "g-4", "mt-1") }) {
@ -147,3 +262,36 @@ try {
} }
} }
} }
fun ensureSlideshowStyles() {
if (window.document.getElementById("slideshow-styles") != null) return
val style = window.document.createElement("style") as org.w3c.dom.HTMLStyleElement
style.id = "slideshow-styles"
style.textContent = """
@keyframes slideIn {
from { opacity: 0; transform: translateX(30px); }
to { opacity: 1; transform: translateX(0); }
}
.slide-animation {
animation: slideIn 0.4s ease-out;
}
.slide-container {
min-height: 320px;
cursor: pointer;
transition: transform 0.2s;
background-color: var(--bs-body-bg);
}
.slide-container:hover {
transform: scale(1.005);
}
.try-hint {
opacity: 0.5;
transition: opacity 0.2s;
pointer-events: none;
}
.slide-container:hover .try-hint {
opacity: 1;
}
""".trimIndent()
window.document.head!!.appendChild(style)
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,20 +18,30 @@
import androidx.compose.runtime.* import androidx.compose.runtime.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.sergeych.lyng.LyngVersion import net.sergeych.lyng.LyngVersion
import net.sergeych.lyng.Scope import net.sergeych.lyng.Script
import net.sergeych.lyng.ScriptError import net.sergeych.lyng.ScriptError
import net.sergeych.lyngweb.EditorWithOverlay import net.sergeych.lyngweb.EditorWithOverlay
import org.jetbrains.compose.web.dom.* import org.jetbrains.compose.web.dom.*
@Composable @Composable
fun TryLyngPage() { fun TryLyngPage(route: String) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var code by remember { val initialCode = remember(route) {
val params = route.substringAfter('?', "")
val codeParam = params.split('&').find { it.startsWith("code=") }?.substringAfter('=')
if (codeParam != null) {
try {
decodeURIComponent(codeParam)
} catch (_: Throwable) {
null
}
} else null
}
var code by remember(initialCode) {
mutableStateOf( mutableStateOf(
""" initialCode ?: """
// Welcome to Lyng! Edit and run. // Welcome to Lyng! Edit and run.
// Try changing the data and press Ctrl+Enter or click Run. // Try changing the data and press Ctrl+Enter or click Run.
import lyng.stdlib
val data = 1..5 // or [1, 2, 3, 4, 5] val data = 1..5 // or [1, 2, 3, 4, 5]
data.filter { it % 2 == 0 }.map { it * it } data.filter { it % 2 == 0 }.map { it * it }
@ -54,16 +64,16 @@ fun TryLyngPage() {
val printed = StringBuilder() val printed = StringBuilder()
try { try {
// Create a fresh module scope each run so imports and vars are clean // Create a fresh module scope each run so imports and vars are clean
val s = Scope.new() val s = Script.newScope()
// Capture printed output from Lyng `print`/`println` into the UI result window // Capture printed output from Lyng `print`/`println` into the UI result window
s.addVoidFn("print") { s.addVoidFn("print") {
for ((i, a) in args.withIndex()) { for ((i, a) in this.args.withIndex()) {
if (i > 0) printed.append(' ') if (i > 0) printed.append(' ')
printed.append(a.toString(this).value) printed.append(a.toString(this).value)
} }
} }
s.addVoidFn("println") { s.addVoidFn("println") {
for ((i, a) in args.withIndex()) { for ((i, a) in this.args.withIndex()) {
if (i > 0) printed.append(' ') if (i > 0) printed.append(' ')
printed.append(a.toString(this).value) printed.append(a.toString(this).value)
} }
@ -118,9 +128,8 @@ fun TryLyngPage() {
} }
fun resetCode() { fun resetCode() {
code = """ code = initialCode ?: """
// Welcome to Lyng! Edit and run. // Welcome to Lyng! Edit and run.
import lyng.stdlib
[1,2,3].map { it * 10 } [1,2,3].map { it * 10 }
""".trimIndent() """.trimIndent()
output = null output = null