[ADD] web_editor: full screen image preview on dblclick (html field)

When double-clicking on an image in an html field, display it "full
screen" (similar to clicking on an image in the attachments list). This
replaces the current behavior (which is to open the media selector to
replace the current image - the Replace action remains available in the
toolbar).

In the toolbar for images, display an icon that users can click on to
open the full screen preview as well.

task-3109331

closes odoo/odoo#119625

Related: odoo/enterprise#40450
Signed-off-by: David Monjoie (dmo) <dmo@odoo.com>
This commit is contained in:
Antoine Guenet
2023-05-23 16:31:21 +02:00
parent 995bf2d883
commit 8ca115b3b9
3 changed files with 73 additions and 22 deletions
@@ -235,6 +235,7 @@ export class HtmlField extends Component {
value: this.props.record.data[this.props.name],
autostart: false,
onAttachmentChange: this._onAttachmentChange.bind(this),
onDblClickEditableMedia: this._onDblClickEditableMedia.bind(this),
onWysiwygBlur: this._onWysiwygBlur.bind(this),
...wysiwygOptions,
...dynamicPlaceholderOptions,
@@ -570,6 +571,12 @@ export class HtmlField extends Component {
},
});
}
_onDblClickEditableMedia(ev) {
const el = ev.target;
if (el.nodeName === 'IMG' && el.src) {
this.wysiwyg.showImageFullscreen(el.src);
}
}
_onWysiwygBlur() {
this.commitChanges();
}
@@ -25,6 +25,8 @@ import { PeerToPeer } from "@web_editor/js/wysiwyg/PeerToPeer";
import { uniqueId } from "@web/core/utils/functions";
import { groupBy } from "@web/core/utils/arrays";
import { debounce } from "@web/core/utils/timing";
import { registry } from "@web/core/registry";
import { FileViewer } from "@web/core/file_viewer/file_viewer";
var _t = core._t;
const QWeb = core.qweb;
@@ -68,6 +70,8 @@ const PTP_CLIENT_DISCONNECTED_STATES = [
// this is a local cache for ice server descriptions
let ICE_SERVERS = null;
let fileViewerId = 0;
const Wysiwyg = Widget.extend({
defaultOptions: {
lang: 'odoo',
@@ -306,29 +310,17 @@ const Wysiwyg = Widget.extend({
this.$editable.on('click', '.o_image, .media_iframe_video', e => e.preventDefault());
this.showTooltip = true;
this.$editable.on('dblclick', mediaSelector, function () {
if (this.isContentEditable || (this.parentElement && this.parentElement.isContentEditable)) {
self.showTooltip = false;
this.$editable.on('dblclick', mediaSelector, ev => {
const target = ev.target;
if (target.isContentEditable || (target.parentElement && target.parentElement.isContentEditable)) {
this.showTooltip = false;
const selection = self.odooEditor.document.getSelection();
const anchorNode = selection.anchorNode;
if (isProtected(anchorNode)) {
return;
}
const $el = $(this);
let params = {node: this};
$el.selectElement();
if (!$el.parent().hasClass('o_stars')) {
// Waiting for all the options to be initialized before
// opening the media dialog and only if the media has not
// been deleted in the meantime.
self.waitForEmptyMutexAction().then(() => {
if ($el[0].parentElement) {
self.openMediaDialog(params);
}
});
if (!isProtected(this.odooEditor.document.getSelection().anchorNode)) {
if (this.options.onDblClickEditableMedia && target.nodeName === 'IMG' && target.src) {
this.options.onDblClickEditableMedia(ev);
} else {
this._onDblClickEditableMedia(ev);
}
}
}
});
@@ -1365,6 +1357,33 @@ const Wysiwyg = Widget.extend({
this.linkTools = undefined;
}
},
/**
* Take an image's URL and display it in a fullscreen viewer.
*
* @todo should use `useFileViewer` instead once Wysiwyg becomes an Owl Component.
* @param {string} url
*/
showImageFullscreen(url) {
const viewerId = `web.file_viewer${fileViewerId++}`;
registry.category("main_components").add(viewerId, {
Component: FileViewer,
props: {
files: [{
isImage: true,
isViewable: true,
displayName: url,
defaultSource: url,
downloadUrl: url,
}],
startIndex: 0,
close: () => {
registry.category('main_components').remove(viewerId);
},
},
});
this.odooEditor.document.getSelection()?.removeAllRanges();
this.odooEditor.editable.blur();
},
/**
* Open the media dialog.
*
@@ -1535,6 +1554,12 @@ const Wysiwyg = Widget.extend({
if (!options.snippets) {
$toolbar.find('#justify, #media-insert').remove();
}
$toolbar.find('#image-fullscreen').click(() => {
if (!this.lastMediaClicked?.src) {
return;
}
this.showImageFullscreen(this.lastMediaClicked.src);
})
$toolbar.find('#media-insert, #media-replace, #media-description').click(openTools);
$toolbar.find('#create-link').click(openTools);
$toolbar.find('#image-shape div, #fa-spin').click(e => {
@@ -1877,6 +1902,7 @@ const Wysiwyg = Widget.extend({
const isInMedia = $target.is(mediaSelector) && !$target.parent().hasClass('o_stars') && e.target &&
(e.target.isContentEditable || (e.target.parentElement && e.target.parentElement.isContentEditable));
this.toolbar.$el.find([
'#image-preview',
'#image-shape',
'#image-width',
'#image-padding',
@@ -2471,6 +2497,20 @@ const Wysiwyg = Widget.extend({
this.options.onAttachmentChange(attachment);
}
},
_onDblClickEditableMedia(ev) {
const $el = $(ev.target);
$el.selectElement();
if (!$el.parent().hasClass('o_stars')) {
// Waiting for all the options to be initialized before
// opening the media dialog and only if the media has not
// been deleted in the meantime.
this.waitForEmptyMutexAction().then(() => {
if ($el[0].parentElement) {
this.openMediaDialog({ node: ev.target });
}
});
}
},
_onSelectionChange() {
if (this.odooEditor.autohideToolbar) {
const isVisible = this.linkPopover && this.linkPopover.el.offsetParent;
@@ -146,6 +146,10 @@
</div>
</t>
<div id="image-preview" class="btn-group">
<div id="image-fullscreen" title="Full screen" class="fa fa-search-plus btn editor-ignore"></div>
</div>
<div id="link" class="btn-group">
<div id="media-insert" title="Insert media" class="fa fa-file-image-o fa-fw btn editor-ignore"></div>
<div id="create-link" title="Insert or edit link" class="fa fa-link fa-fw btn editor-ignore"></div>