Implemented client upload with rangeslider and zip open
[src/app-framework-demo.git] / afb-client / app / Frontend / widgets / FormInput / UploadFiles.js
1
2 /* 
3  * Copyright (C) 2015 "IoT.bzh"
4  * Author "Fulup Ar Foll"
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details..
15  * 
16  * Reference:
17  *   https://developer.mozilla.org/en/docs/Web/API/FileReader 
18  *   https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications#Using_hidden_file_input_elements_using_the_click%28%29_method
19  *   https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs
20  *   https://www.terlici.com/2015/05/16/uploading-files-locally.html
21  *   https://github.com/nervgh/angular-file-upload/blob/master/src/services/FileUploader.js
22  *   https://stuk.github.io/jszip/documentation/howto/read_zip.html
23  *   http://onehungrymind.com/zip-parsing-jszip-angular/
24  */
25
26    
27 function changeInput() {
28      console.log ('input imgClicked'); 
29 }   
30
31 (function() {
32 'use strict';
33
34 // WARNING: Angular ng-change does not work on input/file. Let's hook our callback through standard JS function
35 var tmpl =  '<input type="file" name="{{name}}-input" onchange="angular.element(this).scope().UpLoadFile(this.files)" accept="{{mimetype}}" style="display:none">'+
36             '<div class="upload-file" ng-click="imgClicked()">' +
37             '<img id="{{name}}-img" src="{{thumbnail}}">' +
38             '<range-slider ng-show="!noslider" id="{{name}}-slider" automatic=true inithook="SliderInitCB"></range-slider>' +
39             '</div>';
40     
41
42 function Basename(path) {
43    return path.split('/').reverse()[0];
44 }
45
46 // Service Create xform insert files in and Post it to url
47 function LoadFileSvc (scope, elem, posturl, files, thumbnailCB) {
48     var xmlReq = new XMLHttpRequest();
49     var xform  = new FormData();
50
51     // Update slider during Upload
52     xmlReq.upload.onprogress = function (event) {
53         var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
54         if (scope.slider) scope.slider.setValue (progress);
55     };
56
57     // Upload is finish let's notify controler callback
58     xmlReq.onload = function () {
59         elem.addClass ("success");
60         var response ={
61             status : xmlReq.status,
62             headers: xmlReq.getAllResponseHeaders() 
63         };
64         scope.callback (response);
65     };
66
67     xmlReq.onerror = function () {
68         elem.addClass ("error fail");
69         var response ={
70             status : xmlReq.status,
71             headers: xmlReq.getAllResponseHeaders() 
72         };
73         scope.callback (response);
74     };
75
76     xmlReq.onabort = function () {
77         elem.addClass ("error abort");
78         var response ={
79             status : xmlReq.status,
80             headers: xmlReq.getAllResponseHeaders() 
81         };
82         scope.callback (response);
83     };
84
85     for (var i = 0; i < files.length; i++) {
86         var file = files[i];
87         if (!file.type.match(scope.mimetype)) {
88             continue;
89         }
90
91         console.log ("Selected file=" + file.name + " size="+ file.size/1024 + " Type="+ file.type);
92
93         // File to upload is too big
94         if (file.size > scope.maxsize*1024) {
95             scope.thumbnail = scope.istoobig; // warning if image path is wrong nothing happen
96             scope.$apply('thumbnail'); // we short-circuit Angular resync Image
97             return;
98         }
99
100         // This is not an uploadable file
101         if(isNaN(file.size)) {
102             scope.thumbnail = scope.isnotvalid; 
103             scope.$apply('thumbnail');
104             return;
105         }
106
107         scope.Basename=Basename(file.name);
108         scope.imgElem[0].file = file;
109
110         // If File is an image let display it now
111         if (thumbnailCB) {
112             var reader = new FileReader();
113             reader.readAsArrayBuffer(file);
114             reader.onload = thumbnailCB;
115         }
116
117         // if everything is OK let's add file to xform
118         xform.append(scope.name, file, file.name);
119     }
120
121
122     // everything looks OK let's Post it
123     xmlReq.open("POST", posturl , true);
124     xmlReq.send(xform);
125 };
126
127 angular.module('UploadFiles',['ConfigApp', 'ModalNotification', 'RangeSlider'])
128
129 .directive('uploadImage', function(ConfigApp,  JQemu, Notification) {
130     function mymethods(scope, elem, attrs) {
131         
132         // get widget image handle from template
133         scope.imgElem    = elem.find('img');
134         scope.inputElem  = elem.find('input');
135         
136         // Image was ckick let's simulate an input (file) click
137         scope.imgClicked = function () {
138             scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
139         };
140         
141         // Slider control handle registration after creation
142         scope.SliderInitCB=function (slider) {
143            scope.slider= slider; 
144         };
145         
146         // Upload is delegated to a shared function
147         scope.UpLoadFile=function (files) {
148             var readerCB = function (upload) {
149                 // scope.thumbnail = upload.target.result;
150                 scope.imgElem[0].src = window.URL.createObjectURL(new Blob([upload.target.result], {type: "image"}));
151                 scope.$apply('thumbnail');    // we short-circuit Angular resync image
152             };
153             var posturl = attrs.posturl + "?token=" + ConfigApp.session.token;
154             LoadFileSvc (scope, elem, posturl, files, readerCB);
155         };
156
157         // Initiallize default values from attributes values
158         scope.name= attrs.name || 'avatar';
159         scope.category= attrs.category  || 'image';
160         scope.mimetype= (attrs.accept || 'image') + '/*';
161         scope.maxsize= attrs.maxsize || 100; // default max size 100KB
162         scope.regexp = new RegExp (attrs.accept+ '.*','i');
163
164         if (attrs.thumbnail) scope.thumbnail= ConfigApp.paths[scope.category] +  attrs.thumbnail;
165         else  scope.thumbnail=ConfigApp.paths[scope.category] + 'tux-bzh.png';
166         
167         if (attrs.thumbnail) scope.isnotvalid= ConfigApp.paths[scope.category] +  attrs.isnotvalid;
168         else  scope.isnotvalid=ConfigApp.paths[scope.category] + 'isnotvalid.png';
169
170         if (attrs.istoobig) scope.istoobig= ConfigApp.paths[scope.category] +  attrs.istoobig;
171         else  scope.istoobig=ConfigApp.paths[scope.category] + 'istoobig.png';
172         scope.noslider = attrs.noslider || false;
173
174         if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs);            
175     }
176     return {
177         restrict: 'E',
178         template: tmpl,
179         link: mymethods,
180         scope: {
181             callback : '='
182         }
183     };
184 })
185     
186 .directive('uploadAudio', function(ConfigApp,  JQemu, Notification) {
187     function mymethods(scope, elem, attrs) {
188         
189         // get widget image handle from template
190         scope.imgElem    = elem.find('img');
191         scope.inputElem  = elem.find('input');
192         
193         // Image was ckick let's simulate an input (file) click
194         scope.imgClicked = function () {
195             scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
196         };
197         
198         // Slider control handle registration after creation
199         scope.SliderInitCB=function (slider) {
200            scope.slider= slider; 
201         };
202         
203         // Upload is delegated to a shared function
204         scope.UpLoadFile=function (files) {
205             var posturl = attrs.posturl + "?token=" + ConfigApp.session.token;
206             LoadFileSvc (scope, elem, posturl, files, false);
207         };
208
209         // Initiallize default values from attributes values
210         scope.name= attrs.name || 'audio';
211         scope.category= attrs.category  || 'audio';
212         scope.mimetype= (attrs.accept || 'audio') + '/*';
213         scope.maxsize= attrs.maxsize || 10000; // default max size 10MB
214         scope.regexp = new RegExp (attrs.accept+ '.*','i');
215
216         if (attrs.thumbnail) scope.thumbnail= ConfigApp.paths[scope.category] +  attrs.thumbnail;
217         else  scope.thumbnail=ConfigApp.paths[scope.category] + 'upload-music.png';
218         
219         if (attrs.thumbnail) scope.isnotvalid= ConfigApp.paths[scope.category] +  attrs.isnotvalid;
220         else  scope.isnotvalid=ConfigApp.paths[scope.category] + 'isnotvalid.png';
221
222         if (attrs.istoobig) scope.istoobig= ConfigApp.paths[scope.category] +  attrs.istoobig;
223         else  scope.istoobig=ConfigApp.paths[scope.category] + 'istoobig.png';
224         scope.noslider = attrs.noslider || false;
225
226         if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs);            
227     }
228     return {
229         restrict: 'E',
230         template: tmpl,
231         link: mymethods,
232         scope: {
233             callback : '='
234         }
235     };
236     
237 })
238
239 .directive('uploadAppli', function(ConfigApp,  JQemu, Notification) {
240     function mymethods(scope, elem, attrs) {
241         
242         // get widget image handle from template
243         scope.imgElem    = elem.find('img');
244         scope.inputElem  = elem.find('input');
245         
246         // Image was ckick let's simulate an input (file) click
247         scope.imgClicked = function () {
248             scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
249         };
250         
251         // Slider control handle registration after creation
252         scope.SliderInitCB=function (slider) {
253            scope.slider= slider; 
254         };
255         
256         // Upload is delegated to a shared function
257         scope.UpLoadFile=function (files) {
258                        
259             var readerCB = function (upload) {
260                 var zipapp = new JSZip(upload.target.result);
261                 var thumbnail = zipapp.file("afa-pkg/thumbnail.jpg");
262                 
263                 // Check is we have a thumbnail within loaded Zipfile
264                 if (!thumbnail) {
265                     console.log ("This is not a valid Application Framework APP");
266                     scope.thumbnail=ConfigApp.paths[scope.category] + 'isnotvalid.png';
267                     scope.$apply('thumbnail');    // we short-circuit Angular resync image
268                     return;
269                 } 
270                 scope.imgElem[0].src = window.URL.createObjectURL(new Blob([thumbnail.asArrayBuffer()], {type: "image"}));
271                 scope.$apply('thumbnail');    // we short-circuit Angular resync image
272             };
273                         
274             var posturl = attrs.posturl + "?token=" + ConfigApp.session.token;
275             LoadFileSvc (scope, elem, posturl, files, readerCB);
276         };
277
278         // Initiallize default values from attributes values
279         scope.name= attrs.name || 'appli';
280         scope.category= attrs.category  || 'appli';
281         scope.mimetype= (attrs.accept || '.zip');
282         scope.maxsize= attrs.maxsize || 100000; // default max size 100MB
283         scope.regexp = new RegExp (attrs.accept+ '.*','i');
284
285         if (attrs.thumbnail) scope.thumbnail= ConfigApp.paths[scope.category] +  attrs.thumbnail;
286         else  scope.thumbnail=ConfigApp.paths[scope.category] + 'upload-appli.png';
287         
288         if (attrs.thumbnail) scope.isnotvalid= ConfigApp.paths[scope.category] +  attrs.isnotvalid;
289         else  scope.isnotvalid=ConfigApp.paths[scope.category] + 'isnotvalid.png';
290
291         if (attrs.istoobig) scope.istoobig= ConfigApp.paths[scope.category] +  attrs.istoobig;
292         else  scope.istoobig=ConfigApp.paths[scope.category] + 'istoobig.png';
293         scope.noslider = attrs.noslider || false;
294
295         if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs);            
296     }
297     return {
298         restrict: 'E',
299         template: tmpl,
300         link: mymethods,
301         scope: {
302             callback : '='
303         }
304     };
305     
306 });
307
308 console.log ("UploadFile Loaded");
309 })();