diff options
author | Stephane Desneux <stephane.desneux@iot.bzh> | 2016-05-31 18:16:48 +0200 |
---|---|---|
committer | Stephane Desneux <stephane.desneux@iot.bzh> | 2016-05-31 18:16:48 +0200 |
commit | 5b1e6cc132f44262a873fa8296a2a3e1017b0278 (patch) | |
tree | 43b2cd54e2e300b399ff3f2af4458a2c4ed8a144 /afb-client/app/Frontend/widgets/FormInput | |
parent | f7d2f9ac4168ee5064580c666d508667a73cefc0 (diff) | |
parent | 85ace9c1ce9a98e9b8a22f045c7dd752b38d9129 (diff) |
Merge afb-client
Diffstat (limited to 'afb-client/app/Frontend/widgets/FormInput')
-rw-r--r-- | afb-client/app/Frontend/widgets/FormInput/FormInput.scss | 91 | ||||
-rw-r--r-- | afb-client/app/Frontend/widgets/FormInput/UploadFiles.js | 310 |
2 files changed, 401 insertions, 0 deletions
diff --git a/afb-client/app/Frontend/widgets/FormInput/FormInput.scss b/afb-client/app/Frontend/widgets/FormInput/FormInput.scss new file mode 100644 index 0000000..77aed6e --- /dev/null +++ b/afb-client/app/Frontend/widgets/FormInput/FormInput.scss @@ -0,0 +1,91 @@ +/* + Sample of style overload for a specific widget + + Note: this SCSS is injected with main HTML page, it scope should be reduce + to a specific widget or it value will be propagated at a global level. +*/ + +@import "app/ibz-mixins"; + +.upload-file { + display: inline-block; + float: right; + height : 5rem; + width : 5rem; + margin: 0.5rem; + + img { height: inherit;} + + .ibz-range-slider { + height: 10% !important; + border-radius: 5px; + background-color: lightgrey !important; + + .range-slider-handle { + width: 10% !important; + height: 100% !important; + margin-top: .2rem; + background-color: purple !important; + } + + .range-slider-active-segment { + height: 80% !important; + background-color: lightgreen; + } + } + +} + +input-text { + + alert {@include ibz-input-alert(darkblue, rgba(200, 200, 200, 0.6))}; + + input { + margin-bottom: .5rem !important; + } + + label { + margin-top: 1rem !important; + } + + .required { + color: blue; + float: right; + color: lightskyblue; + } + .required.valid { + color: green; + } + + .required.invalid { + color: plum; + } + + .status-untouch { + border-color: rgba(200, 200, 200, 0.6) !important; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(200, 200, 200, 0.6) !important; + color: #696969 !important; + } + + input:focus { + border-color: rgba(82,168,236,0.8) ; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(82,168,236,0.8) !important; + transition: border 0.2s linear 0s, box-shadow 0.2s linear 0s; + color: darkslateblue !important; + @extend shadow-transition; + } + + .status-valid { + border-color: rgba(154, 205, 50, 0.6)!important; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(154, 205, 50, 0.6)!important; + @extend shadow-transition; + } + + .status-invalid { + border-color: rgba(154, 17, 69, 0.6); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(154, 17, 69, 0.6)!important; + color: rgb(154, 17, 69); + @extend shadow-transition; + } + +} diff --git a/afb-client/app/Frontend/widgets/FormInput/UploadFiles.js b/afb-client/app/Frontend/widgets/FormInput/UploadFiles.js new file mode 100644 index 0000000..90110c9 --- /dev/null +++ b/afb-client/app/Frontend/widgets/FormInput/UploadFiles.js @@ -0,0 +1,310 @@ + +/* + * Copyright (C) 2015 "IoT.bzh" + * Author "Fulup Ar Foll" + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details.. + * + * Reference: + * https://developer.mozilla.org/en/docs/Web/API/FileReader + * https://developer.mozilla.org/en-US/docs/Using_files_from_web_applications#Using_hidden_file_input_elements_using_the_click%28%29_method + * https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs + * https://www.terlici.com/2015/05/16/uploading-files-locally.html + * https://github.com/nervgh/angular-file-upload/blob/master/src/services/FileUploader.js + * https://stuk.github.io/jszip/documentation/howto/read_zip.html + * http://onehungrymind.com/zip-parsing-jszip-angular/ + * http://stackoverflow.com/questions/15341912/how-to-go-from-blob-to-arraybuffer + * + * Bugs: zip file sent even when flag as invalid + */ + + + +(function() { +'use strict'; + +// WARNING: Angular ng-change does not work on input/file. Let's hook our callback through standard JS function +var tmpl = '<input type="file" name="{{name}}-input" onchange="angular.element(this).scope().UpLoadFile(this.files)" accept="{{mimetype}}" style="display:none">'+ + '<div class="upload-file" ng-click="imgClicked()">' + + '<img id="{{name}}-img" src="{{thumbnail}}">' + + '<range-slider ng-show="!noslider" id="{{name}}-slider" automatic=true inithook="SliderInitCB"></range-slider>' + + '</div>'; + + +// Service Create xform insert files in and Post it to url +function LoadFileSvc (scope, elem, posturl, files, thumbnailCB) { + var xmlReq = new XMLHttpRequest(); + var xform = new FormData(); + + var OnLoadCB = function (target) { + var status = thumbnailCB (target); + //if (status) xform.append(scope.name, file, file.name); + }; + // Update slider during Upload + xmlReq.upload.onprogress = function (event) { + var progress = Math.round(event.lengthComputable ? event.loaded * 100 / event.total : 0); + if (scope.slider) scope.slider.setValue (progress); + }; + + // Upload is finish let's notify controler callback + xmlReq.onload = function () { + elem.addClass ("success"); + elem.removeClass ("error"); + var response ={ + status : xmlReq.status, + headers: xmlReq.getAllResponseHeaders() + }; + scope.callback (response); + }; + + xmlReq.onerror = function () { + elem.addClass ("error"); + elem.removeClass ("success"); + var response ={ + status : xmlReq.status, + headers: xmlReq.getAllResponseHeaders() + }; + scope.callback (response); + }; + + xmlReq.onabort = function () { + elem.addClass ("error"); + elem.removeClass ("success"); + var response ={ + status : xmlReq.status, + headers: xmlReq.getAllResponseHeaders() + }; + scope.callback (response); + }; + + for (var i = 0; i < files.length; i++) { + var file = files[i]; + if (!file.type.match(scope.mimetype)) { + continue; + } + + console.log ("Selected file=" + file.name + " size="+ file.size/1024 + " Type="+ file.type); + + // File to upload is too big + if (file.size > scope.maxsize*1024) { + scope.thumbnail = scope.istoobig; // warning if image path is wrong nothing happen + scope.$apply('thumbnail'); // we short-circuit Angular resync Image + return; + } + + // This is not an uploadable file + if(isNaN(file.size)) { + scope.thumbnail = scope.isnotvalid; + scope.$apply('thumbnail'); + return; + } + + scope.Basename= file.name.split('/').reverse()[0]; + scope.imgElem[0].file = file; + + // If File is an image let display it now + if (thumbnailCB) { + var reader = new FileReader(); + reader.readAsArrayBuffer(file); + reader.onload = OnLoadCB; + } + // if everything is OK let's add file to xform + xform.append(scope.name, file, file.name); + } + + + // everything looks OK let's Post it + xmlReq.open("POST", posturl , true); + xmlReq.send(xform); +} + +angular.module('UploadFiles',['AppConfig', 'ModalNotification', 'RangeSlider']) + +.directive('uploadImage', function(AppConfig, JQemu, Notification) { + function mymethods(scope, elem, attrs) { + + // get widget image handle from template + scope.imgElem = elem.find('img'); + scope.inputElem = elem.find('input'); + + // Image was ckick let's simulate an input (file) click + scope.imgClicked = function () { + scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!! + }; + + // Slider control handle registration after creation + scope.SliderInitCB=function (slider) { + scope.slider= slider; + }; + + // Upload is delegated to a shared function + scope.UpLoadFile=function (files) { + var readerCB = function (upload) { + // scope.thumbnail = upload.target.result; + scope.imgElem[0].src = window.URL.createObjectURL(new Blob([upload.target.result], {type: "image"})); + return true; // true activates post + }; + var posturl = attrs.posturl + "?token=" + AppConfig.session.token; + new LoadFileSvc (scope, elem, posturl, files, readerCB); + }; + + // Initiallize default values from attributes values + scope.name= attrs.name || 'file'; + scope.category= attrs.category || 'image'; + scope.mimetype= (attrs.accept || 'image') + '/*'; + scope.maxsize= attrs.maxsize || 100; // default max size 100KB + scope.regexp = new RegExp (attrs.accept+ '.*','i'); + + if (attrs.thumbnail) scope.thumbnail= AppConfig.paths[scope.category] + attrs.thumbnail; + else scope.thumbnail=AppConfig.paths[scope.category] + 'tux-bzh.png'; + + if (attrs.thumbnail) scope.isnotvalid= AppConfig.paths[scope.category] + attrs.isnotvalid; + else scope.isnotvalid=AppConfig.paths[scope.category] + 'isnotvalid.png'; + + if (attrs.istoobig) scope.istoobig= AppConfig.paths[scope.category] + attrs.istoobig; + else scope.istoobig=AppConfig.paths[scope.category] + 'istoobig.png'; + scope.noslider = attrs.noslider || false; + + if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs); + } + return { + restrict: 'E', + template: tmpl, + link: mymethods, + scope: { + callback : '=' + } + }; +}) + +.directive('uploadAudio', function(AppConfig, JQemu, Notification) { + function mymethods(scope, elem, attrs) { + + // get widget image handle from template + scope.imgElem = elem.find('img'); + scope.inputElem = elem.find('input'); + + // Image was ckick let's simulate an input (file) click + scope.imgClicked = function () { + scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!! + }; + + // Slider control handle registration after creation + scope.SliderInitCB=function (slider) { + scope.slider= slider; + }; + + // Upload is delegated to a shared function + scope.UpLoadFile=function (files) { + var posturl = attrs.posturl + "?token=" + AppConfig.session.token; + new LoadFileSvc (scope, elem, posturl, files, false); + }; + + // Initiallize default values from attributes values + scope.name= attrs.name || 'audio'; + scope.category= attrs.category || 'audio'; + scope.mimetype= (attrs.accept || 'audio') + '/*'; + scope.maxsize= attrs.maxsize || 10000; // default max size 10MB + scope.regexp = new RegExp (attrs.accept+ '.*','i'); + + if (attrs.thumbnail) scope.thumbnail= AppConfig.paths[scope.category] + attrs.thumbnail; + else scope.thumbnail=AppConfig.paths[scope.category] + 'upload-music.png'; + + if (attrs.thumbnail) scope.isnotvalid= AppConfig.paths[scope.category] + attrs.isnotvalid; + else scope.isnotvalid=AppConfig.paths[scope.category] + 'isnotvalid.png'; + + if (attrs.istoobig) scope.istoobig= AppConfig.paths[scope.category] + attrs.istoobig; + else scope.istoobig=AppConfig.paths[scope.category] + 'istoobig.png'; + scope.noslider = attrs.noslider || false; + + if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs); + } + return { + restrict: 'E', + template: tmpl, + link: mymethods, + scope: { + callback : '=' + } + }; + +}) + +.directive('uploadAppli', function(AppConfig, JQemu, Notification) { + function mymethods(scope, elem, attrs) { + + // get widget image handle from template + scope.imgElem = elem.find('img'); + scope.inputElem = elem.find('input'); + + // Image was ckick let's simulate an input (file) click + scope.imgClicked = function () { + scope.inputElem[0].click(); // Warning Angular TriggerEvent does not work!!! + }; + + // Slider control handle registration after creation + scope.SliderInitCB=function (slider) { + scope.slider= slider; + }; + + // Upload is delegated to a shared function + scope.UpLoadFile=function (files) { + + var readerCB = function (upload) { + var zipapp = new JSZip(upload.target.result); + var thumbnail = zipapp.file("afa-pkg/thumbnail.jpg"); + + // Check is we have a thumbnail within loaded Zipfile + if (!thumbnail) { + console.log ("This is not a valid Application Framework APP"); + scope.thumbnail=AppConfig.paths[scope.category] + 'isnotvalid.png'; + scope.$apply('thumbnail'); // we short-circuit Angular resync Image + return false; // do not post zip on binder + } + scope.imgElem[0].src = window.URL.createObjectURL(new Blob([thumbnail.asArrayBuffer()], {type: "image"})); + return true; // true activates post + }; + var posturl = attrs.posturl + "?token=" + AppConfig.session.token; + new LoadFileSvc (scope, elem, posturl, files, readerCB); + }; + + // Initiallize default values from attributes values + scope.name= attrs.name || 'appli'; + scope.category= attrs.category || 'appli'; + scope.mimetype= (attrs.accept || '.zip'); + scope.maxsize= attrs.maxsize || 100000; // default max size 100MB + scope.regexp = new RegExp (attrs.accept+ '.*','i'); + + if (attrs.thumbnail) scope.thumbnail= AppConfig.paths[scope.category] + attrs.thumbnail; + else scope.thumbnail=AppConfig.paths[scope.category] + 'upload-appli.png'; + + if (attrs.thumbnail) scope.isnotvalid= AppConfig.paths[scope.category] + attrs.isnotvalid; + else scope.isnotvalid=AppConfig.paths[scope.category] + 'isnotvalid.png'; + + if (attrs.istoobig) scope.istoobig= AppConfig.paths[scope.category] + attrs.istoobig; + else scope.istoobig=AppConfig.paths[scope.category] + 'istoobig.png'; + scope.noslider = attrs.noslider || false; + + if (!attrs.posturl) throw new TypeError('file-upload %s posturl=/api/xxxx/xxxx required', scope.attrs); + } + return { + restrict: 'E', + template: tmpl, + link: mymethods, + scope: { + callback : '=' + } + }; + +}); + +console.log ("UploadFile Loaded"); +})(); |