[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:
Michael (mcm)
2023-03-06 18:52:52 +01:00
parent 15200a122e
commit 13fc4751a4
4 changed files with 103 additions and 5 deletions
+50 -2
View File
@@ -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 = {
+1 -1
View File
@@ -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);
+51 -1
View File
@@ -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");
});
});