diff --git a/Readme.md b/Readme.md index 64c0e635..24fa6e4d 100644 --- a/Readme.md +++ b/Readme.md @@ -62,6 +62,10 @@ If we click in a cell, we will be able to change a value. And then use the "Save Now feel free to examine and run the rest of the scripts or create your own! You can have the server running and develop your scripts with your favorite IDE. Run the scripts within the IDE and view the UI in a browser. +# Tutorial + +To learn the basics of coding with terminal21, please see the [tutorial](docs/tutorial.md) + # Example scripts ```shell diff --git a/docs/images/tutorial/progress.png b/docs/images/tutorial/progress.png new file mode 100644 index 00000000..9c78c2cd Binary files /dev/null and b/docs/images/tutorial/progress.png differ diff --git a/docs/tutorial.md b/docs/tutorial.md new file mode 100644 index 00000000..c8f751d1 --- /dev/null +++ b/docs/tutorial.md @@ -0,0 +1,150 @@ +# Terminal21 tutorial + +This tutorial assumes you have the terminal 21 server running (as described in the readme file of the project) and you would +like to learn how to create user interfaces. + +This tutorial will use `scala-cli` but the same applies for `sbt` or `mill` projects that use the terminal21 libraries. + +## Creating a folder for our scripts + +Create a folder and a file `project.scala` into it. This file will help us include the library dependencies and also +scala & jdk version. It should look like this: + +```scala +//> using jvm "21" +//> using scala 3 + +//> using dep io.github.kostaskougios::terminal21-ui-std:_VERSION_ +``` + +Change `_VERSION_` with the terminal 21 latest version. + +See [project.scala](../example-scripts/project.scala) + +## Creating a hello world app + +To do this we can create a [hello-world.sc](../example-scripts/hello-world.sc) in our folder. + +```scala +#!/usr/bin/env -S scala-cli project.scala + +import org.terminal21.client.* +import org.terminal21.client.components.* +import org.terminal21.client.components.std.* + +Sessions.withNewSession("hello-world", "Hello World Example"): session => + given ConnectedSession = session + + Paragraph(text = "Hello World!").render() + session.leaveSessionOpenAfterExiting() +``` + +The first line, `#!/usr/bin/env -S scala-cli project.scala`, makes our script runnable from the command line. + +```shell +chmod +x hello-world.sc + +./hello-world.sc +``` + +I had issues with intellij and this line, so you may want to comment it out while you develop your scripts. + +It starts `scala-cli` and also includes `project.scala` so that we get our dependencies, jdk 21 and scala 3 when running our code. + + +Next it creates a session. Each session has a unique id (globally unique across scripts), in this case `hello-world`. And a session +title, "Hello World Example", that will be displayed on the browser. + +```scala +Sessions.withNewSession("hello-world", "Hello World Example"): session => + ... +``` + +![hello-world](images/hello-world.png) + +Next is the actual user interface, in this example just a paragraph with a "Hello World!": + +```scala +Paragraph(text = "Hello World!").render() +``` + +The `render()` method sends the UI to the server which in turn sends it to the terminal21 UI so that it is rendered. + +Finally because this is just a presentation script (we don't expect any feedback from the user), we can terminate it but +inform terminal21 we want to leave the session open so that the user has a chance to see it. + +```scala +session.leaveSessionOpenAfterExiting() +``` + +When we run our code, it will compile, download dependencies (if needed) and run. It will exit straight away but the UI for our script +will be available in terminal21 UI. + +## Updating the UI + +Let's create a script that will display a progress bar for some process that will run for some time. The script will update +the progress bar and also give an informative message regarding which stage of the process it is performing. + +[progress.sc](../example-scripts/progress.sc) + +![progress](images/tutorial/progress.png) + +```scala +#!/usr/bin/env -S scala-cli project.scala + +import org.terminal21.client.* +import org.terminal21.client.components.* +import org.terminal21.client.components.std.* +import org.terminal21.client.components.chakra.* + +Sessions.withNewSession("universe-generation", "Universe Generation Progress"): session => + given ConnectedSession = session + + val msg = Paragraph(text = "Generating universe ...") + val progress = Progress(value = 1) + + Seq(msg, progress).render() + + for i <- 1 to 100 do + val p = progress.withValue(i) + val m = + if i < 10 then msg + else if i < 30 then msg.withText("Creating atoms") + else if i < 50 then msg.withText("Big bang!") + else if i < 80 then msg.withText("Inflating") + else msg.withText("Life evolution") + + Seq(p, m).renderChanges() + Thread.sleep(100) + + // clear UI + session.clear() + Paragraph(text = "Universe ready!").render() +``` + +Here we create a paragraph and a progress bar. + +```scala + val msg = Paragraph(text = "Generating universe ...") + val progress = Progress(value = 1) +``` + +Then we render them for the first time on screen. When we want to add a new element to the UI, we use the `render()` method. When +we want to update an existing element we use the `renderChanges()` method. + +```scala + Seq(msg, progress).render() +``` + +Then we have our main loop where the calculations occur. We just use a `Thread.sleep` to simulate that some important task is being calculated. And we +update the progress bar and the message in our paragraph. +```scala +val p = progress.withValue(i) +val m = ... msg.withText("Creating atoms") ... +``` + +Note the `e.withX()` methods. Those help us change a value on a UI element. We get a copy of the UI element which we can render as an update: + +```scala +Seq(p, m).renderChanges() +``` \ No newline at end of file diff --git a/example-scripts/progress.sc b/example-scripts/progress.sc new file mode 100755 index 00000000..29782daa --- /dev/null +++ b/example-scripts/progress.sc @@ -0,0 +1,30 @@ +#!/usr/bin/env -S scala-cli project.scala + +import org.terminal21.client.* +import org.terminal21.client.components.* +import org.terminal21.client.components.std.* +import org.terminal21.client.components.chakra.* + +Sessions.withNewSession("universe-generation", "Universe Generation Progress"): session => + given ConnectedSession = session + + val msg = Paragraph(text = "Generating universe ...") + val progress = Progress(value = 1) + + Seq(msg, progress).render() + + for i <- 1 to 100 do + val p = progress.withValue(i) + val m = + if i < 10 then msg + else if i < 30 then msg.withText("Creating atoms") + else if i < 50 then msg.withText("Big bang!") + else if i < 80 then msg.withText("Inflating") + else msg.withText("Life evolution") + + Seq(p, m).renderChanges() + Thread.sleep(100) + + // clear UI + session.clear() + Paragraph(text = "Universe ready!").render() diff --git a/terminal21-ui-std/src/main/scala/org/terminal21/client/components/chakra/ChakraElement.scala b/terminal21-ui-std/src/main/scala/org/terminal21/client/components/chakra/ChakraElement.scala index fe08e86a..ef601436 100644 --- a/terminal21-ui-std/src/main/scala/org/terminal21/client/components/chakra/ChakraElement.scala +++ b/terminal21-ui-std/src/main/scala/org/terminal21/client/components/chakra/ChakraElement.scala @@ -1755,6 +1755,8 @@ case class AlertDescription( def withKey(v: String) = copy(key = v) def withText(v: String) = copy(text = v) +/** https://chakra-ui.com/docs/components/progress + */ case class Progress( key: String = Keys.nextKey, value: Int = 50,