DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

Open Source Adventures: Episode 19: Porting Imba 1 Apps to Imba 2

Back in the days when I was into Imba 1, I wrote a lot of toy Imba 1 apps. It's a good time to rewrite them in Imba 2.

There are some difficulties with porting:

  • there's no automated way to convert Imba 1 to Imba 2 code, it's a different dialect of CoffeeScript, and it's not just syntactic
  • the component model is not the same, Imba 2 is based on web components
  • as Imba 2 is based on web components, an overall questionable decision - one huge problem with it is that they just plain won't work with SVG, and I used a lot of SVG in my apps; I don't know if there are any workaround for that Imba could do
  • my mini apps all used SCSS, and Imba 2 has its own Tailwind-like CSS system instead

I did one such port before - Imba 1 eyes, Imba 2 eyes - which you can see in action here.

By the way Imba 2 forced tabs make Imba 2 code look like total ass on github, as it uses 8 spaces for tab indentation, and OMG, it is ugly. By comparison 2-spaced Imba 1 code looks neat.

I know you can set your editor to display tabs as 2 spaces, but this setting won't apply everywhere (like GitHub for example, or blog posts). I think it's an absolutely terrible choice, and Imba should just switch to standard 2 spaces every other frontend tech uses, instead of trying to be different.

Imba 1 eyes code

tag Eye < svg:g prop mx prop my def render let max_eye_movement = 0.3 * data:sz let rx = data:x let ry = data:y if mx != null && my != null let dx = mx - data:x let dy = my - data:y let dl = Math.sqrt(dx*dx + dy*dy) if dl > max_eye_movement dx = max_eye_movement * dx/dl dy = max_eye_movement * dy/dl rx += dx ry += dy <self> <svg:circle.eye1 cx=(data:x) cy=(data:y) r=(data:sz)> <svg:circle.eye2 cx=(rx) cy=(ry) r=(data:sz * 0.5) css:fill=(data:color)> <svg:circle.eye3 cx=(rx) cy=(ry) r=(data:sz * 0.2)> tag App def mount schedule(raf: true) def onmousemove(event) let native_event = event:_event let svg = document.get-element-by-id("eyes") let rect = svg.get-bounding-client-rect() @mx = native_event:pageX - rect:x @my = native_event:pageY - rect:y def eye_distance(eye1, eye2) let dx = eye1:x - eye2:x let dy = eye1:y - eye2:y Math.sqrt((dx * dx) + (dy * dy)) def can_place_eye(new_eye) @eyes.every do |eye| eye_distance(eye, new_eye) >= eye:sz + new_eye:sz + 5 def random_color let h = Math.random() * 360 let s = Math.round(50 + Math.random() * 50) let l = Math.round(30 + Math.random() * 40) "hsl({h}, {s}%, {l}%)" def setup let wh = window:inner-height let ww = window:inner-width @mx = Math.random() * ww @my = Math.random() * wh @eyes = [] for i in [1..1000] let sz = 20 + Math.random() * 60 let x = sz + Math.random() * (ww - 2 * sz) let y = sz + Math.random() * (wh - 2 * sz) let new_eye = {x: x, y: y, sz: sz, color: random_color} if can_place_eye(new_eye) @eyes.push(new_eye) def render <self> <svg:svg#eyes> for eye in @eyes <Eye[eye] mx=@mx my=@my> Imba.mount <App> 
Enter fullscreen mode Exit fullscreen mode

Notable design here is that Eye component inherits from svg:g.

Imba 1 eyes scss

@import 'normalize-scss'; @include normalize(); body { overflow: hidden; } .App { width: 100vw; height: 100vh; overflow: hidden; svg { width: 100vw; height: 100vh; display: block; background-color: #aaa; .eye1 { fill: white; stroke: black; stroke-width: 3px; } .eye2 { stroke: black; stroke-width: 1px; } .eye3 { fill: black; } } } 
Enter fullscreen mode Exit fullscreen mode

It could have easily be plain CSS, but I just don't like plain CSS. Also using normalize from a package, the relevant parts would be just a few lines.

Imba 2 eyes code

I had to make every eye its own <svg> instead of just being a <g>. For this toy app it's fine, but there's a lot of cases where Imba 2's approach just won't do.

# NOTE: # Can't inherit from svg:g yet in imba2 # so this is a bit awkward tag spooky-eye def render let max_eye_movement = 0.3 * data.sz let rx = data.x let ry = data.y if mx != null && my != null let dx = mx - data.x let dy = my - data.y let dl = Math.sqrt(dx*dx + dy*dy) if dl > max_eye_movement dx = max_eye_movement * dx/dl dy = max_eye_movement * dy/dl rx += dx ry += dy <self> <svg> <svg:circle.eye1 cx=(data.x) cy=(data.y) r=(data.sz)> <svg:circle.eye2 cx=(rx) cy=(ry) r=(data.sz * 0.5) css:fill=(data.color)> <svg:circle.eye3 cx=(rx) cy=(ry) r=(data.sz * 0.2)> tag app-root def eye_distance(eye1, eye2) let dx = eye1.x - eye2.x let dy = eye1.y - eye2.y Math.sqrt((dx * dx) + (dy * dy)) def can_place_eye(new_eye) eyes.every do |eye| eye_distance(eye, new_eye) >= eye.sz + new_eye.sz + 5 def random_color() let h = Math.random() * 360 let s = Math.round(50 + Math.random() * 50) let l = Math.round(30 + Math.random() * 40) "hsl({h}, {s}%, {l}%)" def onmousemove(event) let element = document.get-element-by-id("eyes") let rect = element.get-bounding-client-rect() mx = event.page-x - rect.x my = event.page-y - rect.y def constructor super let wh = window.inner-height let ww = window.inner-width mx = Math.random() * ww my = Math.random() * wh eyes = [] for i in [1 .. 1000] let sz = 20 + Math.random() * 60 let x = sz + Math.random() * (ww - 2 * sz) let y = sz + Math.random() * (wh - 2 * sz) let new_eye = {x: x, y: y, sz: sz, color: random_color()} if can_place_eye(new_eye) eyes.push(new_eye) def render <self#eyes :mousemove.onmousemove> for eye in eyes <spooky-eye data=eye mx=mx my=my> 
Enter fullscreen mode Exit fullscreen mode

Imba 2 eyes scss

I did not port that to Imba 2's new css system. I believe at the time I was doing the porting it wasn't there yet, so it just reuses the SCSS I had.

@import 'normalize-scss'; @include normalize(); app-root { display: block; width: 100vw; height: 100vh; overflow: hidden; background-color: #aaa; svg { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none; .eye1 { fill: white; stroke: black; stroke-width: 3px; } .eye2 { stroke: black; stroke-width: 1px; } .eye3 { fill: black; } } } 
Enter fullscreen mode Exit fullscreen mode

Coming next

In the next few episodes I'll try to port a few more Imba 1 apps to Imba 2, and maybe try some of the new Imba 2's features like its new CSS system.

Top comments (0)