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

View file

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

View file

@ -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

View file

@ -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,

View file

@ -67,7 +67,6 @@ function makeDraggable(id) {
}
makeDraggable("infoContainer");
makeDraggable("historyContainer");
var coll = document.getElementsByClassName("collapsible");
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);
} catch (e) {
console.warn("Observer failed to run handler");
console.warn(handler);
console.warn(e);
}
});
},