today   31 Oct 2017

access_time  12 minutes to read

The Name

The idea

It was a day like any other, scrolling through random tech articles while I wait for support requests to come through. That’s how I stumbled across the canvas tag new to HTML5. That’s when everything clicked, the gears started turning faster and faster. I’d found an idea, and not just any idea, a great idea!

I was going to make a pure javascript oekaki, no java required! This meant it would work on smart phones which were a relatively new thing at the time. This was going to be my major selling point.

Prototype

That night I went home and got cracking, this project was going to teach me Javascript, PHP, and MySQL. But first things first, I needed to prove I could do the most important part, get lines drawing on a page.

After a couple days I had a prototype, you can check it out here. If you’re on a phone you may have noticed that it didn’t work at all! That’s because I developed this before I had a touch screen smart phone, I had a Nokia E72.

This was the first ever image I got to save to the filesystem using PHP to convert the base64 encoded image into a PNG.

After about a month I finally got my first smart phone with a touch screen, it was the beautiful HTC Incredible S and I got to work getting my software to work on it. Within a week I had it drawing on the phone, albeit way zoomed out and unusable, but the proof of concept had been completed!

The name

With the proof of concept done I set about trying to come up with a name, now I admit, I ain’t the most creative person in the world so this will be a pretty short section.

Basically, I like ducks and I was building an oekaki. Yeah, I just combined quack and oekaki into oeQuacki because I like ducks. It’s just that simple.

I thought if it ever got popular my mascot would be some sort of duck artist because I thought that would be cute.

Now I’m going to spend the next few sections poking fun at my old code and demonstrating what not to do.

The Javascript

My favourite part of the javascript is how I manage the layers of canvas’. You’re actually seeing 4 different layered canvas’ when you’re drawing. It took me a reasonable while to land on this design, I initially had it drawing every layer seperately. This was obviously very bad performance wise, especially so on smart phones back then! Boy were they slow.

Top Canvas -> All layers above the current one
Temp Canvas -> Cleared every tick when drawing to make sure opacity works
Display Canvas -> The current layer
Bottom Canvas -> All layers below the current one

Now, I obviously didn’t realise I could dynamically add/remove classes because I have code like this for enabling and disabling transition effects.

this.displaylayer.style.transition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.displaylayer.style.WebkitTransition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.displaylayer.style.MozTransition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.displaylayer.style.OTransition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.bottomlayer.style.transition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.bottomlayer.style.WebkitTransition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.bottomlayer.style.MozTransition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.bottomlayer.style.OTransition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.templayer.style.transition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.templayer.style.WebkitTransition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.templayer.style.MozTransition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.templayer.style.OTransition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.toplayer.style.transition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.toplayer.style.WebkitTransition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.toplayer.style.MozTransition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";
this.toplayer.style.OTransition = "width .25s linear, height .25s linear, left .25s linear, top .25s linear";

God damn did I love whitespace too.

if ( b64Image.value == "" || threadText.value == "" || threadTitle.value == "" ) {
  
  submitButton.disabled = true;
  
} else {
  
  submitButton.disabled = false;
  
};

Although, very inconsistently because just below it is this.

if ( isNaN(imageWidth.value) || imageWidth.value > 3000 ) {
  imageWidth.value = 640;
};
if ( isNaN(imageHeight.value) || imageHeight.value > 3000 ) {
  imageHeight.value = 480;
};

All in all, I don’t hate my old javascript. It’s got fairly smartly contained classes, variables are decently named, and it’s all pretty well commented.

The things I’m not so fond of would have to be the sheer amount of whitespace, functions that do maybe a little too much, the fact that I didn’t use DOMContentLoaded, and that it’s all in two files instead of only 1 class per file.

The MySQL

Here is where shit gets ugly, if you’re squeamish I’d suggest just skipping to the next section.

For instance, this is the how I save a single favourite record.

function favouriteAdd( $ImageID, $UserID) {
  
  if ( $CheckStatement = $this->connection->prepare( "SELECT `ImageID` FROM `favourites` WHERE `UserID` = ? AND `ImageID` = ?" ) ) {
    
    $CheckStatement->bind_param( "ii", $UserID, $ImageID );
    $CheckStatement->execute();
    
    //Check this isn't already a favourite for this user

    if ( $CheckStatement->get_result()->num_rows == 0 ) {
      
      //Get the AuthorID, ImageTime

      $ImageStatement = $this->connection->prepare( "SELECT `CreationTime`, `UserID` FROM `images` WHERE `ImageID` = ?" );
      $ImageStatement->bind_param( "i", $ImageID );
      $ImageStatement->execute();
      $ImageResult = $ImageStatement->get_result()->fetch_array();
      
      //Get the Author name

      $AuthorStatement = $this->connection->prepare( "SELECT `Name` FROM `users` WHERE `UserID` = ?" );
      $AuthorStatement->bind_param( "i", $UserID );
      $AuthorStatement->execute();
      $AuthorName = $AuthorStatement->get_result()->fetch_array()[0];
      
      //Get the ThreadID/PostID

      $ThreadStatement = $this->connection->prepare( "SELECT `ThreadID`, `PostID` FROM `posts` WHERE `ImageID` = ?" );
      $ThreadStatement->bind_param( "i", $ImageID );
      $ThreadStatement->execute();
      $ThreadResult = $ThreadStatement->get_result()->fetch_array();
      
      //Time

      $savetime = round( microtime( TRUE ) * 1000, 0 );
      
      //Insert new favourite

      $InsertStatement = $this->connection->prepare( "INSERT INTO `favourites` ( UserID, ImageID, CreationTime, ImageTime, AuthorName, AuthorID, ThreadID, PostID ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? )" );
      $InsertStatement->bind_param( "iiiisiii", $UserID, $ImageID, $savetime, $ImageResult['CreationTime'], $AuthorName, $ImageResult['UserID'], $ThreadResult['ThreadID'], $ThreadResult['PostID'] );
      $InsertStatement->execute();
      
      return true;
      
    } else {
      
      return false;
      
    };
    
    
    
    
  } else {
    
    return false;
    
  };
  
}

Let me step you through this behemoth. The first thing I do is check whether or not this has already been favourited, this bit is fine, a bit verbose but fine since I’m not using an ORM of any kind.

Now things get a bit strange, I grab the image CreationTime and UserID in one query, the next I grab the… hang on, what the hell. Do I pass in the currently logged in user or the author of the image?

It really looks like in the first query I’m checking for my favourites, but then I get the author using the same UserID. Shit, it’s the logged in user.

if ( $DB->favouriteAdd( $ImageID, $Login->UserID ) ) {

So I guess I always save the logged in user as the author of the favourite, I guess I didn’t display that anywhere or I’d probably have noticed!

Uh, nope.

echo '<span class = "name">'. $row['AuthorName'] .'</span>';

Alrighty, I guess I just found a bug for real! Ah whatever, it was supposed to get the name of the author. Now we grab the thread and post id. The last query there saves it all into the database!

So what’s so bad about all that? Well, assuming I actually wanted a de-normalised database like that (spoilers, you don’t) I could have gotten all of that data with a single query using a couple of joins.

Which brings me to my next point, my database is terribly normalised, you saw above how I save the author’s name instead of their id and using a join to get all the data back out when viewing. If I had let users update their name I would have had to run two update queries, one for the user and one on the favourites table. That’s very inefficient and error prone.

The rest of my SQL pretty much follows that pattern and lots of N+1. Pretty terrible and I could definitely have achieved much nicer even without an ORM.

The PHP

Honestly it’s not great, but I’m not entirely sure how I’d make it better. I really never learnt PHP proper after this project, I moved onto Rails and my life was a lot better.

It’s filled with stuff like this though which I would definitely change to just take a parameter these days.

<?php
function DisplayHeader() {
  echo '<div id = "header">
    
    <div id = "logo"><a href = "/"><img src = "/style/header.png" alt = "oeQuacki" /></a></div>
    
    <div id = "buttons">
      <span id = "latest" class = "headerbutton"><a href = "/">Latest</a></span><span id = "favourites" class = "headerbutton"><a href="/favourites/">Favourites</a></span>
    </div>
    
  </div>'; //<span id = "hottest" class = "headerbutton">Hottest</span><span id = "following" class = "headerbutton">Following</span>
};

function DisplayHeaderNoButtons() {
  echo '<div id = "header">
    
    <div id = "logo"><a href = "/"><img src = "/style/header.png" alt = "oeQuacki" /></a></div>
    
  </div>';
};
?>

Cringe Time

You can check out the finished product here here, just note that porting it into MaterializeCSS has broken how it works on mobiles, that’s not the fault of MaterializeCSS but of my janky phone code that relied on some very naive assumptions. It does work a treat on desktop though, so do give it a try there!

The last thing I’ll leave you with is my recording that shows off the features of oeQuacki that I put very little effort into. I really should have done a couple more takes, written a script, and cut it into logical segments.

But it gives you a good idea of how my site looked when I launched it and how it all worked in action. You honestly wouldn’t know how terribly it’s built from using it!