NIMIB🐳 GOES INTERACTIVE🤯

github.com/pietroppeter/nimconf22-nimib

👨‍👩‍👧 Pietro

HUGO 🙋‍♂️

  • Engineering Physics student

  • Nimib maintainer - since July 2022

    • nimiSlides

  • SciNim member - since the start 2020

    • NumericalNim

    • Scinim/getting-started (uses nimiBook)

Previously at NimConf 2021

Content of presentation

  • A livecoding👨‍💻 intro to Nimib🐳
  • ✨Release of Nimib 0.3 - BlockMaker🧱
    • make your own NbBlock👷
    • 🤯 nbJsFromCode, nbKaraxCode
  • Nimiboost🚀
  • Roadmap🗺️
  • Contributing🤲

A livecoding👨‍💻 intro to Nimib🐳

✨Nimib 0.3 - BlockMaker🧱

  • make your own NbBlock👷
  • 🤯 nbJsFromCode, nbKaraxCode

✨Nimib 0.3 - BlockMaker🧱

  • 📋CodeAsInSource
  • make your own NbBlock👷
    • Fancy blocks 🖼️
  • 🐍nbPython
  • 🤯 nbJsFromCode, nbKaraxCode
    • A mathy plant app 🌱

✨Nimib 0.3 - BlockMaker🧱

  • 📋CodeAsInSource
  • make your own NbBlock👷
    • Fancy blocks 🖼️
  • 🐍nbPython
  • 🤯 nbJsFromCode, nbKaraxCode
    • A mathy plant app 🌱

more stuff and details in 0.3.0 and 0.3.1 release notes

📋CodeAsInSource

Before (CodeFromAst)

Code in source file:

import math, strformat
let
  n = 1
echo n + 1 # 2
## documentation comment
struct(http):
  s: _ = "HTTP/"
  *header: {headers}

Code as shown in html:

import
  math, strformat

let n = 1
echo n + 1
## documentation comment
struct(http):
  s:
    _ = "HTTP/"
  *header

Before (CodeFromAst)

Code in source file:

import math, strformat
let
  n = 1
echo n + 1 # 2
## documentation comment
struct(http):
  s: _ = "HTTP/"
  *header: {headers}

Code as shown in html:

import
  math, strformat

let n = 1
echo n + 1
## documentation comment
struct(http):
  s:
    _ = "HTTP/"
  *header

Uses macro toStr(body) = body.toStrLit

Still available with -d:nimibCodeFromAst

Now (CodeAsInSource)

Code in source file:

import math, strformat
let
  n = 1
echo n + 1 # 2
## documentation comment
struct(http):
  s: _ = "HTTP/"
  *header: {headers}

Same as shown in html:

import math, strformat
let
  n = 1
echo n + 1 # 2
## documentation comment
struct(http):
  s: _ = "HTTP/"
  *header: {headers}

This is what you expect!✨

Does not compose well (134), there might still be 🐞s

make your own NbBlock👷

What are blocks?

import nimib

nbInit # creates a nb: NbDoc object
# it has attribute nb.blocks: seq[NbBlock] = []

nbText: "hi" # add new NbBlock to nb.blocks = [🟩]

nbCode: echo "hi" # add new NbBlock to nb.blocks = [⬛, 🟩]

nbImage("hi.png") # add new NbBlock to nb.blocks = [⬛, ⬛, 🟩]

nbSave # process and render each block (+theme stuff +save file)

What is a block?

DATA

collected when block is created

PROCESS

optional: process data

(e.g. convert markdown to html)

RENDER

apply templates to data

there are multiple render backends (e.g markdown)

we will focus on HTML backend

Yeah, but what is a NbBlock?

type
  NbBlock = ref object
    ## DATA 👇
    code*: string  ## nbCode source
    output*: string  ## nbCode output / nbText text
    context*: Context ## think of this as a JsonNode           
    command*: string ## (NbCode, NbText, ...) used for render
  NbDoc* = object
    blocks*: seq[NbBlock]
    blk*: NbBlock  ## current block being processed
  ## PROCESS 👇
    renderPlans*: Table[string, seq[string]]
      ## key is command, value is a seq of proc names
    renderProcs*: Table[string, NbRenderProc]
      ## key is proc name, value is implementation
  ## RENDER 👇
    partials*: Table[string,  string]
      ## key is command, value is a mustache template                              
  NbRenderProc* = proc (doc: var NbDoc, blk: var NbBlock) {.nimcall.}

Essentials of mustache

Templates
var partials: Table[string,string]
partials["doc"] = """
<head>
  {{> head }}
</head>
<body>
  {{> main }}
</body>"""
partials["head"] = """
{{#t}}<title>{{t}}</title>{{/t}}
"""          
partials["main"] = """
{{&html}}
{{code}}"""

 

Data
var context = newContext()
context["t"] = "hi"
context["html"] = "<p>hi</p>"
context["code"] = "1 < 2"
context.searchTable(partials)
Render
echo "{{>doc}}".render(context)
<head>
  <title>hi</title>
</head>
<body>
  <p>hi</p>
  1 &lt; 2</body>

nbText

DATA
template nbText*(text: string) =
  newNbSlimBlock("nbText"):
    nb.blk.output = text
PROCESS
nb.renderPlans["nbText"] = @["mdOutputToHtml"]
nb.renderProcs["mdOutputToHtml"] =  mdOutputToHtml
# mdOutputToHtml adds `outputToHtml` to data
RENDER
nb.partials["nbText"] = "{{&outputToHtml}}"

nbCode

DATA
template nbCode*(body: untyped) =
  newNbCodeBlock("nbCode", body): # create block and save source
    captureStdout(nb.blk.output): # run and capture output
      body
PROCESS
nb.renderPlans["nbCode"] = @["highlightCode"]
nb.renderProcs["highlightCode"] =  highlightCode
# highlightCode adds `codeHighlighted` to data
RENDER
nb.partials["nbCode"] = """
{{>nbCodeSource}}
{{>nbCodeOutput}}"""
nb.partials["nbCodeSource"] = 
  "<pre><code class=\"nim hljs\">{{&codeHighlighted}}</code></pre>"
nb.partials["nbCodeOutput"] = """{{#output}}
<pre class="nb-output"><samp>{{output}}</samp></pre>
{{/output}}"""

nbImage

DATA
template nbImage*(url: string, caption = "") =
  newNbSlimBlock("nbImage"):
    nb.blk.context["url"] = url # *special handling of relative paths
    nb.blk.context["caption"] = caption
RENDER
nb.partials["nbImage"] = """<figure>
<img src="{{url}}" alt="{{caption}}">
<figcaption>{{caption}}</figcaption>
</figure>"""

Other blocks

  • nbCodeInBlock: a nbCode in a block:

  • nbTextWithCode: a nbText that saves code source

  • nbFile: writes a file with content (there is an untyped version for nim code)

  • nbRawHtml: used to output raw html

How can you create blocks?

natively
template newNbCodeBlock*(cmd: string, body, blockImpl: untyped) =
  discard

template newNbSlimBlock*(cmd: string, blockImpl: untyped) =
  discard

(a slim block is a block without body)

creatively

How can you create blocks?

creatively

Copying and customizing blocks

template nbCodeHtmlOutput(body: untyped) =
  nbCode:
    body
  nb.blk.command = "nbCodeHtmlOutput"
nb.partials["nbCodeHtmlOutput"] =
  nb.partials["nbCode"].replace("{{>nbCodeOutput}}", "{{&output}}")

nbCodeHtmlOutput:
  for color in ["blue", "green", "yellow"]:
    echo "<span style=\"color:" & color & "\">" & color & "</span>"

blue green yellow

How can you create blocks?

creatively

Composing other blocks

template nbTextRepeat(text: string, repeat: int) =
  for i in 1 .. repeat:
    nbText: text

nbTextRepeat("All work and no play makes Jack a dull boy", 3)

All work and no play makes Jack a dull boy

All work and no play makes Jack a dull boy

All work and no play makes Jack a dull boy

How can you create blocks?

nbRawHtml is particularly powerful

template nbDetails(summary: string, body: untyped) =
  nbRawHtml: "<small><details><summary>" & summary & "</summary>"
  body
  nbRawHtml: "</details></small>"
nbDetails("click to reveal details"):
  nbTextSmall: "one block"
  nbCode: discard "another block"

click to reveal details

one block

discard "another block"

How can you create blocks?

nbRawHtml is particularly powerful

(nimislides uses it for slide template)

but

limited to html backend

cannot be customized

a better solution will come with the container block!

Fancy blocks 🖼️

blocks which use external javascript functionalities

all examples taken from nblog

How to add a line at the end of <head> section

nb.partials["head"] &= "<style>..."

How to add a line at the end of <body> section

nb.partials["main"] &= "<script>..."

nbPython

nbPython

nbPython: hlPy"""
print("Hello World") 
"""

nbPython

import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(-5, 5)
y = np.sin(x)
plt.plot(x, y)
plt.title("nbPython Plot")
plt.savefig("matplotlib_example.png", dpi=60)
  

Nimib goes interactive!

Create interactive elements in Nimib using Nim!

Why?

Engaging content

Comfortable - Nim all the way

Runs locally

Fun!

How?

Nim → JS

Capture variables

API

  • nbJsFromCode - compiles code to JS
  • nbKaraxCode - sugar for Karax

nbJsFromCode

nbRawHtml: """<p id="text-id">You have clicked 0 times!</p>
<button id="btn-id">Click me!</button>"""

nbJsFromCode:
  import std / dom

  let btn = getElementById("btn-id")
  let paragraph = getElementById("text-id")

  var counter: int

  btn.addEventListener("click", proc (event: Event) =
    counter += 1
    paragraph.innerHtml = "You have clicked " & $counter & " times!"
  )

You have clicked 0 times!

Capture variables

let name = "Hugo"
let food = "hot dogs"
nbRawHtml: """
<p id="text2-id">...</p>
<button id="btn2-id">Click me!</button>
"""
nbJsFromCode(name, food):
  import std / dom

  let btn = getElementById("btn2-id")
  let paragraph = getElementById("text2-id")

  btn.addEventListener("click", proc (event: Event) =
    paragraph.innerHtml = name & "'s favourite food is " & food
  )

...

nbJsFromCode + Karax

let rootId = "karax-" & $nb.newId()
nbRawHtml: "<div id=\"" & rootId & "\"></div>"
nbJsFromCode:
  include karax / prelude
  var counter: int
  proc createDom(): VNode =
    result = buildHtml(tdiv):
      p:
        text "You have clicked " & $counter & " times!"
      button:
        text "Click me!"
        proc onClick() =
          counter += 1

  setRenderer(createDom, root = rootId.cstring)

nbKaraxCode

nbKaraxCode:
  var counter: int
  karaxHtml:
    p:
      text "You have clicked " & $counter & " times!"
    button:
      text "Click me!"
      proc onClick() =
        counter += 1

postRender

nbKaraxCode:
  import jscanvas, dom, colors, math, random

  postRender:
    var c = getElementById("canvas-id").CanvasElement
    # canvas will be nil if it hasn't
    # been created by Karax yet
    c.width = 500
    c.height = 100
    var ctx = c.getContext2d()

    # Fill background
    ctx.fillStyle = $colBlack
    ctx.fillRect(0,0, c.width, c.height)
    # Draw ball
    var x = rand(0..c.width)
    var y = rand(0..c.height)
    var ballRadius = 10
    ctx.beginPath()
    ctx.arc(x, y, ballRadius, 0, Pi*2)
    ctx.fillStyle = $colBlue
    ctx.fill()
    ctx.closePath()

  karaxHtml:
    canvas(id="canvas-id")

A mathy plant app 🌱

Nimiboost

VS Codium/VS Code extension

Features

  • Syntax highlighting

  • Preview

Let's head over to VSCodium!

Roadmap🗺️

priorities

  • first goal: produce stuff with nimib 0.3.x!

  • next 0.4 target: backend maker

  • side projects:

    • (Pietro) a blog theme / jekyll clone

0.3.x

  • more blocks, e.g. nbShell #34

  • container block #117

  • improve default theme, e.g. code output #65

  • table of contents #58

  • site index #129

  • dataframe to html table #65

  • ...

further down the road

  • (after 0.4) new backends (e.g latex, twitter?, ...)

  • serving a backend that interacts with the page (streamlit style? jester? htmx?)

  • build a library directly from documentation (like in nbdev)?

  • nimib executable for scaffolding

  • possibility of editing document in the browser

Contributing🤲

👩‍💻Improved Dev Experience

  • docs are now built in CI

  • deploy preview!

  • we added tests (and removed ptest)

  • updated docs and added a separate changelog

  • a new CONTRIBUTE.md!

👩‍💻Improved Dev Experience

Deploy preview

🎪Nimib Speaking Hours!

  • me and Hugo have been meeting regularly

  • thinking of keeping this up (once a month)

  • open to anyone using nimib or contributing

  • will announce somewhere (nimib discussions?)

(inspired by Simon Willson)

Thank you for listening!
Tack
谢谢
धन्यवाद
Gracias

 

Grazie
ありがとう
Merci
Bedankt

 

Danke
Дякую тобі
Obrigado
Dziękuję Ci