Skip to content

Commit a7af7e6

Browse files
committed
Fix: Image Transform button not working
The "Transform Image" button was non-functional due to an undefined "trasnformImage" user command.
1 parent 0973924 commit a7af7e6

File tree

8 files changed

+157
-90
lines changed

8 files changed

+157
-90
lines changed

addons/html_editor/static/src/main/media/image_transform_button.js

Lines changed: 86 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,36 @@ import { toolbarButtonProps } from "@html_editor/main/toolbar/toolbar";
33
import { registry } from "@web/core/registry";
44
import { ImageTransformation } from "./image_transformation";
55

6-
export class ImageTransformButton extends Component {
7-
static template = "html_editor.ImageTransformButton";
8-
static props = {
9-
id: String,
10-
icon: String,
11-
title: String,
12-
getSelectedImage: Function,
13-
resetImageTransformation: Function,
14-
addStep: Function,
15-
document: { validate: (p) => p.nodeType === Node.DOCUMENT_NODE },
16-
editable: { validate: (p) => p.nodeType === Node.ELEMENT_NODE },
17-
...toolbarButtonProps,
18-
activeTitle: String,
19-
};
20-
21-
setup() {
22-
this.state = useState({ active: false });
23-
this.pointerDownInsideTransform = false;
24-
// We close the image transform when we click outside any element not
25-
// related to it. When the pointerdown of the click is inside the image
26-
// transform and pointerup is outside while resizing or rotating the
27-
// image it will consider the click as being done outside image
28-
// transform. So we need to keep track if the pointerdown is inside or
29-
// outside to know if we want to close the image transform component or
30-
// not.
31-
useExternalListener(this.props.document, "pointerdown", (ev) => {
32-
if (this.isNodeInsideTransform(ev.target)) {
33-
this.pointerDownInsideTransform = true;
34-
} else {
35-
this.closeImageTransformation();
36-
this.pointerDownInsideTransform = false;
37-
}
38-
});
39-
useExternalListener(this.props.document, "click", (ev) => {
40-
if (!this.isNodeInsideTransform(ev.target) && !this.pointerDownInsideTransform) {
41-
this.closeImageTransformation();
42-
}
43-
this.pointerDownInsideTransform = false;
44-
});
45-
// When we click on any character the image is deleted and we need to close the image transform
46-
// We handle this by selectionchange
47-
useExternalListener(this.props.document, "selectionchange", (ev) => {
48-
this.closeImageTransformation();
49-
});
50-
}
6+
export function useTransformOperations(state, document, editable, addStep) {
7+
let pointerDownInsideTransform = false;
8+
// We close the image transform when we click outside any element not
9+
// related to it. When the pointerdown of the click is inside the image
10+
// transform and pointerup is outside while resizing or rotating the
11+
// image it will consider the click as being done outside image
12+
// transform. So we need to keep track if the pointerdown is inside or
13+
// outside to know if we want to close the image transform component or
14+
// not.
15+
useExternalListener(document, "pointerdown", (ev) => {
16+
if (isNodeInsideTransform(ev.target)) {
17+
pointerDownInsideTransform = true;
18+
} else {
19+
closeImageTransformation();
20+
pointerDownInsideTransform = false;
21+
}
22+
});
23+
useExternalListener(document, "click", (ev) => {
24+
if (!isNodeInsideTransform(ev.target) && !pointerDownInsideTransform) {
25+
closeImageTransformation();
26+
}
27+
pointerDownInsideTransform = false;
28+
});
29+
// When we click on any character the image is deleted and we need to close the image transform
30+
// We handle this by selectionchange
31+
useExternalListener(document, "selectionchange", (ev) => {
32+
closeImageTransformation();
33+
});
5134

52-
isNodeInsideTransform(node) {
35+
function isNodeInsideTransform(node) {
5336
if (!node) {
5437
return false;
5538
}
@@ -60,7 +43,7 @@ export class ImageTransformButton extends Component {
6043
return true;
6144
}
6245
if (
63-
this.isImageTransformationOpen() &&
46+
isImageTransformationOpen() &&
6447
node.matches(
6548
".transfo-container, .transfo-container div, .transfo-container i, .transfo-container span"
6649
)
@@ -70,41 +53,74 @@ export class ImageTransformButton extends Component {
7053
return false;
7154
}
7255

73-
onButtonClick() {
74-
this.handleImageTransformation(this.props.getSelectedImage());
75-
}
76-
77-
handleImageTransformation(image) {
78-
if (this.isImageTransformationOpen()) {
79-
this.props.resetImageTransformation(image);
80-
this.closeImageTransformation();
81-
} else {
82-
this.openImageTransformation(image);
83-
}
84-
}
85-
86-
openImageTransformation(image) {
87-
this.state.active = true;
56+
function openImageTransformation(image) {
57+
state.active = true;
8858
registry.category("main_components").add("ImageTransformation", {
8959
Component: ImageTransformation,
9060
props: {
9161
image,
92-
document: this.props.document,
93-
editable: this.props.editable,
94-
destroy: () => this.closeImageTransformation(),
95-
onChange: () => this.props.addStep(),
62+
document: document,
63+
editable: editable,
64+
destroy: () => closeImageTransformation(),
65+
onChange: () => addStep(),
9666
},
9767
});
9868
}
9969

100-
isImageTransformationOpen() {
70+
function isImageTransformationOpen() {
10171
return registry.category("main_components").contains("ImageTransformation");
10272
}
10373

104-
closeImageTransformation() {
105-
this.state.active = false;
106-
if (this.isImageTransformationOpen()) {
74+
function closeImageTransformation() {
75+
state.active = false;
76+
if (isImageTransformationOpen()) {
10777
registry.category("main_components").remove("ImageTransformation");
10878
}
10979
}
80+
81+
return {
82+
pointerDownInsideTransform: pointerDownInsideTransform,
83+
isNodeInsideTransform: isNodeInsideTransform,
84+
openImageTransformation: openImageTransformation,
85+
isImageTransformationOpen: isImageTransformationOpen,
86+
closeImageTransformation: closeImageTransformation,
87+
}
88+
}
89+
export class ImageTransformButton extends Component {
90+
static template = "html_editor.ImageTransformButton";
91+
static props = {
92+
id: String,
93+
icon: String,
94+
title: String,
95+
getSelectedImage: Function,
96+
resetImageTransformation: Function,
97+
addStep: Function,
98+
document: { validate: (p) => p.nodeType === Node.DOCUMENT_NODE },
99+
editable: { validate: (p) => p.nodeType === Node.ELEMENT_NODE },
100+
...toolbarButtonProps,
101+
activeTitle: String,
102+
};
103+
104+
setup() {
105+
this.state = useState({ active: false });
106+
Object.assign(this.props, useTransformOperations(
107+
this.state,
108+
this.props.document,
109+
this.props.editable,
110+
this.props.addStep
111+
));
112+
}
113+
114+
onButtonClick() {
115+
this.handleImageTransformation(this.props.getSelectedImage());
116+
}
117+
118+
handleImageTransformation(image) {
119+
if (this.props.isImageTransformationOpen()) {
120+
this.props.resetImageTransformation(image);
121+
this.props.closeImageTransformation();
122+
} else {
123+
this.props.openImageTransformation(image);
124+
}
125+
}
110126
}

addons/website/static/src/builder/plugins/image/image_tool_option.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import { BaseOptionComponent } from "@html_builder/core/utils";
22
import { ImageShapeOption } from "./image_shape_option";
33
import { ImageFilterOption } from "./image_filter_option";
44
import { ImageFormatOption } from "./image_format_option";
5+
import { ImageTransformButton } from "./image_transform_button";
56

67
export class ImageToolOption extends BaseOptionComponent {
78
static template = "html_builder.ImageToolOption";
89
static components = {
910
ImageShapeOption,
1011
ImageFilterOption,
1112
ImageFormatOption,
13+
ImageTransformButton,
1214
};
1315
static props = {};
1416
}

addons/website/static/src/builder/plugins/image/image_tool_option.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
<BuilderRow label.translate="Transform" preview="false">
2020
<BuilderButton title.translate="Crop image" action="'cropImage'" icon="'fa-crop'" id="'cropImage'" />
2121
<BuilderButton title.translate="Reset crop" action="'resetCrop'" t-if="this.isActiveItem('cropImage')">Reset</BuilderButton>
22-
<BuilderButton title.translate="Transform the picture" action="'transformImage'" icon="'fa-object-ungroup'" id="'transformImage'" />
23-
<BuilderButton title.translate="Reset transformation" action="'resetTransformImage'" t-if="this.isActiveItem('transformImage')">Reset</BuilderButton>
22+
<ImageTransformButton id="'transformImage'"/>
2423
</BuilderRow>
2524
<ImageFilterOption />
2625
<BuilderRow label.translate="Size">

addons/website/static/src/builder/plugins/image/image_tool_option_plugin.js

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,23 +77,6 @@ class ImageToolOptionPlugin extends Plugin {
7777
updateImageAttributes();
7878
},
7979
},
80-
transformImage: {
81-
isApplied: ({ editingElement }) => editingElement.matches(`[style*="transform"]`),
82-
apply: () => {
83-
this.dependencies.userCommand.getCommand("transformImage").run();
84-
},
85-
},
86-
resetTransformImage: {
87-
apply: ({ editingElement }) => {
88-
editingElement.setAttribute(
89-
"style",
90-
(editingElement.getAttribute("style") || "").replace(
91-
/[^;]*transform[\w:]*;?/g,
92-
""
93-
)
94-
);
95-
},
96-
},
9780
replaceMedia: {
9881
load: async ({ editingElement }) => {
9982
let icon;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Component, useState } from "@odoo/owl";
2+
import { useTransformOperations } from "@html_editor/main/media/image_transform_button";
3+
import { _t } from "@web/core/l10n/translation";
4+
import { useDomState } from "@html_builder/core/utils";
5+
6+
export class ImageTransformButton extends Component {
7+
static template = "html_builder.ImageTransformButton";
8+
static props = {
9+
pointerDownInsideTransform: Function,
10+
isNodeInsideTransform: Function,
11+
openImageTransformation: Function,
12+
isImageTransformationOpen: Function,
13+
closeImageTransformation: Function,
14+
};
15+
16+
setup() {
17+
this.state = useState({ active: false });
18+
this.domState = useDomState(
19+
(editingElement) => ({
20+
applied: editingElement.matches(`[style*="transform"]`)
21+
})
22+
);
23+
this.document = this.env.editor.document;
24+
this.editable = this.env.editor.editable;
25+
this.addStep = this.env.editor.shared.history.addStep.bind(this);
26+
Object.assign(this.props, useTransformOperations(
27+
this.state,
28+
this.document,
29+
this.editable,
30+
this.addStep,
31+
));
32+
}
33+
34+
getSelectedImage() {
35+
const selectedNodes = this.env.editor.shared.selection.getSelectedNodes();
36+
return selectedNodes.find((node) => node.tagName === "IMG");
37+
}
38+
39+
onResetButtonClick() {
40+
this.resetImageTransformation(this.getSelectedImage());
41+
if (this.props.isImageTransformationOpen()) {
42+
this.props.closeImageTransformation();
43+
}
44+
}
45+
46+
resetImageTransformation(image) {
47+
image.setAttribute(
48+
"style",
49+
(image.getAttribute("style") || "").replace(/[^;]*transform[\w:]*;?/g, "")
50+
);
51+
this.addStep();
52+
}
53+
54+
onTransformButtonClick() {
55+
let image = this.getSelectedImage();
56+
if (!this.props.isImageTransformationOpen()) {
57+
this.props.openImageTransformation(image);
58+
}
59+
}
60+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<templates xml:space="preserve">
2+
<t t-name="html_builder.ImageTransformButton">
3+
<button type="button" class="btn btn-primary fa fa-fw" t-att-class="'fa-object-ungroup' + (state.active ? ' active' : '')" style="min-width: min-content;" title="Transform the picture" t-on-click="onTransformButtonClick" />
4+
<button type="button" class="btn btn-primary" style="min-width: min-content;" title="Reset transformation" t-on-click="onResetButtonClick" t-if="domState.applied">Reset</button>
5+
</t>
6+
</templates>

addons/website/static/src/builder/plugins/options/animate_option.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export class AnimateOption extends BaseOptionComponent {
3434
.getDirectionsItems(editingElement)
3535
.filter((i) => !i.check || i.check(editingElement)),
3636
isInDropdown: editingElement.closest(".dropdown"),
37+
isTransformActive: editingElement.matches(`[style*="transform"]`),
3738
};
3839
});
3940
}

addons/website/static/src/builder/plugins/options/animate_option.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<t t-name="html_builder.AnimateOption">
55
<t t-if="this.state.isOptionActive">
66
<BuilderRow label.translate="Animation">
7-
<BuilderSelect t-if="!this.isActiveItem('transformImage')" preview="false">
7+
<BuilderSelect t-if="!this.state.isTransformActive" preview="false">
88
<BuilderSelectItem action="'setAnimationMode'" actionValue="''"
99
classAction="''"
1010
id="'no_animation_opt'">None</BuilderSelectItem>

0 commit comments

Comments
 (0)