[IMP] web: adapt dialog for mobile

This commit modifies the dialog to make it more user
friendly on small screen. On small screen, the dialog
opens in fullscreen and saves the scroll position when
opening to restore it on close.

Part-of: odoo/odoo#92475
This commit is contained in:
Michael (mcm)
2022-06-30 09:29:46 +02:00
committed by Aaron Bohy
parent 466cfbfb42
commit b1f072cba4
5 changed files with 109 additions and 4 deletions
@@ -15,6 +15,16 @@ export class Dialog extends Component {
});
this.id = `dialog_${this.data.id}`;
useChildSubEnv({ inDialog: true, dialogId: this.id });
owl.onWillDestroy(() => {
if (this.env.isSmall) {
this.data.scrollToOrigin();
}
});
}
get isFullscreen() {
return this.props.fullscreen || this.env.isSmall;
}
}
Dialog.template = "web.Dialog";
+7 -2
View File
@@ -5,16 +5,21 @@
<div class="o_dialog" t-att-id="id" t-att-class="{ o_inactive_modal: !data.isActive }">
<div role="dialog" class="modal d-block"
tabindex="-1"
t-att-class="{ o_technical_modal: props.technical, o_modal_full: props.fullscreen }"
t-att-class="{ o_technical_modal: props.technical, o_modal_full: isFullscreen }"
t-ref="modalRef"
>
<div class="modal-dialog" t-attf-class="modal-{{props.size}}">
<div class="modal-content" t-att-class="props.contentClass">
<header t-if="props.header" class="modal-header">
<t t-if="isFullscreen">
<button class="btn fa fa-arrow-left" data-dismiss="modal" aria-label="Close" t-on-click="data.close" />
</t>
<h4 class="modal-title text-break">
<t t-esc="props.title"/>
</h4>
<button type="button" class="close" aria-label="Close" tabindex="-1" t-on-click="data.close">×</button>
<t t-if="!isFullscreen">
<button type="button" class="close" aria-label="Close" tabindex="-1" t-on-click="data.close">×</button>
</t>
</header>
<!-- FIXME: WOWL there is a bug on t-portal on owl, in which t-portal don't work on multinode.
To avoid this we place the footer before the body -->
@@ -22,7 +22,7 @@ const { markRaw, reactive } = owl;
export const dialogService = {
/** @returns {DialogServiceInterface} */
start() {
start(env) {
const dialogs = reactive({});
let dialogId = 0;
@@ -52,8 +52,20 @@ export const dialogService = {
id,
class: dialogClass,
props: markRaw({ ...props, close }),
dialogData: { isActive: true, close, id },
dialogData: {
isActive: true,
close,
id,
},
};
if (env.isSmall) {
const scrollOrigin = { top: window.scrollY, left: window.scrollX };
dialog.dialogData.scrollToOrigin = () => {
if (!Object.keys(dialogs).length) {
window.scrollTo(scrollOrigin);
}
};
}
dialogs[id] = dialog;
return close;
@@ -11,6 +11,7 @@ import { errorService } from "@web/core/errors/error_service";
import { ConnectionLostError, RPCError } from "@web/core/network/rpc_service";
import { notificationService } from "@web/core/notifications/notification_service";
import { registry } from "@web/core/registry";
import { uiService } from "@web/core/ui/ui_service";
import { registerCleanup } from "../../helpers/cleanup";
import { makeTestEnv } from "../../helpers/mock_env";
import {
@@ -36,6 +37,7 @@ QUnit.module("Error Service", {
serviceRegistry.add("notification", notificationService);
serviceRegistry.add("rpc", makeFakeRPCService());
serviceRegistry.add("localization", makeFakeLocalizationService());
serviceRegistry.add("ui", uiService);
const windowAddEventListener = browser.addEventListener;
browser.addEventListener = (type, cb) => {
if (type === "unhandledrejection") {
@@ -8,6 +8,7 @@ import {
getFixture,
makeDeferred,
nextTick,
patchWithCleanup,
} from "../helpers/utils";
import { makeView, setupViewRegistries } from "../views/helpers";
@@ -391,4 +392,79 @@ QUnit.module("Mobile Views", ({ beforeEach }) => {
);
}
);
QUnit.test(
`preserve current scroll position on form view while closing dialog`,
async function (assert) {
serverData.views = {
"partner,false,kanban": `
<kanban>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click">
<field name="display_name" />
</div>
</t>
</templates>
</kanban>
`,
"partner,false,search": `
<search />
`,
};
await makeView({
type: "form",
resModel: "partner",
resId: 2,
serverData,
arch: `
<form>
<sheet>
<p style="height:500px" />
<field name="trululu" />
<p style="height:500px" />
</sheet>
</form>
`,
});
let position = { top: 0, left: 0 };
patchWithCleanup(window, {
scrollTo(newPosition) {
position = newPosition;
},
get scrollX() {
return position.left;
},
get scrollY() {
return position.top;
},
});
await clickEdit(fixture);
window.scrollTo({ top: 265, left: 0 });
assert.strictEqual(window.scrollY, 265, "Should have scrolled 265 px vertically");
assert.strictEqual(window.screenLeft, 0, "Should be 0 px from left as it is");
// click on m2o field
await click(fixture, ".o_field_many2one input");
// assert.strictEqual(window.scrollY, 0, "Should have scrolled to top (0) px");
assert.containsOnce(
fixture,
".modal.o_modal_full",
"there should be a many2one modal opened in full screen"
);
// click on back button
await click(fixture, ".modal .modal-header .fa-arrow-left");
assert.strictEqual(
window.scrollY,
265,
"Should have scrolled back to 265 px vertically"
);
assert.strictEqual(window.screenLeft, 0, "Should be 0 px from left as it is");
}
);
});