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();
}