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
|
# 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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
|
|
||||||
__version__ = '0.2.13'
|
__version__ = '0.2.14'
|
||||||
|
|
|
@ -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'])
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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
|
3
setup.py
3
setup.py
|
@ -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={
|
||||||
|
|
|
@ -250,3 +250,47 @@
|
||||||
#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
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