From 87ea1be32d5d199f6b6cc8f60ddd7a01fae0245f Mon Sep 17 00:00:00 2001 From: Victor Seiji Hariki Date: Tue, 22 Nov 2022 02:16:17 -0300 Subject: [PATCH] add history list view and interaction Signed-off-by: Victor Seiji Hariki Former-commit-id: 7064b38c9663c0219424b4864243f16d4aa68a24 --- css/index.css | 48 ++++++++++++++++++++++++++++++------ index.html | 8 +++--- js/commands.js | 51 +++++++++++++++++++++++++++++++------- js/index.js | 4 +-- js/settingsbar.js | 1 - js/ui/history.js | 62 +++++++++++++++++++++++++++++++++++++++++++++++ js/util.js | 2 +- 7 files changed, 152 insertions(+), 24 deletions(-) create mode 100644 js/ui/history.js diff --git a/css/index.css b/css/index.css index f0f18fb..b0485fe 100644 --- a/css/index.css +++ b/css/index.css @@ -2,8 +2,8 @@ font-size: 100%; font-family: Arial, Helvetica, sans-serif; } -body{ - margin:0px; +body { + margin: 0px; } .container { @@ -14,8 +14,42 @@ body{ background-color: #ccc; } -.canvas { - border: 1px black solid; +#historyContainer > .info { + padding: 0; +} + +#history.history { + height: 200px; + overflow: scroll; +} + +#history.history > .history-item { + cursor: pointer; + + padding: 5px; + padding-top: 2px; + padding-bottom: 2px; +} + +#history.history > .history-item { + background-color: #0000; +} +#history.history > .history-item:hover { + background-color: #fff5; +} + +#history.history > .history-item.current { + background-color: #66f5; +} +#history.history > .history-item.current:hover { + background-color: #66f5; +} + +#history.history > .history-item.future { + background-color: #4445; +} +#history.history > .history-item.future:hover { + background-color: #ddd5; } .mainHSplit { @@ -35,7 +69,7 @@ body{ } .uiContainer { - position: absolute; + position: fixed; width: 250px; height: auto; z-index: 999; @@ -45,7 +79,7 @@ body{ z-index: 999; cursor: move; background-color: rgba(104, 104, 104, 0.75); - z-index: 999; + z-index: 999; user-select: none; @@ -175,6 +209,6 @@ button.tool:hover { .strokeText { -webkit-text-stroke: 1px #000; font-size: 150%; - font-weight: 600; + font-weight: 600; color: #fff; } diff --git a/index.html b/index.html index 53c370f..79661d3 100644 --- a/index.html +++ b/index.html @@ -125,10 +125,8 @@
History
-
- -
-
+
+
@@ -194,6 +192,8 @@ + + \ No newline at end of file diff --git a/js/commands.js b/js/commands.js index f5594f8..304638a 100644 --- a/js/commands.js +++ b/js/commands.js @@ -1,6 +1,9 @@ /** * Command pattern to allow for editing history */ + +const _commands_events = new Observer(); + const commands = { current: -1, history: [], @@ -30,7 +33,7 @@ const commands = { * The 'state' object will be passed to the 'undo' function as well. */ createCommand(name, run, undo, redo = run) { - const command = function runWrapper(options) { + const command = function runWrapper(title, options) { // Create copy of options and state object const copy = {}; Object.assign(copy, options); @@ -38,7 +41,7 @@ const commands = { // Attempt to run command try { - run(copy, state); + run(title, copy, state); } catch (e) { console.warn(`Error while running command '${name}' with options:`); console.warn(copy); @@ -48,26 +51,56 @@ const commands = { const undoWrapper = () => { console.debug(`Undoing ${name}, currently ${commands.current}`); - undo(state); + undo(title, state); + _commands_events.emit({ + name, + action: "undo", + state, + current: commands.current, + }); }; const redoWrapper = () => { console.debug(`Redoing ${name}, currently ${commands.current}`); - redo(copy, state); + redo(title, copy, state); + _commands_events.emit({ + name, + action: "redo", + state, + current: commands.current, + }); }; // Add to history if (commands.history.length > commands.current + 1) commands.history.splice(commands.current + 1); - commands.history.push({undo: undoWrapper, redo: redoWrapper}); + + const entry = { + id: guid(), + title, + undo: undoWrapper, + redo: redoWrapper, + state, + }; + + commands.history.push(entry); commands.current++; + + _commands_events.emit({ + name, + action: "run", + state, + current: commands.current, + }); + + return entry; }; this.types[name] = command; return command; }, - runCommand(name, options) { - this.types[name](options); + runCommand(name, title, options) { + this.types[name](title, options); }, types: {}, }; @@ -77,7 +110,7 @@ const commands = { */ commands.createCommand( "drawImage", - (options, state) => { + (title, options, state) => { if ( !options || options.image === undefined || @@ -116,7 +149,7 @@ commands.createCommand( // Apply command state.context.drawImage(options.image, state.box.x, state.box.y); }, - (state) => { + (title, state) => { // Clear destination area state.context.clearRect(state.box.x, state.box.y, state.box.w, state.box.h); // Undo diff --git a/js/index.js b/js/index.js index 9467aa4..a7592dd 100644 --- a/js/index.js +++ b/js/index.js @@ -183,7 +183,7 @@ function drop(imageParams) { } function writeArbitraryImage(img, x, y) { - commands.runCommand("drawImage", { + commands.runCommand("drawImage", "Image Stamp", { x, y, image: img, @@ -351,7 +351,7 @@ function clearPaintedMask() { function placeImage() { const img = new Image(); img.onload = function () { - commands.runCommand("drawImage", { + commands.runCommand("drawImage", "Image Dream", { x: tmpImgXYWH.x, y: tmpImgXYWH.y, image: img, diff --git a/js/settingsbar.js b/js/settingsbar.js index b7d6e42..6a4aec6 100644 --- a/js/settingsbar.js +++ b/js/settingsbar.js @@ -67,7 +67,6 @@ function makeDraggable(id) { } makeDraggable("infoContainer"); -makeDraggable("historyContainer"); var coll = document.getElementsByClassName("collapsible"); for (var i = 0; i < coll.length; i++) { diff --git a/js/ui/history.js b/js/ui/history.js new file mode 100644 index 0000000..fb03c40 --- /dev/null +++ b/js/ui/history.js @@ -0,0 +1,62 @@ +(() => { + makeDraggable("historyContainer"); + + const historyView = document.getElementById("history"); + + const makeHistoryEntry = (index, id, title) => { + const historyItemTitle = document.createElement("span"); + historyItemTitle.classList.add(["title"]); + historyItemTitle.textContent = `${index} - ${title}`; + + const historyItem = document.createElement("div"); + historyItem.id = id; + historyItem.classList.add(["history-item"]); + historyItem.title = id; + historyItem.onclick = () => { + const diff = commands.current - index; + if (diff < 0) { + commands.redo(Math.abs(diff)); + } else { + commands.undo(diff); + } + }; + + historyItem.appendChild(historyItemTitle); + + return historyItem; + }; + + _commands_events.on((message) => { + commands.history.forEach((entry, index) => { + console.log("Inserting " + entry.id); + if (!document.getElementById(`hist-${entry.id}`)) { + historyView.appendChild( + makeHistoryEntry(index, `hist-${entry.id}`, entry.title) + ); + } + }); + + while (historyView.children.length > commands.history.length) + historyView.removeChild(historyView.lastElementChild); + + Array.from(historyView.children).forEach((child, index) => { + if (index === commands.current) { + child.classList.remove(["past"]); + child.classList.add(["current"]); + child.classList.remove(["future"]); + } else if (index < commands.current) { + child.classList.add(["past"]); + child.classList.remove(["current"]); + child.classList.remove(["future"]); + } else { + child.classList.remove(["past"]); + child.classList.remove(["current"]); + child.classList.add(["future"]); + } + }); + + if (message.action === "run") { + historyView.scrollTo(0, historyView.scrollHeight); + } + }); +})(); diff --git a/js/util.js b/js/util.js index c2db570..f8b9f51 100644 --- a/js/util.js +++ b/js/util.js @@ -20,7 +20,7 @@ Observer.prototype = { await handler(msg); } catch (e) { console.warn("Observer failed to run handler"); - console.warn(handler); + console.warn(e); } }); },