diff --git a/history.js b/history.js new file mode 100644 index 0000000..c57b13a --- /dev/null +++ b/history.js @@ -0,0 +1,46 @@ +import { Scenario } from './scenario.js'; + +export class History { + static instance = null; + + static getInstance() { + if (!History.instance) { + History.instance = new History(); + } + return History.instance; + } + + constructor() { + this.undoStack = []; + this.redoStack = []; + } + + pushState(state) { + // store state as JSON string to avoid mutation + const serialized = JSON.stringify(state); + this.undoStack.push(serialized); + if (this.undoStack.length > 50) { + this.undoStack.shift(); + } + this.redoStack = []; + } + + undo() { + if (this.undoStack.length <= 1) return; + const current = this.undoStack.pop(); + this.redoStack.push(current); + const prev = this.undoStack[this.undoStack.length - 1]; + if (prev) { + Scenario.deserialize(JSON.parse(prev)); + } + } + + redo() { + if (this.redoStack.length === 0) return; + const state = this.redoStack.pop(); + this.undoStack.push(state); + Scenario.deserialize(JSON.parse(state)); + } +} + +window.HISTORY = History.getInstance(); diff --git a/index.html b/index.html index ec5fe6c..82ee70a 100644 --- a/index.html +++ b/index.html @@ -21,6 +21,8 @@ + + diff --git a/main.js b/main.js index 5c7742c..a41f8f4 100644 --- a/main.js +++ b/main.js @@ -3,6 +3,7 @@ import { Tools } from "./tools.js"; import { defaultImage, defaultImageWidth, defaultImageHeight } from "./staticData.js"; //import './renderer.js'; import './canvasRenderer.js'; +import { History } from "./history.js"; import './palette.js'; import './tileElement.js'; import './tileOptions.js'; @@ -44,6 +45,8 @@ document.addEventListener("DOMContentLoaded", function () { let scenarioOptionsButton = document.querySelector("button#scenario_options"); let saveLocalButton = document.querySelector("button#save_local"); let loadLocalButton = document.querySelector("button#load_local"); + let undoButton = document.querySelector("button#undo"); + let redoButton = document.querySelector("button#redo"); let helpButton = document.querySelector("button#show_help"); let helpModal = document.querySelector("help-modal"); @@ -79,6 +82,12 @@ document.addEventListener("DOMContentLoaded", function () { } modal.openDialog(); }); + undoButton.addEventListener("click", function () { + History.getInstance().undo(); + }); + redoButton.addEventListener("click", function () { + History.getInstance().redo(); + }); helpButton?.addEventListener('click', function () { helpModal?.openDialog(); @@ -550,7 +559,7 @@ document.addEventListener("DOMContentLoaded", function () { })); ContextWheel.Show(event.clientX, event.clientY, opts); }); - + document.addEventListener('keydown', function (e) { if (['INPUT', 'TEXTAREA'].includes(document.activeElement.tagName)) return; @@ -568,6 +577,12 @@ document.addEventListener("DOMContentLoaded", function () { renderer.zoomIn(); } else if (e.key === '-') { renderer.zoomOut(); + } else if ((e.ctrlKey || e.metaKey) && e.key === "z") { + e.preventDefault(); + History.getInstance().undo(); + } else if ((e.ctrlKey || e.metaKey) && e.key === "y") { + e.preventDefault(); + History.getInstance().redo(); } else if (e.key === '0') { renderer.zoomReset(); } else if (e.key.toLowerCase() === 's' && e.ctrlKey) { diff --git a/scenario.js b/scenario.js index e475275..ac8cc29 100644 --- a/scenario.js +++ b/scenario.js @@ -1,5 +1,6 @@ import { Cell } from './cell.js'; import { Tile } from './tile.js'; +import { History } from './history.js'; import { Options } from './options.js'; import { defaultImage, defaultImageWidth, defaultImageHeight } from './staticData.js'; @@ -33,6 +34,7 @@ static DEFAULT_UPPER_LAYER_LIMIT = 32; instance.mapCells = data.mapCells.map(cell => Cell.deserialize(cell, instance.palette)); instance.options = Options.deserialize(data.options, instance); instance.setAdjacents(); + History.getInstance().pushState(instance.serialize()); requestAnimationFrame(() => { instance.fireUpdate(true); @@ -52,6 +54,7 @@ static DEFAULT_UPPER_LAYER_LIMIT = 32; constructor(width, height) { this.options = new Options(this); this.newScenario(width, height); + History.getInstance().pushState(this.serialize()); } serialize() { @@ -90,6 +93,7 @@ static DEFAULT_UPPER_LAYER_LIMIT = 32; return; this.currentTool(cell, this.currentLayer); + History.getInstance().pushState(this.serialize()); this.fireUpdate(); } @@ -104,6 +108,7 @@ static DEFAULT_UPPER_LAYER_LIMIT = 32; this.visibleLayers = { 0: true }; //this.pushImageIntoPalette(defaultImage, defaultImageWidth, defaultImageHeight); this.setMapSize(width, height); + History.getInstance().pushState(this.serialize()); this.fireUpdate(); }