Open Source Adventures: Episode 21: Imba 2 Matrix Rain
Tags: imba, javascript
Published: 2022-03-24 13:42:25

Let's port another Imba 1 app to Imba 2, this time Matrix Rain, a screensaver similar to the Matrix.

[You can see it in action here](https://taw.github.io/imba-matrix-rain/), and [the source code of Imba 1 version here](https://github.com/taw/imba-matrix-rain).

### Imba 1 `app.imba`

This app uses two components - `App` (top level) and `Stream` (a single stream of falling characters), and a few lifecycle hooks, with `setInterval`, and `setup`.

There's also a few loose functions with unusually looking `let def`.

```
let def random_int(min, max)
  min + Math.floor( Math.random() * (max - min + 1) )

let def random_katakana
  String.fromCharCode(random_int(0x30A0, 0x30FF))

let def random_symbols
  for i in [0..random_int(5, 30)]
    { v: random_katakana() }

tag Stream
  def render
    <self css:top=data:y css:left=data:x>
      for symbol, index in data:symbols
        <div.symbol .first=(index==0)>
          symbol:v

tag App
  def setup
    @streams = []
    let x = 10
    while x + 30 < window:inner-width
      @streams.push({
        x: x,
        y: Math.random() * window:inner-height,
        speed: 10 + Math.random() * 20,
        symbols: random_symbols()
      })
      x += 30

  def mount
    setInterval(&,10) do
      for stream in @streams
        stream:y += stream:speed
        if stream:y > window:inner-height + stream:symbols:length * 20
          stream:symbols = random_symbols()
          stream:speed = 10 + Math.random() * 20
          stream:y = - stream:symbols:length * 20
        for symbol in stream:symbols
          if Math.random() < 0.01
            symbol:v = random_katakana()
      Imba.commit

  def render
    <self>
      for stream in @streams
        <Stream[stream]>

Imba.mount <App>
```

### Imba 1 `app.scss`

There's a bit of unusual styling here, featuring rarely used `flex-direction: column-reverse`. As usual, SCSS used only for nesting, and not for anything fancy.

```scss
@import 'normalize-scss';
@include normalize();

body {
  overflow: hidden;
}

.App {
  background-color: black;
  height: 100vh;
  width: 100vw;
  overflow: hidden;

  .Stream {
    position: absolute;
    display: flex;
    flex-direction: column-reverse;

    .symbol {
      height: 20px;
      width: 20px;
      line-height: 20px;
      position: relative;
      font-size: 16px;
      text-align: center;
      color: #8f4;

      &.first {
        color: #dfa;
      }
    }
  }
}
```

### Imba 2 `app.imba`

Here's the app ported to Imba 2:

```
global css body
  background-color: black
  overflow: hidden
  margin: 0
  height: 100vh
  width: 100vw

def random_int(min, max)
  min + Math.floor( Math.random() * (max - min + 1) )

def random_katakana
  String.fromCharCode(random_int(0x30A0, 0x30FF))

def random_symbols
  for i in [0 .. random_int(5, 30)]
    { v: random_katakana() }

tag stream
  prop x
  prop y
  prop symbols

  <self[top:{y}px left:{x}px]>
    for symbol, index in symbols
      <div.symbol>
        symbol.v

  css
    position: absolute
    display: flex
    flex-direction: column-reverse

    .symbol
      height: 20px
      width: 20px
      line-height: 20px
      position: relative
      font-size: 16px
      text-align: center
      color: #8f4

      &:first-child
        color: #dfa

tag app
  def setup
    streams = []
    let x = 10
    while x + 30 < window.innerWidth
      streams.push({
        x: x
        y: Math.random() * window.innerHeight
        speed: 3 + Math.random() * 5
        symbols: random_symbols()
      })
      x += 30

  def mount
    imba.setInterval(&, 10) do
      for stream in streams
        stream.y += stream.speed
        if stream.y > window.innerHeight
          stream.symbols = random_symbols()
          stream.speed = 3 + Math.random() * 5
          stream.y = - stream.symbols.length * 20
        for symbol in stream.symbols
          if Math.random() < 0.01
            symbol.v = random_katakana()

  <self>
    for stream in streams
      <stream x=stream.x y=stream.y symbols=stream.symbols>

  css
    height: 100vh
    width: 100vw
    overflow: hidden

imba.mount <app>
```

Some notes:

* syntax is a bit closer to standard JavaScript
* `imba.setInterval` is like regular `setInterval`, except it also tells Imba that properties might have changed. It's a different model of reactivity than Svelte's.
* each symbol is wrapped with `{v: ...}` to make update in a loop easier
* one baffling thing was `<self[top:{y}px left:{x}px]>` - I first tried `<self[top:{y} left:{x}]>` expecting pixels to be the default units, as that's how everything about DOM works, but somehow Imba converted `10` to `40px`. Like what an actual fuck? It makes no sense whatsoever. It's some weird Tailwind nonsense, where `h-1` means `height: 0.25rem`, and that's `4px`. That's just totally baffling at framework level.
* Imba's CSS has enough SCSS features (like `&:first-child`) that there's no need to use SCSS

### Source code

Source code is in [imba2-matrix-rain repository](https://github.com/taw/imba2-matrix-rain). At some point I'll try to add GitHub Pages support for it.

### Coming next

In the next few episodes I'll try to port a few more Imba 1 apps to Imba 2, and then I guess I'll summarize my thoughts about Imba situation.