JSFiddle - React, Tailwind, and code Playground

by Gwyn Milcote

HTML

<h1>Map Painter</h1>
<p>Load a tileset image. Choose the size of your map. Click on the tileset to select a terrain tile, then choose a layer to paint on. A layer can be cleared, or filled with a certain terrain. Use <b>Import</b> and <b>Export</b> buttons at the bottom to load/get JSON code.</p>

Tileset URL: <input type='text' id='load-tileset-url' value='http://www.rpg-studio.org/wiki/images/9/92/Tileset.png'> <button id='load-tileset-btn'>Load</button>
<br>
Pixel size of each tile: <input type='number' id='load-tile-size' value='32'> | How many tiles per row? <input type='number' id='load-tile-row' value='8'>

<p id='load-tileset-message'></p>

<div id='container'>
    <div class='canvas-container'>
        <canvas id='canvas-tileset'></canvas>
    </div>
    
    <div id='terrain-info'></div>
    
    
    <div id='layer-buttons'>
        Show all layers? <input type='checkbox' id='combine-layers' checked>
         --- 
        <!--<button class='layer-button' id='layer-button-0' data-layer='0'>Combined</button>-->
        <button class='layer-button' id='layer-button-1' data-layer='1'>Layer 1</button>
        <button class='layer-button' id='layer-button-2' data-layer='2'>Layer 2</button>
        <button class='layer-button' id='layer-button-3' data-layer='3'>Layer 3</button>
        <button class='layer-button' id='layer-button-4' data-layer='4'>Layer 4</button>
        <button class='layer-button' id='layer-button-5' data-layer='5'>Layer 5</button>
        
        --- Show grid? <input type='checkbox' id='show-grid' checked>
    </div>
    
    <div id='layer-edit-info'></div>
    <div>
        Width: <input type='number' id='select-rows' min='10' max='100' value='10'> 
        Height: <input type='number' id='select-cols' min='10' max='100' value='10'> 
        Visual tile size: <select id='select-tileSize'> 
            <option>16</option>
            <option>24</option>
            <option selected>32</option>
            <option>40</option>
         ...

CSS

div, img, button {
    box-sizing: border-box;
}

input[type='number'] {
    width: 50px;
}

textarea {
    width: 90%;
    display: block;
    margin: auto;
    max-height: 150px;
    overflow: auto;
}

.canvas-container {
    width: 100%;
    padding: 20px;
    min-height: 200px;
    max-height: 600px;
    overflow: auto;
    background-color: silver;
    text-align: center;
}
#canvas-map, #canvas-tileset {
    cursor: pointer;
    border: 2px solid black;
    line-height: 0px;
    margin: auto;
}


/*.tileset-container {
    width: 100%;
    padding: 10px;
    min-height: 100px;
    max-height: 300px;
    overflow: auto;
    background-color: silver;
    text-align: center;
}*/

.active-layer {
    background-color: gold;
}

JavaScript

class MapEditor {

    constructor(){
        
        //for(let i = 1; i < 6; i++){
        //    this.createCanvas(i);
        //}
        //this.createCanvas("Grid");
        //this.createCanvas("Tileset");
        
        this.canvasTileset = document.getElementById("canvas-tileset");
        this.ctxTileset = this.canvasTileset.getContext("2d");

        this.canvasMap = document.getElementById("canvas-map");
        this.ctxMap = this.canvasMap.getContext("2d");
        

        this.map = [[], [], [], [], []];
        this.rows = 10;
        this.cols = 10;
        
        // tileset is currently 32px
        this.mapTileSize = 32; // seen on map
        this.selectedLayer = 1;
        this.terrain = 1; // for painting
        
        this.displayGrid = true;
        this.combineLayers = true;
        
        this.loadTileset(); // and then launch
    }
    
    // Setup
    /*
    createCanvas(name){
        this["canvas" + name] = document.createElement("canvas");
        this["ctx" + name] = this["canvas" + name].getContext("2d");
    }
    
    appendCanvas(name, target){
        let div = document.getElementById(target);
        div.innerHTML = "";
        div.appendChild(this["canvas" + name]);
    }
    */
    // Editing
    
    
    
    
    
    loadTileset(){
        let img = new Image(),
            _this = this,
            url = $("#load-tileset-url").val(),
            size = $("#load-tile-size").val(),
            perRow = $("#load-tile-row").val();
        
        if(!url || url.length < 8){
            $("#load-tileset-message").html("Are you sure that's a valid url?");
            return;
        }
        if(size < 1){
            $("#load-tileset-message").html("Invalid tile size.");
            return;
        }
        if(perRow < 1){
            $("#load-tileset-message").html("Invalid number of tiles per row.");
            return;
        }
        
        img.onload = function(){
            _this.tileSize = size;
      ...