From 338dc00573746bb20cb79e1aeb18df8ea594ecc1 Mon Sep 17 00:00:00 2001 From: sergeych Date: Sun, 18 Jan 2026 19:09:37 +0300 Subject: [PATCH] better homepage samples --- docs/samples/fs_sample.lyng | 1 - site/src/jsMain/kotlin/App.kt | 4 +- site/src/jsMain/kotlin/HomePage.kt | 234 +++++++++++++++++++++----- site/src/jsMain/kotlin/TryLyngPage.kt | 31 ++-- 4 files changed, 213 insertions(+), 57 deletions(-) diff --git a/docs/samples/fs_sample.lyng b/docs/samples/fs_sample.lyng index b58fe5b..a9c55e4 100755 --- a/docs/samples/fs_sample.lyng +++ b/docs/samples/fs_sample.lyng @@ -1,7 +1,6 @@ #!/bin/env lyng import lyng.io.fs -import lyng.stdlib val files = Path("../..").list().toList() // most long is longest? diff --git a/site/src/jsMain/kotlin/App.kt b/site/src/jsMain/kotlin/App.kt index 873f236..84bbca3 100644 --- a/site/src/jsMain/kotlin/App.kt +++ b/site/src/jsMain/kotlin/App.kt @@ -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"); * 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") }) { when { route.isBlank() -> HomePage() - route == "tryling" -> TryLyngPage() + route.startsWith("tryling") -> TryLyngPage(route) route.startsWith("search") -> SearchPage(route) !isDocsRoute -> ReferencePage() else -> DocsPage( diff --git a/site/src/jsMain/kotlin/HomePage.kt b/site/src/jsMain/kotlin/HomePage.kt index 43756ab..9c5a36f 100644 --- a/site/src/jsMain/kotlin/HomePage.kt +++ b/site/src/jsMain/kotlin/HomePage.kt @@ -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.highlightLyngHtml import net.sergeych.lyngweb.htmlEscape @@ -23,6 +25,141 @@ import org.jetbrains.compose.web.dom.* @Composable 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 Section({ classes("py-4", "py-lg-5") }) { Div({ classes("text-center") }) { @@ -44,7 +181,7 @@ fun HomePage() { } } // 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 = { classes("btn", "btn-primary", "btn-lg") attr("href", "#/docs/tutorial.md") @@ -72,47 +209,25 @@ fun HomePage() { } } - // Code sample - val code = """ -// Create, transform, and verify — the Lyng way -import lyng.stdlib - -fun findFirstPositive(list) { - list.forEach { - if (it > 0) return@findFirstPositive it - } - null -} -assertEquals(42, findFirstPositive([-1, 42, -5])) - -val data = 1..5 // or [1,2,3,4,5] -val evens2 = data.filter { it % 2 == 0 }.map { it * it } -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 = "
" + htmlEscape(code) + "
" - Div({ classes("markdown-body", "mt-3") }) { - UnsafeRawHtml(highlightLyngHtml(ensureBootstrapCodeBlocks(mapHtml))) + // Code sample slideshow + Div({ + classes("markdown-body", "mt-0", "slide-container", "position-relative") + onClick { + window.location.hash = "#tryling?code=" + encodeURIComponent(samples[currentSlide]) + } + }) { + Small({ + classes("position-absolute", "top-0", "end-0", "m-2", "text-muted", "fw-light", "try-hint") + }) { + Text("click to try") + } + key(currentSlide) { + val slideCode = samples[currentSlide] + val mapHtml = "
" + htmlEscape(slideCode) + "
" + Div({ classes("slide-animation") }) { + UnsafeRawHtml(highlightLyngHtml(ensureBootstrapCodeBlocks(mapHtml))) + } + } } // Short features list @@ -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) +} diff --git a/site/src/jsMain/kotlin/TryLyngPage.kt b/site/src/jsMain/kotlin/TryLyngPage.kt index 53abab8..2ab46a6 100644 --- a/site/src/jsMain/kotlin/TryLyngPage.kt +++ b/site/src/jsMain/kotlin/TryLyngPage.kt @@ -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"); * you may not use this file except in compliance with the License. @@ -18,20 +18,30 @@ import androidx.compose.runtime.* import kotlinx.coroutines.launch import net.sergeych.lyng.LyngVersion -import net.sergeych.lyng.Scope +import net.sergeych.lyng.Script import net.sergeych.lyng.ScriptError import net.sergeych.lyngweb.EditorWithOverlay import org.jetbrains.compose.web.dom.* @Composable -fun TryLyngPage() { +fun TryLyngPage(route: String) { 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( - """ + initialCode ?: """ // Welcome to Lyng! Edit and 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] data.filter { it % 2 == 0 }.map { it * it } @@ -54,16 +64,16 @@ fun TryLyngPage() { val printed = StringBuilder() try { // 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 s.addVoidFn("print") { - for ((i, a) in args.withIndex()) { + for ((i, a) in this.args.withIndex()) { if (i > 0) printed.append(' ') printed.append(a.toString(this).value) } } s.addVoidFn("println") { - for ((i, a) in args.withIndex()) { + for ((i, a) in this.args.withIndex()) { if (i > 0) printed.append(' ') printed.append(a.toString(this).value) } @@ -118,9 +128,8 @@ fun TryLyngPage() { } fun resetCode() { - code = """ + code = initialCode ?: """ // Welcome to Lyng! Edit and run. - import lyng.stdlib [1,2,3].map { it * 10 } """.trimIndent() output = null