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
<!-- 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 -->
## Version 0.2.13

View file

@ -21,6 +21,7 @@ This project intends to provide a well-tested and maintainable alternative to
- file-magic
- jinja2
- Pillow
- netaddr
## 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
from bottle import get, post, redirect, abort, request, FormsDict
from PIL import Image
import netaddr
from matemat.db import MatematDatabase
from matemat.db.primitives import User
@ -89,5 +90,11 @@ def signup():
redirect('/')
elif request.method != 'GET':
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',
setupname=config['InstanceName'])

View file

@ -1,10 +1,10 @@
Package: matemat
Version: 0.2.13
Maintainer: s3lph <account-gitlab-ideynizv@kernelpanic.lol>
Version: 0.2.14
Maintainer: s3lph <1375407-s3lph@users.noreply.gitlab.com>
Section: web
Priority: optional
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
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

View file

@ -28,5 +28,12 @@ InstanceName=Matemat
#
# 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
# [HttpHeaders]

View file

@ -9,3 +9,5 @@ LogTarget=stdout
UploadDir=/var/lib/matemat/upload
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',
'file-magic',
'jinja2',
'Pillow'
'Pillow',
'netaddr'
],
test_loader='unittest:TestLoader',
entry_points={

View file

@ -249,4 +249,48 @@
#transfer-userlist-list > li.active {
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 %}