fix: store notifications in the session so that they won't be served to other clients
feat: list all users and products in a table in the settings feat: add back buttons to signup, password login and touchkey login pages feat: if the tabfocus webextension is installed, use it to focus the tab when a barcode is scanned
This commit is contained in:
parent
f614fe1afc
commit
66f23f5dda
18 changed files with 197 additions and 133 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -1,5 +1,21 @@
|
||||||
# Matemat Changelog
|
# Matemat Changelog
|
||||||
|
|
||||||
|
<!-- BEGIN RELEASE v0.3.16 -->
|
||||||
|
## Version 0.3.16
|
||||||
|
|
||||||
|
Settings UI rework
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
<!-- BEGIN CHANGES 0.3.16 -->
|
||||||
|
- fix: store notifications in the session so that they won't be served to other clients
|
||||||
|
- feat: list all users and products in a table in the settings
|
||||||
|
- feat: add back buttons to signup, password login and touchkey login pages
|
||||||
|
- feat: if the tabfocus webextension is installed, use it to focus the tab when a barcode is scanned
|
||||||
|
<!-- END CHANGES 0.3.16 -->
|
||||||
|
|
||||||
|
<!-- END RELEASE v0.3.16 -->
|
||||||
|
|
||||||
<!-- BEGIN RELEASE v0.3.15 -->
|
<!-- BEGIN RELEASE v0.3.15 -->
|
||||||
## Version 0.3.15
|
## Version 0.3.15
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
|
|
||||||
__version__ = '0.3.15'
|
__version__ = '0.3.16'
|
||||||
|
|
|
@ -3,6 +3,8 @@ from bottle import get, post, redirect, request
|
||||||
from matemat.db import MatematDatabase
|
from matemat.db import MatematDatabase
|
||||||
from matemat.webserver import session
|
from matemat.webserver import session
|
||||||
from matemat.webserver import config as c
|
from matemat.webserver import config as c
|
||||||
|
from matemat.webserver.template import Notification
|
||||||
|
from matemat.util.currency_format import format_chf
|
||||||
|
|
||||||
|
|
||||||
@get('/buy')
|
@get('/buy')
|
||||||
|
@ -35,9 +37,11 @@ def buy():
|
||||||
stock_provider = c.get_stock_provider()
|
stock_provider = c.get_stock_provider()
|
||||||
if stock_provider.needs_update():
|
if stock_provider.needs_update():
|
||||||
stock_provider.update_stock(product, -1)
|
stock_provider.update_stock(product, -1)
|
||||||
|
# Show notification on next page load
|
||||||
|
Notification.success(
|
||||||
|
f'Purchased <strong>{product.name}</strong> for <strong>{format_chf(price)}</strong>', decay=True)
|
||||||
# Logout user if configured, logged in via touchkey and no price entry input was shown
|
# Logout user if configured, logged in via touchkey and no price entry input was shown
|
||||||
if user.logout_after_purchase and authlevel < 2 and not product.custom_price:
|
if user.logout_after_purchase and authlevel < 2 and not product.custom_price:
|
||||||
redirect(f'/logout?lastaction=buy&lastproduct={pid}&lastprice={price}')
|
redirect('/logout')
|
||||||
# Redirect to the main page (where this request should have come from)
|
# Redirect to the main page (where this request should have come from)
|
||||||
redirect(f'/?lastaction=buy&lastproduct={pid}&lastprice={price}')
|
|
||||||
redirect('/')
|
redirect('/')
|
||||||
|
|
|
@ -3,6 +3,8 @@ from bottle import get, post, redirect, request
|
||||||
from matemat.db import MatematDatabase
|
from matemat.db import MatematDatabase
|
||||||
from matemat.webserver import session
|
from matemat.webserver import session
|
||||||
from matemat.webserver.config import get_app_config
|
from matemat.webserver.config import get_app_config
|
||||||
|
from matemat.webserver.template import Notification
|
||||||
|
from matemat.util.currency_format import format_chf
|
||||||
|
|
||||||
|
|
||||||
@get('/deposit')
|
@get('/deposit')
|
||||||
|
@ -26,6 +28,7 @@ def deposit():
|
||||||
n = int(str(request.params.n))
|
n = int(str(request.params.n))
|
||||||
# Write the deposit to the database
|
# Write the deposit to the database
|
||||||
db.deposit(user, n)
|
db.deposit(user, n)
|
||||||
|
# Show notification on next page load
|
||||||
|
Notification.success(f'Deposited <strong>{format_chf(n)}</strong>', decay=True)
|
||||||
# Redirect to the main page (where this request should have come from)
|
# Redirect to the main page (where this request should have come from)
|
||||||
redirect(f'/?lastaction=deposit&lastprice={n}')
|
|
||||||
redirect('/')
|
redirect('/')
|
||||||
|
|
|
@ -20,12 +20,6 @@ def main_page():
|
||||||
with MatematDatabase(config['DatabaseFile']) as db:
|
with MatematDatabase(config['DatabaseFile']) as db:
|
||||||
# Fetch the list of products to display
|
# Fetch the list of products to display
|
||||||
products = db.list_products()
|
products = db.list_products()
|
||||||
lastprice = int(request.params.lastprice) if request.params.lastprice else None
|
|
||||||
if request.params.lastaction == 'deposit' and lastprice:
|
|
||||||
Notification.success(f'Deposited {format_chf(lastprice)}', decay=True)
|
|
||||||
elif request.params.lastaction == 'buy' and lastprice and request.params.lastproduct:
|
|
||||||
lastproduct = db.get_product(request.params.lastproduct)
|
|
||||||
Notification.success(f'Purchased {lastproduct.name} for {format_chf(lastprice)}', decay=True)
|
|
||||||
buyproduct = None
|
buyproduct = None
|
||||||
|
|
||||||
if request.params.ean:
|
if request.params.ean:
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
|
|
||||||
from .sessions import start, end, put, get, has, delete
|
from .sessions import start, end, put, get, has, delete, setdefault
|
||||||
|
|
|
@ -20,6 +20,9 @@ def start() -> str:
|
||||||
|
|
||||||
:return: The session ID.
|
:return: The session ID.
|
||||||
"""
|
"""
|
||||||
|
if hasattr(response, 'session_id'):
|
||||||
|
# A session has already been created while handling the same request
|
||||||
|
return response.session_id
|
||||||
# Reference date for session timeout
|
# Reference date for session timeout
|
||||||
now = datetime.now(UTC)
|
now = datetime.now(UTC)
|
||||||
# Read the client's session ID, if any
|
# Read the client's session ID, if any
|
||||||
|
@ -43,6 +46,9 @@ def start() -> str:
|
||||||
(now + timedelta(seconds=_SESSION_TIMEOUT), __session_vars[session_id][1])
|
(now + timedelta(seconds=_SESSION_TIMEOUT), __session_vars[session_id][1])
|
||||||
# Return the session ID and timeout
|
# Return the session ID and timeout
|
||||||
response.set_cookie(_COOKIE_NAME, session_id, secret=__key)
|
response.set_cookie(_COOKIE_NAME, session_id, secret=__key)
|
||||||
|
# Piggy-back the session id onto the response object so that we don't create another session
|
||||||
|
# in subsequent calls to start() while handling the same request.
|
||||||
|
response.session_id = session_id
|
||||||
return session_id
|
return session_id
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,10 +67,10 @@ def put(session_id: str, key: str, value: Any) -> None:
|
||||||
__session_vars[session_id][1][key] = value
|
__session_vars[session_id][1][key] = value
|
||||||
|
|
||||||
|
|
||||||
def get(session_id: str, key: str) -> Any:
|
def get(session_id: str, key: str, default: Any = None) -> Any:
|
||||||
if session_id in __session_vars and key in __session_vars[session_id][1]:
|
if session_id in __session_vars and key in __session_vars[session_id][1]:
|
||||||
return __session_vars[session_id][1][key]
|
return __session_vars[session_id][1][key]
|
||||||
return None
|
return default
|
||||||
|
|
||||||
|
|
||||||
def delete(session_id: str, key: str) -> None:
|
def delete(session_id: str, key: str) -> None:
|
||||||
|
@ -74,3 +80,13 @@ def delete(session_id: str, key: str) -> None:
|
||||||
|
|
||||||
def has(session_id: str, key: str) -> bool:
|
def has(session_id: str, key: str) -> bool:
|
||||||
return session_id in __session_vars and key in __session_vars[session_id][1]
|
return session_id in __session_vars and key in __session_vars[session_id][1]
|
||||||
|
|
||||||
|
|
||||||
|
def setdefault(session_id: str, key: str, value: Any) -> Any:
|
||||||
|
if session_id in __session_vars:
|
||||||
|
if has(session_id, key):
|
||||||
|
return get(session_id, key)
|
||||||
|
else:
|
||||||
|
put(session_id, key, value)
|
||||||
|
return value
|
||||||
|
return None
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
|
||||||
|
from matemat.webserver import session
|
||||||
|
|
||||||
|
|
||||||
class Notification:
|
class Notification:
|
||||||
notifications = []
|
|
||||||
|
|
||||||
def __init__(self, msg: str, classes=None, decay: bool = False):
|
def __init__(self, msg: str, classes=None, decay: bool = False):
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
@ -12,14 +13,18 @@ class Notification:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def render(cls):
|
def render(cls):
|
||||||
n = list(cls.notifications)
|
session_id: str = session.start()
|
||||||
cls.notifications.clear()
|
sn = session.get(session_id, 'notifications', [])
|
||||||
|
n = list(sn)
|
||||||
|
sn.clear()
|
||||||
return n
|
return n
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def success(cls, msg: str, decay: bool = False):
|
def success(cls, msg: str, decay: bool = False):
|
||||||
cls.notifications.append(cls(msg, classes=['success'], decay=decay))
|
session_id: str = session.start()
|
||||||
|
session.setdefault(session_id, 'notifications', []).append(cls(msg, classes=['success'], decay=decay))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def error(cls, msg: str, decay: bool = False):
|
def error(cls, msg: str, decay: bool = False):
|
||||||
cls.notifications.append(cls(msg, classes=['error'], decay=decay))
|
session_id: str = session.start()
|
||||||
|
session.setdefault(session_id, 'notifications', []).append(cls(msg, classes=['error'], decay=decay))
|
||||||
|
|
|
@ -25,10 +25,8 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block eanwebsocket %}
|
{% block eanwebsocket %}
|
||||||
function (e) {
|
|
||||||
let eaninput = document.getElementById("admin-newproduct-ean");
|
let eaninput = document.getElementById("admin-newproduct-ean");
|
||||||
eaninput.value = e.data;
|
eaninput.value = e.data;
|
||||||
eaninput.select();
|
eaninput.select();
|
||||||
eaninput.scrollIntoView();
|
eaninput.scrollIntoView();
|
||||||
}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,105 +1,85 @@
|
||||||
<section id="admin-restricted-newuser">
|
<section id="admin-restricted-newuser">
|
||||||
<h2>Create New User</h2>
|
<h2>Users</h2>
|
||||||
|
|
||||||
<form id="admin-newuser-form" method="post" action="/admin?adminchange=newuser" accept-charset="UTF-8">
|
<form id="admin-newuser-form" method="post" action="/admin?adminchange=newuser" accept-charset="UTF-8">
|
||||||
<label for="admin-newuser-username">Username: </label>
|
<table border="1">
|
||||||
<input id="admin-newuser-username" type="text" name="username" /><br/>
|
<tr>
|
||||||
|
<th>Username</th>
|
||||||
<label for="admin-newuser-email">E-Mail (optional): </label>
|
<th>E-Mail (optional)</th>
|
||||||
<input id="admin-newuser-email" type="text" name="email" /><br/>
|
<th>Password</th>
|
||||||
|
<th>Member</th>
|
||||||
<label for="admin-newuser-password">Password: </label>
|
<th>Admin</th>
|
||||||
<input id="admin-newuser-password" type="password" name="password" /><br/>
|
<th>Logout after purchase</th>
|
||||||
|
<th>Actions</th>
|
||||||
<label for="admin-newuser-ismember">Member: </label>
|
</tr>
|
||||||
<input id="admin-newuser-ismember" type="checkbox" name="ismember" /><br/>
|
<tr>
|
||||||
|
<td><input id="admin-newuser-username" type="text" name="username" placeholder="New username"></td>
|
||||||
<label for="admin-newuser-isadmin">Admin: </label>
|
<td><input id="admin-newuser-email" type="text" name="email" placeholder="New e-mail"></td>
|
||||||
<input id="admin-newuser-isadmin" type="checkbox" name="isadmin" /><br/>
|
<td><input id="admin-newuser-password" type="password" name="password" placeholder="New password"></td>
|
||||||
|
<td><input id="admin-newuser-ismember" type="checkbox" name="ismember"></td>
|
||||||
<label for="admin-newuser-logout-after-purchase">Logout after purchase: </label>
|
<td><input id="admin-newuser-isadmin" type="checkbox" name="isadmin"></td>
|
||||||
<input id="admin-newuser-logout-after-purchase" type="checkbox" name="logout_after_purchase" /><br/>
|
<td><input id="admin-newuser-logout-after-purchase" type="checkbox" name="logout_after_purchase"></td>
|
||||||
|
<td><input type="submit" value="Create User"></td>
|
||||||
<input type="submit" value="Create User" />
|
</tr>
|
||||||
</form>
|
{% for user in users %}
|
||||||
</section>
|
<tr>
|
||||||
|
<td>{{ user.name }}</td>
|
||||||
<section id="admin-restricted-moduser">
|
<td>{{ '✓' if user.email else '✗' }}</td>
|
||||||
<h2>Modify User</h2>
|
<td>••••••••</td>
|
||||||
|
<td>{{ '✓' if user.is_member else '✗' }}</td>
|
||||||
<form id="admin-moduser-form" method="get" action="/moduser" accept-charset="UTF-8">
|
<td>{{ '✓' if user.is_admin else '✗' }}</td>
|
||||||
<label for="admin-moduser-userid">Username: </label>
|
<td>{{ '✓' if user.logout_after_purchase else '✗' }}</td>
|
||||||
<select id="admin-moduser-userid" name="userid">
|
<td>
|
||||||
{% for user in users %}
|
<a style="text-decoration: none; color: #0000ff;" href="/moduser?userid={{ user.id }}">🖊</a>
|
||||||
<option value="{{ user.id }}">{{ user.name }}</option>
|
<a style="text-decoration: none; color: #ff0000;" href="/moduser?userid={{ user.id }}&change=del">🗑</a>
|
||||||
{% endfor %}
|
</td>
|
||||||
</select><br/>
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
<input type="submit" value="Go" />
|
</table>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="admin-restricted-newproduct">
|
<section id="admin-restricted-newproduct">
|
||||||
<h2>Create New Product</h2>
|
<h2>Products</h2>
|
||||||
|
|
||||||
<form id="admin-newproduct-form" method="post" action="/admin?adminchange=newproduct" enctype="multipart/form-data" accept-charset="UTF-8">
|
<form id="admin-newproduct-form" method="post" action="/admin?adminchange=newproduct" enctype="multipart/form-data" accept-charset="UTF-8">
|
||||||
<label for="admin-newproduct-name">Name: </label>
|
<table border="1">
|
||||||
<input id="admin-newproduct-name" type="text" name="name" /><br/>
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
<label for="admin-newproduct-ean">EAN code: </label>
|
<th>EAN code</th>
|
||||||
<input id="admin-newproduct-ean" type="text" name="ean" /><br/>
|
<th>Member price</th>
|
||||||
|
<th>Non-member price</th>
|
||||||
<label for="admin-newproduct-price-member">Member price: </label>
|
<th>Custom price</th>
|
||||||
CHF <input id="admin-newproduct-price-member" type="number" step="0.01" name="pricemember" value="0" /><br/>
|
<th>Stockable</th>
|
||||||
|
<th>Image</th>
|
||||||
<label for="admin-newproduct-price-non-member">Non-member price: </label>
|
<th>Actions</th>
|
||||||
CHF <input id="admin-newproduct-price-non-member" type="number" step="0.01" name="pricenonmember" value="0" /><br/>
|
</tr>
|
||||||
|
<tr>
|
||||||
<label for="admin-custom-price"><abbr title="When 'Custom Price' is enabled, users choose the price to pay, but at least the prices given above">Custom Price</abbr>: </label>
|
<td><input id="admin-newproduct-name" type="text" name="name" placeholder="New product name"></td>
|
||||||
<input id="admin-custom-price" type="checkbox" name="custom_price" /><br/>
|
<td><input id="admin-newproduct-ean" type="text" name="ean" placeholder="Scan to insert EAN"></td>
|
||||||
|
<td>CHF <input id="admin-newproduct-price-member" type="number" step="0.01" name="pricemember" value="0"></td>
|
||||||
<label for="admin-newproduct-stockable">Stockable: </label>
|
<td>CHF <input id="admin-newproduct-price-non-member" type="number" step="0.01" name="pricenonmember" value="0"></td>
|
||||||
<input id="admin-newproduct-stockable" type="checkbox" name="stockable" checked="checked" /><br/>
|
<td><input id="admin-custom-price" type="checkbox" name="custom_price"></td>
|
||||||
|
<td><input id="admin-newproduct-stockable" type="checkbox" name="stockable" checked="checked"></td>
|
||||||
<label for="admin-newproduct-image">Image: </label>
|
<td><input id="admin-newproduct-image" name="image" type="file" accept="image/*"></td>
|
||||||
<input id="admin-newproduct-image" name="image" type="file" accept="image/*" /><br/>
|
<td><input type="submit" value="Create Product"></td>
|
||||||
|
</tr>
|
||||||
<input type="submit" value="Create Product" />
|
{% for product in products %}
|
||||||
</form>
|
<tr>
|
||||||
</section>
|
<td>{{ product.name }}</td>
|
||||||
|
<td>{{ product.ean or '' }}</td>
|
||||||
<section id="admin-restricted-restock">
|
<td>{{ product.price_member | chf }}</td>
|
||||||
<h2>Restock Product</h2>
|
<td>{{ product.price_non_member | chf }}</td>
|
||||||
|
<td>{{ '✓' if product.custom_price else '✗' }}</td>
|
||||||
<form id="admin-restock-form" method="post" action="/admin?adminchange=restock" accept-charset="UTF-8">
|
<td>{{ '✓' if product.stockable else '✗' }}</td>
|
||||||
<label for="admin-restock-productid">Product: </label>
|
<td><img style="height: 2em;" src="/static/upload/thumbnails/products/{{ product.id }}.png?cacheBuster={{ now }}" alt="Picture of {{ product.name }}" draggable="false"></td>
|
||||||
<select id="admin-restock-productid" name="productid">
|
<td>
|
||||||
{% for product in products %}
|
<a style="text-decoration: none; color: #0000ff;" href="/modproduct?productid={{ product.id }}">🖊</a>
|
||||||
{% if product.stockable %}
|
<a style="text-decoration: none; color: #ff0000;" href="/modproduct?productid={{ product.id }}&change=del">🗑</a>
|
||||||
<option value="{{ product.id }}">{{ product.name }} ({{ product.stock }})</option>
|
</td>
|
||||||
{% endif %}
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select><br/>
|
</table>
|
||||||
|
|
||||||
<label for="admin-restock-amount">Amount: </label>
|
|
||||||
<input id="admin-restock-amount" type="number" min="0" name="amount" /><br/>
|
|
||||||
|
|
||||||
<input type="submit" value="Restock" />
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="admin-restricted-modproduct">
|
|
||||||
<h2>Modify Product</h2>
|
|
||||||
|
|
||||||
<form id="admin-modproduct-form" method="get" action="/modproduct" accept-charset="UTF-8">
|
|
||||||
<label for="admin-modproduct-productid">Product: </label>
|
|
||||||
<select id="admin-modproduct-productid" name="productid">
|
|
||||||
{% for product in products %}
|
|
||||||
<option value="{{ product.id }}">{{ product.name }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select><br/>
|
|
||||||
|
|
||||||
<input type="submit" value="Go">
|
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -107,16 +87,32 @@
|
||||||
<h2>Set default images</h2>
|
<h2>Set default images</h2>
|
||||||
|
|
||||||
<form id="admin-default-images-form" method="post" action="/admin?adminchange=defaultimg" enctype="multipart/form-data" accept-charset="UTF-8">
|
<form id="admin-default-images-form" method="post" action="/admin?adminchange=defaultimg" enctype="multipart/form-data" accept-charset="UTF-8">
|
||||||
<label for="admin-default-images-user">
|
<table>
|
||||||
<img src="/static/upload/thumbnails/users/default.png" alt="Default user avatar" />
|
<tr>
|
||||||
</label><br/>
|
<th>Default user avatar</th>
|
||||||
<input id="admin-default-images-user" type="file" name="users" accept="image/*" /><br/>
|
<th>Default product image</th>
|
||||||
|
</tr>
|
||||||
<label for="admin-default-images-product">
|
<tr>
|
||||||
<img src="/static/upload/thumbnails/products/default.png" alt="Default product avatar" />
|
<td>
|
||||||
</label><br/>
|
<label for="admin-default-images-user">
|
||||||
<input id="admin-default-images-product" type="file" name="products" accept="image/*" /><br/>
|
<img src="/static/upload/thumbnails/users/default.png" alt="Default user avatar" />
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label for="admin-default-images-product">
|
||||||
|
<img src="/static/upload/thumbnails/products/default.png" alt="Default product avatar" />
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<input id="admin-default-images-user" type="file" name="users" accept="image/*" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input id="admin-default-images-product" type="file" name="products" accept="image/*" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
<input type="submit" value="Save changes">
|
<input type="submit" value="Save changes">
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -61,7 +61,13 @@
|
||||||
function connect() {
|
function connect() {
|
||||||
let socket = new WebSocket("{{ eanwebsocket }}");
|
let socket = new WebSocket("{{ eanwebsocket }}");
|
||||||
socket.onclose = () => { setTimeout(connect, 1000); };
|
socket.onclose = () => { setTimeout(connect, 1000); };
|
||||||
socket.onmessage = {% block eanwebsocket %}() => {}{% endblock %};
|
socket.onmessage = function (e) {
|
||||||
|
// Focus this tab - requires https://git.kabelsalat.ch/ccc-basel/barcode-utils
|
||||||
|
if (typeof window.extension_tabfocus === "function") {
|
||||||
|
window.extension_tabfocus();
|
||||||
|
}
|
||||||
|
{% block eanwebsocket %}{% endblock %}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
window.addEventListener("load", () => { connect(); });
|
window.addEventListener("load", () => { connect(); });
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -18,6 +18,14 @@
|
||||||
<input type="submit" value="Login">
|
<input type="submit" value="Login">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div class="thumblist-item">
|
||||||
|
<a href="/">Cancel</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block eanwebsocket %}
|
||||||
|
document.location = "/?ean=" + e.data;
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -54,10 +54,8 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block eanwebsocket %}
|
{% block eanwebsocket %}
|
||||||
function (e) {
|
|
||||||
let eaninput = document.getElementById("modproduct-ean");
|
let eaninput = document.getElementById("modproduct-ean");
|
||||||
eaninput.value = e.data;
|
eaninput.value = e.data;
|
||||||
eaninput.select();
|
eaninput.select();
|
||||||
eaninput.scrollIntoView();
|
eaninput.scrollIntoView();
|
||||||
}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -84,12 +84,10 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block eanwebsocket %}
|
{% block eanwebsocket %}
|
||||||
function (e) {
|
|
||||||
let eaninput = document.getElementById("a-buy-ean" + e.data);
|
let eaninput = document.getElementById("a-buy-ean" + e.data);
|
||||||
if (eaninput === null) {
|
if (eaninput === null) {
|
||||||
document.location = "?ean=" + e.data;
|
document.location = "?ean=" + e.data;
|
||||||
} else {
|
} else {
|
||||||
eaninput.click();
|
eaninput.click();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -32,6 +32,11 @@
|
||||||
|
|
||||||
<input type="submit" value="Create account">
|
<input type="submit" value="Create account">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div class="thumblist-item">
|
||||||
|
<a href="/">Cancel</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/touchkey.js" ></script>
|
<script src="/static/js/touchkey.js" ></script>
|
||||||
<script>
|
<script>
|
||||||
initTouchkey(true, 'touchkey-svg', null, 'signup-touchkey');
|
initTouchkey(true, 'touchkey-svg', null, 'signup-touchkey');
|
||||||
|
@ -40,3 +45,7 @@
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block eanwebsocket %}
|
||||||
|
document.location = "/?ean=" + e.data;
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -27,6 +27,10 @@
|
||||||
<input type="submit" value="Create account">
|
<input type="submit" value="Create account">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<div class="thumblist-item">
|
||||||
|
<a href="/">Cancel</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="osk-kbd" class="osk osk-kbd">
|
<div id="osk-kbd" class="osk osk-kbd">
|
||||||
{% set lower = [['1','2','3','4','5','6','7','8','9','0','-','⌫'],
|
{% set lower = [['1','2','3','4','5','6','7','8','9','0','-','⌫'],
|
||||||
['q','w','e','r','t','y','u','i','o','p','[','⇥'],
|
['q','w','e','r','t','y','u','i','o','p','[','⇥'],
|
||||||
|
@ -115,3 +119,7 @@
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block eanwebsocket %}
|
||||||
|
document.location = "/?ean=" + e.data;
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -27,7 +27,10 @@
|
||||||
<input type="hidden" name="buypid" value="{{ buypid }}" />
|
<input type="hidden" name="buypid" value="{{ buypid }}" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
<a href="/">Cancel</a>
|
|
||||||
|
<div class="thumblist-item">
|
||||||
|
<a href="/">Cancel</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/touchkey.js"></script>
|
<script src="/static/js/touchkey.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -37,3 +40,7 @@
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block eanwebsocket %}
|
||||||
|
document.location = "/?ean=" + e.data;
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -36,7 +36,5 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block eanwebsocket %}
|
{% block eanwebsocket %}
|
||||||
function (e) {
|
document.location = "/?ean=" + e.data;
|
||||||
document.location = "?ean=" + e.data;
|
|
||||||
}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue