v0.2.14: Present an on-screen keyboard and disable file upload in kiosk mode (localhost)
This commit is contained in:
parent
d3f2da88cc
commit
8c3e0e08b6
10 changed files with 197 additions and 5 deletions
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -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
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ This project intends to provide a well-tested and maintainable alternative to
|
|||
- file-magic
|
||||
- jinja2
|
||||
- Pillow
|
||||
- netaddr
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
|
||||
__version__ = '0.2.13'
|
||||
__version__ = '0.2.14'
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
3
setup.py
3
setup.py
|
@ -22,7 +22,8 @@ soda machine's touch screen).
|
|||
'bottle',
|
||||
'file-magic',
|
||||
'jinja2',
|
||||
'Pillow'
|
||||
'Pillow',
|
||||
'netaddr'
|
||||
],
|
||||
test_loader='unittest:TestLoader',
|
||||
entry_points={
|
||||
|
|
|
@ -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
117
templates/signup_kiosk.html
Normal 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 %}
|
Loading…
Reference in a new issue