Skip to content

Commit 56de71e

Browse files
robinlejFrancoisGe
authored andcommitted
[FIX] html_builder: improve Edit click feedback + hotkeys in iframe
- the admin navbar is only hidden right before the sidebar appears - meanwhile, the Edit button shows a loading spinner between the click and the sidebar opening - Press Escape to toggle the admin navbar and the sidebar - Ctrl + K doesn't focus the browser URL field but opens Odoo search box - F5 or Ctrl + R while in backend stay in backend
1 parent 8f6a7a2 commit 56de71e

File tree

9 files changed

+113
-21
lines changed

9 files changed

+113
-21
lines changed

addons/html_builder/static/src/website_preview/edit_website_systray_item.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component } from "@odoo/owl";
1+
import { Component, useState } from "@odoo/owl";
22
import { useService } from "@web/core/utils/hooks";
33
import { Dropdown } from "@web/core/dropdown/dropdown";
44
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
@@ -18,6 +18,7 @@ export class EditWebsiteSystrayItem extends Component {
1818

1919
setup() {
2020
this.websiteService = useService("website");
21+
this.websiteContext = useState(this.websiteService.context);
2122
}
2223

2324
onClickEditPage() {

addons/html_builder/static/src/website_preview/edit_website_systray_item.xml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,18 @@
22
<templates xml:space="preserve">
33

44
<t t-name="html_builder.EditWebsiteSystrayItem">
5-
<a t-if="!translatable and !this.websiteService.is404" href="#" class="o-website-btn-custo-primary btn d-flex align-items-center rounded-0 border-0 px-3" t-on-click="this.onClickEditPage" accesskey="a">Edit</a>
5+
<button t-if="!translatable and !this.websiteService.is404"
6+
class="o-website-btn-custo-primary btn d-flex align-items-center rounded-0 border-0 px-3"
7+
t-on-click="this.onClickEditPage"
8+
accesskey="a">
9+
<span t-if="websiteContext.edition" role="img" aria-label="Loading" class="fa fa-circle-o-notch fa-spin"/>
10+
<t t-else="">Edit</t>
11+
</button>
612
<Dropdown t-else="">
7-
<button class="o-website-btn-custo-primary btn rounded-0 border-0 px-3" accesskey="a">Edit</button>
13+
<button class="o-website-btn-custo-primary o-dropdown-toggle-custo btn rounded-0 border-0 px-3" accesskey="a">
14+
<span t-if="websiteContext.edition" role="img" aria-label="Loading" class="fa fa-circle-o-notch fa-spin"/>
15+
<t t-else="">Edit</t>
16+
</button>
817
<t t-set-slot="content">
918
<t t-if="this.websiteService.is404">
1019
<t t-if="translatable">

addons/html_builder/static/src/website_preview/website_builder_action.js

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,22 @@ import {
44
onMounted,
55
onWillDestroy,
66
onWillStart,
7+
useEffect,
78
useRef,
89
useState,
910
useSubEnv,
1011
} from "@odoo/owl";
1112
import { LazyComponent, loadBundle } from "@web/core/assets";
1213
import { browser } from "@web/core/browser/browser";
14+
import { getActiveHotkey } from "@web/core/hotkeys/hotkey_service";
1315
import { _t } from "@web/core/l10n/translation";
1416
import { registry } from "@web/core/registry";
1517
import { ResizablePanel } from "@web/core/resizable_panel/resizable_panel";
1618
import { Deferred } from "@web/core/utils/concurrency";
1719
import { uniqueId } from "@web/core/utils/functions";
1820
import { useChildRef, useService } from "@web/core/utils/hooks";
1921
import { effect } from "@web/core/utils/reactive";
22+
import { redirect } from "@web/core/utils/urls";
2023
import { standardActionServiceProps } from "@web/webclient/actions/action_service";
2124
import { AddPageDialog } from "@website/components/dialog/add_page_dialog";
2225
import { ResourceEditor } from "@website/components/resource_editor/resource_editor";
@@ -36,6 +39,7 @@ export class WebsiteBuilder extends Component {
3639
this.websiteService = useService("website");
3740
this.ui = useService("ui");
3841
this.title = useService("title");
42+
this.hotkeyService = useService("hotkey");
3943
this.websiteService.websiteRootInstance = undefined;
4044

4145
this.websiteContent = useRef("iframe");
@@ -44,6 +48,9 @@ export class WebsiteBuilder extends Component {
4448
});
4549
this.state = useState({ isEditing: false, key: 1 });
4650
this.websiteContext = useState(this.websiteService.context);
51+
52+
this.onKeydownRefresh = this._onKeydownRefresh.bind(this);
53+
4754
onMounted(() => {
4855
// You can't wait for rendering because the Builder depends on the page style synchronously.
4956
effect(
@@ -94,6 +101,7 @@ export class WebsiteBuilder extends Component {
94101
}
95102
});
96103
onMounted(() => {
104+
this.addListeners(document);
97105
this.addSystrayItems();
98106
this.websiteService.useMysterious = true;
99107
const { enable_editor, edit_translations } = this.props.action.context.params || {};
@@ -109,6 +117,27 @@ export class WebsiteBuilder extends Component {
109117
this.websiteService.useMysterious = false;
110118
this.websiteService.currentWebsiteId = null;
111119
});
120+
121+
effect(
122+
(state) => {
123+
this.websiteContext.edition = state.isEditing;
124+
if (!state.isEditing) {
125+
this.addSystrayItems();
126+
}
127+
},
128+
[this.state]
129+
);
130+
useEffect(
131+
(isEditing) => {
132+
document.querySelector("body").classList.toggle("o_builder_open", isEditing);
133+
if (isEditing) {
134+
setTimeout(() => {
135+
registry.category("systray").remove("website.WebsiteSystrayItem");
136+
}, 200);
137+
}
138+
},
139+
() => [this.state.isEditing]
140+
);
112141
}
113142

114143
get menuProps() {
@@ -157,15 +186,10 @@ export class WebsiteBuilder extends Component {
157186
}
158187

159188
async onEditPage() {
160-
document.querySelector("body").classList.add("o_builder_open");
161189
await this.iframeLoaded;
162190
await this.publicRootReady;
163191
await this.loadAssetsEditBundle();
164-
165-
setTimeout(() => {
166-
this.state.isEditing = true;
167-
registry.category("systray").remove("website.WebsiteSystrayItem");
168-
}, 200);
192+
this.state.isEditing = true;
169193
}
170194

171195
async loadAssetsEditBundle() {
@@ -191,7 +215,12 @@ export class WebsiteBuilder extends Component {
191215
return;
192216
}
193217

194-
if (!isHTTPSorNakedDomainRedirection(iframe.contentWindow.location.origin, window.location.origin)) {
218+
if (
219+
!isHTTPSorNakedDomainRedirection(
220+
iframe.contentWindow.location.origin,
221+
window.location.origin
222+
)
223+
) {
195224
// If another domain ends up loading in the iframe (for example,
196225
// if the iframe is being redirected and has no initial URL, so it
197226
// loads "about:blank"), do not push that into the history
@@ -331,9 +360,7 @@ export class WebsiteBuilder extends Component {
331360
async reloadIframeAndCloseEditor() {
332361
const isEditing = false;
333362
await this.reloadIframe(isEditing);
334-
document.querySelector("body").classList.remove("o_builder_open");
335363
this.state.isEditing = isEditing;
336-
this.addSystrayItems();
337364
}
338365

339366
async reloadIframe(isEditing = true, url) {
@@ -371,6 +398,8 @@ export class WebsiteBuilder extends Component {
371398
setIframeLoaded() {
372399
this.iframeLoaded = new Promise((resolve) => {
373400
this.resolveIframeLoaded = () => {
401+
this.hotkeyService.registerIframe(this.websiteContent.el);
402+
this.addListeners(this.websiteContent.el.contentDocument);
374403
resolve(this.websiteContent.el);
375404
};
376405
});
@@ -390,6 +419,42 @@ export class WebsiteBuilder extends Component {
390419
onResourceEditorResize(width) {
391420
browser.localStorage.setItem("ace_editor_width", width);
392421
}
422+
423+
/**
424+
* Handles refreshing while the website preview is active.
425+
* Makes it possible to stay in the backend after an F5 or CTRL-R keypress.
426+
* Cannot be done through the hotkey service due to F5.
427+
*
428+
* @param {KeyboardEvent} ev
429+
*/
430+
_onKeydownRefresh(ev) {
431+
const hotkey = getActiveHotkey(ev);
432+
if (hotkey !== "control+r" && hotkey !== "f5") {
433+
return;
434+
}
435+
// The iframe isn't loaded yet: fallback to default refresh.
436+
if (this.websiteService.contentWindow === undefined) {
437+
return;
438+
}
439+
ev.preventDefault();
440+
const path = this.websiteService.contentWindow.location;
441+
const debugMode = this.env.debug ? `&debug=${this.env.debug}` : "";
442+
redirect(
443+
`/odoo/action-website.website_preview?path=${encodeURIComponent(path)}${debugMode}`
444+
);
445+
}
446+
447+
/**
448+
* Registers listeners on both the main document and the iframe document.
449+
* It can mostly be done through the hotkey service, but not all keys are
450+
* whitelisted, specifically F5 which we want to override.
451+
*
452+
* @param {HTMLElement} target - document or iframe document
453+
*/
454+
addListeners(target) {
455+
target.removeEventListener("keydown", this.onKeydownRefresh);
456+
target.addEventListener("keydown", this.onKeydownRefresh);
457+
}
393458
}
394459

395460
function deleteQueryParam(param, target = window, adaptBrowserUrl = false) {

addons/html_builder/static/src/website_preview/website_builder_action.scss

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,25 @@
55

66
&.o_builder_sidebar_open {
77
width: $o-we-sidebar-width;
8+
transition-delay: 200ms;
9+
10+
.o_website_fullscreen & {
11+
width: 0;
12+
}
13+
}
14+
15+
.o_builder_open & {
16+
transition-delay: 0ms;
817
}
918
}
1019

1120
.o_main_navbar {
12-
transition: margin-top ease 400ms !important;
13-
}
14-
.o_builder_open .o_main_navbar {
15-
margin-top: -$o-navbar-height;
21+
transition: margin-top ease 400ms;
22+
23+
.o_website_fullscreen &,
24+
.o_builder_open & {
25+
margin-top: -$o-navbar-height;
26+
}
1627
}
1728

1829
.o_website_preview {

addons/html_builder/static/src/website_preview/website_builder_action.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
</ResizablePanel>
1717
<LocalOverlayContainer localOverlay="overlayRef" identifier="env.localOverlayContainerKey"/>
1818
<div t-att-class="{'o_builder_sidebar_open': state.isEditing}" class="o-website-builder_sidebar border-start border-dark">
19-
<LazyComponent t-if="state.isEditing" Component="'website.Builder'" props="() => this.menuProps" bundle="'html_builder.assets'" t-key="state.key"/>
19+
<LazyComponent t-if="state.isEditing" Component="'website.Builder'" props="() => this.menuProps" bundle="'html_builder.assets'" t-key="state.key"/>
2020
</div>
2121
</div>
2222
</t>

addons/html_builder/static/tests/translation.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ test("404 page in translate mode", async () => {
9696
setup() {
9797
websiteServiceInTranslateMode.is404 = () => true;
9898
this.websiteService = websiteServiceInTranslateMode;
99+
this.websiteContext = this.websiteService.context;
99100
},
100101
});
101102
await setupWebsiteBuilder(`<h1> Homepage </h1>`, { openEditor: false });
@@ -200,6 +201,7 @@ async function setupSidebarBuilderForTranslation(options) {
200201
super.setup();
201202
this.env.services.website = websiteServiceInTranslateMode;
202203
this.websiteService = websiteServiceInTranslateMode;
204+
this.websiteContext = this.websiteService.context;
203205
},
204206
});
205207
patchWithCleanup(WebsiteBuilder.prototype, {

addons/html_builder/static/tests/website_helpers.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,9 +237,13 @@ async function openBuilderSidebar(editAssetsLoaded) {
237237

238238
await click(".o-website-btn-custo-primary");
239239
await editAssetsLoaded;
240-
// advanceTime linked to the setTimeout in the WebsiteBuilder component
241-
// tick needed to wait for the timeout to be called before advancing time.
240+
// animationFrame linked to state.isEditing rendering the WebsiteBuilder.
241+
await animationFrame();
242+
// tick needed to wait for the timeout in the WebsiteBuilder useEffect to be
243+
// called before advancing time.
242244
await tick();
245+
// advanceTime linked to the setTimeout in the WebsiteBuilder component that
246+
// removes the systray items.
243247
await advanceTime(200);
244248
await animationFrame();
245249
}

addons/website/static/src/js/tours/tour_utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ export function clickOnElement(elementName, selector) {
231231
export function clickOnEditAndWaitEditMode(position = "bottom") {
232232
return [{
233233
content: markup(_t("<b>Click Edit</b> to start designing your homepage.")),
234-
trigger: "body .o_menu_systray a:contains('Edit')",
234+
trigger: "body .o_menu_systray button:contains('Edit')",
235235
tooltipPosition: position,
236236
run: "click",
237237
}, {

addons/website_sale/static/tests/tours/website_sale_restricted_editor_ui.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ registerWebsitePreviewTour('website_sale_restricted_editor_ui', {
1818
},
1919
{
2020
// Wait for the possibility to edit to appear
21-
trigger: ".o_menu_systray a:contains('Edit')",
21+
trigger: ".o_menu_systray button:contains('Edit')",
2222
},
2323
{
2424
content: "Ensure the publish and 'edit-in-backend' buttons are not shown",

0 commit comments

Comments
 (0)