diff --git a/addons/html_builder/static/src/builder_sidebar/tabs/block_tab/block_tab.js b/addons/html_builder/static/src/builder_sidebar/tabs/block_tab/block_tab.js
index c813bcb1df95d..107968af1f1d2 100644
--- a/addons/html_builder/static/src/builder_sidebar/tabs/block_tab/block_tab.js
+++ b/addons/html_builder/static/src/builder_sidebar/tabs/block_tab/block_tab.js
@@ -91,6 +91,9 @@ export class BlockTab extends Component {
this.dropzonePlugin.displayDropZone(snippet);
}
const addElement = this.dropzonePlugin.getAddElement(position);
+ if (addElement.noDrop) {
+ return;
+ }
this.dialog.add(
AddSnippetDialog,
{
diff --git a/addons/html_builder/static/src/plugins/drop_zone_plugin.inside.scss b/addons/html_builder/static/src/plugins/drop_zone_plugin.inside.scss
index fdd8f32911025..b64d7d8d3b1f7 100644
--- a/addons/html_builder/static/src/plugins/drop_zone_plugin.inside.scss
+++ b/addons/html_builder/static/src/plugins/drop_zone_plugin.inside.scss
@@ -1,3 +1,20 @@
+#wrap.o_editable {
+ .oe_drop_zone.oe_insert:only-child {
+ $-margin-inline: 2%;
+ height: 100%;
+ width: 100% - 2 * $-margin-inline;
+ padding: 112px 0;
+ margin: 20px $-margin-inline;
+
+ &::before {
+ content: attr(data-editor-message);
+ display: block;
+ font-size: 1.5rem;
+ text-align: center;
+ }
+ }
+}
+
.oe_drop_zone {
height: 10px;
background: $o-we-dropzone-bg-color;
diff --git a/addons/html_builder/static/src/plugins/drop_zone_plugin.js b/addons/html_builder/static/src/plugins/drop_zone_plugin.js
index 1b05b197423ea..55b90a5393eb0 100644
--- a/addons/html_builder/static/src/plugins/drop_zone_plugin.js
+++ b/addons/html_builder/static/src/plugins/drop_zone_plugin.js
@@ -1,5 +1,6 @@
import { Plugin } from "@html_editor/plugin";
import { isBlock } from "@html_editor/utils/blocks";
+import { _t } from "@web/core/l10n/translation";
import { closest, touching } from "@web/core/utils/ui";
function filterFunction(el, exclude) {
@@ -37,6 +38,7 @@ export class DropZonePlugin extends Plugin {
resources = {
savable_mutation_record_predicates: this.isMutationRecordSavable.bind(this),
+ normalize_handlers: this.updateEmptyDropZone.bind(this),
};
setup() {
@@ -89,6 +91,21 @@ export class DropZonePlugin extends Plugin {
};
}
+ get wrapEl() {
+ return this.document.getElementById("wrap");
+ }
+
+ get emptyDropZoneEl() {
+ return this.wrapEl.querySelector(".oe_drop_zone.oe_insert[data-editor-message]");
+ }
+
+ createDropZone() {
+ const dropZone = this.document.createElement("div");
+ dropZone.className = "oe_drop_zone oe_insert";
+ this.dropZoneElements.push(dropZone);
+ return dropZone;
+ }
+
displayDropZone(snippet) {
this.clearDropZone();
@@ -100,20 +117,16 @@ export class DropZonePlugin extends Plugin {
}
targets.push(...selectorSiblings);
- const createDropZone = () => {
- const dropZone = this.document.createElement("div");
- dropZone.className = "oe_drop_zone oe_insert";
- this.dropZoneElements.push(dropZone);
- return dropZone;
- };
-
+ if (this.emptyDropZoneEl) {
+ return;
+ }
for (const target of targets) {
if (!target.nextElementSibling?.classList.contains("oe_drop_zone")) {
- target.after(createDropZone());
+ target.after(this.createDropZone());
}
if (!target.previousElementSibling?.classList.contains("oe_drop_zone")) {
- target.before(createDropZone());
+ target.before(this.createDropZone());
}
}
}
@@ -126,6 +139,17 @@ export class DropZonePlugin extends Plugin {
}
this.dropZoneElements = [];
+ this.updateEmptyDropZone();
+ }
+
+ updateEmptyDropZone() {
+ if (this.wrapEl.matches(":empty")) {
+ const emptyDropZoneEl = this.createDropZone();
+ emptyDropZoneEl.dataset.editorMessage = _t("DRAG BUILDING BLOCKS HERE");
+ this.wrapEl.appendChild(emptyDropZoneEl);
+ } else {
+ this.emptyDropZoneEl?.remove();
+ }
}
dragElement(element) {
@@ -142,25 +166,42 @@ export class DropZonePlugin extends Plugin {
}
getAddElement(position) {
+ const cancel = () => {
+ this.clearDropZone();
+ const fn = () => {};
+ fn.noDrop = true;
+ return fn;
+ };
+ if (position && !touching([this.document.body], position).length) {
+ return cancel();
+ }
const dropZone = position
- ? closest(touching(this.dropZoneElements, position), position)
+ ? closest(touching(this.dropZoneElements, position), position) ||
+ closest(this.dropZoneElements, position)
: closest(this.dropZoneElements, {
x: window.innerWidth / 2,
y: window.innerHeight / 2,
});
if (!dropZone) {
- this.clearDropZone();
- return () => {};
+ return cancel();
+ }
+
+ let target, insertMethod;
+ if (dropZone === this.emptyDropZoneEl) {
+ insertMethod = "appendChild";
+ target = dropZone.parentElement;
+ }
+ if (!target) {
+ insertMethod = "after";
+ target = dropZone.previousSibling;
}
- let target = dropZone.previousSibling;
- let addAfter = true;
if (!target) {
- addAfter = false;
+ insertMethod = "before";
target = dropZone.nextSibling;
}
this.clearDropZone();
return (elementToAdd) => {
- addAfter ? target.after(elementToAdd) : target.before(elementToAdd);
+ target[insertMethod](elementToAdd);
scrollToWindow(elementToAdd, { behavior: "smooth", offset: 50 });
this.dependencies.history.addStep();
};