Commit 4294185e authored by Romain Courteaud's avatar Romain Courteaud Committed by Gabriel Monnerat

erp5_document_scanner: fully erase the gadget DOM when changing its state

Stop relying of CSS hide/show attributes.
Instead, only put needed elements in the DOM.
parent a89b2fa7
......@@ -7,7 +7,7 @@ div[data-gadget-scope="field_your_document_scanner_gadget"] {
text-align: center;
}
.video, .photo, .camera-output {
.video, .photo, .camera-output, .canvas {
max-width: 100%;
width: auto;
max-height: 500px;
......@@ -17,11 +17,11 @@ div[data-gadget-scope="field_your_document_scanner_gadget"] {
.camera-input, .camera-output {
min-height: 360px;
display: none;
// display: none;
}
.canvas {
display: none;
// display: none;
filter: brightness(1);
}
......@@ -58,12 +58,20 @@ div[data-gadget-scope="field_your_document_scanner_gadget"] {
padding: 3pt;
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 0.325em;
display: "inline-block";
margin-right: 6pt;
}
.ui-btn-icon-left:before {
margin-right: 6pt;
}
/*
.take-picture-btn, .capture-btn, .confirm-btn,
.reset-btn, .confirm-btn, .change-camera-btn, .edit-btn {
display: none;
}
*/
.contentarea {
font-size: 16px;
......
......@@ -69,7 +69,9 @@
</item>
<item>
<key> <string>content_type</string> </key>
<value> <string>text/css</string> </value>
<value>
<none/>
</value>
</item>
<item>
<key> <string>default_reference</string> </key>
......@@ -167,9 +169,7 @@
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<none/>
</value>
<value> <string>publish_alive</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
......@@ -188,50 +188,11 @@
<value>
<object>
<klass>
<global id="3.1" name="DateTime" module="DateTime.DateTime"/>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261202.27</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>draft</string> </value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>publish_alive</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="3.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261323.29</float>
......@@ -262,51 +223,6 @@
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>0.0.0.0</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global id="4.1" name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261202.26</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
......@@ -328,7 +244,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>979.60822.46188.23978</string> </value>
<value> <string>981.19198.64390.32238</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -338,148 +254,15 @@
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261231.12</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>979.60823.12148.22101</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261265.43</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2211.58828.61730</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574688148.62</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2402.31270.49459</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574697983.15</float>
<float>1579541862.76</float>
<string>UTC</string>
</tuple>
</state>
......
......@@ -14,11 +14,13 @@
<link rel="stylesheet" href="gadget_document_scanner.css">
<link rel="stylesheet" href="cropper.min.css">
<script type="text/javascript" src="cropper.min.js"></script>
<script type="text/javascript" src="domsugar.js"></script>
<script type="text/javascript" src="gadget_document_scanner.js"></script>
<title>Gadget Document Scanner</title>
</head>
<body>
<div class="camera">
<div></div>
<!--div class="camera">
<div class="camera-header">
<h4>Page <label class="page-number">1</label></h4>
</div>
......@@ -38,6 +40,6 @@
<button type="button" class="edit-btn ui-btn-icon-left ui-icon-pencil"> Edit</button>
<button type="button" class="change-camera-btn ui-icon-refresh ui-btn-icon-left"> Change Camera</button>
</div>
</div>
</div-->
</body>
</html>
\ No newline at end of file
......@@ -167,9 +167,7 @@
<dictionary>
<item>
<key> <string>action</string> </key>
<value>
<none/>
</value>
<value> <string>publish_alive</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
......@@ -188,50 +186,11 @@
<value>
<object>
<klass>
<global id="3.1" name="DateTime" module="DateTime.DateTime"/>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261595.83</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
<item>
<key> <string>validation_state</string> </key>
<value> <string>draft</string> </value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>publish_alive</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="3.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261995.43</float>
......@@ -262,96 +221,6 @@
<key> <string>_log</string> </key>
<value>
<list>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>0.0.0.0</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass>
<global id="4.1" name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261595.83</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>979.60829.17295.36334</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261616.19</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
......@@ -373,7 +242,7 @@
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>979.60829.39532.12458</string> </value>
<value> <string>981.18960.58948.16110</string> </value>
</item>
<item>
<key> <string>state</string> </key>
......@@ -383,373 +252,15 @@
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574261638.39</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>979.60835.60550.28808</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574262871.91</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>979.60850.34860.27477</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574263390.85</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2211.58828.61730</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574679628.14</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2260.30741.13158</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574679704.62</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2261.48739.22886</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574686933.44</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2382.14691.53162</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574687074.8</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2384.38015.42888</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574688159.56</float>
<string>UTC</string>
</tuple>
</state>
</object>
</value>
</item>
</dictionary>
<dictionary>
<item>
<key> <string>action</string> </key>
<value> <string>edit</string> </value>
</item>
<item>
<key> <string>actor</string> </key>
<value> <string>zope</string> </value>
</item>
<item>
<key> <string>comment</string> </key>
<value>
<none/>
</value>
</item>
<item>
<key> <string>error_message</string> </key>
<value> <string></string> </value>
</item>
<item>
<key> <string>serial</string> </key>
<value> <string>980.2402.43212.46062</string> </value>
</item>
<item>
<key> <string>state</string> </key>
<value> <string>current</string> </value>
</item>
<item>
<key> <string>time</string> </key>
<value>
<object>
<klass> <reference id="4.1"/> </klass>
<klass>
<global name="DateTime" module="DateTime.DateTime"/>
</klass>
<tuple>
<none/>
</tuple>
<state>
<tuple>
<float>1574698038.29</float>
<float>1579527559.65</float>
<string>UTC</string>
</tuple>
</state>
......
/*jslint indent: 2 */
/*global rJS, RSVP, window, navigator, Cropper, Promise, JSON, jIO*/
(function (rJS, RSVP, window, navigator, Cropper, Promise, JSON, jIO) {
/*jslint indent: 2, unparam: true */
/*global rJS, RSVP, window, document, navigator, Cropper, Promise, JSON, jIO, promiseEventListener, domsugar, createImageBitmap*/
(function (rJS, RSVP, window, document, navigator, Cropper, Promise, JSON, jIO, promiseEventListener, domsugar, createImageBitmap) {
"use strict";
function drawCanvas(gadget, img) {
var ratio, x, y,
root = gadget.element,
canvas = root.querySelector("canvas");
canvas.width = gadget.props.image_width;
canvas.height = gadget.props.image_height;
ratio = Math.min(canvas.width / img.width, canvas.height / img.height);
x = (canvas.width - img.width * ratio) / 2;
y = (canvas.height - img.height * ratio) / 2;
canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height, x, y, img.width * ratio, img.height * ratio);
//contrastImage(canvas, canvas, 10);
root.querySelector(".camera-output").style.display = "";
if (gadget.props.cropper) {
gadget.props.cropper.destroy();
//////////////////////////////////////////////////
// Browser API to promise
//////////////////////////////////////////////////
function promiseUserMedia(device_id) {
return navigator.mediaDevices.getUserMedia({
video: {
deviceId: {
exact: device_id
}
// creating Cropper is asynchronous
return new RSVP.Promise(function (resolve) {
gadget.props.cropper = new Cropper(root.querySelector('.photo'), {
data: gadget.props.preferred_cropped_canvas_data,
ready: resolve
},
audio: false
});
}
function handleUserMedia(device_id, callback) {
// Do not modify this function!
// There is no need to add the gadget logic inside
var stream;
function canceller() {
if (stream !== undefined) {
// Stop the streams
stream.getTracks().forEach(function (track) {
track.stop();
});
}
}
function takePicture(gadget) {
var el = gadget.element,
image_capture = gadget.props.image_capture;
return new RSVP.Queue()
function waitForStream(resolve, reject) {
new RSVP.Queue()
.push(function () {
return image_capture.takePhoto({imageWidth: gadget.props.image_width});
})
.push(function (blob) {
return jIO.util.readBlobAsDataURL(blob);
return promiseUserMedia(device_id);
})
.push(function (result) {
var photoInput = el.querySelector(".photoInput"),
photo = el.querySelector("img"),
data_str = result.target.result;
photo.setAttribute("src", data_str);
photoInput.setAttribute("value", data_str.split(",")[1]);
return drawCanvas(gadget, photo);
});
stream = result;
return callback(stream);
})
.push(undefined, function (error) {
if (!(error instanceof RSVP.CancellationError)) {
canceller();
reject(error);
}
function enableButton(root) {
[".reset-btn", ".take-picture-btn",
".confirm-btn", ".change-camera-btn"].forEach(function (e) {
root.querySelector(e).disabled = false;
});
}
function setPageOne(gadget) {
var root = gadget.element;
root.querySelector(".page-number").innerText = gadget.props.page_number;
root.querySelector(".reset-btn").style.display = "none";
root.querySelector(".take-picture-btn").style.display = "inline-block";
root.querySelector(".confirm-btn").style.display = "none";
root.querySelector(".camera-input").style.display = "";
if (gadget.props.camera_list.length > 1) {
root.querySelector(".change-camera-btn").style.display = "inline-block";
}
return enableButton(root);
return new RSVP.Promise(waitForStream, canceller);
}
function setPageTwo(root) {
root.querySelector(".reset-btn").style.display = "inline-block";
root.querySelector(".confirm-btn").style.display = "inline-block";
root.querySelector(".take-picture-btn").style.display = "none";
root.querySelector(".camera-input").style.display = "none";
root.querySelector(".camera-output").style.display = "";
root.querySelector(".change-camera-btn").style.display = "none";
function handleCropper(element, data, callback) {
var cropper;
function canceller() {
cropper.destroy();
}
function disableButton(root) {
[".reset-btn", ".take-picture-btn",
".confirm-btn", ".change-camera-btn"].forEach(function (e) {
root.querySelector(e).disabled = true;
// creating Cropper is asynchronous
return new RSVP.Promise(function (resolve, reject) {
cropper = new Cropper(element, {
data: data,
ready: function () {
return new RSVP.Queue()
.push(function () {
return callback(cropper);
})
.push(undefined, function (error) {
if (!(error instanceof RSVP.CancellationError)) {
canceller();
reject(error);
}
});
}
});
}, canceller);
}
//////////////////////////////////////////////////
// helper function
//////////////////////////////////////////////////
/*function contrastImage(input, output, contrast) {
var i,
outputContext,
......@@ -95,7 +87,6 @@
imageData = inputContext.getImageData(0, 0, input.width, input.height),
data = imageData.data,
factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
for (i = 0; i < data.length; i += 4) {
data[i] = factor * (data[i] - 128) + 128;
data[i + 1] = factor * (data[i + 1] - 128) + 128;
......@@ -114,7 +105,6 @@
imageData = inputContext.getImageData(0, 0, input.width, input.height),
data = imageData.data,
arraylength = input.width * input.height * 4;
//gray = 0.3*R + 0.59*G + 0.11*B
// http://www.tannerhelland.com/3643/grayscale-image-algorithm-vb6/
for (i = arraylength - 1; i > 0; i -= 4) {
......@@ -125,7 +115,6 @@
}
outputContext = outputCanvas.getContext("2d");
outputContext.putImageData(imageData, 0, 0);
data = canvas.toDataURL("image/png");
output.setAttribute("src", data);
if (cropper) {
......@@ -142,203 +131,331 @@
});
}*/
function handleUserMedia(gadget, callback) {
var stream,
video = gadget.props.video;
function getVideoDeviceList() {
if (!navigator.mediaDevices) {
throw new Error("mediaDevices is not supported");
}
video.autoplay = "autoplay";
return new RSVP.Queue()
.push(function () {
return navigator.mediaDevices.enumerateDevices();
})
.push(function (info_list) {
var j,
device,
len = info_list.length,
device_list = [];
function canceller() {
if (stream !== undefined) {
// Stop the streams
stream.getTracks().forEach(function (track) {
track.stop();
});
for (j = len - 1; j >= 0; j -= 1) {
// trick to select back camera in mobile
device = info_list[j];
if (device.kind === 'videoinput') {
device_list.push(device);
}
}
return device_list;
});
}
function waitForStream() {
return new RSVP.Queue()
.push(function () {
return navigator.mediaDevices.getUserMedia({
video: {
deviceId: {
exact: gadget.props.device_id
function selectMediaDevice(current_device_id, force_new_device) {
return getVideoDeviceList()
.push(function (info_list) {
var j,
device,
len = info_list.length;
for (j = len - 1; j >= 0; j -= 1) {
// trick to select back camera in mobile
device = info_list[j];
if (device.kind === 'videoinput') {
if ((!current_device_id) ||
(force_new_device && (device.deviceId !== current_device_id)) ||
(!force_new_device && (device.deviceId === current_device_id))) {
return device.deviceId;
}
}
}
throw new Error("no media found");
});
})
.push(function (mediaStream) {
stream = mediaStream;
video.srcObject = mediaStream;
return callback(gadget, stream);
}
//////////////////////////////////////////////////
// Private gadget function
//////////////////////////////////////////////////
function addDetachedPromise(gadget, key, promise) {
// XXX TODO Handle error
if (gadget.detached_promise_dict.hasOwnProperty(key)) {
gadget.detached_promise_dict[key].cancel('Replacing key: ' + key);
}
gadget.detached_promise_dict[key] = new RSVP.Queue()
.push(function () {
return promise;
})
.push(undefined, function (error) {
if (!(error instanceof RSVP.CancellationError)) {
canceller();
}
// Crash the gadget if the detached promise raise an unexpected error
gadget.raise(error);
});
}
return new RSVP.Promise(waitForStream, canceller);
// Display the video stream from a media source
function renderVideoCapture(gadget) {
var video;
return RSVP.Queue()
.push(function () {
var defer = RSVP.defer();
addDetachedPromise(gadget, 'media_stream',
handleUserMedia(gadget.state.device_id, defer.resolve));
return defer.promise;
})
.push(function (media_stream) {
video = document.createElement('video');
video.srcObject = media_stream;
video.autoplay = "autoplay";
video.loop = "loop";
video.muted = "muted";
return RSVP.any([
// Wait for the video to be ready
promiseEventListener(video, 'loadedmetadata', true),
promiseEventListener(video, 'canplaythrough', true),
promiseEventListener(video, 'error', true, function () {
throw new Error("Can't play the video file");
})
]);
})
.push(function () {
video.play();
return RSVP.all([
getVideoDeviceList(),
gadget.getTranslationList(["Take Picture", "Change Camera"])
]);
})
.push(function (result_list) {
var button_list = [
domsugar('button', {type: 'button',
class: 'take-picture-btn ui-btn-icon-left ui-icon-circle',
text: result_list[1][0]
})
],
div;
// Only display the change camera if device has at least 2 cameras
if (result_list[0].length > 1) {
button_list.push(
domsugar('button', {type: 'button',
class: 'change-camera-btn ui-icon-refresh ui-btn-icon-left',
text: result_list[1][1]
})
);
}
function gotStream(gadget, mediaStream) {
div = domsugar('div', {class: 'camera'}, [
domsugar('div', {class: 'camera-header'}, [
domsugar('h4', [
'Page ',
domsugar('label', {class: 'page-number', text: gadget.state.page})
])
]),
domsugar('div', {class: 'camera-input'}, [video]),
domsugar('div', {class: 'edit-picture'}, button_list)
]);
gadget.element.replaceChild(div, gadget.element.firstElementChild);
});
}
// Capture the media stream
function captureAndRenderPicture(gadget) {
var image_capture = new window.ImageCapture(
gadget.element.querySelector('video').srcObject.getVideoTracks()[0]
),
div;
return new RSVP.Queue()
.push(function () {
var image_capture;
image_capture = new window.ImageCapture(mediaStream.getVideoTracks()[0]);
gadget.props.image_capture = image_capture;
return image_capture.getPhotoCapabilities();
})
.push(function (photoCapabilities) {
gadget.props.image_width = photoCapabilities.imageWidth.max;
gadget.props.image_height = photoCapabilities.imageHeight.max;
return gadget.props.video.play();
.push(function (capabilities) {
return image_capture.takePhoto({imageWidth: capabilities.imageWidth.max});
})
.push(function () {
return setPageOne(gadget);
.push(function (blob) {
gadget.detached_promise_dict.media_stream.cancel('Not needed anymore, as captured');
return RSVP.all([
gadget.getTranslationList(["Reset", "Confirm"]),
createImageBitmap(blob)
]);
})
.push(function (result_list) {
var // blob_url = URL.createObjectURL(blob),
// img = domsugar('img', {src: blob_url});
bitmap = result_list[1],
canvas = domsugar('canvas', {class: 'canvas'}),
defer = RSVP.defer();
// Prepare the cropper canvas
canvas.width = bitmap.width;
canvas.height = bitmap.height;
canvas.getContext('2d').drawImage(bitmap, 0, 0);
div = domsugar('div', {class: 'camera'}, [
domsugar('div', {class: 'camera-header'}, [
domsugar('h4', [
'Page ',
domsugar('label', {class: 'page-number', text: gadget.state.page})
])
]),
canvas,
domsugar('div', {class: 'edit-picture'}, [
domsugar('button', {type: 'button',
class: 'reset-btn ui-btn-icon-left ui-icon-times',
text: result_list[0][0]
}),
domsugar('button', {type: 'button',
class: 'confirm-btn ui-btn-icon-left ui-icon-check',
text: result_list[0][1]
})
])
]);
// XXX How to change the dom only when cropper is ready?
// For now, it needs to access dom element size
gadget.element.replaceChild(div, gadget.element.firstElementChild);
addDetachedPromise(gadget, 'cropper',
handleCropper(canvas,
gadget.state.preferred_cropped_canvas_data,
defer.resolve));
return defer.promise;
})
.push(function (cropper) {
gadget.cropper = cropper;
});
}
function startStream(gadget) {
return handleUserMedia(gadget, gotStream);
function renderSubmittedPicture(gadget) {
var div = domsugar('div', {class: 'camera'}, [
domsugar('div', {class: 'camera-header'}, [
domsugar('h4', [
'Page ',
domsugar('label', {class: 'page-number', text: gadget.state.page})
])
]),
domsugar('img', {src: gadget.state.blob_url})
]);
// XXX How to change the dom only when cropper is ready?
// For now, it needs to access dom element size
gadget.element.replaceChild(div, gadget.element.firstElementChild);
}
//////////////////////////////////////////////////
// Gadget API
//////////////////////////////////////////////////
rJS(window)
.declareAcquiredMethod(
"submitDialogWithCustomDialogMethod",
"submitDialogWithCustomDialogMethod"
)
.declareAcquiredMethod("getTranslationList", "getTranslationList")
.declareAcquiredMethod("notifySubmitted", "notifySubmitted")
.declareJob("startStream", function () {
return startStream(this);
})
.ready(function () {
this.props = {
video: this.element.querySelector(".video")
};
this.detached_promise_dict = {};
})
.declareMethod('render', function (options) {
var root = this.element,
camera_list = [],
gadget = this;
return this.getTranslationList(["Webcam is not available", "Reset", "Take Picture", "Confirm", "Edit", "Change Camera"])
.push(function (result_list) {
var i,
button_list = root.querySelectorAll("button");
for (i = 0; i < button_list.length; i += 1) {
button_list[i].innerText = " " + result_list[i + 1];
.declareJob('raise', function (error) {
throw error;
})
.declareService(function handleDetachedPromiseDict() {
// This service is responsable to cancel all ongoing detached promises
// if the gadget is removed from the page
var gadget = this;
return new RSVP.Promise(function () {return; }, function canceller(msg) {
var key;
for (key in gadget.detached_promise_dict) {
if (gadget.detached_promise_dict.hasOwnProperty(key)) {
gadget.detached_promise_dict[key].cancel(msg);
}
root.querySelector("video").innerText = result_list[0];
}
});
})
.push(function () {
var preferred_cropped_canvas_data = gadget.props.preferred_cropped_canvas_data;
preferred_cropped_canvas_data = preferred_cropped_canvas_data || JSON.parse(options.preferred_cropped_canvas_data);
gadget.props.dialog_method = preferred_cropped_canvas_data.dialog_method;
// Clear photo input
root.querySelector('.photoInput').value = "";
gadget.props.page_number = parseInt(root.querySelector('input[name="page-number"]').value, 10);
root.querySelector(".camera-input").style.display = "";
root.querySelector(".camera-output").style.display = "none";
if (!navigator.mediaDevices) {
throw ("mediaDevices is not supported");
}
gadget.props.preferred_cropped_canvas_data = preferred_cropped_canvas_data;
return navigator.mediaDevices.enumerateDevices();
.setState({
display_step: 'display_video',
page: 1
})
.declareMethod('render', function (options) {
// This method is called during the ERP5 form rendering
// changeState is used to ensure not resetting the gadget current display
// if not needed
var gadget = this;
return selectMediaDevice(gadget.state.device_id, false)
.push(function (device_id) {
return gadget.changeState({
dialog_method: options.dialog_method,
preferred_cropped_canvas_data: JSON.parse(options.preferred_cropped_canvas_data),
device_id: device_id,
key: options.key
});
});
})
.push(function (info_list) {
var j,
device,
len = info_list.length;
if (camera_list.length === 0) {
for (j = 0; j < len; j += 1) {
device = info_list[j];
if (device.kind === 'videoinput') {
camera_list.push(device);
}
.onStateChange(function () {
var gadget = this;
// ALL DOM modifications must be done only in this method
// this prevent concurrency issue on DOM access
if (gadget.state.display_step === 'display_video') {
return renderVideoCapture(gadget);
}
if (gadget.state.display_step === 'crop_picture') {
return captureAndRenderPicture(gadget);
}
if (camera_list.length >= 1) {
// trick to select back camera in mobile
gadget.props.device_id = camera_list[camera_list.length - 1].deviceId;
if (gadget.state.display_step === 'submitting') {
return renderSubmittedPicture(gadget);
}
gadget.props.camera_list = camera_list;
return gadget.startStream();
});
// Ease developper work by raising for not handled cases
throw new Error('Unhandled display step: ' + gadget.state.display_step);
})
.declareMethod('getContent', function () {
var input = this.element.querySelector('.photoInput'),
var gadget = this,
result = {};
result.field_your_document_scanner_gadget = JSON.stringify({
"input_value": input.value,
"preferred_cropped_canvas_data": this.props.preferred_cropped_canvas_data
if (gadget.state.display_step === 'submitting') {
// do not send any content when sending the final form
result[gadget.state.key] = JSON.stringify({
input_value: gadget.state.blob_url.split(';')[1].split(',')[1],
preferred_cropped_canvas_data: gadget.state.preferred_cropped_canvas_data
});
}
return result;
})
.onEvent("click", function (evt) {
var e,
new_preferred_cropped_canvas_data,
gadget = this,
camera_list = this.props.camera_list,
root = this.element;
/*if (evt.target.name === "grayscale") {
return grayscale(root.querySelector(".canvas"),
root.querySelector('.photo'));
}*/
if (evt.target.className.indexOf("change-camera-btn") !== -1) {
// Only handle click on BUTTON element
if (evt.target.tagName !== 'BUTTON') {
return;
}
var gadget = this;
// Disable any button. It must be managed by this gadget
evt.preventDefault();
gadget.element.querySelectorAll('button').forEach(function (elt) {
elt.disabled = true;
});
for (e in camera_list) {
if (camera_list.hasOwnProperty(e)) {
if (camera_list[e].deviceId !== gadget.props.device_id) {
gadget.props.device_id = camera_list[e].deviceId;
break;
}
}
}
return gadget.startStream();
}
if (evt.target.className.indexOf("take-picture-btn") !== -1) {
evt.preventDefault();
return new RSVP.Queue()
.push(function () {
disableButton(root);
root.querySelector(".camera").style.maxWidth = gadget.props.video.offsetWidth + "px";
return takePicture(gadget);
})
.push(function () {
root.querySelector(".camera-input").style.display = "none";
setPageTwo(root);
return enableButton(root);
return gadget.changeState({
display_step: 'crop_picture'
});
}
if (evt.target.className.indexOf("reset-btn") !== -1) {
evt.preventDefault();
root.querySelector(".camera-input").style.display = "";
root.querySelector(".camera-output").style.display = "none";
root.querySelector('.photoInput').value = "";
gadget.props.cropper.destroy();
return setPageOne(gadget);
return gadget.changeState({
display_step: 'display_video'
});
}
if (evt.target.className.indexOf("confirm-btn") !== -1) {
evt.preventDefault();
new_preferred_cropped_canvas_data = gadget.props.cropper.getData();
for (e in new_preferred_cropped_canvas_data) {
if (new_preferred_cropped_canvas_data.hasOwnProperty(e)) {
gadget.props.preferred_cropped_canvas_data[e] = new_preferred_cropped_canvas_data[e];
}
}
return new RSVP.Queue()
.push(function () {
var canvas = gadget.props.cropper.getCroppedCanvas();
disableButton(gadget.element);
var canvas = gadget.cropper.getCroppedCanvas();
return new Promise(function (resolve) {
canvas.toBlob(resolve, 'image/jpeg', 0.85);
});
......@@ -346,22 +463,45 @@
.push(function (blob) {
return jIO.util.readBlobAsDataURL(blob);
})
.push(function (result) {
var base64data = result.target.result,
block = base64data.split(";"),
realData = block[1].split(",")[1];
root.querySelector(".photo").src = base64data;
root.querySelector(".photoInput").value = realData;
gadget.props.cropper.destroy();
.push(function (evt) {
return gadget.changeState({
blob_url: evt.target.result,
preferred_cropped_canvas_data: gadget.cropper.getData(),
display_step: 'submitting'
});
})
.push(function () {
return gadget.submitDialogWithCustomDialogMethod(gadget.props.dialog_method);
gadget.detached_promise_dict.cropper.cancel('Not needed anymore, as cropped');
return gadget.submitDialogWithCustomDialogMethod(gadget.state.dialog_method);
})
.push(function () {
gadget.props.page_number = gadget.props.page_number + 1;
root.querySelector('input[name="page-number"]').value = gadget.props.page_number;
.push(function (evt) {
return gadget.changeState({
blob_url: undefined,
display_step: 'display_video',
page: gadget.state.page + 1
});
});
}
if (evt.target.className.indexOf("change-camera-btn") !== -1) {
return selectMediaDevice(gadget.state.device_id, true)
.push(function (device_id) {
return gadget.changeState({
display_step: 'display_video',
device_id: device_id
});
});
}
}, false, false);
}(rJS, RSVP, window, navigator, Cropper, Promise, JSON, jIO));
\ No newline at end of file
throw new Error('Unhandled button: ' + evt.target.textContent);
}, false, false)
.declareAcquiredMethod(
"submitDialogWithCustomDialogMethod",
"submitDialogWithCustomDialogMethod"
)
.declareAcquiredMethod("getTranslationList", "getTranslationList")
.declareAcquiredMethod("notifySubmitted", "notifySubmitted");
}(rJS, RSVP, window, document, navigator, Cropper, Promise, JSON, jIO, promiseEventListener, domsugar, createImageBitmap));
\ No newline at end of file
......@@ -7,6 +7,5 @@ selection_mapping = portal.portal_selections.getSelectionParamsFor(
REQUEST=context.REQUEST) or {}
canvas_data = selection_mapping.get(context.REQUEST["HTTP_USER_AGENT"]) or {}
canvas_data["dialog_method"] = context.Base_storeDocumentFromCameraInActiveProcess.getId()
return json.dumps(canvas_data)
portal = context.getPortalObject()
translateString = portal.Base_translateString
active_process = context.Base_storeDocumentFromCameraInActiveProcess(
active_process_url=active_process_url,
batch_mode=True,
**kw)
# Avoid to pass huge images to the activity
kw.pop("your_document_scanner_gadget", None)
context.activate().Base_uploadDocumentFromCamera(
active_process_url=active_process.getRelativeUrl(),
active_process_url=active_process_url,
**kw)
return context.Base_redirect('view',
......
......@@ -141,7 +141,7 @@
<dictionary>
<item>
<key> <string>_text</string> </key>
<value> <string>python: [(\'preferred_cropped_canvas_data\', context.Base_getPreferredCropperSettingsFromSelection()),]</string> </value>
<value> <string>python: [(\'dialog_method\', \'Base_storeDocumentFromCameraInActiveProcess\'), (\'preferred_cropped_canvas_data\', context.Base_getPreferredCropperSettingsFromSelection()),]</string> </value>
</item>
</dictionary>
</pickle>
......
web_page_module/rjs_gadget_document_scanner_css
web_page_module/rjs_gadget_document_scanner_html
web_page_module/rjs_gadget_document_scanner_js
\ No newline at end of file
......@@ -101,20 +101,15 @@
<td>//button[@class="take-picture-btn ui-btn-icon-left ui-icon-circle"]</td>
<td></td>
</tr>
<tr>
<td>waitForCondition</td>
<td>selenium.browserbot.getCurrentWindow().document.querySelector("video").readyState == 4</td>
<td>30000</td>
</tr>
<tr>
<td>click</td>
<td>//button[@class="take-picture-btn ui-btn-icon-left ui-icon-circle"]</td>
<td></td>
</tr>
<tr>
<td>waitForCondition</td>
<td>selenium.browserbot.getCurrentWindow().document.querySelector(".confirm-btn").style.display != "none"</td>
<td>30000</td>
<td>waitForElementPresent</td>
<td>//button[@class="reset-btn ui-btn-icon-left ui-icon-times"]</td>
<td></td>
</tr>
<tr>
<td>click</td>
......@@ -122,9 +117,9 @@
<td></td>
</tr>
<tr>
<td>waitForCondition</td>
<td>selenium.browserbot.getCurrentWindow().document.querySelector(".confirm-btn").style.display == "none"</td>
<td>30000</td>
<td>waitForElementPresent</td>
<td>//button[@class="take-picture-btn ui-btn-icon-left ui-icon-circle"]</td>
<td></td>
</tr>
<tr>
<td>click</td>
......@@ -132,9 +127,9 @@
<td></td>
</tr>
<tr>
<td>waitForCondition</td>
<td>selenium.browserbot.getCurrentWindow().document.querySelector(".confirm-btn").style.display != "none"</td>
<td>3000</td>
<td>waitForElementPresent</td>
<td>//button[@class="confirm-btn ui-btn-icon-left ui-icon-check"]</td>
<td></td>
</tr>
<tr>
<td>click</td>
......@@ -149,11 +144,6 @@
</div>
<!--tr>
<td>waitForCondition</td>
<td>selenium.browserbot.getCurrentWindow().document.querySelector(".page-number").innerText == "2"</td>
<td>30000</td>
</tr-->
<tr>
<td>storeValue</td>
<td>//input[@id="field_your_active_process_url"]</td>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment