This commit is contained in:
cthienan
2021-08-13 10:16:14 +02:00
19 changed files with 2706 additions and 19 deletions

View File

@@ -340,7 +340,7 @@ def get_status_by_id(request, code):
return results
def get_motifs(request):
query = """SELECT * FROM p_motfs;"""
query = """SELECT * FROM p_motifs;"""
results = request.dbsession.execute(query,).fetchall()
return results
@@ -349,3 +349,36 @@ def get_factures_en_att(request, societe):
FROM facture f JOIN p_statuts s ON f.STATUS = s.CODE WHERE f.societe=:societe AND f.STATUS < 8 ORDER BY f.societe, f.STATUS, f.nomcli;"""
results = request.dbsession.execute(query, {'societe': societe}).fetchall()
return results
def insert_dem_note(request, nodossier, type_note, logged_in):
societe = nodossier[0:2]
nochantier = int(nodossier[3:])
# une note ou croquis
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, 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 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()
return results
def delete_dem_note(request, nodossier, noligne):
societe = nodossier[0:2]
nochantier = int(nodossier[3:])
# une note ou croquis
query = "DELETE FROM dem_notes WHERE societe=:societe AND nochantier=:nochantier AND noligne=:noligne;"
execute_query(request, query, {'societe': societe, 'nochantier': nochantier, 'noligne': noligne})
def update_dem_note(request, nodossier, noligne, notes):
societe = nodossier[0:2]
nochantier = int(nodossier[3:])
# une note ou croquis
query = "UPDATE dem_notes SET notes=:notes WHERE societe=:societe AND nochantier=:nochantier AND noligne=:noligne;"
execute_query(request, query, {'societe': societe, 'nochantier': nochantier, 'noligne': noligne, 'notes': notes})

View File

@@ -34,14 +34,18 @@ 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')
config.add_route('delete_img','/delete_img/{nodossier}/{norapport}/{origine}/{nomfic}')
config.add_route('dossier_edit', '/dossier_edit/{nodossier}')
config.add_route('dossier_lookup', '/dossier_lookup')
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}')
config.add_route('rdf_edit','/rdf_edit/{nodossier}/{date_inter}')
@@ -53,7 +57,6 @@ def includeme(config):
config.add_route('upload_doc', '/upload_doc/{nodossier}/{origine}')
config.add_route('upload_img', '/upload_img/{norapport}/{origine}')
config.add_route('upload_om', '/upload_om')
config.add_route('dem_devis','/dem_devis')
# parametres
config.add_route('parametres', '/parametres')
config.add_route('article_edit', '/article_edit/{ref}')

View File

@@ -276,6 +276,10 @@ color: black;
}
}
#dessin {
width: 1140px;
height: 1140px;
}
/* ne pas affichier l'url after the link */
@media print {

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
/* 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; 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; }

View File

@@ -0,0 +1,971 @@
/* 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 = '<div class="drawing-board-canvas-wrapper"></canvas><canvas class="drawing-board-canvas"></canvas><div class="drawing-board-cursor drawing-board-utils-hidden"></div></div>';
if (this.opts.controlsPosition.indexOf("bottom") > -1) tpl += '<div class="drawing-board-controls"></div>';
else tpl = '<div class="drawing-board-controls"></div>' + tpl;
this.$el.addClass('drawing-board').append(tpl);
this.dom = {
$canvasWrapper: this.$el.find('.drawing-board-canvas-wrapper'),
$canvas: this.$el.find('.drawing-board-canvas'),
$cursor: this.$el.find('.drawing-board-cursor'),
$controls: this.$el.find('.drawing-board-controls')
};
$.each(['left', 'right', 'center'], $.proxy(function(n, val) {
if (this.opts.controlsPosition.indexOf(val) > -1) {
this.dom.$controls.attr('data-align', val);
return false;
}
}, this));
this.canvas = this.dom.$canvas.get(0);
this.ctx = this.canvas && this.canvas.getContext && this.canvas.getContext('2d') ? this.canvas.getContext('2d') : null;
this.color = this.opts.color;
if (!this.ctx) {
if (this.opts.errorMessage)
this.$el.html(this.opts.errorMessage);
return false;
}
this.storage = this._getStorage();
this.initHistory();
//init default board values before controls are added (mostly pencil color and size)
this.reset({ webStorage: false, history: false, background: false });
//init controls (they will need the default board values to work like pencil color and size)
this.initControls();
//set board's size after the controls div is added
this.resize();
//reset the board to take all resized space
this.reset({ webStorage: false, history: false, background: true });
this.restoreWebStorage();
this.initDropEvents();
this.initDrawEvents();
};
DrawingBoard.Board.defaultOpts = {
controls: ['Color', 'DrawingMode', 'Size', 'Navigation'],
controlsPosition: "top left",
color: "#000000",
size: 1,
background: "#fff",
eraserColor: "background",
fillTolerance: 100,
fillHack: true, //try to prevent issues with anti-aliasing with a little hack by default
webStorage: 'session',
droppable: false,
enlargeYourContainer: false,
errorMessage: "<p>It seems you use an obsolete browser. <a href=\"http://browsehappy.com/\" target=\"_blank\">Update it</a> to start drawing.</p>",
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
};
}
};

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -14,12 +14,9 @@
<div class="form-group">
<label class="control-label col-sm-2">Type de texte</label>
<div class="col-sm-10">
<label class="radio-inline"><input type="radio" name="ref" value="T1"
tal:attributes="checked ligne.ref=='T1'">Titre</label>
<label class="radio-inline"><input type="radio" name="ref" value="T2"
tal:attributes="checked ligne.ref=='T2'">Sous-titre</label>
<label class="radio-inline"><input type="radio" name="ref" value="TX"
tal:attributes="checked ligne.ref=='TX'">Texte libre</label>
<label class="radio-inline"><input type="radio" name="ref" value="T1" checked>Titre</label>
<label class="radio-inline"><input type="radio" name="ref" value="T2">Sous-titre</label>
<label class="radio-inline"><input type="radio" name="ref" value="TX">Texte libre</label>
</div>
</div>
<div class="form-group">
@@ -47,8 +44,11 @@
<div class="form-group">
<label class="control-label col-sm-2">Quantité</label>
<div class="col-sm-10">
<input class="form-control" type="text" id="qte" name="qte" value="${ligne.qte}">
</div>
<div class="input-group">
<span class="input-group-addon" id="unite">.00</span>
<input class="form-control" type="text" id="qte" name="qte" value="${ligne.qte}">
</div>
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2">Prix HT</label>
@@ -196,11 +196,13 @@ $(document).ready(function() {
var ref = response[0]['ref'];
var libelle = response[0]['libelle'];
var prixht = response[0]['prixht'];
var unite = response[0]['unite'];
// Set value to textboxes
document.getElementById('ref').value = ref;
document.getElementById('libelle').value = libelle;
document.getElementById('prixht').value = prixht;
document.getElementById("unite").innerHTML= unite;
}
}
});

View File

@@ -0,0 +1,28 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<div style="row">
<div id="dessin"></div>
</div> <!-- row -->
<!-- Drawingboard -->
<link href="${request.static_url('mondumas:static/dist/drawingboard/drawingboard.min.css')}" rel="stylesheet" media="all">
<script src="${request.static_url('mondumas:static/dist/drawingboard/drawingboard.min.js')}"></script>
<script>
var simpleBoard = new DrawingBoard.Board('dessin', {
background: 'none',
size: 10,
controls: [
'Color',
{ Size: { type: 'dropdown' } },
{ Navigation: { } },
],
size: 1,
webStorage: 'session',
enlargeYourContainer: true
});
</script>
</div><!-- content -->
</metal:block>

View File

@@ -282,6 +282,39 @@
<td class="text-center">${detail.usermaj}</td>
</tr>
</table>
<h3 class="text-center">NOTES et CROQUIS</h3>
<br />
<div class="row">
<!-- bouton AJOUT NOTE-->
<div class="text-center col-sm-3">
<a href="${request.application_url}/note_add/${nodossier}">
<span class="glyphicon glyphicon-plus logo-success"></span>
<h4>NOTE</h4></a>
</div>
<!-- Listes des Notes -->
<div class="text-center" tal:repeat="item dem_notes">
<div class="col-sm-3">
<a href="${request.application_url}/note_edit/${nodossier}/${item.noligne}" tal:condition="item.type_note=='NOTE'">
<span class="glyphicon glyphicon-text-size logo-primary"></span>
<h4>${item.libelle}</h4></a>
</div>
</div>
</div>
<div class="row">
<div class="text-center col-sm-3">
<a href="${request.application_url}/croquis_edit/">
<span class="glyphicon glyphicon-plus logo-success"></span>
<h4>CROQUIS</h4></a>
</div>
<div class="text-center" tal:repeat="item dem_croquis">
<div class="col-sm-3">
<a href="${request.application_url}/croquis_edit/" tal:condition="item.type_note=='CROQUIS'">
<span class="glyphicon glyphicon-picture logo-primary"></span>
<h4>${item.libelle}</h4></a>
</div>
</div>
</div>
</div>
<!-- PANEL RDF -->
<div id="tab_rdf" class="tab-pane fade">
@@ -345,8 +378,8 @@
<div class="col-xs-8">
<select class="form-control" id="motif" name="motif">
<option selected> </option>
<div tal:repeat="item motif">
<option>${item.code} | ${item.libelle}</option>
<div tal:repeat="item motifs">
<option>${item.libelle}</option>
</div>
</select>
</div>
@@ -361,6 +394,34 @@
</div>
</div>
</div>
<!-- Modal : add NOTES et CROQUIS -->
<div class="modal fade" id="addNote" role="dialog" aria-labelledby="addNoteLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Ajouter une note ou un croquis</h4>
</div>
<div class="modal-body">
<!-- The form is placed inside the body of modal -->
<form id="add_justif-form" class="form-horizontal" action="${url}" method="post">
<div class="form-group">
<label class="control-label col-sm-6">Voulez-vous ajouter</label>
<div class="col-sm-6">
<label class="radio-inline"><input type="radio" name="type_note" value="NOTE" checked>une note</label>
<label class="radio-inline"><input type="radio" name="type_note" value="CROQUIS">un croquis</label>
</div>
</div>
<br>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-warning" name="form.addNote">Ajouter</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</metal:block>

View File

@@ -0,0 +1,38 @@
<metal:block use-macro="main_template">
<div metal:fill-slot="content">
<div class="alert alert-danger" tal:condition="message" tal:content="message" />
<div class="row">
<form id="text_edit-form" action="${url}" method="post">
<div class="form-group">
<label class="control-label" for="text-text">Tapez ou dictez le texte :</label>
<textarea class="form-control monospace-font" rows="30" cols="40" id="notes" name="notes">${note.notes}</textarea>
</div>
<p>Modifié le : ${note.modif_le.strftime('%d-%m-%Y')} par ${note.usermaj}
</p>
<div class="form-group">
<a href="${request.application_url}/dossier_view/${nodossier}" class="btn btn-default" role="button">
<span class="glyphicon glyphicon-chevron-left"></span>&nbsp;Annuler</a>
<button class="btn btn-primary" type="submit" name="form.submitted">
<span class="glyphicon glyphicon-ok"></span>&nbsp;Enregistrer</button>
<button class="btn btn-warning" type="submit" name="form.deleted">
<span class="glyphicon glyphicon-remove"></span>&nbsp;Supprimer</button>
</div>
</form>
<br />
<br />
</div> <!-- row -->
<script>
$(document).ready(function() {
$('#text_edit-form').formValidation();
$('form input').on('keypress', function(e) {
return e.which !== 13;
});
});
</script>
</div>
</metal:block>

View File

@@ -34,10 +34,6 @@
<a href="${request.application_url}/text_list"><span class="glyphicon glyphicon-list logo-primary"></span></a>
<h4>TEXTES EMAIL</h4>
</div>
<div class="col-sm-3">
<a href="${request.application_url}/new_home"><span class="glyphicon glyphicon-dashboard logo-primary"></span></a>
<h4>PROTOTYPE HOME</h4>
</div>
</div>
<br />
<div class="row text-center">

View File

@@ -87,16 +87,19 @@
var options_ca_3y_1 = {
title: '${title1}',
colors: ['purple', 'ff9900', '#dc3912', '3366cc', 'green'],
pieHole: 0.3,
};
var options_ca_3y_2 = {
title: '${title2}',
colors: ['purple', 'ff9900', '#dc3912', '3366cc', 'green'],
pieHole: 0.3,
};
var options_ca_3y_3 = {
title: '${title3}',
colors: ['purple', 'ff9900', '#dc3912', '3366cc', 'green'],
pieHole: 0.3,
};
var options_ca_3y_1_x = {

View File

@@ -410,6 +410,6 @@ def ajax_article(request):
d['ref'] = items.ref
d['libelle'] = items.libelle
d['prixht'] = "%.2f" % items.prixht
d['unite'] = items.unite
liste.append(d)
return Response(json.dumps(liste))

View File

@@ -128,6 +128,10 @@ def dossier_view(request):
status = get_status_by_id(request, '')
motifs = get_motifs(request)
# lire les notes du dossier
dem_notes = get_dem_notes(request, nodossier, '0', 'NOTE')
# lire les croquis du dossier
dem_croquis = get_dem_notes(request, nodossier, '0', 'CROQUIS')
if 'form.close' in request.params:
status = request.params["status"]
@@ -161,6 +165,9 @@ def dossier_view(request):
'access': access,
'status': status,
'motifs': motifs,
'motif': '',
'dem_notes': dem_notes,
'dem_croquis': dem_croquis,
}
@view_config(route_name='dossier_selected', permission='view')
@@ -1579,3 +1586,48 @@ def dem_devis(request):
'societe': societe,
'order_option': order_option,
}
@view_config(route_name='note_add', permission='view')
def note_add(request):
logged_in = request.authenticated_userid.upper()
nodossier = request.matchdict['nodossier']
insert_dem_note(request, nodossier, 'NOTE', logged_in)
return HTTPFound(request.route_url('dossier_view', nodossier=nodossier))
@view_config(route_name='note_edit', renderer='../templates/dossier/note_edit.pt', permission='view')
def note_edit(request):
nodossier = request.matchdict['nodossier']
noligne = request.matchdict['noligne']
url = request.route_url('note_edit', nodossier=nodossier, noligne=noligne)
message = ""
note = get_dem_notes(request, nodossier, noligne)
if 'form.submitted' in request.params:
notes = request.params["notes"]
update_dem_note(request, nodossier, noligne, notes)
request.session.flash("'%s' a été modifiée avec succès." % note.libelle, 'success')
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier))
if 'form.deleted' in request.params:
delete_dem_note(request, nodossier, noligne)
request.session.flash("'%s' a été supprimée avec succès." % note.libelle, 'success')
return HTTPFound(location=request.route_url('dossier_view', nodossier=nodossier))
return {
'page_title': note.libelle,
'url': url,
'message': message,
'nodossier': nodossier,
'note': note,
}
@view_config(route_name='croquis_edit', renderer='../templates/dossier/croquis_edit.pt', permission='view')
def croquis_edit(request):
return {
'page_title': 'NOUVEAU CROQUIS',
}