Typolino – Adding Features (one year later)

Coming back to a side project after a year can be pretty painful. I actually wanted to see how it goes with Typolino. Is the CI/CD pipeline still working? Can I easily upgrade my dependencies and add new features?

A product should grow with your clients and Typolino is no different. My kids get older and they want some new challenges. 🙂

CI/CD

Besides allowing teams to focus on writing code adding business value, having a CI/CD pipeline is also great to come back to an abandoned project. I didn’t have to do anything, it still works fine. I recently tend to tell my colleagues that we should think CI/CD first (like mobile first, API first, …) and I believe this pays off very quickly. I’m aware this can also go wrong, but I was lucky enough. Having a regular run to prove your build still works fine may be a good idea, so that you an fix issues right away.

Upgrade

Upgrading npm dependencies is always a bit cumbersome, especially if you want to use the latest major versions. There is still a lot of movement in the web area. I thought it would be cool to see how I can upgrade a pretty old Angular version to Angular 12. It worked quite well, but it was not painless. To be honest I think this should be easier, it is not something you want to think about too much about, specifically if it affects the build system itself. I didn’t have any breaking API changes, but the build system was updated. It took me more than an hour of absolutely not-fun work. 🙂

As a side note: the Angular team decided to go “prod build by default”. Which now kind of killed the faster development builds, I need to do some more research on how to get this back, but waiting like 20 seconds to see the changes during development is a no go. I’m sure I can fix it, just a question how many extra config in angular.json it will require.

Features

Having the CI/CD still working and the upgrade done I got enough time to add some features.

  • Better styling, I added an Angular component that can render words as keyboard keys. Looks a bit fancier now
  • Updated the colors to be a bit sharper
  • Added a hard mode, that will blank a letter in the word. This is great for my older one as he now needs to know how to properly write the word

Conclusion

Having a proper CI/CD pipeline frees me up from manual activities that would probably hold be back from additional changes in “legacy” code and lets me focus on adding actual value. The hurdles are so much lower in my view.

If you want to test the new features, visit Typolino (still German, sorry).

Getting Started with CVXPY to solve convex optimization problems

I don’t know much about convex optimization problems, but being a hands on person I like to have everything ready to experiment. My final goal is to run some scripts on Azure Databricks. So I’m still ony my journey to become Azure Solutions Architect. 😉

Installation

First things first: to get started I had to install Python and all required libraries. This was not super straight forward on Windows and it took me longer than expected. The main problem was, that I needed to install some additional software (some Visual Studio stuff).

Python

I used to use Anaconda to manage my Python environments. But I didn’t have it ready on this machine. So when typing python in the commandline of Windows 10, it basically directed me to the the Microsoft Store.

So, the installation of Python was pretty straight forward and I was able to verify the installation in a new command line window.

CVXPY

The manual says: pip install cvxpy.

But of course it didn’t work. 😉 But reading the error message carefully revealed that I had to install some additional software: error: Microsoft Visual C++ 14.0 or greater is required. Get it with “Microsoft C++ Build Tools”: https://visualstudio.microsoft.com/visual-cpp-build-tools/

It took me two attempts as I was not reading it carefully enough and I missed to install the correct pieces. But following the link in the error and firing up the installer is actually simple and didn’t confront me with any issues. After a restart of my machine I was finally able to install CVXPY without errors.

Verification

Having all dependencies installed I used one of the samples to verify my installation. I used least-squares problem.

And it worked just fine!

Conclusion

It was not a big deal, but it took me some time. Specifically setting Python up and installing the missing Visual Studio dependencies. I’m by no Python expert, I don’t use it everyday and getting back to this beautiful programming language with its rich set of powerful, yet simple to use libraries is always nice.

Moving youcode.ch to Azure

Since a few years I have this idea of youcode.ch. I never invested a lot into this side project after buying some WordPress theme and paying for the domain. I like WordPress for simple websites, but I never got warm with it for more complex stuff.

Anyhow the website stopped working and appearantly WordPress is pretty resource hungry and the company where I host my stuff suggested to go for another offering, which of course is more expensive. 🙂

Therefore, I decided to give it another try and move youcode.ch over to Azure.

In essence I want to host a static web app (Angular, custom domain) and add some magic using Azure functions (which may connect to other services like storage to read/write some data).

So the first thing to do is: setup a static web app and configure the custom domain.

Create Static Web App

Best watch my YouTube video on this topic. I followed the same steps for youcode.ch.

  1. Create a new ng app
  2. Setup Azure Static Webapp in VSC

I amable to access my brand new web app, but the domain isnot yet what I want. So let’s configure a custom domain.

Custom Domain

I already own youcode.ch – or at least I pay for it. So let’s see how simple we can get this configured! To do this, we first need to go to the Azure portal and open the static web app resource. Clicking on the custom domains will show us all registered domains.

Hitting the “add” button will open up a sidebar. Where we are guided through the necessairy steps.

Let’s go for the TXT record type, as this will allow me to add the root domain youcode.ch (and not the subdomain www.youcode.ch). On my hosting provider it is pretty simple to add this record.

Every provider has its own ways to deal with DNS configuration. You can find a lot of useful information here:
Set up a custom domain in Azure Static Web Apps | Microsoft Docs

Now I have to wait a few hours, and I’m not sure it will work as expected as I haven’t seen a way to add ALIAS records at first sight. But let’s see!

How To Keep Up To Date

Recently I was asked what you should do as a software engineer to keep your skill up to date.

Of course, there are many things you can do. But one of the simplest and most effective practices in my view is reading.

There are three websites I visit and scan as part of my daily routine to learn about new trends in technology.

Personally I don’t limit myself to a specific topic. I just pick whatever looks interesting. The reason is that although I may not have a problem that I need to solve today, the gained knowledge may serve me very well later.

What I particularly like about dev.to is that you get a continuous feed of articles – and they are usually pretty short.

Stay curious!

Getting Started With Azure App Services (and learning how to edit videos for YouTube)

I was playing around with Azure App Services and Visual Studio Code recently and I think this is a great service to get something up and running quickly – basically diving into the code right away. Let’s see if we can do everything just from within Visual Studio Code!

I never considered to become a Youtuber – but why not give it a shot, therefore this will be my first personal Youtube video. 🙂

What I used to record:

  • My laptop with all the development software installed
    • Visual Studio Code with some Plugins
      • Java Extension Pack
      • Spring Boot Extension Pack
    • JDK 14
    • Maven
  • OBS Studio for screen recording
  • Blender for video editing

Don’t expect anything professional, but somehow you have to get started, right?

Conclusion

It is pretty simple to deploy a Spring Boot application to Azure. But you need to follow some steps to be successful. I think the process could be simplified a bit: why can’t I just upload a jar file and Azure applies some meaningful defaults? What I tried first is to create the app service upfront using the VSC plugin and then just deploy the Jar – but this was not successful – I guess I would need to provide the web.config manually.

The YouTube video took much more time. It is not easy and actually a lot of work. But I learned a new things and I’m sure I can be much more productive by practicing more.

  • The software I used is pretty overloaded for a beginner, I had no idea where to look at or which buttons I had to click. Specifically Blender is very feature rich. The most important feature was to cut the video into smaller peaces so that I could remove unwanted content.
  • I think I recorded the video about 5 times. At the beginning I started over again when I made a mistake, but I soon realized that it is impossible to do a perfect run at least for me. Therefore I just repeated some steps again during the same recording.
  • Talking to myself felt a bit awkward. This is definitely an area I plan to improve.

Typolino – Analyze Bundle Size

Finally I had some time to fix some bugs on the weekend – and I switched all queries to get() as I don’t see the point for this application to get live updates – one may ask why use Firebase at all. But I have to say it’s more than just the realtime features.

How to

To find out more about the bundle size of your application you could use a bundle analzyer. The tool is really easy to install and use.

npm install --save-dev webpack-bundle-analyzer 

Build the application and generating the stats file, which is required to analyze the bundles.

ng build --stats-json 

And finally run the tool

npx webpack-bundle-analyzer .\dist\typolino\stats-es2015.json

If you want to see the effects of tree shaking you can analyze the prod bundles as well (same as above, but just run ng build –prod –stats-json).

Typolino – Add animations

I never used animations with Angular. Funny as I remember having used the AngularJS variant quite excessively.

So why not try it out. I’m pretty sure there is a cleverer way of doing it but maybe I just need to get used to the system first.

Seems as if one can simply define the animations as part of a component. Basically you define a trigger and possible transitions. As I wanted to make the image border light up nicely I decided to use keyframe style.

Component({
  selector: 'app-lesson',
  templateUrl: './lesson.component.html',
  styleUrls: ['./lesson.component.less'],
  animations: [
    trigger('lastInput', [
      transition('* => correct', [
        animate(
          '0.4s',
          keyframes([
            style({ backgroundColor: '#21e6c1' }),
            style({ backgroundColor: '#dbdbdb' }),
          ])
        ),
      ]),
      transition('* => wrong', [
        animate(
          '0.4s',
          keyframes([
            style({ backgroundColor: '#ffbd69' }),
            style({ backgroundColor: '#dbdbdb' }),
          ])
        ),
      ]),
    ]),
  ],
})

And this is the template part:

 <img
        [@lastInput]="lastInput"
        (@lastInput.done)="resetAnimation()"
        class="lesson__img"
        [hidden]="!imageLoaded"
        [src]="currentWord.imageUrl"
        (load)="loaded()"
      />

Basically this let’s the image “listen” to changes on the state. Angular evaluates the lastInput and triggers the defined animation. So whenever we set lastInput to either correct or wrong, the animation is triggered. We can trigger now programmatically when a letter is typed:

 const newWord = this.userWord + event.key;
    if (this.currentWord.word.toUpperCase().startsWith(newWord.toUpperCase())) {
      this.lastInput = 'correct';
      this.userWord = newWord;
      this.checkWord();
    } else {
      this.lastInput = 'wrong';
      this.millisSpent += 2_000; // penalty
      this.highscoreMissed = this.millisSpent > this.lesson.bestTime;
    }
  }

To ensure we can play the animation over and over somehow we need to reset the trigger. Really not sure if there isn’t an easier way, but (@lastInput.done)=”resetAnimation()” solves the problem for me.

  resetAnimation() {
    this.lastInput = null;
  }

Conclusion

I’m really not an expert on the animations part of Angular. But it looks pretty thought through and I feel like I’m in control of the animations.

Typolino – Web Worker Revisited

The first iteration of the web worker was OK and did the job. But somehow it didn’t feel good enough. I wanted to rewrite it using RxJs. The main reason being that with RxJs I have built in functionality to control concurrency. I don’t want a lesson with a lot of images to go crazy. Therefore I decided to rewrite everything and (altough painful) passing all data always down the stream. Not sure if this would be considered good practice, but I wanted to try it out. It seems quite natural to use tap() and / or access variables that are somewhere in the scope from an operator – but if you think about proper decomposition, purity and testability..

/// <reference lib="webworker" />

import { Lesson } from './lesson';
import { environment } from '@typolino/environments/environment';
import * as firebase from 'firebase/app';
import 'firebase/storage';
import 'firebase/auth';

import { from, of, forkJoin } from 'rxjs';
import {
  mergeMap,
  switchMap,
  map,
  withLatestFrom,
  delay,
} from 'rxjs/operators';

const firebaseConfig = environment.firebase;
firebase.initializeApp(firebaseConfig);

addEventListener('message', ({ data }) => {
  const lesson = data as Lesson;

  from(lesson.words)
    .pipe(
      withLatestFrom(of(lesson)),
      mergeMap(
        ([word, lesson]) =>
          from(
            firebase
              .storage()
              .ref(`${lesson.id}/${word.imageId}`)
              .getDownloadURL()
          ).pipe(withLatestFrom(of(word))),
        5 // concurrency
      ),
      mergeMap(([downloadUrl, word]) =>
        from(
          fetch(downloadUrl, {
            mode: 'no-cors',
            cache: 'default',
          })
        ).pipe(withLatestFrom(of(downloadUrl), of(word)))
      )
    )
    .subscribe(([response, downloadUrl, word]) => {
      word.imageUrl = downloadUrl;
      postMessage({
        imageId: word.imageId,
        imageUrl: downloadUrl,
      });
    });
});

It does almost the same as before, but I had to rewrite some parts. The good thing is that we can control concurrency now and add delays etc. as we wish.

Processing the messages has changed a bit too, as we don’t have the index anymore. I don’t think it is terribly inefficient, but could be improved:

 worker.onmessage = ({ data }) => {
          from(lesson.words)
            .pipe(
              withLatestFrom(of(data)),
              filter(([word, data]) => word.imageId === data.imageId)
            )
            .subscribe(([word, data]) => {
              word.imageUrl = data.imageUrl;
            });
        };

Typolino – Web Worker

Just for fun I was thinking to add web workers to fetch the image download URLs and also the images in a web worker. If the URL is already there, perfect, if not wait for the web worker to complete its task.

Actually web workers are quite well supported and it is super simple to add your own web worker to an Angular application. I think the easiest way to describe a web worker would be a background script or some sort of thread that you can spawn that does some things.

If you have a strong Java background you are for sure familar with threads and the related issues if you misuse synchronization locks. Web workers are simpler as they provide a clear interface to communicate with your application. You don’t have to care about synchronization.

Create a Web Worker

To create a new web worker we can use our beloved ng g command.

ng g web-worker image-loader

This will create a more or less empty web worker that we can use. The web worker interface is really simple:

  • We can post messages to it
  • We can get messages from it

So what we would like to achieve: once we are starting a Typolino lesson we pass it the lesson to load the data in the background. Once we’ve got an image URL we try to fetch it. To be honest I’m not 100% sure if we win anything (maybe it’s even worse given the serialization overhead) as the operations are anyhow asynchronous by nature – but why not try it with web workers.

/// <reference lib="webworker" />

import { Lesson } from './lesson';
import { environment } from '@typolino/environments/environment';
import * as firebase from 'firebase';

const firebaseConfig = environment.firebase;
firebase.initializeApp(firebaseConfig);

addEventListener('message', ({ data }) => {
  const lesson = data as Lesson;
  lesson.words.forEach((word, index) => {
    firebase
      .storage()
      .ref(`${lesson.id}/${word.imageId}`)
      .getDownloadURL()
      .then((url) =>
        Promise.all([
          fetch(url, {
            mode: 'no-cors',
            cache: 'default',
          }),
          Promise.resolve(url),
        ])
      )
      .then(([response, url]) => {
        postMessage({
          index,
          url,
        });
      });
  });
});

Create the worker..

const worker = new Worker('@typolino/app/image-loader.worker', {
  type: 'module',
});

..and in the lesson.component

 ngOnInit(): void {
    this.route.paramMap
      .pipe(
        first(),
        map((params) => params.get('lessonId')),
        switchMap((lessonId: string) =>
          this.lessonService.selectLesson(lessonId)
        )
      )
      .subscribe((lesson) => {
        this.lesson = lesson;
        this.setupWord(this.lesson.words[0]); 

        worker.onmessage = ({ data }) => {
          this.lesson.words[data.index].imageUrl = data.url;
        };

        worker.postMessage(this.lesson);
      });
  }

It works! When we load a lesson we see that the browser is fetching all the URLs and images. As soon as the first image is available it should be rendered and the data is read from the cache as expected.

Conclusion

Using web workers is quite straight forward. Of course it’s a bit cheap as I’m only supporting modern browsers – but it is way more fun to code like this. When using Typolino the images are just there – for me it feels really fast when using the application. There are definitely other techniques but it was fun trying it out.

Typolino – Prefetching Assets

Caching has a big effect. But a first time visitor might still have to wait for certain resources to download.

To improve the experience for the users we can try to prefetch resources that we most probably will need later (e.g. after the login). In our example application Typolino the candidates are found easily:

  • the alphabet.mp3 audio file
  • the placeholder image
  • the card image (which besides making the UI look a bit fancier is totally useless)

For this we can add the prefetch instructions directly to our index.html

<link rel="prefetch" href="assets/alphabet.mp3" as="fetch">
<link rel="prefetch" href="assets/img_placeholder.jpg" as="image">
<link rel="prefetch" href="assets/lesson_1.jpg" as="image">

If we clear the cache and just navigate to our login page we will see that the browser is fetching the files as soon as it finds some time for it:

…and later when we actually need the files…

Find the full source code below in case something is a bit out of context.

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Typolino</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
  <link href="https://fonts.googleapis.com/css2?family=Baloo+Paaji+2&family=Roboto:wght@300&display=swap" rel="stylesheet">
  <link rel="prefetch" href="assets/alphabet.mp3" as="fetch">
  <link rel="prefetch" href="assets/img_placeholder.jpg" as="image">
  <link rel="prefetch" href="assets/lesson_1.jpg" as="image">
</head>

<body>
  <app-root></app-root>
</body>

</html>

Conclusion

pre-fetching resources can improve the overall UX of your application. It’s worth having a look at this capability. For Typolino it may look rather simple and I’m not so sure we can easily extend this to some Firebase queries as well (I don’t really want to construct the URL myself) but I’m sure you will find a chunk, image, script or any other resource that may be required in just a moment.