Skip to content

Commit 21859d9

Browse files
committed
image gallery edit interaction
1 parent 6e5c893 commit 21859d9

14 files changed

+133
-79
lines changed

addons/html_builder/__manifest__.py

+8
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
('remove', 'html_builder/static/src/website_preview/**/*'),
4545
('remove', 'html_builder/static/src/website_mass_mailing/**/*'),
4646
('remove', 'html_builder/static/src/website_builder/plugins/website_edit_service.js'),
47+
('remove', 'html_builder/static/src/interactions/**/*'),
4748
],
4849
'html_builder.inside_builder_style': [
4950
('include', 'web._assets_helpers'),
@@ -66,6 +67,13 @@
6667
'html_builder/static/tests/**/*',
6768
('include', 'html_builder.assets'),
6869
],
70+
'web.assets_frontend': [
71+
'html_builder/static/src/interactions/**/*',
72+
('remove', 'html_builder/static/src/interactions/**/*.edit.*'),
73+
],
74+
'website.assets_edit_frontend': [
75+
'html_builder/static/src/interactions/**/*.edit.*',
76+
],
6977
},
7078
'license': 'LGPL-3',
7179
}

addons/html_builder/static/src/builder.js

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export class Builder extends Component {
119119
replaceSnippet: async (snippet) => await this.snippetModel.replaceSnippet(snippet),
120120
saveSnippet: (snippetEl, cleanForSaveHandlers) =>
121121
this.snippetModel.saveSnippet(snippetEl, cleanForSaveHandlers),
122+
getShared: () => this.editor.shared,
122123
},
123124
this.env.services
124125
);

addons/html_builder/static/src/core/anchor/anchor_plugin.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ export class AnchorPlugin extends Plugin {
1919
static dependencies = ["history"];
2020
static shared = ["createOrEditAnchorLink"];
2121
resources = {
22-
on_clone_handlers: this.onClone.bind(this),
22+
on_cloned_handlers: this.onCloned.bind(this),
2323
get_options_container_top_buttons: withSequence(
2424
0,
2525
this.getOptionsContainerTopButtons.bind(this)
2626
),
2727
};
2828

29-
onClone({ cloneEl }) {
29+
onCloned({ cloneEl }) {
3030
const anchorEls = getElementsWithOption(cloneEl, anchorSelector, anchorExclude);
3131
anchorEls.forEach((anchorEl) => this.deleteAnchor(anchorEl));
3232
}

addons/html_builder/static/src/core/clone_plugin.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ export class ClonePlugin extends Plugin {
2121
get_overlay_buttons: withSequence(2, {
2222
getButtons: this.getActiveOverlayButtons.bind(this),
2323
}),
24+
// Resource definitions:
25+
on_will_clone_handlers: [
26+
// ({ originalEl: el }) => {
27+
// called on the original element before clone
28+
// }
29+
],
30+
on_cloned_handlers: [
31+
// ({ cloneEl: cloneEl, originalEl: el }) => {
32+
// called after an element was cloned and inserted in the DOM
33+
// }
34+
],
2435
};
2536

2637
setup() {
@@ -65,13 +76,13 @@ export class ClonePlugin extends Plugin {
6576
}
6677

6778
cloneElement(el, { position = "afterend", scrollToClone = false } = {}) {
68-
// TODO snippet_will_be_cloned ?
79+
this.dispatchTo("on_will_clone_handlers", { originalEl: el });
6980
// TODO cleanUI resource for each option
7081
const cloneEl = el.cloneNode(true);
7182
this.cleanElement(cloneEl);
7283
el.insertAdjacentElement(position, cloneEl);
7384
this.dependencies["builder-options"].updateContainers(cloneEl);
74-
this.dispatchTo("on_clone_handlers", { cloneEl: cloneEl, originalEl: el });
85+
this.dispatchTo("on_cloned_handlers", { cloneEl: cloneEl, originalEl: el });
7586
if (scrollToClone && !isElementInViewport(cloneEl)) {
7687
cloneEl.scrollIntoView({ behavior: "smooth", block: "center" });
7788
}

addons/html_builder/static/src/core/move_plugin.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export class MovePlugin extends Plugin {
6969
get_overlay_buttons: withSequence(0, {
7070
getButtons: this.getActiveOverlayButtons.bind(this),
7171
}),
72-
on_clone_handlers: this.onClone.bind(this),
72+
on_cloned_handlers: this.onCloned.bind(this),
7373
on_remove_handlers: this.onRemove.bind(this),
7474
};
7575

@@ -113,7 +113,7 @@ export class MovePlugin extends Plugin {
113113
return buttons;
114114
}
115115

116-
onClone({ cloneEl, originalEl }) {
116+
onCloned({ cloneEl, originalEl }) {
117117
if (!isMovable(originalEl)) {
118118
return;
119119
}

addons/html_builder/static/src/core/save_plugin.js

+17
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,23 @@ export class SavePlugin extends Plugin {
1212

1313
resources = {
1414
handleNewRecords: this.handleMutations,
15+
// Resource definitions:
16+
before_save_handlers: [
17+
// () => {
18+
// called at the very beginning of the save process
19+
// }
20+
],
21+
clean_for_save_handlers: [
22+
// ({root, preserveSelection = false}) => {
23+
// clean DOM before save (leaving edit mode)
24+
// root is the clone of a node that was o_dirty
25+
// }
26+
],
27+
save_handlers: [
28+
// () => {
29+
// called at the very end of the save process
30+
// }
31+
],
1532
};
1633

1734
async save(isTranslation) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Interaction } from "@web/public/interaction";
2+
import { registry } from "@web/core/registry";
3+
4+
export class ImageGalleryEdit extends Interaction {
5+
static selector = ".s_image_gallery";
6+
dynamicContent = {
7+
".o_empty_gallery_alert": {
8+
"t-on-click": this.onAddImage.bind(this),
9+
},
10+
};
11+
setup() {
12+
const containerEl = this.el.querySelector(
13+
".container, .container-fluid, .o_container_small"
14+
);
15+
this.renderAt("html_builder.empty_image_gallery_alert", {}, containerEl);
16+
}
17+
onAddImage() {
18+
const applySpec = { editingElement: this.el };
19+
this.services["website_edit"].applyAction("addImage", applySpec);
20+
}
21+
}
22+
23+
registry.category("public.interactions.edit").add("html_builder.image_gallery_edit", {
24+
Interaction: ImageGalleryEdit,
25+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<templates xml:space="preserve">
3+
4+
<t t-name="html_builder.empty_image_gallery_alert">
5+
<div class="alert alert-info o_empty_gallery_alert text-center o_not_editable" contentEditable="false">
6+
<i class="fa fa-plus-circle"/>
7+
<span class="o_add_images"> Add Images</span>
8+
</div>
9+
</t>
10+
11+
</templates>

addons/html_builder/static/src/website_builder/plugins/edit_interaction_plugin.js

+10-8
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@ import { registry } from "@web/core/registry";
33

44
export class EditInteractionPlugin extends Plugin {
55
static id = "edit_interaction";
6-
static dependencies = ["history"];
76

87
resources = {
98
normalize_handlers: this.restartInteractions.bind(this),
109
option_visibility_updated: this.restartInteractions.bind(this),
1110
content_manually_updated_handlers: this.restartInteractions.bind(this),
11+
before_save_handlers: () => this.stopInteractions(),
12+
on_will_clone_handlers: ({ originalEl }) => {
13+
this.stopInteractions(originalEl);
14+
},
15+
on_cloned_handlers: ({ originalEl, clonedEl }) => {
16+
this.restartInteractions(originalEl);
17+
this.restartInteractions(clonedEl);
18+
},
1219
};
1320

1421
setup() {
@@ -20,17 +27,12 @@ export class EditInteractionPlugin extends Plugin {
2027
{ once: true }
2128
);
2229
const event = new CustomEvent("edit_interaction_plugin_loaded");
23-
event.historyCallbacks = {
24-
ignoreDOMMutations: this.dependencies.history.ignoreDOMMutations,
25-
};
30+
event.shared = this.config.getShared();
2631
window.parent.document.dispatchEvent(event);
2732
}
2833
destroy() {
2934
this.websiteEditService?.uninstallPatches?.();
30-
}
31-
32-
destroy() {
33-
this.stopInteractions(this.editable);
35+
this.stopInteractions();
3436
}
3537

3638
updateEditInteraction({ detail: { websiteEditService } }) {

addons/html_builder/static/src/website_builder/plugins/options/button_option_plugin.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { registry } from "@web/core/registry";
44
class ButtonOptionPlugin extends Plugin {
55
static id = "buttonOption";
66
resources = {
7-
on_clone_handlers: this.onClone.bind(this),
7+
on_cloned_handlers: this.onCloned.bind(this),
88
on_add_element_handlers: this.onAddElement.bind(this),
99
};
1010

11-
onClone({ cloneEl }) {
11+
onCloned({ cloneEl }) {
1212
if (cloneEl.matches("a.btn[data-snippet='s_button']")) {
1313
this.adaptButtons(cloneEl, false);
1414
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.s_image_gallery {
2+
&:has(.img) .o_empty_gallery_alert {
3+
display: none;
4+
}
5+
.o_empty_gallery_alert {
6+
.o_add_images {
7+
cursor: pointer;
8+
}
9+
}
10+
}

addons/html_builder/static/src/website_builder/plugins/options/image_gallery_option_plugin.js

+9-60
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { registry } from "@web/core/registry";
22
import { Plugin } from "@html_editor/plugin";
33
import { applyModifications, loadImageInfo } from "@html_editor/utils/image_processing";
4-
import { _t } from "@web/core/l10n/translation";
54

65
class ImageGalleryOption extends Plugin {
76
static id = "imageGalleryOption";
@@ -15,8 +14,6 @@ class ImageGalleryOption extends Plugin {
1514
],
1615
builder_actions: this.getActions(),
1716
system_classes: ["o_empty_gallery_alert"],
18-
clean_for_save_handlers: this.cleanForSave.bind(this),
19-
normalize_handlers: this.updateAlertBanner.bind(this),
2017
on_reorder_items_handlers: this.reorderGalleryItems.bind(this),
2118
on_remove_handlers: this.onRemove.bind(this),
2219
after_remove_handlers: this.afterRemove.bind(this),
@@ -26,8 +23,15 @@ class ImageGalleryOption extends Plugin {
2623
return {
2724
addImage: this.addImageAction,
2825
removeAllImages: {
29-
apply: ({ editingElement }) => {
30-
this.insertEmptyGalleryAlert(this.getContainer(editingElement));
26+
apply: ({ editingElement: el }) => {
27+
const containerEl = el.querySelector(
28+
".container, .container-fluid, .o_container_small"
29+
);
30+
for (const subEl of containerEl.querySelectorAll(
31+
":scope > *:not(.o_empty_gallery_alert)"
32+
)) {
33+
subEl.remove();
34+
}
3135
},
3236
},
3337
setImageGalleryLayout: {
@@ -69,21 +73,6 @@ class ImageGalleryOption extends Plugin {
6973
}
7074
}
7175

72-
cleanForSave({ root }) {
73-
for (const emptyGalleryAlert of root.querySelectorAll(".o_empty_gallery_alert")) {
74-
emptyGalleryAlert.remove();
75-
}
76-
}
77-
78-
updateAlertBanner() {
79-
const imageGalleries = this.document.querySelectorAll(".s_image_gallery");
80-
for (const imageGallery of imageGalleries) {
81-
const container = this.getContainer(imageGallery);
82-
if (!container.querySelector("img") && !container.querySelector(".alert")) {
83-
this.insertEmptyGalleryAlert(container);
84-
}
85-
}
86-
}
8776
reorderGalleryItems({ elementToReorder, position, optionName }) {
8877
if (optionName === "GalleryImageList") {
8978
const editingGalleryElement = elementToReorder.closest(".s_image_gallery");
@@ -145,46 +134,6 @@ class ImageGalleryOption extends Plugin {
145134
};
146135
}
147136

148-
insertEmptyGalleryAlert(container) {
149-
const addImg = document.createElement("div");
150-
addImg.classList.add(
151-
"alert",
152-
"alert-info",
153-
"o_empty_gallery_alert",
154-
"text-center",
155-
"o_not_editable"
156-
);
157-
addImg.contentEditable = false;
158-
const text = document.createElement("span");
159-
text.classList.add("o_add_images");
160-
text.textContent = _t(" Add Images");
161-
text.style.cursor = "pointer";
162-
163-
const icon = document.createElement("i");
164-
icon.classList.add("fa", "fa-plus-circle");
165-
166-
addImg.appendChild(icon);
167-
addImg.appendChild(text);
168-
container.replaceChildren(addImg);
169-
170-
this.addDomListener(addImg, "click", ({ target }) => {
171-
const editingElement = target.closest(".s_image_gallery");
172-
const applySpec = { editingElement };
173-
this.dependencies.operation.next(
174-
() => {
175-
this.addImageAction.apply(applySpec);
176-
this.dependencies.history.addStep();
177-
},
178-
{
179-
load: async () => {
180-
const loadResult = await this.addImageAction.load({ editingElement });
181-
applySpec.loadResult = loadResult;
182-
},
183-
}
184-
);
185-
});
186-
}
187-
188137
/**
189138
* Set the images in the gallery by following the wanted layout
190139
* @param {Element} imageGalleryElement

addons/html_builder/static/src/website_builder/plugins/options/popup_option_plugin.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class PopupOptionPlugin extends Plugin {
2525
dropIn: ":not(p).oe_structure:not(.oe_structure_solo):not([data-snippet] *), :not(.o_mega_menu):not(p)[data-oe-type=html]:not([data-snippet] *)",
2626
},
2727
builder_actions: this.getActions(),
28-
on_clone_handlers: this.onClone.bind(this),
28+
on_cloned_handlers: this.onCloned.bind(this),
2929
on_add_element_handlers: this.onAddElement.bind(this),
3030
target_show: this.onTargetShow.bind(this),
3131
target_hide: this.onTargetHide.bind(this),
@@ -102,7 +102,7 @@ class PopupOptionPlugin extends Plugin {
102102
};
103103
}
104104

105-
onClone({ cloneEl }) {
105+
onCloned({ cloneEl }) {
106106
if (cloneEl.matches(".s_popup")) {
107107
this.assignUniqueID(cloneEl);
108108
}

addons/html_builder/static/src/website_builder/plugins/website_edit_service.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ registry.category("services").add("website_edit", {
5050
let editMode = false;
5151
const patches = [];
5252
const historyCallbacks = {};
53+
const shared = {};
5354

5455
const update = (target, mode) => {
5556
// editMode = true;
@@ -133,13 +134,31 @@ registry.category("services").add("website_edit", {
133134
}
134135
patches.length = 0;
135136
};
137+
const applyAction = (actionId, spec) => {
138+
const action = shared.builderActions.getAction(actionId);
139+
shared.operation.next(
140+
() => {
141+
action.apply(spec);
142+
shared.history.addStep();
143+
},
144+
{
145+
load: async () => {
146+
if (action.load) {
147+
const loadResult = await action.load(spec);
148+
spec.loadResult = loadResult;
149+
}
150+
},
151+
}
152+
);
153+
};
136154

137155
const websiteEditService = {
138156
isEditingTranslations,
139157
update,
140158
stop,
141159
installPatches,
142160
uninstallPatches,
161+
applyAction,
143162
};
144163

145164
// Transfer the iframe website_edit service to the EditInteractionPlugin
@@ -151,7 +170,8 @@ registry.category("services").add("website_edit", {
151170
},
152171
})
153172
);
154-
Object.assign(historyCallbacks, ev.historyCallbacks);
173+
Object.assign(shared, ev.shared);
174+
historyCallbacks.ignoreDOMMutations = shared.history.ignoreDOMMutations;
155175
});
156176

157177
return websiteEditService;

0 commit comments

Comments
 (0)