[IMP] *: remove jQueryUI draggable and droppable
*: mass_mailing, test_website, web, web_editor, website, website_event, website_sale This commit removes jQueryUI draggable and droppable component to instead use our own implementation of the feature. This replaces the last use of the lib: the web_editor drag and drop. This takes advantage of the code that was already written for other features of the backend. task-3079246 closes odoo/odoo#136793 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
committed by
qsm-odoo
parent
fae131e44c
commit
7594d71ca8
@@ -23,9 +23,9 @@ registry.category("web_tour.tours").add('mailing_editor', {
|
||||
run: () => {},
|
||||
}, {
|
||||
content: 'drag the "Title" snippet from the design panel and drop it in the editor',
|
||||
trigger: '[name="body_arch"] iframe #email_designer_default_body [name="Title"] .ui-draggable-handle',
|
||||
trigger: '[name="body_arch"] iframe #email_designer_default_body [name="Title"] .oe_snippet_thumbnail',
|
||||
run: function (actions) {
|
||||
actions.drag_and_drop('[name="body_arch"] iframe .o_editable', this.$anchor);
|
||||
actions.drag_and_drop_native('[name="body_arch"] iframe .o_editable', this.$anchor);
|
||||
}
|
||||
}, {
|
||||
content: 'wait for the snippet menu to finish the drop process',
|
||||
|
||||
@@ -50,10 +50,10 @@ registry.category("web_tour.tours").add('mass_mailing_code_view_tour', {
|
||||
content: 'Verify that the dropable zone was not removed',
|
||||
run: () => {},
|
||||
}, {
|
||||
trigger: '[name="body_arch"] iframe #email_designer_default_body [name="Title"] .ui-draggable-handle',
|
||||
trigger: '[name="body_arch"] iframe #email_designer_default_body [name="Title"] .oe_snippet_thumbnail',
|
||||
content: 'Drag the "Title" snippet from the design panel and drop it in the editor',
|
||||
run: function (actions) {
|
||||
actions.drag_and_drop('[name="body_arch"] iframe .o_editable', this.$anchor);
|
||||
actions.drag_and_drop_native('[name="body_arch"] iframe .o_editable', this.$anchor);
|
||||
}
|
||||
}, {
|
||||
trigger: '[name="body_arch"] iframe .o_editable h1',
|
||||
|
||||
@@ -66,13 +66,7 @@ wTourUtils.registerWebsitePreviewTour('test_custom_snippet', {
|
||||
content: "confirm rename",
|
||||
trigger: ".oe_snippet[name='Custom Banner'] we-button.o_we_confirm_btn",
|
||||
},
|
||||
{
|
||||
content: "drop custom snippet",
|
||||
trigger: ".oe_snippet[name='Bruce Banner'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
extra_trigger: "iframe body.editor_enable",
|
||||
moveTrigger: ".oe_drop_zone",
|
||||
run: "drag_and_drop iframe #wrap",
|
||||
},
|
||||
wTourUtils.dragNDrop({ name: "Bruce Banner" }),
|
||||
{
|
||||
content: "ensure banner section exists",
|
||||
trigger: "iframe #wrap section[data-name='Banner']",
|
||||
|
||||
@@ -48,13 +48,13 @@ const setupSteps = [{
|
||||
content: "drop a snippet",
|
||||
trigger: "#oe_snippets .oe_snippet[name='Text - Image'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
moveTrigger: "iframe .oe_drop_zone",
|
||||
run: "drag_and_drop iframe #wrap",
|
||||
run: "drag_and_drop_native iframe #wrap",
|
||||
}, {
|
||||
content: "drop a snippet",
|
||||
trigger: "#oe_snippets .oe_snippet[name='Image Gallery'] .oe_snippet_thumbnail:not(.o_we_already_dragging)",
|
||||
extra_trigger: "body.editor_has_snippets",
|
||||
moveTrigger: ".oe_drop_zone",
|
||||
run: "drag_and_drop iframe #wrap",
|
||||
run: "drag_and_drop_native iframe #wrap",
|
||||
}];
|
||||
|
||||
const formatErrorMsg = "format is not supported. Try with: .gif, .jpe, .jpeg, .jpg, .png, .svg, .webp";
|
||||
|
||||
@@ -23,7 +23,7 @@ wTourUtils.registerWebsitePreviewTour('test_reset_page_view_complete_flow_part1'
|
||||
content: "drop a snippet",
|
||||
trigger: ".oe_snippet:has(.s_cover) .oe_snippet_thumbnail",
|
||||
// id starting by 'oe_structure..' will actually create an inherited view
|
||||
run: "drag_and_drop iframe #oe_structure_test_website_page",
|
||||
run: "drag_and_drop_native iframe #oe_structure_test_website_page",
|
||||
},
|
||||
...wTourUtils.clickOnSave(),
|
||||
// 2. Edit that COW'd view in the HTML editor to break it.
|
||||
|
||||
+2
-6
@@ -1,13 +1,9 @@
|
||||
/*! jQuery UI - v1.12.1 - 2023-10-04
|
||||
/*! jQuery UI - v1.12.1 - 2023-10-10
|
||||
* http://jqueryui.com
|
||||
* Includes: draggable.css, core.css, autocomplete.css, menu.css, theme.css
|
||||
* Includes: core.css, autocomplete.css, menu.css, theme.css
|
||||
* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif
|
||||
* Copyright jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
.ui-draggable-handle {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
/* Layout helpers
|
||||
----------------------------------*/
|
||||
.ui-helper-hidden {
|
||||
|
||||
+2
-1747
File diff suppressed because it is too large
Load Diff
@@ -94,6 +94,7 @@ const DEFAULT_ACCEPTED_PARAMS = {
|
||||
edgeScrolling: [Object, Function],
|
||||
delay: [Number],
|
||||
tolerance: [Number],
|
||||
iframeWindow: [Object, Function],
|
||||
};
|
||||
const DEFAULT_DEFAULT_PARAMS = {
|
||||
elements: `.${DRAGGABLE_CLASS}`,
|
||||
@@ -231,7 +232,7 @@ function makeDOMHelpers(cleanup) {
|
||||
if (!el || !classNames.length) {
|
||||
return;
|
||||
}
|
||||
cleanup.add(saveAttribute(el, "class"));
|
||||
cleanup.add(() => el.classList.remove(...classNames));
|
||||
el.classList.add(...classNames);
|
||||
};
|
||||
|
||||
@@ -552,6 +553,11 @@ export function makeDraggableHook(hookParams) {
|
||||
}
|
||||
|
||||
dom.addClass(document.body, "pe-none", "user-select-none");
|
||||
if (params.iframeWindow) {
|
||||
dom.addClass(params.iframeWindow.body, "pe-none", "user-select-none");
|
||||
}
|
||||
// FIXME: adding pe-none and cursor on the same element makes
|
||||
// no sense as pe-none prevents the cursor to be displayed.
|
||||
if (ctx.cursor) {
|
||||
dom.addStyle(document.body, { cursor: ctx.cursor });
|
||||
}
|
||||
@@ -861,6 +867,12 @@ export function makeDraggableHook(hookParams) {
|
||||
passive: false,
|
||||
noAddedStyle: true,
|
||||
});
|
||||
if (params.iframeWindow) {
|
||||
dom.addListener(params.iframeWindow, "touchmove", safePrevent, {
|
||||
passive: false,
|
||||
noAddedStyle: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -987,14 +999,20 @@ export function makeDraggableHook(hookParams) {
|
||||
},
|
||||
() => [ctx.ref.el]
|
||||
);
|
||||
const addWindowListener = (type, listener, options) => {
|
||||
if (params.iframeWindow) {
|
||||
setupHooks.addListener(params.iframeWindow, type, listener, options);
|
||||
}
|
||||
setupHooks.addListener(window, type, listener, options);
|
||||
};
|
||||
// Other global event listeners.
|
||||
const throttledOnPointerMove = setupHooks.throttle(onPointerMove);
|
||||
setupHooks.addListener(window, "pointermove", throttledOnPointerMove, {
|
||||
addWindowListener("pointermove", throttledOnPointerMove, {
|
||||
passive: false,
|
||||
});
|
||||
setupHooks.addListener(window, "pointerup", onPointerUp);
|
||||
setupHooks.addListener(window, "pointercancel", onPointerCancel);
|
||||
setupHooks.addListener(window, "keydown", onKeyDown, { capture: true });
|
||||
addWindowListener("pointerup", onPointerUp);
|
||||
addWindowListener("pointercancel", onPointerCancel);
|
||||
addWindowListener("keydown", onKeyDown, { capture: true });
|
||||
setupHooks.teardown(() => dragEnd(null));
|
||||
|
||||
return state;
|
||||
|
||||
@@ -127,7 +127,7 @@ Odoo Web Editor widget.
|
||||
'web_editor/static/src/js/editor/odoo-editor/src/commands/toggleList.js',
|
||||
|
||||
# utils
|
||||
'web_editor/static/src/js/editor/smooth_scroll_on_drag.js',
|
||||
'web_editor/static/src/js/editor/drag_and_drop.js',
|
||||
'web_editor/static/src/js/wysiwyg/linkDialogCommand.js',
|
||||
'web_editor/static/src/js/wysiwyg/MoveNodePlugin.js',
|
||||
'web_editor/static/src/js/wysiwyg/PeerToPeer.js',
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
/** @odoo-module **/
|
||||
import { makeDraggableHook } from "@web/core/utils/draggable_hook_builder";
|
||||
import { pick } from "@web/core/utils/objects";
|
||||
import { reactive } from "@odoo/owl";
|
||||
import { throttleForAnimation } from "@web/core/utils/timing";
|
||||
import { touching } from "@web/core/utils/ui";
|
||||
|
||||
/** @typedef {import("@web/core/utils/draggable_hook_builder").DraggableHandlerParams} DraggableHandlerParams */
|
||||
/** @typedef {import("@web/core/utils/draggable_hook_builder").DraggableBuilderParams} DraggableBuilderParams */
|
||||
/** @typedef {import("@web/core/utils/draggable").DraggableParams} DraggableParams */
|
||||
|
||||
/** @typedef {DraggableHandlerParams & { dropzone: HTMLElement | null, helper: HTMLElement }} DragAndDropHandlerParams */
|
||||
/** @typedef {DraggableHandlerParams & { helper: HTMLElement }} DragAndDropStartParams */
|
||||
/** @typedef {DraggableHandlerParams & { dropzone: HTMLElement }} DropzoneHandlerParams */
|
||||
/**
|
||||
* @typedef DragAndDropParams
|
||||
* @extends {DraggableParams}
|
||||
*
|
||||
* MANDATORY
|
||||
* @property {(() => Array)} dropzones a function that returns the available dropzones
|
||||
* @property {(() => HTMLElement)} helper a function that returns a helper element
|
||||
* that will follow the cursor when dragging
|
||||
* @property {HTMLElement || (() => HTMLElement)} scrollingElement the element on
|
||||
* which a scroll should be triggered
|
||||
*
|
||||
* HANDLERS (Optional)
|
||||
* @property {(params: DragAndDropStartParams) => any} [onDragStart]
|
||||
* called when a dragging sequence is initiated
|
||||
* @property {(params: DropzoneHandlerParams) => any} [dropzoneOver]
|
||||
* called when an element is over a dropzone
|
||||
* @property {(params: DropzoneHandlerParams) => any} [dropzoneOut]
|
||||
* called when an element is leaving a dropzone
|
||||
* @property {(params: DragAndDropHandlerParams) => any} [onDrag]
|
||||
* called when an element is being dragged
|
||||
* @property {(params: DragAndDropHandlerParams) => any} [onDragEnd]
|
||||
* called when the dragging sequence is over
|
||||
*/
|
||||
/**
|
||||
* @typedef NativeDraggableState
|
||||
* @property {(params: DraggableParams) => any} update
|
||||
* method to update the params of the draggable
|
||||
* @property {import("@web/core/utils/draggable").DraggableState} state
|
||||
* state of the draggable component
|
||||
* @property {() => any} destroy
|
||||
* method to destroy and unbind the draggable component
|
||||
*/
|
||||
/**
|
||||
* Utility function to create a native draggable component
|
||||
*
|
||||
* @param {DraggableBuilderParams} hookParams
|
||||
* @param {DraggableParams} initialParams
|
||||
* @returns {NativeDraggableState}
|
||||
*/
|
||||
export function useNativeDraggable(hookParams, initialParams) {
|
||||
const setupFunctions = new Map();
|
||||
const cleanupFunctions = [];
|
||||
const currentParams = { ...initialParams };
|
||||
const setupHooks = {
|
||||
wrapState: reactive,
|
||||
throttle: throttleForAnimation,
|
||||
addListener: (el, type, callback, options) => {
|
||||
el.addEventListener(type, callback, options);
|
||||
cleanupFunctions.push(() => el.removeEventListener(type, callback));
|
||||
},
|
||||
setup: (setupFn, depsFn) => setupFunctions.set(setupFn, depsFn),
|
||||
teardown: (cleanupFn) => {
|
||||
cleanupFunctions.push(cleanupFn);
|
||||
}
|
||||
};
|
||||
// Compatibility for tests
|
||||
const el = initialParams.ref.el;
|
||||
el.classList.add("o_draggable");
|
||||
cleanupFunctions.push(() => el.classList.remove("o_draggable"));
|
||||
|
||||
const draggableState = makeDraggableHook({ setupHooks, ...hookParams})(currentParams);
|
||||
draggableState.enable = true;
|
||||
const draggableComponent = {
|
||||
state: draggableState,
|
||||
update: (newParams) => {
|
||||
Object.assign(currentParams, newParams);
|
||||
setupFunctions.forEach((depsFn, setupFn) => setupFn(...depsFn()));
|
||||
},
|
||||
destroy: () => {
|
||||
cleanupFunctions.forEach((cleanupFn) => cleanupFn());
|
||||
}
|
||||
};
|
||||
draggableComponent.update({});
|
||||
return draggableComponent;
|
||||
}
|
||||
|
||||
function updateElementPosition(el, { x, y }, styleFn, offset = { x: 0, y: 0 }) {
|
||||
return styleFn(el, { top: `${y - offset.y}px`, left: `${x - offset.x}px`});
|
||||
}
|
||||
/** @type DraggableBuilderParams */
|
||||
const dragAndDropHookParams = {
|
||||
name: "useDragAndDrop",
|
||||
acceptedParams: {
|
||||
dropzones: [Function],
|
||||
scrollingElement: [Object, Function],
|
||||
helper: [Function],
|
||||
extraWindow: [Object, Function],
|
||||
},
|
||||
edgeScrolling: { enabled: true },
|
||||
onComputeParams({ ctx, params }) {
|
||||
// The helper is mandatory and will follow the cursor instead
|
||||
ctx.followCursor = false;
|
||||
ctx.scrollingElement = params.scrollingElement;
|
||||
ctx.getHelper = params.helper;
|
||||
ctx.getDropZones = params.dropzones;
|
||||
},
|
||||
onWillStartDrag: ({ ctx }) => {
|
||||
ctx.current.container = ctx.scrollingElement;
|
||||
ctx.current.helperOffset = { x: 0, y: 0 };
|
||||
},
|
||||
onDragStart: ({ ctx, addStyle, addCleanup }) => {
|
||||
// Use the helper as the tracking element to properly update scroll values.
|
||||
ctx.current.element = ctx.getHelper({ ...ctx.current, ...ctx.pointer });
|
||||
ctx.current.helper = ctx.current.element;
|
||||
ctx.current.helper.style.position = "fixed";
|
||||
// We want the pointer events on the helper so that the cursor
|
||||
// is properly displayed.
|
||||
ctx.current.helper.classList.remove("o_dragged");
|
||||
ctx.current.helper.style.cursor = ctx.cursor;
|
||||
ctx.current.helper.style.pointerEvents = "auto";
|
||||
|
||||
// If the helper is inside the iframe, we want pointer events on the
|
||||
// frame element so that they reach the window and properly apply
|
||||
// the cursor.
|
||||
const frameElement = ctx.current.helper.ownerDocument.defaultView.frameElement;
|
||||
if (frameElement) {
|
||||
addStyle(frameElement, { pointerEvents: "auto" });
|
||||
}
|
||||
|
||||
addCleanup(() => ctx.current.helper.remove());
|
||||
|
||||
updateElementPosition(ctx.current.helper, ctx.pointer, addStyle, ctx.current.helperOffset);
|
||||
|
||||
return pick(ctx.current, "element", "helper");
|
||||
},
|
||||
onDrag: ({ ctx, addStyle, callHandler }) => {
|
||||
ctx.current.helper.classList.add("o_draggable_dragging");
|
||||
|
||||
updateElementPosition(ctx.current.helper, ctx.pointer, addStyle, ctx.current.helperOffset);
|
||||
// Unfortunately, DOMRect is not an Object, so spreading operator from
|
||||
// `touching` does not work, so convert DOMRect to plain object.
|
||||
let helperRect = ctx.current.helper.getBoundingClientRect();
|
||||
helperRect = {
|
||||
x: helperRect.x,
|
||||
y: helperRect.y,
|
||||
width: helperRect.width,
|
||||
height: helperRect.height,
|
||||
};
|
||||
const dropzoneEl = touching(ctx.getDropZones(), helperRect)[0];
|
||||
// Update the drop zone if it's in grid mode
|
||||
if (ctx.current.dropzone?.el && ctx.current.dropzone.el.classList.contains("oe_grid_zone")) {
|
||||
ctx.current.dropzone.rect = ctx.current.dropzone.el.getBoundingClientRect();
|
||||
}
|
||||
if (
|
||||
ctx.current.dropzone &&
|
||||
(
|
||||
ctx.current.dropzone.el === dropzoneEl ||
|
||||
touching([ctx.current.helper], ctx.current.dropzone.rect).length > 0
|
||||
)
|
||||
) {
|
||||
// If no new dropzone but old one is still valid, return early.
|
||||
return pick(ctx.current, "element", "dropzone", "helper");
|
||||
}
|
||||
|
||||
if (ctx.current.dropzone && dropzoneEl !== ctx.current.dropzone.el) {
|
||||
callHandler("dropzoneOut", { dropzone: ctx.current.dropzone });
|
||||
delete ctx.current.dropzone;
|
||||
}
|
||||
|
||||
if (dropzoneEl) {
|
||||
// Save rect information prior to calling the over function
|
||||
// to keep a consistent dropzone even if content was added.
|
||||
const rect = DOMRect.fromRect(dropzoneEl.getBoundingClientRect());
|
||||
ctx.current.dropzone = {
|
||||
el: dropzoneEl,
|
||||
rect: {
|
||||
x: rect.x, y: rect.y, width: rect.width, height: rect.height
|
||||
}
|
||||
};
|
||||
callHandler("dropzoneOver", { dropzone: ctx.current.dropzone });
|
||||
}
|
||||
return pick(ctx.current, "element", "dropzone", "helper");
|
||||
},
|
||||
onDragEnd({ ctx }) {
|
||||
return pick(ctx.current, "element", "dropzone", "helper");
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Function to start a drag and drop handler
|
||||
*
|
||||
* @param {DragAndDropParams} initialParams params given to the drag and drop
|
||||
* component
|
||||
* @returns {NativeDraggableState}
|
||||
*/
|
||||
export function useDragAndDrop(initialParams) {
|
||||
return useNativeDraggable(dragAndDropHookParams, initialParams);
|
||||
}
|
||||
@@ -1,420 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
/**
|
||||
* Provides a helper for SmoothScrollOnDrag options.offsetElements
|
||||
*/
|
||||
class OffsetElementsHelper {
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Object} offsetElements
|
||||
* @param {jQuery} [offsetElements.$top] top offset element
|
||||
* @param {jQuery} [offsetElements.$right] right offset element
|
||||
* @param {jQuery} [offsetElements.$bottom] bottom offset element
|
||||
* @param {jQuery} [offsetElements.$left] left offset element
|
||||
*/
|
||||
constructor(offsetElements) {
|
||||
this.offsetElements = offsetElements;
|
||||
}
|
||||
top () {
|
||||
if (!this.offsetElements.$top || !this.offsetElements.$top.length) {
|
||||
return 0;
|
||||
}
|
||||
return this.offsetElements.$top.get(0).getBoundingClientRect().bottom;
|
||||
}
|
||||
right () {
|
||||
if (!this.offsetElements.$right || !this.offsetElements.$right.length) {
|
||||
return 0;
|
||||
}
|
||||
return this.offsetElements.$right.get(0).getBoundingClientRect().left;
|
||||
}
|
||||
bottom () {
|
||||
if (!this.offsetElements.$bottom || !this.offsetElements.$bottom.length) {
|
||||
return 0;
|
||||
}
|
||||
return this.offsetElements.$bottom.get(0).getBoundingClientRect().top;
|
||||
}
|
||||
left () {
|
||||
if (!this.offsetElements.$left || !this.offsetElements.$left.length) {
|
||||
return 0;
|
||||
}
|
||||
return this.offsetElements.$left.get(0).getBoundingClientRect().right;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides smooth scroll behaviour on drag.
|
||||
*/
|
||||
export class SmoothScrollOnDrag {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Object} parent The parent widget that uses this class.
|
||||
* @param {jQuery} $element The element the smooth scroll on drag has to be set on.
|
||||
* @param {jQuery} $scrollTarget The element the scroll will be triggered on.
|
||||
* @param {Object} [options={}]
|
||||
* @param {Object} [options.jQueryDraggableOptions={}] The configuration to be passed to
|
||||
* the jQuery draggable function (all will be passed except scroll which will
|
||||
* be overridden to false).
|
||||
* @param {Number} [options.scrollOffsetThreshold=150] (Integer) The distance from the
|
||||
* bottom/top of the options.$scrollTarget from which the smooth scroll will be
|
||||
* triggered.
|
||||
* @param {Number} [options.scrollStep=20] (Integer) The step of the scroll.
|
||||
* @param {Number} [options.scrollTimerInterval=5] (Integer) The interval (in ms) the
|
||||
* scrollStep will be applied.
|
||||
* @param {Object} [options.scrollBoundaries = {}] Specifies whether scroll can still be triggered
|
||||
* when dragging $element outside of target.
|
||||
* @param {Object} [options.scrollBoundaries.top = true] Specifies whether scroll can still be triggered
|
||||
* when dragging $element above the top edge of target.
|
||||
* @param {Object} [options.scrollBoundaries.right = true] Specifies whether scroll can still be triggered
|
||||
* when dragging $element after the right edge of target.
|
||||
* @param {Object} [options.scrollBoundaries.bottom = true] Specifies whether scroll can still be triggered
|
||||
* when dragging $element bellow the bottom edge of target.
|
||||
* @param {Object} [options.scrollBoundaries.left = true] Specifies whether scroll can still be triggered
|
||||
* when dragging $element before the left edge of target.
|
||||
* @param {Object} [options.offsetElements={}] Visible elements in $scrollTarget that
|
||||
* reduce $scrollTarget drag visible area (scroll will be triggered sooner than
|
||||
* normally). A selector is passed so that elements such as automatically hidden
|
||||
* menu can then be correctly handled.
|
||||
* @param {jQuery} [options.offsetElements.$top] Visible top offset element which height will
|
||||
* be taken into account when triggering scroll at the top of the $scrollTarget.
|
||||
* @param {jQuery} [options.offsetElements.$right] Visible right offset element which width
|
||||
* will be taken into account when triggering scroll at the right side of the
|
||||
* $scrollTarget.
|
||||
* @param {jQuery} [options.offsetElements.$bottom] Visible bottom offset element which height
|
||||
* will be taken into account when triggering scroll at bottom of the $scrollTarget.
|
||||
* @param {jQuery} [options.offsetElements.$left] Visible left offset element which width
|
||||
* will be taken into account when triggering scroll at the left side of the
|
||||
* $scrollTarget.
|
||||
* @param {boolean} [options.disableHorizontalScroll = false] Disable horizontal scroll if not needed.
|
||||
*/
|
||||
constructor($element, $scrollTarget, options = {}) {
|
||||
this.$element = $element;
|
||||
this.$scrollTarget = $scrollTarget;
|
||||
this.options = options;
|
||||
this.targetWindow = this.$element[0].ownerDocument.defaultView;
|
||||
const insideIframe = this.targetWindow !== window.top;
|
||||
|
||||
// Setting optional options to their default value if not provided
|
||||
this.options.jQueryDraggableOptions = this.options.jQueryDraggableOptions || {};
|
||||
if (!this.options.jQueryDraggableOptions.cursorAt) {
|
||||
this.$element.on('mousedown.smooth_scroll', this._onElementMouseDown.bind(this));
|
||||
}
|
||||
this.options.scrollOffsetThreshold = this.options.scrollOffsetThreshold || 150;
|
||||
this.options.scrollStep = this.options.scrollStep || 20;
|
||||
this.options.scrollTimerInterval = this.options.scrollTimerInterval || 5;
|
||||
this.options.offsetElements = this.options.offsetElements || {};
|
||||
this.options.offsetElementsManager = new OffsetElementsHelper(this.options.offsetElements);
|
||||
this.options.scrollBoundaries = Object.assign({
|
||||
top: true,
|
||||
right: true,
|
||||
bottom: true,
|
||||
left: true
|
||||
}, this.options.scrollBoundaries);
|
||||
|
||||
this.autoScrollHandler = null;
|
||||
|
||||
this.scrollStepDirectionEnum = {
|
||||
up: -1,
|
||||
right: 1,
|
||||
down: 1,
|
||||
left: -1,
|
||||
};
|
||||
|
||||
this.options.jQueryDraggableOptions.scroll = false;
|
||||
this.options.disableHorizontalScroll = this.options.disableHorizontalScroll || false;
|
||||
const draggableOptions = Object.assign({}, this.options.jQueryDraggableOptions, {
|
||||
start: (ev, ui) => {
|
||||
this._onSmoothDragStart(ev, ui, this.options.jQueryDraggableOptions.start);
|
||||
if (insideIframe) {
|
||||
this.onParentWindowMouseup = this._onParentWindowMouseup.bind(this);
|
||||
window.top.addEventListener('mouseup', this.onParentWindowMouseup, {once: true});
|
||||
}
|
||||
},
|
||||
drag: (ev, ui) => this._onSmoothDrag(ev, ui, this.options.jQueryDraggableOptions.drag),
|
||||
stop: (ev, ui) => {
|
||||
if (insideIframe) {
|
||||
window.top.removeEventListener('mouseup', this.onParentWindowMouseup, {once: true});
|
||||
}
|
||||
this._onSmoothDragStop(ev, ui, this.options.jQueryDraggableOptions.stop);
|
||||
}
|
||||
});
|
||||
this.$element.draggable(draggableOptions);
|
||||
}
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
destroy() {
|
||||
this.$element.off('.smooth_scroll');
|
||||
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
|
||||
* @param {Object} ui The jQuery drag handler ui parameter.
|
||||
*/
|
||||
_startSmoothScroll(ui) {
|
||||
this._stopSmoothScroll();
|
||||
|
||||
this._initialScrollBehavior = this.$scrollTarget[0].style.scrollBehavior;
|
||||
this.$scrollTarget[0].style.scrollBehavior = 'auto';
|
||||
|
||||
this.autoScrollHandler = setInterval(
|
||||
() => {
|
||||
// Prevents Delta's from being different from 0 when scroll should not occur (except when
|
||||
// helper is dragged outside of this.$scrollTarget's visible area as it increases
|
||||
// this.$scrollTarget's scrollHeight).
|
||||
// Also, this code prevents the helper from being incorrectly repositioned when target is
|
||||
// a child of this.$scrollTarget.
|
||||
this.verticalDelta = Math.min(
|
||||
// Ensures scrolling stops when dragging bellow this.$scrollTarget bottom.
|
||||
Math.max(
|
||||
0,
|
||||
this.$scrollTarget.get(0).scrollHeight
|
||||
- (this.$scrollTarget.scrollTop() + this.$scrollTarget.innerHeight())
|
||||
),
|
||||
// Ensures scrolling stops when dragging above this.$scrollTarget top.
|
||||
Math.max(
|
||||
this.verticalDelta,
|
||||
-this.$scrollTarget.scrollTop()
|
||||
)
|
||||
);
|
||||
this.horizontalDelta = Math.min(
|
||||
//Ensures scrolling stops when dragging left to this.$scrollTarget.
|
||||
Math.max(
|
||||
0,
|
||||
this.$scrollTarget.get(0).scrollWidth
|
||||
- (this.$scrollTarget.scrollLeft() + this.$scrollTarget.innerWidth())
|
||||
),
|
||||
//Ensures scrolling stops when dragging right to this.$scrollTarget.
|
||||
Math.max(
|
||||
this.horizontalDelta,
|
||||
-this.$scrollTarget.scrollLeft()
|
||||
)
|
||||
);
|
||||
|
||||
// Keep helper at right position while scrolling when helper is a child of this.$scrollTarget.
|
||||
if (this.scrollTargetIsParent) {
|
||||
const offset = ui.helper.offset();
|
||||
ui.helper.offset({
|
||||
top: offset.top + this.verticalDelta,
|
||||
left: offset.left + this.horizontalDelta
|
||||
});
|
||||
}
|
||||
this.$scrollTarget.scrollTop(
|
||||
this.$scrollTarget.scrollTop() +
|
||||
this.verticalDelta
|
||||
);
|
||||
if (!this.options.disableHorizontalScroll) {
|
||||
this.$scrollTarget.scrollLeft(
|
||||
this.$scrollTarget.scrollLeft() +
|
||||
this.horizontalDelta
|
||||
);
|
||||
}
|
||||
},
|
||||
this.options.scrollTimerInterval
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Stops the scroll process if any is running.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_stopSmoothScroll() {
|
||||
clearInterval(this.autoScrollHandler);
|
||||
|
||||
this.$scrollTarget[0].style.scrollBehavior = this._initialScrollBehavior || '';
|
||||
}
|
||||
/**
|
||||
* 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 {Object} ui The jQuery drag handler ui parameter.
|
||||
*/
|
||||
_updatePositionOptions(ui) {
|
||||
const draggableHelperOffset = ui.offset;
|
||||
const scrollTargetOffset = this.$scrollTarget.offset();
|
||||
let visibleOffset = {
|
||||
top: draggableHelperOffset.top
|
||||
- scrollTargetOffset.top
|
||||
+ this.options.jQueryDraggableOptions.cursorAt.top
|
||||
- this.options.offsetElementsManager.top(),
|
||||
right: scrollTargetOffset.left + this.$scrollTarget.outerWidth()
|
||||
- draggableHelperOffset.left
|
||||
- this.options.jQueryDraggableOptions.cursorAt.left
|
||||
- this.options.offsetElementsManager.right(),
|
||||
bottom: scrollTargetOffset.top + this.$scrollTarget.outerHeight()
|
||||
- draggableHelperOffset.top
|
||||
- this.options.jQueryDraggableOptions.cursorAt.top
|
||||
- this.options.offsetElementsManager.bottom(),
|
||||
left: draggableHelperOffset.left
|
||||
- scrollTargetOffset.left
|
||||
+ this.options.jQueryDraggableOptions.cursorAt.left
|
||||
- this.options.offsetElementsManager.left(),
|
||||
};
|
||||
|
||||
if (this.iframeOffset) {
|
||||
const { x, y } = this.iframeOffset;
|
||||
visibleOffset.left -= x;
|
||||
visibleOffset.top -= y;
|
||||
visibleOffset.bottom += y;
|
||||
visibleOffset.right += x;
|
||||
}
|
||||
|
||||
// If this.$scrollTarget is the html tag, we need to take the scroll
|
||||
// position into account as offsets positions are calculated relative to
|
||||
// the document (thus <html>). Except in the case where what is being
|
||||
// dragged is out of an iframe and that the scrolling is being done
|
||||
// inside that iframe.
|
||||
if (this.scrollTargetIsDocument && !this.iframeOffset) {
|
||||
const scrollTargetScrollTop = this.$scrollTarget.scrollTop();
|
||||
const scrollTargetScrollLeft = this.$scrollTarget.scrollLeft();
|
||||
visibleOffset.top -= scrollTargetScrollTop;
|
||||
visibleOffset.right += scrollTargetScrollLeft;
|
||||
visibleOffset.bottom += scrollTargetScrollTop;
|
||||
visibleOffset.left -= scrollTargetScrollLeft;
|
||||
}
|
||||
|
||||
const scrollDecelerator = {
|
||||
vertical: 0,
|
||||
horizontal: 0,
|
||||
};
|
||||
|
||||
const scrollStepDirection = {
|
||||
vertical: this.scrollStepDirectionEnum.down,
|
||||
horizontal: this.scrollStepDirectionEnum.right,
|
||||
};
|
||||
|
||||
// Prevent scroll if outside of scroll boundaries
|
||||
if ((!this.options.scrollBoundaries.top && visibleOffset.top < 0) ||
|
||||
(!this.options.scrollBoundaries.right && visibleOffset.right < 0) ||
|
||||
(!this.options.scrollBoundaries.bottom && visibleOffset.bottom < 0) ||
|
||||
(!this.options.scrollBoundaries.left && visibleOffset.left < 0)) {
|
||||
scrollDecelerator.horizontal = 1;
|
||||
scrollDecelerator.vertical = 1;
|
||||
} else {
|
||||
// Manage vertical scroll
|
||||
if (visibleOffset.bottom <= this.options.scrollOffsetThreshold) {
|
||||
scrollDecelerator.vertical = Math.max(0, visibleOffset.bottom)
|
||||
/ this.options.scrollOffsetThreshold;
|
||||
} else if (visibleOffset.top <= this.options.scrollOffsetThreshold) {
|
||||
scrollDecelerator.vertical = Math.max(0, visibleOffset.top)
|
||||
/ this.options.scrollOffsetThreshold;
|
||||
scrollStepDirection.vertical = this.scrollStepDirectionEnum.up;
|
||||
} else {
|
||||
scrollDecelerator.vertical = 1;
|
||||
}
|
||||
|
||||
// Manage horizontal scroll
|
||||
if (visibleOffset.right <= this.options.scrollOffsetThreshold) {
|
||||
scrollDecelerator.horizontal = Math.max(0, visibleOffset.right)
|
||||
/ this.options.scrollOffsetThreshold;
|
||||
} else if (visibleOffset.left <= this.options.scrollOffsetThreshold) {
|
||||
scrollDecelerator.horizontal = Math.max(0, visibleOffset.left)
|
||||
/ this.options.scrollOffsetThreshold;
|
||||
scrollStepDirection.horizontal = this.scrollStepDirectionEnum.left;
|
||||
} else {
|
||||
scrollDecelerator.horizontal = 1;
|
||||
}
|
||||
}
|
||||
|
||||
this.verticalDelta = Math.ceil(scrollStepDirection.vertical *
|
||||
this.options.scrollStep *
|
||||
(1 - Math.sqrt(scrollDecelerator.vertical)));
|
||||
this.horizontalDelta = Math.ceil(scrollStepDirection.horizontal *
|
||||
this.options.scrollStep *
|
||||
(1 - Math.sqrt(scrollDecelerator.horizontal)));
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Called when mouse button is down on this.$element.
|
||||
* Updates the mouse cursor position variable.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} ev The jQuery mousedown handler event parameter.
|
||||
*/
|
||||
_onElementMouseDown(ev) {
|
||||
const elementOffset = $(ev.target).offset();
|
||||
this.options.jQueryDraggableOptions.cursorAt = {
|
||||
top: ev.pageY - elementOffset.top,
|
||||
left: ev.pageX - elementOffset.left,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
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.scrollTargetIsDocument = this.$scrollTarget.is('html');
|
||||
this.iframeOffset = false;
|
||||
this.scrollTargetIsParent = this.$scrollTarget.get(0).contains(this.$element.get(0));
|
||||
this._updatePositionOptions(ui);
|
||||
this._startSmoothScroll(ui);
|
||||
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);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Called when the mouse is released outside the page iframe (e.g. the
|
||||
* editor panel in Website). This is only useful in Chrome, where the 'stop'
|
||||
* event of jQuery Draggable does not trigger a 'mouseup' event outside of
|
||||
* the "preview" page iframe.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onParentWindowMouseup() {
|
||||
this.targetWindow.document.dispatchEvent(new Event('mouseup'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import core from "@web/legacy/js/services/core";
|
||||
import Dialog from "@web/legacy/js/core/dialog";
|
||||
import dom from "@web/legacy/js/core/dom";
|
||||
import Widget from "@web/legacy/js/core/widget";
|
||||
import { useDragAndDrop } from "@web_editor/js/editor/drag_and_drop";
|
||||
import options from "@web_editor/js/editor/snippets.options";
|
||||
import { SmoothScrollOnDrag } from "@web_editor/js/editor/smooth_scroll_on_drag";
|
||||
import weUtils from "@web_editor/js/common/utils";
|
||||
import * as gridUtils from "@web_editor/js/common/grid_layout_utils";
|
||||
import { escape } from "@web/core/utils/strings";
|
||||
@@ -113,10 +113,11 @@ var SnippetEditor = Widget.extend({
|
||||
// Initialize move/clone/remove buttons
|
||||
if (this.isTargetMovable) {
|
||||
this.dropped = false;
|
||||
this.draggableComponent = this._initSmoothScrollOnDrag(".o_move_handle", this.$el);
|
||||
this.draggableComponent = this._initDragAndDrop(".o_move_handle", ".oe_overlay", this.el);
|
||||
if (!this.$target[0].matches("section")) {
|
||||
// Allow the user to drag the image itself to move the target.
|
||||
this.draggableComponentImgs = this._initSmoothScrollOnDrag("img", this.$target);
|
||||
this.$target[0].classList.add("o_draggable");
|
||||
this.draggableComponentImgs = this._initDragAndDrop("img", ".o_draggable", this.$target[0]);
|
||||
}
|
||||
} else {
|
||||
this.$('.o_overlay_move_options').addClass('d-none');
|
||||
@@ -821,20 +822,26 @@ var SnippetEditor = Widget.extend({
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Creates a SmoothScrollOnDrag object.
|
||||
* Initialize drag and drop handlers.
|
||||
*
|
||||
* @private
|
||||
* @param {String} handle css selector for grabble element
|
||||
* @param {jQuery} $element the smooth scroll on drag has to be set on
|
||||
* @returns {SmoothScrollOnDrag}
|
||||
* @param {String} elementsSelector selector for elements that will be dragged.
|
||||
* @param {HTMLElement} element element to listen for drag events.
|
||||
* @returns {Object} the drag state.
|
||||
*/
|
||||
_initSmoothScrollOnDrag(handle, $element) {
|
||||
const jQueryDraggableOptions = {
|
||||
cursorAt: {
|
||||
left: 10,
|
||||
top: 10
|
||||
},
|
||||
_initDragAndDrop(handle, elementsSelector, element) {
|
||||
const modalAncestorEl = this.$target[0].closest('.modal');
|
||||
const $scrollable = modalAncestorEl && $(modalAncestorEl)
|
||||
|| (this.options.wysiwyg.snippetsMenu && this.options.wysiwyg.snippetsMenu.$scrollable)
|
||||
|| (this.$scrollingElement.length && this.$scrollingElement)
|
||||
|| $().getScrollingElement(this.ownerDocument);
|
||||
const dragAndDropOptions = {
|
||||
ref: { el: element },
|
||||
elements: elementsSelector,
|
||||
handle: handle,
|
||||
scrollingElement: $scrollable[0],
|
||||
enable: () => !!this.$el.find('.o_move_handle:visible').length || this.dragStarted,
|
||||
helper: () => {
|
||||
const cloneEl = this.$el[0].cloneNode(true);
|
||||
cloneEl.style.width = "24px";
|
||||
@@ -842,18 +849,17 @@ var SnippetEditor = Widget.extend({
|
||||
cloneEl.style.border = "0";
|
||||
this.$el[0].ownerDocument.body.appendChild(cloneEl);
|
||||
cloneEl.classList.remove("d-none");
|
||||
return $(cloneEl);
|
||||
cloneEl.classList.remove("o_dragged");
|
||||
return cloneEl;
|
||||
},
|
||||
start: (ev, ui) => {
|
||||
if (!this.$el.find('.o_move_handle:visible').length) {
|
||||
return false;
|
||||
}
|
||||
onDragStart: (args) => {
|
||||
this.dragStarted = true;
|
||||
this.mousePositionYOnElement = ev.originalEvent.clientY - ui.offset.top;
|
||||
this.mousePositionXOnElement = ev.originalEvent.clientX - ui.offset.left;
|
||||
this._onDragAndDropStart();
|
||||
const targetRect = this.$target[0].getBoundingClientRect();
|
||||
this.mousePositionYOnElement = args.y - targetRect.y;
|
||||
this.mousePositionXOnElement = args.x - targetRect.x;
|
||||
this._onDragAndDropStart(args);
|
||||
},
|
||||
stop: (...args) => {
|
||||
onDragEnd: (...args) => {
|
||||
if (!this.dragStarted) {
|
||||
return false;
|
||||
}
|
||||
@@ -866,16 +872,13 @@ var SnippetEditor = Widget.extend({
|
||||
this._onDragAndDropStop(...args);
|
||||
}, 0);
|
||||
},
|
||||
refreshPositions: true, // So the dropzone expands when its size increases.
|
||||
onDrag: this._onDragMove.bind(this),
|
||||
dropzoneOver: this.dropzoneOver.bind(this),
|
||||
dropzoneOut: this.dropzoneOut.bind(this),
|
||||
dropzones: () => this.$dropZones?.toArray() || [],
|
||||
};
|
||||
const scrollOptions =
|
||||
this.options.getScrollOptions({ jQueryDraggableOptions: jQueryDraggableOptions });
|
||||
const modalAncestorEl = this.$target[0].closest('.modal');
|
||||
const $scrollable = modalAncestorEl && $(modalAncestorEl)
|
||||
|| (this.options.wysiwyg.snippetsMenu && this.options.wysiwyg.snippetsMenu.$scrollable)
|
||||
|| (this.$scrollingElement.length && this.$scrollingElement)
|
||||
|| $().getScrollingElement(this.ownerDocument);
|
||||
return new SmoothScrollOnDrag($element, $scrollable, scrollOptions);
|
||||
const finalOptions = this.options.getDragAndDropOptions(dragAndDropOptions);
|
||||
return useDragAndDrop(finalOptions);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -935,7 +938,7 @@ var SnippetEditor = Widget.extend({
|
||||
const rowEl = previousDropzoneEl.parentNode;
|
||||
|
||||
if (rowEl.classList.contains('o_grid_mode')) {
|
||||
self.$body[0].removeEventListener('mousemove', self.onDragMove, false);
|
||||
self.dragState.gridMode = false;
|
||||
const fromGridToGrid = currentDropzoneEl.classList.contains('oe_grid_zone');
|
||||
if (fromGridToGrid) {
|
||||
// If we went from a grid dropzone to an other grid one.
|
||||
@@ -989,7 +992,15 @@ var SnippetEditor = Widget.extend({
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onDragAndDropStart: function () {
|
||||
_onDragAndDropStart({ helper, addStyle }) {
|
||||
// If the helper is inside the iframe, we want pointer events on the
|
||||
// frame element so that they reach the window and properly apply
|
||||
// the cursor.
|
||||
const frameElement = helper.ownerDocument.defaultView.frameElement;
|
||||
if (frameElement) {
|
||||
addStyle(frameElement, { pointerEvents: "auto" });
|
||||
}
|
||||
|
||||
this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
|
||||
this.trigger_up('drag_and_drop_start');
|
||||
this.options.wysiwyg.odooEditor.automaticStepUnactive();
|
||||
@@ -1018,8 +1029,6 @@ var SnippetEditor = Widget.extend({
|
||||
const allowGridMode = hasGridLayoutOption || rowEl.classList.contains('o_grid_mode');
|
||||
|
||||
// Number of grid columns and rows in the grid item (BS column).
|
||||
let columnColCount;
|
||||
let columnRowCount;
|
||||
if (rowEl.classList.contains('row') && this.options.isWebsite) {
|
||||
if (allowGridMode) {
|
||||
// Toggle grid mode if it is not already on.
|
||||
@@ -1037,10 +1046,8 @@ var SnippetEditor = Widget.extend({
|
||||
const rowStart = self.$target[0].style.gridRowStart;
|
||||
const rowEnd = self.$target[0].style.gridRowEnd;
|
||||
|
||||
columnColCount = columnEnd - columnStart;
|
||||
columnRowCount = rowEnd - rowStart;
|
||||
this.dragState.columnColCount = columnColCount;
|
||||
this.dragState.columnRowCount = columnRowCount;
|
||||
this.dragState.columnColCount = columnEnd - columnStart;
|
||||
this.dragState.columnRowCount = rowEnd - rowStart;
|
||||
|
||||
// Storing the current grid and grid area to use them for the
|
||||
// history.
|
||||
@@ -1173,156 +1180,146 @@ var SnippetEditor = Widget.extend({
|
||||
} else if (canBeSanitizedUnless === 'form') {
|
||||
this.$dropZones = this.$dropZones.not('[data-oe-sanitize][data-oe-sanitize!="allow_form"] .oe_drop_zone');
|
||||
}
|
||||
this.$dropZones.droppable({
|
||||
over: function () {
|
||||
if (self.dropped) {
|
||||
self.$target.detach();
|
||||
}
|
||||
},
|
||||
dropzoneOver({ dropzone }) {
|
||||
if (this.dropped) {
|
||||
this.$target.detach();
|
||||
}
|
||||
|
||||
// Prevent a column to be trapped in an upper grid dropzone at
|
||||
// the start of the drag.
|
||||
if (self.dragState.isColumn && self.dragState.overFirstDropzone) {
|
||||
self.dragState.overFirstDropzone = false;
|
||||
// Prevent a column to be trapped in an upper grid dropzone at
|
||||
// the start of the drag.
|
||||
if (this.dragState.isColumn && this.dragState.overFirstDropzone) {
|
||||
this.dragState.overFirstDropzone = false;
|
||||
|
||||
// The column is considered as glued to the dropzone if the
|
||||
// dropzone is above and if the space between them is less
|
||||
// than 25px (the move handle height is 22px so 25 is a
|
||||
// safety margin).
|
||||
const columnTop = self.dragState.columnTop;
|
||||
const dropzoneBottom = this.getBoundingClientRect().bottom;
|
||||
const areDropzonesGlued = (columnTop >= dropzoneBottom) && (columnTop - dropzoneBottom < 25);
|
||||
// The column is considered as glued to the dropzone if the
|
||||
// dropzone is above and if the space between them is less
|
||||
// than 25px (the move handle height is 22px so 25 is a
|
||||
// safety margin).
|
||||
const columnTop = this.dragState.columnTop;
|
||||
const dropzoneBottom = dropzone.el.getBoundingClientRect().bottom;
|
||||
const areDropzonesGlued = (columnTop >= dropzoneBottom) && (columnTop - dropzoneBottom < 25);
|
||||
|
||||
if (areDropzonesGlued && this.classList.contains('oe_grid_zone')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (areDropzonesGlued && dropzone.el.classList.contains('oe_grid_zone')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.dropped = true;
|
||||
const $dropzone = $(this).first().after(self.$target);
|
||||
$dropzone.addClass('invisible');
|
||||
this.dropped = true;
|
||||
const $dropzone = $(dropzone.el).first().after(this.$target);
|
||||
$dropzone.addClass('invisible');
|
||||
|
||||
// Checking if the "out" event happened before this "over": if
|
||||
// `self.dragState.currentDropzoneEl` exists, "out" didn't
|
||||
// happen because it deletes it. We are therefore in the case
|
||||
// of an "over" after an "over" and we need to escape the
|
||||
// previous dropzone first.
|
||||
if (self.dragState.currentDropzoneEl) {
|
||||
self._outPreviousDropzone.apply(self.dragState.currentDropzoneEl, [self, $dropzone[0]]);
|
||||
}
|
||||
self.dragState.currentDropzoneEl = $dropzone[0];
|
||||
// Checking if the "out" event happened before dropzone.el "over": if
|
||||
// `this.dragState.currentDropzoneEl` exists, "out" didn't
|
||||
// happen because it deletes it. We are therefore in the case
|
||||
// of an "over" after an "over" and we need to escape the
|
||||
// previous dropzone first.
|
||||
if (this.dragState.currentDropzoneEl) {
|
||||
this._outPreviousDropzone.apply(this.dragState.currentDropzoneEl, [this, $dropzone[0]]);
|
||||
}
|
||||
this.dragState.currentDropzoneEl = $dropzone[0];
|
||||
|
||||
if ($dropzone[0].classList.contains('oe_grid_zone')) {
|
||||
// Case where the column we are dragging is over a grid
|
||||
// dropzone.
|
||||
const rowEl = $dropzone[0].parentNode;
|
||||
if ($dropzone[0].classList.contains('oe_grid_zone')) {
|
||||
// Case where the column we are dragging is over a grid
|
||||
// dropzone.
|
||||
const rowEl = $dropzone[0].parentNode;
|
||||
|
||||
// If the column doesn't come from a grid mode snippet.
|
||||
if (!self.$target[0].classList.contains('o_grid_item')) {
|
||||
// Converting the column to grid.
|
||||
self.options.wysiwyg.odooEditor.observerActive('dragAndDropMoveSnippet');
|
||||
const spans = gridUtils._convertColumnToGrid(rowEl, self.$target[0], self.dragState.columnWidth, self.dragState.columnHeight);
|
||||
self.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
|
||||
columnColCount = spans.columnColCount;
|
||||
columnRowCount = spans.columnRowCount;
|
||||
// If the column doesn't come from a grid mode snippet.
|
||||
if (!this.$target[0].classList.contains('o_grid_item')) {
|
||||
// Converting the column to grid.
|
||||
this.options.wysiwyg.odooEditor.observerActive('dragAndDropMoveSnippet');
|
||||
const spans = gridUtils._convertColumnToGrid(rowEl, this.$target[0], this.dragState.columnWidth, this.dragState.columnHeight);
|
||||
this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
|
||||
this.dragState.columnColCount = spans.columnColCount;
|
||||
this.dragState.columnRowCount = spans.columnRowCount;
|
||||
|
||||
// Storing the column spans.
|
||||
self.dragState.columnColCount = columnColCount;
|
||||
self.dragState.columnRowCount = columnRowCount;
|
||||
}
|
||||
// Storing the column spans.
|
||||
}
|
||||
|
||||
// Creating the drag helper.
|
||||
const dragHelperEl = document.createElement('div');
|
||||
dragHelperEl.classList.add('o_we_drag_helper');
|
||||
dragHelperEl.style.gridArea = `1 / 1 / ${1 + columnRowCount} / ${1 + columnColCount}`;
|
||||
rowEl.append(dragHelperEl);
|
||||
const columnColCount = this.dragState.columnColCount;
|
||||
const columnRowCount = this.dragState.columnRowCount;
|
||||
// Creating the drag helper.
|
||||
const dragHelperEl = document.createElement('div');
|
||||
dragHelperEl.classList.add('o_we_drag_helper');
|
||||
dragHelperEl.style.gridArea = `1 / 1 / ${1 + columnRowCount} / ${1 + columnColCount}`;
|
||||
rowEl.append(dragHelperEl);
|
||||
|
||||
// Creating the background grid and updating the dropzone
|
||||
// (in the case where the column over the dropzone is
|
||||
// bigger than the grid).
|
||||
const backgroundGridEl = gridUtils._addBackgroundGrid(rowEl, columnRowCount);
|
||||
const rowCount = Math.max(rowEl.dataset.rowCount, columnRowCount);
|
||||
$dropzone[0].style.gridRowEnd = rowCount + 1;
|
||||
// Creating the background grid and updating the dropzone
|
||||
// (in the case where the column over the dropzone is
|
||||
// bigger than the grid).
|
||||
const backgroundGridEl = gridUtils._addBackgroundGrid(rowEl, columnRowCount);
|
||||
const rowCount = Math.max(rowEl.dataset.rowCount, columnRowCount);
|
||||
$dropzone[0].style.gridRowEnd = rowCount + 1;
|
||||
|
||||
self.options.wysiwyg.odooEditor.observerActive('dragAndDropMoveSnippet');
|
||||
// Setting the moving grid item, the background grid and
|
||||
// the drag helper z-indexes. The grid item z-index is set
|
||||
// to its original one if we are in its starting grid, or
|
||||
// to the maximum z-index of the grid otherwise.
|
||||
if (rowEl === self.dragState.startingGrid) {
|
||||
self.$target[0].style.zIndex = self.dragState.startingZIndex;
|
||||
} else {
|
||||
gridUtils._setElementToMaxZindex(self.$target[0], rowEl);
|
||||
}
|
||||
gridUtils._setElementToMaxZindex(backgroundGridEl, rowEl);
|
||||
gridUtils._setElementToMaxZindex(dragHelperEl, rowEl);
|
||||
this.options.wysiwyg.odooEditor.observerActive('dragAndDropMoveSnippet');
|
||||
// Setting the moving grid item, the background grid and
|
||||
// the drag helper z-indexes. The grid item z-index is set
|
||||
// to its original one if we are in its starting grid, or
|
||||
// to the maximum z-index of the grid otherwise.
|
||||
if (rowEl === this.dragState.startingGrid) {
|
||||
this.$target[0].style.zIndex = this.dragState.startingZIndex;
|
||||
} else {
|
||||
gridUtils._setElementToMaxZindex(this.$target[0], rowEl);
|
||||
}
|
||||
gridUtils._setElementToMaxZindex(backgroundGridEl, rowEl);
|
||||
gridUtils._setElementToMaxZindex(dragHelperEl, rowEl);
|
||||
|
||||
// Setting the column height and width to keep its size
|
||||
// when the grid-area is removed (as it prevents it from
|
||||
// moving with the mouse).
|
||||
const gridProp = gridUtils._getGridProperties(rowEl);
|
||||
const columnHeight = columnRowCount * (gridProp.rowSize + gridProp.rowGap) - gridProp.rowGap;
|
||||
const columnWidth = columnColCount * (gridProp.columnSize + gridProp.columnGap) - gridProp.columnGap;
|
||||
self.$target[0].style.height = columnHeight + 'px';
|
||||
self.$target[0].style.width = columnWidth + 'px';
|
||||
self.$target[0].style.position = 'absolute';
|
||||
self.$target[0].style.removeProperty('grid-area');
|
||||
rowEl.style.position = 'relative';
|
||||
self.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
|
||||
// Setting the column height and width to keep its size
|
||||
// when the grid-area is removed (as it prevents it from
|
||||
// moving with the mouse).
|
||||
const gridProp = gridUtils._getGridProperties(rowEl);
|
||||
const columnHeight = columnRowCount * (gridProp.rowSize + gridProp.rowGap) - gridProp.rowGap;
|
||||
const columnWidth = columnColCount * (gridProp.columnSize + gridProp.columnGap) - gridProp.columnGap;
|
||||
this.$target[0].style.height = columnHeight + 'px';
|
||||
this.$target[0].style.width = columnWidth + 'px';
|
||||
this.$target[0].style.position = 'absolute';
|
||||
this.$target[0].style.removeProperty('grid-area');
|
||||
rowEl.style.position = 'relative';
|
||||
this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
|
||||
|
||||
// Storing useful information and adding an event listener.
|
||||
self.dragState.startingHeight = rowEl.clientHeight;
|
||||
self.dragState.currentHeight = rowEl.clientHeight;
|
||||
self.dragState.dragHelperEl = dragHelperEl;
|
||||
self.dragState.backgroundGridEl = backgroundGridEl;
|
||||
self.onDragMove = self._onDragMove.bind(self);
|
||||
self.$body[0].addEventListener('mousemove', self.onDragMove, false);
|
||||
}
|
||||
},
|
||||
out: function () {
|
||||
const dropzoneEl = this;
|
||||
const rowEl = dropzoneEl.parentNode;
|
||||
// Storing useful information and adding an event listener.
|
||||
this.dragState.startingHeight = rowEl.clientHeight;
|
||||
this.dragState.currentHeight = rowEl.clientHeight;
|
||||
this.dragState.dragHelperEl = dragHelperEl;
|
||||
this.dragState.backgroundGridEl = backgroundGridEl;
|
||||
this.dragState.gridMode = true;
|
||||
}
|
||||
},
|
||||
dropzoneOut({ dropzone }) {
|
||||
const rowEl = dropzone.el.parentNode;
|
||||
|
||||
// Checking if the "out" event happens right after the "over"
|
||||
// of the same dropzone. If it is not the case, we don't do
|
||||
// anything since the previous dropzone was already escaped (at
|
||||
// the start of the over).
|
||||
const sameDropzoneAsCurrent = self.dragState.currentDropzoneEl === dropzoneEl;
|
||||
// Checking if the "out" event happens right after the "over"
|
||||
// of the same dropzone. If it is not the case, we don't do
|
||||
// anything since the previous dropzone was already escaped (at
|
||||
// the start of the over).
|
||||
const sameDropzoneAsCurrent = this.dragState.currentDropzoneEl === dropzone.el;
|
||||
|
||||
if (sameDropzoneAsCurrent) {
|
||||
if (rowEl.classList.contains('o_grid_mode')) {
|
||||
// Removing the listener + cleaning.
|
||||
self.$body[0].removeEventListener('mousemove', self.onDragMove, false);
|
||||
gridUtils._gridCleanUp(rowEl, self.$target[0]);
|
||||
self.$target[0].style.removeProperty('z-index');
|
||||
if (sameDropzoneAsCurrent) {
|
||||
if (rowEl.classList.contains('o_grid_mode')) {
|
||||
// Removing the listener + cleaning.
|
||||
this.dragState.gridMode = false;
|
||||
gridUtils._gridCleanUp(rowEl, this.$target[0]);
|
||||
this.$target[0].style.removeProperty('z-index');
|
||||
|
||||
// Removing the drag helper and the background grid and
|
||||
// resizing the grid and the dropzone.
|
||||
self.dragState.dragHelperEl.remove();
|
||||
self.dragState.backgroundGridEl.remove();
|
||||
self.options.wysiwyg.odooEditor.observerActive('dragAndDropMoveSnippet');
|
||||
gridUtils._resizeGrid(rowEl);
|
||||
self.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
|
||||
const rowCount = parseInt(rowEl.dataset.rowCount);
|
||||
dropzoneEl.style.gridRowEnd = Math.max(rowCount + 1, 1);
|
||||
}
|
||||
// Removing the drag helper and the background grid and
|
||||
// resizing the grid and the dropzone.
|
||||
this.dragState.dragHelperEl.remove();
|
||||
this.dragState.backgroundGridEl.remove();
|
||||
this.options.wysiwyg.odooEditor.observerActive('dragAndDropMoveSnippet');
|
||||
gridUtils._resizeGrid(rowEl);
|
||||
this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
|
||||
const rowCount = parseInt(rowEl.dataset.rowCount);
|
||||
dropzone.el.style.gridRowEnd = Math.max(rowCount + 1, 1);
|
||||
}
|
||||
|
||||
var prev = self.$target.prev();
|
||||
if (this === prev[0]) {
|
||||
self.dropped = false;
|
||||
self.$target.detach();
|
||||
$(this).removeClass('invisible');
|
||||
}
|
||||
var prev = this.$target.prev();
|
||||
if (dropzone.el === prev[0]) {
|
||||
this.dropped = false;
|
||||
this.$target.detach();
|
||||
$(dropzone.el).removeClass('invisible');
|
||||
}
|
||||
|
||||
delete self.dragState.currentDropzoneEl;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Trigger a scroll on the draggable element so that jQuery updates
|
||||
// the position of the drop zones.
|
||||
self.draggableComponent.$scrollTarget.on('scroll.scrolling_element', function () {
|
||||
self.$el.trigger('scroll');
|
||||
});
|
||||
delete this.dragState.currentDropzoneEl;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Called when the snippet is dropped after being dragged thanks to the
|
||||
@@ -1332,7 +1329,7 @@ var SnippetEditor = Widget.extend({
|
||||
* @param {Event} ev
|
||||
* @param {Object} ui
|
||||
*/
|
||||
_onDragAndDropStop: function (ev, ui) {
|
||||
_onDragAndDropStop({ x, y }) {
|
||||
this.options.wysiwyg.odooEditor.automaticStepActive();
|
||||
this.options.wysiwyg.odooEditor.automaticStepSkipStack();
|
||||
this.options.wysiwyg.odooEditor.unbreakableStepUnactive();
|
||||
@@ -1341,8 +1338,8 @@ var SnippetEditor = Widget.extend({
|
||||
if (rowEl && rowEl.classList.contains('o_grid_mode')) {
|
||||
// Case when dropping the column in a grid.
|
||||
|
||||
// Removing the event listener.
|
||||
this.$body[0].removeEventListener('mousemove', this.onDragMove, false);
|
||||
// Disable dragMove handler
|
||||
this.dragState.gridMode = false;
|
||||
|
||||
// Defining the column grid area with its position.
|
||||
const gridProp = gridUtils._getGridProperties(rowEl);
|
||||
@@ -1374,7 +1371,7 @@ var SnippetEditor = Widget.extend({
|
||||
|
||||
// TODO lot of this is duplicated code of the d&d feature of snippets
|
||||
if (!this.dropped) {
|
||||
let $el = $(closest(this.$body[0].querySelectorAll('.oe_drop_zone'), {x: ui.position.left, y: ui.position.top}));
|
||||
let $el = $(closest(this.$body[0].querySelectorAll('.oe_drop_zone'), {x, y}));
|
||||
// Some drop zones might have been disabled.
|
||||
$el = $el.filter(this.$dropZones);
|
||||
if ($el.length) {
|
||||
@@ -1429,7 +1426,6 @@ var SnippetEditor = Widget.extend({
|
||||
this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropMoveSnippet');
|
||||
}
|
||||
|
||||
this.$dropZones.droppable('destroy');
|
||||
this.$editable.find('.oe_drop_zone').remove();
|
||||
|
||||
var prev = this.$target.first()[0].previousSibling;
|
||||
@@ -1470,10 +1466,6 @@ var SnippetEditor = Widget.extend({
|
||||
this.trigger_up('drag_and_drop_stop', {
|
||||
$snippet: this.$target,
|
||||
});
|
||||
this.draggableComponent.$scrollTarget.off('scroll.scrolling_element');
|
||||
if (this.draggableComponentImgs) {
|
||||
this.draggableComponentImgs.$scrollTarget.off('scroll.scrolling_element');
|
||||
}
|
||||
const samePositionAsStart = this.$target[0].classList.contains('o_grid_item')
|
||||
? (this.$target[0].parentNode === this.dragState.startingGrid
|
||||
&& this.$target[0].style.gridArea === this.dragState.prevGridArea)
|
||||
@@ -1650,7 +1642,10 @@ var SnippetEditor = Widget.extend({
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onDragMove(ev) {
|
||||
_onDragMove({ x, y }) {
|
||||
if (!this.dragState.gridMode || !this.dragState.currentDropzoneEl) {
|
||||
return;
|
||||
}
|
||||
const columnEl = this.$target[0];
|
||||
const rowEl = columnEl.parentNode;
|
||||
|
||||
@@ -1664,9 +1659,9 @@ var SnippetEditor = Widget.extend({
|
||||
const columnWidth = columnEl.clientWidth + 2 * borderWidth;
|
||||
|
||||
// Placing the column where the mouse is.
|
||||
let top = ev.pageY - rowElTop - this.mousePositionYOnElement;
|
||||
let top = y - rowElTop - this.mousePositionYOnElement;
|
||||
const bottom = top + columnHeight;
|
||||
let left = ev.pageX - rowElLeft - this.mousePositionXOnElement;
|
||||
let left = x - rowElLeft - this.mousePositionXOnElement;
|
||||
|
||||
// Horizontal & vertical overflow.
|
||||
left = clamp(left, 0, rowEl.clientWidth - columnWidth);
|
||||
@@ -1912,7 +1907,7 @@ var SnippetsMenu = Widget.extend({
|
||||
id: 'oe_manipulators',
|
||||
});
|
||||
this.$body.prepend(this.$snippetEditorArea);
|
||||
this.options.getScrollOptions = this._getScrollOptions.bind(this);
|
||||
this.options.getDragAndDropOptions = this._getDragAndDropOptions.bind(this);
|
||||
|
||||
// Add tooltips on we-title elements whose text overflows and on all
|
||||
// elements with available tooltip text. Note that the tooltips of the
|
||||
@@ -3068,7 +3063,7 @@ var SnippetsMenu = Widget.extend({
|
||||
this.$el.html($html);
|
||||
this.$el.append(this.customizePanel);
|
||||
this.$el.append(this.invisibleDOMPanelEl);
|
||||
this._makeSnippetDraggable(this.$snippets);
|
||||
this._makeSnippetDraggable();
|
||||
this._disableUndroppableSnippets();
|
||||
|
||||
this.$el.addClass('o_loaded');
|
||||
@@ -3212,18 +3207,14 @@ var SnippetsMenu = Widget.extend({
|
||||
* @param {Object} [options={}]
|
||||
* @returns {Object}
|
||||
*/
|
||||
_getScrollOptions(options = {}) {
|
||||
_getDragAndDropOptions(options = {}) {
|
||||
let iframeWindow = false;
|
||||
if (this.$body[0].ownerDocument.defaultView !== window) {
|
||||
iframeWindow = this.$body[0].ownerDocument.defaultView;
|
||||
}
|
||||
return Object.assign({}, options, {
|
||||
scrollBoundaries: Object.assign({
|
||||
right: false,
|
||||
}, options.scrollBoundaries),
|
||||
jQueryDraggableOptions: Object.assign({
|
||||
appendTo: this.$body,
|
||||
cursor: 'move',
|
||||
greedy: true,
|
||||
scroll: false,
|
||||
}, options.jQueryDraggableOptions),
|
||||
disableHorizontalScroll: true,
|
||||
iframeWindow,
|
||||
cursor: "move",
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -3285,10 +3276,11 @@ var SnippetsMenu = Widget.extend({
|
||||
* Make given snippets be draggable/droppable thanks to their thumbnail.
|
||||
*
|
||||
* @private
|
||||
* @param {jQuery} $snippets
|
||||
*/
|
||||
_makeSnippetDraggable: function ($snippets) {
|
||||
var self = this;
|
||||
_makeSnippetDraggable() {
|
||||
if (this.draggableComponent) {
|
||||
this.draggableComponent.destroy();
|
||||
}
|
||||
var $toInsert, dropped, $snippet;
|
||||
let $dropZones;
|
||||
|
||||
@@ -3298,178 +3290,179 @@ var SnippetsMenu = Widget.extend({
|
||||
$scrollingElement = $(this.ownerDocument).find('.o_editable');
|
||||
}
|
||||
|
||||
const smoothScrollOptions = this._getScrollOptions({
|
||||
jQueryDraggableOptions: {
|
||||
handle: '.oe_snippet_thumbnail:not(.o_we_already_dragging)',
|
||||
cancel: '.oe_snippet.o_disabled',
|
||||
helper: function () {
|
||||
const dragSnip = this.cloneNode(true);
|
||||
dragSnip.querySelectorAll('.o_delete_btn, .o_rename_btn').forEach(
|
||||
el => el.remove()
|
||||
);
|
||||
self.$el[0].ownerDocument.body.append(dragSnip);
|
||||
return dragSnip;
|
||||
},
|
||||
start: function () {
|
||||
self._hideSnippetTooltips();
|
||||
const dragAndDropOptions = this.options.getDragAndDropOptions({
|
||||
el: this.$el[0],
|
||||
elements: ".oe_snippet",
|
||||
scrollingElement: $scrollingElement[0],
|
||||
handle: '.oe_snippet_thumbnail:not(.o_we_already_dragging)',
|
||||
cancel: '.oe_snippet.o_disabled',
|
||||
dropzones: () => {
|
||||
return $dropZones.toArray();
|
||||
},
|
||||
helper: ({ element, elementRect, helperOffset, x, y }) => {
|
||||
const dragSnip = element.cloneNode(true);
|
||||
dragSnip.querySelectorAll('.o_delete_btn, .o_rename_btn').forEach(
|
||||
el => el.remove()
|
||||
);
|
||||
dragSnip.style.position = "fixed";
|
||||
this.$el[0].ownerDocument.body.append(dragSnip);
|
||||
// Prepare the offset of the helper to be at the position it was dragged from
|
||||
helperOffset.x = x - elementRect.x;
|
||||
helperOffset.y = y - elementRect.y;
|
||||
return dragSnip;
|
||||
},
|
||||
onDragStart: ({ element }) => {
|
||||
this._hideSnippetTooltips();
|
||||
|
||||
const prom = new Promise(resolve => dragAndDropResolve = () => resolve());
|
||||
self._mutex.exec(() => prom);
|
||||
const prom = new Promise(resolve => dragAndDropResolve = () => resolve());
|
||||
this._mutex.exec(() => prom);
|
||||
|
||||
const doc = self.options.wysiwyg.odooEditor.document;
|
||||
$(doc.body).addClass('oe_dropzone_active');
|
||||
const doc = this.options.wysiwyg.odooEditor.document;
|
||||
$(doc.body).addClass('oe_dropzone_active');
|
||||
|
||||
self.options.wysiwyg.odooEditor.automaticStepUnactive();
|
||||
this.options.wysiwyg.odooEditor.automaticStepUnactive();
|
||||
|
||||
self.$el.find('.oe_snippet_thumbnail').addClass('o_we_already_dragging');
|
||||
self.options.wysiwyg.odooEditor.observerUnactive('dragAndDropCreateSnippet');
|
||||
this.$el.find('.oe_snippet_thumbnail').addClass('o_we_already_dragging');
|
||||
this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropCreateSnippet');
|
||||
|
||||
dropped = false;
|
||||
$snippet = $(this);
|
||||
var $baseBody = $snippet.find('.oe_snippet_body');
|
||||
var $selectorSiblings = $();
|
||||
var $selectorChildren = $();
|
||||
const selectorExcludeAncestor = [];
|
||||
var temp = self.templateOptions;
|
||||
for (var k in temp) {
|
||||
if ($baseBody.is(temp[k].base_selector) && !$baseBody.is(temp[k].base_exclude)) {
|
||||
if (temp[k]['drop-near']) {
|
||||
$selectorSiblings = $selectorSiblings.add(temp[k]['drop-near'].all());
|
||||
}
|
||||
if (temp[k]['drop-in']) {
|
||||
$selectorChildren = $selectorChildren.add(temp[k]['drop-in'].all());
|
||||
}
|
||||
if (temp[k]['drop-exclude-ancestor']) {
|
||||
selectorExcludeAncestor.push(temp[k]['drop-exclude-ancestor']);
|
||||
}
|
||||
dropped = false;
|
||||
$snippet = $(element);
|
||||
var $baseBody = $snippet.find('.oe_snippet_body');
|
||||
var $selectorSiblings = $();
|
||||
var $selectorChildren = $();
|
||||
const selectorExcludeAncestor = [];
|
||||
var temp = this.templateOptions;
|
||||
for (var k in temp) {
|
||||
if ($baseBody.is(temp[k].base_selector) && !$baseBody.is(temp[k].base_exclude)) {
|
||||
if (temp[k]['drop-near']) {
|
||||
$selectorSiblings = $selectorSiblings.add(temp[k]['drop-near'].all());
|
||||
}
|
||||
if (temp[k]['drop-in']) {
|
||||
$selectorChildren = $selectorChildren.add(temp[k]['drop-in'].all());
|
||||
}
|
||||
if (temp[k]['drop-exclude-ancestor']) {
|
||||
selectorExcludeAncestor.push(temp[k]['drop-exclude-ancestor']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent dropping an element into another one.
|
||||
// (E.g. ToC inside another ToC)
|
||||
for (const excludedAncestorSelector of selectorExcludeAncestor) {
|
||||
$selectorSiblings = $selectorSiblings.filter((i, el) => !el.closest(excludedAncestorSelector));
|
||||
$selectorChildren = $selectorChildren.filter((i, el) => !el.closest(excludedAncestorSelector));
|
||||
}
|
||||
// Prevent dropping an element into another one.
|
||||
// (E.g. ToC inside another ToC)
|
||||
for (const excludedAncestorSelector of selectorExcludeAncestor) {
|
||||
$selectorSiblings = $selectorSiblings.filter((i, el) => !el.closest(excludedAncestorSelector));
|
||||
$selectorChildren = $selectorChildren.filter((i, el) => !el.closest(excludedAncestorSelector));
|
||||
}
|
||||
|
||||
$toInsert = $baseBody.clone();
|
||||
// Color-customize dynamic SVGs in dropped snippets with current theme colors.
|
||||
[...$toInsert.find('img[src^="/web_editor/shape/"]')].forEach(dynamicSvg => {
|
||||
const colorCustomizedURL = new URL(dynamicSvg.getAttribute('src'), window.location.origin);
|
||||
colorCustomizedURL.searchParams.forEach((value, key) => {
|
||||
const match = key.match(/^c([1-5])$/);
|
||||
if (match) {
|
||||
colorCustomizedURL.searchParams.set(key, weUtils.getCSSVariableValue(`o-color-${match[1]}`));
|
||||
}
|
||||
});
|
||||
dynamicSvg.src = colorCustomizedURL.pathname + colorCustomizedURL.search;
|
||||
$toInsert = $baseBody.clone();
|
||||
// Color-customize dynamic SVGs in dropped snippets with current theme colors.
|
||||
[...$toInsert.find('img[src^="/web_editor/shape/"]')].forEach(dynamicSvg => {
|
||||
const colorCustomizedURL = new URL(dynamicSvg.getAttribute('src'), window.location.origin);
|
||||
colorCustomizedURL.searchParams.forEach((value, key) => {
|
||||
const match = key.match(/^c([1-5])$/);
|
||||
if (match) {
|
||||
colorCustomizedURL.searchParams.set(key, weUtils.getCSSVariableValue(`o-color-${match[1]}`));
|
||||
}
|
||||
});
|
||||
dynamicSvg.src = colorCustomizedURL.pathname + colorCustomizedURL.search;
|
||||
});
|
||||
|
||||
if (!$selectorSiblings.length && !$selectorChildren.length) {
|
||||
console.warn($snippet.find('.oe_snippet_thumbnail_title').text() + " have not insert action: data-drop-near or data-drop-in");
|
||||
return;
|
||||
}
|
||||
if (!$selectorSiblings.length && !$selectorChildren.length) {
|
||||
console.warn($snippet.find('.oe_snippet_thumbnail_title').text() + " have not insert action: data-drop-near or data-drop-in");
|
||||
return;
|
||||
}
|
||||
|
||||
const forbidSanitize = $snippet.data('oeForbidSanitize');
|
||||
const canBeSanitizedUnless = forbidSanitize === 'form' ? 'form' : !forbidSanitize;
|
||||
// Specific case for inline snippet (e.g. "s_badge")
|
||||
const forbidSanitize = $snippet.data('oeForbidSanitize');
|
||||
const canBeSanitizedUnless = forbidSanitize === 'form' ? 'form' : !forbidSanitize;
|
||||
// Specific case for inline snippet (e.g. "s_badge")
|
||||
$baseBody[0].classList.remove("oe_snippet_body");
|
||||
const toInsertInline = window.getComputedStyle($baseBody[0]).display.includes('inline');
|
||||
$baseBody[0].classList.add("oe_snippet_body");
|
||||
self._activateInsertionZones($selectorSiblings, $selectorChildren, canBeSanitizedUnless, toInsertInline);
|
||||
$dropZones = self.getEditableArea().find('.oe_drop_zone');
|
||||
if (forbidSanitize === 'form') {
|
||||
$dropZones = $dropZones.filter((i, el) => !el.closest('[data-oe-sanitize]:not([data-oe-sanitize="allow_form"]) .oe_drop_zone'));
|
||||
} else if (forbidSanitize) {
|
||||
$dropZones = $dropZones.filter((i, el) => !el.closest('[data-oe-sanitize] .oe_drop_zone'));
|
||||
this._activateInsertionZones($selectorSiblings, $selectorChildren, canBeSanitizedUnless, toInsertInline);
|
||||
$dropZones = this.getEditableArea().find('.oe_drop_zone');
|
||||
if (forbidSanitize === 'form') {
|
||||
$dropZones = $dropZones.filter((i, el) => !el.closest('[data-oe-sanitize]:not([data-oe-sanitize="allow_form"]) .oe_drop_zone'));
|
||||
} else if (forbidSanitize) {
|
||||
$dropZones = $dropZones.filter((i, el) => !el.closest('[data-oe-sanitize] .oe_drop_zone'));
|
||||
}
|
||||
// If a modal is open, the scroll target must be that modal
|
||||
const $openModal = this.getEditableArea().find('.modal:visible');
|
||||
if ($openModal.length) {
|
||||
this.draggableComponent.update({ scrollingElement: $openModal[0]});
|
||||
$scrollingElement = $openModal;
|
||||
}
|
||||
this.trigger_up('drop_zone_start');
|
||||
},
|
||||
dropzoneOver: ({ dropzone }) => {
|
||||
if (dropped) {
|
||||
$toInsert.detach();
|
||||
$toInsert.addClass('oe_snippet_body');
|
||||
[...$dropZones].forEach(dropzoneEl =>
|
||||
dropzoneEl.classList.remove("invisible"));
|
||||
}
|
||||
dropped = true;
|
||||
$(dropzone.el).first().after($toInsert).addClass('invisible');
|
||||
$toInsert.removeClass('oe_snippet_body');
|
||||
this.trigger_up('drop_zone_over');
|
||||
},
|
||||
dropzoneOut: ({ dropzone }) => {
|
||||
var prev = $toInsert.prev();
|
||||
if (dropzone.el === prev[0]) {
|
||||
dropped = false;
|
||||
$toInsert.detach();
|
||||
$(dropzone.el).removeClass('invisible');
|
||||
$toInsert.addClass('oe_snippet_body');
|
||||
}
|
||||
this.trigger_up('drop_zone_out');
|
||||
},
|
||||
onDragEnd: async ({ x, y, helper }) => {
|
||||
const doc = this.options.wysiwyg.odooEditor.document;
|
||||
$(doc.body).removeClass('oe_dropzone_active');
|
||||
this.options.wysiwyg.odooEditor.automaticStepUnactive();
|
||||
this.options.wysiwyg.odooEditor.automaticStepSkipStack();
|
||||
$toInsert.removeClass('oe_snippet_body');
|
||||
$scrollingElement.off('scroll.scrolling_element');
|
||||
if (!dropped && y > 3 && x + helper.getBoundingClientRect().height < this.el.getBoundingClientRect().left) {
|
||||
const point = { x, y };
|
||||
let droppedOnNotNearest = touching(doc.body.querySelectorAll('.oe_structure_not_nearest'), point);
|
||||
// If dropped outside of a dropzone with class oe_structure_not_nearest,
|
||||
// move the snippet to the nearest dropzone without it
|
||||
const selector = droppedOnNotNearest
|
||||
? '.oe_drop_zone'
|
||||
: ':not(.oe_structure_not_nearest) > .oe_drop_zone';
|
||||
let $el = $(closest(doc.body.querySelectorAll(selector), point));
|
||||
// Some drop zones might have been disabled.
|
||||
$el = $el.filter($dropZones);
|
||||
if ($el.length) {
|
||||
$el.after($toInsert);
|
||||
dropped = true;
|
||||
}
|
||||
$dropZones.droppable({
|
||||
over: function () {
|
||||
if (dropped) {
|
||||
$toInsert.detach();
|
||||
$toInsert.addClass('oe_snippet_body');
|
||||
[...$dropZones].forEach(dropzoneEl =>
|
||||
dropzoneEl.classList.remove("invisible"));
|
||||
}
|
||||
dropped = true;
|
||||
$(this).first().after($toInsert).addClass('invisible');
|
||||
$toInsert.removeClass('oe_snippet_body');
|
||||
self.trigger_up('drop_zone_over');
|
||||
},
|
||||
out: function () {
|
||||
var prev = $toInsert.prev();
|
||||
if (this === prev[0]) {
|
||||
dropped = false;
|
||||
$toInsert.detach();
|
||||
$(this).removeClass('invisible');
|
||||
$toInsert.addClass('oe_snippet_body');
|
||||
}
|
||||
self.trigger_up('drop_zone_out');
|
||||
},
|
||||
});
|
||||
// If a modal is open, the scroll target must be that modal
|
||||
const $openModal = self.getEditableArea().find('.modal:visible');
|
||||
if ($openModal.length) {
|
||||
self.draggableComponent.$scrollTarget = $openModal;
|
||||
}
|
||||
|
||||
this.getEditableArea().find('.oe_drop_zone').remove();
|
||||
|
||||
let $toInsertParent;
|
||||
let prev;
|
||||
let next;
|
||||
if (dropped) {
|
||||
prev = $toInsert.first()[0].previousSibling;
|
||||
next = $toInsert.last()[0].nextSibling;
|
||||
|
||||
$toInsertParent = $toInsert.parent();
|
||||
$toInsert.detach();
|
||||
}
|
||||
|
||||
this.options.wysiwyg.odooEditor.observerActive('dragAndDropCreateSnippet');
|
||||
|
||||
if (dropped) {
|
||||
if (prev) {
|
||||
$toInsert.insertAfter(prev);
|
||||
} else if (next) {
|
||||
$toInsert.insertBefore(next);
|
||||
} else {
|
||||
$toInsertParent.prepend($toInsert);
|
||||
}
|
||||
|
||||
// Trigger a scroll on the draggable element so that jQuery updates
|
||||
// the position of the drop zones.
|
||||
self.draggableComponent.$scrollTarget.on('scroll.scrolling_element', function () {
|
||||
self.$el.trigger('scroll');
|
||||
});
|
||||
self.trigger_up('drop_zone_start');
|
||||
},
|
||||
stop: async function (ev, ui) {
|
||||
const doc = self.options.wysiwyg.odooEditor.document;
|
||||
$(doc.body).removeClass('oe_dropzone_active');
|
||||
self.options.wysiwyg.odooEditor.automaticStepUnactive();
|
||||
self.options.wysiwyg.odooEditor.automaticStepSkipStack();
|
||||
$toInsert.removeClass('oe_snippet_body');
|
||||
self.draggableComponent.$scrollTarget.off('scroll.scrolling_element');
|
||||
if (!dropped && ui.position.top > 3 && ui.position.left + ui.helper.outerHeight() < self.el.getBoundingClientRect().left) {
|
||||
const point = {x: ui.position.left, y: ui.position.top};
|
||||
let droppedOnNotNearest = touching(doc.body.querySelectorAll('.oe_structure_not_nearest'), point);
|
||||
// If dropped outside of a dropzone with class oe_structure_not_nearest,
|
||||
// move the snippet to the nearest dropzone without it
|
||||
const selector = droppedOnNotNearest
|
||||
? '.oe_drop_zone'
|
||||
: ':not(.oe_structure_not_nearest) > .oe_drop_zone';
|
||||
let $el = $(closest(doc.body.querySelectorAll(selector), point));
|
||||
// Some drop zones might have been disabled.
|
||||
$el = $el.filter($dropZones);
|
||||
if ($el.length) {
|
||||
$el.after($toInsert);
|
||||
dropped = true;
|
||||
}
|
||||
}
|
||||
|
||||
$dropZones.droppable('destroy');
|
||||
self.getEditableArea().find('.oe_drop_zone').remove();
|
||||
|
||||
let $toInsertParent;
|
||||
let prev;
|
||||
let next;
|
||||
if (dropped) {
|
||||
prev = $toInsert.first()[0].previousSibling;
|
||||
next = $toInsert.last()[0].nextSibling;
|
||||
|
||||
$toInsertParent = $toInsert.parent();
|
||||
$toInsert.detach();
|
||||
}
|
||||
|
||||
self.options.wysiwyg.odooEditor.observerActive('dragAndDropCreateSnippet');
|
||||
|
||||
if (dropped) {
|
||||
if (prev) {
|
||||
$toInsert.insertAfter(prev);
|
||||
} else if (next) {
|
||||
$toInsert.insertBefore(next);
|
||||
} else {
|
||||
$toInsertParent.prepend($toInsert);
|
||||
}
|
||||
|
||||
var $target = $toInsert;
|
||||
var $target = $toInsert;
|
||||
|
||||
if ($target[0].classList.contains("o_snippet_drop_in_only")) {
|
||||
// If it's a "drop in only" snippet, after dropping
|
||||
@@ -3481,36 +3474,35 @@ var SnippetsMenu = Widget.extend({
|
||||
delete $target[0].dataset.name;
|
||||
}
|
||||
|
||||
self.options.wysiwyg.odooEditor.observerUnactive('dragAndDropCreateSnippet');
|
||||
await self._scrollToSnippet($target, self.$scrollable);
|
||||
self.options.wysiwyg.odooEditor.observerActive('dragAndDropCreateSnippet');
|
||||
this.options.wysiwyg.odooEditor.observerUnactive('dragAndDropCreateSnippet');
|
||||
await this._scrollToSnippet($target, this.$scrollable);
|
||||
this.options.wysiwyg.odooEditor.observerActive('dragAndDropCreateSnippet');
|
||||
|
||||
browser.setTimeout(async () => {
|
||||
// Free the mutex now to allow following operations
|
||||
// (mutexed as well).
|
||||
dragAndDropResolve();
|
||||
browser.setTimeout(async () => {
|
||||
// Free the mutex now to allow following operations
|
||||
// (mutexed as well).
|
||||
dragAndDropResolve();
|
||||
|
||||
await self.callPostSnippetDrop($target);
|
||||
await this.callPostSnippetDrop($target);
|
||||
|
||||
// Restore editor to its normal edition state, also
|
||||
// make sure the undroppable snippets are updated.
|
||||
self._disableUndroppableSnippets();
|
||||
self.options.wysiwyg.odooEditor.unbreakableStepUnactive();
|
||||
self.options.wysiwyg.odooEditor.historyStep();
|
||||
self.$el.find('.oe_snippet_thumbnail').removeClass('o_we_already_dragging');
|
||||
});
|
||||
} else {
|
||||
$toInsert.remove();
|
||||
if (dragAndDropResolve) {
|
||||
dragAndDropResolve();
|
||||
}
|
||||
self.$el.find('.oe_snippet_thumbnail').removeClass('o_we_already_dragging');
|
||||
// Restore editor to its normal edition state, also
|
||||
// make sure the undroppable snippets are updated.
|
||||
this._disableUndroppableSnippets();
|
||||
this.options.wysiwyg.odooEditor.unbreakableStepUnactive();
|
||||
this.options.wysiwyg.odooEditor.historyStep();
|
||||
this.$el.find('.oe_snippet_thumbnail').removeClass('o_we_already_dragging');
|
||||
});
|
||||
} else {
|
||||
$toInsert.remove();
|
||||
if (dragAndDropResolve) {
|
||||
dragAndDropResolve();
|
||||
}
|
||||
self.trigger_up('drop_zone_stop');
|
||||
},
|
||||
this.$el.find('.oe_snippet_thumbnail').removeClass('o_we_already_dragging');
|
||||
}
|
||||
this.trigger_up('drop_zone_stop');
|
||||
},
|
||||
});
|
||||
this.draggableComponent = new SmoothScrollOnDrag($snippets, $scrollingElement, smoothScrollOptions);
|
||||
this.draggableComponent = useDragAndDrop({ ref: { el: this.el }, ...dragAndDropOptions });
|
||||
},
|
||||
/**
|
||||
* Adds the 'o_default_snippet_text' class on nodes which contain only
|
||||
|
||||
@@ -1,12 +1,39 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import {
|
||||
ancestors,
|
||||
closestElement,
|
||||
resetOuids,
|
||||
setSelection,
|
||||
} from '@web_editor/js/editor/odoo-editor/src/OdooEditor';
|
||||
import { SmoothScrollOnDrag } from "@web_editor/js/editor/smooth_scroll_on_drag";
|
||||
import { useNativeDraggable } from "@web_editor/js/editor/drag_and_drop";
|
||||
|
||||
const simpleDraggableHook = {
|
||||
acceptedParams: {
|
||||
helper: [Function],
|
||||
},
|
||||
edgeScrolling: { enable: true },
|
||||
onComputeParams({ ctx, params }) {
|
||||
ctx.helper = params.helper;
|
||||
ctx.followCursor = false;
|
||||
},
|
||||
onDragStart({ ctx }) {
|
||||
ctx.current.element = ctx.helper();
|
||||
ctx.current.element.style.left = `${ctx.pointer.x + 10}px`;
|
||||
ctx.current.element.style.top = `${ctx.pointer.y + 10}px`;
|
||||
ctx.current.element.style.position = "fixed";
|
||||
// makeDraggableHook disables pointer events, we want them in this case
|
||||
document.body.classList.remove("pe-none");
|
||||
return ctx.current;
|
||||
},
|
||||
onDrag({ ctx }) {
|
||||
ctx.current.element.style.left = `${ctx.pointer.x}px`;
|
||||
ctx.current.element.style.top = `${ctx.pointer.y}px`;
|
||||
},
|
||||
onDragEnd({ ctx }) {
|
||||
ctx.current.element.remove();
|
||||
return ctx.current;
|
||||
},
|
||||
};
|
||||
|
||||
const WIDGET_CONTAINER_WIDTH = 25;
|
||||
const WIDGET_MOVE_SIZE = 20;
|
||||
@@ -231,28 +258,24 @@ export class MoveNodePlugin {
|
||||
|
||||
if (this._scrollableElement) {
|
||||
this.smoothScrollOnDrag && this.smoothScrollOnDrag.destroy();
|
||||
this.smoothScrollOnDrag = new SmoothScrollOnDrag($(this._moveWidget), $(this._scrollableElement), {
|
||||
scrollBoundaries: {
|
||||
right: false,
|
||||
},
|
||||
jQueryDraggableOptions: {
|
||||
start: () => this._startDropzones(movableElement, containerRect),
|
||||
stop: () => this._stopDropzones(movableElement),
|
||||
helper: () => {
|
||||
const container = document.createElement('div');
|
||||
container.append(movableElement.cloneNode(true))
|
||||
const style = getComputedStyle(movableElement);
|
||||
container.style.height = style.height;
|
||||
container.style.width = style.width;
|
||||
container.style.paddingLeft = '25px';
|
||||
container.style.opacity = '0.4';
|
||||
return $(container);
|
||||
},
|
||||
appendTo: this._dragHelperContainer,
|
||||
cursor: 'move',
|
||||
scroll: false,
|
||||
},
|
||||
disableHorizontalScroll: true,
|
||||
// TODO: This should be made more generic, one hook for the entire
|
||||
// editable with each element handled.
|
||||
this.smoothScrollOnDrag = useNativeDraggable(simpleDraggableHook, {
|
||||
ref: { el: this._widgetContainer },
|
||||
elements: ".oe-sidewidget-move",
|
||||
onDragStart: () => this._startDropzones(movableElement, containerRect),
|
||||
onDragEnd: () => this._stopDropzones(movableElement),
|
||||
helper: () => {
|
||||
const container = document.createElement('div');
|
||||
container.append(movableElement.cloneNode(true));
|
||||
const style = getComputedStyle(movableElement);
|
||||
container.style.height = style.height;
|
||||
container.style.width = style.width;
|
||||
container.style.paddingLeft = '25px';
|
||||
container.style.opacity = '0.4';
|
||||
this._dragHelperContainer.append(container);
|
||||
return container;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
width: 77px;
|
||||
background-color: $o-we-sidebar-blocks-content-snippet-bg;
|
||||
|
||||
&.ui-draggable-dragging {
|
||||
&.o_draggable_dragging {
|
||||
transform: rotate(-3deg) scale(1.2);
|
||||
box-shadow: 0 5px 25px -10px black;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
@@ -2467,7 +2467,7 @@ we-select.o_we_border_preview_aligned_select {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
.oe_overlay.ui-draggable-dragging .o_handles {
|
||||
.oe_overlay.o_draggable_dragging .o_handles {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -203,7 +203,10 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_getScrollOptions(options = {}) {
|
||||
_getDragAndDropOptions(options = {}) {
|
||||
// TODO: This is currently not in use by Odoo's D&D
|
||||
// There is currently no way in Odoo D&D to offset the edge scrolling.
|
||||
// When there is, this code should be adapted.
|
||||
const finalOptions = this._super(...arguments);
|
||||
if (!options.offsetElements || !options.offsetElements.$top) {
|
||||
const $header = $('#top');
|
||||
@@ -212,7 +215,6 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
|
||||
finalOptions.offsetElements.$top = $header;
|
||||
}
|
||||
}
|
||||
finalOptions.jQueryDraggableOptions.iframeFix = true;
|
||||
return finalOptions;
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -69,7 +69,7 @@ function selectColorPalette(position = "left") {
|
||||
|
||||
function changeColumnSize(position = "right") {
|
||||
return {
|
||||
trigger: `iframe .oe_overlay.ui-draggable.o_we_overlay_sticky.oe_active .o_handle.e`,
|
||||
trigger: `iframe .oe_overlay.o_draggable.o_we_overlay_sticky.oe_active .o_handle.e`,
|
||||
content: markup(_t("<b>Slide</b> this button to change the column size.")),
|
||||
position: position,
|
||||
};
|
||||
@@ -133,7 +133,7 @@ function changePaddingSize(direction) {
|
||||
position = "bottom";
|
||||
}
|
||||
return {
|
||||
trigger: `iframe .oe_overlay.ui-draggable.o_we_overlay_sticky.oe_active .o_handle.${paddingDirection}`,
|
||||
trigger: `iframe .oe_overlay.o_draggable.o_we_overlay_sticky.oe_active .o_handle.${paddingDirection}`,
|
||||
content: markup(_t("<b>Slide</b> this button to change the %s padding", direction)),
|
||||
consumeEvent: 'mousedown',
|
||||
position: position,
|
||||
@@ -239,7 +239,7 @@ function dragNDrop(snippet, position = "bottom") {
|
||||
position: position,
|
||||
// Normally no main snippet can be dropped in the default footer but
|
||||
// targeting it allows to force "dropping at the end of the page".
|
||||
run: "drag_and_drop iframe #wrapwrap > footer",
|
||||
run: "drag_and_drop_native iframe #wrapwrap > footer",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -171,8 +171,8 @@ wTourUtils.clickOnSnippet(snippets[0]),
|
||||
wTourUtils.clickOnSnippet(snippets[1]),
|
||||
{
|
||||
content: "Drag the 'Banner' snippet to the end of the page",
|
||||
trigger: "iframe .o_overlay_move_options .ui-draggable-handle",
|
||||
run: "drag_and_drop iframe #wrapwrap footer",
|
||||
trigger: "iframe .o_overlay_move_options .o_move_handle",
|
||||
run: "drag_and_drop_native iframe #wrapwrap footer",
|
||||
},
|
||||
...checkEyesIconAfterSave(false),
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ function clickAndCheck(blockID, expected) {
|
||||
}, {
|
||||
content: 'Once the related overlays are enabled/disabled, check that the focus/blur calls have been correct.',
|
||||
trigger: blockID
|
||||
? `iframe .oe_overlay.ui-draggable:eq(${blockData.overlayIndex}).oe_active`
|
||||
? `iframe .oe_overlay.o_draggable:eq(${blockData.overlayIndex}).oe_active`
|
||||
: `iframe #oe_manipulators:not(:has(.oe_active))`,
|
||||
allowInvisible: !blockID,
|
||||
run: function (actions) {
|
||||
@@ -57,7 +57,7 @@ wTourUtils.registerWebsitePreviewTour("focus_blur_snippets", {
|
||||
{
|
||||
content: 'Drag the custom block into the page',
|
||||
trigger: '#snippet_structure .oe_snippet:has(.oe_snippet_body.s_focusblur) .oe_snippet_thumbnail',
|
||||
run: 'drag_and_drop iframe #wrap',
|
||||
run: 'drag_and_drop_native iframe #wrap',
|
||||
},
|
||||
...clickAndCheck('parent', ['focus parent']),
|
||||
...clickAndCheck(null, ['blur parent']),
|
||||
|
||||
@@ -16,7 +16,7 @@ wTourUtils.registerWebsitePreviewTour('html_editor_multiple_templates', {
|
||||
content: "drop a snippet",
|
||||
trigger: ".oe_snippet:has(.s_cover) .oe_snippet_thumbnail",
|
||||
// id starting by 'oe_structure..' will actually create an inherited view
|
||||
run: "drag_and_drop iframe #oe_structure_test_ui",
|
||||
run: "drag_and_drop_native iframe #oe_structure_test_ui",
|
||||
},
|
||||
...wTourUtils.clickOnSave(),
|
||||
// 2. Edit generic view
|
||||
|
||||
@@ -107,7 +107,7 @@ wTourUtils.dragNDrop({
|
||||
trigger: '.snippet-option-layout_column [data-select-count="3"]',
|
||||
}, {
|
||||
content: "The snippet should have the correct number of columns.",
|
||||
trigger: 'iframe .s_text_block .container > .row',
|
||||
trigger: 'iframe .s_text_block .container > .row .col-lg-4:eq(3)',
|
||||
run() {
|
||||
if (this.$anchor[0].childElementCount !== 3) {
|
||||
console.error("The snippet does not have the correct number of columns");
|
||||
|
||||
@@ -83,7 +83,7 @@ wTourUtils.registerWebsitePreviewTour("snippet_popup_and_animations", {
|
||||
content: "Drag the Columns building block and drop it at the bottom of the popup.",
|
||||
trigger: '#oe_snippets .oe_snippet[name="Columns"] .oe_snippet_thumbnail:not(.o_we_already_dragging)',
|
||||
extra_trigger: ".o_website_preview.editor_enable.editor_has_snippets",
|
||||
run: "drag_and_drop iframe #wrapwrap .modal-content .s_media_list .container > .row > :last-child",
|
||||
run: "drag_and_drop_native iframe #wrapwrap .modal-content .s_media_list .container > .row > :last-child",
|
||||
},
|
||||
wTourUtils.clickOnElement("3rd columns", "iframe .s_popup .s_three_columns .row > :last-child"),
|
||||
...setOnScrollAnim(),
|
||||
|
||||
@@ -52,7 +52,7 @@ wTourUtils.registerWebsitePreviewTour("snippet_popup_and_scrollbar", {
|
||||
{
|
||||
content: "Drag the Media List block and drop it in the popup.",
|
||||
trigger: "#oe_snippets .oe_snippet:has(> [data-snippet='s_media_list']) .oe_snippet_thumbnail",
|
||||
run: "drag_and_drop iframe #wrap .s_popup .modal-content.oe_structure",
|
||||
run: "drag_and_drop_native iframe #wrap .s_popup .modal-content.oe_structure",
|
||||
},
|
||||
checkScrollbar(false),
|
||||
{
|
||||
@@ -89,7 +89,7 @@ wTourUtils.registerWebsitePreviewTour("snippet_popup_and_scrollbar", {
|
||||
{
|
||||
content: "Drag the Media List block and drop it in the popup.",
|
||||
trigger: "#oe_snippets .oe_snippet:has(> [data-snippet='s_media_list']) .oe_snippet_thumbnail",
|
||||
run: "drag_and_drop iframe #wrap .s_popup .modal-content.oe_structure",
|
||||
run: "drag_and_drop_native iframe #wrap .s_popup .modal-content.oe_structure",
|
||||
},
|
||||
checkScrollbar(false),
|
||||
{
|
||||
@@ -108,7 +108,7 @@ wTourUtils.registerWebsitePreviewTour("snippet_popup_and_scrollbar", {
|
||||
{
|
||||
content: "Drag a Media List snippet and drop it in the Cookies Bar.",
|
||||
trigger: "#oe_snippets .oe_snippet:has(> [data-snippet='s_media_list']) .oe_snippet_thumbnail",
|
||||
run: "drag_and_drop iframe #website_cookies_bar .modal-content.oe_structure",
|
||||
run: "drag_and_drop_native iframe #website_cookies_bar .modal-content.oe_structure",
|
||||
},
|
||||
{
|
||||
content: "Select the Media List snippet in the Cookies Bar.",
|
||||
|
||||
@@ -47,6 +47,8 @@ wTourUtils.registerWebsitePreviewTour('snippet_table_of_content', {
|
||||
content: "Hide the first TOC on mobile",
|
||||
trigger: '[data-toggle-device-visibility="no_mobile"]',
|
||||
},
|
||||
// Go back to blocks tabs to avoid changing the first ToC options
|
||||
wTourUtils.goBackToBlocks(),
|
||||
{
|
||||
content: "Click on the second TOC's title",
|
||||
trigger: 'iframe .s_table_of_content:eq(1) h2',
|
||||
|
||||
@@ -17,10 +17,10 @@ wTourUtils.registerWebsitePreviewTour("snippet_version", {
|
||||
}),
|
||||
{
|
||||
content: "Test t-snippet and t-snippet-call: snippets have data-snippet set",
|
||||
trigger: '#oe_snippets .o_panel_body > .oe_snippet.ui-draggable',
|
||||
trigger: '#oe_snippets .o_panel_body > .oe_snippet',
|
||||
run: function () {
|
||||
// Tests done here as all these are not visible on the page
|
||||
const draggableSnippets = document.querySelectorAll('#oe_snippets .o_panel_body > .oe_snippet.ui-draggable > :nth-child(2)');
|
||||
const draggableSnippets = document.querySelectorAll('#oe_snippets .o_panel_body > .oe_snippet.o_draggable > :nth-child(2)');
|
||||
if (![...draggableSnippets].every(el => el.dataset.snippet)) {
|
||||
console.error("error Some t-snippet are missing their template name");
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ for (const snippet of snippetsNames) {
|
||||
const snippetSteps = [{
|
||||
content: `Drop ${snippet} snippet [${n}/${snippetsNames.length}]`,
|
||||
trigger: `#oe_snippets .oe_snippet:has( > [data-snippet='${snippet}']) .oe_snippet_thumbnail`,
|
||||
run: "drag_and_drop iframe #wrap",
|
||||
run: "drag_and_drop_native iframe #wrap",
|
||||
}, {
|
||||
content: `Edit ${snippet} snippet`,
|
||||
trigger: `iframe #wrap.o_editable [data-snippet='${snippet}']${isModal ? ' .modal.show' : ''}`,
|
||||
|
||||
@@ -12,7 +12,7 @@ wTourUtils.registerWebsitePreviewTour('website_start_cloned_snippet', {
|
||||
id: 's_countdown',
|
||||
};
|
||||
const dragNDropOutOfFooter = wTourUtils.dragNDrop(countdownSnippet);
|
||||
dragNDropOutOfFooter.run = 'drag_and_drop iframe #wrapwrap #wrap';
|
||||
dragNDropOutOfFooter.run = 'drag_and_drop_native iframe #wrapwrap #wrap';
|
||||
return [
|
||||
dragNDropOutOfFooter,
|
||||
wTourUtils.clickOnSnippet(countdownSnippet),
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
{
|
||||
content: "Drop the form snippet",
|
||||
trigger: '#oe_snippets .oe_snippet:has(.s_website_form) .oe_snippet_thumbnail',
|
||||
run: 'drag_and_drop iframe #wrap',
|
||||
run: 'drag_and_drop_native iframe #wrap',
|
||||
}, {
|
||||
content: "Select form by clicking on an input field",
|
||||
extra_trigger: 'iframe .s_website_form_field',
|
||||
@@ -551,7 +551,7 @@
|
||||
{
|
||||
content: "Add the form snippet",
|
||||
trigger: '#oe_snippets .oe_snippet:has(.s_website_form) .oe_snippet_thumbnail',
|
||||
run: 'drag_and_drop iframe #wrap',
|
||||
run: 'drag_and_drop_native iframe #wrap',
|
||||
}, {
|
||||
content: "Select the form by clicking on an input field",
|
||||
extra_trigger: 'iframe .s_website_form_field',
|
||||
|
||||
@@ -22,7 +22,7 @@ patch(EventAdditionalTourSteps.prototype, {
|
||||
trigger: '#oe_snippets.o_loaded div[name="Image - Text"] .oe_snippet_thumbnail',
|
||||
content: markup(_t("<b>Drag and Drop</b> this snippet below the event title.")),
|
||||
position: 'bottom',
|
||||
run: 'drag_and_drop iframe #o_wevent_event_main_col',
|
||||
run: 'drag_and_drop_native iframe #o_wevent_event_main_col',
|
||||
}, {
|
||||
trigger: 'button[data-action="save"]',
|
||||
content: markup(_t("Don't forget to click <b>save</b> when you're done.")),
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
trigger: "#oe_snippets.o_loaded #snippet_structure .oe_snippet:eq(2) .oe_snippet_thumbnail",
|
||||
content: _t("Drag this block and drop it in your page."),
|
||||
position: "bottom",
|
||||
run: "drag_and_drop",
|
||||
run: "drag_and_drop_native iframe #wrapwrap > main",
|
||||
}, {
|
||||
trigger: "button[data-action=save]",
|
||||
content: _t("Once you click on save, your event is updated."),
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
extra_trigger: "body:not(.modal-open)",
|
||||
content: _t("Drag this website block and drop it in your page."),
|
||||
position: "bottom",
|
||||
run: "drag_and_drop",
|
||||
run: "drag_and_drop_native iframe #wrapwrap > main",
|
||||
}, {
|
||||
trigger: "button[data-action=save]",
|
||||
content: markup(_t("Once you click on <b>Save</b>, your product is updated.")),
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ wTourUtils.registerWebsitePreviewTour('category_page_and_products_snippet_editio
|
||||
}, () => [
|
||||
Object.assign(wTourUtils.dragNDrop({id: 's_dynamic_snippet_products', name: 'Products'}), {
|
||||
content: "Drag and drop the product snippet inside the category area",
|
||||
run: 'drag_and_drop iframe #category_header',
|
||||
run: 'drag_and_drop_native iframe #category_header',
|
||||
}),
|
||||
{
|
||||
content: "Click on the product snippet to show its options",
|
||||
|
||||
Reference in New Issue
Block a user