Skip to content
Permalink
Browse files

wip preview and quality selector

  • Loading branch information...
seb-odoo committed Feb 19, 2019
1 parent 11a9f77 commit 7a2bb3ca786ca8658926937536fc5220cc1ea87e
@@ -191,6 +191,8 @@ def _image_to_attachment(self, res_model, res_id, data, name, datas_fname, disab
#------------------------------------------------------
@http.route('/web_editor/attachment/add', type='http', auth='user', methods=['POST'], website=True)
def attach(self, upload=None, url=None, disable_optimization=None, filters=None, **kwargs):
# TODO SEB change this to use upload and get rid of multi (this is handled in the js)

# the upload argument doesn't allow us to access the files if more than
# one file is uploaded, as upload references the first file
# therefore we have to recover the files from the request object
@@ -242,6 +244,21 @@ def attach(self, upload=None, url=None, disable_optimization=None, filters=None,
('Content-Type', 'application/json'),
])

@http.route('/web_editor/image/preview', type='json', auth='user', methods=['POST'], website=True)
def preview_image(self, image_original, quality=80, **kwargs):
# the min quality possible for Pillow is 1
quality = int(quality) or 1
# TODO SEB this will crash with SVG
image = Image.open(io.BytesIO(base64.b64decode(image_original)))
w, h = image.size
if w * h > 42e6: # Nokia Lumia 1020 photo resolution
raise ValueError(
u"Image size excessive, uploaded images must be smaller "
u"than 42 million pixel")
if image.format in ('PNG', 'JPEG'):
data = tools.image_save_for_web(image, quality=quality)
return {'image': tools.image_data_uri(base64.b64encode(data))}

#------------------------------------------------------
# remove attachment (images or link)
#------------------------------------------------------
@@ -5,13 +5,92 @@ var ajax = require('web.ajax');
var core = require('web.core');
var Dialog = require('web.Dialog');
var fonts = require('wysiwyg.fonts');
var utils = require('web.utils');
var Widget = require('web.Widget');
var concurrency = require('web.concurrency');

var QWeb = core.qweb;

var _t = core._t;

var ImagePreviewDialog = Dialog.extend({
template: 'wysiwyg.widgets.image_preview',
xmlDependencies: ['/web_editor/static/src/xml/wysiwyg.xml'],

events: _.extend({}, Dialog.prototype.events, {
'change .js_quality_range': '_onChangeQuality',
}),
/**
* @constructor
*/
init: function (parent, options, file, def) {
this._super(parent, _.extend({}, {
title: _t("Improve your Image"),
buttons: [
{text: _t("Save"), classes: 'btn-primary', close: true, click: this._onSave.bind(this)},
{text: _t("Cancel"), close: true, click: this._onCancel.bind(this)}
],
}, options));

this.filename = file.name;
this.imageOriginal = file.data;
this.defaultQuality = 80;
this.def = def;
},
/**
* @constructor
*/
start: function () {
var defParent = this._super.apply(this, arguments);
this.$previewImage = this.$('.js_preview_image');
this.$qualityRange = this.$('.js_quality_range');
this.$currentQuality = this.$('.js_current_quality');
this.$currentSize = this.$('.js_current_size');
this.$originalSize = this.$('.js_original_size');
var defPreview = this._updatePreview();
return $.when(defParent, defPreview);
},
/**
*
*/
_updatePreview: function () {
var self = this;
var quality = this.$qualityRange.val();
var base64Original = this.imageOriginal.split(',')[1];
return this._rpc({
route: '/web_editor/image/preview',
params: {
'quality': quality,
'image_original': base64Original,
}
}).then(function (res) {
self.$currentQuality.text(quality);
self.$currentSize.text(utils.binaryToBinsize(res.image.split(',')[1]));
self.$originalSize.text(utils.binaryToBinsize(base64Original));
self.$previewImage.attr('src', res.image);
});
},
/**
*
*/
_onChangeQuality: _.debounce(function (ev) {
return this._updatePreview();
}),
/**
*
*/
_onSave: _.debounce(function () {
this.def.resolve(this.filename, this.$currentQuality.val(), this.imageOriginal);
}),
/**
*
*/
_onCancel: _.debounce(function () {
this.def.reject();
}),

});

var MediaWidget = Widget.extend({
xmlDependencies: ['/web_editor/static/src/xml/wysiwyg.xml'],
events: {
@@ -463,55 +542,99 @@ var ImageWidget = MediaWidget.extend({
* @private
*/
_uploadFile: function () {
return this._mutex.exec(this._uploadImageFile.bind(this));
return this._mutex.exec(this._previewImageFiles.bind(this));
},
_uploadImageFile: function () {
/**
* Preview one uploaded image at a time.
*
* @private
*/
_previewImageFiles: function () {
var self = this;
var def = ajax.post('/web_editor/attachment/add', {}, this.$el[0]).then(function (res) {
var attachments = res.attachments;
var error = res.error;

self.$('.well > span').remove();
self.$('.well > div').show();
_.each(attachments, function (record) {
record.src = record.url || _.str.sprintf('/web/image/%s/%s', record.id, encodeURI(record.name)); // Name is added for SEO purposes
record.isDocument = !(/gif|jpe|jpg|png/.test(record.mimetype));
var previewMutex = new concurrency.Mutex();
for (var file of this.$('.o_file_input')[0].files) {
previewMutex.exec(function () {
var defPreview = self._previewImageFile(file);
// start to upload the first attachment
defPreview.then(self._uploadImageFile.bind(self));
// open the next preview while it uploads
return defPreview;
});
if (error || !attachments.length) {
_processFile(null, error || !attachments.length);
}
self.images = attachments;
for (var i = 0; i < attachments.length; i++) {
_processFile(attachments[i], error);
}

if (self.options.onUpload) {
self.options.onUpload(attachments);
}

function _processFile(attachment, error) {
var $button = self.$('.o_upload_image_button');
if (!error) {
$button.addClass('btn-success');
self._toggleImage(attachment, true);
} else {
$button.addClass('btn-danger');
self.$el.addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
self.$el.find('.form-text').text(error);
}
self.$('.o_file_input').val('');
// TODO SEB need to wait for the last upload before resolving here.
return previewMutex.getUnlockedDef();
},
/**
* Open the image preview dialog for the given file.
*
* @private
*/
_previewImageFile: function (file) {
var self = this;
var def = $.Deferred();
var reader = new FileReader();
reader.onload = function (ev) {
new ImagePreviewDialog(self, {}, {
'name': file.name,
'data': ev.target.result,
}, def).open();
};
reader.readAsDataURL(file);
return def;
},
/**
* Create the attachment with the given parameters.
*
* @private
*/
_uploadImageFile: function (filename, quality, data) {
var self = this;
data = data.split(',')[1];
// TODO SEB use json RPC here and get data from the form
return ajax.post('/web_editor/attachment/add', {}, this.$el[0])
.progress(function (ev) {
// TODO SEB make a nice progress bar?
}).then(function (res) {
var attachments = res.attachments;
var error = res.error;

self.$('.well > span').remove();
self.$('.well > div').show();
_.each(attachments, function (record) {
record.src = record.url || _.str.sprintf('/web/image/%s/%s', record.id, encodeURI(record.name)); // Name is added for SEO purposes
record.isDocument = !(/gif|jpe|jpg|png/.test(record.mimetype));
});
if (error || !attachments.length) {
_processFile(null, error || !attachments.length);
}
self.images = attachments;
for (var i = 0; i < attachments.length; i++) {
_processFile(attachments[i], error);
}

if (!self.multiImages) {
self.trigger_up('save_request');
if (self.options.onUpload) {
self.options.onUpload(attachments);
}
}
});

this.$('.o_file_input').val('');
function _processFile(attachment, error) {
var $button = self.$('.o_upload_image_button');
if (!error) {
$button.addClass('btn-success');
self._toggleImage(attachment, true);
} else {
$button.addClass('btn-danger');
self.$el.addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
self.$el.find('.form-text').text(error);
}

return def;
if (!self.multiImages) {
self.trigger_up('save_request');
}
}
});
},


//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
@@ -182,6 +182,29 @@
</div>
</div>
</t>

<t t-name="wysiwyg.widgets.image_preview">
<form>
<div class="form-group">
<label for="filename">Filename</label>
<input type="text" class="form-control" id="filename" name="filename" aria-describedby="filenameHelp" t-att-value="widget.filename"/>
<small id="filenameHelp" class="form-text text-muted">Give a relevant name to your file to optimize search engine results.</small>
</div>

<label for="quality_range">Quality</label>
<input type="range" class="custom-range js_quality_range" id="quality_range" name="quality_range" aria-describedby="qualityHelp" t-att-value="widget.defaultQuality"/>
<small id="qualityHelp" class="form-text text-muted">Reduce the quality as much as possible to increase the speed and performance of your website.</small>

<h3>Preview of your image with the current settings</h3>
<p>
Current Quality: <span class="js_current_quality"></span>,
Current Size: <span class="js_current_size"></span>
Original Size: <span class="js_original_size"></span>
</p>
<img class="img img-fluid js_preview_image" src="" alt="Image Preview"/>
</form>
</t>

<t t-name="wysiwyg.widgets.files.existing.content">
<div class="existing-attachments">
<div class="row mt16 ml-auto" t-as="row" t-foreach="rows">
@@ -141,7 +141,7 @@ def image_resize_and_sharpen(image, size, preserve_aspect_ratio=False, factor=2.
return image


def image_save_for_web(image, fp=None, format=None):
def image_save_for_web(image, fp=None, format=None, quality=80):
"""
Save image optimized for web usage.
@@ -161,7 +161,7 @@ def image_save_for_web(image, fp=None, format=None):
if alpha:
image.putalpha(alpha)
elif image.format == 'JPEG':
opt.update(optimize=True, quality=80)
opt.update(optimize=True, quality=quality)
if fp:
image.save(fp, **opt)
else:

0 comments on commit 7a2bb3c

Please sign in to comment.
You can’t perform that action at this time.