diff --git a/extension/Resources/Public/Json/fabric.buttons.json b/extension/Resources/Public/Json/fabric.buttons.json index 0e2674b07597130f322fd3f90135bac87ebe751b..5dd0f782b5b81630f0d2aa872a8c04fa98486cdf 100644 --- a/extension/Resources/Public/Json/fabric.buttons.json +++ b/extension/Resources/Public/Json/fabric.buttons.json @@ -9,6 +9,8 @@ "toggle": "isDrawingMode", "hasToggleElement": false, "toggleElement": "", + "disabled": false, + "tooltip": "Draw with a pencil", "icon": "glyphicon-pencil" }, { @@ -20,6 +22,8 @@ "toggle": "isHighlightMode", "hasToggleElement": false, "toggleElement": "", + "disabled": false, + "tooltip": "Highlighter", "icon": "glyphicon-pencil" }, @@ -32,6 +36,8 @@ "toggle": "emojiMode", "hasToggleElement": true, "toggleElement": "emojiContainer", + "disabled": false, + "tooltip": "Emojis", "icon": "glyphicon-ice-lolly-tasted" }, { @@ -43,6 +49,8 @@ "toggle": "drawRectangleMode", "hasToggleElement": false, "toggleElement": "", + "disabled": false, + "tooltip": "Draw a rectangle to highlight areas", "icon": "glyphicon-stop" }, { @@ -54,6 +62,8 @@ "toggle": "", "hasToggleElement": false, "toggleElement": "", + "disabled": false, + "tooltip": "Adds a customizable text to canvas", "icon": "glyphicon-text-height" }, { @@ -65,6 +75,8 @@ "toggle": "moveMode", "hasToggleElement": false, "toggleElement": "", + "disabled": false, + "tooltip": "Move the viewport, useful if zoomed", "icon": "glyphicon-move" }, { @@ -76,6 +88,8 @@ "toggle": "", "hasToggleElement": false, "toggleElement": "", + "disabled": false, + "tooltip": "Delete selected object", "icon": "glyphicon-trash" }, { @@ -87,6 +101,8 @@ "toggle": "isZoomMode", "hasToggleElement": false, "toggleElement": "", + "disabled": false, + "tooltip": "Zoom viewport", "icon": "glyphicon-search" }, { @@ -98,8 +114,37 @@ "toggle": "isMouseMode", "hasToggleElement": false, "toggleElement": "", + "disabled": false, + "tooltip": "Normal mouse to select objects in canvas", "icon": "glyphicon-hand-up" + }, + { + "name": "undo", + "selector": "undo", + "requiresDrawing": false, + "requiresSelection": false, + "isToggle": false, + "toggle": "", + "hasToggleElement": false, + "toggleElement": "", + "disabled": true, + "tooltip": "Undo", + "icon": "glyphicon-repeat icon-flipped" + }, + { + "name": "redo", + "selector": "redo", + "requiresDrawing": false, + "requiresSelection": false, + "isToggle": false, + "toggle": "", + "hasToggleElement": false, + "toggleElement": "", + "disabled": true, + "tooltip": "Redo", + "icon": "glyphicon-repeat" } + ], "currentMode": "draw", "colors": [ diff --git a/javascript/src/History.js b/javascript/src/History.js new file mode 100644 index 0000000000000000000000000000000000000000..9169d6f07c537ebe1d657ba2be811ad5912fd4ea --- /dev/null +++ b/javascript/src/History.js @@ -0,0 +1,77 @@ +/** + * @author Benjamin Baer <benjamin.baer@math.uzh.ch> + */ + +/* global $ */ + +/** + * Qfq Namespace + * + * @namespace QfqNS + */ +var QfqNS = QfqNS || {}; + +(function (n) { + 'use strict'; + +/** + * A custom history to use for undo and redo functionality. + **/ + + n.History = function() { + this.history = []; + this.pointer = 0; + }; + + n.History.prototype.put = function(object) { + if (this.history.length > 1) { + if (this.canGoForward()) { + console.log("trying to remove history"); + this._removeForwardHistory(); + } + } + if (JSON.stringify(this.history[this.pointer]) !== JSON.stringify(object)) { + this.history.push(object); + this.pointer = this.history.length - 1; + } + console.log(this); + }; + + n.History.prototype.back = function() { + if (this.canGoBack()) { + this.pointer = this.pointer - 1; + console.log(this.pointer + "/" + this.history.length); + console.log(this.history); + return this.history[this.pointer]; + } else { + console.log("At the beginning of history"); + return false; + } + }; + + n.History.prototype.forward = function() { + console.log(this.pointer); + if (this.canGoForward()) { + this.pointer = this.pointer + 1; + return this.history[this.pointer]; + } else { + console.log("At the end of history"); + return false; + } + }; + + n.History.prototype.canGoBack = function() { + return this.pointer > 0; + }; + + n.History.prototype.canGoForward = function() { + return this.pointer < this.history.length - 1; + }; + + n.History.prototype._removeForwardHistory = function() { + this.history.splice(this.pointer + 1, this.history.length - this.pointer); + }; + + + +})(QfqNS); \ No newline at end of file diff --git a/javascript/src/Plugins/qfq.fabric.js b/javascript/src/Plugins/qfq.fabric.js index 0bdbf51db8f5d0de5fd9671ca30344165c09b002..37dd18cbfb8a74369f237dd03caa7e08acf7b689 100644 --- a/javascript/src/Plugins/qfq.fabric.js +++ b/javascript/src/Plugins/qfq.fabric.js @@ -26,6 +26,7 @@ $(function (n) { this.emojiContainer = {}; this.eventEmitter = new EventEmitter(); this.qfqPage = {}; + this.changeHistory = true; this.scaled = false; this.textContainer = {}; this.userTextInput = {}; @@ -63,6 +64,8 @@ $(function (n) { this.mouseInsideCanvas = false; this.imageOutput = ''; this.localStore = new n.LocalStorage("fabric"); + this.history = new n.History(); + this.firstLoad = false; // Handles button states and generation of said buttons. Should be renamed. function ModeSettings() { @@ -110,6 +113,8 @@ $(function (n) { $button.on("click", function() { that.qFabric.buttonPress(modePressed, $button) }); + $button.prop("disabled", o.disabled); + $button.prop("title", o.tooltip) }); $controlWrapper.append($buttonGroup); @@ -186,6 +191,11 @@ $(function (n) { } }; + ModeSettings.prototype.disableButton = function(id, bool) { + var $button = this.getButtonById(id); + $button.prop("disabled",bool); + }; + ModeSettings.prototype.getModeByName = function (string) { $.each(this.myModes.modes, function(i, o) { if (o.name === string) { @@ -314,7 +324,9 @@ $(function (n) { this.generateCanvas(width, height); if (this.outputField.val()) { var fabricJSON = this.prepareJSON(this.outputField.val()); + this.history.put(fabricJSON); this.canvas.loadFromJSON(fabricJSON, function() { + this.firstLoad = true; that.setBackground(); that.resizeCanvas(); that.setBrush(); @@ -326,6 +338,7 @@ $(function (n) { that.setBrush(); that.canvas.renderAll(); that.userChangePossible = true; + this.history.put(this.canvas.toJSON()); } var defaultColor = $fabricElement.data('fabric-color') || false; if (defaultColor) { @@ -530,6 +543,7 @@ $(function (n) { this.setBackground(); this.canvas.renderAll(); } + }; n.Fabric.prototype.zoomCanvas = function(o, zoomCalc) { @@ -558,7 +572,10 @@ $(function (n) { centeredScaling: true, centeredRotation: true }); - that.canvas.setBackgroundImage(img, that.canvas.renderAll.bind(that.canvas)); + that.canvas.setBackgroundImage(img, function() { + that.canvas.renderAll.bind(that.canvas); + that.canvas.renderAll(); + }); }); } else { var $image = document.getElementsByClassName("qfq-fabric-image")[0]; @@ -574,9 +591,12 @@ $(function (n) { centeredRotation: true }); img.rotate(that.rotation); - that.canvas.setBackgroundImage(img, that.canvas.renderAll.bind(that.canvas)); - that.canvas.renderAll(); + that.canvas.setBackgroundImage(img, function() { + that.canvas.renderAll.bind(that.canvas); + that.canvas.renderAll(); + }); }; + that.canvas.renderAll(); } }; @@ -843,6 +863,12 @@ $(function (n) { case "exportImage": this.prepareForExport(); break; + case "undo": + this.changeState("undo"); + break; + case "redo": + this.changeState("redo"); + break; default: console.error("unrecognized mode"); } @@ -980,6 +1006,38 @@ $(function (n) { this.setBrush(); }; + /** + * Calls state from attached history and moves in defined + * direction. (undo / redo) + * @param direction string, undo or redo + */ + n.Fabric.prototype.changeState = function(direction) { + var state = {}; + console.log(direction); + if (direction === "undo") { + state = this.history.back(); + } else { + state = this.history.forward(); + } + + if (state) { + this.changeHistory = false; + var that = this; + this.canvas.loadFromJSON(state, function() { + that.setBackground(); + that.canvas.renderAll(); + that.changeHistory = true; + }); + } + this.updateHistoryButtons(); + }; + + n.Fabric.prototype.updateHistoryButtons = function() { + this.modeSettings.disableButton("undo", !this.history.canGoBack()); + this.modeSettings.disableButton("redo", !this.history.canGoForward()); + }; + + n.Fabric.prototype.rectangle = function() { this.drawRectangleMode = true; }; @@ -1000,7 +1058,12 @@ $(function (n) { n.Fabric.prototype.defaultChangeHandler = function () { /* Important! Changes only possible after initialisation */ + if (this.userChangePossible) { + if (this.changeHistory) { + this.history.put(this.canvas.toJSON()); + this.updateHistoryButtons(); + } var that = this; this.outputField.val(JSON.stringify(that.canvas.toJSON())); if (this.qfqPage.qfqForm) { diff --git a/less/qfq-bs.css.less b/less/qfq-bs.css.less index 7b95336e1af8b6114434d9d067d758bcf46699c5..0c7bf520760af7f7a410c55a12e9694eabf5e0bd 100644 --- a/less/qfq-bs.css.less +++ b/less/qfq-bs.css.less @@ -765,7 +765,9 @@ select.qfq-locked:invalid { } legend { - margin-bottom: 5px; + margin-bottom: 25px; + margin-top: 15px; + padding-bottom: 5px; } /* glyphicon functions */ diff --git a/mockup/mockData/fabric.buttons.json b/mockup/mockData/fabric.buttons.json index f6d432e5846b9d85414d652f49dd2a1d98a3f429..493a6e2bbc8b625cb59974fffc5fac4770464f1e 100644 --- a/mockup/mockData/fabric.buttons.json +++ b/mockup/mockData/fabric.buttons.json @@ -89,8 +89,18 @@ "icon": "glyphicon-trash" }, { - "name": "rotateRight", - "selector": "turn-right", + "name": "undo", + "selector": "undo", + "requiresDrawing": false, + "requiresSelection": false, + "isToggle": false, + "toggle": "", + "hasToggleElement": false, + "toggleElement": "", + "icon": "glyphicon-repeat" + }, { + "name": "redo", + "selector": "redo", "requiresDrawing": false, "requiresSelection": false, "isToggle": false,