[IMP] web: allow to move dialog
After this commit, the user will be able to move dialogs to see the content hidden behind the dialog. closes odoo/odoo#111654 Task: 3132894 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -3,8 +3,34 @@
|
||||
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
|
||||
import { useActiveElement } from "../ui/ui_service";
|
||||
import { useForwardRefToParent } from "@web/core/utils/hooks";
|
||||
import { Component, useChildSubEnv, useExternalListener, useState } from "@odoo/owl";
|
||||
import { throttleForAnimation } from "@web/core/utils/timing";
|
||||
import { makeDraggableHook } from "../utils/draggable_hook_builder";
|
||||
|
||||
const useDialogDraggable = makeDraggableHook({
|
||||
name: "useDialogDraggable",
|
||||
onWillStartDrag({ ctx, addCleanup, addStyle, getRect }) {
|
||||
const { height, width } = getRect(ctx.current.element);
|
||||
ctx.current.container = document.createElement("div");
|
||||
addStyle(ctx.current.container, {
|
||||
position: "fixed",
|
||||
top: "0",
|
||||
bottom: `${70 - height}px`,
|
||||
left: `${70 - width}px`,
|
||||
right: `${70 - width}px`,
|
||||
});
|
||||
ctx.current.element.after(ctx.current.container);
|
||||
addCleanup(() => ctx.current.container.remove());
|
||||
},
|
||||
onDrop({ ctx, getRect }) {
|
||||
const { top, left } = getRect(ctx.current.element);
|
||||
return {
|
||||
left: left - ctx.current.elementRect.left,
|
||||
top: top - ctx.current.elementRect.top,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
import { Component, useChildSubEnv, useState } from "@odoo/owl";
|
||||
export class Dialog extends Component {
|
||||
setup() {
|
||||
this.modalRef = useForwardRefToParent("modalRef");
|
||||
@@ -15,7 +41,20 @@ export class Dialog extends Component {
|
||||
});
|
||||
this.id = `dialog_${this.data.id}`;
|
||||
useChildSubEnv({ inDialog: true, dialogId: this.id, closeDialog: this.data.close });
|
||||
|
||||
this.position = useState({ left: 0, top: 0 });
|
||||
useDialogDraggable({
|
||||
ref: this.modalRef,
|
||||
elements: ".modal-content",
|
||||
handle: ".modal-header",
|
||||
ignore: "button",
|
||||
edgeScrolling: { enabled: false },
|
||||
onDrop: ({ top, left }) => {
|
||||
this.position.left += left;
|
||||
this.position.top += top;
|
||||
},
|
||||
});
|
||||
const throttledResize = throttleForAnimation(this.onResize.bind(this));
|
||||
useExternalListener(window, "resize", throttledResize);
|
||||
owl.onWillDestroy(() => {
|
||||
if (this.env.isSmall) {
|
||||
this.data.scrollToOrigin();
|
||||
@@ -26,6 +65,15 @@ export class Dialog extends Component {
|
||||
get isFullscreen() {
|
||||
return this.props.fullscreen || this.env.isSmall;
|
||||
}
|
||||
|
||||
get contentStyle() {
|
||||
return `top: ${this.position.top}px; left: ${this.position.left}px;`;
|
||||
}
|
||||
|
||||
onResize() {
|
||||
this.position.left = 0;
|
||||
this.position.top = 0;
|
||||
}
|
||||
}
|
||||
Dialog.template = "web.Dialog";
|
||||
Dialog.props = {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
t-ref="modalRef"
|
||||
>
|
||||
<div class="modal-dialog" t-attf-class="modal-{{props.size}}">
|
||||
<div class="modal-content" t-att-class="props.contentClass">
|
||||
<div class="modal-content" t-att-class="props.contentClass" t-att-style="contentStyle">
|
||||
<header t-if="props.header" class="modal-header">
|
||||
<t t-slot="header" close="data.close" isFullscreen="isFullscreen">
|
||||
<t t-call="web.Dialog.header">
|
||||
|
||||
@@ -645,7 +645,7 @@ export function makeDraggableHook(hookParams) {
|
||||
const { container, element, scrollParent } = current;
|
||||
// Container rect
|
||||
current.containerRect = dom.getRect(container, { adjust: true });
|
||||
if (scrollParent) {
|
||||
if (scrollParent && ctx.edgeScrolling.enabled) {
|
||||
// Adjust container rect according to scrollparent
|
||||
const parentRect = dom.getRect(scrollParent, { adjust: true });
|
||||
current.containerRect.x = Math.max(current.containerRect.x, parentRect.x);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { uiService } from "@web/core/ui/ui_service";
|
||||
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { makeTestEnv } from "../helpers/mock_env";
|
||||
import { click, destroy, getFixture, mount } from "../helpers/utils";
|
||||
import { click, destroy, getFixture, mount, triggerEvent, dragAndDrop } from "../helpers/utils";
|
||||
import { makeFakeDialogService } from "../helpers/mock_services";
|
||||
|
||||
import { Component, useState, onMounted, xml } from "@odoo/owl";
|
||||
@@ -296,4 +296,54 @@ QUnit.module("Components", (hooks) => {
|
||||
"UI owner should be reset to the default (document)"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("dialog can be moved", async (assert) => {
|
||||
class Parent extends Component {
|
||||
static template = xml`<Dialog>content</Dialog>`;
|
||||
static components = { Dialog };
|
||||
}
|
||||
|
||||
await mount(Parent, target, { env: await makeDialogTestEnv() });
|
||||
const content = target.querySelector(".modal-content");
|
||||
assert.strictEqual(content.style.top, "0px");
|
||||
assert.strictEqual(content.style.left, "0px");
|
||||
|
||||
const header = content.querySelector(".modal-header");
|
||||
const headerRect = header.getBoundingClientRect();
|
||||
await dragAndDrop(header, document.body, {
|
||||
// the util function sets the source coordinates at (x; y) + (w/2; h/2)
|
||||
// so we need to move the dialog based on these coordinates.
|
||||
x: headerRect.x + headerRect.width / 2 + 20,
|
||||
y: headerRect.y + headerRect.height / 2 + 50,
|
||||
});
|
||||
assert.strictEqual(content.style.top, "50px");
|
||||
assert.strictEqual(content.style.left, "20px");
|
||||
});
|
||||
|
||||
QUnit.test("dialog's position is reset on resize", async (assert) => {
|
||||
class Parent extends Component {
|
||||
static template = xml`<Dialog>content</Dialog>`;
|
||||
static components = { Dialog };
|
||||
}
|
||||
|
||||
await mount(Parent, target, { env: await makeDialogTestEnv() });
|
||||
const content = target.querySelector(".modal-content");
|
||||
assert.strictEqual(content.style.top, "0px");
|
||||
assert.strictEqual(content.style.left, "0px");
|
||||
|
||||
const header = content.querySelector(".modal-header");
|
||||
const headerRect = header.getBoundingClientRect();
|
||||
await dragAndDrop(header, document.body, {
|
||||
// the util function sets the source coordinates at (x; y) + (w/2; h/2)
|
||||
// so we need to move the dialog based on these coordinates.
|
||||
x: headerRect.x + headerRect.width / 2 + 20,
|
||||
y: headerRect.y + headerRect.height / 2 + 50,
|
||||
});
|
||||
assert.strictEqual(content.style.top, "50px");
|
||||
assert.strictEqual(content.style.left, "20px");
|
||||
|
||||
await triggerEvent(window, null, "resize");
|
||||
assert.strictEqual(content.style.top, "0px");
|
||||
assert.strictEqual(content.style.left, "0px");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user