diff --git a/.eslintrc b/.eslintrc index f94193e00e..4ea7f0edff 100644 --- a/.eslintrc +++ b/.eslintrc @@ -54,6 +54,7 @@ "Taggle": true, "Gantt": true, "Slick": true, + "Webcam": true, "PhotoSwipe": true, "PhotoSwipeUI_Default": true, "fluxify": true, diff --git a/.github/logo.png b/.github/logo.png new file mode 100644 index 0000000000..258408edfa Binary files /dev/null and b/.github/logo.png differ diff --git a/.gitignore b/.gitignore index 75bee9a091..436a08414e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ locale dist/ build/ frappe/docs/current +.vscode +node_modules \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..fe7159848b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.linting.pylintEnabled": false +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..956e51befb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2016-2017 Frappé Technologies Pvt. Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..f30645229e --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +clean: + python setup.py clean \ No newline at end of file diff --git a/README.md b/README.md index 9621b58b46..b8427fc055 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,33 @@ -## Frappé Framework - -[![Build Status](https://travis-ci.org/frappe/frappe.png)](https://travis-ci.org/frappe/frappe) +
+ +

+ + frappé + +

+

+ a web framework with "batteries included" +

+
+ it's pronounced - fra-pay +
+
+ +
+ + + + + + +
Full-stack web application framework that uses Python and MariaDB on the server side and a tightly integrated client side library. Built for [ERPNext](https://erpnext.com) +### Table of Contents +* [Installation](#installation) +* [License](#license) + ### Installation [Install via Frappé Bench](https://github.com/frappe/bench) @@ -20,5 +44,4 @@ For details and documentation, see the website [https://frappe.io](https://frappe.io) ### License - -MIT License +This repository has been released under the [MIT License](LICENSE). diff --git a/frappe/build.py b/frappe/build.py index 2a201fee6f..e0ce46f923 100644 --- a/frappe/build.py +++ b/frappe/build.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals, print_function from frappe.utils.minify import JavascriptMinify import subprocess +import warnings from six import iteritems, text_type @@ -25,12 +26,12 @@ def setup(): except ImportError: pass app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules] -def bundle(no_compress, make_copy=False, verbose=False): +def bundle(no_compress, make_copy=False, restore=False, verbose=False): """concat / minify js files""" # build js files setup() - make_asset_dirs(make_copy=make_copy) + make_asset_dirs(make_copy=make_copy, restore=restore) # new nodejs build system command = 'node --use_strict ../apps/frappe/frappe/build.js --build' @@ -60,7 +61,8 @@ def watch(no_compress): # time.sleep(3) -def make_asset_dirs(make_copy=False): +def make_asset_dirs(make_copy=False, restore=False): + # don't even think of making assets_path absolute - rm -rf ahead. assets_path = os.path.join(frappe.local.sites_path, "assets") for dir_path in [ os.path.join(assets_path, 'js'), @@ -80,11 +82,28 @@ def make_asset_dirs(make_copy=False): for source, target in symlinks: source = os.path.abspath(source) - if not os.path.exists(target) and os.path.exists(source): - if make_copy: - shutil.copytree(source, target) + if os.path.exists(source): + if restore: + if os.path.exists(target): + if os.path.islink(target): + os.unlink(target) + else: + shutil.rmtree(target) + shutil.copytree(source, target) + elif make_copy: + if os.path.exists(target): + warnings.warn('Target {target} already exists.'.format(target = target)) + else: + shutil.copytree(source, target) else: + if os.path.exists(target): + if os.path.islink(target): + os.unlink(target) + else: + shutil.rmtree(target) os.symlink(source, target) + else: + warnings.warn('Source {source} does not exists.'.format(source = source)) def build(no_compress=False, verbose=False): assets_path = os.path.join(frappe.local.sites_path, "assets") diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 710327b5fe..9b4d40dcd5 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -8,13 +8,14 @@ from frappe.utils import update_progress_bar @click.command('build') @click.option('--make-copy', is_flag=True, default=False, help='Copy the files instead of symlinking') +@click.option('--restore', is_flag=True, default=False, help='Copy the files instead of symlinking with force') @click.option('--verbose', is_flag=True, default=False, help='Verbose') -def build(make_copy=False, verbose=False): +def build(make_copy=False, restore = False, verbose=False): "Minify + concatenate JS and CSS files, build translations" import frappe.build import frappe frappe.init('') - frappe.build.bundle(False, make_copy=make_copy, verbose=verbose) + frappe.build.bundle(False, make_copy=make_copy, restore = restore, verbose=verbose) @click.command('watch') def watch(): diff --git a/frappe/core/page/desktop/desktop.js b/frappe/core/page/desktop/desktop.js index 8b4c062ab6..f4d5d759f8 100644 --- a/frappe/core/page/desktop/desktop.js +++ b/frappe/core/page/desktop/desktop.js @@ -112,13 +112,17 @@ $.extend(frappe.desktop, { }, setup_module_click: function() { + frappe.desktop.wiggling = false; + if(frappe.list_desktop) { frappe.desktop.wrapper.on("click", ".desktop-list-item", function() { frappe.desktop.open_module($(this)); }); } else { frappe.desktop.wrapper.on("click", ".app-icon", function() { - frappe.desktop.open_module($(this).parent()); + if ( !frappe.desktop.wiggling ) { + frappe.desktop.open_module($(this).parent()); + } }); } frappe.desktop.wrapper.on("click", ".circle", function() { @@ -127,6 +131,115 @@ $.extend(frappe.desktop, { frappe.ui.notifications.show_open_count_list(doctype); } }); + + frappe.desktop.setup_wiggle(); + }, + + setup_wiggle: () => { + // Wiggle, Wiggle, Wiggle. + const DURATION_LONG_PRESS = 1000; + // lesser the antidode, more the wiggle (like your drunk uncle) + // 75 seems good to replicate the iOS feels. + const WIGGLE_ANTIDODE = 75; + + var timer_id = 0; + const $cases = frappe.desktop.wrapper.find('.case-wrapper'); + const $icons = frappe.desktop.wrapper.find('.app-icon'); + const $notis = $(frappe.desktop.wrapper.find('.circle').toArray().filter((object) => { + // This hack is so bad, I should punch myself. + const doctype = $(object).data('doctype'); + + return doctype; + })); + + const clearWiggle = () => { + const $closes = $cases.find('.module-remove'); + $closes.hide(); + $notis.show(); + + $icons.trigger('stopRumble'); + + frappe.desktop.wiggling = false; + }; + + // initiate wiggling. + $icons.jrumble({ + speed: WIGGLE_ANTIDODE // seems neat enough to match the iOS way + }); + + frappe.desktop.wrapper.on('mousedown', '.app-icon', () => { + timer_id = setTimeout(() => { + frappe.desktop.wiggling = true; + // hide all notifications. + $notis.hide(); + + $cases.each((i) => { + const $case = $($cases[i]); + const template = + ` +
+
+ + × + +
+
+ `; + + $case.append(template); + const $close = $case.find('.module-remove'); + const name = $case.attr('title'); + $close.click(() => { + // good enough to create dynamic dialogs? + const dialog = new frappe.ui.Dialog({ + title: __(`Hide ${name}?`) + }); + dialog.set_primary_action(__('Hide'), () => { + frappe.call({ + method: 'frappe.desk.doctype.desktop_icon.desktop_icon.hide', + args: { name: name }, + freeze: true, + callback: (response) => + { + if ( response.message ) { + location.reload(); + } + } + }) + + dialog.hide(); + + clearWiggle(); + }); + // Hacks, Hacks and Hacks. + var $cancel = dialog.get_close_btn(); + $cancel.click(() => { + clearWiggle(); + }); + $cancel.html(__(`Cancel`)); + + dialog.show(); + }); + }); + + $icons.trigger('startRumble'); + }, DURATION_LONG_PRESS); + }); + frappe.desktop.wrapper.on('mouseup mouseleave', '.app-icon', () => { + clearTimeout(timer_id); + }); + + // also stop wiggling if clicked elsewhere. + $('body').click((event) => { + if ( frappe.desktop.wiggling ) { + const $target = $(event.target); + // our target shouldn't be .app-icons or .close + const $parent = $target.parents('.case-wrapper'); + if ( $parent.length == 0 ) + clearWiggle(); + } + }); + // end wiggle }, open_module: function(parent) { diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.py b/frappe/desk/doctype/desktop_icon/desktop_icon.py index 0787af88f7..91de421e8f 100644 --- a/frappe/desk/doctype/desktop_icon/desktop_icon.py +++ b/frappe/desk/doctype/desktop_icon/desktop_icon.py @@ -404,3 +404,16 @@ palette = ( ('#4F8EA8', 1), ('#428B46', 1) ) + +@frappe.whitelist() +def hide(name, user = None): + if not user: + user = frappe.session.user + + try: + set_hidden(name, user, hidden = 1) + clear_desktop_icons_cache() + except Exception: + return False + + return True \ No newline at end of file diff --git a/frappe/public/build.json b/frappe/public/build.json index 913adfe735..8d10760cf1 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -23,6 +23,7 @@ "public/js/frappe/misc/rating_icons.html" ], "js/control.min.js": [ + "public/js/frappe/ui/capture.js", "public/js/frappe/form/controls/base_control.js", "public/js/frappe/form/controls/base_input.js", "public/js/frappe/form/controls/data.js", @@ -55,12 +56,15 @@ "js/dialog.min.js": [ "public/js/frappe/dom.js", "public/js/frappe/ui/modal.html", + "public/js/frappe/form/formatters.js", "public/js/frappe/form/layout.js", "public/js/frappe/ui/field_group.js", "public/js/frappe/form/link_selector.js", "public/js/frappe/form/multi_select_dialog.js", "public/js/frappe/ui/dialog.js", + "public/js/frappe/ui/capture.js", + "public/js/frappe/form/controls/base_control.js", "public/js/frappe/form/controls/base_input.js", "public/js/frappe/form/controls/data.js", @@ -130,7 +134,9 @@ "public/js/lib/jSignature.min.js", "public/js/frappe/translate.js", "public/js/lib/datepicker/datepicker.min.js", - "public/js/lib/datepicker/locale-all.js" + "public/js/lib/datepicker/locale-all.js", + "public/js/lib/jquery.jrumble.min.js", + "public/js/lib/webcam.min.js" ], "js/desk.min.js": [ "public/js/frappe/class.js", @@ -168,6 +174,7 @@ "public/js/frappe/form/link_selector.js", "public/js/frappe/form/multi_select_dialog.js", "public/js/frappe/ui/dialog.js", + "public/js/frappe/ui/capture.js", "public/js/frappe/ui/app_icon.js", "public/js/frappe/model/model.js", diff --git a/frappe/public/js/frappe/form/controls/color.js b/frappe/public/js/frappe/form/controls/color.js index d13c22d19d..6e8880a72a 100644 --- a/frappe/public/js/frappe/form/controls/color.js +++ b/frappe/public/js/frappe/form/controls/color.js @@ -36,9 +36,11 @@ frappe.ui.form.ControlColor = frappe.ui.form.ControlData.extend({ set_formatted_input: function(value) { this._super(value); - if(!value) value = '#ffffff'; + if (!value) value = '#FFFFFF'; + const contrast = frappe.ui.color.get_contrast_color(value); + this.$input.css({ - "background-color": value + "background-color": value, "color": contrast }); }, bind_events: function () { diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js index 00af468754..5904ae8ec1 100644 --- a/frappe/public/js/frappe/form/controls/text_editor.js +++ b/frappe/public/js/frappe/form/controls/text_editor.js @@ -7,6 +7,23 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ this.setup_image_dialog(); this.setting_count = 0; }, + render_camera_button: (context) => { + var ui = $.summernote.ui; + var button = ui.button({ + contents: '', + tooltip: 'Camera', + click: () => { + const capture = new frappe.ui.Capture(); + capture.open(); + + capture.click((data) => { + context.invoke('editor.insertImage', data); + }); + } + }); + + return button.render(); + }, make_editor: function() { var me = this; this.editor = $("
").appendTo(this.input_area); @@ -25,9 +42,12 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ ['color', ['color']], ['para', ['ul', 'ol', 'paragraph', 'hr']], //['height', ['height']], - ['media', ['link', 'picture', 'video', 'table']], + ['media', ['link', 'picture', 'camera', 'video', 'table']], ['misc', ['fullscreen', 'codeview']] ], + buttons: { + camera: this.render_camera_button, + }, keyMap: { pc: { 'CTRL+ENTER': '' @@ -80,6 +100,7 @@ frappe.ui.form.ControlTextEditor = frappe.ui.form.ControlCode.extend({ 'outdent': 'fa fa-outdent', 'arrowsAlt': 'fa fa-arrows-alt', 'bold': 'fa fa-bold', + 'camera': 'fa fa-camera', 'caret': 'caret', 'circle': 'fa fa-circle', 'close': 'fa fa-close', diff --git a/frappe/public/js/frappe/ui/capture.js b/frappe/public/js/frappe/ui/capture.js new file mode 100644 index 0000000000..f555243975 --- /dev/null +++ b/frappe/public/js/frappe/ui/capture.js @@ -0,0 +1,94 @@ +frappe.ui.Capture = class +{ + constructor (options = { }) + { + this.options = Object.assign({}, frappe.ui.Capture.DEFAULT_OPTIONS, options); + this.dialog = new frappe.ui.Dialog(); + this.template = + ` +
+
+
+
+
+ +
+
+
+ + + +
+
+ + +
+
+
+ `; + $(this.dialog.body).append(this.template); + + this.$btnBarSnap = $(this.dialog.body).find('#frappe-capture-btn-toolbar-snap'); + this.$btnBarKnap = $(this.dialog.body).find('#frappe-capture-btn-toolbar-knap'); + this.$btnBarKnap.hide(); + + Webcam.set(this.options); + } + + open ( ) + { + this.dialog.show(); + + Webcam.attach('#frappe-capture'); + } + + freeze ( ) + { + this.$btnBarSnap.hide(); + this.$btnBarKnap.show(); + + Webcam.freeze(); + } + + unfreeze ( ) + { + this.$btnBarSnap.show(); + this.$btnBarKnap.hide(); + + Webcam.unfreeze(); + } + + click (callback) + { + $(this.dialog.body).find('#frappe-capture-btn-snap').click(() => { + this.freeze(); + + $(this.dialog.body).find('#frappe-capture-btn-discard').click(() => { + this.unfreeze(); + }); + + $(this.dialog.body).find('#frappe-capture-btn-accept').click(() => { + Webcam.snap((data) => { + callback(data); + }); + + this.hide(); + }); + }); + } + + hide ( ) + { + Webcam.reset(); + + $(this.dialog.$wrapper).remove(); + } +}; +frappe.ui.Capture.DEFAULT_OPTIONS = +{ + width: 480, height: 320, flip_horiz: true +}; \ No newline at end of file diff --git a/frappe/public/js/lib/jquery.jrumble.min.js b/frappe/public/js/lib/jquery.jrumble.min.js new file mode 100644 index 0000000000..71de6ffb7c --- /dev/null +++ b/frappe/public/js/lib/jquery.jrumble.min.js @@ -0,0 +1,2 @@ +/* jRumble v1.3 - http://jackrugile.com/jrumble - MIT License */ +(function(f){f.fn.jrumble=function(g){var a=f.extend({x:2,y:2,rotation:1,speed:15,opacity:false,opacityMin:0.5},g);return this.each(function(){var b=f(this),h=a.x*2,i=a.y*2,k=a.rotation*2,g=a.speed===0?1:a.speed,m=a.opacity,n=a.opacityMin,l,j,o=function(){var e=Math.floor(Math.random()*(h+1))-h/2,a=Math.floor(Math.random()*(i+1))-i/2,c=Math.floor(Math.random()*(k+1))-k/2,d=m?Math.random()+n:1,e=e===0&&h!==0?Math.random()<0.5?1:-1:e,a=a===0&&i!==0?Math.random()<0.5?1:-1:a;b.css("display")==="inline"&&(l=true,b.css("display","inline-block"));b.css({position:"relative",left:e+"px",top:a+"px","-ms-filter":"progid:DXImageTransform.Microsoft.Alpha(Opacity="+d*100+")",filter:"alpha(opacity="+d*100+")","-moz-opacity":d,"-khtml-opacity":d,opacity:d,"-webkit-transform":"rotate("+c+"deg)","-moz-transform":"rotate("+c+"deg)","-ms-transform":"rotate("+c+"deg)","-o-transform":"rotate("+c+"deg)",transform:"rotate("+c+"deg)"})},p={left:0,top:0,"-ms-filter":"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)",filter:"alpha(opacity=100)","-moz-opacity":1,"-khtml-opacity":1,opacity:1,"-webkit-transform":"rotate(0deg)","-moz-transform":"rotate(0deg)","-ms-transform":"rotate(0deg)","-o-transform":"rotate(0deg)",transform:"rotate(0deg)"};b.bind({startRumble:function(a){a.stopPropagation();clearInterval(j);j=setInterval(o,g)},stopRumble:function(a){a.stopPropagation();clearInterval(j);l&&b.css("display","inline");b.css(p)}})})}})(jQuery); \ No newline at end of file diff --git a/frappe/public/js/lib/webcam.min.js b/frappe/public/js/lib/webcam.min.js new file mode 100644 index 0000000000..3325ccfd05 --- /dev/null +++ b/frappe/public/js/lib/webcam.min.js @@ -0,0 +1,2 @@ +// WebcamJS v1.0.22 - http://github.com/jhuckaby/webcamjs - MIT Licensed +(function(e){var t;function a(){var e=Error.apply(this,arguments);e.name=this.name="FlashError";this.stack=e.stack;this.message=e.message}function i(){var e=Error.apply(this,arguments);e.name=this.name="WebcamError";this.stack=e.stack;this.message=e.message}IntermediateInheritor=function(){};IntermediateInheritor.prototype=Error.prototype;a.prototype=new IntermediateInheritor;i.prototype=new IntermediateInheritor;var Webcam={version:"1.0.22",protocol:location.protocol.match(/https/i)?"https":"http",loaded:false,live:false,userMedia:true,iOS:/iPad|iPhone|iPod/.test(navigator.userAgent)&&!e.MSStream,params:{width:0,height:0,dest_width:0,dest_height:0,image_format:"jpeg",jpeg_quality:90,enable_flash:true,force_flash:false,flip_horiz:false,fps:30,upload_name:"webcam",constraints:null,swfURL:"",flashNotDetectedText:"ERROR: No Adobe Flash Player detected. Webcam.js relies on Flash for browsers that do not support getUserMedia (like yours).",noInterfaceFoundText:"No supported webcam interface found.",unfreeze_snap:true,iosPlaceholderText:"Click here to open camera.",user_callback:null,user_canvas:null},errors:{FlashError:a,WebcamError:i},hooks:{},init:function(){var t=this;this.mediaDevices=navigator.mediaDevices&&navigator.mediaDevices.getUserMedia?navigator.mediaDevices:navigator.mozGetUserMedia||navigator.webkitGetUserMedia?{getUserMedia:function(e){return new Promise(function(t,a){(navigator.mozGetUserMedia||navigator.webkitGetUserMedia).call(navigator,e,t,a)})}}:null;e.URL=e.URL||e.webkitURL||e.mozURL||e.msURL;this.userMedia=this.userMedia&&!!this.mediaDevices&&!!e.URL;if(navigator.userAgent.match(/Firefox\D+(\d+)/)){if(parseInt(RegExp.$1,10)<21)this.userMedia=null}if(this.userMedia){e.addEventListener("beforeunload",function(e){t.reset()})}},exifOrientation:function(e){var t=new DataView(e);if(t.getUint8(0)!=255||t.getUint8(1)!=216){console.log("Not a valid JPEG file");return 0}var a=2;var i=null;while(a8){console.log("Invalid EXIF orientation value ("+p+")");return 0}return p}}}else{a+=2+t.getUint16(a+2)}}return 0},fixOrientation:function(e,t,a){var i=new Image;i.addEventListener("load",function(e){var s=document.createElement("canvas");var r=s.getContext("2d");if(t<5){s.width=i.width;s.height=i.height}else{s.width=i.height;s.height=i.width}switch(t){case 2:r.transform(-1,0,0,1,i.width,0);break;case 3:r.transform(-1,0,0,-1,i.width,i.height);break;case 4:r.transform(1,0,0,-1,0,i.height);break;case 5:r.transform(0,1,1,0,0,0);break;case 6:r.transform(0,1,-1,0,i.height,0);break;case 7:r.transform(0,-1,-1,0,i.height,i.width);break;case 8:r.transform(0,-1,1,0,0,i.width);break}r.drawImage(i,0,0);a.src=s.toDataURL()},false);i.src=e},attach:function(a){if(typeof a=="string"){a=document.getElementById(a)||document.querySelector(a)}if(!a){return this.dispatch("error",new i("Could not locate DOM element to attach to."))}this.container=a;a.innerHTML="";var s=document.createElement("div");a.appendChild(s);this.peg=s;if(!this.params.width)this.params.width=a.offsetWidth;if(!this.params.height)this.params.height=a.offsetHeight;if(!this.params.width||!this.params.height){return this.dispatch("error",new i("No width and/or height for webcam. Please call set() first, or attach to a visible element."))}if(!this.params.dest_width)this.params.dest_width=this.params.width;if(!this.params.dest_height)this.params.dest_height=this.params.height;this.userMedia=t===undefined?this.userMedia:t;if(this.params.force_flash){t=this.userMedia;this.userMedia=null}if(typeof this.params.fps!=="number")this.params.fps=30;var r=this.params.width/this.params.dest_width;var o=this.params.height/this.params.dest_height;if(this.userMedia){var n=document.createElement("video");n.setAttribute("autoplay","autoplay");n.style.width=""+this.params.dest_width+"px";n.style.height=""+this.params.dest_height+"px";if(r!=1||o!=1){a.style.overflow="hidden";n.style.webkitTransformOrigin="0px 0px";n.style.mozTransformOrigin="0px 0px";n.style.msTransformOrigin="0px 0px";n.style.oTransformOrigin="0px 0px";n.style.transformOrigin="0px 0px";n.style.webkitTransform="scaleX("+r+") scaleY("+o+")";n.style.mozTransform="scaleX("+r+") scaleY("+o+")";n.style.msTransform="scaleX("+r+") scaleY("+o+")";n.style.oTransform="scaleX("+r+") scaleY("+o+")";n.style.transform="scaleX("+r+") scaleY("+o+")"}a.appendChild(n);this.video=n;var h=this;this.mediaDevices.getUserMedia({audio:false,video:this.params.constraints||{mandatory:{minWidth:this.params.dest_width,minHeight:this.params.dest_height}}}).then(function(t){n.onloadedmetadata=function(e){h.stream=t;h.loaded=true;h.live=true;h.dispatch("load");h.dispatch("live");h.flip()};n.src=e.URL.createObjectURL(t)||t}).catch(function(e){if(h.params.enable_flash&&h.detectFlash()){setTimeout(function(){h.params.force_flash=1;h.attach(a)},1)}else{h.dispatch("error",e)}})}else if(this.iOS){var l=document.createElement("div");l.id=this.container.id+"-ios_div";l.className="webcamjs-ios-placeholder";l.style.width=""+this.params.width+"px";l.style.height=""+this.params.height+"px";l.style.textAlign="center";l.style.display="table-cell";l.style.verticalAlign="middle";l.style.backgroundRepeat="no-repeat";l.style.backgroundSize="contain";l.style.backgroundPosition="center";var c=document.createElement("span");c.className="webcamjs-ios-text";c.innerHTML=this.params.iosPlaceholderText;l.appendChild(c);var d=document.createElement("img");d.id=this.container.id+"-ios_img";d.style.width=""+this.params.dest_width+"px";d.style.height=""+this.params.dest_height+"px";d.style.display="none";l.appendChild(d);var f=document.createElement("input");f.id=this.container.id+"-ios_input";f.setAttribute("type","file");f.setAttribute("accept","image/*");f.setAttribute("capture","camera");var h=this;var m=this.params;f.addEventListener("change",function(e){if(e.target.files.length>0&&e.target.files[0].type.indexOf("image/")==0){var t=URL.createObjectURL(e.target.files[0]);var a=new Image;a.addEventListener("load",function(e){var t=document.createElement("canvas");t.width=m.dest_width;t.height=m.dest_height;var i=t.getContext("2d");ratio=Math.min(a.width/m.dest_width,a.height/m.dest_height);var s=m.dest_width*ratio;var r=m.dest_height*ratio;var o=(a.width-s)/2;var n=(a.height-r)/2;i.drawImage(a,o,n,s,r,0,0,m.dest_width,m.dest_height);var h=t.toDataURL();d.src=h;l.style.backgroundImage="url('"+h+"')"},false);var i=new FileReader;i.addEventListener("load",function(e){var i=h.exifOrientation(e.target.result);if(i>1){h.fixOrientation(t,i,a)}else{a.src=t}},false);var s=new XMLHttpRequest;s.open("GET",t,true);s.responseType="blob";s.onload=function(e){if(this.status==200||this.status===0){i.readAsArrayBuffer(this.response)}};s.send()}},false);f.style.display="none";a.appendChild(f);l.addEventListener("click",function(e){if(m.user_callback){h.snap(m.user_callback,m.user_canvas)}else{f.style.display="block";f.focus();f.click();f.style.display="none"}},false);a.appendChild(l);this.loaded=true;this.live=true}else if(this.params.enable_flash&&this.detectFlash()){e.Webcam=Webcam;var l=document.createElement("div");l.innerHTML=this.getSWFHTML();a.appendChild(l)}else{this.dispatch("error",new i(this.params.noInterfaceFoundText))}if(this.params.crop_width&&this.params.crop_height){var p=Math.floor(this.params.crop_width*r);var u=Math.floor(this.params.crop_height*o);a.style.width=""+p+"px";a.style.height=""+u+"px";a.style.overflow="hidden";a.scrollLeft=Math.floor(this.params.width/2-p/2);a.scrollTop=Math.floor(this.params.height/2-u/2)}else{a.style.width=""+this.params.width+"px";a.style.height=""+this.params.height+"px"}},reset:function(){if(this.preview_active)this.unfreeze();this.unflip();if(this.userMedia){if(this.stream){if(this.stream.getVideoTracks){var e=this.stream.getVideoTracks();if(e&&e[0]&&e[0].stop)e[0].stop()}else if(this.stream.stop){this.stream.stop()}}delete this.stream;delete this.video}if(this.userMedia!==true&&this.loaded&&!this.iOS){var t=this.getMovie();if(t&&t._releaseCamera)t._releaseCamera()}if(this.container){this.container.innerHTML="";delete this.container}this.loaded=false;this.live=false},set:function(){if(arguments.length==1){for(var e in arguments[0]){this.params[e]=arguments[0][e]}}else{this.params[arguments[0]]=arguments[1]}},on:function(e,t){e=e.replace(/^on/i,"").toLowerCase();if(!this.hooks[e])this.hooks[e]=[];this.hooks[e].push(t)},off:function(e,t){e=e.replace(/^on/i,"").toLowerCase();if(this.hooks[e]){if(t){var a=this.hooks[e].indexOf(t);if(a>-1)this.hooks[e].splice(a,1)}else{this.hooks[e]=[]}}},dispatch:function(){var t=arguments[0].replace(/^on/i,"").toLowerCase();var s=Array.prototype.slice.call(arguments,1);if(this.hooks[t]&&this.hooks[t].length){for(var r=0,o=this.hooks[t].length;rERROR: the Webcam.js Flash fallback does not work from local disk. Please run it from a web server.'}if(!this.detectFlash()){this.dispatch("error",new a("Adobe Flash Player not found. Please install from get.adobe.com/flashplayer and try again."));return'

'+this.params.flashNotDetectedText+"

"}if(!i){var s="";var r=document.getElementsByTagName("script");for(var o=0,n=r.length;o';return t},getMovie:function(){if(!this.loaded)return this.dispatch("error",new a("Flash Movie is not loaded yet"));var e=document.getElementById("webcam_movie_obj");if(!e||!e._snap)e=document.getElementById("webcam_movie_embed");if(!e)this.dispatch("error",new a("Cannot locate Flash movie in DOM"));return e},freeze:function(){var e=this;var t=this.params;if(this.preview_active)this.unfreeze();var a=this.params.width/this.params.dest_width;var i=this.params.height/this.params.dest_height;this.unflip();var s=t.crop_width||t.dest_width;var r=t.crop_height||t.dest_height;var o=document.createElement("canvas");o.width=s;o.height=r;var n=o.getContext("2d");this.preview_canvas=o;this.preview_context=n;if(a!=1||i!=1){o.style.webkitTransformOrigin="0px 0px";o.style.mozTransformOrigin="0px 0px";o.style.msTransformOrigin="0px 0px";o.style.oTransformOrigin="0px 0px";o.style.transformOrigin="0px 0px";o.style.webkitTransform="scaleX("+a+") scaleY("+i+")";o.style.mozTransform="scaleX("+a+") scaleY("+i+")";o.style.msTransform="scaleX("+a+") scaleY("+i+")";o.style.oTransform="scaleX("+a+") scaleY("+i+")";o.style.transform="scaleX("+a+") scaleY("+i+")"}this.snap(function(){o.style.position="relative";o.style.left=""+e.container.scrollLeft+"px";o.style.top=""+e.container.scrollTop+"px";e.container.insertBefore(o,e.peg);e.container.style.overflow="hidden";e.preview_active=true},o)},unfreeze:function(){if(this.preview_active){this.container.removeChild(this.preview_canvas);delete this.preview_context;delete this.preview_canvas;this.preview_active=false;this.flip()}},flip:function(){if(this.params.flip_horiz){var e=this.container.style;e.webkitTransform="scaleX(-1)";e.mozTransform="scaleX(-1)";e.msTransform="scaleX(-1)";e.oTransform="scaleX(-1)";e.transform="scaleX(-1)";e.filter="FlipH";e.msFilter="FlipH"}},unflip:function(){if(this.params.flip_horiz){var e=this.container.style;e.webkitTransform="scaleX(1)";e.mozTransform="scaleX(1)";e.msTransform="scaleX(1)";e.oTransform="scaleX(1)";e.transform="scaleX(1)";e.filter="";e.msFilter=""}},savePreview:function(e,t){var a=this.params;var i=this.preview_canvas;var s=this.preview_context;if(t){var r=t.getContext("2d");r.drawImage(i,0,0)}e(t?null:i.toDataURL("image/"+a.image_format,a.jpeg_quality/100),i,s);if(this.params.unfreeze_snap)this.unfreeze()},snap:function(e,t){if(!e)e=this.params.user_callback;if(!t)t=this.params.user_canvas;var a=this;var s=this.params;if(!this.loaded)return this.dispatch("error",new i("Webcam is not loaded yet"));if(!e)return this.dispatch("error",new i("Please provide a callback function or canvas to snap()"));if(this.preview_active){this.savePreview(e,t);return null}var r=document.createElement("canvas");r.width=this.params.dest_width;r.height=this.params.dest_height;var o=r.getContext("2d");if(this.params.flip_horiz){o.translate(s.dest_width,0);o.scale(-1,1)}var n=function(){if(this.src&&this.width&&this.height){o.drawImage(this,0,0,s.dest_width,s.dest_height)}if(s.crop_width&&s.crop_height){var a=document.createElement("canvas");a.width=s.crop_width;a.height=s.crop_height;var i=a.getContext("2d");i.drawImage(r,Math.floor(s.dest_width/2-s.crop_width/2),Math.floor(s.dest_height/2-s.crop_height/2),s.crop_width,s.crop_height,0,0,s.crop_width,s.crop_height);o=i;r=a}if(t){var n=t.getContext("2d");n.drawImage(r,0,0)}e(t?null:r.toDataURL("image/"+s.image_format,s.jpeg_quality/100),r,o)};if(this.userMedia){o.drawImage(this.video,0,0,this.params.dest_width,this.params.dest_height);n()}else if(this.iOS){var h=document.getElementById(this.container.id+"-ios_div");var l=document.getElementById(this.container.id+"-ios_img");var c=document.getElementById(this.container.id+"-ios_input");iFunc=function(e){n.call(l);l.removeEventListener("load",iFunc);h.style.backgroundImage="none";l.removeAttribute("src");c.value=null};if(!c.value){l.addEventListener("load",iFunc);c.style.display="block";c.focus();c.click();c.style.display="none"}else{iFunc(null)}}else{var d=this.getMovie()._snap();var l=new Image;l.onload=n;l.src="data:image/"+this.params.image_format+";base64,"+d}return null},configure:function(e){if(!e)e="camera";this.getMovie()._configure(e)},flashNotify:function(e,t){switch(e){case"flashLoadComplete":this.loaded=true;this.dispatch("load");break;case"cameraLive":this.live=true;this.dispatch("live");break;case"error":this.dispatch("error",new a(t));break;default:break}},b64ToUint6:function(e){return e>64&&e<91?e-65:e>96&&e<123?e-71:e>47&&e<58?e+4:e===43?62:e===47?63:0},base64DecToArr:function(e,t){var a=e.replace(/[^A-Za-z0-9\+\/]/g,""),i=a.length,s=t?Math.ceil((i*3+1>>2)/t)*t:i*3+1>>2,r=new Uint8Array(s);for(var o,n,h=0,l=0,c=0;c>>(16>>>o&24)&255}h=0}}return r},upload:function(e,t,a){var i=this.params.upload_name||"webcam";var s="";if(e.match(/^data\:image\/(\w+)/))s=RegExp.$1;else throw"Cannot locate image format in Data URI";var r=e.replace(/^data\:image\/\w+\;base64\,/,"");var o=new XMLHttpRequest;o.open("POST",t,true);if(o.upload&&o.upload.addEventListener){o.upload.addEventListener("progress",function(e){if(e.lengthComputable){var t=e.loaded/e.total;Webcam.dispatch("uploadProgress",t,e)}},false)}var n=this;o.onload=function(){if(a)a.apply(n,[o.status,o.responseText,o.statusText]);Webcam.dispatch("uploadComplete",o.status,o.responseText,o.statusText)};var h=new Blob([this.base64DecToArr(r)],{type:"image/"+s});var l=new FormData;l.append(i,h,i+"."+s.replace(/e/,""));o.send(l)}};Webcam.init();if(typeof define==="function"&&define.amd){define(function(){return Webcam})}else if(typeof module==="object"&&module.exports){module.exports=Webcam}else{e.Webcam=Webcam}})(window); \ No newline at end of file diff --git a/license.txt b/license.txt deleted file mode 100644 index a76f4ef0ba..0000000000 --- a/license.txt +++ /dev/null @@ -1,9 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Frappe Technologies Pvt. Ltd. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/requirements.txt b/requirements.txt index b5c564eede..0216f85690 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ httplib2 jinja2 markdown2 markupsafe -mysqlclient==1.3.10 +-e git+https://github.com/frappe/mysqlclient-python.git@1.3.12#egg=mysqlclient python-geoip python-geoip-geolite2 python-dateutil diff --git a/setup.py b/setup.py index bbcf6cfb37..466449e956 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,7 @@ +# imports - standard imports +import os, shutil +from distutils.command.clean import clean as Clean + from setuptools import setup, find_packages from pip.req import parse_requirements import re, ast @@ -11,6 +15,31 @@ with open('frappe/__init__.py', 'rb') as f: requirements = parse_requirements("requirements.txt", session="") +class CleanCommand(Clean): + def run(self): + Clean.run(self) + + basedir = os.path.abspath(os.path.dirname(__file__)) + + for relpath in ['build', '.cache', '.coverage', 'dist', 'frappe.egg-info']: + abspath = os.path.join(basedir, relpath) + if os.path.exists(abspath): + if os.path.isfile(abspath): + os.remove(abspath) + else: + shutil.rmtree(abspath) + + for dirpath, dirnames, filenames in os.walk(basedir): + for filename in filenames: + _, extension = os.path.splitext(filename) + if extension in ['.pyc']: + abspath = os.path.join(dirpath, filename) + os.remove(abspath) + for dirname in dirnames: + if dirname in ['__pycache__']: + abspath = os.path.join(dirpath, dirname) + shutil.rmtree(abspath) + setup( name='frappe', version=version, @@ -21,5 +50,9 @@ setup( zip_safe=False, include_package_data=True, install_requires=[str(ir.req) for ir in requirements], - dependency_links=[str(ir._link) for ir in requirements if ir._link] -) + dependency_links=[str(ir._link) for ir in requirements if ir._link], + cmdclass = \ + { + 'clean': CleanCommand + } +) \ No newline at end of file