Merge pull request #31 from seijihariki/history_view
add history list view and interaction Former-commit-id: c107303986209f70129de637b7f2ed6a05507f00
This commit is contained in:
commit
9590345b03
7 changed files with 152 additions and 24 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
62
js/ui/history.js
Normal 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);
|
||||
}
|
||||
});
|
||||
})();
|
|
@ -20,7 +20,7 @@ Observer.prototype = {
|
|||
await handler(msg);
|
||||
} catch (e) {
|
||||
console.warn("Observer failed to run handler");
|
||||
console.warn(handler);
|
||||
console.warn(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue