mirror of
https://github.com/pretix/pretix.git
synced 2026-05-09 15:54:03 +00:00
Dialog for cart renewal, async task without page refresh (#5148)
* async_task: deduplicate response handling code
* extend cart without full page reload
* update dialog markup
* fix error response from CartExtend
* refactor asynctask, make sure waitingDialog.show() re-initializes dialog contents
* add cart expiry notification
* add aria references to other dialogs
* improve error handling
* fix error if max_extend=None
* different message for expiring soon and expired carts
* refactor dialog css
* add classes to further dialog elements
* switch extend-cart-dialog and loadingmodal to <dialog>
* Backport simple_block_tag from Django 5.2
* Use simple_block_tag for {% dialog %} tag
* add alertdialog role
* Update src/pretix/static/pretixbase/scss/_dialogs.scss
Co-authored-by: Richard Schreiber <schreiber@rami.io>
* fix mobile dialog styles not being overwritten
* asynctask dialog: prevent close by escape on chrome
* remove dynamic aria-live from #cart-deadline
dynamic aria-live is generally not well supported and as we have the dialog now anyways, we can remove it
* move continue-button to right
* Update src/pretix/static/pretixpresale/js/ui/cart.js
Co-authored-by: Richard Schreiber <schreiber@rami.io>
* Fix CSS for old-style dialog
* fix heading display/level
* align dialogs at the top as they originally were
* fix </div> from merge-conflict
* fix missing grow for dialog-content
* improve cart-extend-button ui
* do not show cart-extend-dialog onload
* improve message if 0 minutes
* do not save messae in session if ajax_dont_redirect
* add ajax_dont_redirect to async_task_check_url
* improve draw_deadline to only update #cart-deadline if necessary
* add renew-confirmation-message
---------
Co-authored-by: Richard Schreiber <schreiber@rami.io>
Co-authored-by: Raphael Michel <michel@rami.io>
This commit is contained in:
@@ -1655,7 +1655,7 @@ def clear_cart(self, event: Event, cart_id: str=None, locale='en', sales_channel
|
|||||||
|
|
||||||
|
|
||||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(CartError,))
|
||||||
def extend_cart_reservation(self, event: Event, cart_id: str=None, locale='en', sales_channel='web', override_now_dt: datetime=None) -> None:
|
def extend_cart_reservation(self, event: Event, cart_id: str=None, locale='en', sales_channel='web', override_now_dt: datetime=None) -> dict:
|
||||||
"""
|
"""
|
||||||
Resets the expiry time of a cart to the configured reservation time of this event.
|
Resets the expiry time of a cart to the configured reservation time of this event.
|
||||||
Limited to 11x the reservation time.
|
Limited to 11x the reservation time.
|
||||||
@@ -1672,7 +1672,7 @@ def extend_cart_reservation(self, event: Event, cart_id: str=None, locale='en',
|
|||||||
try:
|
try:
|
||||||
cm = CartManager(event=event, cart_id=cart_id, sales_channel=sales_channel)
|
cm = CartManager(event=event, cart_id=cart_id, sales_channel=sales_channel)
|
||||||
cm.commit()
|
cm.commit()
|
||||||
return cm.num_extended_positions
|
return {"success": cm.num_extended_positions, "expiry": cm._expiry, "max_expiry_extend": cm._max_expiry_extend}
|
||||||
except LockTimeoutException:
|
except LockTimeoutException:
|
||||||
self.retry()
|
self.retry()
|
||||||
except (MaxRetriesExceededError, LockTimeoutException):
|
except (MaxRetriesExceededError, LockTimeoutException):
|
||||||
|
|||||||
@@ -23,38 +23,39 @@ from django import template
|
|||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
from pretix.helpers.templatetags.simple_block_tag import (
|
||||||
|
register_simple_block_tag,
|
||||||
|
)
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _ # NOQA
|
from django.utils.translation import gettext_lazy as _ # NOQA
|
||||||
|
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register_simple_block_tag(register)
|
||||||
def dialog(html_id, label, description, *args, **kwargs):
|
def dialog(content, html_id, title, description, *args, **kwargs):
|
||||||
format_kwargs = {
|
format_kwargs = {
|
||||||
"id": html_id,
|
"id": html_id,
|
||||||
"label": label,
|
"title": title,
|
||||||
"description": description,
|
"description": description,
|
||||||
"icon": format_html('<div class="modal-card-icon"><span class="fa fa-{}" aria-hidden="true"></span></div>', kwargs["icon"]) if "icon" in kwargs else "",
|
"icon": format_html('<div class="modal-card-icon"><span class="fa fa-{}" aria-hidden="true"></span></div>', kwargs["icon"]) if "icon" in kwargs else "",
|
||||||
"alert": mark_safe('role="alertdialog"') if kwargs.get("alert", "False") != "False" else "",
|
"alert": mark_safe('role="alertdialog"') if kwargs.get("alert", "False") != "False" else "",
|
||||||
|
"content": content,
|
||||||
}
|
}
|
||||||
result = """
|
result = """
|
||||||
<dialog {alert}
|
<dialog {alert}
|
||||||
id="{id}"
|
id="{id}" class="modal-card"
|
||||||
aria-labelledby="{id}-label"
|
aria-labelledby="{id}-title"
|
||||||
aria-describedby="{id}-description">
|
aria-describedby="{id}-description">
|
||||||
<form method="dialog" class="modal-card form-horizontal">
|
<form method="dialog" class="modal-card-inner form-horizontal">
|
||||||
{icon}
|
{icon}
|
||||||
<div class="modal-card-content">
|
<div class="modal-card-content">
|
||||||
<h2 id="{id}-label">{label}</h2>
|
<h2 id="{id}-title" class="modal-card-title h3">{title}</h2>
|
||||||
<p id="{id}-description">{description}</p>
|
<p id="{id}-description" class="modal-card-description">{description}</p>
|
||||||
"""
|
{content}
|
||||||
return format_html(result, **format_kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
|
||||||
def enddialog(*args, **kwargs):
|
|
||||||
return mark_safe("""
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
""")
|
"""
|
||||||
|
return format_html(result, **format_kwargs)
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class AsyncMixin:
|
|||||||
def get_check_url(self, task_id, ajax):
|
def get_check_url(self, task_id, ajax):
|
||||||
return self.request.path + '?async_id=%s' % task_id + ('&ajax=1' if ajax else '')
|
return self.request.path + '?async_id=%s' % task_id + ('&ajax=1' if ajax else '')
|
||||||
|
|
||||||
def _ajax_response_data(self):
|
def _ajax_response_data(self, value):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _return_ajax_result(self, res, timeout=.5):
|
def _return_ajax_result(self, res, timeout=.5):
|
||||||
@@ -85,7 +85,7 @@ class AsyncMixin:
|
|||||||
logger.warning('Ignored ResponseError in AsyncResult.get()')
|
logger.warning('Ignored ResponseError in AsyncResult.get()')
|
||||||
except ConnectionError:
|
except ConnectionError:
|
||||||
# Redis probably just restarted, let's just report not ready and retry next time
|
# Redis probably just restarted, let's just report not ready and retry next time
|
||||||
data = self._ajax_response_data()
|
data = self._ajax_response_data(None)
|
||||||
data.update({
|
data.update({
|
||||||
'async_id': res.id,
|
'async_id': res.id,
|
||||||
'ready': False
|
'ready': False
|
||||||
@@ -93,7 +93,7 @@ class AsyncMixin:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
state, info = res.state, res.info
|
state, info = res.state, res.info
|
||||||
data = self._ajax_response_data()
|
data = self._ajax_response_data(info)
|
||||||
data.update({
|
data.update({
|
||||||
'async_id': res.id,
|
'async_id': res.id,
|
||||||
'ready': ready,
|
'ready': ready,
|
||||||
@@ -102,23 +102,21 @@ class AsyncMixin:
|
|||||||
if ready:
|
if ready:
|
||||||
if state == states.SUCCESS and not isinstance(info, Exception):
|
if state == states.SUCCESS and not isinstance(info, Exception):
|
||||||
smes = self.get_success_message(info)
|
smes = self.get_success_message(info)
|
||||||
if smes:
|
if smes and 'ajax_dont_redirect' not in self.request.GET and 'ajax_dont_redirect' not in self.request.POST:
|
||||||
messages.success(self.request, smes)
|
messages.success(self.request, smes)
|
||||||
# TODO: Do not store message if the ajax client states that it will not redirect
|
|
||||||
# but handle the message itself
|
|
||||||
data.update({
|
data.update({
|
||||||
'redirect': self.get_success_url(info),
|
'redirect': self.get_success_url(info),
|
||||||
'success': True,
|
'success': True,
|
||||||
'message': str(self.get_success_message(info))
|
'message': str(smes)
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
messages.error(self.request, self.get_error_message(info))
|
smes = self.get_error_message(info)
|
||||||
# TODO: Do not store message if the ajax client states that it will not redirect
|
if smes and 'ajax_dont_redirect' not in self.request.GET and 'ajax_dont_redirect' not in self.request.POST:
|
||||||
# but handle the message itself
|
messages.error(self.request, smes)
|
||||||
data.update({
|
data.update({
|
||||||
'redirect': self.get_error_url(),
|
'redirect': self.get_error_url(),
|
||||||
'success': False,
|
'success': False,
|
||||||
'message': str(self.get_error_message(info))
|
'message': str(smes)
|
||||||
})
|
})
|
||||||
elif state == 'PROGRESS':
|
elif state == 'PROGRESS':
|
||||||
data.update({
|
data.update({
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
{% load statici18n %}
|
{% load statici18n %}
|
||||||
{% load eventsignal %}
|
{% load eventsignal %}
|
||||||
{% load eventurl %}
|
{% load eventurl %}
|
||||||
|
{% load dialog %}
|
||||||
|
{% load icon %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html{% if rtl %} dir="rtl" class="rtl"{% endif %}>
|
<html{% if rtl %} dir="rtl" class="rtl"{% endif %}>
|
||||||
<head>
|
<head>
|
||||||
@@ -463,25 +465,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="ajaxerr">
|
<div id="ajaxerr" class="modal-wrapper" hidden>
|
||||||
</div>
|
</div>
|
||||||
<div id="loadingmodal">
|
{% dialog "loadingmodal" "" "" icon="cog rotating" %}
|
||||||
<div class="modal-card">
|
<p class="status">{% trans "If this takes longer than a few minutes, please contact us." %}</p>
|
||||||
<div class="modal-card-icon">
|
<div class="progress">
|
||||||
<i class="fa fa-cog big-rotating-icon"></i>
|
<div class="progress-bar progress-bar-success">
|
||||||
</div>
|
|
||||||
<div class="modal-card-content">
|
|
||||||
<h3></h3>
|
|
||||||
<p class="text"></p>
|
|
||||||
<p class="status">{% trans "If this takes longer than a few minutes, please contact us." %}</p>
|
|
||||||
<div class="progress">
|
|
||||||
<div class="progress-bar progress-bar-success">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="steps">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="steps">
|
||||||
|
</div>
|
||||||
|
{% enddialog %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
133
src/pretix/helpers/templatetags/simple_block_tag.py
Normal file
133
src/pretix/helpers/templatetags/simple_block_tag.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pretix (Community Edition).
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||||
|
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||||
|
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||||
|
#
|
||||||
|
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||||
|
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||||
|
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||||
|
# this file, see <https://pretix.eu/about/en/license>.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
|
# <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# backport from Django 5.2 (django/django/template/library.py)
|
||||||
|
#
|
||||||
|
# TODO: remove once we upgrade to Django 5.2
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
|
from inspect import getfullargspec, unwrap
|
||||||
|
|
||||||
|
from django.template.exceptions import TemplateSyntaxError
|
||||||
|
from django.template.library import SimpleNode, parse_bits
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleBlockNode(SimpleNode):
|
||||||
|
def __init__(self, nodelist, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.nodelist = nodelist
|
||||||
|
|
||||||
|
def get_resolved_arguments(self, context):
|
||||||
|
resolved_args, resolved_kwargs = super().get_resolved_arguments(context)
|
||||||
|
|
||||||
|
# Restore the "content" argument.
|
||||||
|
# It will move depending on whether takes_context was passed.
|
||||||
|
resolved_args.insert(
|
||||||
|
1 if self.takes_context else 0, self.nodelist.render(context)
|
||||||
|
)
|
||||||
|
|
||||||
|
return resolved_args, resolved_kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def register_simple_block_tag(library, func=None, takes_context=None, name=None, end_name=None):
|
||||||
|
"""
|
||||||
|
Register a callable as a compiled block template tag. Example:
|
||||||
|
|
||||||
|
@register_simple_block_tag(register)
|
||||||
|
def hello(content):
|
||||||
|
return 'world'
|
||||||
|
"""
|
||||||
|
def dec(func):
|
||||||
|
nonlocal end_name
|
||||||
|
|
||||||
|
(
|
||||||
|
params,
|
||||||
|
varargs,
|
||||||
|
varkw,
|
||||||
|
defaults,
|
||||||
|
kwonly,
|
||||||
|
kwonly_defaults,
|
||||||
|
_,
|
||||||
|
) = getfullargspec(unwrap(func))
|
||||||
|
function_name = name or func.__name__
|
||||||
|
|
||||||
|
if end_name is None:
|
||||||
|
end_name = f"end{function_name}"
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
def compile_func(parser, token):
|
||||||
|
tag_params = params.copy()
|
||||||
|
|
||||||
|
if takes_context:
|
||||||
|
if len(tag_params) >= 2 and tag_params[1] == "content":
|
||||||
|
del tag_params[1]
|
||||||
|
else:
|
||||||
|
raise TemplateSyntaxError(
|
||||||
|
f"{function_name!r} is decorated with takes_context=True so"
|
||||||
|
" it must have a first argument of 'context' and a second "
|
||||||
|
"argument of 'content'"
|
||||||
|
)
|
||||||
|
elif tag_params and tag_params[0] == "content":
|
||||||
|
del tag_params[0]
|
||||||
|
else:
|
||||||
|
raise TemplateSyntaxError(
|
||||||
|
f"'{function_name}' must have a first argument of 'content'"
|
||||||
|
)
|
||||||
|
|
||||||
|
bits = token.split_contents()[1:]
|
||||||
|
target_var = None
|
||||||
|
if len(bits) >= 2 and bits[-2] == "as":
|
||||||
|
target_var = bits[-1]
|
||||||
|
bits = bits[:-2]
|
||||||
|
|
||||||
|
nodelist = parser.parse((end_name,))
|
||||||
|
parser.delete_first_token()
|
||||||
|
|
||||||
|
args, kwargs = parse_bits(
|
||||||
|
parser,
|
||||||
|
bits,
|
||||||
|
tag_params,
|
||||||
|
varargs,
|
||||||
|
varkw,
|
||||||
|
defaults,
|
||||||
|
kwonly,
|
||||||
|
kwonly_defaults,
|
||||||
|
takes_context,
|
||||||
|
function_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
return SimpleBlockNode(
|
||||||
|
nodelist, func, takes_context, args, kwargs, target_var
|
||||||
|
)
|
||||||
|
|
||||||
|
library.tag(function_name, compile_func)
|
||||||
|
return func
|
||||||
|
|
||||||
|
if func is None:
|
||||||
|
# @register.simple_block_tag(...)
|
||||||
|
return dec
|
||||||
|
elif callable(func):
|
||||||
|
# @register.simple_block_tag
|
||||||
|
return dec(func)
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid arguments provided to simple_block_tag")
|
||||||
@@ -495,7 +495,7 @@
|
|||||||
<form class="text-muted" id="cart-extend-form" data-asynctask data-asynctask-no-redirect
|
<form class="text-muted" id="cart-extend-form" data-asynctask data-asynctask-no-redirect
|
||||||
method="post" action="{% eventurl request.event "presale:event.cart.extend" cart_namespace=cart_namespace %}">
|
method="post" action="{% eventurl request.event "presale:event.cart.extend" cart_namespace=cart_namespace %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<span id="cart-deadline" data-expires="{{ cart.first_expiry|date:"Y-m-d H:i:sO" }}" data-max-expiry-extend="{{ cart.max_expiry_extend|date:"Y-m-d H:i:sO" }}">
|
<p id="cart-deadline" data-expires="{{ cart.first_expiry|date:"Y-m-d H:i:sO" }}" data-max-expiry-extend="{{ cart.max_expiry_extend|date:"Y-m-d H:i:sO" }}">
|
||||||
{% if cart.minutes_left > 0 or cart.seconds_left > 0 %}
|
{% if cart.minutes_left > 0 or cart.seconds_left > 0 %}
|
||||||
{% blocktrans trimmed with minutes=cart.minutes_left %}
|
{% blocktrans trimmed with minutes=cart.minutes_left %}
|
||||||
The items in your cart are reserved for you for {{ minutes }} minutes.
|
The items in your cart are reserved for you for {{ minutes }} minutes.
|
||||||
@@ -503,9 +503,12 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{% trans "The items in your cart are no longer reserved for you. You can still complete your order as long as they’re available." %}
|
{% trans "The items in your cart are no longer reserved for you. You can still complete your order as long as they’re available." %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</p>
|
||||||
<button class="btn btn-link" type="submit" id="cart-extend-button">
|
<p>
|
||||||
<i class="fa fa-refresh" aria-hidden="true"></i> {% trans "Extend" %}</button>
|
<button class="btn btn-default" type="submit" id="cart-extend-button" aria-describedby="cart-deadline">
|
||||||
|
<i class="fa fa-refresh" aria-hidden="true"></i> {% trans "Extend reservation" %}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="sr-only" id="cart-description">{% trans "Overview of your ordered products." %}</p>
|
<p class="sr-only" id="cart-description">{% trans "Overview of your ordered products." %}</p>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{% load rich_text %}
|
{% load rich_text %}
|
||||||
{% load money %}
|
{% load money %}
|
||||||
<details class="panel {% if open %}panel-primary{% else %}panel-default{% endif %} cart" {% if open %}open{% endif %}>
|
<details class="panel {% if open %}panel-primary{% else %}panel-default{% endif %} cart" {% if open %}open{% endif %}>
|
||||||
<summary class="panel-heading">
|
<summary class="panel-heading" aria-describedby="cart-deadline">
|
||||||
<h2 class="panel-title">
|
<h2 class="panel-title">
|
||||||
<span>
|
<span>
|
||||||
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
|
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
|
||||||
|
|||||||
@@ -4,18 +4,19 @@
|
|||||||
{% load escapejson %}
|
{% load escapejson %}
|
||||||
{% load icon %}
|
{% load icon %}
|
||||||
{% load dialog %}
|
{% load dialog %}
|
||||||
<div id="ajaxerr">
|
<div id="ajaxerr" class="modal-wrapper" hidden>
|
||||||
</div>
|
</div>
|
||||||
<div id="popupmodal" hidden aria-live="polite">
|
<div id="popupmodal" class="modal-wrapper" hidden aria-live="polite" role="dialog"
|
||||||
|
aria-labelledby="popupmodal-title">
|
||||||
<div class="modal-card">
|
<div class="modal-card">
|
||||||
<div class="modal-card-icon">
|
<div class="modal-card-icon">
|
||||||
<i class="fa fa-window-restore big-icon" aria-hidden="true"></i>
|
<i class="fa fa-window-restore big-icon" aria-hidden="true"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-card-content">
|
<div class="modal-card-content">
|
||||||
<div>
|
<div>
|
||||||
<h3>
|
<h2 id="popupmodal-title" class="h3">
|
||||||
{% trans "We've started the requested process in a new window." %}
|
{% trans "We've started the requested process in a new window." %}
|
||||||
</h3>
|
</h2>
|
||||||
<p class="text">
|
<p class="text">
|
||||||
{% trans "If you do not see the new window, we can help you launch it again." %}
|
{% trans "If you do not see the new window, we can help you launch it again." %}
|
||||||
</p>
|
</p>
|
||||||
@@ -32,20 +33,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="loadingmodal" hidden aria-live="polite">
|
|
||||||
<div class="modal-card">
|
{% dialog "loadingmodal" "" "" icon="cog rotating" %}
|
||||||
<div class="modal-card-icon">
|
<p class="status">{% trans "If this takes longer than a few minutes, please contact us." %}</p>
|
||||||
<i class="fa fa-cog big-rotating-icon" aria-hidden="true"></i>
|
<div class="progress">
|
||||||
</div>
|
<div class="progress-bar progress-bar-success">
|
||||||
<div class="modal-card-content">
|
|
||||||
<h3 id="loadingmodal-label"></h3>
|
|
||||||
<div id="loadingmodal-description">
|
|
||||||
<p class="text"></p>
|
|
||||||
<p class="status">{% trans "If this takes longer than a few minutes, please contact us." %}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="steps">
|
||||||
|
</div>
|
||||||
|
{% enddialog %}
|
||||||
|
|
||||||
|
{% trans "Please let us know you're still there." as label_cart_extend_dialog %}
|
||||||
|
{% dialog "dialog-cart-extend" label_cart_extend_dialog "" icon="clock-o" alert=true %}
|
||||||
|
<p class="modal-card-confirm"><button class="btn btn-lg btn-primary">{% trans "Continue" %}</button></p>
|
||||||
|
{% enddialog %}
|
||||||
|
|
||||||
<dialog id="lightbox-dialog" role="alertdialog" aria-labelledby="lightbox-label">
|
<dialog id="lightbox-dialog" role="alertdialog" aria-labelledby="lightbox-label">
|
||||||
<form method="dialog" class="modal-card">
|
<form method="dialog" class="modal-card">
|
||||||
|
|||||||
@@ -229,11 +229,13 @@ class CartMixin:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
first_expiry = min(p.expires for p in positions) if positions else now()
|
first_expiry = min(p.expires for p in positions) if positions else now()
|
||||||
|
max_expiry_extend = min((p.max_extend for p in positions if p.max_extend), default=None)
|
||||||
total_seconds_left = max(first_expiry - now(), timedelta()).total_seconds()
|
total_seconds_left = max(first_expiry - now(), timedelta()).total_seconds()
|
||||||
minutes_left = int(total_seconds_left // 60)
|
minutes_left = int(total_seconds_left // 60)
|
||||||
seconds_left = int(total_seconds_left % 60)
|
seconds_left = int(total_seconds_left % 60)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
first_expiry = None
|
first_expiry = None
|
||||||
|
max_expiry_extend = None
|
||||||
minutes_left = None
|
minutes_left = None
|
||||||
seconds_left = None
|
seconds_left = None
|
||||||
|
|
||||||
@@ -250,6 +252,7 @@ class CartMixin:
|
|||||||
'minutes_left': minutes_left,
|
'minutes_left': minutes_left,
|
||||||
'seconds_left': seconds_left,
|
'seconds_left': seconds_left,
|
||||||
'first_expiry': first_expiry,
|
'first_expiry': first_expiry,
|
||||||
|
'max_expiry_extend': max_expiry_extend,
|
||||||
'is_ordered': bool(order),
|
'is_ordered': bool(order),
|
||||||
'itemcount': sum(c.count for c in positions if not c.addon_to),
|
'itemcount': sum(c.count for c in positions if not c.addon_to),
|
||||||
'current_selected_payments': [p for p in self.current_selected_payments(total) if p.get('multi_use_supported')]
|
'current_selected_payments': [p for p in self.current_selected_payments(total) if p.get('multi_use_supported')]
|
||||||
|
|||||||
@@ -542,8 +542,14 @@ class CartExtendReservation(EventViewMixin, CartActionMixin, AsyncAction, View):
|
|||||||
task = extend_cart_reservation
|
task = extend_cart_reservation
|
||||||
known_errortypes = ['CartError']
|
known_errortypes = ['CartError']
|
||||||
|
|
||||||
|
def _ajax_response_data(self, value):
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
def get_success_message(self, value):
|
def get_success_message(self, value):
|
||||||
if value > 0:
|
if value['success'] > 0:
|
||||||
return _('Your cart timeout was extended.')
|
return _('Your cart timeout was extended.')
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
@@ -561,7 +567,7 @@ class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
|
|||||||
def get_success_message(self, value):
|
def get_success_message(self, value):
|
||||||
return _('The products have been successfully added to your cart.')
|
return _('The products have been successfully added to your cart.')
|
||||||
|
|
||||||
def _ajax_response_data(self):
|
def _ajax_response_data(self, value):
|
||||||
cart_id = get_or_create_cart_id(self.request)
|
cart_id = get_or_create_cart_id(self.request)
|
||||||
return {
|
return {
|
||||||
'cart_id': cart_id,
|
'cart_id': cart_id,
|
||||||
|
|||||||
@@ -5,74 +5,94 @@ var async_task_check_url = null;
|
|||||||
var async_task_old_url = null;
|
var async_task_old_url = null;
|
||||||
var async_task_is_download = false;
|
var async_task_is_download = false;
|
||||||
var async_task_is_long = false;
|
var async_task_is_long = false;
|
||||||
|
var async_task_dont_redirect = false;
|
||||||
|
|
||||||
function async_task_check() {
|
var async_task_status_messages = {
|
||||||
|
long_task_started: gettext(
|
||||||
|
'Your request is currently being processed. Depending on the size of your event, this might take up to ' +
|
||||||
|
'a few minutes.'
|
||||||
|
),
|
||||||
|
long_task_pending: gettext(
|
||||||
|
'Your request has been queued on the server and will soon be ' +
|
||||||
|
'processed.'
|
||||||
|
),
|
||||||
|
short_task: gettext(
|
||||||
|
'Your request arrived on the server but we still wait for it to be ' +
|
||||||
|
'processed. If this takes longer than two minutes, please contact us or go ' +
|
||||||
|
'back in your browser and try again.'
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
function async_task_schedule_check(context, timeout) {
|
||||||
"use strict";
|
"use strict";
|
||||||
$.ajax(
|
async_task_timeout = window.setTimeout(function() {
|
||||||
{
|
$.ajax(
|
||||||
'type': 'GET',
|
{
|
||||||
'url': async_task_check_url,
|
'type': 'GET',
|
||||||
'success': async_task_check_callback,
|
'url': async_task_check_url,
|
||||||
'error': async_task_check_error,
|
'success': async_task_check_callback,
|
||||||
'context': this,
|
'error': async_task_check_error,
|
||||||
'dataType': 'json'
|
'context': context,
|
||||||
|
'dataType': 'json'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
function async_task_on_success(data) {
|
||||||
|
"use strict";
|
||||||
|
if ((async_task_is_download && data.success) || async_task_dont_redirect) {
|
||||||
|
waitingDialog.hide();
|
||||||
|
if (location.href.indexOf("async_id") !== -1) {
|
||||||
|
history.replaceState({}, "pretix", async_task_old_url);
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
|
if (!async_task_dont_redirect)
|
||||||
|
location.href = data.redirect;
|
||||||
|
$(this).trigger('pretix:async-task-success', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
function async_task_check_callback(data, textStatus, jqXHR) {
|
function async_task_check_callback(data, textStatus, jqXHR) {
|
||||||
"use strict";
|
"use strict";
|
||||||
if (data.ready && data.redirect) {
|
if (data.ready && data.redirect) {
|
||||||
if (async_task_is_download && data.success) {
|
async_task_on_success.call(this, data);
|
||||||
waitingDialog.hide();
|
|
||||||
if (location.href.indexOf("async_id") !== -1) {
|
|
||||||
history.replaceState({}, "pretix", async_task_old_url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
location.href = data.redirect;
|
|
||||||
return;
|
return;
|
||||||
} else if (typeof data.percentage === "number") {
|
|
||||||
$("#loadingmodal .progress").show();
|
|
||||||
$("#loadingmodal .progress .progress-bar").css("width", data.percentage + "%");
|
|
||||||
if (typeof data.steps === "object" && Array.isArray(data.steps)) {
|
|
||||||
var $steps = $("#loadingmodal .steps");
|
|
||||||
$steps.html("").show()
|
|
||||||
for (var step of data.steps) {
|
|
||||||
$steps.append(
|
|
||||||
$("<span>").addClass("fa fa-fw")
|
|
||||||
.toggleClass("fa-check text-success", step.done)
|
|
||||||
.toggleClass("fa-cog fa-spin text-muted", !step.done)
|
|
||||||
).append(
|
|
||||||
$("<span>").text(step.label)
|
|
||||||
).append(
|
|
||||||
$("<br>")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
async_task_timeout = window.setTimeout(async_task_check, 250);
|
|
||||||
|
|
||||||
|
if (typeof data.percentage === "number") {
|
||||||
|
waitingDialog.setProgress(data.percentage);
|
||||||
|
}
|
||||||
|
if (typeof data.steps === "object" && Array.isArray(data.steps)) {
|
||||||
|
waitingDialog.setSteps(data.steps);
|
||||||
|
}
|
||||||
|
async_task_schedule_check(this, 250);
|
||||||
|
|
||||||
|
async_task_update_status(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function async_task_update_status(data) {
|
||||||
if (async_task_is_long) {
|
if (async_task_is_long) {
|
||||||
if (data.started) {
|
if (data.started) {
|
||||||
$("#loadingmodal p.status").text(gettext(
|
waitingDialog.setStatus(async_task_status_messages.long_task_started);
|
||||||
'Your request is currently being processed. Depending on the size of your event, this might take up to ' +
|
|
||||||
'a few minutes.'
|
|
||||||
));
|
|
||||||
} else {
|
} else {
|
||||||
$("#loadingmodal p.status").text(gettext(
|
waitingDialog.setStatus(async_task_status_messages.long_task_pending);
|
||||||
'Your request has been queued on the server and will soon be ' +
|
|
||||||
'processed.'
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$("#loadingmodal p.status").text(gettext(
|
waitingDialog.setStatus(async_task_status_messages.short_task);
|
||||||
'Your request arrived on the server but we still wait for it to be ' +
|
|
||||||
'processed. If this takes longer than two minutes, please contact us or go ' +
|
|
||||||
'back in your browser and try again.'
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function async_task_replace_page(target, new_html) {
|
||||||
|
"use strict";
|
||||||
|
waitingDialog.hide();
|
||||||
|
$(target).html(new_html);
|
||||||
|
setup_basics($(target));
|
||||||
|
form_handlers($(target));
|
||||||
|
setup_collapsible_details($(target));
|
||||||
|
window.setTimeout(function () { $(window).scrollTop(0) }, 200)
|
||||||
|
$(document).trigger("pretix:bind-forms");
|
||||||
|
}
|
||||||
|
|
||||||
function async_task_check_error(jqXHR, textStatus, errorThrown) {
|
function async_task_check_error(jqXHR, textStatus, errorThrown) {
|
||||||
"use strict";
|
"use strict";
|
||||||
var respdom = $(jqXHR.responseText);
|
var respdom = $(jqXHR.responseText);
|
||||||
@@ -80,16 +100,10 @@ function async_task_check_error(jqXHR, textStatus, errorThrown) {
|
|||||||
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
||||||
// This is a failed form validation, let's just use it
|
// This is a failed form validation, let's just use it
|
||||||
$("body").data('ajaxing', false);
|
$("body").data('ajaxing', false);
|
||||||
waitingDialog.hide();
|
async_task_replace_page("body", jqXHR.responseText.substring(
|
||||||
$("body").html(jqXHR.responseText.substring(
|
|
||||||
jqXHR.responseText.indexOf("<body"),
|
jqXHR.responseText.indexOf("<body"),
|
||||||
jqXHR.responseText.indexOf("</body")
|
jqXHR.responseText.indexOf("</body")
|
||||||
));
|
));
|
||||||
setup_basics($("body"));
|
|
||||||
form_handlers($("body"));
|
|
||||||
setup_collapsible_details($("body"));
|
|
||||||
window.setTimeout(function () { $(window).scrollTop(0) }, 200)
|
|
||||||
$(document).trigger("pretix:bind-forms");
|
|
||||||
} else if (c.length > 0) {
|
} else if (c.length > 0) {
|
||||||
// This is some kind of 500/404/403 page, show it in an overlay
|
// This is some kind of 500/404/403 page, show it in an overlay
|
||||||
$("body").data('ajaxing', false);
|
$("body").data('ajaxing', false);
|
||||||
@@ -105,9 +119,9 @@ function async_task_check_error(jqXHR, textStatus, errorThrown) {
|
|||||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
||||||
} else {
|
} else {
|
||||||
// 500 can be an application error or overload in some cases :(
|
// 500 can be an application error or overload in some cases :(
|
||||||
$("#loadingmodal p.status").text(gettext('We currently cannot reach the server, but we keep trying.' +
|
waitingDialog.setStatus(gettext('We currently cannot reach the server, but we keep trying.' +
|
||||||
' Last error code: {code}').replace(/\{code\}/, jqXHR.status));
|
' Last error code: {code}').replace(/\{code\}/, jqXHR.status));
|
||||||
async_task_timeout = window.setTimeout(async_task_check, 5000);
|
async_task_schedule_check(this, 5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,38 +130,19 @@ function async_task_callback(data, jqXHR, status) {
|
|||||||
"use strict";
|
"use strict";
|
||||||
$("body").data('ajaxing', false);
|
$("body").data('ajaxing', false);
|
||||||
if (data.redirect) {
|
if (data.redirect) {
|
||||||
if (async_task_is_download && data.success) {
|
async_task_on_success.call(this, data);
|
||||||
waitingDialog.hide();
|
|
||||||
if (location.href.indexOf("async_id") !== -1) {
|
|
||||||
history.replaceState({}, "pretix", async_task_old_url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
location.href = data.redirect;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
async_task_id = data.async_id;
|
var check_url = new URL(data.check_url);
|
||||||
async_task_check_url = data.check_url;
|
if (async_task_dont_redirect) {
|
||||||
async_task_timeout = window.setTimeout(async_task_check, 100);
|
check_url.searchParams.set('ajax_dont_redirect', '1');
|
||||||
|
|
||||||
if (async_task_is_long) {
|
|
||||||
if (data.started) {
|
|
||||||
$("#loadingmodal p.status").text(gettext(
|
|
||||||
'Your request is currently being processed. Depending on the size of your event, this might take up to ' +
|
|
||||||
'a few minutes.'
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
$("#loadingmodal p.status").text(gettext(
|
|
||||||
'Your request has been queued on the server and will soon be ' +
|
|
||||||
'processed.'
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$("#loadingmodal p.status").text(gettext(
|
|
||||||
'Your request arrived on the server but we still wait for it to be ' +
|
|
||||||
'processed. If this takes longer than two minutes, please contact us or go ' +
|
|
||||||
'back in your browser and try again.'
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
async_task_id = data.async_id;
|
||||||
|
async_task_check_url = check_url.toString();
|
||||||
|
async_task_schedule_check(this, 100);
|
||||||
|
|
||||||
|
async_task_update_status(data);
|
||||||
|
|
||||||
if (location.href.indexOf("async_id") === -1) {
|
if (location.href.indexOf("async_id") === -1) {
|
||||||
history.pushState({}, "Waiting", async_task_check_url.replace(/ajax=1/, ''));
|
history.pushState({}, "Waiting", async_task_check_url.replace(/ajax=1/, ''));
|
||||||
}
|
}
|
||||||
@@ -156,48 +151,34 @@ function async_task_callback(data, jqXHR, status) {
|
|||||||
function async_task_error(jqXHR, textStatus, errorThrown) {
|
function async_task_error(jqXHR, textStatus, errorThrown) {
|
||||||
"use strict";
|
"use strict";
|
||||||
$("body").data('ajaxing', false);
|
$("body").data('ajaxing', false);
|
||||||
|
waitingDialog.hide();
|
||||||
if (textStatus === "timeout") {
|
if (textStatus === "timeout") {
|
||||||
alert(gettext("The request took too long. Please try again."));
|
alert(gettext("The request took too long. Please try again."));
|
||||||
waitingDialog.hide();
|
|
||||||
} else if (jqXHR.responseText.indexOf('<html') > 0) {
|
} else if (jqXHR.responseText.indexOf('<html') > 0) {
|
||||||
var respdom = $(jqXHR.responseText);
|
var respdom = $(jqXHR.responseText);
|
||||||
var c = respdom.filter('.container');
|
var c = respdom.filter('.container');
|
||||||
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
if (respdom.filter('form') && (respdom.filter('.has-error') || respdom.filter('.alert-danger'))) {
|
||||||
// This is a failed form validation, let's just use it
|
// This is a failed form validation, let's just use it
|
||||||
waitingDialog.hide();
|
|
||||||
|
|
||||||
if (respdom.filter('#page-wrapper') && $('#page-wrapper').length) {
|
if (respdom.filter('#page-wrapper') && $('#page-wrapper').length) {
|
||||||
$("#page-wrapper").html(respdom.find("#page-wrapper").html());
|
async_task_replace_page("#page-wrapper", respdom.find("#page-wrapper").html());
|
||||||
setup_basics($("#page-wrapper"));
|
|
||||||
form_handlers($("#page-wrapper"));
|
|
||||||
setup_collapsible_details($("#page-wrapper"));
|
|
||||||
$(document).trigger("pretix:bind-forms");
|
|
||||||
window.setTimeout(function () { $(window).scrollTop(0) }, 200)
|
|
||||||
} else {
|
} else {
|
||||||
$("body").html(jqXHR.responseText.substring(
|
async_task_replace_page("body", jqXHR.responseText.substring(
|
||||||
jqXHR.responseText.indexOf("<body"),
|
jqXHR.responseText.indexOf("<body"),
|
||||||
jqXHR.responseText.indexOf("</body")
|
jqXHR.responseText.indexOf("</body")
|
||||||
));
|
));
|
||||||
setup_basics($("body"));
|
|
||||||
form_handlers($("body"));
|
|
||||||
setup_collapsible_details($("body"));
|
|
||||||
$(document).trigger("pretix:bind-forms");
|
|
||||||
window.setTimeout(function () { $(window).scrollTop(0) }, 200)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (c.length > 0) {
|
} else if (c.length > 0) {
|
||||||
waitingDialog.hide();
|
// This is some kind of 500/404/403 page, show it in an overlay
|
||||||
ajaxErrDialog.show(c.first().html());
|
ajaxErrDialog.show(c.first().html());
|
||||||
} else {
|
} else {
|
||||||
waitingDialog.hide();
|
|
||||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
if (jqXHR.status >= 400 && jqXHR.status < 500) {
|
||||||
waitingDialog.hide();
|
|
||||||
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
alert(gettext('An error of type {code} occurred.').replace(/\{code\}/, jqXHR.status));
|
||||||
} else {
|
} else {
|
||||||
waitingDialog.hide();
|
|
||||||
alert(gettext('We currently cannot reach the server. Please try again. ' +
|
alert(gettext('We currently cannot reach the server. Please try again. ' +
|
||||||
'Error code: {code}').replace(/\{code\}/, jqXHR.status));
|
'Error code: {code}').replace(/\{code\}/, jqXHR.status));
|
||||||
}
|
}
|
||||||
@@ -221,28 +202,26 @@ $(function () {
|
|||||||
}
|
}
|
||||||
async_task_id = null;
|
async_task_id = null;
|
||||||
async_task_is_download = $(this).is("[data-asynctask-download]");
|
async_task_is_download = $(this).is("[data-asynctask-download]");
|
||||||
|
async_task_dont_redirect = $(this).is("[data-asynctask-no-redirect]");
|
||||||
async_task_is_long = $(this).is("[data-asynctask-long]");
|
async_task_is_long = $(this).is("[data-asynctask-long]");
|
||||||
async_task_old_url = location.href;
|
async_task_old_url = location.href;
|
||||||
$("body").data('ajaxing', true);
|
$("body").data('ajaxing', true);
|
||||||
if ($(this).is("[data-asynctask-headline]")) {
|
waitingDialog.show(
|
||||||
waitingDialog.show($(this).attr("data-asynctask-headline"));
|
$(this).attr("data-asynctask-headline") || gettext('We are processing your request …'),
|
||||||
} else {
|
$(this).attr("data-asynctask-text") || '',
|
||||||
waitingDialog.show(gettext('We are processing your request …'));
|
gettext(
|
||||||
}
|
'We are currently sending your request to the server. If this takes longer ' +
|
||||||
if ($(this).is("[data-asynctask-text]")) {
|
'than one minute, please check your internet connection and then reload ' +
|
||||||
$("#loadingmodal p.text").text($(this).attr("data-asynctask-text")).show();
|
'this page and try again.'
|
||||||
} else {
|
)
|
||||||
$("#loadingmodal p.text").hide();
|
);
|
||||||
}
|
|
||||||
$("#loadingmodal p.status").text(gettext(
|
|
||||||
'We are currently sending your request to the server. If this takes longer ' +
|
|
||||||
'than one minute, please check your internet connection and then reload ' +
|
|
||||||
'this page and try again.'
|
|
||||||
));
|
|
||||||
|
|
||||||
var action = this.action;
|
var action = this.action;
|
||||||
var formData = new FormData(this);
|
var formData = new FormData(this);
|
||||||
formData.append('ajax', '1');
|
formData.append('ajax', '1');
|
||||||
|
if (async_task_dont_redirect) {
|
||||||
|
formData.append('ajax_dont_redirect', '1');
|
||||||
|
}
|
||||||
if (submitter && submitter.name) {
|
if (submitter && submitter.name) {
|
||||||
formData.append(submitter.name, submitter.value);
|
formData.append(submitter.name, submitter.value);
|
||||||
}
|
}
|
||||||
@@ -275,21 +254,66 @@ $(function () {
|
|||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
$("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide);
|
||||||
|
$("#loadingmodal").on("cancel", function() {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
$("#loadingmodal").prop("closedBy", "none");
|
||||||
});
|
});
|
||||||
|
|
||||||
var waitingDialog = {
|
var waitingDialog = {
|
||||||
show: function (message) {
|
show: function (title, text, status) {
|
||||||
"use strict";
|
"use strict";
|
||||||
$("#loadingmodal h3").html(message);
|
this.setTitle(title);
|
||||||
$("#loadingmodal .progress").hide();
|
this.setText(text);
|
||||||
$("#loadingmodal .steps").hide();
|
this.setStatus(status || gettext('If this takes longer than a few minutes, please contact us.'));
|
||||||
$("body").addClass("loading");
|
this.setProgress(null);
|
||||||
$("#loadingmodal").removeAttr("hidden");
|
this.setSteps(null);
|
||||||
|
document.getElementById("loadingmodal").showModal();
|
||||||
},
|
},
|
||||||
hide: function () {
|
hide: function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
$("body").removeClass("loading");
|
document.getElementById("loadingmodal").close();
|
||||||
$("#loadingmodal").attr("hidden", true);
|
},
|
||||||
|
setTitle: function(title) {
|
||||||
|
$("#loadingmodal .modal-card-title").text(title);
|
||||||
|
},
|
||||||
|
setStatus: function(statusText) {
|
||||||
|
$("#loadingmodal p.status").text(statusText);
|
||||||
|
},
|
||||||
|
setText: function(text) {
|
||||||
|
if (text)
|
||||||
|
$("#loadingmodal .modal-card-description").text(text).show();
|
||||||
|
else
|
||||||
|
$("#loadingmodal .modal-card-description").hide();
|
||||||
|
},
|
||||||
|
setProgress: function(percentage) {
|
||||||
|
if (typeof percentage === 'number') {
|
||||||
|
$("#loadingmodal .progress").show();
|
||||||
|
$("#loadingmodal .progress .progress-bar").css("width", percentage + "%");
|
||||||
|
} else {
|
||||||
|
$("#loadingmodal .progress").hide();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setSteps: function(steps) {
|
||||||
|
var $steps = $("#loadingmodal .steps");
|
||||||
|
if (steps) {
|
||||||
|
$steps.html("").show()
|
||||||
|
for (var step of steps) {
|
||||||
|
$steps.append(
|
||||||
|
$("<span>").addClass("fa fa-fw")
|
||||||
|
.toggleClass("fa-check text-success", step.done)
|
||||||
|
.toggleClass("fa-cog fa-spin text-muted", !step.done)
|
||||||
|
).append(
|
||||||
|
$("<span>").text(step.label)
|
||||||
|
).append(
|
||||||
|
$("<br>")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$steps.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -299,10 +323,10 @@ var ajaxErrDialog = {
|
|||||||
$("#ajaxerr").html(c);
|
$("#ajaxerr").html(c);
|
||||||
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
|
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
|
||||||
+ gettext("Close message") + "</a>");
|
+ gettext("Close message") + "</a>");
|
||||||
$("body").addClass("ajaxerr");
|
$("body").addClass("ajaxerr has-modal-dialog");
|
||||||
},
|
},
|
||||||
hide: function () {
|
hide: function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
$("body").removeClass("ajaxerr");
|
$("body").removeClass("ajaxerr has-modal-dialog");
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
174
src/pretix/static/pretixbase/scss/_dialogs.scss
Normal file
174
src/pretix/static/pretixbase/scss/_dialogs.scss
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
/* Modal dialogs using HTML5 dialog tags for accessibility */
|
||||||
|
dialog.modal-card {
|
||||||
|
border: none;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 43em;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 60px;
|
||||||
|
box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1),0 3px 6px 0 rgba(0,0,0,.07);
|
||||||
|
background: white;
|
||||||
|
border-radius: $border-radius-large;
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity .5s allow-discrete;
|
||||||
|
}
|
||||||
|
.modal-card-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-content: stretch;
|
||||||
|
}
|
||||||
|
dialog.modal-card .modal-card-icon {
|
||||||
|
background: $brand-primary;
|
||||||
|
font-size: 2em;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
padding: 3px;
|
||||||
|
.rotating {
|
||||||
|
-webkit-animation: fa-spin 8s infinite linear;
|
||||||
|
animation: fa-spin 8s infinite linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.modal-card .modal-card-content {
|
||||||
|
padding: 1.5em;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
.modal-card-content>*:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.modal-card-content>*:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-card-confirm {
|
||||||
|
margin-top: 2em;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1em;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.modal-card-confirm-spread {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog::backdrop {
|
||||||
|
background-color: rgba(255, 255, 255, .5);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity .5s allow-discrete;
|
||||||
|
backdrop-filter: blur(2px);
|
||||||
|
}
|
||||||
|
dialog[open], dialog[open]::backdrop {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
@starting-style {
|
||||||
|
dialog[open], dialog[open]::backdrop {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $screen-sm-min) {
|
||||||
|
dialog.modal-card:has(.modal-card-icon) .modal-card-inner {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
dialog.modal-card .modal-card-content {
|
||||||
|
padding: 2em;
|
||||||
|
}
|
||||||
|
dialog.modal-card .modal-card-icon {
|
||||||
|
font-size: 4em;
|
||||||
|
padding: 6px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shake-once {
|
||||||
|
animation: shake .2s;
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
backface-visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
0% { transform: skewX(0deg); }
|
||||||
|
20% { transform: skewX(-5deg); }
|
||||||
|
40% { transform: skewX(5deg); }
|
||||||
|
60% { transform: skewX(-5deg); }
|
||||||
|
80% { transform: skewX(5deg); }
|
||||||
|
100% { transform: skewX(0deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Legacy dialogs (still used for #ajaxerr and #popupmodal) */
|
||||||
|
body.has-modal-dialog .container, body.has-modal-dialog #wrapper {
|
||||||
|
-webkit-filter: blur(2px);
|
||||||
|
-moz-filter: blur(2px);
|
||||||
|
-ms-filter: blur(2px);
|
||||||
|
-o-filter: blur(2px);
|
||||||
|
filter: blur(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(255, 255, 255, .7);
|
||||||
|
z-index: 900000;
|
||||||
|
padding: 10px;
|
||||||
|
.modal-card {
|
||||||
|
margin: 50px auto 0;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 600px;
|
||||||
|
max-height: calc(100vh - 100px);
|
||||||
|
overflow-y: auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: $border-radius-large;
|
||||||
|
box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1),0 3px 6px 0 rgba(0,0,0,.07);
|
||||||
|
padding: 20px;
|
||||||
|
min-height: 160px;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
.modal-card-icon {
|
||||||
|
float: left;
|
||||||
|
width: 150px;
|
||||||
|
text-align: center;
|
||||||
|
.big-icon {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-size: 100px;
|
||||||
|
color: $brand-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-card-content {
|
||||||
|
margin-left: 160px;
|
||||||
|
text-align: left;
|
||||||
|
h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.modal-wrapper .modal-card {
|
||||||
|
margin: 25px auto 0;
|
||||||
|
max-height: calc(100vh - 50px - 20px);
|
||||||
|
.modal-card-icon {
|
||||||
|
float: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.modal-card-content {
|
||||||
|
text-align: center;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ajaxerr {
|
||||||
|
background: rgba(236, 236, 236, .9);
|
||||||
|
|
||||||
|
.big-icon {
|
||||||
|
margin-top: 50px;
|
||||||
|
font-size: 200px;
|
||||||
|
color: $brand-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -263,117 +263,3 @@ svg.svg-icon {
|
|||||||
@include table-row-variant('warning', var(--pretix-brand-warning-lighten-40), var(--pretix-brand-warning-lighten-35));
|
@include table-row-variant('warning', var(--pretix-brand-warning-lighten-40), var(--pretix-brand-warning-lighten-35));
|
||||||
@include table-row-variant('danger', var(--pretix-brand-danger-lighten-30), var(--pretix-brand-danger-lighten-25));
|
@include table-row-variant('danger', var(--pretix-brand-danger-lighten-30), var(--pretix-brand-danger-lighten-25));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
dialog {
|
|
||||||
border: none;
|
|
||||||
width: 80%;
|
|
||||||
max-width: 43em;
|
|
||||||
padding: 0;
|
|
||||||
box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1),0 3px 6px 0 rgba(0,0,0,.07);
|
|
||||||
background: white;
|
|
||||||
border-radius: $border-radius-large;
|
|
||||||
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity .5s allow-discrete;
|
|
||||||
|
|
||||||
.modal-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-content: stretch;
|
|
||||||
}
|
|
||||||
.modal-card-icon {
|
|
||||||
background: $brand-primary;
|
|
||||||
font-size: 2em;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
.modal-card-content {
|
|
||||||
padding: 1.5em;
|
|
||||||
}
|
|
||||||
.modal-card-content>*:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.modal-card-content>*:first-child {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-card-confirm {
|
|
||||||
margin-top: 2em;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 1em;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.modal-card-confirm-spread {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dialog::backdrop {
|
|
||||||
background-color: rgba(255, 255, 255, .5);
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity .5s allow-discrete;
|
|
||||||
}
|
|
||||||
dialog[open], dialog[open]::backdrop {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
@starting-style {
|
|
||||||
dialog[open], dialog[open]::backdrop {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: $screen-sm-min) {
|
|
||||||
dialog {
|
|
||||||
.modal-card:has(.modal-card-icon) {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
.modal-card-content {
|
|
||||||
padding: 2em;
|
|
||||||
}
|
|
||||||
.modal-card-icon {
|
|
||||||
font-size: 4em;
|
|
||||||
padding: 6px 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.shake-once {
|
|
||||||
animation: shake .2s;
|
|
||||||
transform: translate3d(0, 0, 0);
|
|
||||||
backface-visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes shake {
|
|
||||||
0% { transform: skewX(0deg); }
|
|
||||||
20% { transform: skewX(-5deg); }
|
|
||||||
40% { transform: skewX(5deg); }
|
|
||||||
60% { transform: skewX(-5deg); }
|
|
||||||
80% { transform: skewX(5deg); }
|
|
||||||
100% { transform: skewX(0deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
#lightbox-dialog {
|
|
||||||
width: fit-content;
|
|
||||||
max-width: 80%;
|
|
||||||
min-width: 24em;
|
|
||||||
.modal-card-content {
|
|
||||||
padding: 2.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
position: absolute;
|
|
||||||
top: 1em;
|
|
||||||
right: 1em;
|
|
||||||
}
|
|
||||||
figcaption {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -51,32 +51,6 @@ function formatPrice(price, currency, locale) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var waitingDialog = {
|
|
||||||
show: function (message) {
|
|
||||||
"use strict";
|
|
||||||
$("#loadingmodal").find("h1").html(message);
|
|
||||||
$("body").addClass("loading");
|
|
||||||
},
|
|
||||||
hide: function () {
|
|
||||||
"use strict";
|
|
||||||
$("body").removeClass("loading");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var ajaxErrDialog = {
|
|
||||||
show: function (c) {
|
|
||||||
"use strict";
|
|
||||||
$("#ajaxerr").html(c);
|
|
||||||
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
|
|
||||||
+ gettext("Close message") + "</a>");
|
|
||||||
$("body").addClass("ajaxerr");
|
|
||||||
},
|
|
||||||
hide: function () {
|
|
||||||
"use strict";
|
|
||||||
$("body").removeClass("ajaxerr");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var apiGET = function (url, callback) {
|
var apiGET = function (url, callback) {
|
||||||
$.getJSON(url, function (data) {
|
$.getJSON(url, function (data) {
|
||||||
callback(data);
|
callback(data);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
@import "../../bootstrap/scss/_bootstrap.scss";
|
@import "../../bootstrap/scss/_bootstrap.scss";
|
||||||
@import "../../fontawesome/scss/font-awesome.scss";
|
@import "../../fontawesome/scss/font-awesome.scss";
|
||||||
@import "../../pretixbase/scss/_theme.scss";
|
@import "../../pretixbase/scss/_theme.scss";
|
||||||
|
@import "../../pretixbase/scss/_dialogs.scss";
|
||||||
@import "../../typeahead/typeahead.scss";
|
@import "../../typeahead/typeahead.scss";
|
||||||
@import "../../charts/morris.scss";
|
@import "../../charts/morris.scss";
|
||||||
@import "../../cropper/cropper.scss";
|
@import "../../cropper/cropper.scss";
|
||||||
@@ -234,91 +235,6 @@ p.bigger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body.loading #wrapper {
|
|
||||||
-webkit-filter: blur(2px);
|
|
||||||
-moz-filter: blur(2px);
|
|
||||||
-ms-filter: blur(2px);
|
|
||||||
-o-filter: blur(2px);
|
|
||||||
filter: blur(2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
#loadingmodal, #ajaxerr {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
background: rgba(255, 255, 255, .7);
|
|
||||||
opacity: 0;
|
|
||||||
z-index: 900000;
|
|
||||||
visibility: hidden;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
.big-icon {
|
|
||||||
margin-top: 50px;
|
|
||||||
font-size: 200px;
|
|
||||||
color: $brand-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-card {
|
|
||||||
margin: 50px auto 0;
|
|
||||||
top: 50px;
|
|
||||||
width: 90%;
|
|
||||||
max-width: 600px;
|
|
||||||
background: white;
|
|
||||||
border-radius: $border-radius-large;
|
|
||||||
box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1),0 3px 6px 0 rgba(0,0,0,.07);
|
|
||||||
padding: 20px;
|
|
||||||
min-height: 160px;
|
|
||||||
|
|
||||||
.modal-card-icon {
|
|
||||||
float: left;
|
|
||||||
width: 150px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.big-rotating-icon {
|
|
||||||
font-size: 120px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.modal-card-content {
|
|
||||||
margin-left: 160px;
|
|
||||||
text-align: left;
|
|
||||||
h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 700px) {
|
|
||||||
#loadingmodal, #ajaxerr {
|
|
||||||
.modal-card {
|
|
||||||
.modal-card-icon {
|
|
||||||
float: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.modal-card-content {
|
|
||||||
text-align: center;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ajaxerr {
|
|
||||||
background: rgba(236, 236, 236, .9);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading #loadingmodal, .ajaxerr #ajaxerr {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
transition: opacity .5s ease-in-out;
|
|
||||||
-moz-transition: opacity .5s ease-in-out;
|
|
||||||
-webkit-transition: opacity .5s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.big-rotating-icon {
|
.big-rotating-icon {
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
-webkit-animation: fa-spin 8s infinite linear;
|
-webkit-animation: fa-spin 8s infinite linear;
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
/*global $,gettext,ngettext */
|
/*global $,gettext,ngettext */
|
||||||
var cart = {
|
var cart = {
|
||||||
_deadline: null,
|
_deadline: null,
|
||||||
_deadline_interval: null,
|
_deadline_timeout: null,
|
||||||
_deadline_call: 0,
|
_deadline_call: 0,
|
||||||
_time_offset: 0,
|
_time_offset: 0,
|
||||||
|
_prev_diff_minutes: 0,
|
||||||
|
_renewed_message: "",
|
||||||
|
|
||||||
_get_now: function () {
|
_get_now: function () {
|
||||||
return moment().add(cart._time_offset, 'ms');
|
return moment().add(cart._time_offset, 'ms');
|
||||||
@@ -20,7 +22,12 @@ var cart = {
|
|||||||
cart._time_offset = server_time - client_time;
|
cart._time_offset = server_time - client_time;
|
||||||
},
|
},
|
||||||
|
|
||||||
draw_deadline: function () {
|
show_expiry_notification: function () {
|
||||||
|
document.getElementById("dialog-cart-extend").showModal();
|
||||||
|
cart._expiry_notified = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
draw_deadline: function (renewed="") {
|
||||||
function pad(n, width, z) {
|
function pad(n, width, z) {
|
||||||
z = z || '0';
|
z = z || '0';
|
||||||
n = n + '';
|
n = n + '';
|
||||||
@@ -33,36 +40,73 @@ var cart = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var now = cart._get_now();
|
var now = cart._get_now();
|
||||||
var diff_minutes = Math.floor(cart._deadline.diff(now) / 1000 / 60);
|
var diff_total_seconds = cart._deadline.diff(now) / 1000;
|
||||||
var diff_seconds = Math.floor(cart._deadline.diff(now) / 1000 % 60);
|
var diff_minutes = Math.floor(diff_total_seconds / 60);
|
||||||
|
var diff_seconds = Math.floor(diff_total_seconds % 60);
|
||||||
if (diff_minutes < 2 || diff_minutes == 5) $("#cart-deadline").get(0).setAttribute("aria-live", "polite");
|
|
||||||
else $("#cart-deadline").get(0).removeAttribute("aria-live");
|
|
||||||
|
|
||||||
if (diff_minutes < 0) {
|
if (diff_minutes < 0) {
|
||||||
$("#cart-deadline").text(gettext("The items in your cart are no longer reserved for you. You can still complete your order as long as they’re available."));
|
$("#cart-deadline").text(gettext("The items in your cart are no longer reserved for you. You can still complete your order as long as they’re available."));
|
||||||
$("#cart-deadline-short").text(
|
$("#cart-deadline-short").text(
|
||||||
gettext("Cart expired")
|
gettext("Cart expired")
|
||||||
);
|
);
|
||||||
window.clearInterval(cart._deadline_interval);
|
if (!cart._deadline_timeout) {
|
||||||
|
// no timeout => first time draw_deadline is invoked, but cart already expired => do not show dialog
|
||||||
|
cart._expiry_notified = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$("#cart-deadline").text(ngettext(
|
if (diff_minutes !== cart._prev_diff_minutes) {
|
||||||
"The items in your cart are reserved for you for one minute.",
|
if (diff_minutes == 0) {
|
||||||
"The items in your cart are reserved for you for {num} minutes.",
|
$("#cart-deadline").text(gettext("Your cart is about to expire. If you want to continue, please click here:"))
|
||||||
diff_minutes
|
} else {
|
||||||
).replace(/\{num\}/g, diff_minutes));
|
$("#cart-deadline").text(
|
||||||
|
cart._renewed_message + " " +
|
||||||
|
ngettext(
|
||||||
|
"The items in your cart are reserved for you for one minute.",
|
||||||
|
"The items in your cart are reserved for you for {num} minutes.",
|
||||||
|
diff_minutes
|
||||||
|
).replace(/\{num\}/g, diff_minutes)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cart._prev_diff_minutes = diff_minutes;
|
||||||
|
}
|
||||||
|
|
||||||
$("#cart-deadline-short").text(
|
$("#cart-deadline-short").text(
|
||||||
pad(diff_minutes.toString(), 2) + ':' + pad(diff_seconds.toString(), 2)
|
pad(diff_minutes.toString(), 2) + ':' + pad(diff_seconds.toString(), 2)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
cart._renewed_message = "";
|
||||||
|
cart._deadline_timeout = window.setTimeout(cart.draw_deadline, 500);
|
||||||
|
}
|
||||||
|
var already_expired = diff_total_seconds <= 0;
|
||||||
|
var can_extend_cart = diff_minutes < 3 && (already_expired || cart._deadline < cart._max_extend);
|
||||||
|
$("#cart-extend-button").toggle(can_extend_cart);
|
||||||
|
if (can_extend_cart && diff_total_seconds < 45) {
|
||||||
|
if (!cart._expiry_notified) cart.show_expiry_notification();
|
||||||
|
$("#dialog-cart-extend-description").text(already_expired
|
||||||
|
? gettext("Your cart has expired. If you want to continue, please click here:")
|
||||||
|
: gettext("Your cart is about to expire. If you want to continue, please click here:"));
|
||||||
}
|
}
|
||||||
$("#cart-extend-button").toggle(diff_minutes < 3);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
init: function () {
|
init: function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
cart._deadline = moment($("#cart-deadline").attr("data-expires"));
|
|
||||||
cart._deadline_interval = window.setInterval(cart.draw_deadline, 500);
|
|
||||||
cart._calc_offset();
|
cart._calc_offset();
|
||||||
|
cart.set_deadline(
|
||||||
|
$("#cart-deadline").attr("data-expires"),
|
||||||
|
$("#cart-deadline").attr("data-max-expiry-extend")
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
set_deadline: function (expiry, max_extend, renewed_message) {
|
||||||
|
"use strict";
|
||||||
|
cart._expiry_notified = false;
|
||||||
|
cart._deadline = moment(expiry);
|
||||||
|
if (cart._deadline_timeout) {
|
||||||
|
window.clearTimeout(cart._deadline_timeout);
|
||||||
|
}
|
||||||
|
cart._deadline_timeout = null;
|
||||||
|
cart._max_extend = moment(max_extend);
|
||||||
|
cart._renewed_message = renewed_message || "";
|
||||||
cart.draw_deadline();
|
cart.draw_deadline();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -74,6 +118,22 @@ $(function () {
|
|||||||
cart.init();
|
cart.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$("#cart-extend-form").on("pretix:async-task-success", function(e, data) {
|
||||||
|
if (data.success) {
|
||||||
|
cart.set_deadline(data.expiry, data.max_expiry_extend, data.message);
|
||||||
|
var cart_panel_heading = $(this).closest(".panel").find(".panel-heading").get(0);
|
||||||
|
if (cart_panel_heading) {
|
||||||
|
cart_panel_heading.focus();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert(data.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#dialog-cart-extend form").submit(function() {
|
||||||
|
$("#cart-extend-form").submit();
|
||||||
|
});
|
||||||
|
|
||||||
$(".toggle-container").each(function() {
|
$(".toggle-container").each(function() {
|
||||||
var summary = $(".toggle-summary", this);
|
var summary = $(".toggle-summary", this);
|
||||||
var content = $("> :not(.toggle-summary)", this);
|
var content = $("> :not(.toggle-summary)", this);
|
||||||
|
|||||||
@@ -407,8 +407,6 @@ $(function () {
|
|||||||
$("#voucher-toggle").slideUp();
|
$("#voucher-toggle").slideUp();
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#ajaxerr").on("click", ".ajaxerr-close", ajaxErrDialog.hide);
|
|
||||||
|
|
||||||
// Handlers for "Copy answers from above" buttons
|
// Handlers for "Copy answers from above" buttons
|
||||||
$(".js-copy-answers").click(function (e) {
|
$(".js-copy-answers").click(function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ $(function () {
|
|||||||
var popup_window = null
|
var popup_window = null
|
||||||
var popup_check_interval = null
|
var popup_check_interval = null
|
||||||
|
|
||||||
$("#popupmodal").removeAttr("hidden");
|
|
||||||
|
|
||||||
$("a[data-open-in-popup-window]").on("click", function (e) {
|
$("a[data-open-in-popup-window]").on("click", function (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
@@ -22,11 +20,13 @@ $(function () {
|
|||||||
"presale-popup",
|
"presale-popup",
|
||||||
"scrollbars=yes,resizable=yes,status=yes,location=yes,toolbar=no,menubar=no,width=940,height=620,left=50,top=50"
|
"scrollbars=yes,resizable=yes,status=yes,location=yes,toolbar=no,menubar=no,width=940,height=620,left=50,top=50"
|
||||||
)
|
)
|
||||||
$("body").addClass("has-popup")
|
$("body").addClass("has-popup has-modal-dialog")
|
||||||
|
$("#popupmodal").removeAttr("hidden");
|
||||||
|
|
||||||
popup_check_interval = window.setInterval(function () {
|
popup_check_interval = window.setInterval(function () {
|
||||||
if (popup_window.closed) {
|
if (popup_window.closed) {
|
||||||
$("body").removeClass("has-popup")
|
$("body").removeClass("has-popup has-modal-dialog")
|
||||||
|
$("#popupmodal").attr("hidden", true);
|
||||||
window.clearInterval(popup_check_interval)
|
window.clearInterval(popup_check_interval)
|
||||||
}
|
}
|
||||||
}, 250)
|
}, 250)
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ $headings-small-color: $text-muted;
|
|||||||
|
|
||||||
@import "../../bootstrap/scss/_bootstrap_reduced.scss";
|
@import "../../bootstrap/scss/_bootstrap_reduced.scss";
|
||||||
@import "../../pretixbase/scss/_theme.scss";
|
@import "../../pretixbase/scss/_theme.scss";
|
||||||
|
@import "../../pretixbase/scss/_dialogs.scss";
|
||||||
@import "../../lightbox/css/lightbox.scss";
|
@import "../../lightbox/css/lightbox.scss";
|
||||||
@import "../../cropper/cropper.scss";
|
@import "../../cropper/cropper.scss";
|
||||||
@import "../../datetimepicker/_bootstrap-datetimepicker.scss";
|
@import "../../datetimepicker/_bootstrap-datetimepicker.scss";
|
||||||
@@ -113,7 +114,7 @@ pre {
|
|||||||
h1, h2, h3, h4, h5, h6, p, li {
|
h1, h2, h3, h4, h5, h6, p, li {
|
||||||
a:not(.btn) {
|
a:not(.btn) {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.no-underline:link, nav li a:link {
|
a.no-underline:link, nav li a:link {
|
||||||
@@ -277,119 +278,12 @@ a:hover .panel-primary > .panel-heading {
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.loading .container {
|
|
||||||
-webkit-filter: blur(2px);
|
|
||||||
-moz-filter: blur(2px);
|
|
||||||
-ms-filter: blur(2px);
|
|
||||||
-o-filter: blur(2px);
|
|
||||||
filter: blur(2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.big-rotating-icon {
|
.big-rotating-icon {
|
||||||
-webkit-animation: fa-spin 8s infinite linear;
|
-webkit-animation: fa-spin 8s infinite linear;
|
||||||
animation: fa-spin 8s infinite linear;
|
animation: fa-spin 8s infinite linear;
|
||||||
font-size: 120px;
|
font-size: 120px;
|
||||||
color: $brand-primary;
|
color: $brand-primary;
|
||||||
}
|
}
|
||||||
#loadingmodal, #ajaxerr, #popupmodal {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
background: rgba(255, 255, 255, .7);
|
|
||||||
opacity: 0;
|
|
||||||
z-index: 900000;
|
|
||||||
visibility: hidden;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
.big-icon {
|
|
||||||
margin-top: 50px;
|
|
||||||
font-size: 200px;
|
|
||||||
color: $brand-primary;
|
|
||||||
}
|
|
||||||
&#popupmodal .big-icon {
|
|
||||||
margin-top: 10px;
|
|
||||||
font-size: 100px;
|
|
||||||
color: $brand-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-card {
|
|
||||||
margin: 50px auto 0;
|
|
||||||
width: 90%;
|
|
||||||
max-width: 600px;
|
|
||||||
max-height: calc(100vh - 100px);
|
|
||||||
overflow-y: auto;
|
|
||||||
background: white;
|
|
||||||
border-radius: $border-radius-large;
|
|
||||||
box-shadow: 0 7px 14px 0 rgba(78, 50, 92, 0.1),0 3px 6px 0 rgba(0,0,0,.07);
|
|
||||||
padding: 20px;
|
|
||||||
min-height: 160px;
|
|
||||||
|
|
||||||
.modal-card-icon {
|
|
||||||
float: left;
|
|
||||||
width: 150px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.modal-card-content {
|
|
||||||
margin-left: 160px;
|
|
||||||
text-align: left;
|
|
||||||
h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&#cookie-consent-modal {
|
|
||||||
background: rgba(255, 255, 255, .5);
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
display: none;
|
|
||||||
.modal-card-content {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
details {
|
|
||||||
& > summary {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
& > summary::-webkit-details-marker,
|
|
||||||
& > summary::marker {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 700px) {
|
|
||||||
#loadingmodal, #ajaxerr, #popupmodal {
|
|
||||||
.modal-card {
|
|
||||||
margin: 25px auto 0;
|
|
||||||
max-height: calc(100vh - 50px - 20px);
|
|
||||||
.modal-card-icon {
|
|
||||||
float: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.modal-card-content {
|
|
||||||
text-align: center;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ajaxerr {
|
|
||||||
background: rgba(236, 236, 236, .9);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading #loadingmodal, .ajaxerr #ajaxerr, .has-popup #popupmodal {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
transition: opacity .5s ease-in-out;
|
|
||||||
-moz-transition: opacity .5s ease-in-out;
|
|
||||||
-webkit-transition: opacity .5s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typo-alert span[data-typosuggest] {
|
.typo-alert span[data-typosuggest] {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
|||||||
Reference in New Issue
Block a user