I turned GitHub into my own Billboard
Tags: github, chromeextension, javascript, 100daysofcode
Published: 2022-01-18 03:43:10

The day I made my GitHub account, I immediately realized the possibilities of the Commit Heatmap being a colorful grid. I wanted to show some personality, like a smiley face or a friendly greeting, welcoming potential employers to my page. Unfortunately, Git had other ideas, and soon my Heatmap looked more like toxic sludge than a welcome mat.

![my messy commit history](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rgcylm2ue4p3fnk16szi.png)

No matter, I have the power of Inspect Element! I'll just manually adjust each cell. After about 10 minutes into this plan, I had restarted 3 times without completing a single letter. It was clear human input is not reliable enough to complete this task. Good thing I know JavaScript and have several hours to burn!

The first step on this project was to redefine the alphabet. The GitHub commit history is just a grid, so I needed an alphabet that can fit in a grid. Credit to @hgcummings on GitHub for [this library](https://github.com/hgcummings/pixel-fonts). It's exactly what I needed, so I copied and pasted the object into my project.

![hgcumming's font structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02dokedoylmznixleoig.png)

When I was trying to manually change each cell, I got well acquainted with the commit history HTML. The grid is made of 52 groups with 7 `<rect>` elements each. Each cell has a `"data-level"` attribute between 0 and 4, this determines what shade of green the cell is.

Immediately I noticed a problem. The commit history is grouped vertically, by weeks. But, my font object is sorted horizontally. If I carried on from here my message would come out sideways! I spent a few minutes thinking about how to proceed, how could I reorganize the arrays into the structure I needed? In the end, I decided it was best to just rewrite the data by hand.


![my font data structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/izuocns31g4yetbfcitw.png)

While it certainly would be possible to write a sorting algorithm, I think this choice saved me time in the long run. It also gave me the minor performance boost of shipping and running less code. I also noticed many letters of the alphabet have straight vertical lines (26 lines to be exact). Instead of writing `[1,1,1,1,1]` several times, I opted to define it as a variable to be reused. This saved me a small amount of time.

```
let message = prompt("What do you want to say?", "")

message
 .toUpperCase()
 .split("")
 .map((character) => Letters[character])

//this prompts the user for an input
//then turns that input into an array of pixels
```
I finally had all the pieces I needed. All that's left to do is plug the user's input into the commit history's grid. This task is easier said than done. The commit history is an array of arrays of cells, and the user message is an array of arrays of pixels. It seems simple in theory, but connecting the right cell with the right pixel is a bit complicated.

My first idea was to access each cell by its `"data-date"` attribute. This solution is probably the simplest, but ultimately flawed. The commit history changes each week when a new row is added, eventually, the cell I was accessing would no longer exist. Making the text scroll would be incredibly complicated, computing which cell is where in the grid and how many days are between adjacent cells. This solution quickly was abandoned.

To solve this, I had to think of programming in the most basic terms. What I like to think all programming is at its core is Data Manipulation. HTML is just data, and JavaScript is just a way to manipulate data. With this mindset, I could make a plan.


![a visualization of the commit history's data structure compared to the pixelated message's data structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9yjkhhyudemkgnf0ahym.png)

Visualizing the data like this helps me to conceptualize how to connect it. In this format you can clearly see how each grid cell has a corresponding pixel value derived from the user message. After understanding the data structure, all that's left is writing the code.
```
const writeToBillboard = (message, rowIndex) => 
  clearBoard();
  let currentWeek = rowIndex;
  message
    .toUpperCase()
    .split("")
    .map((character) => Letters[character])
    .map((pixelLetter, index) => {
      pixelLetter.map((pixelLine, index) => {
        let week = weeks[currentWeek];
        pixelLine.map((pixel, index) => {
          if (currentWeek >= 0 && currentWeek < 53) {
            if (Array.from(week.children)[index + 1]) {
              Array.from(week.children)[index + 1].setAttribute(
                "data-level",
                pixel ? "4" : "0"
              );
            }
          }
        });
        //move to next row
        currentWeek += 1;
      });
      //skip a line after a letter is complete
      currentWeek += 1;
    });
};{
```
First, we convert the user input string into an array of pixels. Next, we access each pixel letter, then each pixel line. Then we define the `week` as the current row of cells we're accessing. After that, we access each cell, and we're ready to overwrite the data! We do a couple of checks to make sure we're on a row that exists and a cell that exists (otherwise it throws about 30 errors per second), then we set the cell's `data-level` attribute to a 4 or 0, depending on the pixel's value. Finally! We've put a message on our commit history!

The hard part is over, but it's not quite ready to push yet. We still need to make it scroll. This is simpler than it sounds. With `setInterval()` we can call `writeToBillboard()` every 100ms, and increment the `rowIndex` down one each time. This writes the message one row to the left ten times per second, giving the illusion of a moving image.
```
const scrollWrite = () => 
  let rowIndex = 53;
  let interval = setInterval(() => {
      writeToBillboard(message, rowIndex)
    //check if rowIndex has extended far enough for all
    //characters
    rowIndex < 0 - message.split("").length * 4
      ? //if true: reset
        (rowIndex = 53)
      : //if false: keep going
        (rowIndex -= 1);
  }, 100);
};{
```
We define `rowIndex` as 53 to start at the row furthest to the right. We call `writeToBillboard()`, passing in the message and `rowIndex` as arguments. After the message is written, we check if all characters have passed the left side of the grid. Each character takes 4 rows to write, including the gap between characters. So by subtracting the number of characters multiplied by 4, we know what `rowIndex` will be far enough to have passed all characters. If `rowindex` has passed this number, we reset `rowIndex` to 53, if not we subtract one so the message moves a little bit to the left.

The final step in the process is to turn our code into a Chrome Extension. Chrome extensions require a `manifest.json` to define the properties of the code and let Chrome know what to do with it. We have to name the extension, version it, and give it a description. In `"content_scripts"`, we tell the extension to run content.js when on any github.com page.
```
{
  "manifest_version": 3,


  "name": "GitHub Billboard",
  "version": "1.0.0",
  "description": "Turn your GitHub commits into a marquee billboard",
  "icons": {
    "32": "icon32.png",
    "128": "icon128.png"
  },


  "content_scripts": [
    {
      "js": ["content.js"],
      "matches": ["https://github.com/*"]
    }
  ]
}
```
And with that, we're ready to push our code, you can check it out [here](https://github.com/LodenH16/GitHub-Billboard). This project was a lot of fun to work on and I was amazed at the power and simplicity of Chrome Extensions. I was impressed by how quickly I was able to get up and running with a custom extension. I'll be making more in the future!