v0.2.14: Present an on-screen keyboard and disable file upload in kiosk mode (localhost)

This commit is contained in:
s3lph 2022-10-22 16:14:19 +02:00
parent d3f2da88cc
commit 8c3e0e08b6
10 changed files with 197 additions and 5 deletions

View file

@ -1,5 +1,18 @@
# Matemat Changelog # Matemat Changelog
<!-- BEGIN RELEASE v0.2.14 -->
## Version 0.2.14
UX release
### Changes
<!-- BEGIN CHANGES 0.2.14 -->
- Present an on-screen keyboard and disable file upload in kiosk mode (localhost)
<!-- END CHANGES 0.2.14 -->
<!-- END RELEASE v0.2.14 -->
<!-- BEGIN RELEASE v0.2.13 --> <!-- BEGIN RELEASE v0.2.13 -->
## Version 0.2.13 ## Version 0.2.13

View file

@ -21,6 +21,7 @@ This project intends to provide a well-tested and maintainable alternative to
- file-magic - file-magic
- jinja2 - jinja2
- Pillow - Pillow
- netaddr
## Usage ## Usage

View file

@ -1,2 +1,2 @@
__version__ = '0.2.13' __version__ = '0.2.14'

View file

@ -6,6 +6,7 @@ from io import BytesIO
import magic import magic
from bottle import get, post, redirect, abort, request, FormsDict from bottle import get, post, redirect, abort, request, FormsDict
from PIL import Image from PIL import Image
import netaddr
from matemat.db import MatematDatabase from matemat.db import MatematDatabase
from matemat.db.primitives import User from matemat.db.primitives import User
@ -89,5 +90,11 @@ def signup():
redirect('/') redirect('/')
elif request.method != 'GET': elif request.method != 'GET':
abort(405, 'Method not allowed') abort(405, 'Method not allowed')
acl = netaddr.IPSet([addr.strip() for addr in config.get('SignupKioskMode', '').split(',')])
if request.remote_addr in acl:
return template.render('signup_kiosk.html',
zip=zip,
setupname=config['InstanceName'])
return template.render('signup.html', return template.render('signup.html',
setupname=config['InstanceName']) setupname=config['InstanceName'])

View file

@ -1,10 +1,10 @@
Package: matemat Package: matemat
Version: 0.2.13 Version: 0.2.14
Maintainer: s3lph <account-gitlab-ideynizv@kernelpanic.lol> Maintainer: s3lph <1375407-s3lph@users.noreply.gitlab.com>
Section: web Section: web
Priority: optional Priority: optional
Architecture: all Architecture: all
Depends: python3 (>= 3.6), python3-bottle, python3-jinja2, python3-magic, python3-pil Depends: python3 (>= 3.6), python3-bottle, python3-jinja2, python3-magic, python3-netaddr, python3-pil
Description: Soda machine stock-keeping webservice Description: Soda machine stock-keeping webservice
A web service for automated stock-keeping of a soda machine written in Python. A web service for automated stock-keeping of a soda machine written in Python.
It provides a touch-input-friendly user interface (as most input happens It provides a touch-input-friendly user interface (as most input happens

View file

@ -28,5 +28,12 @@ InstanceName=Matemat
# #
# SmtpSendReceipts=1 # SmtpSendReceipts=1
#
# Permit user signup
#
#SignupEnabled=1
#SignupKioskMode= ::1, ::ffff:127.0.0.0/8, 127.0.0.0/8
# Add static HTTP headers in this section # Add static HTTP headers in this section
# [HttpHeaders] # [HttpHeaders]

View file

@ -9,3 +9,5 @@ LogTarget=stdout
UploadDir=/var/lib/matemat/upload UploadDir=/var/lib/matemat/upload
DatabaseFile=/var/lib/matemat/matemat.db DatabaseFile=/var/lib/matemat/matemat.db
SignupKioskMode= ::1, ::ffff:127.0.0.0/8, 127.0.0.0/8

View file

@ -22,7 +22,8 @@ soda machine's touch screen).
'bottle', 'bottle',
'file-magic', 'file-magic',
'jinja2', 'jinja2',
'Pillow' 'Pillow',
'netaddr'
], ],
test_loader='unittest:TestLoader', test_loader='unittest:TestLoader',
entry_points={ entry_points={

View file

@ -249,4 +249,48 @@
#transfer-userlist-list > li.active { #transfer-userlist-list > li.active {
background: #60f060; background: #60f060;
}
div.osk-kbd {
display: none;
font-family: sans-serif;
position: fixed;
left: 0;
bottom: 0;
right: 0;
flex-direction: column;
z-index: 100;
background: white;
font-size: 5vh;
}
div.osk-kbd.visible {
display: flex;
}
div.osk-kbd-row {
width: 100%;
height: 10vh;
flex-grow: 1;
display: flex;
flex-direction: row;
}
div.osk-button {
flex: 1 0 1px;
background: #f0f0f0;
padding: 5px;
margin: 2px;
text-align: center;
line-height: calc(10vh - 10px);
}
div.osk-button:active, div.osk-button.osk-locked {
background: #606060;
}
div.osk-button.osk-button-space {
flex: 5 0 1px;
color: #606060;
} }

117
templates/signup_kiosk.html Normal file
View file

@ -0,0 +1,117 @@
{% extends "base.html" %}
{% block header %}
<h1>Signup</h1>
{{ super() }}
{% endblock %}
{% block main %}
{# Show a username/password signup form #}
<form method="post" action="/signup" id="signupform" enctype="multipart/form-data" accept-charset="UTF-8">
<label for="signup-username"><b>Username</b>: </label>
<input id="signup-username" type="text" name="username" required="required" class="osk-target"/><br/>
<label for="signup-password"><b>Choose a password</b>: </label>
<input id="signup-password" type="password" name="password" required="required" class="osk-target"/><br/>
<label for="signup-password2"><b>Repeat password</b>: </label>
<input id="signup-password2" type="password" name="password2" required="required" class="osk-target"/><br/>
<label for="signup-touchkey">Draw a touchkey (touchscreen login pattern)</label>
<br/>
<svg id="touchkey-svg" width="400" height="400"></svg>
<br/>
<input id="signup-touchkey" type="hidden" name="touchkey" value="" />
<input type="submit" value="Create account">
</form>
<div id="osk-kbd" class="osk osk-kbd">
{% set lower = [['1','2','3','4','5','6','7','8','9','0','-','⌫'],
['q','w','e','r','t','y','u','i','o','p','[','⇥'],
['a','s','d','f','g','h','j','k','l',';','\'','⇤'],
['z','x','c','v','b','n','m',',','.','/','{','⇧'],
['SPACE']] %}
{% set upper = [['!','@','#','$','%','^','&','*','(',')','_','⌫'],
['Q','W','E','R','T','Y','U','I','O','P',']','⇥'],
['A','S','D','F','G','H','J','K','L',':','"','⇤'],
['Z','X','C','V','B','N','M','<','>','?','}','⇧'],
['SPACE']] %}
{% for lrow, urow in zip(lower, upper) %}
<div class="osk osk-kbd-row">
{% for lc, uc in zip(lrow, urow) %}
<div tabindex="1000" class="osk osk-button{% if lc == 'SPACE' %} osk-button-space{% endif %}" data-lowercase="{{ lc }}" data-uppercase="{{ uc }}">{{ lc }}</div>
{% endfor %}
</div>
{% endfor %}
</div>
<script src="/static/js/touchkey.js" ></script>
<script>
initTouchkey(true, 'touchkey-svg', null, 'signup-touchkey');
</script>
<script>
let lastfocus = null;
let shift = 0;
let osk = document.getElementById('osk-kbd');
let inputs = [].slice.call(document.getElementsByClassName('osk-target'));
let oskButtons = document.getElementsByClassName('osk-button');
for (let i = 0; i < inputs.length; ++i) {
inputs[i].onfocus = () => {
osk.classList.add('visible');
}
inputs[i].onblur = (blur) => {
if (blur.relatedTarget !== undefined && blur.relatedTarget !== null && blur.relatedTarget.classList.contains('osk')) {
lastfocus = blur.target;
} else {
lastfocus = null;
if (blur.relatedTarget === undefined || blur.relatedTarget === null || !blur.relatedTarget.classList.contains('osk-target')) {
osk.classList.remove('visible');
}
}
}
}
for (let i = 0; i < oskButtons.length; ++i) {
oskButtons[i].onclick = (click) => {
if (lastfocus === null || lastfocus === undefined) {
return;
}
let btn = click.target.innerText;
let idx = inputs.indexOf(lastfocus);
switch (btn) {
case '⇥':
lastfocus = inputs[(idx + 1) % inputs.length];
break;
case '⇤':
lastfocus = inputs[(((idx - 1) % inputs.length) + inputs.length) % inputs.length];
break;
case '⌫':
if (lastfocus.value.length > 0) {
lastfocus.value = lastfocus.value.substring(0, lastfocus.value.length-1);
}
break;
case 'SPACE':
lastfocus.value += ' ';
break;
case '⇧':
shift = !shift;
click.target.classList.toggle('osk-locked');
for (let j = 0; j < oskButtons.length; ++j) {
oskButtons[j].innerText = oskButtons[j].getAttribute(shift ? 'data-uppercase' : 'data-lowercase');
}
break;
default: {
lastfocus.value += btn;
break;
}
}
lastfocus.focus();
}
}
</script>
{{ super() }}
{% endblock %}