diff --git a/.travis.yml b/.travis.yml index 79a6fd81ccb7..dab0a44a0c1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,9 @@ language: python python: - "2.7" - + sudo: false +group: edge cache: pip addons: diff --git a/web_drop_target/README.rst b/web_drop_target/README.rst new file mode 100644 index 000000000000..a3718dff6470 --- /dev/null +++ b/web_drop_target/README.rst @@ -0,0 +1,75 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: https://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +=================== +Drop target support +=================== + +This module was written to extend the functionality of the web client to support dropping local files into the web client. + +By default, this will create an attachment when a file is being dropped on a form, other modules can add more features. + +Further, this module is meant as a base module supporting other actions after some file is dropped so that we don't get incompatible drag&drop modules within the OCA. + +Usage +===== + +To use this module, you need to: + +#. drag a file from your local computer onto an Odoo form view +#. it should become an attachment of the currently opened record + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/162/10.0 + +Known issues / Roadmap +====================== + +* dropping on list or kanban views would be nice too +* handle multiple files +* add an upload progress meter for huge files + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Libraries +--------- + +* `base64js `_. + +Contributors +------------ + +* Holger Brunn + +Do not contact contributors directly about help with questions or problems concerning this addon, but use the `community mailing list `_ or the `appropriate specialized mailinglist `_ for help, and the bug tracker linked in `Bug Tracker`_ above for technical issues. + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/web_drop_target/__init__.py b/web_drop_target/__init__.py new file mode 100644 index 000000000000..8c501711bc6c --- /dev/null +++ b/web_drop_target/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# Copyright 2018 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). diff --git a/web_drop_target/__manifest__.py b/web_drop_target/__manifest__.py new file mode 100644 index 000000000000..b0821c02e6f7 --- /dev/null +++ b/web_drop_target/__manifest__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Copyright 2018 Therp BV +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +{ + "name": "Drop target support", + "version": "10.0.1.0.0", + "author": "Therp BV,Odoo Community Association (OCA)", + "license": "AGPL-3", + "category": "Usability", + "summary": "Allows to drag files into Odoo", + "depends": [ + 'web', + ], + "data": [ + 'views/templates.xml', + ], +} diff --git a/web_drop_target/static/description/icon.png b/web_drop_target/static/description/icon.png new file mode 100644 index 000000000000..3a0328b516c4 Binary files /dev/null and b/web_drop_target/static/description/icon.png differ diff --git a/web_drop_target/static/lib/base64js.min.js b/web_drop_target/static/lib/base64js.min.js new file mode 100644 index 000000000000..8b055fb56b74 --- /dev/null +++ b/web_drop_target/static/lib/base64js.min.js @@ -0,0 +1 @@ +(function(r){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=r()}else if(typeof define==="function"&&define.amd){define([],r)}else{var e;if(typeof window!=="undefined"){e=window}else if(typeof global!=="undefined"){e=global}else if(typeof self!=="undefined"){e=self}else{e=this}e.base64js=r()}})(function(){var r,e,n;return function(){function r(e,n,t){function o(i,a){if(!n[i]){if(!e[i]){var u=typeof require=="function"&&require;if(!a&&u)return u(i,!0);if(f)return f(i,!0);var d=new Error("Cannot find module '"+i+"'");throw d.code="MODULE_NOT_FOUND",d}var c=n[i]={exports:{}};e[i][0].call(c.exports,function(r){var n=e[i][1][r];return o(n?n:r)},c,c.exports,r,e,n,t)}return n[i].exports}var f=typeof require=="function"&&require;for(var i=0;i0){throw new Error("Invalid string. Length must be a multiple of 4")}return r[e-2]==="="?2:r[e-1]==="="?1:0}function c(r){return r.length*3/4-d(r)}function v(r){var e,n,t,i,a;var u=r.length;i=d(r);a=new f(u*3/4-i);n=i>0?u-4:u;var c=0;for(e=0;e>16&255;a[c++]=t>>8&255;a[c++]=t&255}if(i===2){t=o[r.charCodeAt(e)]<<2|o[r.charCodeAt(e+1)]>>4;a[c++]=t&255}else if(i===1){t=o[r.charCodeAt(e)]<<10|o[r.charCodeAt(e+1)]<<4|o[r.charCodeAt(e+2)]>>2;a[c++]=t>>8&255;a[c++]=t&255}return a}function l(r){return t[r>>18&63]+t[r>>12&63]+t[r>>6&63]+t[r&63]}function h(r,e,n){var t;var o=[];for(var f=e;fd?d:u+a))}if(o===1){e=r[n-1];f+=t[e>>2];f+=t[e<<4&63];f+="=="}else if(o===2){e=(r[n-2]<<8)+r[n-1];f+=t[e>>10];f+=t[e>>4&63];f+=t[e<<2&63];f+="="}i.push(f);return i.join("")}},{}]},{},[])("/")}); diff --git a/web_drop_target/static/src/css/web_drop_target.css b/web_drop_target/static/src/css/web_drop_target.css new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/web_drop_target/static/src/js/web_drop_target.js b/web_drop_target/static/src/js/web_drop_target.js new file mode 100644 index 000000000000..474058c756a4 --- /dev/null +++ b/web_drop_target/static/src/js/web_drop_target.js @@ -0,0 +1,132 @@ +//-*- coding: utf-8 -*- +//Copyright 2018 Therp BV +//License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +odoo.define('web_drop_target', function(require) +{ + var Model = require('web.Model'), + FormView = require('web.FormView'); + + // this is the main contribution of this addon: A mixin you can use + // to make some widget a drop target. Read on how to use this yourself + var DropTargetMixin = { + // add the mime types you want to support here, leave empty for + // all types. For more control, override _get_drop_item in your class + _drop_allowed_types: [], + + // a class being applied when the user drags something we can handle + _drag_over_class: 'o_drag_over', + + start: function() { + var result = this._super.apply(this, arguments); + this.$el.on('drop.widget_events', this.proxy('_on_drop')); + this.$el.on('dragenter.widget_events', this.proxy('_on_dragenter')); + this.$el.on('dragover.widget_events', this.proxy('_on_dragenter')); + this.$el.on('dragleave.widget_events', this.proxy('_on_dragleave')); + return result; + }, + + _on_drop: function(e) { + var drop_item = this._get_drop_item(e); + if(!drop_item) { + return; + } + jQuery(e.delegateTarget).removeClass(this._drag_over_class); + var reader = new FileReader(); + reader.onloadend = this.proxy( + _.partial(this._handle_file_drop, drop_item.getAsFile()) + ); + reader.readAsArrayBuffer(drop_item.getAsFile()); + e.preventDefault(); + }, + + _on_dragenter: function(e) { + if(this._get_drop_item(e)) { + e.preventDefault(); + jQuery(e.delegateTarget).addClass(this._drag_over_class); + return false; + }; + }, + + _on_dragleave: function(e) { + jQuery(e.delegateTarget).removeClass(this._drag_over_class); + }, + + _get_drop_item: function(e) { + var self = this, + dataTransfer = e.originalEvent.dataTransfer, + drop_item = null; + _.each(dataTransfer.items, function(item) { + if( + _.contains(self._drop_allowed_types, item.type) || + _.isEmpty(self._drop_allowed_types) + ) { + drop_item = item; + } + }); + return drop_item; + }, + + _handle_file_drop: function(drop_file, e) { + // do something here, for example call the helper function below + // e is the on_load_end handler for the FileReader above, + // so e.target.result contains an ArrayBuffer of the data + }, + + _handle_file_drop_attach: function( + drop_file, e, res_model, res_id, extra_data + ) { + // helper to upload an attachment and update the sidebar + var self = this; + return new Model('ir.attachment').call( + 'create', + [ + _.extend({ + name: drop_file.name, + datas: base64js.fromByteArray( + new Uint8Array(e.target.result) + ), + datas_fname: drop_file.name, + res_model: res_model, + res_id: res_id, + }, extra_data || {}) + ], + ) + .then(function() { + // try to find a sidebar and update it if we found one + var p = self; + while(p && !p.sidebar) { + p = p.getParent ? p.getParent() : null; + } + if(p) { + var sidebar = p.sidebar; + sidebar.do_attachement_update( + sidebar.dataset, sidebar.model_id + ); + } + }); + }, + }; + + // and here we apply the mixin to form views, allowing any files and + // adding them as attachment + FormView.include(_.extend(DropTargetMixin, { + _get_drop_file: function() { + // disable drag&drop when we're on an unsaved record + if(!this.datarecord.id) { + return null; + } else { + return this._super.apply(this, arguments); + } + }, + _handle_file_drop: function(drop_file, e) { + return this._handle_file_drop_attach( + drop_file, e, this.dataset.model, this.datarecord.id, + ); + }, + })); + + return { + 'DropTargetMixin': DropTargetMixin, + } +}); diff --git a/web_drop_target/views/templates.xml b/web_drop_target/views/templates.xml new file mode 100644 index 000000000000..6021a21e7360 --- /dev/null +++ b/web_drop_target/views/templates.xml @@ -0,0 +1,12 @@ + + + + + +