[IMP] web_editor: scroll smoothly when dragging snippets
Part of https://github.com/odoo/odoo/pull/51236 task-2176020 closes odoo/odoo#51236 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
committed by
qsm-odoo
parent
0d06773f14
commit
ab72fe6c46
@@ -2,6 +2,7 @@ odoo.define('web_editor.snippet.editor', function (require) {
|
||||
'use strict';
|
||||
|
||||
var concurrency = require('web.concurrency');
|
||||
var Class = require('web.Class');
|
||||
var core = require('web.core');
|
||||
var Dialog = require('web.Dialog');
|
||||
var dom = require('web.dom');
|
||||
@@ -18,6 +19,169 @@ var globalSelector = {
|
||||
is: () => false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that provides smooth scroll behaviour on drag.
|
||||
*
|
||||
* Do not forget to call unsetDraggable to ensure proper resource clearing.
|
||||
* @see {@link unsetDraggable}.
|
||||
*/
|
||||
const SmoothOnDragComponent = Class.extend({
|
||||
/**
|
||||
* @constructor
|
||||
* @param {jQuery} $element The element the smooth drag has to be set on
|
||||
* @param {Object} jQueryDraggableOptions The configuration to be passed to
|
||||
* the jQuery draggable function
|
||||
*/
|
||||
init($element, jQueryDraggableOptions) {
|
||||
this.$element = $element;
|
||||
this.$scrollTarget = $('html');
|
||||
this.autoScrollHandler = null;
|
||||
this.cursorAt = jQueryDraggableOptions.cursorAt || {left: 0, top: 0};
|
||||
this.draggableOffset = 0;
|
||||
this.mainNavBarHeight = $('.o_main_navbar').innerHeight();
|
||||
this.scrollOffsetThreshold = 150;
|
||||
this.scrollStep = 20;
|
||||
this.scrollStepDirection = 1;
|
||||
this.scrollStepDirectionEnum = {up: -1, down: 1};
|
||||
this.scrollDecelerator = 0;
|
||||
this.scrollTimer = 5;
|
||||
this.visibleBottomOffset = 0;
|
||||
this.visibleTopOffset = 0;
|
||||
|
||||
jQueryDraggableOptions.scroll = false;
|
||||
const draggableOptions = Object.assign({}, jQueryDraggableOptions, {
|
||||
start: (ev, ui) => this._onSmoothDragStart(ev, ui, jQueryDraggableOptions.start),
|
||||
drag: (ev, ui) => this._onSmoothDrag(ev, ui, jQueryDraggableOptions.drag),
|
||||
stop: (ev, ui) => this._onSmoothDragStop(ev, ui, jQueryDraggableOptions.stop),
|
||||
});
|
||||
this.$element.draggable(draggableOptions);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Ensures correct clearing of resources.
|
||||
*/
|
||||
unsetDraggable() {
|
||||
this._stopSmoothScroll();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Starts the scroll process using the options.
|
||||
* The options will be updated dynamically when the handler _onSmoothDrag
|
||||
* will be called. The interval will be cleared when the handler
|
||||
* _onSmoothDragStop will be called.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_startSmoothScroll() {
|
||||
this._stopSmoothScroll();
|
||||
this.autoScrollHandler = setInterval(
|
||||
() => {
|
||||
this.$scrollTarget.scrollTop(
|
||||
this.$scrollTarget.scrollTop() +
|
||||
this.scrollStepDirection *
|
||||
this.scrollStep *
|
||||
(1 - this.scrollDecelerator)
|
||||
);
|
||||
},
|
||||
this.scrollTimer
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Stops the scroll process if any is running.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_stopSmoothScroll() {
|
||||
clearInterval(this.autoScrollHandler);
|
||||
},
|
||||
/**
|
||||
* Updates the options depending on the offset position of the draggable
|
||||
* helper. In the same time options are used by an interval to trigger
|
||||
* scroll behaviour.
|
||||
* @see {@link _startSmoothScroll} for interval implementation details.
|
||||
*
|
||||
* @private
|
||||
* @param {number} dragTopOffset The offset position of the draggable helper
|
||||
*/
|
||||
_updatePositionOptions(dragTopOffset) {
|
||||
this.draggableOffset = this.cursorAt.top + dragTopOffset;
|
||||
this.visibleBottomOffset = this.$scrollTarget.scrollTop() +
|
||||
this.$scrollTarget.get(0).clientHeight - this.draggableOffset;
|
||||
this.visibleTopOffset = this.draggableOffset - this.$scrollTarget.scrollTop();
|
||||
if (this.visibleTopOffset <= this.scrollOffsetThreshold + this.mainNavBarHeight) {
|
||||
this.scrollDecelerator = this.visibleTopOffset /
|
||||
(this.scrollOffsetThreshold + this.mainNavBarHeight);
|
||||
this.scrollStepDirection = this.scrollStepDirectionEnum.up;
|
||||
} else if (this.visibleBottomOffset <= this.scrollOffsetThreshold) {
|
||||
this.scrollDecelerator = this.visibleBottomOffset /
|
||||
this.scrollOffsetThreshold;
|
||||
this.scrollStepDirection = this.scrollStepDirectionEnum.down;
|
||||
} else {
|
||||
this.scrollDecelerator = 1;
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Called when dragging the element.
|
||||
* Updates the position options and call the provided callback if any.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} ev The jQuery drag handler event parameter.
|
||||
* @param {Object} ui The jQuery drag handler ui parameter.
|
||||
* @param {Function} onDragCallback The jQuery drag callback.
|
||||
*/
|
||||
_onSmoothDrag(ev, ui, onDragCallback) {
|
||||
this._updatePositionOptions(ui.offset.top);
|
||||
if (typeof onDragCallback === 'function') {
|
||||
onDragCallback.call(ui.helper, ev, ui);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Called when starting to drag the element.
|
||||
* Updates the position params, starts smooth scrolling process and call the
|
||||
* provided callback if any.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} ev The jQuery drag handler event parameter.
|
||||
* @param {Object} ui The jQuery drag handler ui parameter.
|
||||
* @param {Function} onDragStartCallBack The jQuery drag callback.
|
||||
*/
|
||||
_onSmoothDragStart(ev, ui, onDragStartCallBack) {
|
||||
this._updatePositionOptions(ui.offset.top);
|
||||
this._startSmoothScroll();
|
||||
if (typeof onDragStartCallBack === 'function') {
|
||||
onDragStartCallBack.call(ui.helper, ev, ui);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Called when stopping to drag the element.
|
||||
* Stops the smooth scrolling process and call the provided callback if any.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} ev The jQuery drag handler event parameter.
|
||||
* @param {Object} ui The jQuery drag handler ui parameter.
|
||||
* @param {Function} onDragEndCallBack The jQuery drag callback.
|
||||
*/
|
||||
_onSmoothDragStop(ev, ui, onDragEndCallBack) {
|
||||
this._stopSmoothScroll();
|
||||
if (typeof onDragEndCallBack === 'function') {
|
||||
onDragEndCallBack.call(ui.helper, ev, ui);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Management of the overlay and option list for a snippet.
|
||||
*/
|
||||
@@ -71,20 +235,21 @@ var SnippetEditor = Widget.extend({
|
||||
// Initialize move/clone/remove buttons
|
||||
if (this.isTargetMovable) {
|
||||
this.dropped = false;
|
||||
this.$el.draggable({
|
||||
greedy: true,
|
||||
this.draggableComponent = new SmoothOnDragComponent(this.$el, {
|
||||
appendTo: this.$body,
|
||||
cursor: 'move',
|
||||
handle: '.o_move_handle',
|
||||
cursorAt: {
|
||||
left: 10,
|
||||
top: 10
|
||||
},
|
||||
greedy: true,
|
||||
handle: '.o_move_handle',
|
||||
helper: () => {
|
||||
var $clone = this.$el.clone().css({width: '24px', height: '24px', border: 0});
|
||||
$clone.appendTo(this.$body).removeClass('d-none');
|
||||
return $clone;
|
||||
},
|
||||
scroll: false,
|
||||
start: this._onDragAndDropStart.bind(this),
|
||||
stop: (...args) => {
|
||||
// Delay our stop handler so that some summernote handlers
|
||||
@@ -139,6 +304,9 @@ var SnippetEditor = Widget.extend({
|
||||
*/
|
||||
destroy: function () {
|
||||
this._super(...arguments);
|
||||
if (this.draggableComponent) {
|
||||
this.draggableComponent.unsetDraggable();
|
||||
}
|
||||
this.$target.removeData('snippet-editor');
|
||||
this.$target.off('.snippet_editor');
|
||||
},
|
||||
@@ -1018,6 +1186,9 @@ var SnippetsMenu = Widget.extend({
|
||||
*/
|
||||
destroy: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.draggableComponent) {
|
||||
this.draggableComponent.unsetDraggable();
|
||||
}
|
||||
if (this.$window) {
|
||||
this.$snippetEditorArea.remove();
|
||||
this.$window.off('.snippets_menu');
|
||||
@@ -1758,17 +1929,20 @@ var SnippetsMenu = Widget.extend({
|
||||
var $tumb = $snippets.find('.oe_snippet_thumbnail_img:first');
|
||||
var $toInsert, dropped, $snippet;
|
||||
|
||||
$snippets.draggable({
|
||||
greedy: true,
|
||||
helper: function () {
|
||||
const dragSnip = this.cloneNode(true);
|
||||
dragSnip.querySelectorAll('.o_delete_btn, .o_image_ribbon').forEach(el => el.remove());
|
||||
return dragSnip;
|
||||
},
|
||||
this.draggableComponent = new SmoothOnDragComponent($snippets, {
|
||||
appendTo: this.$body,
|
||||
cursor: 'move',
|
||||
distance: 0,
|
||||
greedy: true,
|
||||
handle: '.oe_snippet_thumbnail',
|
||||
helper: function () {
|
||||
const dragSnip = this.cloneNode(true);
|
||||
dragSnip.querySelectorAll('.o_delete_btn, .o_image_ribbon').forEach(
|
||||
el => el.remove()
|
||||
);
|
||||
return dragSnip;
|
||||
},
|
||||
scroll: false,
|
||||
start: function () {
|
||||
dropped = false;
|
||||
$snippet = $(this);
|
||||
@@ -2265,6 +2439,7 @@ var SnippetsMenu = Widget.extend({
|
||||
return {
|
||||
Class: SnippetsMenu,
|
||||
Editor: SnippetEditor,
|
||||
SmoothOnDragComponent: SmoothOnDragComponent,
|
||||
globalSelector: globalSelector,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user