[IMP] web_editor, website: add an "on hover" option for animations
This commit adds an "on hover" option for animations. We've added the "on hover" option to the animation selector. This feature is only available for images. It allows you to pick an animation that triggers when you hover your mouse over an image. There are six different animations to choose from. This new animation option doesn't work with animated shapes and devices shapes. task-3094258 closes odoo/odoo#119197 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
@@ -180,6 +180,18 @@ export class MediaDialog extends Component {
|
||||
if (this.props.media.dataset.shapeRotate) {
|
||||
element.dataset.shapeRotate = this.props.media.dataset.shapeRotate;
|
||||
}
|
||||
if (this.props.media.dataset.hoverEffect) {
|
||||
element.dataset.hoverEffect = this.props.media.dataset.hoverEffect;
|
||||
}
|
||||
if (this.props.media.dataset.hoverEffectColor) {
|
||||
element.dataset.hoverEffectColor = this.props.media.dataset.hoverEffectColor;
|
||||
}
|
||||
if (this.props.media.dataset.hoverEffectStrokeWidth) {
|
||||
element.dataset.hoverEffectStrokeWidth = this.props.media.dataset.hoverEffectStrokeWidth;
|
||||
}
|
||||
if (this.props.media.dataset.hoverEffectIntensity) {
|
||||
element.dataset.hoverEffectIntensity = this.props.media.dataset.hoverEffectIntensity;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const otherTab of Object.keys(TABS).filter(key => key !== this.state.activeTab)) {
|
||||
|
||||
@@ -6115,6 +6115,13 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
async setImgShape(previewMode, widgetValue, params) {
|
||||
const img = this._getImg();
|
||||
const saveData = previewMode === false;
|
||||
if (img.dataset.hoverEffect && !widgetValue) {
|
||||
// When a shape is removed and there is a hover effect on the
|
||||
// image, we then place the "Square" shape as the default because a
|
||||
// shape is required for the hover effects to work.
|
||||
const shapeImgShapeWidget = this._requestUserValueWidgets("shape_img_square_opt")[0];
|
||||
widgetValue = shapeImgShapeWidget.getActiveValue("setImgShape");
|
||||
}
|
||||
if (widgetValue) {
|
||||
await this._loadShape(widgetValue);
|
||||
if (previewMode === 'reset' && img.dataset.shapeColors) {
|
||||
@@ -6131,9 +6138,18 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
}
|
||||
// When the user selects a shape, we remove the data attributes
|
||||
// that are not compatible with this shape.
|
||||
if (saveData && !this._isTransformableShape()) {
|
||||
delete img.dataset.shapeFlip;
|
||||
delete img.dataset.shapeRotate;
|
||||
if (saveData) {
|
||||
if (!this._isTransformableShape()) {
|
||||
delete img.dataset.shapeFlip;
|
||||
delete img.dataset.shapeRotate;
|
||||
}
|
||||
if (params.animated || this._isDeviceShape()) {
|
||||
delete img.dataset.hoverEffect;
|
||||
delete img.dataset.hoverEffectColor;
|
||||
delete img.dataset.hoverEffectStrokeWidth;
|
||||
delete img.dataset.hoverEffectIntensity;
|
||||
img.classList.remove("o_animate_on_hover");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -6198,6 +6214,111 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
async setImgShapeRotateRight(previewMode, widgetValue, params) {
|
||||
await this._setImgShapeRotate(90);
|
||||
},
|
||||
/**
|
||||
* Sets the hover effects of the image shape.
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
async setImgShapeHoverEffect(previewMode, widgetValue, params) {
|
||||
const imgEl = this._getImg();
|
||||
delete imgEl.dataset.hoverEffectColor;
|
||||
delete imgEl.dataset.hoverEffectIntensity;
|
||||
delete imgEl.dataset.hoverEffectStrokeWidth;
|
||||
if (params.name === "hover_effect_overlay_opt") {
|
||||
imgEl.dataset.hoverEffectColor = this._getCSSColorValue("black-25");
|
||||
} else if (params.name === "hover_effect_outline_opt") {
|
||||
imgEl.dataset.hoverEffectColor = this._getCSSColorValue("primary");
|
||||
imgEl.dataset.hoverEffectStrokeWidth = 10;
|
||||
} else {
|
||||
imgEl.dataset.hoverEffectIntensity = 20;
|
||||
if (params.name !== "hover_effect_mirror_blur_opt") {
|
||||
imgEl.dataset.hoverEffectColor = "rgba(0, 0, 0, 0)";
|
||||
}
|
||||
}
|
||||
await this._applyOptions();
|
||||
},
|
||||
/**
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
async selectDataAttribute(previewMode, widgetValue, params) {
|
||||
await this._super(...arguments);
|
||||
if (["hoverEffectIntensity", "hoverEffectStrokeWidth"].includes(params.attributeName)) {
|
||||
await this._applyOptions();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Sets the color of hover effects.
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
async setHoverEffectColor(previewMode, widgetValue, params) {
|
||||
const img = this._getImg();
|
||||
let defaultColor = "rgba(0, 0, 0, 0)";
|
||||
if (img.dataset.hoverEffect === "overlay") {
|
||||
defaultColor = "black-25";
|
||||
} else if (img.dataset.hoverEffect === "outline") {
|
||||
defaultColor = "primary";
|
||||
}
|
||||
img.dataset.hoverEffectColor = this._getCSSColorValue(widgetValue || defaultColor);
|
||||
await this._applyOptions();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
notify(name) {
|
||||
if (name === "enable_hover_effect") {
|
||||
this.trigger_up("snippet_edition_request", {exec: () => {
|
||||
// Add the "Overlay" hover effect to the shape.
|
||||
const hoverEffectOverlayWidget = this._requestUserValueWidgets("hover_effect_overlay_opt")[0];
|
||||
hoverEffectOverlayWidget.enable();
|
||||
hoverEffectOverlayWidget.getParent().close(); // FIXME remove this ugly hack asap
|
||||
// Add the "square" shape to the image if it has no shape
|
||||
// because the "hover effects" need a shape to work.
|
||||
const imgEl = this._getImg();
|
||||
const shapeName = imgEl.dataset.shape?.split("/")[2];
|
||||
if (!shapeName) {
|
||||
const shapeImgSquareWidget = this._requestUserValueWidgets("shape_img_square_opt")[0];
|
||||
shapeImgSquareWidget.enable();
|
||||
shapeImgSquareWidget.getParent().close(); // FIXME remove this ugly hack asap
|
||||
}
|
||||
}});
|
||||
} else if (name === "disable_hover_effect") {
|
||||
this._disableHoverEffect();
|
||||
} else {
|
||||
this._super(...arguments);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async updateUI() {
|
||||
await this._super(...arguments);
|
||||
// Adapts the colorpicker label according to the selected "On Hover"
|
||||
// animation.
|
||||
const hoverEffectName = this.$target[0].dataset.hoverEffect;
|
||||
if (hoverEffectName) {
|
||||
const hoverEffectColorWidget = this.findWidget("hover_effect_color_opt");
|
||||
const needToAdaptLabel = ["image_zoom_in", "image_zoom_out", "dolly_zoom"].includes(hoverEffectName);
|
||||
const labelEl = hoverEffectColorWidget.el.querySelector("we-title");
|
||||
if (!this._originalHoverEffectColorLabel) {
|
||||
this._originalHoverEffectColorLabel = labelEl.textContent;
|
||||
}
|
||||
labelEl.textContent = needToAdaptLabel
|
||||
? _t("Overlay")
|
||||
: this._originalHoverEffectColorLabel;
|
||||
}
|
||||
// Move the "hover effects" options to the 'websiteAnimate' options.
|
||||
const hoverEffectsOptionsEl = this.$el[0].querySelector("#o_hover_effects_options");
|
||||
const animationEffectWidget = this._requestUserValueWidgets("animation_effect_opt")[0];
|
||||
if (hoverEffectsOptionsEl && animationEffectWidget) {
|
||||
animationEffectWidget.getParent().$el[0].append(hoverEffectsOptionsEl);
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
@@ -6285,6 +6406,7 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
async _writeShape(svgText) {
|
||||
const img = this._getImg();
|
||||
const initialImageWidth = img.naturalWidth;
|
||||
let needToRefreshPublicWidgets = false;
|
||||
|
||||
const svg = new DOMParser().parseFromString(svgText, 'image/svg+xml').documentElement;
|
||||
|
||||
@@ -6307,6 +6429,14 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
svg.querySelector("#filterPath").setAttribute("style", `transform: ${shapeTransformValues.join(" ")}; ${transformOrigin}`);
|
||||
}
|
||||
|
||||
// Add shape animations on hover.
|
||||
if (img.dataset.hoverEffect && !this._isDeviceShape() && !this._isAnimatedShape()) {
|
||||
this._addImageShapeHoverEffect(svg, img);
|
||||
// The "ImageShapeHoverEffet" public widget needs to restart
|
||||
// (e.g. image replacement).
|
||||
needToRefreshPublicWidgets = true;
|
||||
}
|
||||
|
||||
const svgAspectRatio = parseInt(svg.getAttribute('width')) / parseInt(svg.getAttribute('height'));
|
||||
// We will store the image in base64 inside the SVG.
|
||||
// applyModifications will return a dataURL with the current filters
|
||||
@@ -6319,7 +6449,9 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
};
|
||||
const imgDataURL = await applyModifications(img, options);
|
||||
svg.removeChild(svg.querySelector('#preview'));
|
||||
svg.querySelector('image').setAttribute('xlink:href', imgDataURL);
|
||||
svg.querySelectorAll("image").forEach(image => {
|
||||
image.setAttribute("xlink:href", imgDataURL);
|
||||
});
|
||||
// Force natural width & height (note: loading the original image is
|
||||
// needed for Safari where natural width & height of SVG does not return
|
||||
// the correct values).
|
||||
@@ -6342,7 +6474,11 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
const dataURL = await createDataURL(blob);
|
||||
const imgFilename = (img.dataset.originalSrc.split('/').pop()).split('.')[0];
|
||||
img.dataset.fileName = `${imgFilename}.svg`;
|
||||
return loadImage(dataURL, img);
|
||||
const loadedImg = await loadImage(dataURL, img);
|
||||
if (needToRefreshPublicWidgets) {
|
||||
this._refreshPublicWidgets();
|
||||
}
|
||||
return loadedImg;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
@@ -6420,6 +6556,33 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
"img_shape_transform_rotate_x_opt", "img_shape_transform_rotate_y_opt"].includes(params.name)) {
|
||||
return this._isTransformableShape();
|
||||
}
|
||||
if (widgetName === "hover_effect_none_opt") {
|
||||
// The hover effects are removed with the "WebsiteAnimate" animation
|
||||
// selector so this option should not be visible.
|
||||
return false;
|
||||
}
|
||||
if (params.optionsPossibleValues.setImgShapeHoverEffect) {
|
||||
const imgEl = this._getImg();
|
||||
return imgEl.classList.contains("o_animate_on_hover") && !this._isDeviceShape() && !this._isAnimatedShape();
|
||||
}
|
||||
// If "Description" or "Tooltip" options.
|
||||
if (["alt", "title"].includes(params.attributeName)) {
|
||||
const imgEl = this._getImg();
|
||||
return !imgEl.matches("[data-oe-type='image'] > img, [data-oe-xpath]");
|
||||
}
|
||||
// The "Square" shape is only used for hover effects. It is
|
||||
// automatically set when there is an hover effect and no shape is
|
||||
// chosen by the user. This shape is always hidden in the shape select.
|
||||
if (widgetName === "shape_img_square_opt") {
|
||||
return false;
|
||||
}
|
||||
if (widgetName === "remove_img_shape_opt") {
|
||||
// Do not show the "remove shape" button when the "square" shape is
|
||||
// enable. The "square" shape is only enable when there is a hover
|
||||
// effect and it is always hidden in the shape select.
|
||||
const shapeImgShareWidget = this._requestUserValueWidgets("shape_img_square_opt")[0];
|
||||
return !shapeImgShareWidget.isActive();
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
@@ -6461,6 +6624,10 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
const imgEl = this._getImg();
|
||||
return imgEl.dataset.shapeFlip?.includes("y") || "";
|
||||
}
|
||||
case 'setHoverEffectColor': {
|
||||
const imgEl = this._getImg();
|
||||
return imgEl.dataset.hoverEffectColor || "";
|
||||
}
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
@@ -6578,6 +6745,10 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
delete img.dataset.originalMimetype;
|
||||
delete img.dataset.shapeFlip;
|
||||
delete img.dataset.shapeRotate;
|
||||
delete img.dataset.hoverEffect;
|
||||
delete img.dataset.hoverEffectColor;
|
||||
delete img.dataset.hoverEffectStrokeWidth;
|
||||
delete img.dataset.hoverEffectIntensity;
|
||||
return;
|
||||
}
|
||||
if (img.dataset.mimetype !== "image/svg+xml") {
|
||||
@@ -6670,6 +6841,149 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
const shapeImgWidget = this._requestUserValueWidgets("shape_img_opt")[0];
|
||||
return (shapeImgWidget && !shapeImgWidget.getMethodsParams().noTransform) && !this._isDeviceShape();
|
||||
},
|
||||
/**
|
||||
* Checks if the shape is in animated.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isAnimatedShape() {
|
||||
const shapeImgWidget = this._requestUserValueWidgets("shape_img_opt")[0];
|
||||
return shapeImgWidget && shapeImgWidget.getMethodsParams().animated;
|
||||
},
|
||||
/**
|
||||
* Adds hover effect to the SVG.
|
||||
*
|
||||
* @private
|
||||
* @param {HTMLElement} svgEl
|
||||
* @param {HTMLImageElement} [img] img element
|
||||
*/
|
||||
async _addImageShapeHoverEffect(svgEl, img) {
|
||||
let rgba = null;
|
||||
let rbg = null;
|
||||
let opacity = null;
|
||||
// Add the required parts for the hover effects to the SVG.
|
||||
const hoverEffectName = img.dataset.hoverEffect;
|
||||
if (!this.hoverEffectsSvg) {
|
||||
this.hoverEffectsSvg = await this._getHoverEffects();
|
||||
}
|
||||
const hoverEffectEls = this.hoverEffectsSvg.querySelectorAll(`#${hoverEffectName} > *`);
|
||||
hoverEffectEls.forEach(hoverEffectEl => {
|
||||
svgEl.appendChild(hoverEffectEl.cloneNode(true));
|
||||
});
|
||||
// Modifies the svg according to the chosen hover effect and the value
|
||||
// of the options.
|
||||
const animateEl = svgEl.querySelector("animate");
|
||||
const animateTransformEls = svgEl.querySelectorAll("animateTransform");
|
||||
const animateElValues = animateEl?.getAttribute("values");
|
||||
let animateTransformElValues = animateTransformEls[0]?.getAttribute("values");
|
||||
if (img.dataset.hoverEffectColor) {
|
||||
rgba = convertCSSColorToRgba(img.dataset.hoverEffectColor);
|
||||
rbg = `rgb(${rgba.red},${rgba.green},${rgba.blue})`;
|
||||
opacity = rgba.opacity / 100;
|
||||
if (!["outline", "image_mirror_blur"].includes(hoverEffectName)) {
|
||||
svgEl.querySelector('[fill="hover_effect_color"]').setAttribute("fill", rbg);
|
||||
animateEl.setAttribute("values", animateElValues.replace("hover_effect_opacity", opacity));
|
||||
}
|
||||
}
|
||||
switch (hoverEffectName) {
|
||||
case "outline": {
|
||||
svgEl.querySelector('[stroke="hover_effect_color"]').setAttribute("stroke", rbg);
|
||||
svgEl.querySelector('[stroke-opacity="hover_effect_opacity"]').setAttribute("stroke-opacity", opacity);
|
||||
// The stroke width needs to be multiplied by two because half
|
||||
// of the stroke is invisible since it is centered on the path.
|
||||
const strokeWidth = parseInt(img.dataset.hoverEffectStrokeWidth) * 2;
|
||||
animateEl.setAttribute("values", animateElValues.replace("hover_effect_stroke_width", strokeWidth));
|
||||
break;
|
||||
}
|
||||
case "image_zoom_in":
|
||||
case "image_zoom_out":
|
||||
case "dolly_zoom": {
|
||||
const imageEl = svgEl.querySelector("image");
|
||||
const clipPathEl = svgEl.querySelector("#clip-path");
|
||||
imageEl.setAttribute("id", "shapeImage");
|
||||
// Modify the SVG so that the clip-path is not zoomed when the
|
||||
// image is zoomed.
|
||||
imageEl.setAttribute("style", "transform-origin: center; width: 100%; height: 100%");
|
||||
imageEl.setAttribute("preserveAspectRatio", "none");
|
||||
svgEl.setAttribute("viewBox", "0 0 1 1");
|
||||
svgEl.setAttribute("preserveAspectRatio", "none");
|
||||
clipPathEl.setAttribute("clipPathUnits", "userSpaceOnUse");
|
||||
const clipPathValue = imageEl.getAttribute("clip-path");
|
||||
imageEl.removeAttribute("clip-path");
|
||||
const gEl = document.createElementNS("http://www.w3.org/2000/svg", "g");
|
||||
gEl.setAttribute("clip-path", clipPathValue);
|
||||
imageEl.parentNode.replaceChild(gEl, imageEl);
|
||||
gEl.appendChild(imageEl);
|
||||
let zoomValue = 1.01 + parseInt(img.dataset.hoverEffectIntensity) / 200;
|
||||
animateTransformEls[0].setAttribute("values", animateTransformElValues.replace("hover_effect_zoom", zoomValue));
|
||||
if (hoverEffectName === "image_zoom_out") {
|
||||
// Set zoom intensity for the image.
|
||||
const styleAttr = svgEl.querySelector("style");
|
||||
styleAttr.textContent = styleAttr.textContent.replace("hover_effect_zoom", zoomValue);
|
||||
}
|
||||
if (hoverEffectName === "dolly_zoom") {
|
||||
clipPathEl.setAttribute("style", "transform-origin: center;");
|
||||
// Set zoom intensity for clip-path and overlay.
|
||||
zoomValue = 0.99 - parseInt(img.dataset.hoverEffectIntensity) / 2000;
|
||||
animateTransformEls.forEach((animateTransformEl, index) => {
|
||||
if (index > 0) {
|
||||
animateTransformElValues = animateTransformEl.getAttribute("values");
|
||||
animateTransformEl.setAttribute("values", animateTransformElValues.replace("hover_effect_zoom", zoomValue));
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "image_mirror_blur": {
|
||||
const imageEl = svgEl.querySelector("image");
|
||||
imageEl.setAttribute('id', 'shapeImage');
|
||||
imageEl.setAttribute('style', 'transform-origin: center;');
|
||||
const imageMirrorEl = imageEl.cloneNode();
|
||||
imageMirrorEl.setAttribute("id", 'shapeImageMirror');
|
||||
imageMirrorEl.setAttribute("filter", "url(#blurFilter)");
|
||||
imageEl.insertAdjacentElement("beforebegin", imageMirrorEl);
|
||||
const zoomValue = 0.99 - parseInt(img.dataset.hoverEffectIntensity) / 200;
|
||||
animateTransformEls[0].setAttribute("values", animateTransformElValues.replace("hover_effect_zoom", zoomValue));
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Gets the hover effects list.
|
||||
*
|
||||
* @private
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
_getHoverEffects() {
|
||||
const hoverEffectsURL = "/website/static/src/svg/hover_effects.svg";
|
||||
return fetch(hoverEffectsURL)
|
||||
.then(response => response.text())
|
||||
.then(text => {
|
||||
const parser = new DOMParser();
|
||||
const xmlDoc = parser.parseFromString(text, "text/xml");
|
||||
return xmlDoc.getElementsByTagName("svg")[0];
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Disables the hover effect on the image.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
async _disableHoverEffect() {
|
||||
const imgEl = this._getImg();
|
||||
const shapeName = imgEl.dataset.shape?.split("/")[2];
|
||||
delete imgEl.dataset.hoverEffect;
|
||||
delete imgEl.dataset.hoverEffectColor;
|
||||
delete imgEl.dataset.hoverEffectStrokeWidth;
|
||||
delete imgEl.dataset.hoverEffectIntensity;
|
||||
await this._applyOptions();
|
||||
// If "Square" shape, remove it, it doesn't make sense to keep it
|
||||
// without hover effect.
|
||||
if (shapeName === "geo_square") {
|
||||
this._requestUserValueWidgets("remove_img_shape_opt")[0].enable();
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
|
||||
@@ -480,17 +480,6 @@
|
||||
</we-button-group>
|
||||
</div>
|
||||
|
||||
<div data-selector="img" data-exclude="[data-oe-type='image'] > img, [data-oe-xpath]">
|
||||
<we-input string="Description" class="o_we_large"
|
||||
data-select-attribute="" data-attribute-name="alt"
|
||||
placeholder="Alt tag"
|
||||
title="'Alt tag' specifies an alternate text for an image, if the image cannot be displayed (slow connection, missing image, screen reader ...)."/>
|
||||
<we-input string="Tooltip" class="o_we_large"
|
||||
data-select-attribute="" data-attribute-name="title"
|
||||
placeholder="Title tag"
|
||||
title="'Title tag' is shown as a tooltip when you hover the picture."/>
|
||||
</div>
|
||||
|
||||
<t t-set="no_animations" t-value="False"/>
|
||||
<div data-js="ImageTools"
|
||||
data-selector="img">
|
||||
@@ -518,7 +507,7 @@
|
||||
<div class="o_scroll_shapes_basic">
|
||||
<we-title class="">Geometrics</we-title>
|
||||
<we-select-page string="Geometrics">
|
||||
<we-button data-set-img-shape="web_editor/geometric/geo_square" data-select-label="Square" data-no-transform="true"/>
|
||||
<we-button data-set-img-shape="web_editor/geometric/geo_square" data-no-transform="true" data-name="shape_img_square_opt"/> <!-- hidden and only used for hover effects -->
|
||||
<we-button data-set-img-shape="web_editor/geometric/geo_shuriken" data-select-label="Shuriken" data-no-transform="true"/>
|
||||
<we-button data-set-img-shape="web_editor/geometric/geo_diamond" data-select-label="Diamond" data-no-transform="true"/>
|
||||
<we-button data-set-img-shape="web_editor/geometric/geo_triangle" data-select-label="Triangle"/>
|
||||
@@ -710,7 +699,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</we-select-pager>
|
||||
<we-button data-set-img-shape="" data-no-preview="true" data-label="None" data-dependencies="shape_img_opt" class="o_we_image_shape_remove o_we_bg_danger fa fa-fw fa-times"/>
|
||||
<we-button data-set-img-shape="" data-no-preview="true" data-label="None" data-dependencies="shape_img_opt"
|
||||
class="o_we_image_shape_remove o_we_bg_danger fa fa-fw fa-times" data-name="remove_img_shape_opt"/>
|
||||
</we-row>
|
||||
<we-row string="Colors" class="o_we_sublevel_1">
|
||||
<we-colorpicker data-set-img-shape-color="true" data-dependencies="shape_img_opt" data-name="img-shape-color-0" data-color-id="0"/>
|
||||
@@ -730,7 +720,14 @@
|
||||
data-no-preview="true" data-dependencies="shape_img_opt" title="Rotate right"/>
|
||||
</we-row>
|
||||
</div>
|
||||
|
||||
<we-input string="Description" class="o_we_large"
|
||||
data-select-attribute="" data-attribute-name="alt"
|
||||
placeholder="Alt tag"
|
||||
title="'Alt tag' specifies an alternate text for an image, if the image cannot be displayed (slow connection, missing image, screen reader ...)."/>
|
||||
<we-input string="Tooltip" class="o_we_large"
|
||||
data-select-attribute="" data-attribute-name="title"
|
||||
placeholder="Title tag"
|
||||
title="'Title tag' is shown as a tooltip when you hover the picture."/>
|
||||
<we-row string="Transform">
|
||||
<we-button class="fa fa-fw fa-crop" data-crop="true" data-no-preview="true" title="Crop Image"/>
|
||||
<we-button class="o_we_bg_danger ms-0 border-start-0" data-reset-crop="" data-no-preview="true" title="Reset crop">
|
||||
@@ -744,7 +741,7 @@
|
||||
|
||||
<t t-call="web_editor.snippet_options_image_optimization_widgets"/>
|
||||
|
||||
<we-button-group string="Width" data-css-property="width">
|
||||
<we-button-group string="Size" data-css-property="width">
|
||||
<we-button data-select-style="" title="Resize Default">Default</we-button>
|
||||
<we-button data-select-style="25%" title="Resize Quarter">25%</we-button>
|
||||
<we-button data-select-style="50%" title="Resize Half">50%</we-button>
|
||||
|
||||
@@ -1681,6 +1681,139 @@ registry.ZoomedBackgroundShape = publicWidget.Widget.extend({
|
||||
},
|
||||
});
|
||||
|
||||
registry.ImageShapeHoverEffet = publicWidget.Widget.extend({
|
||||
selector: "img[data-hover-effect]",
|
||||
disabledInEditableMode: false,
|
||||
events: {
|
||||
"mouseenter": "_onMouseEnter",
|
||||
"mouseleave": "_onMouseLeave",
|
||||
},
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
this.lastMouseEvent = Promise.resolve();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start() {
|
||||
this._super(...arguments);
|
||||
this.originalImgSrc = this.el.src;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
destroy() {
|
||||
this._super(...arguments);
|
||||
if (this.originalImgSrc && (this.lastImgSrc === this.el.src)) {
|
||||
this.el.src = this.originalImgSrc;
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onMouseEnter() {
|
||||
if (!this.originalImgSrc) {
|
||||
return;
|
||||
}
|
||||
this.lastMouseEvent = this.lastMouseEvent.then(() => new Promise((resolve) => {
|
||||
if (!this.svgInEl) {
|
||||
fetch(this.el.src)
|
||||
.then(response => response.text())
|
||||
.then(text => {
|
||||
const parser = new DOMParser();
|
||||
const result = parser.parseFromString(text, "text/xml");
|
||||
const svg = result.getElementsByTagName("svg")[0];
|
||||
this.svgInEl = svg;
|
||||
if (!this.svgInEl) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
// Start animations.
|
||||
const animateEls = this.svgInEl.querySelectorAll("#hoverEffects animateTransform, #hoverEffects animate");
|
||||
animateEls.forEach(animateTransformEl => {
|
||||
animateTransformEl.removeAttribute("begin");
|
||||
});
|
||||
this._setImgSrc(this.svgInEl, resolve);
|
||||
});
|
||||
} else {
|
||||
this._setImgSrc(this.svgInEl, resolve);
|
||||
}
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onMouseLeave() {
|
||||
this.lastMouseEvent = this.lastMouseEvent.then(() => new Promise((resolve) => {
|
||||
if (!this.originalImgSrc || !this.svgInEl) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
if (!this.svgOutEl) {
|
||||
// Reverse animations.
|
||||
this.svgOutEl = this.svgInEl.cloneNode(true);
|
||||
const animateTransformEls = this.svgOutEl.querySelectorAll("#hoverEffects animateTransform, #hoverEffects animate");
|
||||
animateTransformEls.forEach(animateTransformEl => {
|
||||
let valuesValue = animateTransformEl.getAttribute("values");
|
||||
valuesValue = valuesValue.split(";").reverse().join(";");
|
||||
animateTransformEl.setAttribute("values", valuesValue);
|
||||
});
|
||||
}
|
||||
this._setImgSrc(this.svgOutEl, resolve);
|
||||
}));
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Converts the SVG to a data URI and set it as the image source.
|
||||
*
|
||||
 * @private
|
||||
* @param {HTMLElement} svg
|
||||
 */
|
||||
_setImgSrc(svg, resolve) {
|
||||
// Add random class to prevent browser from caching image. Otherwise the
|
||||
// animations do not trigger more than once.
|
||||
const previousRandomClass = [...svg.classList].find(cl => cl.startsWith("o_shape_anim_random_"));
|
||||
svg.classList.remove(previousRandomClass);
|
||||
svg.classList.add("o_shape_anim_random_" + Date.now());
|
||||
// Convert the SVG element to a data URI.
|
||||
const svg64 = btoa(new XMLSerializer().serializeToString(svg));
|
||||
// The image is preloaded to avoid a flickering when it is added to the
|
||||
// DOM.
|
||||
const preloadedImg = new Image();
|
||||
preloadedImg.src = `data:image/svg+xml;base64,${svg64}`;
|
||||
preloadedImg.onload = () => {
|
||||
if (this.isDestroyed()) {
|
||||
// In some cases, it is possible for the "preloadedImg" to
|
||||
// finish loading while the widget has already been destroyed.
|
||||
// So, we do not set the image source because that can cause
|
||||
// unexpected reverse of the animation.
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerUnactive("setImgHoverEffectSrc");
|
||||
this.el.src = preloadedImg.src;
|
||||
this.options.wysiwyg && this.options.wysiwyg.odooEditor.observerActive("setImgHoverEffectSrc");
|
||||
this.lastImgSrc = preloadedImg.src;
|
||||
this.el.onload = () => {
|
||||
resolve();
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
Widget: publicWidget.Widget,
|
||||
Animation: Animation,
|
||||
|
||||
@@ -3566,7 +3566,7 @@ options.registry.WebsiteAnimate = options.Class.extend({
|
||||
this._forceAnimation();
|
||||
}
|
||||
if (params.isAnimationTypeSelection) {
|
||||
this.$target[0].classList.toggle('o_animate_preview', !!widgetValue);
|
||||
this.$target[0].classList.toggle("o_animate_preview", this.$target[0].classList.contains("o_animate"));
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -3598,13 +3598,20 @@ options.registry.WebsiteAnimate = options.Class.extend({
|
||||
delete this.$target[0].dataset.scrollZoneStart;
|
||||
delete this.$target[0].dataset.scrollZoneEnd;
|
||||
}
|
||||
if (!params.activeValue && widgetValue) {
|
||||
// If "Animation" was on "None" and it is no longer, it is set to
|
||||
// "fade_in" by default.
|
||||
if (params.activeValue === "o_animate_on_hover") {
|
||||
this.trigger_up("option_update", {
|
||||
optionName: "ImageTools",
|
||||
name: "disable_hover_effect",
|
||||
});
|
||||
}
|
||||
if ((!params.activeValue || params.activeValue === "o_animate_on_hover")
|
||||
&& widgetValue && widgetValue !== "onHover") {
|
||||
// If "Animation" was on "None" or "o_animate_on_hover" and it is no
|
||||
// longer, it is set to "fade_in" by default.
|
||||
targetClassList.add('o_anim_fade_in');
|
||||
this._toggleImagesLazyLoading(false);
|
||||
}
|
||||
if (!widgetValue) {
|
||||
if (!widgetValue || widgetValue === "onHover") {
|
||||
const possibleEffects = this._requestUserValueWidgets('animation_effect_opt')[0].getMethodsParams('selectClass').possibleValues;
|
||||
const possibleDirections = this._requestUserValueWidgets('animation_direction_opt')[0].getMethodsParams('selectClass').possibleValues;
|
||||
const possibleEffectsAndDirections = possibleEffects.concat(possibleDirections);
|
||||
@@ -3619,6 +3626,12 @@ options.registry.WebsiteAnimate = options.Class.extend({
|
||||
this.$target[0].style.animationDuration = '';
|
||||
this._toggleImagesLazyLoading(true);
|
||||
}
|
||||
if (widgetValue === "onHover") {
|
||||
this.trigger_up("option_update", {
|
||||
optionName: "ImageTools",
|
||||
name: "enable_hover_effect",
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Sets the animation intensity.
|
||||
@@ -3665,18 +3678,28 @@ options.registry.WebsiteAnimate = options.Class.extend({
|
||||
* @override
|
||||
*/
|
||||
_computeWidgetVisibility(widgetName, params) {
|
||||
const hasAnimateClass = this.$target[0].classList.contains("o_animate");
|
||||
switch (widgetName) {
|
||||
case 'no_animation_opt': {
|
||||
return !this.isAnimatedText;
|
||||
}
|
||||
case 'animation_effect_opt': {
|
||||
return hasAnimateClass;
|
||||
}
|
||||
case 'animation_trigger_opt': {
|
||||
return !this.$target[0].closest('.dropdown');
|
||||
}
|
||||
case 'animation_on_scroll_opt':
|
||||
case 'animation_direction_opt': {
|
||||
if (widgetName === "animation_direction_opt" && !hasAnimateClass) {
|
||||
return false;
|
||||
}
|
||||
return !this.limitedAnimations.some(className => this.$target[0].classList.contains(className));
|
||||
}
|
||||
case 'animation_intensity_opt': {
|
||||
if (!hasAnimateClass) {
|
||||
return false;
|
||||
}
|
||||
const possibleDirections = this._requestUserValueWidgets('animation_direction_opt')[0].getMethodsParams('selectClass').possibleValues;
|
||||
if (this.$target[0].classList.contains('o_anim_fade_in')) {
|
||||
for (const targetClass of this.$target[0].classList) {
|
||||
@@ -3691,6 +3714,15 @@ options.registry.WebsiteAnimate = options.Class.extend({
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case 'animation_on_hover_opt': {
|
||||
const [hoverEffectOverlayWidget] = this._requestUserValueWidgets("hover_effect_overlay_opt");
|
||||
if (hoverEffectOverlayWidget) {
|
||||
const hoverEffectWidget = hoverEffectOverlayWidget.getParent();
|
||||
const imageToolsOpt = hoverEffectWidget.getParent();
|
||||
return !imageToolsOpt._isDeviceShape() && !imageToolsOpt._isAnimatedShape();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
|
||||
<!-- Image Zoom In -->
|
||||
<g id="image_zoom_in">
|
||||
<svg viewBox="0 0 1 1" preserveAspectRatio="none">
|
||||
<use id="shapeOverlay" xlink:href="#filterPath" fill="hover_effect_color" fill-opacity="0"/>
|
||||
</svg>
|
||||
<g id="hoverEffects">
|
||||
<animateTransform
|
||||
xlink:href="#shapeImage"
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="scale"
|
||||
values="1;hover_effect_zoom"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.5 0 0.5 1"
|
||||
dur="0.3"
|
||||
fill="freeze"
|
||||
repeatCount="1"
|
||||
begin="indefinite"/>
|
||||
<animate
|
||||
xlink:href="#shapeOverlay"
|
||||
attributeName="fill-opacity"
|
||||
values="0;hover_effect_opacity"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.5 0 0.5 1"
|
||||
dur="0.3"
|
||||
fill="freeze"
|
||||
repeatCount="1"
|
||||
begin="indefinite"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Image Zoom Out -->
|
||||
<g id="image_zoom_out">
|
||||
<svg viewBox="0 0 1 1" preserveAspectRatio="none">
|
||||
<use id="shapeOverlay" xlink:href="#filterPath" fill="hover_effect_color" fill-opacity="0"/>
|
||||
</svg>
|
||||
<g id="hoverEffects">
|
||||
<animateTransform
|
||||
xlink:href="#shapeImage"
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="scale"
|
||||
values="hover_effect_zoom;1"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.5 0 0.5 1"
|
||||
dur="0.3"
|
||||
fill="freeze"
|
||||
repeatCount="1"
|
||||
begin="indefinite"/>
|
||||
<animate
|
||||
xlink:href="#shapeOverlay"
|
||||
attributeName="fill-opacity"
|
||||
values="0;hover_effect_opacity"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.5 0 0.5 1"
|
||||
dur="0.3"
|
||||
fill="freeze"
|
||||
repeatCount="1"
|
||||
begin="indefinite"/>
|
||||
</g>
|
||||
<style>
|
||||
<!-- only if not already animated -->
|
||||
svg:not([class^="o_shape_anim_random_"]) #shapeImage {
|
||||
scale: hover_effect_zoom;
|
||||
}
|
||||
</style>
|
||||
</g>
|
||||
|
||||
<!-- Dolly Zoom -->
|
||||
<g id="dolly_zoom">
|
||||
<svg viewBox="0 0 1 1" preserveAspectRatio="none">
|
||||
<use id="shapeOverlay" xlink:href="#filterPath" fill="hover_effect_color" fill-opacity="0" style="transform-origin: center;"/>
|
||||
</svg>
|
||||
<g id="hoverEffects">
|
||||
<animateTransform
|
||||
xlink:href="#shapeImage"
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="scale"
|
||||
calcMode="spline"
|
||||
values="1;hover_effect_zoom"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.5 0 0.5 1"
|
||||
dur="0.3s"
|
||||
fill="freeze"
|
||||
repeatCount="1"
|
||||
begin="indefinite"/>
|
||||
<animateTransform
|
||||
xlink:href="#clip-path"
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="scale"
|
||||
calcMode="spline"
|
||||
values="1;hover_effect_zoom"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.5 0 0.5 1"
|
||||
dur="0.3s"
|
||||
fill="freeze"
|
||||
repeatCount="1"
|
||||
begin="indefinite"/>
|
||||
<animate
|
||||
xlink:href="#shapeOverlay"
|
||||
attributeName="fill-opacity"
|
||||
values="0;hover_effect_opacity"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.5 0 0.5 1"
|
||||
dur="0.3"
|
||||
fill="freeze"
|
||||
repeatCount="1"
|
||||
begin="indefinite"/>
|
||||
<animateTransform
|
||||
xlink:href="#shapeOverlay"
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="scale"
|
||||
calcMode="spline"
|
||||
values="1;hover_effect_zoom"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.5 0 0.5 1"
|
||||
dur="0.3s"
|
||||
fill="freeze"
|
||||
repeatCount="1"
|
||||
begin="indefinite"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Outline -->
|
||||
<g id="outline">
|
||||
<svg viewBox="0 0 1 1" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<clipPath id="shapeBorderClipPath">
|
||||
<!-- Needed because the border is "centered" on the path -->
|
||||
<use xlink:href="#filterPath"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<use id="shapeBorder" xlink:href="#filterPath" fill="none" stroke="hover_effect_color"
|
||||
stroke-width="0" stroke-opacity="hover_effect_opacity" clip-path="url(#shapeBorderClipPath)"/>
|
||||
</svg>
|
||||
<g id="hoverEffects">
|
||||
<animate
|
||||
xlink:href="#shapeBorder"
|
||||
attributeName="stroke-width"
|
||||
values="1;hover_effect_stroke_width"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.5 0 0.5 1"
|
||||
dur="0.3"
|
||||
fill="freeze"
|
||||
repeatCount="1"
|
||||
begin="indefinite"/>
|
||||
</g>
|
||||
<style>
|
||||
#filterPath {
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
</style>
|
||||
</g>
|
||||
|
||||
<!-- Overlay -->
|
||||
<g id="overlay">
|
||||
<svg viewBox="0 0 1 1" preserveAspectRatio="none">
|
||||
<use id="shapeOverlay" xlink:href="#filterPath" fill="hover_effect_color" fill-opacity="0"/>
|
||||
</svg>
|
||||
<g id="hoverEffects">
|
||||
<animate
|
||||
xlink:href="#shapeOverlay"
|
||||
attributeName="fill-opacity"
|
||||
values="0;hover_effect_opacity"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.5 0 0.5 1"
|
||||
dur="0.3"
|
||||
fill="freeze"
|
||||
repeatCount="1"
|
||||
begin="indefinite"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Image Mirror Blur -->
|
||||
<g id="image_mirror_blur">
|
||||
<g id="hoverEffects">
|
||||
<animateTransform
|
||||
xlink:href="#shapeImage"
|
||||
attributeName="transform"
|
||||
attributeType="XML"
|
||||
type="scale"
|
||||
values="1;hover_effect_zoom"
|
||||
keyTimes="0;1"
|
||||
keySplines="0.5 0 0.5 1"
|
||||
dur="0.3"
|
||||
fill="freeze"
|
||||
repeatCount="1"
|
||||
begin="indefinite"/>
|
||||
|
||||
<filter xmlns="http://www.w3.org/2000/svg" id="blurFilter">
|
||||
<feGaussianBlur stdDeviation="3"/>
|
||||
</filter>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
@@ -347,6 +347,30 @@
|
||||
<attribute name="data-dependencies">no_animation_opt</attribute>
|
||||
</xpath>
|
||||
|
||||
<!-- Hover effects options -->
|
||||
<xpath expr="//div[@data-js='ImageTools']" position="inside">
|
||||
<div id="o_hover_effects_options">
|
||||
<we-select string="Effect" class="o_we_sublevel_1" data-attribute-name="hoverEffect"
|
||||
data-set-img-shape-hover-effect="true" data-no-preview="true">
|
||||
<we-button data-select-data-attribute="" data-name="hover_effect_none_opt">None</we-button>
|
||||
<we-button data-select-data-attribute="overlay" data-name="hover_effect_overlay_opt">Overlay</we-button>
|
||||
<we-button data-select-data-attribute="image_zoom_in" data-name="hover_effect_zoom_in_opt">Zoom In</we-button>
|
||||
<we-button data-select-data-attribute="image_zoom_out" data-name="hover_effect_zoom_out_opt">Zoom Out</we-button>
|
||||
<we-button data-select-data-attribute="dolly_zoom" data-name="hover_effect_dolly_zoom_opt">Dolly Zoom</we-button>
|
||||
<we-button data-select-data-attribute="outline" data-name="hover_effect_outline_opt">Outline</we-button>
|
||||
<we-button data-select-data-attribute="image_mirror_blur" data-name="hover_effect_mirror_blur_opt">Mirror Blur</we-button>
|
||||
</we-select>
|
||||
<we-range string="Intensity" class="o_we_sublevel_2" data-select-data-attribute="" data-attribute-name="hoverEffectIntensity"
|
||||
data-dependencies="hover_effect_zoom_in_opt, hover_effect_zoom_out_opt, hover_effect_mirror_blur_opt, hover_effect_dolly_zoom_opt"
|
||||
data-min="1" data-max="100" data-step="1" data-display-range-value="true" data-no-preview="true"/>
|
||||
<we-colorpicker string="Color" class="o_we_sublevel_2" data-set-hover-effect-color="true" data-excluded="theme, common"
|
||||
data-dependencies="hover_effect_dolly_zoom_opt, hover_effect_overlay_opt, hover_effect_zoom_in_opt, hover_effect_zoom_out_opt, hover_effect_outline_opt"
|
||||
data-no-preview="true" data-name="hover_effect_color_opt"/>
|
||||
<we-input string="Stroke Width" class="o_we_sublevel_2" data-select-data-attribute="" data-attribute-name="hoverEffectStrokeWidth"
|
||||
data-step="1" data-min="1" data-unit="px" data-dependencies="hover_effect_outline_opt" data-no-preview="true"/>
|
||||
</div>
|
||||
</xpath>
|
||||
|
||||
<!-- =================================================================== -->
|
||||
<!-- Adding website specific snippet options -->
|
||||
<!-- =================================================================== -->
|
||||
@@ -1391,6 +1415,9 @@
|
||||
data-select-class="o_animate o_animate_on_scroll"
|
||||
data-force-animation="true"
|
||||
data-name="animation_on_scroll_opt">On Scroll</we-button>
|
||||
<we-button data-animation-mode="onHover"
|
||||
data-select-class="o_animate_on_hover"
|
||||
data-name="animation_on_hover_opt">On Hover</we-button>
|
||||
</we-select>
|
||||
<we-button-group data-dependencies="animation_on_scroll_opt"
|
||||
data-no-preview="true" data-force-animation="true">
|
||||
@@ -1399,8 +1426,7 @@
|
||||
</we-button-group>
|
||||
</we-row>
|
||||
<!-- Effect -->
|
||||
<we-select string="Effect" class="o_we_sublevel_1" data-dependencies="!no_animation_opt"
|
||||
data-name="animation_effect_opt">
|
||||
<we-select string="Effect" class="o_we_sublevel_1" data-name="animation_effect_opt">
|
||||
<we-button data-select-class=""
|
||||
data-name="o_anim_no_effect_opt"
|
||||
data-dependencies="no_animation_opt">None</we-button>
|
||||
@@ -1438,8 +1464,7 @@
|
||||
data-trigger="animation_direction_in_place_opt">Flip-In-Y</we-button>
|
||||
</we-select>
|
||||
<!-- Direction -->
|
||||
<we-select string="Direction" class="o_we_sublevel_1"
|
||||
data-dependencies="!no_animation_opt" data-name="animation_direction_opt"
|
||||
<we-select string="Direction" class="o_we_sublevel_1" data-name="animation_direction_opt"
|
||||
data-force-animation="true">
|
||||
<we-button data-select-class=""
|
||||
data-name="animation_direction_in_place_opt"
|
||||
@@ -1469,8 +1494,7 @@
|
||||
<we-button data-select-class="o_animate_both_scroll">Every Time</we-button>
|
||||
</we-select>
|
||||
<!-- Intensity -->
|
||||
<we-range string="Intensity" class="o_we_sublevel_1"
|
||||
data-name="animation_intensity_opt" data-dependencies="!no_animation_opt"
|
||||
<we-range string="Intensity" class="o_we_sublevel_1" data-name="animation_intensity_opt"
|
||||
data-animation-intensity="" data-min="1" data-max="100" data-step="1"
|
||||
data-display-range-value="true" data-no-preview="true"/>
|
||||
<!-- Scroll Zone -->
|
||||
|
||||
Reference in New Issue
Block a user