diff --git a/addons/hr/models/hr_employee.py b/addons/hr/models/hr_employee.py
index 8746e3ecc06..a197d9dc7a4 100644
--- a/addons/hr/models/hr_employee.py
+++ b/addons/hr/models/hr_employee.py
@@ -92,18 +92,11 @@ class HrEmployeePrivate(models.Model):
# image: all image fields are base64 encoded and PIL-supported
image = fields.Binary(
- "Photo", default=_default_image,
- help="This field holds the image used as photo for the employee, limited to 1024x1024px.")
+ "Photo", default=_default_image)
image_medium = fields.Binary(
- "Medium-sized photo",
- help="Medium-sized photo of the employee. It is automatically "
- "resized as a 128x128px image, with aspect ratio preserved. "
- "Use this field in form views or some kanban views.")
+ "Medium-sized photo")
image_small = fields.Binary(
- "Small-sized photo",
- help="Small-sized photo of the employee. It is automatically "
- "resized as a 64x64px image, with aspect ratio preserved. "
- "Use this field anywhere a small image is required.")
+ "Small-sized photo")
phone = fields.Char(related='address_home_id.phone', related_sudo=False, string="Private Phone", groups="hr.group_hr_user")
# employee in company
parent_id = fields.Many2one('hr.employee', 'Manager')
diff --git a/addons/hr/views/hr_employee_public_views.xml b/addons/hr/views/hr_employee_public_views.xml
index a97c63c3feb..d6a97209f11 100644
--- a/addons/hr/views/hr_employee_public_views.xml
+++ b/addons/hr/views/hr_employee_public_views.xml
@@ -23,7 +23,7 @@
-
+
@@ -98,9 +98,9 @@
-
+
-
+
diff --git a/addons/hr/views/hr_employee_views.xml b/addons/hr/views/hr_employee_views.xml
index bb1dcde2d77..97a05228ea2 100644
--- a/addons/hr/views/hr_employee_views.xml
+++ b/addons/hr/views/hr_employee_views.xml
@@ -48,7 +48,7 @@
-
+
@@ -204,9 +204,9 @@
-
+
-
+
diff --git a/addons/hr/views/hr_templates.xml b/addons/hr/views/hr_templates.xml
index 465c98d39e0..99a33ef3185 100644
--- a/addons/hr/views/hr_templates.xml
+++ b/addons/hr/views/hr_templates.xml
@@ -7,5 +7,4 @@
-
diff --git a/addons/hr/views/res_users.xml b/addons/hr/views/res_users.xml
index 7ed9b1001ad..d417b93341b 100644
--- a/addons/hr/views/res_users.xml
+++ b/addons/hr/views/res_users.xml
@@ -40,7 +40,7 @@
-
+
diff --git a/addons/web/static/src/js/fields/basic_fields.js b/addons/web/static/src/js/fields/basic_fields.js
index 14f03a83222..9b78d1e723b 100644
--- a/addons/web/static/src/js/fields/basic_fields.js
+++ b/addons/web/static/src/js/fields/basic_fields.js
@@ -22,6 +22,8 @@ var utils = require('web.utils');
var view_dialogs = require('web.view_dialogs');
var field_utils = require('web.field_utils');
+require("web.zoomodoo");
+
var qweb = core.qweb;
var _t = core._t;
var _lt = core._lt;
@@ -1571,6 +1573,25 @@ var FieldBinaryImage = AbstractFieldBinary.extend({
'i': 'png',
'P': 'svg+xml',
},
+ /**
+ * Returns the image URL from a model.
+ *
+ * @private
+ * @param {string} model model from which to retrieve the image
+ * @param {string} res_id id of the record
+ * @param {string} field name of the image field
+ * @param {string} unique an unique integer for the record, usually __last_update
+ * @returns {string} URL of the image
+ */
+ _getImageUrl: function (model, res_id, field, unique) {
+ return session.url('/web/image', {
+ model: model,
+ id: JSON.stringify(res_id),
+ field: field,
+ // unique forces a reload of the image when the record has been updated
+ unique: field_utils.format.datetime(unique).replace(/[^0-9]/g, ''),
+ });
+ },
_render: function () {
var self = this;
var url = this.placeholder;
@@ -1579,13 +1600,9 @@ var FieldBinaryImage = AbstractFieldBinary.extend({
// Use magic-word technique for detecting image type
url = 'data:image/' + (this.file_type_magic_word[this.value[0]] || 'png') + ';base64,' + this.value;
} else {
- url = session.url('/web/image', {
- model: this.model,
- id: JSON.stringify(this.res_id),
- field: this.nodeOptions.preview_image || this.name,
- // unique forces a reload of the image when the record has been updated
- unique: field_utils.format.datetime(this.recordData.__last_update).replace(/[^0-9]/g, ''),
- });
+ var field = this.nodeOptions.preview_image || this.name;
+ var unique = this.recordData.__last_update;
+ url = this._getImageUrl(this.model, this.res_id, field, unique);
}
}
var $img = $(qweb.render("FieldBinaryImage-img", {widget: this, url: url}));
@@ -1608,6 +1625,62 @@ var FieldBinaryImage = AbstractFieldBinary.extend({
$img.attr('src', self.placeholder);
self.do_warn(_t("Image"), _t("Could not display the selected image."));
});
+
+ return this._super.apply(this, arguments);
+ },
+ /**
+ * Only enable the zoom on image in read-only mode, and if the option is enabled.
+ *
+ * @override
+ * @private
+ */
+ _renderReadonly: function () {
+ this._super.apply(this, arguments);
+
+ if(this.nodeOptions.zoom) {
+ var unique = this.recordData.__last_update;
+ var url = this._getImageUrl(this.model, this.res_id, 'image', unique);
+ var $img;
+
+ if(this.nodeOptions.background)
+ {
+ if('tag' in this.nodeOptions) {
+ this.tagName = this.nodeOptions.tag;
+ }
+
+ if('class' in this.attrs) {
+ this.$el.addClass(this.attrs.class);
+ }
+
+ var urlThumb = this._getImageUrl(this.model, this.res_id, 'image_medium', unique);
+
+ this.$el.empty();
+ $img = this.$el;
+ $img.css('backgroundImage', 'url(' + urlThumb + ')');
+ } else {
+ $img = this.$('img');
+ }
+
+ if(this.recordData.image) {
+ $img.attr('data-zoom', 1);
+ $img.attr('data-zoom-image', url);
+
+ $img.zoomOdoo({
+ event: 'mouseenter',
+ attach: '.o_content',
+ attachToTarget: true,
+ onShow: function () {
+ if(this.$zoom.height() < 256 && this.$zoom.width() < 256) {
+ this.hide();
+ }
+ },
+ beforeAttach: function () {
+ this.$flyout.css({ width: '512px', height: '512px' });
+ },
+ preventClicks: this.nodeOptions.preventClicks,
+ });
+ }
+ }
},
});
diff --git a/addons/website/static/src/js/content/zoomodoo.js b/addons/web/static/src/js/libs/zoomodoo.js
similarity index 76%
rename from addons/website/static/src/js/content/zoomodoo.js
rename to addons/web/static/src/js/libs/zoomodoo.js
index 1e85e46062f..cbe0511102a 100644
--- a/addons/website/static/src/js/content/zoomodoo.js
+++ b/addons/web/static/src/js/libs/zoomodoo.js
@@ -1,4 +1,4 @@
-odoo.define('website.content.zoomodoo', function (require) {
+odoo.define('web.zoomodoo', function (require) {
'use strict';
/**
@@ -48,7 +48,10 @@ var defaults = {
onHide: $.noop,
// Callback function to execute when the cursor is moved while over the image.
- onMove: $.noop
+ onMove: $.noop,
+
+ // Callback function to execute when the flyout is attached to the target.
+ beforeAttach: $.noop
};
@@ -87,6 +90,9 @@ ZoomOdoo.prototype._init = function () {
if (this.opts.preventClicks) {
this.$target.on('click.zoomodoo', function (e) { e.preventDefault(); });
+ } else {
+ var self = this;
+ this.$target.on('click.zoomodoo', function () { self.hide(); self.$target.unbind(); });
}
}
};
@@ -114,8 +120,51 @@ ZoomOdoo.prototype.show = function (e, testMouseOver) {
if (this.opts.attach !== undefined && this.$target.parents(this.opts.attach).length) {
$attach = this.$target.parents(this.opts.attach);
}
+
+ // Prevents having multiple zoom flyouts
+ $attach.parent().find('.zoomodoo-flyout').remove();
+ this.$flyout.removeAttr('style');
$attach.parent().append(this.$flyout);
+ if (this.opts.attachToTarget) {
+ this.opts.beforeAttach.call(this);
+
+ if(this.$zoom.height() < this.$flyout.height()) {
+ this.$flyout.css('height', this.$zoom.height() + 'px');
+ }
+ if(this.$zoom.width() < this.$flyout.width()) {
+ this.$flyout.css('width', this.$zoom.width() + 'px');
+ }
+
+ var offset = this.$target.offset();
+ var left = offset.left - this.$flyout.width();
+ var top = offset.top;
+
+ // Position the zoom on the right side of the target
+ // if there's not enough room on the left
+ if(left < 0) {
+ if(offset.left < ($(document).width() / 2)) {
+ left = offset.left + this.$target.width();
+ } else {
+ left = 0;
+ }
+ }
+
+ // Prevents the flyout to overflow
+ if(left + this.$flyout.width() > $(document).width()) {
+ this.$flyout.css('width', $(document).width() - left + 'px');
+ } else if(left === 0) { // Limit the max width if displayed on the left
+ this.$flyout.css('width', offset.left + 'px');
+ }
+
+ // Prevents the zoom to be displayed outside the current viewport
+ if((top + this.$flyout.height()) > $(document).height()) {
+ top = $(document).height() - this.$flyout.height();
+ }
+
+ this.$flyout.css('transform', 'translate3d(' + left + 'px, ' + top + 'px, 0px)');
+ }
+
w1 = this.$target.width();
h1 = this.$target.height();
@@ -235,7 +284,7 @@ ZoomOdoo.prototype._move = function (e) {
var xl = Math.ceil(pl * rw);
// Close if outside
- if (xl < 0 || xt < 0 || xl > dw || xt > dh) {
+ if (xl < 0 || xt < 0 || xl > dw || xt > dh || lx > (offset.left + this.$target.width())) {
this.hide();
} else {
var top = xt * -1;
diff --git a/addons/website/static/src/scss/website.zoomodoo.scss b/addons/web/static/src/scss/web.zoomodoo.scss
similarity index 100%
rename from addons/website/static/src/scss/website.zoomodoo.scss
rename to addons/web/static/src/scss/web.zoomodoo.scss
diff --git a/addons/web/views/webclient_templates.xml b/addons/web/views/webclient_templates.xml
index e67005f0eaf..6494eda9622 100644
--- a/addons/web/views/webclient_templates.xml
+++ b/addons/web/views/webclient_templates.xml
@@ -76,6 +76,7 @@
+
@@ -159,6 +160,8 @@
+
+
diff --git a/addons/website/static/src/js/content/website_root.js b/addons/website/static/src/js/content/website_root.js
index 612265124cd..4b61a33d779 100644
--- a/addons/website/static/src/js/content/website_root.js
+++ b/addons/website/static/src/js/content/website_root.js
@@ -4,7 +4,7 @@ odoo.define('website.root', function (require) {
var core = require('web.core');
var Dialog = require('web.Dialog');
var publicRootData = require('web.public.root');
-require('website.content.zoomodoo');
+require("web.zoomodoo");
var _t = core._t;
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index 4dd19528c0d..aa264dd76a7 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -55,7 +55,6 @@
-
@@ -71,7 +70,6 @@
-
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index c4888233e93..85ef87d0e08 100644
--- a/addons/website_sale/static/src/js/website_sale.js
+++ b/addons/website_sale/static/src/js/website_sale.js
@@ -123,7 +123,7 @@ var config = require('web.config');
var concurrency = require('web.concurrency');
var publicWidget = require('web.public.widget');
var VariantMixin = require('sale.VariantMixin');
-require("website.content.zoomodoo");
+require("web.zoomodoo");
var qweb = core.qweb;
diff --git a/odoo/addons/base/models/res_partner.py b/odoo/addons/base/models/res_partner.py
index e14174d65c8..87822b05279 100644
--- a/odoo/addons/base/models/res_partner.py
+++ b/odoo/addons/base/models/res_partner.py
@@ -225,8 +225,7 @@ class Partner(models.Model):
company_name = fields.Char('Company Name')
# image: all image fields are base64 encoded and PIL-supported
- image = fields.Binary("Image",
- help="This field holds the image used as avatar for this contact, limited to 1024x1024px",)
+ image = fields.Binary("Image")
image_medium = fields.Binary("Medium-sized image",
help="Medium-sized image of this contact. It is automatically "\
"resized as a 128x128px image, with aspect ratio preserved. "\
diff --git a/odoo/addons/base/views/res_users_views.xml b/odoo/addons/base/views/res_users_views.xml
index a975f5a33cd..8ea5272348d 100644
--- a/odoo/addons/base/views/res_users_views.xml
+++ b/odoo/addons/base/views/res_users_views.xml
@@ -125,7 +125,7 @@
You are inviting a new user.
-
+