Skip to content

Commit 4e52e6e

Browse files
committed
product image option plugin
1 parent 79fa833 commit 4e52e6e

File tree

9 files changed

+138
-48
lines changed

9 files changed

+138
-48
lines changed

addons/html_builder/static/src/plugins/image/image_tool_option_plugin.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import {
1313
ALIGNMENT_STYLE_PADDING,
1414
} from "@html_builder/utils/option_sequence";
1515

16+
export const REPLACE_MEDIA_SELECTOR = "img, .media_iframe_video, span.fa, i.fa";
17+
export const REPLACE_MEDIA_EXCLUDE =
18+
"[data-oe-xpath], a[href^='/website/social/'] > i.fa, a[class*='s_share_'] > i.fa";
19+
1620
class ImageToolOptionPlugin extends Plugin {
1721
static id = "imageToolOption";
1822
static dependencies = [
@@ -28,9 +32,8 @@ class ImageToolOptionPlugin extends Plugin {
2832
builder_options: [
2933
withSequence(REPLACE_MEDIA, {
3034
OptionComponent: ReplaceMediaOption,
31-
selector: "img, .media_iframe_video, span.fa, i.fa",
32-
exclude:
33-
"[data-oe-xpath], a[href^='/website/social/'] > i.fa, a[class*='s_share_'] > i.fa",
35+
selector: REPLACE_MEDIA_SELECTOR,
36+
exclude: REPLACE_MEDIA_EXCLUDE,
3437
}),
3538
withSequence(IMAGE_TOOL, {
3639
OptionComponent: ImageToolOption,

addons/html_builder/static/src/plugins/image/replace_media_option.js

+23-17
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,46 @@ export class ReplaceMediaOption extends BaseOptionComponent {
88
this.state = useDomState((editingElement) => ({
99
canSetLink: this.canSetLink(editingElement),
1010
hasHref: this.hasHref(editingElement),
11+
isProductPageImage: isProductPageImage(editingElement),
1112
}));
1213
}
1314
canSetLink(editingElement) {
1415
return (
15-
this.isImageSupportedForStyle(editingElement) &&
16+
isImageSupportedForStyle(editingElement) &&
1617
!searchSupportedParentLinkEl(editingElement).matches("a[data-oe-xpath]")
1718
);
1819
}
1920
hasHref(editingElement) {
2021
const parentEl = searchSupportedParentLinkEl(editingElement);
2122
return parentEl.tagName === "A" && parentEl.hasAttribute("href");
2223
}
23-
isImageSupportedForStyle(img) {
24-
if (!img.parentElement) {
25-
return false;
26-
}
24+
}
2725

28-
// See also `[data-oe-type='image'] > img` added as data-exclude of some
29-
// snippet options.
30-
const isTFieldImg = "oeType" in img.parentElement.dataset;
26+
export function isImageSupportedForStyle(img) {
27+
if (!img.parentElement) {
28+
return false;
29+
}
3130

32-
// Editable root elements are technically *potentially* supported here (if
33-
// the edited attributes are not computed inside the related view, they
34-
// could technically be saved... but as we cannot tell the computed ones
35-
// apart from the "static" ones, we choose to not support edition at all in
36-
// those "root" cases).
37-
// See also `[data-oe-xpath]` added as data-exclude of some snippet options.
38-
const isEditableRootElement = "oeXpath" in img.dataset;
31+
// See also `[data-oe-type='image'] > img` added as data-exclude of some
32+
// snippet options.
33+
const isTFieldImg = "oeType" in img.parentElement.dataset;
3934

40-
return !isTFieldImg && !isEditableRootElement;
41-
}
35+
// Editable root elements are technically *potentially* supported here (if
36+
// the edited attributes are not computed inside the related view, they
37+
// could technically be saved... but as we cannot tell the computed ones
38+
// apart from the "static" ones, we choose to not support edition at all in
39+
// those "root" cases).
40+
// See also `[data-oe-xpath]` added as data-exclude of some snippet options.
41+
const isEditableRootElement = "oeXpath" in img.dataset;
42+
43+
return !isTFieldImg && !isEditableRootElement;
4244
}
4345

4446
export function searchSupportedParentLinkEl(editingElement) {
4547
const parentEl = editingElement.parentElement;
4648
return parentEl.matches("figure") ? parentEl.parentElement : parentEl;
4749
}
50+
51+
export function isProductPageImage(editingElement) {
52+
return !!editingElement.closest(".o_wsale_product_images");
53+
}

addons/html_builder/static/src/plugins/image/replace_media_option.xml

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
<templates xml:space="preserve">
33

44
<t t-name="html_builder.ReplaceMediaOption">
5-
<BuilderRow label.translate="Media">
5+
<BuilderRow label.translate="Media" t-if="!state.isProductPageImage">
66
<BuilderButton
77
action="'replaceMedia'"
88
title.translate="Replace image"
9-
className="'o_we_bg_success'"
9+
className="'flex-grow-1'"
10+
type="'success'"
1011
preview="false"
1112
label.translate="Replace" />
1213
<BuilderButton icon="'fa-link'"

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

+1-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { KeepLast } from "@web/core/utils/concurrency";
22
import { BaseOptionComponent, useDomState } from "@html_builder/core/utils";
3+
import { isImageSupportedForStyle } from "@html_builder/plugins/image/replace_media_option";
34

45
export class AnimateOption extends BaseOptionComponent {
56
static template = "html_builder.AnimateOption";
@@ -75,27 +76,3 @@ export class AnimateOption extends BaseOptionComponent {
7576
return hasDirection;
7677
}
7778
}
78-
79-
/**
80-
* @param {HTMLImageElement} img
81-
* @returns {Boolean}
82-
*/
83-
export function isImageSupportedForStyle(img) {
84-
if (!img.parentElement) {
85-
return false;
86-
}
87-
88-
// See also `[data-oe-type='image'] > img` added as data-exclude of some
89-
// snippet options.
90-
const isTFieldImg = "oeType" in img.parentElement.dataset;
91-
92-
// Editable root elements are technically *potentially* supported here (if
93-
// the edited attributes are not computed inside the related view, they
94-
// could technically be saved... but as we cannot tell the computed ones
95-
// apart from the "static" ones, we choose to not support edition at all in
96-
// those "root" cases).
97-
// See also `[data-oe-xpath]` added as data-exclude of some snippet options.
98-
const isEditableRootElement = "oeXpath" in img.dataset;
99-
100-
return !isTFieldImg && !isEditableRootElement;
101-
}

addons/html_builder/static/src/website_sale/product_attribute_option_plugin.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { registry } from "@web/core/registry";
33
import { rpc } from "@web/core/network/rpc";
44

55
class ProductAttributeOptionPlugin extends Plugin {
6-
static id = "productAtttributeOption";
6+
static id = "productAttributeOption";
77
resources = {
88
builder_options: {
99
template: "website_sale.ProductAttributeOption",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { BaseOptionComponent, useDomState } from "@html_builder/core/utils";
2+
import { isProductPageImage } from "@html_builder/plugins/image/replace_media_option";
3+
4+
export class ProductImageOption extends BaseOptionComponent {
5+
static template = "website_sale.ProductImageOption";
6+
static props = {};
7+
8+
setup() {
9+
super.setup();
10+
this.state = useDomState((editingElement) => ({
11+
isProductPageImage: isProductPageImage(editingElement),
12+
}));
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<templates xml:space="preserve">
3+
4+
5+
<t t-name="website_sale.ProductImageOption">
6+
<BuilderRow label.translate="Media" t-if="state.isProductPageImage">
7+
<BuilderButton
8+
action="'replaceMedia'"
9+
title.translate="Replace image"
10+
className="'flex-grow-1'"
11+
type="'success'"
12+
preview="false"
13+
label.translate="Replace" />
14+
<BuilderButton
15+
action="'removeMedia'"
16+
className="'flex-grow-1'"
17+
type="'danger'"
18+
preview="false"
19+
label.translate="Remove"/>
20+
</BuilderRow>
21+
<BuilderRow label.translate="Re-order" t-if="state.isProductPageImage">
22+
<BuilderButtonGroup preview="false" action="'setPosition'">
23+
<BuilderButton icon="'fa-angle-double-left'" title.translate="Move to first" actionValue="'first'"/>
24+
<BuilderButton icon="'fa-angle-left'" title.translate="Move to previous" actionValue="'left'"/>
25+
<BuilderButton icon="'fa-angle-right'" title.translate="Move to next" actionValue="'right'"/>
26+
<BuilderButton icon="'fa-angle-double-right'" title.translate="Move to last" actionValue="'last'"/>
27+
</BuilderButtonGroup>
28+
</BuilderRow>
29+
</t>
30+
31+
</templates>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { REPLACE_MEDIA } from "@html_builder/utils/option_sequence";
2+
import {
3+
REPLACE_MEDIA_SELECTOR,
4+
REPLACE_MEDIA_EXCLUDE,
5+
} from "@html_builder/plugins/image/image_tool_option_plugin";
6+
import { Plugin } from "@html_editor/plugin";
7+
import { withSequence } from "@html_editor/utils/resource";
8+
import { rpc } from "@web/core/network/rpc";
9+
import { registry } from "@web/core/registry";
10+
import { ProductImageOption } from "./product_image_option";
11+
12+
export class ProductImageOptionPlugin extends Plugin {
13+
static id = "productImageOption";
14+
resources = {
15+
builder_options: [
16+
withSequence(REPLACE_MEDIA, {
17+
OptionComponent: ProductImageOption,
18+
selector: REPLACE_MEDIA_SELECTOR,
19+
exclude: REPLACE_MEDIA_EXCLUDE,
20+
editableOnly: false,
21+
}),
22+
],
23+
builder_actions: {
24+
/*
25+
* Change sequence of product page images
26+
*/
27+
setPosition: {
28+
reload: {},
29+
apply: async ({ editingElement: el, value }) => {
30+
const params = {
31+
image_res_model: el.parentElement.dataset.oeModel,
32+
image_res_id: el.parentElement.dataset.oeId,
33+
move: value,
34+
};
35+
36+
await rpc("/shop/product/resequence-image", params);
37+
},
38+
},
39+
/*
40+
* Removes the image in the back-end
41+
*/
42+
removeMedia: {
43+
reload: {},
44+
apply: async ({ editingElement: el }) => {
45+
if (el.parentElement.dataset.oeModel === "product.image") {
46+
// Unlink the "product.image" record as it is not the main product image.
47+
await this.services.orm.unlink("product.image", [
48+
parseInt(el.parentElement.dataset.oeId),
49+
]);
50+
}
51+
el.remove();
52+
},
53+
},
54+
},
55+
};
56+
}
57+
58+
registry.category("website-plugins").add(ProductImageOptionPlugin.id, ProductImageOptionPlugin);

addons/website/static/tests/tours/media_dialog.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ registerWebsitePreviewTour("website_media_dialog_image_shape", {
156156
},
157157
{
158158
content: "Open MediaDialog from an image",
159-
trigger: ".o_we_bg_success[data-action-id='replaceMedia']",
159+
trigger: ".btn-success[data-action-id='replaceMedia']",
160160
run: "click",
161161
},
162162
{

0 commit comments

Comments
 (0)