From 1d49c98cf210de45f8bd154de9fbeaecede09da2 Mon Sep 17 00:00:00 2001 From: Richard Schreiber Date: Thu, 29 Jun 2023 12:23:00 +0200 Subject: [PATCH] Widget: add lightbox for product images (Z#23123811) (#3439) --- src/pretix/presale/views/widget.py | 16 ++-- .../static/pretixpresale/js/widget/widget.js | 60 +++++++++++- .../static/pretixpresale/scss/widget.scss | 96 +++++++++++++++++++ src/tests/presale/test_widget.py | 6 ++ 4 files changed, 171 insertions(+), 7 deletions(-) diff --git a/src/pretix/presale/views/widget.py b/src/pretix/presale/views/widget.py index b1e1d5d389..1a4131ec88 100644 --- a/src/pretix/presale/views/widget.py +++ b/src/pretix/presale/views/widget.py @@ -212,11 +212,14 @@ def price_dict(item, price): } -def get_picture(event, picture): - try: - thumb = get_thumbnail(picture.name, '60x60^').thumb.url - except: - logger.exception(f'Failed to create thumbnail of {picture.name}') +def get_picture(event, picture, size=None): + thumb = None + if size: + try: + thumb = get_thumbnail(picture.name, size).thumb.url + except: + logger.exception(f'Failed to create thumbnail of {picture.name}') + if not thumb: thumb = default_storage.url(picture.name) return urljoin(build_absolute_uri(event, 'presale:event.index'), thumb) @@ -264,7 +267,8 @@ class WidgetAPIProductList(EventListMixin, View): { 'id': item.pk, 'name': str(item.name), - 'picture': get_picture(self.request.event, item.picture) if item.picture else None, + 'picture': get_picture(self.request.event, item.picture, '60x60^') if item.picture else None, + 'picture_fullsize': get_picture(self.request.event, item.picture) if item.picture else None, 'description': str(rich_text(item.description, safelinks=False)) if item.description else None, 'has_variations': item.has_variations, 'require_voucher': item.require_voucher, diff --git a/src/pretix/static/pretixpresale/js/widget/widget.js b/src/pretix/static/pretixpresale/js/widget/widget.js index edcc24c73f..84a0aa0cc5 100644 --- a/src/pretix/static/pretixpresale/js/widget/widget.js +++ b/src/pretix/static/pretixpresale/js/widget/widget.js @@ -450,7 +450,7 @@ Vue.component('item', { // Product description + '
' - + '' + + '' + '
' + '' ); +var shared_lightbox_fragment = ( + '
' + + '
' + + '' + + '
' + + '
' + + '
' + + '' + + '
{{$root.lightbox.description}}
' + + '
' + + '' + + '
' + + '
' +); + Vue.component('pretix-overlay', { template: ('
' + shared_iframe_fragment + shared_alert_fragment + + shared_lightbox_fragment + '
' ), + watch: { + '$root.lightbox': function (newValue, oldValue) { + if (newValue) { + if (newValue.image != oldValue?.image) { + this.$set(newValue, "loading", true); + } + if (!oldValue) { + window.addEventListener('keyup', this.lightboxCloseOnKeyup); + } + } else { + window.removeEventListener('keyup', this.lightboxCloseOnKeyup); + } + } + }, computed: { frameClasses: function () { return { @@ -803,8 +841,27 @@ Vue.component('pretix-overlay', { 'pretix-widget-alert-shown': this.$root.error_message, }; }, + lightboxClasses: function () { + return { + 'pretix-widget-lightbox-holder': true, + 'pretix-widget-lightbox-shown': this.$root.lightbox, + 'pretix-widget-lightbox-isloading': this.$root.lightbox?.loading, + }; + }, }, methods: { + lightboxCloseOnKeyup: function (event) { + if (event.keyCode === 27) { + // abort on ESC-key + this.lightboxClose(); + } + }, + lightboxClose: function () { + this.$root.lightbox = null; + }, + lightboxLoaded: function () { + this.$root.lightbox.loading = false; + }, errorClose: function () { this.$root.error_message = null; this.$root.error_url_after = null; @@ -1795,6 +1852,7 @@ var create_overlay = function (app) { error_url_after: null, error_url_after_new_tab: true, error_message: null, + lightbox: null, } }, methods: { diff --git a/src/pretix/static/pretixpresale/scss/widget.scss b/src/pretix/static/pretixpresale/scss/widget.scss index 63b0e7d0cf..e8257b6de6 100644 --- a/src/pretix/static/pretixpresale/scss/widget.scss +++ b/src/pretix/static/pretixpresale/scss/widget.scss @@ -768,6 +768,102 @@ } } +.pretix-widget-lightbox-holder { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + background: rgba(255, 255, 255, 0.8); + z-index: 16777271; + visibility: hidden; + opacity: 0; + transition: opacity 0.5s, visibility 0.5s; + display: flex; + align-items: center; + justify-content: center; + + .pretix-widget-lightbox-loading svg { + margin: 40px; + -webkit-animation: pretix-widget-spin 6s linear infinite; + -moz-animation: pretix-widget-spin 6s linear infinite; + animation: pretix-widget-spin 6s linear infinite; + } + + &.pretix-widget-lightbox-shown { + visibility: visible; + opacity: 1; + transition: opacity 0.5s, visibility 0.5s; + } + + .pretix-widget-lightbox-inner { + position: relative; + background: white; + border-radius: 5px 5px 5px 5px; + -moz-border-radius: 5px 5px 5px 5px; + -webkit-border-radius: 5px 5px 5px 5px; + box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09); + -webkit-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09); + -moz-box-shadow: 0 4px 18px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.09); + box-sizing: border-box; + padding: 10px; + max-width: 90%; + max-height: 90%; + } + &.pretix-widget-lightbox-isloading .pretix-widget-lightbox-inner { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; + } + + .pretix-widget-lightbox-image { + margin: 0; + padding: 0; + text-align: center; + } + .pretix-widget-lightbox-image img { + max-width: 80vw; + max-height: 80vh; + object-fit: scale-down; + } + .pretix-widget-lightbox-image figcaption { + margin: 0.5em 0 0; + } + + .pretix-widget-lightbox-close { + position: absolute; + right: -12px; + top: -12px; + width: 24px; + height: 24px; + background: $brand-primary; + margin: 0; + border: none; + border-radius: 12px; + -moz-border-radius: 12px; + -webkit-border-radius: 12px; + text-align: center; + color: white; + font-weight: bold; + font-family: sans-serif; + text-decoration: none; + padding: 4px 0; + display: inline-block; + line-height: 16px; + cursor: pointer; + } + + .pretix-widget-lightbox-close svg { + display: inline-block; + border: none; + } +} + .pretix-widget-primary-color { /* in SVG */ fill: $brand-primary; diff --git a/src/tests/presale/test_widget.py b/src/tests/presale/test_widget.py index 075c003d9f..e04e51fd65 100644 --- a/src/tests/presale/test_widget.py +++ b/src/tests/presale/test_widget.py @@ -185,6 +185,7 @@ class WidgetCartTest(CartTestMixin, TestCase): "max_price": None, "price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False}, "picture": None, + "picture_fullsize": None, "has_variations": 0, "allow_waitinglist": True, "mandatory_priced_addons": False, @@ -204,6 +205,7 @@ class WidgetCartTest(CartTestMixin, TestCase): "max_price": "14.00", "price": None, "picture": None, + "picture_fullsize": None, "has_variations": 4, "allow_waitinglist": True, "mandatory_priced_addons": False, @@ -265,6 +267,7 @@ class WidgetCartTest(CartTestMixin, TestCase): "price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False}, "picture": None, + "picture_fullsize": None, "has_variations": 0, "allow_waitinglist": True, "mandatory_priced_addons": False, @@ -310,6 +313,7 @@ class WidgetCartTest(CartTestMixin, TestCase): "max_price": "14.00", "price": None, "picture": None, + "picture_fullsize": None, "has_variations": 4, "allow_waitinglist": True, "mandatory_priced_addons": False, @@ -371,6 +375,7 @@ class WidgetCartTest(CartTestMixin, TestCase): "max_price": None, "price": {"gross": "23.00", "net": "19.33", "tax": "3.67", "name": "", "rate": "19.00", "includes_mixed_tax_rate": False}, "picture": None, + "picture_fullsize": None, "has_variations": 0, "allow_waitinglist": True, "mandatory_priced_addons": False, @@ -425,6 +430,7 @@ class WidgetCartTest(CartTestMixin, TestCase): 'id': self.shirt.pk, 'name': 'T-Shirt', 'picture': None, + "picture_fullsize": None, 'description': None, 'has_variations': 2, "allow_waitinglist": True,