Handle Error Modal on upload
[src/app-framework-demo.git] / afm-client / app / Frontend / widgets / FormInput / UploadAppli.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  *   http://stackoverflow.com/questions/15341912/how-to-go-from-blob-to-arraybuffer
25  *   
26  *   Bugs: zip file sent even when flag as invalid 
27  */
28
29  
30
31 (function() {
32 'use strict';
33
34 var tmplAppli = '<input type="file" name="{{name}}-input" onchange="angular.element(this).scope().UpLoadFile(this.files)" accept="{{mimetype}}" style="display:none">'+
35             '<div class="upload-file" ng-click="imgClicked()">' +
36             '<i class="{{icon}}"></i> <span>{{label}}</span>' +
37             '<range-slider ng-show="!noslider" id="{{name}}-slider" automatic=true inithook="SliderInitCB"></range-slider>' +
38             '</div>';
39     
40 var tmplModal = '<span class="modal-text">Upload Application <b>{{appname}}</b> ?</span>' +
41             '<div>'+
42             '<img ng-src="{{icon}}">' +
43             '<submit-button icon="fi-x" label="Cancel" clicked="abandon"></submit-button>'+
44             '<submit-button icon="fi-like" label="Install" clicked="success"></submit-button> ' +
45             '</div>';
46     
47 var tmplError = '<span class="modal-text">Invalid Application <b>{{appname}}</b> ?</span>' +
48             '<div>'+
49             '<img ng-src="{{icon}}">' +
50             '<submit-button icon="fi-x" label="Close" clicked="abandon"></submit-button>'+
51             '</div>';
52     
53
54 // Service Create xform insert files in and Post it to url
55 function LoadFileSvc (scope, files, fileCB) {
56     var xmlReq = new XMLHttpRequest();
57     var xform  = new FormData();
58     
59     // Update slider during Upload
60     xmlReq.upload.onprogress = function (event) {
61         var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0);
62         if (scope.slider) scope.slider.setValue (progress);
63     };
64
65     // Upload is finish let's notify controler callback
66     xmlReq.onload = function () {
67         scope.divElem.addClass ("success");
68         scope.divElem.removeClass ("error");
69         var response ={
70             status : xmlReq.status,
71             headers: xmlReq.getAllResponseHeaders() 
72         };
73         scope.callback (response);
74     };
75
76     xmlReq.onerror = function () {
77         scope.divElem.addClass ("error");
78         scope.divElem.removeClass ("success");
79     };
80
81     xmlReq.onabort = function () {
82         scope.divElem.addClass ("error");
83         scope.divElem.removeClass ("success");
84         var response ={
85             status : xmlReq.status,
86             headers: xmlReq.getAllResponseHeaders() 
87         };
88         scope.callback (response);
89     };
90     
91     this.postfile = function(posturl) { 
92         // everything looks OK let's Post it
93         xmlReq.open("POST", posturl , true);
94         xmlReq.send(xform);
95     };
96
97     for (var i = 0; i < files.length; i++) {
98         this.file = files[i];
99         // Unknow Type !!! if (!this.file.type.match(scope.mimetype)) continue;
100
101         console.log ("Selected file=" + this.file.name + " size="+ this.file.size/1024 + " Type="+ this.file.type);
102         
103         this.basename= this.file.name.split('/').reverse()[0];
104
105         // File to upload is too big
106         if (isNaN(this.file.size) || this.file.size > scope.maxsize*1024) {
107             setTimeout (fileCB,100);  // On error asynchronous callback without argument
108             
109         } else {
110
111             // If File is readable let's read it
112             var reader = new FileReader();
113             reader.readAsArrayBuffer(this.file);
114             reader.onload = fileCB;
115
116             // if everything is OK let's add file to xform
117             xform.append(scope.name, this.file, this.file.name);
118         }
119     }
120 }
121
122 angular.module('UploadFiles',['AppConfig', 'ModalNotification', 'RangeSlider'])
123
124 .directive('uploadAppli', function(AppConfig,  JQemu, Notification, ModalFactory, $timeout) {
125     function mymethods(scope, elem, attrs) {
126         
127         // get widget image handle from template
128         scope.inputElem  = elem.find('input');
129         scope.divElem    = elem.find('div');
130         
131         // Image was ckick let's simulate an input (file) click
132         scope.imgClicked = function () {
133             scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!!
134         };
135         
136         // Slider control handle registration after creation
137         scope.SliderInitCB=function (slider) {
138            scope.slider= slider; 
139         };
140         
141         // Upload is delegated to a shared function
142         scope.UpLoadFile=function (files) {
143             var handle; 
144             var appicon;
145             var template;
146             
147             var success = function() {
148                 // This Looks OK let's Post Xform/File
149                 handle.postfile(attrs.posturl + "?token=" + AppConfig.session.token);
150
151                 scope.modal.deactivate();
152                 $timeout (function() {scope.modal.destroy();}, 1000);
153             };
154             
155             var abandon = function() {
156                 scope.modal.deactivate();
157                 $timeout (function() {scope.modal.destroy();}, 1000);
158             };
159                        
160             var readerCB = function (upload) {
161                
162                 // File upload fail handle error
163                 if (! upload) {
164                     if (handle.file.size > scope.maxsize*1024) {
165                         appicon = scope.istoobig;
166                         template= tmplError;
167                     }
168                     
169                     if (isNaN(handle.file.size)) {
170                         appicon = scope.isnotvalid; 
171                         template= tmplError;
172                     }
173                                         
174                 } else {
175
176                     var zipapp = new JSZip (upload.target.result);
177                     var thumbnail = zipapp.file("icon_128.png");
178
179                     // Check is we have a thumbnail within loaded Zipfile
180                     if (!thumbnail) {
181                         console.log ("This is not a valid Application Framework APP");
182                         appicon = scope.isnotvalid;
183                         template= tmplError;
184                         
185                     } else {
186                         //scope.imgElem[0].src = window.URL.createObjectURL(new Blob([thumbnail.asArrayBuffer()], {type: "image"}));
187                         appicon = window.URL.createObjectURL(new Blob([thumbnail.asArrayBuffer()], {type: "image"}));
188                         template = tmplModal;
189                     }
190                 }
191                 
192                 // reference http://foundation.zurb.com/apps/docs/#!/angular-modules
193                 var config = {
194                     animationIn: 'slideInFromTop',
195                     contentScope: {
196                         success : success,
197                         abandon : abandon,
198                         icon    : appicon,
199                         appname : handle.basename
200                     }, template : template
201                 }; 
202                 // Popup Modal to render application data
203                 scope.modal = new ModalFactory(config);
204                 scope.modal.activate ();
205             };
206             
207             // Load file within browser and if OK call readerCB
208             handle = new LoadFileSvc (scope, files, readerCB);
209             console.log (handle);
210         };
211
212         // Initiallize default values from attributes values
213         scope.name= attrs.name || 'appli';
214         scope.category= attrs.category  || 'appli';
215         scope.mimetype= (attrs.accept || '.wgt');
216         scope.maxsize = attrs.maxsize || 100000; // default max size 100MB
217         scope.regexp  = new RegExp (attrs.accept+ '.*','i');
218         scope.icon    = attrs.icon || 'fi-upload';
219         scope.label   = attrs.label || 'Upload';
220         
221         if (attrs.thumbnail) scope.isnotvalid= AppConfig.paths[scope.category] +  attrs.isnotvalid;
222         else  scope.isnotvalid=AppConfig.paths[scope.category] + 'w3c-widget.png';
223
224         if (attrs.istoobig) scope.istoobig= AppConfig.paths[scope.category] +  attrs.istoobig;
225         else  scope.istoobig=AppConfig.paths[scope.category] + 'istoobig.png';
226         scope.noslider = attrs.noslider || false;
227
228         if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs);            
229     }
230     return {
231         restrict: 'E',
232         template: tmplAppli,
233         link: mymethods,
234         scope: {
235             callback : '='
236         }
237     };
238     
239 });
240
241 console.log ("UploadFile Loaded");
242 })();