summaryrefslogtreecommitdiff
path: root/addons/web_unsplash
diff options
context:
space:
mode:
authorSamuel Degueldre <sad@odoo.com>2020-02-11 07:43:23 +0000
committerSamuel Degueldre <sad@odoo.com>2020-03-30 11:06:45 +0000
commit5576a5f84d7d7b539498f15b7e26165b61c52ba5 (patch)
tree6af69fe4f0e0d7b53fa82ec5e7a7ec496bc45693 /addons/web_unsplash
parent634c00a32fe18365039f83d890965fe35de7d23b (diff)
[IMP] web_editor, web_unsplash: improve media-dialog UX
Previously, searching for images in the database and on unsplash was mutually exclusive, and controlled by a dropdown menu next to the search bar. Additionally, the display of db images and unsplash images was very different, making using the search function inconsistent and awkward. This commit merges the unsplash search into the db search. When searching, the first few matches from the database are displayed, then results from unsplash. Additionally, the layout of images has been greatly improved, eliminating almost all padding, using some flex-box magic to allow the images to take up all the available space without being cropped or deformed. The input to add an attachment by URL is now collapsed by default, and clicking on it will expand it to reduce visual clutter. Finally, when not in multi-images mode, the clicked image is immediately used, rather than the user having to click it and then click the add button, or having to double click, which was not obvious. task-2091417 closes odoo/odoo#45174
Diffstat (limited to 'addons/web_unsplash')
-rw-r--r--addons/web_unsplash/static/src/js/unsplash_image_widget.js223
-rw-r--r--addons/web_unsplash/static/src/scss/unsplash.scss62
-rw-r--r--addons/web_unsplash/static/src/xml/unsplash_image_widget.xml44
3 files changed, 124 insertions, 205 deletions
diff --git a/addons/web_unsplash/static/src/js/unsplash_image_widget.js b/addons/web_unsplash/static/src/js/unsplash_image_widget.js
index 86d299c46a6..35a9771c838 100644
--- a/addons/web_unsplash/static/src/js/unsplash_image_widget.js
+++ b/addons/web_unsplash/static/src/js/unsplash_image_widget.js
@@ -7,17 +7,28 @@ var widgetsMedia = require('wysiwyg.widgets.media');
var unsplashAPI = null;
+// Prevent base class from treating unsplash images like regular attachments
+const originalEvents = widgetsMedia.ImageWidget.prototype.events;
+const clickHandler = originalEvents['click .o_existing_attachment_cell'];
+if (!clickHandler) {
+ throw new Error(`Couldn't find a handler for o_existing_attachment_cell clicks.
+The unsplash image widget needs to prevent this handler from executing on unsplash attachments.`);
+}
+_.extend(originalEvents, {
+ 'click .o_existing_attachment_cell:not(.o_unsplash_attachment_cell)': clickHandler,
+});
+delete originalEvents['click .o_existing_attachment_cell'];
+
widgetsMedia.ImageWidget.include({
xmlDependencies: widgetsMedia.ImageWidget.prototype.xmlDependencies.concat(
['/web_unsplash/static/src/xml/unsplash_image_widget.xml']
),
events: _.extend({}, widgetsMedia.ImageWidget.prototype.events, {
- 'dblclick .unsplash_img_container [data-imgid]': '_onUnsplashImgDblClick',
- 'click .unsplash_img_container [data-imgid]': '_onUnsplashImgClick',
- 'click button.save_unsplash': '_onSaveUnsplash',
- 'click .o_search_an_image, .o_search_from_unsplash': '_onSourceSwitchClick',
- 'keyup .o_we_search, .o_search_an_image, .o_search_from_unsplash': '_onPressKeySearch',
+ 'click .o_unsplash_attachment_cell[data-imgid]': '_onUnsplashImgClick',
+ 'click button.save_unsplash': '_onSaveUnsplashCredentials',
}),
+ // Max number of database attachments to show when searching.
+ maxDbAttachments: 5,
/**
* @override
@@ -26,11 +37,11 @@ widgetsMedia.ImageWidget.include({
this._super.apply(this, arguments);
this._unsplash = {
- isActive: false,
selectedImages: {},
isMaxed: false,
query: false,
error: false,
+ records: [],
};
// TODO improve this
@@ -55,13 +66,6 @@ widgetsMedia.ImageWidget.include({
/**
* @override
*/
- start: function () {
- this.$('.o_we_search_icon').replaceWith(core.qweb.render('web_unsplash.dialog.media.search_icon'));
- return this._super.apply(this, arguments);
- },
- /**
- * @override
- */
destroy: function () {
// TODO See `hack` explained in `init`. This prevent the media dialog destroy
// to destroy unsplashAPI when destroying the children
@@ -76,50 +80,43 @@ widgetsMedia.ImageWidget.include({
/**
* @override
*/
- _save: function () {
- if (!this._unsplash.query) {
- return this._super.apply(this, arguments);
+ _save: async function () {
+ const _super = this._super;
+ if (Object.keys(this._unsplash.selectedImages).length) {
+ this.saved = true;
+ const images = await this._rpc({
+ route: '/web_unsplash/attachment/add',
+ params: {
+ unsplashurls: this._unsplash.selectedImages,
+ res_model: this.options.res_model,
+ res_id: this.options.res_id,
+ query: this._unsplash.query,
+ },
+ });
+ this.attachments.push(...images);
+ this.selectedAttachments.push(...images);
}
- var self = this;
- var args = arguments;
- var _super = this._super;
- return this._rpc({
- route: '/web_unsplash/attachment/add',
- params: {
- unsplashurls: self._unsplash.selectedImages,
- res_model: self.options.res_model,
- res_id: self.options.res_id,
- query: self._unsplash.query,
- }
- }).then(function (images) {
- self.attachments = images;
- self.selectedAttachments = images;
- return _super.apply(self, args);
- });
+ return _super.apply(this, arguments);
},
/**
* @override
*/
- search: function (needle, noRender) {
+ search: async function (needle) {
var self = this;
- if (!this._unsplash.isActive) {
- return this._super.apply(this, arguments);
- }
+ await this._super(needle, true);
this._unsplash.query = needle;
+ if (!needle) {
+ return;
+ }
- var always = function () {
- if (!noRender) {
- self._renderImages();
- }
- };
- return this.unsplashAPI.getImages(needle, this.numberOfAttachmentsToDisplay).then(function (res) {
+ await this.unsplashAPI.getImages(needle, this.numberOfAttachmentsToDisplay).then(function (res) {
self._unsplash.isMaxed = res.isMaxed;
self._unsplash.records = res.images;
self._unsplash.error = false;
}, function (err) {
self._unsplash.error = err;
- }).then(always).guardedCatch(always);
+ });
},
//--------------------------------------------------------------------------
@@ -130,60 +127,68 @@ widgetsMedia.ImageWidget.include({
* @override
*/
_highlightSelected: function () {
- var self = this;
- if (!this._unsplash.isActive) {
- return this._super.apply(this, arguments);
- }
+ this._super.apply(this, arguments);
- this.$('.o_unsplash_img_cell.o_selected').removeClass('o_selected');
- var $select = this.$('.o_unsplash_img_cell [data-imgid]').filter(function () {
- return $(this).data('imgid') in self._unsplash.selectedImages;
- });
- $select.closest('.o_unsplash_img_cell').addClass('o_selected');
+ const $select = this.$('.o_unsplash_attachment_cell[data-imgid]').filter((i, el) => {
+ return $(el).data('imgid') in this._unsplash.selectedImages;
+ }).addClass('o_we_attachment_selected');
return $select;
},
/**
* @private
*/
_loadMoreImages: function (forceSearch) {
- if (!this._unsplash.isActive) {
+ if (!this.$('.o_we_search').val()) {
return this._super(forceSearch);
}
this.numberOfAttachmentsToDisplay += 10;
- this.search(this.$('.o_we_search').val() || '');
+ this.search(this.$('.o_we_search').val()).then(() => this._renderThumbnails());
},
/**
* @override
*/
- _renderImages: function () {
- if (!this._unsplash.isActive) {
- return this._super.apply(this, arguments);
- }
-
- if (this._unsplash.error) {
- this.$('.unsplash_img_container').html(
+ _renderThumbnails: function () {
+ this._super(...arguments);
+ if (this._unsplash.query && this._unsplash.error) {
+ this.$('.unsplash_error').html(
core.qweb.render('web_unsplash.dialog.error.content', {
status: this._unsplash.error,
})
);
return;
}
+ this.$('.unsplash_error').empty();
- this.$('.unsplash_img_container').html(core.qweb.render('web_unsplash.dialog.image.content', {records: this._unsplash.records}));
- this._highlightSelected();
-
- this.$('.o_load_more').toggleClass('d-none', !!this._unsplash.error || this._unsplash.isMaxed);
- this.$('.o_load_done_msg').toggleClass('d-none', !!this._unsplash.error || !this._unsplash.isMaxed);
+ if (this._unsplash.query && !this._unsplash.isMaxed) {
+ this.$('.o_load_more').removeClass('d-none');
+ this.$('.o_load_done_msg').addClass('d-none');
+ }
},
/**
- * @private
- * @param {boolean} show
+ * @override
*/
- _toggleUnsplashContainer: function (show) {
- this._unsplash.isActive = show;
- this.$('.o_we_existing_attachments').toggleClass('d-none', show);
- this.$('.unsplash_img_container').toggleClass('d-none', !show);
- this.$('.o_we_search_icon > span').text(show ? "Unsplash" : "");
+ _renderExisting: function (attachments) {
+ if (this._unsplash.records.length) {
+ attachments.splice(this.maxDbAttachments);
+ }
+ return this._super(attachments.concat(this._unsplash.records.map(record => {
+ const url = new URL(record.urls.regular);
+ // In small windows, row height could get quite a bit larger than the min, so we keep some leeway.
+ url.searchParams.set('h', 2 * this.MIN_ROW_HEIGHT);
+ url.searchParams.delete('w');
+ return Object.assign({}, record, {
+ url: url.toString(),
+ });
+ })));
+ },
+ /**
+ * @override
+ */
+ _selectAttachement: function (attachment, save) {
+ if (!this.options.multiImages) {
+ this._unsplash.selectedImages = {};
+ }
+ this._super(...arguments);
},
//--------------------------------------------------------------------------
@@ -193,7 +198,7 @@ widgetsMedia.ImageWidget.include({
/**
* @private
*/
- _onSaveUnsplash: function () {
+ _onSaveUnsplashCredentials: function () {
var self = this;
var key = this.$('#accessKeyInput').val().trim();
var appId = this.$('#appIdInput').val().trim();
@@ -202,19 +207,14 @@ widgetsMedia.ImageWidget.include({
this.$('#appIdInput').toggleClass('is-invalid', !appId);
if (key && appId) {
- var params = {
- 'key': key,
- 'appId': appId,
- };
-
if (!this.$el.find('.is-invalid').length) {
this._rpc({
route: '/web_unsplash/save_unsplash',
- params: params,
+ params: {key: key, appId: appId},
}).then(function () {
self.unsplashAPI.clientId = key;
self._unsplash.error = false;
- self.search(self._unsplash.query);
+ self.search(self._unsplash.query).then(() => self._renderThumbnails());
});
}
}
@@ -223,65 +223,26 @@ widgetsMedia.ImageWidget.include({
* @private
*/
_onUnsplashImgClick: function (ev) {
- var imgid = $(ev.currentTarget).data('imgid');
- var url = $(ev.currentTarget).data('url');
- var downloadURL = $(ev.currentTarget).data('download-url');
+ if (this.saved) {
+ // already saved, probably a double click. Ignore.
+ return;
+ }
+ const {imgid, url, downloadUrl} = ev.currentTarget.dataset;
if (!this.options.multiImages) {
this._unsplash.selectedImages = {};
+ this.selectedAttachments = [];
}
if (imgid in this._unsplash.selectedImages) {
delete this._unsplash.selectedImages[imgid];
} else {
- this._unsplash.selectedImages[imgid] = {url: url, download_url: downloadURL};
+ const _1920Url = new URL(url);
+ _1920Url.searchParams.set('w', '1920');
+ this._unsplash.selectedImages[imgid] = {url: _1920Url.href, download_url: downloadUrl};
}
this._highlightSelected();
- },
- /**
- * @private
- */
- _onUnsplashImgDblClick: function (ev) {
- this.trigger_up('save_request');
- },
- /**
- * @private
- * @param {Event} ev
- */
- _onSourceSwitchClick: function (ev) {
- this._toggleUnsplashContainer(!$(ev.currentTarget).is('.o_search_an_image'));
- this.search(this.$('.o_we_search').val() || '');
- },
- /**
- * @private
- */
- _onPressKeySearch: function (ev) {
- switch (ev.which) {
- case $.ui.keyCode.UP:
- this.$('.o_search_an_image').focus();
- break;
- case $.ui.keyCode.DOWN:
- this.$('.o_search_from_unsplash').focus();
- break;
- default:
- return;
- }
- ev.preventDefault();
- },
- /**
- * @override
- */
- _onSearchInput: function (ev) {
- var $input = $(ev.currentTarget);
- var inputValue = $input.val();
- this.$('.o_search_value_input').text(inputValue);
-
- var $icon = this.$('.o_we_search_icon');
- if ($icon.parent().is('.show')) {
- $icon.dropdown('update');
- } else if (!this.hasSearched) {
- $icon.dropdown('toggle');
- $input.focus();
+ if (!this.options.multiImages) {
+ this.trigger_up('save_request');
}
- this._super.apply(this, arguments);
},
});
});
diff --git a/addons/web_unsplash/static/src/scss/unsplash.scss b/addons/web_unsplash/static/src/scss/unsplash.scss
index 39b8bdde40c..340b21581ec 100644
--- a/addons/web_unsplash/static/src/scss/unsplash.scss
+++ b/addons/web_unsplash/static/src/scss/unsplash.scss
@@ -1,46 +1,22 @@
-.unsplash_img_container {
- $o-border-image: 2px;
- .o_unsplash_img_cell {
- .o_image {
- cursor: pointer;
- transition: opacity 400ms;
-
- &.o_webimage {
- width: 100%;
- height: auto;
- background-image: none;
-
- img {
- max-height: 150px;
- margin: 0 auto;
- border: $o-border-image solid transparent;
- }
- }
-
- }
- &.o_selected .o_image img {
- border-color: $o-brand-primary;
- box-shadow: 0px 0px 2px 2px $o-brand-secondary;
- }
- .author_name {
- font-size: 11px;
- }
- }
-
- .not-found {
- font-size: 25px;
- padding: 30px 0;
- }
-
- .unsplash_error {
- padding: 30px 0;
- .access_key_box {
- padding: 9px;
- background-color: #fcfcfc;
- border: 1px solid #ededee;
- input {
- min-width: 300px;
- }
+.o_we_unplash_author {
+ font-size: 11px;
+ bottom: 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 100%;
+ position: absolute;
+ text-align: center;
+ background-color: #FFFB;
+}
+.unsplash_error {
+ padding: 30px 0;
+ .access_key_box {
+ padding: 9px;
+ background-color: #fcfcfc;
+ border: 1px solid #ededee;
+ input {
+ min-width: 300px;
}
}
}
diff --git a/addons/web_unsplash/static/src/xml/unsplash_image_widget.xml b/addons/web_unsplash/static/src/xml/unsplash_image_widget.xml
index 5610e977b9b..aeb6edf8094 100644
--- a/addons/web_unsplash/static/src/xml/unsplash_image_widget.xml
+++ b/addons/web_unsplash/static/src/xml/unsplash_image_widget.xml
@@ -3,42 +3,24 @@
<t t-extend="wysiwyg.widgets.file">
<t t-jquery=".o_we_existing_attachments" t-operation="after">
- <div class="unsplash_img_container"></div>
+ <div class="unsplash_error"></div>
</t>
</t>
-
-<t t-name="web_unsplash.dialog.media.search_icon" t-extend="">
- <div class="dropdown">
- <button type="button" class="btn btn-primary dropdown-toggle o_we_search_icon" data-toggle="dropdown">
- <i class="fa fa-search"/>
- <span/>
- </button>
- <div class="dropdown-menu dropdown-menu-right">
- <button class="dropdown-item o_search_an_image" type="button">
- Search among my images for:
- <div class="font-weight-bold o_search_value_input"></div>
- </button>
- <button class="dropdown-item o_search_from_unsplash" type="button">
- Search from Unsplash for:
- <div class="font-weight-bold o_search_value_input"></div>
- </button>
- </div>
- </div>
+<t t-extend="wysiwyg.widgets.image.existing.attachments">
+ <t t-jquery="t[t-call]" t-operation="replace">
+ <t t-if="typeof(attachment.id) === 'number'">
+ <t t-call="wysiwyg.widgets.image.existing.attachment"/>
+ </t>
+ <t t-else="">
+ <t t-call="web_unsplash.dialog.image.content"/>
+ </t>
+ </t>
</t>
<t t-name="web_unsplash.dialog.image.content">
- <div class="row mt16">
- <div class="col-sm-2 o_unsplash_img_cell text-center" t-as="img" t-foreach="records">
- <div class="o_image o_webimage text-center" t-att-data-imgid="img.id" t-att-data-url="img.urls.regular" t-att-data-download-url="img.links.download_location">
- <img class="img-fluid" t-att-src="img.urls.thumb"/>
- </div>
- <div class="author_name"><a t-att-href="img.user.links.html" target="_blank"><t t-esc="img.user.name"/></a></div>
- </div>
- </div>
- <div class="row mt16" t-if="records.length == 0">
- <div class="m-auto text-muted not-found">
- <i class="fa fa-camera mr8"/> Photos not found
- </div>
+ <div class="o_existing_attachment_cell o_unsplash_attachment_cell position-relative align-items-center justify-content-center bg-light" t-att-data-imgid="attachment.id" t-att-data-id="attachment.id" t-att-data-url="attachment.urls.regular" t-att-data-download-url="attachment.links.download_location">
+ <img class="img img-fluid o_we_attachment_highlight" t-att-src="attachment.url" t-att-alt="attachment.alt_description" style="max-height: 100%;"/>
+ <a class="o_we_unplash_author" t-att-href="attachment.user.links.html" target="_blank" t-esc="attachment.user.name" t-att-title="attachment.user.name"/>
</div>
</t>