Open Source Adventures: Episode 19: Porting Imba 1 Apps to Imba 2
Tomasz Wegrzanowski
Posted on March 23, 2022
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>
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;
}
}
}
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>
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;
}
}
}
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.
Posted on March 23, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.