diff --git a/mondumas/models/dossier.py b/mondumas/models/dossier.py index 1497858..6aac63f 100644 --- a/mondumas/models/dossier.py +++ b/mondumas/models/dossier.py @@ -357,13 +357,13 @@ def insert_dem_note(request, nodossier, type_note, logged_in): query = "INSERT INTO dem_notes (societe, nochantier, type_note, usermaj) VALUES (:societe, :nochantier, :type_note, :logged_in);" execute_query(request, query, {'societe': societe, 'nochantier': nochantier, 'type_note': type_note, 'logged_in': logged_in}) -def get_dem_notes(request, nodossier, noligne): +def get_dem_notes(request, nodossier, noligne, type): societe = nodossier[0:2] nochantier = int(nodossier[3:]) if noligne == '0': - query = "SELECT societe, nochantier, noligne, type_note, libelle FROM dem_notes WHERE societe = :societe AND nochantier = :nochantier ORDER BY libelle;" - results = request.dbsession.execute(query, {'societe': societe, 'nochantier': nochantier, 'noligne': noligne}).fetchall() + query = "SELECT societe, nochantier, noligne, type_note, libelle FROM dem_notes WHERE societe = :societe AND nochantier = :nochantier AND type_note=:type ORDER BY libelle;" + results = request.dbsession.execute(query, {'societe': societe, 'nochantier': nochantier, 'noligne': noligne, 'type': type}).fetchall() else: query = "SELECT * FROM dem_notes WHERE societe = :societe AND nochantier = :nochantier AND noligne = :noligne;" results = request.dbsession.execute(query, {'societe': societe, 'nochantier': nochantier, 'noligne': noligne}).first() diff --git a/mondumas/routes.py b/mondumas/routes.py index 8a68211..7e0c413 100644 --- a/mondumas/routes.py +++ b/mondumas/routes.py @@ -34,6 +34,7 @@ def includeme(config): config.add_route('facture_select', '/facture_select/{date}') config.add_route('facture_selected', '/facture_selected/{goto}/{date}/{nofacture}') # dossier + config.add_route('croquis_edit','/croquis_edit/') config.add_route('demandes','/demandes') config.add_route('demandes_dl','/demandes_dl/{societe}/{email_from}/{email_uid}') config.add_route('dem_devis','/dem_devis') @@ -43,6 +44,7 @@ def includeme(config): config.add_route('dossier_select', '/dossier_select/{date}') config.add_route('dossier_selected', '/dossier_selected/{goto}/{date}/{nodossier}') config.add_route('dossier_view', '/dossier_view/{nodossier}') + config.add_route('note_add','/note_add/{nodossier}') config.add_route('note_edit','/note_edit/{nodossier}/{noligne}') config.add_route('rdf_bill','/rdf_bill/{no_id}') config.add_route('rdf_client','/rdf_client/{no_id}') diff --git a/mondumas/static/css/style.less b/mondumas/static/css/style.less index b23ac15..cf0bfaf 100644 --- a/mondumas/static/css/style.less +++ b/mondumas/static/css/style.less @@ -276,6 +276,10 @@ color: black; } } +#dessin { + width: 1140px; + height: 1140px; +} /* ne pas affichier l'url after the link */ @media print { diff --git a/mondumas/static/dist/drawingboard/drawingboard.css b/mondumas/static/dist/drawingboard/drawingboard.css new file mode 100644 index 0000000..90920c6 --- /dev/null +++ b/mondumas/static/dist/drawingboard/drawingboard.css @@ -0,0 +1,88 @@ +/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js +* Copyright (c) 2015 Emmanuel Pelletier +* Licensed MIT */ +.drawing-board, .drawing-board * { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; } + +.drawing-board-utils-hidden, .drawing-board-controls-hidden { display: none !important; } + +.drawing-board { position: relative; display: block; } + +.drawing-board-canvas-wrapper { position: relative; margin: 0; border: 1px solid #ddd; } + +.drawing-board-canvas { position: absolute; top: 0; left: 0; z-index: 10; width: auto; } + +.drawing-board-canvas { cursor: crosshair; z-index: 20; } + +.drawing-board-cursor { position: absolute; top: 0; left: 0; pointer-events: none; border-radius: 50%; background: #ccc; background: rgba(0, 0, 0, 0.2); z-index: 30; } + +.drawing-board-control > button, .drawing-board-control-colors-rainbows, .drawing-board-control-size .drawing-board-control-inner, .drawing-board-control-size-dropdown { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; overflow: hidden; border: none; background-color: #eee; padding: 2px 4px; border: 1px solid #ccc; box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); -webkit-box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); height: 28px; } + +.drawing-board-control > button { cursor: pointer; min-width: 28px; line-height: 14px; } +.drawing-board-control > button:hover, .drawing-board-control > button:focus { background-color: #ddd; } +.drawing-board-control > button:active, .drawing-board-control > button.active { box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.2); -webkit-box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.2); background-color: #ddd; } +.drawing-board-control > button[disabled] { color: gray; } +.drawing-board-control > button[disabled]:hover, .drawing-board-control > button[disabled]:focus, .drawing-board-control > button[disabled]:active, .drawing-board-control > button[disabled].active { background-color: #eee; box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); -webkit-box-shadow: 0 1px 3px -2px #121212, inset 0 2px 5px 0 rgba(255, 255, 255, 0.3); cursor: default; } + +.drawing-board-controls { margin: 0 auto; text-align: center; font-size: 0; display: table; border-spacing: 9.33333px 0; position: relative; min-height: 28px; } +.drawing-board-controls[data-align="left"] { margin: 0; left: -9.33333px; } +.drawing-board-controls[data-align="right"] { margin: 0 0 0 auto; right: -9.33333px; } +.drawing-board-canvas-wrapper + .drawing-board-controls, .drawing-board-controls + .drawing-board-canvas-wrapper { margin-top: 5px; } + +.drawing-board-controls-hidden { height: 0; min-height: 0; padding: 0; margin: 0; border: 0; } + +.drawing-board-control { display: table-cell; border-collapse: separate; vertical-align: middle; font-size: 16px; height: 100%; } + +.drawing-board-control-inner { position: relative; height: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } + +.drawing-board-control > button { margin: 0; vertical-align: middle; } + +.drawing-board-control-colors { font-size: 0; line-height: 0; } + +.drawing-board-control-colors-current { border: 1px solid #ccc; cursor: pointer; display: inline-block; width: 26px; height: 26px; } + +.drawing-board-control-colors-rainbows { display: inline-block; margin-left: 5px; position: absolute; left: 0; top: 33px; margin-left: 0; z-index: 100; width: 250px; height: auto; padding: 4px; } + +.drawing-board-control-colors-rainbow { height: 18px; } + +.drawing-board-control-colors-picker:first-child { margin-right: 5px; } + +.drawing-board-control-colors-picker { display: inline-block; width: 18px; height: 18px; cursor: pointer; } + +.drawing-board-control-colors-picker[data-color="rgba(255, 255, 255, 1)"] { width: 16px; height: 17px; border: 1px solid #ccc; border-bottom: none; } + +.drawing-board-control-colors-picker:hover { width: 16px; height: 16px; border: 1px solid #555; } + +.drawing-board-control-drawingmode > button { margin-right: 2px; } +.drawing-board-control-drawingmode > button:last-child { margin-right: 0; } + +.drawing-board-control-drawingmode-pencil-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAe9JREFUeNpiZAACVlFRBhYREQZcQPnbNwa3N28YlL5+ZfgLFfvPwGD9m4FhIgsDHuAO0gTUDNKIBvyBmqt/MTDMY8Gl0f31azD7L6oUIxCnAzWmAPHBfwwM01AMUAV6JfPQIVwOYgVqqPnFyOjz6///O38YGKpAgmAD1OXlGdTk5PD5hgeouZudj8/uy9evP/78/dsFFPsJNiAoKIiBABAHap4oLi9v8fTNm48//v7NBwbgWZgkE7rqt8DY+A8JZRBW+cfIuEDT0NDlzadP3z98/doPFDuCrB7TAGFhBqCNIGwM9OcKUzs7+xdv3355+f79VqDYAiTDwZgJh7ONgYpnOvn4GL949erT7UePdgL5JVCD4fgBLBBxaX74+PG789evnwby0/8jKXgExIeB+CG6Af///1e9Ki9vFSAkZPzoyZPPJy9evA9MB77/sWiEARZkzV+/fvXYtGnTpG3btj28EBT0BqjZ5D8OjXCwPksUhA1Wpggf/PHjx/9169Y9EBERaUlgZmaIAcrLE4rk5sIqBqDmlefnRPzfWGX5EaSZm5ubgRloADGA5QZ3RgK7gESY4PMNn9ZtObPpzZvfU4DiYkiB/RcHG+S7fyxAMH/lFU2GOZd2bLx18/cEUMoD4j9I+DcS/RtJHGTYf4AAAwAxaOMYHjxKFwAAAABJRU5ErkJggg=='); background-position: 50% 50%; background-repeat: no-repeat; } +.drawing-board-control-drawingmode-pencil-button:before { content: ""; display: block; width: 0; height: 100%; } + +.drawing-board-control-drawingmode-eraser-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAkpJREFUeNp0kk+IElEcx39vFBc9+OfQRTAwzFt4CaYOKStj6MoeculStzoIQSB4kCVckmDx4iGCXWYJIqjoVOzO1l4qT1F7WfBWHvxzDPyTB3XUmXn93suRybUffHmP997n9/cRsFgwGARJkiAcDsPlwgEIeEZQAhCRAkgAlOD6SQP4rgMFDWVnYCAQgFgsBqFQCBwOByzZNQOotPHx1RNCCCipu6bfb+zSnslkeOQVILPrBkAirbws9btdTEWAzZPXpfepOzaeGMBXwe/3w3+MwTc3Dl+UeghTiskbBvR6Pbh18mZHB0jjmxvCKhIfR37s3r+Sevf8ca/T4TBF2HTSODuDxP7uNjrZFFbBk8lEzOVyspa4ykGYw2zfbTb/7ilvok1YhlVVFfP5vDydTkHXdXDdlhZOOnPY4/HA0YPtp3h6LFjh8XgsFgoFGTPgsKm1zDr8ajTQh8Fh5eGjZzjGI8yjKlgjF4tFGdd/YKYmRja24hw+zu3sYe2HiH3hYzQjl8tleTQanWtou93G6Qngdrth6+1+9h6hTULJZ/PeziJXKhV5OByeg1ut1gJOp9NZTdNOcQ419ot+ggp1qoLdBFmqVmNpm3A8Huewy+Wq1RH8QH9zmBlJJpMRdCIqiiIPBgN+2MCGsW/r8/kgGo1m0fmpzWarseayHlmNeL1eFiWC0cRqtSr3+/3FpSiKHMZtjU1glbFyfKgLTqfzEka9OJvNeDnzz1JnCaFmqOl8ZdJY1SiDOXCiXKg1NtG5DIt0y6ov3dE/AgwAENFWYYLj4mYAAAAASUVORK5CYII='); background-position: 50% 50%; background-repeat: no-repeat; } +.drawing-board-control-drawingmode-eraser-button:before { content: ""; display: block; width: 0; height: 100%; } + +.drawing-board-control-drawingmode-filler-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAnNJREFUeNp0k0trE1EUx89MJpNJooYYXBgDNtCKdRPwlbqoCKUtaNVNA0Uo7UbMxoVPEARTXEi+QWfnwn6DEAlEkrSLttTGRiULEQlJ8yChmbzI++E50yTUJA78uMy953/u/557LmOz2WDEZ2m1WrckSRJSqdR2tVrdHQyYebwHtVoNuFHqTqczhQnWKaBYLDoKhcIuzgHDMKBSqeD20qd+LNdsNocSoFhRr9ctpVLJigl4xIIJQizLAmG4cAPa7bYcy9Iug5TL5UYikbD6/X7Rbre/IUcYe3WUW5ZsnQQzW9LpNOPz+UQc5aBM5mgdh7vI9FCCAesW2tnr9YqZTAby+bw8f3AQRP6853n+Ph5hemSCntjj8YjZbFYWx2IxeS2RSEMwuA87O79eqdXquVolK+GxnP0EPbHb7RZJSGABIR6PA11zJHKIR2MhHA5DIPDj7eH3j95KpfK60Wg8Yntil8slkqgnpioLghacTidoNDpEC3q9HnheCc3s1jZeLcW943pirPw/4lKpBkqlDubnl/riycnLsLy88EKj0fhzuRyZv8RFo1E6wpBYkiqy7Z54YmIcVlYeyOKC4mYwJ0nHRaQuM5vNT6hB/iceG7sIq6sPnwmC4MerDkby40AOCCoiddie1Wp92W7zQ2KTyQSLizNP8T0EsPLBbxEDnCj0GkM2qIEwyZRCobizsfH5A1ZXFhuN52F29vpz3HkL574mk8lj24Y5wsHkvjjoX0BOIWc5jruHzbK2ufmzEwpFO3jnDhQv4JoROYdoERVyGjEgZ8iBDlF3FzXo4go6utZ9lftY4N/dXisjR0i1G0ublv8KMAA0ZoUlicxrhwAAAABJRU5ErkJggg=='); background-position: 50% 50%; background-repeat: no-repeat; } +.drawing-board-control-drawingmode-filler-button:before { content: ""; display: block; width: 0; height: 100%; } + +.drawing-board-control-navigation > button { font-family: Helvetica, Arial, sans-serif; font-size: 14px; font-weight: bold; margin-right: 2px; } +.drawing-board-control-navigation > button:last-child { margin-right: 0; } + +.drawing-board-control-size[data-drawing-board-type="range"] .drawing-board-control-inner { width: 75px; } +.drawing-board-control-size[data-drawing-board-type="dropdown"] .drawing-board-control-inner { overflow: visible; } + +.drawing-board-control-size-range-input { position: relative; width: 100%; z-index: 100; margin: 0; padding: 0; border: 0; } + +.drawing-board-control-size-range-current, .drawing-board-control-size-dropdown-current span, .drawing-board-control-size-dropdown span { display: block; background: #333; opacity: .8; } + +.drawing-board-control-size-range-current { display: inline-block; opacity: .15; position: absolute; pointer-events: none; left: 50%; top: 50%; z-index: 50; } + +.drawing-board-control-size-dropdown-current { display: block; height: 100%; width: 40px; overflow: hidden; position: relative; } +.drawing-board-control-size-dropdown-current span { position: absolute; left: 50%; top: 50%; } + +.drawing-board-control-size-dropdown { position: absolute; left: -6px; top: 33px; height: auto; list-style-type: none; margin: 0; padding: 0; z-index: 100; } +.drawing-board-control-size-dropdown li { display: block; padding: 4px; margin: 3px 0; min-height: 16px; } +.drawing-board-control-size-dropdown li:hover { background: #ccc; } +.drawing-board-control-size-dropdown span { margin: 0 auto; } + +.drawing-board-control-download-button { overflow: hidden; *text-indent: -9999px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAoBJREFUeNqMkr9PU1EUx7/vR1tQ3yu10hAmTawtBSYSy2YccFOcnDQm/gewOLnj5GYMg4sdXFxkMca4OBAwdUBe/ZkIGFp+9tHXvh/3/fTcAm01aLzJybnn3nM+95tzrnDl6Tb+sibuTmWUWj3C6/Juk+LySUmyvt0FCKKA02ryOCy6LBiu15ngMbZ5DDCNBqIw6gKM+n4nECUJru3glKry8CpjQaHVYmC2rVH82DIMMMdGGARdwJ+SPNdFS9chx+MXDNMp/NzagWNatk/nQU/hiYAoih6FYTBCBs9zUXMCbAhx2OYOv351lPOJ3EwH4LteL6Dcp/Rfu3FrstDyIizt+agpaYxNDU0M9gl4v7Ck+TYrCYLQqZHUyTtdQBiutPSGUflczSXHs5lVKwZdSOBMvwztxVvN0RtzsiyXBFHsAvL5PBSnCpXV2getILFiE2SjspYbuZzPiDSZ2vOXmlvX5yQqTmMfg9ZXqtls1wnT09OHEyAq0aFLg/gSXsSWq9wWk+p9PrCoYTwcijdLOfE7UsEufN9HGIYnT4EnTGIXe1KqtNNIvuNnGamxfi7SgQD/nIJCTbzOPQ/SQh1pud7T4M6W/8qFIw/5WAr5m7Ozsw9UVc069Fls2yJzSC5/lnc9RhaHZVnfSqUnEgXP2oBqtYqBgYG2+mKxmOVADnAcB4yxHgD1RzehKKns/LyV4gUHBweQy+UyRkdH6UKJ6fQDFxcXoWkaXJeRuTgUGCdLQJ9bx72lGZimGWs2m+083oN+2iiFQiGxvLy8RrDzudyltgrG3N8U2G8CrPz4sGYYRqJSqWR4H/jNWbJhUjAWi8XG8R/L87yPpGCVttVfAgwAVpZR+8tZC08AAAAASUVORK5CYII='); background-position: 50% 50%; background-repeat: no-repeat; } +.drawing-board-control-download-button:before { content: ""; display: block; width: 0; height: 100%; } diff --git a/mondumas/static/dist/drawingboard/drawingboard.js b/mondumas/static/dist/drawingboard/drawingboard.js new file mode 100644 index 0000000..fbaee53 --- /dev/null +++ b/mondumas/static/dist/drawingboard/drawingboard.js @@ -0,0 +1,1374 @@ +/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js +* Copyright (c) 2015 Emmanuel Pelletier +* Licensed MIT */ +(function() { + +'use strict'; + +/** + * SimpleUndo is a very basic javascript undo/redo stack for managing histories of basically anything. + * + * options are: { + * * `provider` : required. a function to call on `save`, which should provide the current state of the historized object through the given "done" callback + * * `maxLength` : the maximum number of items in history + * * `opUpdate` : a function to call to notify of changes in history. Will be called on `save`, `undo`, `redo` and `clear` + * } + * + */ +var SimpleUndo = function(options) { + + var settings = options ? options : {}; + var defaultOptions = { + provider: function() { + throw new Error("No provider!"); + }, + maxLength: 30, + onUpdate: function() {} + }; + + this.provider = (typeof settings.provider != 'undefined') ? settings.provider : defaultOptions.provider; + this.maxLength = (typeof settings.maxLength != 'undefined') ? settings.maxLength : defaultOptions.maxLength; + this.onUpdate = (typeof settings.onUpdate != 'undefined') ? settings.onUpdate : defaultOptions.onUpdate; + + this.initialItem = null; + this.clear(); +}; + +function truncate (stack, limit) { + while (stack.length > limit) { + stack.shift(); + } +} + +SimpleUndo.prototype.initialize = function(initialItem) { + this.stack[0] = initialItem; + this.initialItem = initialItem; +}; + + +SimpleUndo.prototype.clear = function() { + this.stack = [this.initialItem]; + this.position = 0; + this.onUpdate(); +}; + +SimpleUndo.prototype.save = function() { + this.provider(function(current) { + truncate(this.stack, this.maxLength); + this.position = Math.min(this.position,this.stack.length - 1); + + this.stack = this.stack.slice(0, this.position + 1); + this.stack.push(current); + this.position++; + this.onUpdate(); + }.bind(this)); +}; + +SimpleUndo.prototype.undo = function(callback) { + if (this.canUndo()) { + var item = this.stack[--this.position]; + this.onUpdate(); + + if (callback) { + callback(item); + } + } +}; + +SimpleUndo.prototype.redo = function(callback) { + if (this.canRedo()) { + var item = this.stack[++this.position]; + this.onUpdate(); + + if (callback) { + callback(item); + } + } +}; + +SimpleUndo.prototype.canUndo = function() { + return this.position > 0; +}; + +SimpleUndo.prototype.canRedo = function() { + return this.position < this.count(); +}; + +SimpleUndo.prototype.count = function() { + return this.stack.length - 1; // -1 because of initial item +}; + + + + + +//exports +// node module +if (typeof module != 'undefined') { + module.exports = SimpleUndo; +} + +// browser global +if (typeof window != 'undefined') { + window.SimpleUndo = SimpleUndo; +} + +})(); +window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {}; + + +DrawingBoard.Utils = {}; + +/*! +* Tim (lite) +* github.com/premasagar/tim +*//* + A tiny, secure JavaScript micro-templating script. +*/ +DrawingBoard.Utils.tpl = (function(){ + "use strict"; + + var start = "{{", + end = "}}", + path = "[a-z0-9_][\\.a-z0-9_]*", // e.g. config.person.name + pattern = new RegExp(start + "\\s*("+ path +")\\s*" + end, "gi"), + undef; + + return function(template, data){ + // Merge data into the template string + return template.replace(pattern, function(tag, token){ + var path = token.split("."), + len = path.length, + lookup = data, + i = 0; + + for (; i < len; i++){ + lookup = lookup[path[i]]; + + // Property not found + if (lookup === undef){ + throw "tim: '" + path[i] + "' not found in " + tag; + } + + // Return the required value + if (i === len - 1){ + return lookup; + } + } + }); + }; +}()); + +/** + * https://github.com/jeromeetienne/microevent.js + * MicroEvent - to make any js object an event emitter (server or browser) + * + * - pure javascript - server compatible, browser compatible + * - dont rely on the browser doms + * - super simple - you get it immediatly, no mistery, no magic involved + * + * - create a MicroEventDebug with goodies to debug + * - make it safer to use +*/ +DrawingBoard.Utils.MicroEvent = function(){}; + +DrawingBoard.Utils.MicroEvent.prototype = { + bind : function(event, fct){ + this._events = this._events || {}; + this._events[event] = this._events[event] || []; + this._events[event].push(fct); + }, + unbind : function(event, fct){ + this._events = this._events || {}; + if( event in this._events === false ) return; + this._events[event].splice(this._events[event].indexOf(fct), 1); + }, + trigger : function(event /* , args... */){ + this._events = this._events || {}; + if( event in this._events === false ) return; + for(var i = 0; i < this._events[event].length; i++){ + this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1)); + } + } +}; + +//I know. +DrawingBoard.Utils._boxBorderSize = function($el, withPadding, withMargin, direction) { + withPadding = !!withPadding || true; + withMargin = !!withMargin || false; + var width = 0, + props; + if (direction == "width") { + props = ['border-left-width', 'border-right-width']; + if (withPadding) props.push('padding-left', 'padding-right'); + if (withMargin) props.push('margin-left', 'margin-right'); + } else { + props = ['border-top-width', 'border-bottom-width']; + if (withPadding) props.push('padding-top', 'padding-bottom'); + if (withMargin) props.push('margin-top', 'margin-bottom'); + } + for (var i = props.length - 1; i >= 0; i--) + width += parseInt($el.css(props[i]).replace('px', ''), 10); + return width; +}; + +DrawingBoard.Utils.boxBorderWidth = function($el, withPadding, withMargin) { + return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'width'); +}; + +DrawingBoard.Utils.boxBorderHeight = function($el, withPadding, withMargin) { + return DrawingBoard.Utils._boxBorderSize($el, withPadding, withMargin, 'height'); +}; + +DrawingBoard.Utils.isColor = function(string) { + if (!string || !string.length) return false; + return (/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i).test(string) || $.inArray(string.substring(0, 3), ['rgb', 'hsl']) !== -1; +}; + +/** + * Packs an RGB color into a single integer. + */ +DrawingBoard.Utils.RGBToInt = function(r, g, b) { + var c = 0; + c |= (r & 255) << 16; + c |= (g & 255) << 8; + c |= (b & 255); + return c; +}; + +/** + * Returns informations on the pixel located at (x,y). + */ +DrawingBoard.Utils.pixelAt = function(image, x, y) { + var i = (y * image.width + x) * 4; + var c = DrawingBoard.Utils.RGBToInt( + image.data[i], + image.data[i + 1], + image.data[i + 2] + ); + + return [ + i, // INDEX + x, // X + y, // Y + c // COLOR + ]; +}; + +/** + * Compares two colors with the given tolerance (between 0 and 255). + */ +DrawingBoard.Utils.compareColors = function(a, b, tolerance) { + if (tolerance === 0) { + return (a === b); + } + + var ra = (a >> 16) & 255, rb = (b >> 16) & 255, + ga = (a >> 8) & 255, gb = (b >> 8) & 255, + ba = a & 255, bb = b & 255; + + return (Math.abs(ra - rb) <= tolerance) + && (Math.abs(ga - gb) <= tolerance) + && (Math.abs(ba - bb) <= tolerance); +}; + +(function() { + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; + } +}()); + +window.DrawingBoard = typeof DrawingBoard !== "undefined" ? DrawingBoard : {}; + +/** + * pass the id of the html element to put the drawing board into + * and some options : { + * controls: array of controls to initialize with the drawingboard. 'Colors', 'Size', and 'Navigation' by default + * instead of simple strings, you can pass an object to define a control opts + * ie ['Color', { Navigation: { reset: false }}] + * controlsPosition: "top left" by default. Define where to put the controls: at the "top" or "bottom" of the canvas, aligned to "left"/"right"/"center" + * background: background of the drawing board. Give a hex color or an image url "#ffffff" (white) by default + * color: pencil color ("#000000" by default) + * size: pencil size (3 by default) + * webStorage: 'session', 'local' or false ('session' by default). store the current drawing in session or local storage and restore it when you come back + * droppable: true or false (false by default). If true, dropping an image on the canvas will include it and allow you to draw on it, + * errorMessage: html string to put in the board's element on browsers that don't support canvas. + * stretchImg: default behavior of image setting on the canvas: set to the canvas width/height or not? false by default + * } + */ +DrawingBoard.Board = function(id, opts) { + this.opts = this.mergeOptions(opts); + + this.ev = new DrawingBoard.Utils.MicroEvent(); + + this.id = id; + this.$el = $(document.getElementById(id)); + if (!this.$el.length) + return false; + + var tpl = '
It seems you use an obsolete browser. Update it to start drawing.
", + stretchImg: false //when setting the canvas img, strech the image at the whole canvas size when this opt is true +}; + + + +DrawingBoard.Board.prototype = { + + mergeOptions: function(opts) { + opts = $.extend({}, DrawingBoard.Board.defaultOpts, opts); + if (!opts.background && opts.eraserColor === "background") + opts.eraserColor = "transparent"; + return opts; + }, + + /** + * Canvas reset/resize methods: put back the canvas to its default values + * + * depending on options, can set color, size, background back to default values + * and store the reseted canvas in webstorage and history queue + * + * resize values depend on the `enlargeYourContainer` option + */ + + reset: function(opts) { + opts = $.extend({ + color: this.opts.color, + size: this.opts.size, + webStorage: true, + history: true, + background: false + }, opts); + + this.setMode('pencil'); + + if (opts.background) { + this.resetBackground(this.opts.background, $.proxy(function() { + if (opts.history) this.saveHistory(); + }, this)); + } + + if (opts.color) this.setColor(opts.color); + if (opts.size) this.ctx.lineWidth = opts.size; + + this.ctx.lineCap = "round"; + this.ctx.lineJoin = "round"; + // this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.width); + + if (opts.webStorage) this.saveWebStorage(); + + // if opts.background we already dealt with the history + if (opts.history && !opts.background) this.saveHistory(); + + this.blankCanvas = this.getImg(); + + this.ev.trigger('board:reset', opts); + }, + + resetBackground: function(background, callback) { + background = background || this.opts.background; + + var bgIsColor = DrawingBoard.Utils.isColor(background); + var prevMode = this.getMode(); + this.setMode('pencil'); + this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + if (bgIsColor) { + this.ctx.fillStyle = background; + this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + this.history.initialize(this.getImg()); + if (callback) callback(); + } else if (background) + this.setImg(background, { + callback: $.proxy(function() { + this.history.initialize(this.getImg()); + if (callback) callback(); + }, this) + }); + this.setMode(prevMode); + }, + + resize: function() { + this.dom.$controls.toggleClass('drawing-board-controls-hidden', (!this.controls || !this.controls.length)); + + var canvasWidth, canvasHeight; + var widths = [ + this.$el.width(), + DrawingBoard.Utils.boxBorderWidth(this.$el), + DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper, true, true) + ]; + var heights = [ + this.$el.height(), + DrawingBoard.Utils.boxBorderHeight(this.$el), + this.dom.$controls.height(), + DrawingBoard.Utils.boxBorderHeight(this.dom.$controls, false, true), + DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper, true, true) + ]; + var that = this; + var sum = function(values, multiplier) { //make the sum of all array values + multiplier = multiplier || 1; + var res = values[0]; + for (var i = 1; i < values.length; i++) { + res = res + (values[i]*multiplier); + } + return res; + }; + var sub = function(values) { return sum(values, -1); }; //substract all array values from the first one + + if (this.opts.enlargeYourContainer) { + canvasWidth = this.$el.width(); + canvasHeight = this.$el.height(); + + this.$el.width( sum(widths) ); + this.$el.height( sum(heights) ); + } else { + canvasWidth = sub(widths); + canvasHeight = sub(heights); + } + + this.dom.$canvasWrapper.css('width', canvasWidth + 'px'); + this.dom.$canvasWrapper.css('height', canvasHeight + 'px'); + + this.dom.$canvas.css('width', canvasWidth + 'px'); + this.dom.$canvas.css('height', canvasHeight + 'px'); + + this.canvas.width = canvasWidth; + this.canvas.height = canvasHeight; + }, + + + + /** + * Controls: + * the drawing board can has various UI elements to control it. + * one control is represented by a class in the namespace DrawingBoard.Control + * it must have a $el property (jQuery object), representing the html element to append on the drawing board at initialization. + * + */ + + initControls: function() { + this.controls = []; + if (!this.opts.controls.length || !DrawingBoard.Control) return false; + for (var i = 0; i < this.opts.controls.length; i++) { + var c = null; + if (typeof this.opts.controls[i] == "string") + c = new window['DrawingBoard']['Control'][this.opts.controls[i]](this); + else if (typeof this.opts.controls[i] == "object") { + for (var controlName in this.opts.controls[i]) break; + c = new window['DrawingBoard']['Control'][controlName](this, this.opts.controls[i][controlName]); + } + if (c) { + this.addControl(c); + } + } + }, + + //add a new control or an existing one at the position you want in the UI + //to add a totally new control, you can pass a string with the js class as 1st parameter and control options as 2nd ie "addControl('Navigation', { reset: false }" + //the last parameter (2nd or 3rd depending on the situation) is always the position you want to place the control at + addControl: function(control, optsOrPos, pos) { + if (typeof control !== "string" && (typeof control !== "object" || !control instanceof DrawingBoard.Control)) + return false; + + var opts = typeof optsOrPos == "object" ? optsOrPos : {}; + pos = pos ? pos*1 : (typeof optsOrPos == "number" ? optsOrPos : null); + + if (typeof control == "string") + control = new window['DrawingBoard']['Control'][control](this, opts); + + if (pos) + this.dom.$controls.children().eq(pos).before(control.$el); + else + this.dom.$controls.append(control.$el); + + if (!this.controls) + this.controls = []; + this.controls.push(control); + this.dom.$controls.removeClass('drawing-board-controls-hidden'); + }, + + + + /** + * History methods: undo and redo drawed lines + */ + + initHistory: function() { + this.history = new SimpleUndo({ + maxLength: 30, + provider: $.proxy(function(done) { + done(this.getImg()); + }, this), + onUpdate: $.proxy(function() { + this.ev.trigger('historyNavigation'); + }, this) + }); + }, + + saveHistory: function() { + this.history.save(); + }, + + restoreHistory: function(image) { + this.setImg(image, { + callback: $.proxy(function() { + this.saveWebStorage(); + }, this) + }); + }, + + goBackInHistory: function() { + this.history.undo($.proxy(this.restoreHistory, this)); + }, + + goForthInHistory: function() { + this.history.redo($.proxy(this.restoreHistory, this)); + }, + + /** + * Image methods: you can directly put an image on the canvas, get it in base64 data url or start a download + */ + + setImg: function(src, opts) { + opts = $.extend({ + stretch: this.opts.stretchImg, + callback: null + }, opts); + + var ctx = this.ctx; + var img = new Image(); + var oldGCO = ctx.globalCompositeOperation; + img.onload = function() { + ctx.globalCompositeOperation = "source-over"; + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + + if (opts.stretch) { + ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height); + } else { + ctx.drawImage(img, 0, 0); + } + + ctx.globalCompositeOperation = oldGCO; + + if (opts.callback) { + opts.callback(); + } + }; + img.src = src; + }, + + getImg: function() { + return this.canvas.toDataURL("image/png"); + }, + + downloadImg: function() { + var img = this.getImg(); + img = img.replace("image/png", "image/octet-stream"); + window.location.href = img; + }, + + + + /** + * WebStorage handling : save and restore to local or session storage + */ + + saveWebStorage: function() { + if (window[this.storage]) { + window[this.storage].setItem('drawing-board-' + this.id, this.getImg()); + this.ev.trigger('board:save' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), this.getImg()); + } + }, + + restoreWebStorage: function() { + if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) { + this.setImg(window[this.storage].getItem('drawing-board-' + this.id)); + this.ev.trigger('board:restore' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), window[this.storage].getItem('drawing-board-' + this.id)); + } + }, + + clearWebStorage: function() { + if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) { + window[this.storage].removeItem('drawing-board-' + this.id); + this.ev.trigger('board:clear' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1)); + } + }, + + _getStorage: function() { + if (!this.opts.webStorage || !(this.opts.webStorage === 'session' || this.opts.webStorage === 'local')) return false; + return this.opts.webStorage + 'Storage'; + }, + + + + /** + * Drop an image on the canvas to draw on it + */ + + initDropEvents: function() { + if (!this.opts.droppable) + return false; + + this.dom.$canvas.on('dragover dragenter drop', function(e) { + e.stopPropagation(); + e.preventDefault(); + }); + + this.dom.$canvas.on('drop', $.proxy(this._onCanvasDrop, this)); + }, + + _onCanvasDrop: function(e) { + e = e.originalEvent ? e.originalEvent : e; + var files = e.dataTransfer.files; + if (!files || !files.length || files[0].type.indexOf('image') == -1 || !window.FileReader) + return false; + var fr = new FileReader(); + fr.readAsDataURL(files[0]); + fr.onload = $.proxy(function(ev) { + this.setImg(ev.target.result, { + callback: $.proxy(function() { + this.saveHistory(); + }, this) + }); + this.ev.trigger('board:imageDropped', ev.target.result); + this.ev.trigger('board:userAction'); + }, this); + }, + + + + /** + * set and get current drawing mode + * + * possible modes are "pencil" (draw normally), "eraser" (draw transparent, like, erase, you know), "filler" (paint can) + */ + + setMode: function(newMode, silent) { + silent = silent || false; + newMode = newMode || 'pencil'; + + this.ev.unbind('board:startDrawing', $.proxy(this.fill, this)); + + if (this.opts.eraserColor === "transparent") + this.ctx.globalCompositeOperation = newMode === "eraser" ? "destination-out" : "source-over"; + else { + if (newMode === "eraser") { + if (this.opts.eraserColor === "background" && DrawingBoard.Utils.isColor(this.opts.background)) + this.ctx.strokeStyle = this.opts.background; + else if (DrawingBoard.Utils.isColor(this.opts.eraserColor)) + this.ctx.strokeStyle = this.opts.eraserColor; + } else if (!this.mode || this.mode === "eraser") { + this.ctx.strokeStyle = this.color; + } + + if (newMode === "filler") + this.ev.bind('board:startDrawing', $.proxy(this.fill, this)); + } + this.mode = newMode; + if (!silent) + this.ev.trigger('board:mode', this.mode); + }, + + getMode: function() { + return this.mode || "pencil"; + }, + + setColor: function(color) { + var that = this; + color = color || this.color; + if (!DrawingBoard.Utils.isColor(color)) + return false; + this.color = color; + if (this.opts.eraserColor !== "transparent" && this.mode === "eraser") { + var setStrokeStyle = function(mode) { + if (mode !== "eraser") + that.strokeStyle = that.color; + that.ev.unbind('board:mode', setStrokeStyle); + }; + this.ev.bind('board:mode', setStrokeStyle); + } else + this.ctx.strokeStyle = this.color; + }, + + /** + * Fills an area with the current stroke color. + */ + fill: function(e) { + if (this.getImg() === this.blankCanvas) { + this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + this.ctx.fillStyle = this.color; + this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + return; + } + + var img = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); + + // constants identifying pixels components + var INDEX = 0, X = 1, Y = 2, COLOR = 3; + + // target color components + var stroke = this.ctx.strokeStyle; + var r = parseInt(stroke.substr(1, 2), 16); + var g = parseInt(stroke.substr(3, 2), 16); + var b = parseInt(stroke.substr(5, 2), 16); + + // starting point + var start = DrawingBoard.Utils.pixelAt(img, parseInt(e.coords.x, 10), parseInt(e.coords.y, 10)); + var startColor = start[COLOR]; + var tolerance = this.opts.fillTolerance; + var useHack = this.opts.fillHack; //see https://github.com/Leimi/drawingboard.js/pull/38 + + // no need to continue if starting and target colors are the same + if (DrawingBoard.Utils.compareColors(startColor, DrawingBoard.Utils.RGBToInt(r, g, b), tolerance)) + return; + + // pixels to evaluate + var queue = [start]; + + // loop vars + var pixel, x, y; + var maxX = img.width - 1; + var maxY = img.height - 1; + + function updatePixelColor(pixel) { + img.data[pixel[INDEX]] = r; + img.data[pixel[INDEX] + 1] = g; + img.data[pixel[INDEX] + 2] = b; + } + + while ((pixel = queue.pop())) { + if (useHack) + updatePixelColor(pixel); + + if (DrawingBoard.Utils.compareColors(pixel[COLOR], startColor, tolerance)) { + if (!useHack) + updatePixelColor(pixel); + if (pixel[X] > 0) // west + queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] - 1, pixel[Y])); + if (pixel[X] < maxX) // east + queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] + 1, pixel[Y])); + if (pixel[Y] > 0) // north + queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] - 1)); + if (pixel[Y] < maxY) // south + queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] + 1)); + } + } + + this.ctx.putImageData(img, 0, 0); + }, + + + /** + * Drawing handling, with mouse or touch + */ + + initDrawEvents: function() { + this.isDrawing = false; + this.isMouseHovering = false; + this.coords = {}; + this.coords.old = this.coords.current = this.coords.oldMid = { x: 0, y: 0 }; + + this.dom.$canvas.on('mousedown touchstart', $.proxy(function(e) { + this._onInputStart(e, this._getInputCoords(e) ); + }, this)); + + this.dom.$canvas.on('mousemove touchmove', $.proxy(function(e) { + this._onInputMove(e, this._getInputCoords(e) ); + }, this)); + + this.dom.$canvas.on('mousemove', $.proxy(function(e) { + + }, this)); + + this.dom.$canvas.on('mouseup touchend', $.proxy(function(e) { + this._onInputStop(e, this._getInputCoords(e) ); + }, this)); + + this.dom.$canvas.on('mouseover', $.proxy(function(e) { + this._onMouseOver(e, this._getInputCoords(e) ); + }, this)); + + this.dom.$canvas.on('mouseout', $.proxy(function(e) { + this._onMouseOut(e, this._getInputCoords(e) ); + + }, this)); + + $('body').on('mouseup touchend', $.proxy(function(e) { + this.isDrawing = false; + }, this)); + + if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(this.draw, this) ); + }, + + draw: function() { + //if the pencil size is big (>10), the small crosshair makes a friend: a circle of the size of the pencil + //todo: have the circle works on every browser - it currently should be added only when CSS pointer-events are supported + //we assume that if requestAnimationFrame is supported, pointer-events is too, but this is terribad. + if (window.requestAnimationFrame && this.ctx.lineWidth > 10 && this.isMouseHovering) { + this.dom.$cursor.css({ width: this.ctx.lineWidth + 'px', height: this.ctx.lineWidth + 'px' }); + var transform = DrawingBoard.Utils.tpl("translateX({{x}}px) translateY({{y}}px)", { x: this.coords.current.x-(this.ctx.lineWidth/2), y: this.coords.current.y-(this.ctx.lineWidth/2) }); + this.dom.$cursor.css({ 'transform': transform, '-webkit-transform': transform, '-ms-transform': transform }); + this.dom.$cursor.removeClass('drawing-board-utils-hidden'); + } else { + this.dom.$cursor.addClass('drawing-board-utils-hidden'); + } + + if (this.isDrawing) { + var currentMid = this._getMidInputCoords(this.coords.current); + this.ctx.beginPath(); + this.ctx.moveTo(currentMid.x, currentMid.y); + this.ctx.quadraticCurveTo(this.coords.old.x, this.coords.old.y, this.coords.oldMid.x, this.coords.oldMid.y); + this.ctx.stroke(); + + this.coords.old = this.coords.current; + this.coords.oldMid = currentMid; + } + + if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(function() { this.draw(); }, this) ); + }, + + _onInputStart: function(e, coords) { + this.coords.current = this.coords.old = coords; + this.coords.oldMid = this._getMidInputCoords(coords); + this.isDrawing = true; + + if (!window.requestAnimationFrame) this.draw(); + + this.ev.trigger('board:startDrawing', {e: e, coords: coords}); + e.stopPropagation(); + e.preventDefault(); + }, + + _onInputMove: function(e, coords) { + this.coords.current = coords; + this.ev.trigger('board:drawing', {e: e, coords: coords}); + + if (!window.requestAnimationFrame) this.draw(); + + e.stopPropagation(); + e.preventDefault(); + }, + + _onInputStop: function(e, coords) { + if (this.isDrawing && (!e.touches || e.touches.length === 0)) { + this.isDrawing = false; + + this.saveWebStorage(); + this.saveHistory(); + + this.ev.trigger('board:stopDrawing', {e: e, coords: coords}); + this.ev.trigger('board:userAction'); + e.stopPropagation(); + e.preventDefault(); + } + }, + + _onMouseOver: function(e, coords) { + this.isMouseHovering = true; + this.coords.old = this._getInputCoords(e); + this.coords.oldMid = this._getMidInputCoords(this.coords.old); + + this.ev.trigger('board:mouseOver', {e: e, coords: coords}); + }, + + _onMouseOut: function(e, coords) { + this.isMouseHovering = false; + + this.ev.trigger('board:mouseOut', {e: e, coords: coords}); + }, + + _getInputCoords: function(e) { + e = e.originalEvent ? e.originalEvent : e; + var + rect = this.canvas.getBoundingClientRect(), + width = this.dom.$canvas.width(), + height = this.dom.$canvas.height() + ; + var x, y; + if (e.touches && e.touches.length == 1) { + x = e.touches[0].pageX; + y = e.touches[0].pageY; + } else { + x = e.pageX; + y = e.pageY; + } + x = x - this.dom.$canvas.offset().left; + y = y - this.dom.$canvas.offset().top; + x *= (width / rect.width); + y *= (height / rect.height); + return { + x: x, + y: y + }; + }, + + _getMidInputCoords: function(coords) { + return { + x: this.coords.old.x + coords.x>>1, + y: this.coords.old.y + coords.y>>1 + }; + } +}; + +DrawingBoard.Control = function(drawingBoard, opts) { + this.board = drawingBoard; + this.opts = $.extend({}, this.defaults, opts); + + this.$el = $(document.createElement('div')).addClass('drawing-board-control'); + if (this.name) + this.$el.addClass('drawing-board-control-' + this.name); + + this.board.ev.bind('board:reset', $.proxy(this.onBoardReset, this)); + + this.initialize.apply(this, arguments); + return this; +}; + +DrawingBoard.Control.prototype = { + + name: '', + + defaults: {}, + + initialize: function() { + + }, + + addToBoard: function() { + this.board.addControl(this); + }, + + onBoardReset: function(opts) { + + } + +}; + +//extend directly taken from backbone.js +DrawingBoard.Control.extend = function(protoProps, staticProps) { + var parent = this; + var child; + if (protoProps && protoProps.hasOwnProperty('constructor')) { + child = protoProps.constructor; + } else { + child = function(){ return parent.apply(this, arguments); }; + } + $.extend(child, parent, staticProps); + var Surrogate = function(){ this.constructor = child; }; + Surrogate.prototype = parent.prototype; + child.prototype = new Surrogate(); + if (protoProps) $.extend(child.prototype, protoProps); + child.__super__ = parent.prototype; + return child; +}; +DrawingBoard.Control.Color = DrawingBoard.Control.extend({ + name: 'colors', + + initialize: function() { + this.initTemplate(); + + var that = this; + this.$el.on('click', '.drawing-board-control-colors-picker', function(e) { + var color = $(this).attr('data-color'); + that.board.setColor(color); + that.$el.find('.drawing-board-control-colors-current') + .css('background-color', color) + .attr('data-color', color); + + that.board.ev.trigger('color:changed', color); + that.$el.find('.drawing-board-control-colors-rainbows').addClass('drawing-board-utils-hidden'); + + e.preventDefault(); + }); + + this.$el.on('click', '.drawing-board-control-colors-current', function(e) { + that.$el.find('.drawing-board-control-colors-rainbows').toggleClass('drawing-board-utils-hidden'); + e.preventDefault(); + }); + + $('body').on('click', function(e) { + var $target = $(e.target); + var $relatedButton = $target.hasClass('drawing-board-control-colors-current') ? $target : $target.closest('.drawing-board-control-colors-current'); + var $myButton = that.$el.find('.drawing-board-control-colors-current'); + var $popup = that.$el.find('.drawing-board-control-colors-rainbows'); + if ( (!$relatedButton.length || $relatedButton.get(0) !== $myButton.get(0)) && !$popup.hasClass('drawing-board-utils-hidden') ) + $popup.addClass('drawing-board-utils-hidden'); + }); + }, + + initTemplate: function() { + var tpl = 'It seems you use an obsolete browser. Update it to start drawing.
',stretchImg:!1},DrawingBoard.Board.prototype={mergeOptions:function(a){return a=$.extend({},DrawingBoard.Board.defaultOpts,a),a.background||"background"!==a.eraserColor||(a.eraserColor="transparent"),a},reset:function(a){a=$.extend({color:this.opts.color,size:this.opts.size,webStorage:!0,history:!0,background:!1},a),this.setMode("pencil"),a.background&&this.resetBackground(this.opts.background,$.proxy(function(){a.history&&this.saveHistory()},this)),a.color&&this.setColor(a.color),a.size&&(this.ctx.lineWidth=a.size),this.ctx.lineCap="round",this.ctx.lineJoin="round",a.webStorage&&this.saveWebStorage(),a.history&&!a.background&&this.saveHistory(),this.blankCanvas=this.getImg(),this.ev.trigger("board:reset",a)},resetBackground:function(a,b){a=a||this.opts.background;var c=DrawingBoard.Utils.isColor(a),d=this.getMode();this.setMode("pencil"),this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),c?(this.ctx.fillStyle=a,this.ctx.fillRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),this.history.initialize(this.getImg()),b&&b()):a&&this.setImg(a,{callback:$.proxy(function(){this.history.initialize(this.getImg()),b&&b()},this)}),this.setMode(d)},resize:function(){this.dom.$controls.toggleClass("drawing-board-controls-hidden",!this.controls||!this.controls.length);var a,b,c=[this.$el.width(),DrawingBoard.Utils.boxBorderWidth(this.$el),DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper,!0,!0)],d=[this.$el.height(),DrawingBoard.Utils.boxBorderHeight(this.$el),this.dom.$controls.height(),DrawingBoard.Utils.boxBorderHeight(this.dom.$controls,!1,!0),DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper,!0,!0)],e=function(a,b){b=b||1;for(var c=a[0],d=1;dIt seems you use an obsolete browser. Update it to start drawing.
", + stretchImg: false //when setting the canvas img, strech the image at the whole canvas size when this opt is true +}; + + + +DrawingBoard.Board.prototype = { + + mergeOptions: function(opts) { + opts = $.extend({}, DrawingBoard.Board.defaultOpts, opts); + if (!opts.background && opts.eraserColor === "background") + opts.eraserColor = "transparent"; + return opts; + }, + + /** + * Canvas reset/resize methods: put back the canvas to its default values + * + * depending on options, can set color, size, background back to default values + * and store the reseted canvas in webstorage and history queue + * + * resize values depend on the `enlargeYourContainer` option + */ + + reset: function(opts) { + opts = $.extend({ + color: this.opts.color, + size: this.opts.size, + webStorage: true, + history: true, + background: false + }, opts); + + this.setMode('pencil'); + + if (opts.background) { + this.resetBackground(this.opts.background, $.proxy(function() { + if (opts.history) this.saveHistory(); + }, this)); + } + + if (opts.color) this.setColor(opts.color); + if (opts.size) this.ctx.lineWidth = opts.size; + + this.ctx.lineCap = "round"; + this.ctx.lineJoin = "round"; + // this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.width); + + if (opts.webStorage) this.saveWebStorage(); + + // if opts.background we already dealt with the history + if (opts.history && !opts.background) this.saveHistory(); + + this.blankCanvas = this.getImg(); + + this.ev.trigger('board:reset', opts); + }, + + resetBackground: function(background, callback) { + background = background || this.opts.background; + + var bgIsColor = DrawingBoard.Utils.isColor(background); + var prevMode = this.getMode(); + this.setMode('pencil'); + this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + if (bgIsColor) { + this.ctx.fillStyle = background; + this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + this.history.initialize(this.getImg()); + if (callback) callback(); + } else if (background) + this.setImg(background, { + callback: $.proxy(function() { + this.history.initialize(this.getImg()); + if (callback) callback(); + }, this) + }); + this.setMode(prevMode); + }, + + resize: function() { + this.dom.$controls.toggleClass('drawing-board-controls-hidden', (!this.controls || !this.controls.length)); + + var canvasWidth, canvasHeight; + var widths = [ + this.$el.width(), + DrawingBoard.Utils.boxBorderWidth(this.$el), + DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper, true, true) + ]; + var heights = [ + this.$el.height(), + DrawingBoard.Utils.boxBorderHeight(this.$el), + this.dom.$controls.height(), + DrawingBoard.Utils.boxBorderHeight(this.dom.$controls, false, true), + DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper, true, true) + ]; + var that = this; + var sum = function(values, multiplier) { //make the sum of all array values + multiplier = multiplier || 1; + var res = values[0]; + for (var i = 1; i < values.length; i++) { + res = res + (values[i]*multiplier); + } + return res; + }; + var sub = function(values) { return sum(values, -1); }; //substract all array values from the first one + + if (this.opts.enlargeYourContainer) { + canvasWidth = this.$el.width(); + canvasHeight = this.$el.height(); + + this.$el.width( sum(widths) ); + this.$el.height( sum(heights) ); + } else { + canvasWidth = sub(widths); + canvasHeight = sub(heights); + } + + this.dom.$canvasWrapper.css('width', canvasWidth + 'px'); + this.dom.$canvasWrapper.css('height', canvasHeight + 'px'); + + this.dom.$canvas.css('width', canvasWidth + 'px'); + this.dom.$canvas.css('height', canvasHeight + 'px'); + + this.canvas.width = canvasWidth; + this.canvas.height = canvasHeight; + }, + + + + /** + * Controls: + * the drawing board can has various UI elements to control it. + * one control is represented by a class in the namespace DrawingBoard.Control + * it must have a $el property (jQuery object), representing the html element to append on the drawing board at initialization. + * + */ + + initControls: function() { + this.controls = []; + if (!this.opts.controls.length || !DrawingBoard.Control) return false; + for (var i = 0; i < this.opts.controls.length; i++) { + var c = null; + if (typeof this.opts.controls[i] == "string") + c = new window['DrawingBoard']['Control'][this.opts.controls[i]](this); + else if (typeof this.opts.controls[i] == "object") { + for (var controlName in this.opts.controls[i]) break; + c = new window['DrawingBoard']['Control'][controlName](this, this.opts.controls[i][controlName]); + } + if (c) { + this.addControl(c); + } + } + }, + + //add a new control or an existing one at the position you want in the UI + //to add a totally new control, you can pass a string with the js class as 1st parameter and control options as 2nd ie "addControl('Navigation', { reset: false }" + //the last parameter (2nd or 3rd depending on the situation) is always the position you want to place the control at + addControl: function(control, optsOrPos, pos) { + if (typeof control !== "string" && (typeof control !== "object" || !control instanceof DrawingBoard.Control)) + return false; + + var opts = typeof optsOrPos == "object" ? optsOrPos : {}; + pos = pos ? pos*1 : (typeof optsOrPos == "number" ? optsOrPos : null); + + if (typeof control == "string") + control = new window['DrawingBoard']['Control'][control](this, opts); + + if (pos) + this.dom.$controls.children().eq(pos).before(control.$el); + else + this.dom.$controls.append(control.$el); + + if (!this.controls) + this.controls = []; + this.controls.push(control); + this.dom.$controls.removeClass('drawing-board-controls-hidden'); + }, + + + + /** + * History methods: undo and redo drawed lines + */ + + initHistory: function() { + this.history = new SimpleUndo({ + maxLength: 30, + provider: $.proxy(function(done) { + done(this.getImg()); + }, this), + onUpdate: $.proxy(function() { + this.ev.trigger('historyNavigation'); + }, this) + }); + }, + + saveHistory: function() { + this.history.save(); + }, + + restoreHistory: function(image) { + this.setImg(image, { + callback: $.proxy(function() { + this.saveWebStorage(); + }, this) + }); + }, + + goBackInHistory: function() { + this.history.undo($.proxy(this.restoreHistory, this)); + }, + + goForthInHistory: function() { + this.history.redo($.proxy(this.restoreHistory, this)); + }, + + /** + * Image methods: you can directly put an image on the canvas, get it in base64 data url or start a download + */ + + setImg: function(src, opts) { + opts = $.extend({ + stretch: this.opts.stretchImg, + callback: null + }, opts); + + var ctx = this.ctx; + var img = new Image(); + var oldGCO = ctx.globalCompositeOperation; + img.onload = function() { + ctx.globalCompositeOperation = "source-over"; + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + + if (opts.stretch) { + ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height); + } else { + ctx.drawImage(img, 0, 0); + } + + ctx.globalCompositeOperation = oldGCO; + + if (opts.callback) { + opts.callback(); + } + }; + img.src = src; + }, + + getImg: function() { + return this.canvas.toDataURL("image/png"); + }, + + downloadImg: function() { + var img = this.getImg(); + img = img.replace("image/png", "image/octet-stream"); + window.location.href = img; + }, + + + + /** + * WebStorage handling : save and restore to local or session storage + */ + + saveWebStorage: function() { + if (window[this.storage]) { + window[this.storage].setItem('drawing-board-' + this.id, this.getImg()); + this.ev.trigger('board:save' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), this.getImg()); + } + }, + + restoreWebStorage: function() { + if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) { + this.setImg(window[this.storage].getItem('drawing-board-' + this.id)); + this.ev.trigger('board:restore' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1), window[this.storage].getItem('drawing-board-' + this.id)); + } + }, + + clearWebStorage: function() { + if (window[this.storage] && window[this.storage].getItem('drawing-board-' + this.id) !== null) { + window[this.storage].removeItem('drawing-board-' + this.id); + this.ev.trigger('board:clear' + this.storage.charAt(0).toUpperCase() + this.storage.slice(1)); + } + }, + + _getStorage: function() { + if (!this.opts.webStorage || !(this.opts.webStorage === 'session' || this.opts.webStorage === 'local')) return false; + return this.opts.webStorage + 'Storage'; + }, + + + + /** + * Drop an image on the canvas to draw on it + */ + + initDropEvents: function() { + if (!this.opts.droppable) + return false; + + this.dom.$canvas.on('dragover dragenter drop', function(e) { + e.stopPropagation(); + e.preventDefault(); + }); + + this.dom.$canvas.on('drop', $.proxy(this._onCanvasDrop, this)); + }, + + _onCanvasDrop: function(e) { + e = e.originalEvent ? e.originalEvent : e; + var files = e.dataTransfer.files; + if (!files || !files.length || files[0].type.indexOf('image') == -1 || !window.FileReader) + return false; + var fr = new FileReader(); + fr.readAsDataURL(files[0]); + fr.onload = $.proxy(function(ev) { + this.setImg(ev.target.result, { + callback: $.proxy(function() { + this.saveHistory(); + }, this) + }); + this.ev.trigger('board:imageDropped', ev.target.result); + this.ev.trigger('board:userAction'); + }, this); + }, + + + + /** + * set and get current drawing mode + * + * possible modes are "pencil" (draw normally), "eraser" (draw transparent, like, erase, you know), "filler" (paint can) + */ + + setMode: function(newMode, silent) { + silent = silent || false; + newMode = newMode || 'pencil'; + + this.ev.unbind('board:startDrawing', $.proxy(this.fill, this)); + + if (this.opts.eraserColor === "transparent") + this.ctx.globalCompositeOperation = newMode === "eraser" ? "destination-out" : "source-over"; + else { + if (newMode === "eraser") { + if (this.opts.eraserColor === "background" && DrawingBoard.Utils.isColor(this.opts.background)) + this.ctx.strokeStyle = this.opts.background; + else if (DrawingBoard.Utils.isColor(this.opts.eraserColor)) + this.ctx.strokeStyle = this.opts.eraserColor; + } else if (!this.mode || this.mode === "eraser") { + this.ctx.strokeStyle = this.color; + } + + if (newMode === "filler") + this.ev.bind('board:startDrawing', $.proxy(this.fill, this)); + } + this.mode = newMode; + if (!silent) + this.ev.trigger('board:mode', this.mode); + }, + + getMode: function() { + return this.mode || "pencil"; + }, + + setColor: function(color) { + var that = this; + color = color || this.color; + if (!DrawingBoard.Utils.isColor(color)) + return false; + this.color = color; + if (this.opts.eraserColor !== "transparent" && this.mode === "eraser") { + var setStrokeStyle = function(mode) { + if (mode !== "eraser") + that.strokeStyle = that.color; + that.ev.unbind('board:mode', setStrokeStyle); + }; + this.ev.bind('board:mode', setStrokeStyle); + } else + this.ctx.strokeStyle = this.color; + }, + + /** + * Fills an area with the current stroke color. + */ + fill: function(e) { + if (this.getImg() === this.blankCanvas) { + this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + this.ctx.fillStyle = this.color; + this.ctx.fillRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); + return; + } + + var img = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); + + // constants identifying pixels components + var INDEX = 0, X = 1, Y = 2, COLOR = 3; + + // target color components + var stroke = this.ctx.strokeStyle; + var r = parseInt(stroke.substr(1, 2), 16); + var g = parseInt(stroke.substr(3, 2), 16); + var b = parseInt(stroke.substr(5, 2), 16); + + // starting point + var start = DrawingBoard.Utils.pixelAt(img, parseInt(e.coords.x, 10), parseInt(e.coords.y, 10)); + var startColor = start[COLOR]; + var tolerance = this.opts.fillTolerance; + var useHack = this.opts.fillHack; //see https://github.com/Leimi/drawingboard.js/pull/38 + + // no need to continue if starting and target colors are the same + if (DrawingBoard.Utils.compareColors(startColor, DrawingBoard.Utils.RGBToInt(r, g, b), tolerance)) + return; + + // pixels to evaluate + var queue = [start]; + + // loop vars + var pixel, x, y; + var maxX = img.width - 1; + var maxY = img.height - 1; + + function updatePixelColor(pixel) { + img.data[pixel[INDEX]] = r; + img.data[pixel[INDEX] + 1] = g; + img.data[pixel[INDEX] + 2] = b; + } + + while ((pixel = queue.pop())) { + if (useHack) + updatePixelColor(pixel); + + if (DrawingBoard.Utils.compareColors(pixel[COLOR], startColor, tolerance)) { + if (!useHack) + updatePixelColor(pixel); + if (pixel[X] > 0) // west + queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] - 1, pixel[Y])); + if (pixel[X] < maxX) // east + queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X] + 1, pixel[Y])); + if (pixel[Y] > 0) // north + queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] - 1)); + if (pixel[Y] < maxY) // south + queue.push(DrawingBoard.Utils.pixelAt(img, pixel[X], pixel[Y] + 1)); + } + } + + this.ctx.putImageData(img, 0, 0); + }, + + + /** + * Drawing handling, with mouse or touch + */ + + initDrawEvents: function() { + this.isDrawing = false; + this.isMouseHovering = false; + this.coords = {}; + this.coords.old = this.coords.current = this.coords.oldMid = { x: 0, y: 0 }; + + this.dom.$canvas.on('mousedown touchstart', $.proxy(function(e) { + this._onInputStart(e, this._getInputCoords(e) ); + }, this)); + + this.dom.$canvas.on('mousemove touchmove', $.proxy(function(e) { + this._onInputMove(e, this._getInputCoords(e) ); + }, this)); + + this.dom.$canvas.on('mousemove', $.proxy(function(e) { + + }, this)); + + this.dom.$canvas.on('mouseup touchend', $.proxy(function(e) { + this._onInputStop(e, this._getInputCoords(e) ); + }, this)); + + this.dom.$canvas.on('mouseover', $.proxy(function(e) { + this._onMouseOver(e, this._getInputCoords(e) ); + }, this)); + + this.dom.$canvas.on('mouseout', $.proxy(function(e) { + this._onMouseOut(e, this._getInputCoords(e) ); + + }, this)); + + $('body').on('mouseup touchend', $.proxy(function(e) { + this.isDrawing = false; + }, this)); + + if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(this.draw, this) ); + }, + + draw: function() { + //if the pencil size is big (>10), the small crosshair makes a friend: a circle of the size of the pencil + //todo: have the circle works on every browser - it currently should be added only when CSS pointer-events are supported + //we assume that if requestAnimationFrame is supported, pointer-events is too, but this is terribad. + if (window.requestAnimationFrame && this.ctx.lineWidth > 10 && this.isMouseHovering) { + this.dom.$cursor.css({ width: this.ctx.lineWidth + 'px', height: this.ctx.lineWidth + 'px' }); + var transform = DrawingBoard.Utils.tpl("translateX({{x}}px) translateY({{y}}px)", { x: this.coords.current.x-(this.ctx.lineWidth/2), y: this.coords.current.y-(this.ctx.lineWidth/2) }); + this.dom.$cursor.css({ 'transform': transform, '-webkit-transform': transform, '-ms-transform': transform }); + this.dom.$cursor.removeClass('drawing-board-utils-hidden'); + } else { + this.dom.$cursor.addClass('drawing-board-utils-hidden'); + } + + if (this.isDrawing) { + var currentMid = this._getMidInputCoords(this.coords.current); + this.ctx.beginPath(); + this.ctx.moveTo(currentMid.x, currentMid.y); + this.ctx.quadraticCurveTo(this.coords.old.x, this.coords.old.y, this.coords.oldMid.x, this.coords.oldMid.y); + this.ctx.stroke(); + + this.coords.old = this.coords.current; + this.coords.oldMid = currentMid; + } + + if (window.requestAnimationFrame) requestAnimationFrame( $.proxy(function() { this.draw(); }, this) ); + }, + + _onInputStart: function(e, coords) { + this.coords.current = this.coords.old = coords; + this.coords.oldMid = this._getMidInputCoords(coords); + this.isDrawing = true; + + if (!window.requestAnimationFrame) this.draw(); + + this.ev.trigger('board:startDrawing', {e: e, coords: coords}); + e.stopPropagation(); + e.preventDefault(); + }, + + _onInputMove: function(e, coords) { + this.coords.current = coords; + this.ev.trigger('board:drawing', {e: e, coords: coords}); + + if (!window.requestAnimationFrame) this.draw(); + + e.stopPropagation(); + e.preventDefault(); + }, + + _onInputStop: function(e, coords) { + if (this.isDrawing && (!e.touches || e.touches.length === 0)) { + this.isDrawing = false; + + this.saveWebStorage(); + this.saveHistory(); + + this.ev.trigger('board:stopDrawing', {e: e, coords: coords}); + this.ev.trigger('board:userAction'); + e.stopPropagation(); + e.preventDefault(); + } + }, + + _onMouseOver: function(e, coords) { + this.isMouseHovering = true; + this.coords.old = this._getInputCoords(e); + this.coords.oldMid = this._getMidInputCoords(this.coords.old); + + this.ev.trigger('board:mouseOver', {e: e, coords: coords}); + }, + + _onMouseOut: function(e, coords) { + this.isMouseHovering = false; + + this.ev.trigger('board:mouseOut', {e: e, coords: coords}); + }, + + _getInputCoords: function(e) { + e = e.originalEvent ? e.originalEvent : e; + var + rect = this.canvas.getBoundingClientRect(), + width = this.dom.$canvas.width(), + height = this.dom.$canvas.height() + ; + var x, y; + if (e.touches && e.touches.length == 1) { + x = e.touches[0].pageX; + y = e.touches[0].pageY; + } else { + x = e.pageX; + y = e.pageY; + } + x = x - this.dom.$canvas.offset().left; + y = y - this.dom.$canvas.offset().top; + x *= (width / rect.width); + y *= (height / rect.height); + return { + x: x, + y: y + }; + }, + + _getMidInputCoords: function(coords) { + return { + x: this.coords.old.x + coords.x>>1, + y: this.coords.old.y + coords.y>>1 + }; + } +}; diff --git a/mondumas/static/dist/drawingboard/drawingboard.nocontrol.min.css b/mondumas/static/dist/drawingboard/drawingboard.nocontrol.min.css new file mode 100644 index 0000000..1ca2893 --- /dev/null +++ b/mondumas/static/dist/drawingboard/drawingboard.nocontrol.min.css @@ -0,0 +1,5 @@ +/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js +* Copyright (c) 2015 Emmanuel Pelletier +* Licensed MIT */ + +.drawing-board,.drawing-board *{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.drawing-board-utils-hidden{display:none!important}.drawing-board{position:relative;display:block}.drawing-board-canvas-wrapper{position:relative;margin:0;border:1px solid #ddd}.drawing-board-canvas{position:absolute;top:0;left:0;width:auto;cursor:crosshair;z-index:20}.drawing-board-cursor{position:absolute;top:0;left:0;pointer-events:none;border-radius:50%;background:#ccc;background:rgba(0,0,0,.2);z-index:30} \ No newline at end of file diff --git a/mondumas/static/dist/drawingboard/drawingboard.nocontrol.min.js b/mondumas/static/dist/drawingboard/drawingboard.nocontrol.min.js new file mode 100644 index 0000000..874de44 --- /dev/null +++ b/mondumas/static/dist/drawingboard/drawingboard.nocontrol.min.js @@ -0,0 +1,4 @@ +/* drawingboard.js v0.4.6 - https://github.com/Leimi/drawingboard.js +* Copyright (c) 2015 Emmanuel Pelletier +* Licensed MIT */ +!function(){"use strict";function a(a,b){for(;a.length>b;)a.shift()}var b=function(a){var b=a?a:{},c={provider:function(){throw new Error("No provider!")},maxLength:30,onUpdate:function(){}};this.provider="undefined"!=typeof b.provider?b.provider:c.provider,this.maxLength="undefined"!=typeof b.maxLength?b.maxLength:c.maxLength,this.onUpdate="undefined"!=typeof b.onUpdate?b.onUpdate:c.onUpdate,this.initialItem=null,this.clear()};b.prototype.initialize=function(a){this.stack[0]=a,this.initialItem=a},b.prototype.clear=function(){this.stack=[this.initialItem],this.position=0,this.onUpdate()},b.prototype.save=function(){this.provider(function(b){a(this.stack,this.maxLength),this.position=Math.min(this.position,this.stack.length-1),this.stack=this.stack.slice(0,this.position+1),this.stack.push(b),this.position++,this.onUpdate()}.bind(this))},b.prototype.undo=function(a){if(this.canUndo()){var b=this.stack[--this.position];this.onUpdate(),a&&a(b)}},b.prototype.redo=function(a){if(this.canRedo()){var b=this.stack[++this.position];this.onUpdate(),a&&a(b)}},b.prototype.canUndo=function(){return this.position>0},b.prototype.canRedo=function(){return this.positionIt seems you use an obsolete browser. Update it to start drawing.
',stretchImg:!1},DrawingBoard.Board.prototype={mergeOptions:function(a){return a=$.extend({},DrawingBoard.Board.defaultOpts,a),a.background||"background"!==a.eraserColor||(a.eraserColor="transparent"),a},reset:function(a){a=$.extend({color:this.opts.color,size:this.opts.size,webStorage:!0,history:!0,background:!1},a),this.setMode("pencil"),a.background&&this.resetBackground(this.opts.background,$.proxy(function(){a.history&&this.saveHistory()},this)),a.color&&this.setColor(a.color),a.size&&(this.ctx.lineWidth=a.size),this.ctx.lineCap="round",this.ctx.lineJoin="round",a.webStorage&&this.saveWebStorage(),a.history&&!a.background&&this.saveHistory(),this.blankCanvas=this.getImg(),this.ev.trigger("board:reset",a)},resetBackground:function(a,b){a=a||this.opts.background;var c=DrawingBoard.Utils.isColor(a),d=this.getMode();this.setMode("pencil"),this.ctx.clearRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),c?(this.ctx.fillStyle=a,this.ctx.fillRect(0,0,this.ctx.canvas.width,this.ctx.canvas.height),this.history.initialize(this.getImg()),b&&b()):a&&this.setImg(a,{callback:$.proxy(function(){this.history.initialize(this.getImg()),b&&b()},this)}),this.setMode(d)},resize:function(){this.dom.$controls.toggleClass("drawing-board-controls-hidden",!this.controls||!this.controls.length);var a,b,c=[this.$el.width(),DrawingBoard.Utils.boxBorderWidth(this.$el),DrawingBoard.Utils.boxBorderWidth(this.dom.$canvasWrapper,!0,!0)],d=[this.$el.height(),DrawingBoard.Utils.boxBorderHeight(this.$el),this.dom.$controls.height(),DrawingBoard.Utils.boxBorderHeight(this.dom.$controls,!1,!0),DrawingBoard.Utils.boxBorderHeight(this.dom.$canvasWrapper,!0,!0)],e=function(a,b){b=b||1;for(var c=a[0],d=1;d- Notes / Croquis -