Award winning stuff: the Jexia Image Editor

Maarten Bezemer · Jul 19, 2019

Jexia went live on June 19th 💪🏻🙌🏾! We performed an internal hackathon to celebrate this achievement and build authentic applications utilizing our platform. In this blog post we describe our hackathon project.

By Maarten Bezemer and Tarik Demirci

Project and the features

We built a collaborative image editor which allows multiple people to work on the same canvas. The canvas is updated by other people's changes in real time, so if someone makes a change it will be shown directly on the canvas of all other users.

On the canvas, users can create shapes such as circles and rectangles, images and texts. The shapes have properties like rotation, size, color, and effects (shadow, glow), which can be set.


The editor also supports importing and exporting the image to PNG, SVG, etc. User can even see a t-shirt preview of the canvas (ordering the t-shirt is not included though…)!

Tech stack we used

Since we are both backend developers, we don’t have much experience with browser applications. So we did some quick Googling to figure out how a   HTML5 canvas works and which framework seemed easiest to use. We went for Fabric.js[1] as it looks nice and complete, while easy to use. In order to have a quick ramp-up for the hackathon we decided to use fabric-image editor[2] as a base.

Even though having these starting points, it was quite a challenge to do a project which mainly requires JavaScript development in the limited time we had. So we ended up using Material, Bootstrap and some custom CSS for the UI, combining different HTTP clients and JavaScript techniques like promises, callbacks, etc… 😁

In the end we got this ‘peculiar’ tech stack working, without having (too much) conflicts, and the editor was reasonably stable to use. As a matter of fact, we used the editor itself for the final presentation instead of more commonly used presentation software!

Jexia Features we used

In order to have a collaborative application, user accounts and storage are needed. So we created a Jexia project, created a dataset to store all image shapes and policies so users can access this dataset.

The Jexia Image Editor has a signup screen to create accounts. With a simple API call, the account is created:

fetch(`https://${projectAddress}/ums/signup`, {
    method: 'post',
    body: JSON.stringify({
        email: email,
        password: password,
    })
}).then(/* process response */)

In a similar way the user is able to sign in with his account, the API returns an (access) token and a refresh token. (The refresh token is used when the access token is expired.)

As said the dataset holds all shapes. Since each shape has its own properties (circle has radius, text has font, size, style, etc.) we did not define fields in the dataset, but instead decided to use its schemaless feature. So with some simple code (and another HTTP client 😜) we can serialize the shape and store it:

data = JSON.stringify(shape.toJSON());
 
req = https.request({
    hostname: projectAddress,
    port: 443,
    path: '/ds/' + datasetName,
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': "Bearer " + token,
        'Content-Length': data.length
    }
}
// handle response (e.g. store returned id in the shape so we can find it back)
// write serialized shape
req.write(data);
req.end();

Updating shapes is similar: serialize shape (including id) and use HTTP PUT method. And to delete we can simply make a HTTP DELETE request to https://${projectAddress}/ds/${datasetName}/${shapeId}

Final part is to update the image if some other user modified it. We used Real Time Communication (RTC) feature of Jexia. RTC uses websocket protocol to notify browser. Whenever a shape is updated, all clients are notified immediately. After receiving the event, client fetches the shape from dataset and updates its canvas. Subscribing for new RTC events from the JavaScript is pretty easy:

websocketUrl = `wss://${projectAddress}/rtc?access_token=${token}`;
websocketConn = new WebSocket(websocketUrl);	

After starting the websocket connection, we let Jexia know which events we are interested in:

websocketConn.onopen = () => {
	websocketConn.send(JSON.stringify({
   	   "type": "command",
   	   "data": {
   	 	"command": "subscribe",
   	 	"arguments": {
   	   		"action": [
   	     			"created",
   	     			"updated",
   	     			"deleted"
   	   		],
   	   		"resource": {
   	     			"type": "ds",
   	     			"name": datasetName
   	   		}
   	 	}
   	   }
   	 }));
}

Now we can wait for events and take actions depending on their type:

websocketConn.onmessage = e => {
	var event = JSON.parse(e.data);
	switch(event.data.action) {
    	case "created":
        	for(var i=0; i<event.data.data.length; i++) {
            	// handle creation of new shape.
        	}
        	break;
    	case "updated":
        	for(var i=0; i<event.data.data.length; i++) {
        	 	// handle shape update.
          }
        	break;
    	case "deleted":
        	for(var i=0; i<event.data.data.length; i++) {
            	// handle a deleted shape.
        	}
        	break;
	}
}

Final

We won 3rd prize🥉! So we are quite happy with our project and the things we achieved. Jexia platform worked quite nicely, we found some issues with CORS, rate limiting, and efficiency/usability of the consumption APIs (fixed first day after hackathon 😉), but nothing serious enough to block us from quickly creating this nice result!

Footnotes:
[1] Fabric.js is a javascript library to manage browser canvas.
[2] https://github.com/danielktaylor/fabric-js-editor