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-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;
|
||||||
|
|
|
@ -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>
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
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);
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue