Merge pull request #31 from seijihariki/history_view

add history list view and interaction

Former-commit-id: c107303986209f70129de637b7f2ed6a05507f00
This commit is contained in:
tim h 2022-11-22 00:53:56 -06:00 committed by GitHub
commit 9590345b03
7 changed files with 152 additions and 24 deletions

View file

@ -2,8 +2,8 @@
font-size: 100%; font-size: 100%;
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
} }
body{ body {
margin:0px; margin: 0px;
} }
.container { .container {
@ -14,8 +14,42 @@ body{
background-color: #ccc; background-color: #ccc;
} }
.canvas { #historyContainer > .info {
border: 1px black solid; 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 { .mainHSplit {
@ -35,7 +69,7 @@ body{
} }
.uiContainer { .uiContainer {
position: absolute; position: fixed;
width: 250px; width: 250px;
height: auto; height: auto;
z-index: 999; z-index: 999;

View file

@ -125,10 +125,8 @@
<div id="historyContainer" class="uiContainer" style="right: 0;"> <div id="historyContainer" class="uiContainer" style="right: 0;">
<div id="historyTitleBar" class="draggable uiTitleBar">History</div> <div id="historyTitleBar" class="draggable uiTitleBar">History</div>
<div class="info" style="min-width:200px;"> <div class="info" style="min-width:200px;">
<div id="history" class="history"> <div id="history" class="history"></div>
<div class="toolbar" style="padding: 10px;">
</div>
<div class="toolbar">
<button type="button" onclick="commands.undo()" class="tool">undo</button> <button type="button" onclick="commands.undo()" class="tool">undo</button>
<button type="button" onclick="commands.redo()" class="tool">redo</button> <button type="button" onclick="commands.redo()" class="tool">redo</button>
</div> </div>
@ -194,6 +192,8 @@
<script src="js/index.js" type="text/javascript"></script> <script src="js/index.js" type="text/javascript"></script>
<script src="js/settingsbar.js" type="text/javascript"></script> <script src="js/settingsbar.js" type="text/javascript"></script>
<script src="js/shortcuts.js" type="text/javascript"></script> <script src="js/shortcuts.js" type="text/javascript"></script>
<script src="js/ui/history.js" type="text/javascript"></script>
</body> </body>
</html> </html>

View file

@ -1,6 +1,9 @@
/** /**
* Command pattern to allow for editing history * Command pattern to allow for editing history
*/ */
const _commands_events = new Observer();
const commands = { const commands = {
current: -1, current: -1,
history: [], history: [],
@ -30,7 +33,7 @@ const commands = {
* The 'state' object will be passed to the 'undo' function as well. * The 'state' object will be passed to the 'undo' function as well.
*/ */
createCommand(name, run, undo, redo = run) { createCommand(name, run, undo, redo = run) {
const command = function runWrapper(options) { const command = function runWrapper(title, options) {
// Create copy of options and state object // Create copy of options and state object
const copy = {}; const copy = {};
Object.assign(copy, options); Object.assign(copy, options);
@ -38,7 +41,7 @@ const commands = {
// Attempt to run command // Attempt to run command
try { try {
run(copy, state); run(title, copy, state);
} catch (e) { } catch (e) {
console.warn(`Error while running command '${name}' with options:`); console.warn(`Error while running command '${name}' with options:`);
console.warn(copy); console.warn(copy);
@ -48,26 +51,56 @@ const commands = {
const undoWrapper = () => { const undoWrapper = () => {
console.debug(`Undoing ${name}, currently ${commands.current}`); console.debug(`Undoing ${name}, currently ${commands.current}`);
undo(state); undo(title, state);
_commands_events.emit({
name,
action: "undo",
state,
current: commands.current,
});
}; };
const redoWrapper = () => { const redoWrapper = () => {
console.debug(`Redoing ${name}, currently ${commands.current}`); 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 // Add to history
if (commands.history.length > commands.current + 1) if (commands.history.length > commands.current + 1)
commands.history.splice(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.current++;
_commands_events.emit({
name,
action: "run",
state,
current: commands.current,
});
return entry;
}; };
this.types[name] = command; this.types[name] = command;
return command; return command;
}, },
runCommand(name, options) { runCommand(name, title, options) {
this.types[name](options); this.types[name](title, options);
}, },
types: {}, types: {},
}; };
@ -77,7 +110,7 @@ const commands = {
*/ */
commands.createCommand( commands.createCommand(
"drawImage", "drawImage",
(options, state) => { (title, options, state) => {
if ( if (
!options || !options ||
options.image === undefined || options.image === undefined ||
@ -116,7 +149,7 @@ commands.createCommand(
// Apply command // Apply command
state.context.drawImage(options.image, state.box.x, state.box.y); state.context.drawImage(options.image, state.box.x, state.box.y);
}, },
(state) => { (title, state) => {
// Clear destination area // Clear destination area
state.context.clearRect(state.box.x, state.box.y, state.box.w, state.box.h); state.context.clearRect(state.box.x, state.box.y, state.box.w, state.box.h);
// Undo // Undo

View file

@ -183,7 +183,7 @@ function drop(imageParams) {
} }
function writeArbitraryImage(img, x, y) { function writeArbitraryImage(img, x, y) {
commands.runCommand("drawImage", { commands.runCommand("drawImage", "Image Stamp", {
x, x,
y, y,
image: img, image: img,
@ -351,7 +351,7 @@ function clearPaintedMask() {
function placeImage() { function placeImage() {
const img = new Image(); const img = new Image();
img.onload = function () { img.onload = function () {
commands.runCommand("drawImage", { commands.runCommand("drawImage", "Image Dream", {
x: tmpImgXYWH.x, x: tmpImgXYWH.x,
y: tmpImgXYWH.y, y: tmpImgXYWH.y,
image: img, image: img,

View file

@ -67,7 +67,6 @@ function makeDraggable(id) {
} }
makeDraggable("infoContainer"); makeDraggable("infoContainer");
makeDraggable("historyContainer");
var coll = document.getElementsByClassName("collapsible"); var coll = document.getElementsByClassName("collapsible");
for (var i = 0; i < coll.length; i++) { for (var i = 0; i < coll.length; i++) {

62
js/ui/history.js Normal file
View file

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

View file

@ -20,7 +20,7 @@ Observer.prototype = {
await handler(msg); await handler(msg);
} catch (e) { } catch (e) {
console.warn("Observer failed to run handler"); console.warn("Observer failed to run handler");
console.warn(handler); console.warn(e);
} }
}); });
}, },