[IMP] website_sale: improve zoom features in front-end
This commit adds a new option for picture zoom on product pages. The designer may now select a attachment viewer like carousel upon clicking on images in the view. The hover zoom has also been fixed as it was broken due to the bootstrap 5 changes. The designer may choose between zoom on hover, on click, both or no zoom. TaskId-2922062 closes odoo/odoo#98305 Related: odoo/upgrade#3785 Signed-off-by: William Braeckman (wbr) <wbr@odoo.com>
This commit is contained in:
@@ -59,6 +59,8 @@
|
||||
'website_sale/static/src/js/website_sale_tracking.js',
|
||||
'website/static/lib/multirange/multirange_custom.js',
|
||||
'website_sale/static/src/js/website_sale_category_link.js',
|
||||
'website_sale/static/src/xml/website_sale_image_viewer.xml',
|
||||
'website_sale/static/src/js/components/website_sale_image_viewer.js',
|
||||
],
|
||||
'web._assets_primary_variables': [
|
||||
'website_sale/static/src/scss/primary_variables.scss',
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useWowlService } from '@web/legacy/utils';
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
|
||||
|
||||
const { Component, onRendered, useRef, useEffect, useState, xml } = owl;
|
||||
|
||||
const ZOOM_STEP = 0.1;
|
||||
|
||||
export class ProductImageViewer extends Dialog {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.imageContainerRef = useRef("imageContainer");
|
||||
this.images = [...this.props.images].map(image => {
|
||||
return {
|
||||
src: image.dataset.zoomImage || image.src,
|
||||
thumbnailSrc: image.src.replace('/image_1024/', '/image_128/'),
|
||||
};
|
||||
});
|
||||
this.state = useState({
|
||||
selectedImageIdx: this.props.selectedImageIdx || 0,
|
||||
imageScale: 1,
|
||||
});
|
||||
this.isDragging = false;
|
||||
this.dragStartPos = { x: 0, y: 0 };
|
||||
// Doing a full render for the translate is too slow.
|
||||
this.imageTranslate = { x: 0, y: 0 };
|
||||
useHotkey("arrowleft", this.previousImage.bind(this));
|
||||
useHotkey("arrowright", this.nextImage.bind(this));
|
||||
useHotkey("r", () => {
|
||||
this.imageTranslate = { x: 0, y: 0 };
|
||||
this.isDragging = false;
|
||||
this.state.imageScale = 1;
|
||||
this.updateImage();
|
||||
});
|
||||
|
||||
// Not using a t-on-click on purpose because we want to be able to cancel the drag
|
||||
// when we go outside of the window.
|
||||
useEffect(
|
||||
(document) => {
|
||||
const onGlobalClick = this.onGlobalClick.bind(this);
|
||||
document.addEventListener("click", onGlobalClick);
|
||||
return () => {document.removeEventListener("click", onGlobalClick)};
|
||||
},
|
||||
() => [document],
|
||||
);
|
||||
// For some reason the styling does not always update properly.
|
||||
onRendered(() => {
|
||||
this.updateImage();
|
||||
})
|
||||
}
|
||||
|
||||
get selectedImage() {
|
||||
return this.images[this.state.selectedImageIdx];
|
||||
}
|
||||
|
||||
set selectedImage(image) {
|
||||
this.state.imageScale = 1;
|
||||
this.imageTranslate = { x: 0, y: 0 };
|
||||
this.state.selectedImageIdx = this.images.indexOf(image);
|
||||
}
|
||||
|
||||
get imageStyle() {
|
||||
return `transform:
|
||||
scale3d(${this.state.imageScale}, ${this.state.imageScale}, 1);
|
||||
`;
|
||||
}
|
||||
|
||||
get imageContainerStyle() {
|
||||
return `transform: translate(${this.imageTranslate.x}px, ${this.imageTranslate.y}px);`;
|
||||
}
|
||||
|
||||
previousImage() {
|
||||
this.selectedImage = this.images[(this.state.selectedImageIdx - 1 + this.images.length) % this.images.length];
|
||||
}
|
||||
|
||||
nextImage() {
|
||||
this.selectedImage = this.images[(this.state.selectedImageIdx + 1) % this.images.length];
|
||||
}
|
||||
|
||||
updateImage() {
|
||||
if (!this.imageContainerRef || !this.imageContainerRef.el) {
|
||||
return;
|
||||
}
|
||||
this.imageContainerRef.el.style = this.imageContainerStyle;
|
||||
}
|
||||
|
||||
onGlobalClick(ev) {
|
||||
if (ev.target.tagName === "IMG") {
|
||||
// Only zoom if the image did not move
|
||||
if (this.dragStartPos.clientX === ev.clientX && this.dragStartPos.clientY === ev.clientY) {
|
||||
this.zoomIn(ZOOM_STEP * 3);
|
||||
}
|
||||
}
|
||||
if (ev.target.classList.contains('o_wsale_image_viewer_void') && !this.isDragging) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
this.data.close();
|
||||
} else {
|
||||
this.isDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
zoomIn(step=undefined) {
|
||||
this.state.imageScale += step || ZOOM_STEP;
|
||||
}
|
||||
|
||||
zoomOut(step=undefined) {
|
||||
this.state.imageScale = Math.max(0.5, this.state.imageScale - (step || ZOOM_STEP));
|
||||
}
|
||||
|
||||
onWheelImage(ev) {
|
||||
if (ev.deltaY > 0) {
|
||||
this.zoomOut();
|
||||
} else {
|
||||
this.zoomIn();
|
||||
}
|
||||
}
|
||||
|
||||
onMousedownImage(ev) {
|
||||
this.isDragging = true;
|
||||
this.dragStartPos = {
|
||||
x: ev.clientX - this.imageTranslate.x,
|
||||
y: ev.clientY - this.imageTranslate.y,
|
||||
clientX: ev.clientX,
|
||||
clientY: ev.clientY,
|
||||
};
|
||||
}
|
||||
|
||||
onGlobalMousemove(ev) {
|
||||
if (!this.isDragging) {
|
||||
return;
|
||||
}
|
||||
this.imageTranslate.x = ev.clientX - this.dragStartPos.x;
|
||||
this.imageTranslate.y = ev.clientY - this.dragStartPos.y;
|
||||
this.updateImage();
|
||||
}
|
||||
}
|
||||
ProductImageViewer.props = {
|
||||
...Dialog.props,
|
||||
images: { type: NodeList, required: true },
|
||||
selectedImageIdx: { type: Number, optional: true },
|
||||
close: Function,
|
||||
};
|
||||
delete ProductImageViewer.props.slots;
|
||||
ProductImageViewer.template = "website_sale.ProductImageViewer";
|
||||
|
||||
export class ProductImageViewerWrapper extends Component {
|
||||
setup() {
|
||||
this.dialogs = useWowlService('dialog');
|
||||
|
||||
onRendered(() => {
|
||||
this.dialogs.add(ProductImageViewer, this.props);
|
||||
});
|
||||
}
|
||||
}
|
||||
ProductImageViewerWrapper.template = xml``;
|
||||
@@ -701,22 +701,44 @@ options.registry.WebsiteSaleProductPage = options.Class.extend({
|
||||
}).then(() => this.trigger_up('request_save', {reload: true, optionSelector: this.data.selector}));
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setImageWidth(previewMode, widgetValue, params) {
|
||||
this._updateWebsiteConfig({
|
||||
product_page_image_width: widgetValue,
|
||||
_getZoomOptionData() {
|
||||
return this._userValueWidgets.find(widget => {
|
||||
return widget.options && widget.options.dataAttributes && widget.options.dataAttributes.name === "o_wsale_zoom_mode";
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setImageLayout(previewMode, widgetValue, params) {
|
||||
this._updateWebsiteConfig({
|
||||
product_page_image_layout: widgetValue,
|
||||
});
|
||||
async setImageWidth(previewMode, widgetValue, params) {
|
||||
const zoomOption = this._getZoomOptionData();
|
||||
const updateWidth = this._updateWebsiteConfig.bind(this, { product_page_image_width: widgetValue });
|
||||
if (!zoomOption || widgetValue !== "100_pc") {
|
||||
updateWidth();
|
||||
} else {
|
||||
const defaultZoomOption = "website_sale.product_picture_magnify_click";
|
||||
await this._customizeWebsiteData(defaultZoomOption, { possibleValues: zoomOption._methodsParams.optionsPossibleValues["customizeWebsiteViews"] }, true);
|
||||
updateWidth();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async setImageLayout(previewMode, widgetValue, params) {
|
||||
const zoomOption = this._getZoomOptionData();
|
||||
const updateLayout = this._updateWebsiteConfig.bind(this, { product_page_image_layout: widgetValue });
|
||||
if (!zoomOption) {
|
||||
updateLayout();
|
||||
} else {
|
||||
const imageWidthOption = this.productDetailMain.dataset.image_width;
|
||||
let defaultZoomOption = widgetValue === "grid" ? "website_sale.product_picture_magnify_click" : "website_sale.product_picture_magnify_hover";
|
||||
if (imageWidthOption === "100_pc" && defaultZoomOption === "website_sale.product_picture_magnify_hover") {
|
||||
defaultZoomOption = "website_sale.product_picture_magnify_click";
|
||||
}
|
||||
await this._customizeWebsiteData(defaultZoomOption, { possibleValues: zoomOption._methodsParams.optionsPossibleValues["customizeWebsiteViews"] }, true);
|
||||
updateLayout();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -846,12 +868,25 @@ options.registry.WebsiteSaleProductPage = options.Class.extend({
|
||||
},
|
||||
|
||||
async _computeWidgetVisibility(widgetName, params) {
|
||||
const hasImages = this.productDetailMain.dataset.image_width != 'none';
|
||||
const isFullImage = this.productDetailMain.dataset.image_width == '100_pc';
|
||||
switch (widgetName) {
|
||||
case 'o_wsale_thumbnail_pos':
|
||||
return Boolean(this.productPageCarousel);
|
||||
return Boolean(this.productPageCarousel) && hasImages;
|
||||
case 'o_wsale_grid_spacing':
|
||||
case 'o_wsale_grid_columns':
|
||||
return Boolean(this.productPageGrid);
|
||||
return Boolean(this.productPageGrid) && hasImages;
|
||||
case 'o_wsale_image_layout':
|
||||
case 'o_wsale_zoom_click':
|
||||
case 'o_wsale_zoom_none':
|
||||
case 'o_wsale_replace_main_image':
|
||||
case 'o_wsale_add_extra_images':
|
||||
case 'o_wsale_clear_extra_images':
|
||||
case 'o_wsale_zoom_mode':
|
||||
return hasImages;
|
||||
case 'o_wsale_zoom_hover':
|
||||
case 'o_wsale_zoom_both':
|
||||
return hasImages && !isFullImage;
|
||||
}
|
||||
return this._super(widgetName, params);
|
||||
}
|
||||
|
||||
@@ -207,6 +207,8 @@ require("web.zoomodoo");
|
||||
const {extraMenuUpdateCallbacks} = require('website.content.menu');
|
||||
const dom = require('web.dom');
|
||||
const { cartesian } = require('@web/core/utils/arrays');
|
||||
const { ComponentWrapper } = require('web.OwlCompatibility');
|
||||
const { ProductImageViewerWrapper } = require("@website_sale/js/components/website_sale_image_viewer");
|
||||
|
||||
publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, cartHandlerMixin, {
|
||||
selector: '.oe_website_sale',
|
||||
@@ -280,6 +282,10 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, car
|
||||
this.getRedirectOption();
|
||||
return def;
|
||||
},
|
||||
destroy() {
|
||||
this._super.apply(this, arguments);
|
||||
this._cleanupZoom();
|
||||
},
|
||||
/**
|
||||
* The selector is different when using list view of variants.
|
||||
*
|
||||
@@ -460,38 +466,97 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, car
|
||||
return VariantMixin._getProductId.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
_getProductImageLayout: function () {
|
||||
return document.querySelector("#product_detail_main").dataset.image_layout;
|
||||
},
|
||||
_getProductImageWidth: function () {
|
||||
return document.querySelector("#product_detail_main").dataset.image_width;
|
||||
},
|
||||
_getProductImageContainerSelector: function () {
|
||||
return {
|
||||
'carousel': "#o-carousel-product",
|
||||
'grid': "#o-grid-product",
|
||||
}[this._getProductImageLayout()];
|
||||
},
|
||||
_getProductImageContainer: function () {
|
||||
return document.querySelector(this._getProductImageContainerSelector());
|
||||
},
|
||||
_isEditorEnabled() {
|
||||
return document.body.classList.contains("editor_enable");
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_startZoom: function () {
|
||||
// Do not activate image zoom for mobile devices, since it might prevent users from scrolling the page
|
||||
if (!config.device.isMobile) {
|
||||
var autoZoom = $('.ecom-zoomable').data('ecom-zoom-auto') || false,
|
||||
attach = '#o-carousel-product';
|
||||
_.each($('.ecom-zoomable img[data-zoom]'), function (el) {
|
||||
onImageLoaded(el, function () {
|
||||
var $img = $(el);
|
||||
$img.zoomOdoo({event: autoZoom ? 'mouseenter' : 'click', attach: attach});
|
||||
$img.attr('data-zoom', 1);
|
||||
});
|
||||
});
|
||||
// Do not activate image zoom on hover for mobile devices
|
||||
const salePage = document.querySelector(".o_wsale_product_page");
|
||||
if (!salePage || config.device.mobile || this._getProductImageWidth() === "none") {
|
||||
return;
|
||||
}
|
||||
|
||||
function onImageLoaded(img, callback) {
|
||||
// On Chrome the load event already happened at this point so we
|
||||
// have to rely on complete. On Firefox it seems that the event is
|
||||
// always triggered after this so we can rely on it.
|
||||
//
|
||||
// However on the "complete" case we still want to keep listening to
|
||||
// the event because if the image is changed later (eg. product
|
||||
// configurator) a new load event will be triggered (both browsers).
|
||||
$(img).on('load', function () {
|
||||
callback();
|
||||
});
|
||||
if (img.complete) {
|
||||
callback();
|
||||
this._cleanupZoom();
|
||||
this.zoomCleanup = [];
|
||||
// Zoom on hover
|
||||
if (salePage.dataset.ecomZoomAuto) {
|
||||
const images = salePage.querySelectorAll("img[data-zoom]");
|
||||
for (const image of images) {
|
||||
const $image = $(image);
|
||||
const callback = () => {
|
||||
$image.zoomOdoo({
|
||||
event: "mouseenter",
|
||||
attach: this._getProductImageContainerSelector(),
|
||||
preventClicks: salePage.dataset.ecomZoomClick,
|
||||
attachToTarget: this._getProductImageLayout() === "grid",
|
||||
});
|
||||
image.dataset.zoom = 1;
|
||||
};
|
||||
image.addEventListener('load', callback);
|
||||
this.zoomCleanup.push(() => {
|
||||
image.removeEventListener('load', callback);
|
||||
const zoomOdoo = $image.data("zoomOdoo");
|
||||
if (zoomOdoo) {
|
||||
zoomOdoo.hide();
|
||||
$image.unbind();
|
||||
}
|
||||
});
|
||||
if (image.complete) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Zoom on click
|
||||
if (salePage.dataset.ecomZoomClick) {
|
||||
// In this case we want all the images not just the ones that are "zoomables"
|
||||
const images = salePage.querySelectorAll(".product_detail_img");
|
||||
for (const image of images ) {
|
||||
const handler = () => {
|
||||
if (salePage.dataset.ecomZoomAuto) {
|
||||
// Remove any flyout
|
||||
const flyouts = document.querySelectorAll(".zoomodoo-flyout");
|
||||
for (const flyout of flyouts) {
|
||||
flyout.remove();
|
||||
}
|
||||
}
|
||||
const dialog = new ComponentWrapper(this, ProductImageViewerWrapper, {
|
||||
selectedImageIdx: [...images].indexOf(image),
|
||||
images,
|
||||
});
|
||||
dialog.mount(document.body);
|
||||
};
|
||||
image.addEventListener("click", handler);
|
||||
this.zoomCleanup.push(() => {
|
||||
image.removeEventListener("click", handler);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
_cleanupZoom() {
|
||||
if (!this.zoomCleanup || !this.zoomCleanup.length) {
|
||||
return;
|
||||
}
|
||||
for (const cleanup of this.zoomCleanup) {
|
||||
cleanup();
|
||||
}
|
||||
this.zoomCleanup = undefined;
|
||||
},
|
||||
/**
|
||||
* On website, we display a carousel instead of only one image
|
||||
@@ -500,14 +565,11 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, car
|
||||
* @private
|
||||
*/
|
||||
_updateProductImage: function ($productContainer, displayImage, productId, productTemplateId, newImages, isCombinationPossible) {
|
||||
let $images = $productContainer.find('#o-carousel-product');
|
||||
if (!$images.length) {
|
||||
$images = $productContainer.find("#o-grid-product");
|
||||
}
|
||||
let $images = $productContainer.find(this._getProductImageContainerSelector());
|
||||
// When using the web editor, don't reload this or the images won't
|
||||
// be able to be edited depending on if this is done loading before
|
||||
// or after the editor is ready.
|
||||
if ($images.length && window.location.search.indexOf('enable_editor') === -1) {
|
||||
if ($images.length && !this._isEditorEnabled()) {
|
||||
const $newImages = $(newImages);
|
||||
$images.after($newImages);
|
||||
$images.remove();
|
||||
|
||||
@@ -516,8 +516,6 @@ a.no-decoration {
|
||||
|
||||
.carousel-inner {
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
@@ -666,21 +664,20 @@ a.no-decoration {
|
||||
}
|
||||
|
||||
.ecom-zoomable {
|
||||
&:not(.ecom-autozoom) {
|
||||
img[data-zoom] {
|
||||
&[data-ecom-zoom-click] {
|
||||
img.product_detail_img {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
}
|
||||
&.ecom-autozoom {
|
||||
img[data-zoom] {
|
||||
cursor: crosshair;
|
||||
}
|
||||
img[data-zoom] {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
.o_editable img[data-zoom] {
|
||||
cursor: pointer;
|
||||
}
|
||||
.zoomodoo-flyout {
|
||||
box-shadow: 0 0 20px 2px rgba(black, 0.2);
|
||||
z-index: 1050;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -138,3 +138,62 @@ $o-wsale-wizard-label-completed: $success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_wsale_image_viewer {
|
||||
z-index: -1;
|
||||
|
||||
.o_wsale_image_viewer_header {
|
||||
height: 40px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.o_wsale_image_viewer_image {
|
||||
.o_wsale_image_viewer_void {
|
||||
padding-top: 64px;
|
||||
padding-bottom: 156px;
|
||||
}
|
||||
|
||||
img {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
}
|
||||
|
||||
.o_wsale_image_viewer_carousel {
|
||||
z-index:1;
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
|
||||
.o_wsale_image_viewer_thumbnail {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
li img{
|
||||
border: 1px solid #CED4DA;
|
||||
|
||||
&.active {
|
||||
border-color: #35979c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_wsale_image_viewer_control {
|
||||
z-index: 1;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($gray-400, 0.1);
|
||||
}
|
||||
|
||||
&.o_wsale_image_viewer_previous {
|
||||
margin: 1px 1px 0 0; // not correctly centered for some reasons
|
||||
}
|
||||
|
||||
&.o_wsale_image_viewer_next {
|
||||
margin: 1px 0 0 1px; // not correctly centered for some reasons
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<t t-name="website_sale.ProductImageViewer" owl="1">
|
||||
<div class="o_dialog" t-att-id="id" t-att-class="{ o_inactive_modal: !data.isActive }">
|
||||
<div role="dialog" class="modal" t-ref="modalRef">
|
||||
<div class="o_wsale_image_viewer flex-column align-items-center d-flex w-100 h-100" t-on-mousemove="onGlobalMousemove">
|
||||
<!-- Header -->
|
||||
<div class="o_wsale_image_viewer_header d-flex w-100 text-white">
|
||||
<div class="flex-grow-1"/>
|
||||
<div class="d-flex align-items-center mb-0 px-3 h4 text-reset cursor-pointer">
|
||||
<span class="fa fa-times" t-on-click="data.close"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Content -->
|
||||
<div class="o_wsale_image_viewer_image position-absolute top-0 bottom-0 start-0 end-0 align-items-center justify-content-center d-flex o_with_img overflow-hidden">
|
||||
<div class="o_wsale_image_viewer_void position-absolute align-items-center justify-content-center d-flex w-100 h-100" t-ref="imageContainer" t-att-style="imageContainerStyle">
|
||||
<img class="mw-100 mh-100 bg-black transition-base" t-att-src="selectedImage.src" draggable="false" alt="Viewer" t-att-style="imageStyle" t-on-wheel.stop="onWheelImage" t-on-mousedown="onMousedownImage"/>
|
||||
</div>
|
||||
</div>
|
||||
<t t-if="images.length > 1">
|
||||
<!-- Footer -->
|
||||
<div class="o_wsale_image_viewer_carousel position-absolute bottom-0 d-flex" role="toolbar">
|
||||
<ol class="d-flex justify-content-start ps-0 pt-2 pt-lg-0 mx-auto my-0 text-start">
|
||||
<t t-foreach="images" t-as="image" t-key="image">
|
||||
<li t-attf-class="align-top position-relative px-1 pb-1 {{image === selectedImage ? 'active' : ''}}" t-on-click="() => this.selectedImage = image">
|
||||
<div>
|
||||
<img t-att-src="image.thumbnailSrc" t-attf-class="img o_wsale_image_viewer_thumbnail {{image === selectedImage ? 'active' : ''}}" t-att-alt="props.title" loading="lazy"/>
|
||||
</div>
|
||||
</li>
|
||||
</t>
|
||||
</ol>
|
||||
</div>
|
||||
<!-- Controls -->
|
||||
<div class="o_wsale_image_viewer_control o_wsale_image_viewer_previous btn btn-dark position-absolute top-0 bottom-0 start-0 align-items-center justify-content-center d-flex my-auto ms-3 rounded-circle" t-on-click="previousImage" title="Previous (Left-Arrow)" role="button">
|
||||
<span class="fa fa-chevron-left" role="img"/>
|
||||
</div>
|
||||
<div class="o_wsale_image_viewer_control o_wsale_image_viewer_next btn btn-dark position-absolute top-0 bottom-0 end-0 align-items-center justify-content-center d-flex my-auto me-3 rounded-circle" t-on-click="nextImage" title="Next (Right-Arrow)" role="button">
|
||||
<span class="fa fa-chevron-right" role="img"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -23,8 +23,13 @@ tour.register('shop_zoom', {
|
||||
run: 'clicknoleave',
|
||||
},
|
||||
{
|
||||
content: "check there is no zoom on that small image",
|
||||
trigger: 'body:not(:has(.zoomodoo-flyout img))',
|
||||
content: "check that the image viewer opened",
|
||||
trigger: '.o_wsale_image_viewer',
|
||||
run: () => {},
|
||||
},
|
||||
{
|
||||
content: "close the image viewer",
|
||||
trigger: '.o_wsale_image_viewer_header span.fa-times',
|
||||
},
|
||||
{
|
||||
content: "change variant",
|
||||
@@ -42,7 +47,8 @@ tour.register('shop_zoom', {
|
||||
},
|
||||
{
|
||||
content: "check there is a zoom on that big image",
|
||||
trigger: '.zoomodoo-flyout img',
|
||||
trigger: '.o_wsale_image_viewer',
|
||||
run: () => {},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -202,6 +202,14 @@ class TestWebsiteSaleImage(odoo.tests.HttpCase):
|
||||
|
||||
# self.env.cr.commit() # uncomment to save the product to test in browser
|
||||
|
||||
# Make sure we have zoom on click
|
||||
self.env['ir.ui.view'].with_context(active_test=False).search(
|
||||
[('key', 'in', ('website_sale.product_picture_magnify_hover', 'website_sale.product_picture_magnify_click', 'website_sale.product_picture_magnify_both'))]
|
||||
).write({'active': False})
|
||||
self.env['ir.ui.view'].with_context(active_test=False).search(
|
||||
[('key', '=', 'website_sale.product_picture_magnify_click')]
|
||||
).write({'active': True})
|
||||
|
||||
self.start_tour("/", 'shop_zoom', login="admin")
|
||||
|
||||
# CASE: unlink move image to fallback if fallback image empty
|
||||
|
||||
@@ -235,10 +235,16 @@
|
||||
<we-button data-set-image-width="66_pc" data-img="/website_sale/static/src/img/snippet_options/image-width-66.svg" title="66 percent"/>
|
||||
<we-button data-set-image-width="100_pc" data-img="/website_sale/static/src/img/snippet_options/image-width-100.svg" title="100 percent"/>
|
||||
</we-button-group>
|
||||
<we-select string="Layout" data-no-preview="true" data-reload="/">
|
||||
<we-select string="Layout" data-name="o_wsale_image_layout" data-no-preview="true" data-reload="/">
|
||||
<we-button data-set-image-layout="carousel">Carousel</we-button>
|
||||
<we-button data-set-image-layout="grid">Grid</we-button>
|
||||
</we-select>
|
||||
<we-select string="Image Zoom" class="o_we_sublevel_1" data-name="o_wsale_zoom_mode" data-no-preview="true" data-reload="/">
|
||||
<we-button data-name="o_wsale_zoom_hover" data-customize-website-views="website_sale.product_picture_magnify_hover">Magnifier on hover</we-button>
|
||||
<we-button data-name="o_wsale_zoom_click" data-customize-website-views="website_sale.product_picture_magnify_click">Pop-up on Click</we-button>
|
||||
<we-button data-name="o_wsale_zoom_both" data-customize-website-views="website_sale.product_picture_magnify_both">Both</we-button>
|
||||
<we-button data-name="o_wsale_zoom_none" data-customize-website-views="">None</we-button>
|
||||
</we-select>
|
||||
<!-- Carousel config -->
|
||||
<we-button-group string="Thumbnails" class="o_we_sublevel_1" data-name="o_wsale_thumbnail_pos" data-no-preview="true" data-reload="/">
|
||||
<we-button class="fa fa-fw fa-long-arrow-left" title="Left" data-customize-website-views="website_sale.carousel_product_indicators_left"/>
|
||||
@@ -252,17 +258,12 @@
|
||||
<we-button data-set-columns="3">3</we-button>
|
||||
</we-select>
|
||||
<we-row string="Main image">
|
||||
<we-button class="o_we_bg_brand_primary" data-replace-main-image="true" data-no-preview="true">Replace</we-button>
|
||||
<we-button class="o_we_bg_brand_primary" data-name="o_wsale_replace_main_image" data-replace-main-image="true" data-no-preview="true">Replace</we-button>
|
||||
</we-row>
|
||||
<we-row string="Extra Images">
|
||||
<we-button class="o_we_bg_success" data-add-images="true" data-no-preview="true">Add</we-button>
|
||||
<we-button class="o_we_bg_danger" data-clear-images="true" data-no-preview="true">Remove all</we-button>
|
||||
<we-button class="o_we_bg_success" data-name="o_wsale_add_extra_images" data-add-images="true" data-no-preview="true">Add</we-button>
|
||||
<we-button class="o_we_bg_danger" data-name="o_wsale_clear_extra_images" data-clear-images="true" data-no-preview="true">Remove all</we-button>
|
||||
</we-row>
|
||||
<we-select string="Image Zoom" data-no-preview="true" data-reload="/">
|
||||
<we-button data-customize-website-views="">None</we-button>
|
||||
<we-button data-customize-website-views="website_sale.product_picture_magnify">On click</we-button>
|
||||
<we-button data-customize-website-views="website_sale.product_picture_magnify_auto">On hover</we-button>
|
||||
</we-select>
|
||||
</div>
|
||||
<!-- Checkout page -->
|
||||
<div data-selector="main:has(.oe_website_sale .wizard)" data-page-options="true" data-no-check="true" string="Checkout Pages">
|
||||
|
||||
@@ -630,7 +630,7 @@
|
||||
t-att-data-image_width="website.product_page_image_width"
|
||||
t-att-data-image_layout="website.product_page_image_layout">
|
||||
<t t-set="image_cols" t-value="website._get_product_page_proportions()"/>
|
||||
<div t-attf-class="col-lg-#{image_cols[0]} mt-lg-4 o_wsale_product_images" t-if="website.product_page_image_width != 'none'">
|
||||
<div t-attf-class="col-lg-#{image_cols[0]} mt-lg-4 o_wsale_product_images position-relative" t-if="website.product_page_image_width != 'none'">
|
||||
<t t-call="website_sale.shop_product_images"/>
|
||||
</div>
|
||||
<div t-attf-class="col-lg-#{image_cols[1]} mt-md-4" id="product_details">
|
||||
@@ -770,17 +770,26 @@
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template inherit_id='website_sale.product' id="product_picture_magnify" name="Image Zoom">
|
||||
<xpath expr='//div[hasclass("js_sale")]' position='attributes'>
|
||||
<!-- Product options: Zoom -->
|
||||
<template inherit_id='website_sale.product' id="product_picture_magnify_hover" name="Automatic Image Zoom">
|
||||
<xpath expr='//div[hasclass("o_wsale_product_page")]' position='attributes'>
|
||||
<attribute name="data-ecom-zoom-auto">1</attribute>
|
||||
<attribute name="class" separator=" " add="ecom-zoomable zoomodoo-next" />
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template inherit_id='website_sale.product' id="product_picture_magnify_auto" active="False" name="Automatic Image Zoom">
|
||||
<xpath expr='//div[hasclass("js_sale")]' position='attributes'>
|
||||
<attribute name="data-ecom-zoom-auto">1</attribute>
|
||||
<template inherit_id='website_sale.product' id="product_picture_magnify_click" active="False" name="Image Zoom On Click">
|
||||
<xpath expr='//div[hasclass("o_wsale_product_page")]' position='attributes'>
|
||||
<attribute name="data-ecom-zoom-click">1</attribute>
|
||||
<attribute name="class" separator=" " add="ecom-zoomable zoomodoo-next" />
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template inherit_id='website_sale.product' id="product_picture_magnify_both" active="False" name="Automatic Image Zoom And On Click">
|
||||
<xpath expr='//div[hasclass("o_wsale_product_page")]' position='attributes'>
|
||||
<attribute name="data-ecom-zoom-auto">1</attribute>
|
||||
<attribute name="data-ecom-zoom-click">1</attribute>
|
||||
<attribute name="class" separator=" " add="ecom-zoomable zoomodoo-next" />
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -1992,7 +2001,7 @@
|
||||
</template>
|
||||
|
||||
<template id="website_sale.shop_product_image">
|
||||
<div t-if="product_image._name == 'product.image' and product_image.embed_code" t-att-class="image_classes + 'ratio ratio-16x9'">
|
||||
<div t-if="product_image._name == 'product.image' and product_image.embed_code" t-att-class="image_classes + ' ratio ratio-16x9'">
|
||||
<t t-out="product_image.embed_code"/>
|
||||
</div>
|
||||
<div t-else="" t-field="product_image.image_1920" t-att-class="image_classes + ' oe_unmovable'" t-options='{"widget": "image", "preview_image": "image_1024", "class": "oe_unmovable product_detail_img mh-100", "alt-field": "name", "zoom": product_image.can_image_1024_be_zoomed and "image_1920", "itemprop": "image"}'/>
|
||||
|
||||
Reference in New Issue
Block a user