Add undo/redo feature. Fix broken save button

master
Joshua Sigona 5 years ago
parent 596e680237
commit 3442541741
  1. 186
      canvas.js
  2. 8
      style.css

@ -8,7 +8,20 @@ var customColorToolbar=false;
var fillTool = false; var fillTool = false;
var floodFillInProgress=0; var floodFillInProgress=0;
var changedPixels = {}; //pixel data for all changed pixels in this step.
/*
STEPTYPE:
ADD //Pixel was added. Remove all pixels in this object.
//Each pixel will have an old_pos_X_Y indicating their old color. Do the reverse to undo. The pixel list itself will be in "PIXELS": X,Y,X2,Y2,X3,Y3, etc.
FILL //Fill was done. A color is provided by "backcolor" and a pixel containing the click and source color. To undo it, fill with the backcolor instead of the source color.
*/
var pixelStates = []; //Last 20 pixel states stored here.
var currentPixelState = -1; //Which pixel state we're on. Max out at 20.
document.addEventListener("DOMContentLoaded",()=>{ document.addEventListener("DOMContentLoaded",()=>{
var undoButton = document.createElement("button")
var redoButton = document.createElement("button")
class Coordinate{ class Coordinate{
constructor(box) { constructor(box) {
@ -24,6 +37,11 @@ document.addEventListener("DOMContentLoaded",()=>{
} }
} }
//Returns a coordinate class.
var getCoordinates = (box)=>{
return new Coordinate(box);
}
function insertAfter(newNode, existingNode) { function insertAfter(newNode, existingNode) {
existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling); existingNode.parentNode.insertBefore(newNode, existingNode.nextSibling);
} }
@ -35,6 +53,63 @@ document.addEventListener("DOMContentLoaded",()=>{
var rawColors = rgbvalue.replace("rgb(","").replace(")","").split(","); var rawColors = rgbvalue.replace("rgb(","").replace(")","").split(",");
return "#" + componentToHex(Number(rawColors[0])) + componentToHex(Number(rawColors[1])) + componentToHex(Number(rawColors[2])); return "#" + componentToHex(Number(rawColors[0])) + componentToHex(Number(rawColors[1])) + componentToHex(Number(rawColors[2]));
} }
function Undo() {
//Execute the reverse.
var state = pixelStates[currentPixelState];
console.log("Undo "+JSON.stringify(state))
switch (state["STEPTYPE"]) {
case "ADD":{
//Take each pixel and color it its previous color.
var pixels = state["PIXELS"].split(",")
for (var i=0;i<pixels.length;i+=2) {
var pixelX = pixels[i];
var pixelY = pixels[i+1];
var oldColor = state["old_pos_"+pixelX+"_"+pixelY]
var cell = document.getElementById("pos_"+pixelX+"_"+pixelY)
cell.style.background = oldColor;
}
}break;
case "FILL":{
floodFill(state["PIXELX"],state["PIXELY"],document.getElementById("pos_"+state["PIXELX"]+"_"+state["PIXELY"]).style.background,state["backcolor"])
}break;
}
redoButton.disabled = false;
undoButton.disabled = true;
if (currentPixelState>0) {
currentPixelState--;
undoButton.disabled = false;
}
}
function Redo() {
//Execute the re-reverse.
var state = pixelStates[currentPixelState];
console.log("Redo "+JSON.stringify(state))
switch (state["STEPTYPE"]) {
case "ADD":{
//Take each pixel and color it its previous color.
var pixels = state["PIXELS"].split(",")
for (var i=0;i<pixels.length;i+=2) {
var pixelX = pixels[i];
var pixelY = pixels[i+1];
var newColor = state["pos_"+pixelX+"_"+pixelY]
var cell = document.getElementById("pos_"+pixelX+"_"+pixelY)
cell.style.background = newColor;
}
}break;
case "FILL":{
floodFill(state["PIXELX"],state["PIXELY"],document.getElementById("pos_"+state["PIXELX"]+"_"+state["PIXELY"]).style.background,state["newcolor"])
}break;
}
redoButton.disabled = true;
undoButton.disabled = false;
if (currentPixelState<pixelStates.length-1) {
currentPixelState++;
redoButton.disabled = false;
}
}
var canvas = document.getElementsByClassName("canvas")[0]; var canvas = document.getElementsByClassName("canvas")[0];
var toolbar = document.getElementsByClassName("toolbar")[0]; var toolbar = document.getElementsByClassName("toolbar")[0];
@ -52,11 +127,23 @@ document.addEventListener("DOMContentLoaded",()=>{
var MouseListener = (e)=>{ var MouseListener = (e)=>{
e.preventDefault(); e.preventDefault();
if (!fillTool) { if (!fillTool) {
if (e.target.tagName==="TH") {
if (mouseState>=0) { if (mouseState>=0) {
if (mouseState<2 && e.target.tagName==="TH") { var mycoords = getCoordinates(e.target)
if ("PIXELS" in changedPixels) {
changedPixels["PIXELS"]+=","+mycoords.x+","+mycoords.y
} else {
changedPixels["PIXELS"]=mycoords.x+","+mycoords.y
}
changedPixels["old_"+e.target.id]=e.target.style.background;
if (mouseState<2) {
e.target.style.background=selectedColor; e.target.style.background=selectedColor;
changedPixels[e.target.id]=selectedColor;
} else { } else {
e.target.style.background="white"; e.target.style.background="white";
changedPixels[e.target.id]="white";
}
changedPixels["STEPTYPE"]="ADD"
} }
} }
} }
@ -71,20 +158,60 @@ document.addEventListener("DOMContentLoaded",()=>{
if (e.target.tagName==="TH") { if (e.target.tagName==="TH") {
var coords = getCoordinates(e.target) var coords = getCoordinates(e.target)
floodFill(coords.x,coords.y,e.target.style.background,selectedColor) floodFill(coords.x,coords.y,e.target.style.background,selectedColor)
changedPixels["STEPTYPE"]="FILL"
changedPixels["backcolor"]=e.target.style.background
changedPixels["newcolor"]=selectedColor
changedPixels["PIXELX"]=coords.x
changedPixels["PIXELY"]=coords.y
} }
} }
toolbar.style.visibility = "visible"; toolbar.style.visibility = "visible";
//console.log(changedPixels);
if ("STEPTYPE" in changedPixels) {
//Add changedPixels to the current pixelState.
if (currentPixelState!==-1 && currentPixelState!==pixelStates.length-1) {
//Delete everything after this.
if (currentPixelState===0) {
pixelStates = [];
} else {
pixelStates = pixelStates.slice(0,currentPixelState+1)
}
redoButton.disabled = true;
}
if (pixelStates.length<20) {
pixelStates.push(changedPixels);
undoButton.disabled = false;
redoButton.disabled = true;
} else {
pixelStates = pixelStates.slice(1)
pixelStates.push(changedPixels)
}
changedPixels = {}
console.log(pixelStates)
currentPixelState = pixelStates.length-1;
}
} }
var MouseStateDown = (e)=>{ var MouseStateDown = (e)=>{
e.preventDefault(); e.preventDefault();
mouseState = e.button; mouseState = e.button;
if (!fillTool) { if (!fillTool) {
if (e.target.tagName==="TH") { if (e.target.tagName==="TH") {
var mycoords = getCoordinates(e.target)
if ("PIXELS" in changedPixels) {
changedPixels["PIXELS"]+=","+mycoords.x+","+mycoords.y
} else {
changedPixels["PIXELS"]=mycoords.x+","+mycoords.y
}
changedPixels["old_"+e.target.id]=e.target.style.background;
if (e.button===0) { if (e.button===0) {
e.target.style.background=selectedColor; e.target.style.background=selectedColor;
changedPixels[e.target.id]=selectedColor;
} else { } else {
e.target.style.background="white"; e.target.style.background="white";
changedPixels[e.target.id]="white";
} }
changedPixels["STEPTYPE"]="ADD"
} }
} }
toolbar.style.visibility = "hidden"; toolbar.style.visibility = "hidden";
@ -282,7 +409,7 @@ document.addEventListener("DOMContentLoaded",()=>{
finalData["ROWS"]=ROWS; finalData["ROWS"]=ROWS;
finalData["COLS"]=COLS; finalData["COLS"]=COLS;
finalData["CUSTOMCOLOR"]=document.getElementsByTagName("img")[0].style.background; finalData["CUSTOMCOLOR"]=document.getElementsByTagName("img")[0].style.background;
//console.log(JSON.stringify(finalData)) console.log(JSON.stringify(finalData))
localStorage.setItem("save_pixelart",JSON.stringify(finalData)) localStorage.setItem("save_pixelart",JSON.stringify(finalData))
var consoleText = document.createElement("span") var consoleText = document.createElement("span")
consoleText.innerHTML = "Saved!" consoleText.innerHTML = "Saved!"
@ -331,6 +458,18 @@ document.addEventListener("DOMContentLoaded",()=>{
saveButton.addEventListener("click",SaveData) saveButton.addEventListener("click",SaveData)
toolbar.appendChild(loadButton); toolbar.appendChild(loadButton);
toolbar.appendChild(saveButton); toolbar.appendChild(saveButton);
undoButton.type = "button"
undoButton.innerText = "Undo"
undoButton.classList.add("undobutton")
undoButton.disabled = true
undoButton.addEventListener("click",Undo)
redoButton.type = "button"
redoButton.innerText = "Redo"
redoButton.classList.add("redobutton")
redoButton.addEventListener("click",Redo)
redoButton.disabled = true
toolbar.appendChild(undoButton);
toolbar.appendChild(redoButton);
var toggleGridButton = document.createElement("img"); var toggleGridButton = document.createElement("img");
toggleGridButton.src = "gridtoggle.png"; toggleGridButton.src = "gridtoggle.png";
toggleGridButton.classList.add("tinybutton"); toggleGridButton.classList.add("tinybutton");
@ -361,53 +500,48 @@ document.addEventListener("DOMContentLoaded",()=>{
toolbar.appendChild(fillToolButton); toolbar.appendChild(fillToolButton);
} }
//Returns a coordinate class. var floodFill = (startx,starty,baseColor,newColor,force=false)=>{
var getCoordinates = (box)=>{ //console.log("Flood fill at "+startx+","+starty)
return new Coordinate(box);
}
var floodFill = (startx,starty,baseColor,newColor)=>{
console.log("Flood fill at "+startx+","+starty)
//Start a flood fill in 4 cardinations directions and the current spot. //Start a flood fill in 4 cardinations directions and the current spot.
//Set the base color to what the dot currently is. Then all around this spot, fill in any dots that are also this color. Don't spread the dot if there's not a color of that type. //Set the base color to what the dot currently is. Then all around this spot, fill in any dots that are also this color. Don't spread the dot if there's not a color of that type.
floodFillInProgress++; if (!force) {if (!force) {floodFillInProgress++;}}
if (baseColor===newColor) { if (baseColor===newColor) {
floodFillInProgress--; floodFillInProgress--;
return return
} }
startx = Number(startx) startx = Number(startx)
starty = Number(starty) starty = Number(starty)
floodFillInProgress++; if (!force) {floodFillInProgress++;}
setTimeout(()=>{if (document.getElementById("pos_"+(startx)+"_"+(starty)).style.background===baseColor) { setTimeout(()=>{if (document.getElementById("pos_"+(startx)+"_"+(starty)).style.background===baseColor) {
document.getElementById("pos_"+(startx)+"_"+(starty)).style.background = newColor document.getElementById("pos_"+(startx)+"_"+(starty)).style.background = newColor
floodFill(startx,starty,baseColor,newColor) floodFill(startx,starty,baseColor,newColor,force)
} }
floodFillInProgress--;},0) if (!force) {floodFillInProgress--;}},0)
floodFillInProgress++; if (!force) {floodFillInProgress++;}
setTimeout(()=>{if (startx+1 < COLS && document.getElementById("pos_"+(startx+1)+"_"+(starty+0)).style.background===baseColor) { setTimeout(()=>{if (startx+1 < COLS && document.getElementById("pos_"+(startx+1)+"_"+(starty+0)).style.background===baseColor) {
document.getElementById("pos_"+(startx+1)+"_"+(starty+0)).style.background = newColor document.getElementById("pos_"+(startx+1)+"_"+(starty+0)).style.background = newColor
floodFill(startx+1,starty,baseColor,newColor) floodFill(startx+1,starty,baseColor,newColor,force)
} }
floodFillInProgress--;},0) if (!force) {floodFillInProgress--;}},0)
floodFillInProgress++; if (!force) {floodFillInProgress++;}
setTimeout(()=>{if (startx-1 >= 0 && document.getElementById("pos_"+(startx-1)+"_"+(starty+0)).style.background===baseColor) { setTimeout(()=>{if (startx-1 >= 0 && document.getElementById("pos_"+(startx-1)+"_"+(starty+0)).style.background===baseColor) {
document.getElementById("pos_"+(startx-1)+"_"+(starty+0)).style.background = newColor document.getElementById("pos_"+(startx-1)+"_"+(starty+0)).style.background = newColor
floodFill(startx-1,starty,baseColor,newColor) floodFill(startx-1,starty,baseColor,newColor,force)
} }
floodFillInProgress--;},0) if (!force) {floodFillInProgress--;}},0)
floodFillInProgress++; if (!force) {floodFillInProgress++;}
setTimeout(()=>{if (starty+1 < ROWS && document.getElementById("pos_"+(startx)+"_"+(starty+1)).style.background===baseColor) { setTimeout(()=>{if (starty+1 < ROWS && document.getElementById("pos_"+(startx)+"_"+(starty+1)).style.background===baseColor) {
document.getElementById("pos_"+(startx)+"_"+(starty+1)).style.background = newColor document.getElementById("pos_"+(startx)+"_"+(starty+1)).style.background = newColor
floodFill(startx,starty+1,baseColor,newColor) floodFill(startx,starty+1,baseColor,newColor,force)
} }
floodFillInProgress--;},0) if (!force) {floodFillInProgress--;}},0)
floodFillInProgress++; if (!force) {floodFillInProgress++;}
setTimeout(()=>{if (starty-1 >= 0 && document.getElementById("pos_"+(startx)+"_"+(starty-1)).style.background===baseColor) { setTimeout(()=>{if (starty-1 >= 0 && document.getElementById("pos_"+(startx)+"_"+(starty-1)).style.background===baseColor) {
document.getElementById("pos_"+(startx)+"_"+(starty-1)).style.background = newColor document.getElementById("pos_"+(startx)+"_"+(starty-1)).style.background = newColor
floodFill(startx,starty-1,baseColor,newColor) floodFill(startx,starty-1,baseColor,newColor,force)
} }
floodFillInProgress--;},0) if (!force) {floodFillInProgress--;}},0)
floodFillInProgress--; if (!force) {floodFillInProgress--;}
} }
generateTable(ROWS,COLS); generateTable(ROWS,COLS);

@ -34,6 +34,14 @@ th{
margin: 0px 2px 0px 2px; margin: 0px 2px 0px 2px;
background-color: #FF9999; background-color: #FF9999;
} }
.undobutton{
margin: 0px 2px 0px 2px;
background-color: #99FF99;
}
.redobutton{
margin: 0px 2px 0px 2px;
background-color: #FFFF99;
}
.console{ .console{
font-style: strong; font-style: strong;
margin-left: 4px; margin-left: 4px;

Loading…
Cancel
Save