Map Maker Online: Creating a Map Editing Software

Andrew Lukes
9 min readNov 17, 2023

--

Start of the next software development journey

Hi, this day marks the announcement of my new in-browser editing application. I've been working on it for the past two weeks. We will look at the idea, execution, and progress, as well as plans for the future,

So let's dive in!

· Inspiration
· Goal Of My Project
· Getting Started:
· Tech Stack
React.js:
Next.js:
React/Signals:
MUI Library
TypeScript:
Node.js:
· Setting Up Development Environment
· Challenges and Solutions
Passing Information Around
Switching Canvases:
Implementing state
Debugging:
· Current Features
Drawing
Background Layer
Marker Layer:
Frontline Layer:
· Live Demo
· Your Feedback Matters
· Conclusion
· About the Author

Inspiration

Some months ago a YouTube history creator called Eastory, who focuses on creating infographic maps, published a video titled Eastory 400 000 Subs Q&A. In the video, he showcased some of the tools and work that go into them. The whole development process seemed needlessly complicated featuring multiple software and hundreds of hours of work. That is why I decided to get my take on creating an app that would simplify the production of animated maps.

Example of Eastory's work from Eastern Front animated: 1941

Goal Of My Project

The aim is to create an editing software, that would be able to combine the key features of Blender with GIMP and create a fast-paced development experience right in the browser

The app will be composed of multiple layers of canvases with their own settings and functionality and simple transition. They will be compiled into frames on a timeline. The files from the timeline could be then compiled into a single file to create an animation.

How will the app work

I am planning to put demo versions of the projects online during development, so you can test out the newest features.

Getting Started:

Tech Stack

Aiming the development on the browser allows me to use the following technology:

React logo

React.js:

  • React is a declarative, efficient, and flexible JavaScript library for building user interfaces. It allows developers to create reusable UI components and manage the state of an application efficiently. It is the foundation of frontend development, providing a powerful and efficient way to build interactive and dynamic user interfaces, especially when combined with Next.js.
Next.js logo

Next.js:

  • Next.js is a React framework that simplifies the development of server-side rendered (SSR) and statically generated web applications. It offers automatic code splitting, a fast refresh feature for development, and a strong convention for file-based routing. Additionally, Next.js provides excellent performance optimization out of the box. I have chosen it for its seamless integration with React, efficient server-side rendering capabilities, and structured approach to building complex web applications with ease.
Preact logo

React/Signals:

  • React Signals is a state management library for React applications. It facilitates a unidirectional data flow and simplifies the handling of state changes in React components. It is much more intuitive than React hooks which are the standard when working inside the React framework.
Left: Mui library logo Right: Some MUI predesigned components

MUI Library

  • Excellent library for creating a unified design across the whole project. Its main features are pre-styled HTML components, the creation of custom themes, and seamless integration with popular JavaScript frameworks.
Typescript logo

TypeScript:

  • TypeScript is a superset of JavaScript that adds static typing to the language. It helps catch common errors during development, improves code readability, and provides better tooling support. Inside the React environment, it works even better, thanks to the lack of need to run additional cmd commands for compiling files.
Node.js logo

Node.js:

  • Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine. It allows server-side execution of JavaScript, enabling the development of scalable and high-performance server applications. It will serve as the backend runtime for my application, providing a non-blocking, event-driven architecture that aligns well with JavaScript.

Setting Up Development Environment

First I have created the skeleton of my app using the Next.js initialization command.

npx create-next-app@latest mapping-project

Then I proceeded to create the folders:

app
┣ canvasEditor
┃ ┣ layers
┃ ┣ settings
┃ ┣ theme
┃ ┣ Globals.tsx
┃ ┣ page.tsx
┣ components
┃ ┣ drawing
┃ ┣ frontline
┃ ┣ markerLayer
┃ ┣ settings
┃ ┗ utility
types
┣ GeometryTypes.tsx
┗ OtherTypes.tsx

The structure might change with the growth of the application, but for now it works fine.

After that, I installed the libraries and was ready to write some code!

{
"name": "mapping-software",
"version": "0.1.0",
"private": true,
"configurations": [
{
"name": "Next: Chrome",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack:///./*": "${webRoot}/*"
}
}
],
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/material": "^5.14.17",
"@preact/signals": "^1.2.1",
"html2canvas": "^1.4.1",
"next": "14.0.0",
"react": "^18",
"react-dom": "^18",
"react-dropzone": "^14.2.3",
"uuidv4": "^6.2.13"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10",
"eslint": "^8",
"eslint-config-next": "14.0.0",
"postcss": "^8",
"postcss-preset-env": "^9.2.0",
"tailwindcss": "^3.3.5",
"typescript": "^5"
}
}

Challenges and Solutions

Passing Information Around

I am using both React built-in useContext hook as well as Preact Signals. The first is useful when handling variables that change quickly, since I have found, that changing preact signals contains a small delay. On the other hand, signals can simplify exporting of value, so I use them whenever their main disadvantage isn't a problem

export const CanvasProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const canvasRef = useRef<HTMLCanvasElement | undefined>(null);
const markerCanvasRef = useRef<HTMLCanvasElement | null>(null);
const frontlineCanvasRef = useRef<HTMLCanvasElement | null>(null);
const compiledCanvasRef = useRef<HTMLCanvasElement | null>(null);
const [frontLines, setFrontlines] = useState<FrontlineData[]>([])
const backgroundCanvasRef = useRef<HTMLCanvasElement | null>(null);
const [globalData, setGlobalData] = useState<GlobalDataType>({ mouseDownTime: 0 });

const canvasContextValue: CanvasContextType = {
canvasRef,
markerCanvasRef,
frontlineCanvasRef,
backgroundCanvasRef,
compiledCanvasRef,
frontLines,
setFrontlines
};



const updateGlobalData:UpdateGlobalDataType = (paramName , paramValue ) => {
setGlobalData((prevData) => ({
...prevData,
[paramName]: paramValue,
}));
};

const globalDataContextValue: GlobalDataContextType = {
GlobalData: globalData,
updateGlobalData,
};

return (
<CanvasContext.Provider value={canvasContextValue}>
<GlobalDataContext.Provider value={globalDataContextValue}>
{children}
</GlobalDataContext.Provider>
</CanvasContext.Provider>
);
};
export const settings: Settings = signal({
radius: 5,
color: `#000000`,
lineType: "squared",
activeLayer: "draw",
canvasSize: { x: 800, y: 600 },
markerSettings: {
width: 40,
color: `#000000`,
textColor: `#ffffff`,
topValue: "X",
bottomValue: "Y",
imageURL: null,
popularMarkerColors: [],
},
popularColors: [],
canvasZindexes: { marker: 10, draw: 10, background: 0, frontLine: 10, compiled:0 },
});

export const frontLineSettings: FrontLineSettings = signal({
insertionPointIndex: -1,
frontLineColor: "#0000ff",
activeFrontLine: null,
frontLines: [],
controlPointRadius: 5
});

export const markers:MarkerArraySignal = signal([])


export const backgroundImage = signal<File | null>(null);

Switching Canvases:

The app has different editing layers for editing maps:

  • drawing layer(basically ms paint)
  • frontline editing layer(for easily creating and moving lines)
  • unit marker layer(for placing units on the map)
  • background-image layer(to set the background map)
  • compiled layer(to see the final look of the canvas)

All layers have a different default zIndex which is increased when I make the layer active using this selection panel

Select button for switching layers

All the other layers are then moved to the background and their opacity decreases to 40%.

Frontline layer on top, the rest in the background with decreased opacity

Implementing state

I have found out that there is a useful React hook called reducer, that can be used as a state machine handler. A state machine is basically a code structure where your algorithm uses a different set of commands based on the value of a certain variable. I have already talked more about them in this article.

const reducer: React.Reducer<DrawingState, DrawAction> = (state, action) => {
// console.log("SWITCHING TO ", action.type, action?.payload);
switch (action.type) {
case "DRAW":
const drawPayload = action.payload as DrawPayload;
console.log(drawPayload);
drawPayload.drawFunction(
drawPayload.drawArgs?.ctx,
drawPayload.drawArgs.x,
drawPayload.drawArgs.y,
drawPayload.drawArgs.radius,
drawPayload.drawArgs.color
);
return DrawingState.Drawing;

case "ERASE":
const erasePayload = action.payload as ErasePayload;
erasePayload.eraseFunction(erasePayload.eraseArgs);
return DrawingState.Erasing;

case "MOUSE_UP":
case "MOUSE_LEAVE":
return DrawingState.Idle;

case "ENTER_BUCKET_MODE":
return DrawingState.BucketFill === state
? DrawingState.Idle
: DrawingState.BucketFill;

default:
console.error("INVALID ACTION: ");
return state;
}
};

**State machines for my drawing layer

Debugging:

A big problem with debugging in React is, that the page constantly refreshes. When you want to print a variable in a component like this, it will spam your console with messages.

import React from 'react'

const foo = () => {
console.log('important info')
return (
<div>foo</div>
)
}

export default foo

To counter that I have created a debugging window, that shows the necessary information right in the app.

import React from 'react';

type DebugInfoProps = {
data: Record<string, any>;
};

const DebugInfo: React.FC<DebugInfoProps> = ({ data }) => (
<div style={{ position: 'fixed', top: '10px', right: '10px', padding: '10px', background: 'rgba(255, 255, 255, 0.7)', zIndex:"1000" }}>
<p>Debug Information:</p>
{Object.entries(data).map(([key, value]) => (
<p key={key}>{`${key}: ${JSON.stringify(value)}`}</p>
))}
</div>
);

export default DebugInfo;

Then I mounted it on the page.tsx and added the information I wanted to view


return (
< >
<DebugInfo
data={{
radius: settings.value.radius,
// canvasState: canvasState,
color: settings.value.color,
layer: settings.value.activeLayer,
mousePosition: mousePosition,
mousDownTime: elapsedTime,
numMarkers:markers.value.length
// activeFrontLine: frontLineSettings.value.activeFrontline?.idNum,
// GlobalData: GlobalData.mouseDownTime,
}}
/>
<CanvasSettings />
<DrawingCanvas />
<Timeline />
</>
);
};

So now everything shows inside of a rectangle in the top right corner.

debug window

Current Features

Drawing

In the drawing layer, you can use basic paint settings like

  • pencil width
  • choosing different line shapes
  • eraser
  • changing color
  • defining favorite colors

In the future, I want to add other common tools like bucket fill, and ruler, or draw more geometrical shapes like triangles.

Drawing Layer

Background Layer

This layer allows you to choose the background image (preferably a map) to draw on. Because it is on a different layer, you can draw whatever you want on it without mutilating it.

I am planning to add these features:

  • resizing of background images
  • basic editing of background images
Background Layer

Marker Layer:

This layer is dedicated to adding units, you can place them wherever you want, move them wherever you want, and customize their individual appearance through the settings.

Currently, you can change their color, text, and background image.

Marker Layer

Frontline Layer:

The frontline layer allows you to define a line on the map by placing points and then moving them around to the desired shape. You are even able to create cyclic lines as well as changing their color and width

Frontline editor

Live Demo

To check out the current state, check out this webpage > Live Demo

Your Feedback Matters

I would be extremely grateful for any feedback possible. If you have any feature that you would like to see in the app you can do it here.

If you want to watch the development more closely you can check out my new discord here > Map Maker Online

Discord logo

Conclusion

This is the current state of the project! We have looked at the setup of the project environment, the architecture, and the first features of the editor. These are some of the new features you can look forward to:

  • Curved Lines
  • Bucket Fill
  • Quick Erase
  • Switching between frames
  • and more…

If you are interested I once again suggest joining my discord server and sharing your opinion and feedback. I hope I will see you in the next diary entry!

Photo by Carlos Muza on Unsplash

About the Author

My name is Andrew. I have been coding for the past 2 years. I am primarily focused on coding in JavaScript/react with some knowledge of Python and GD script for game development. I have already finished projects like a fullstack web app for my school magazine a chess clone or a top-down randomly generated game. In the future, I want to create easy-to-use web applications utilizing the power of AI and prompt engineering.

--

--

Andrew Lukes

Introducing Andrew Lukes: a Prague web dev & language enthusiast who shares his ideas on Medium. You can visit andrewebdev.online to see some of my projects