info field. ability to go wwithout defaults
This commit is contained in:
34
README.md
34
README.md
@@ -18,41 +18,69 @@ Setup:
|
||||
{
|
||||
"type": "checkbox",
|
||||
"name": "my_check",
|
||||
"default": "off"
|
||||
"title": "Click to enable",
|
||||
"default": "on"
|
||||
},
|
||||
{
|
||||
"type": "info",
|
||||
"title": "Piece of text shown here."
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"name": "my_number",
|
||||
"default": "1.0"
|
||||
"default": "1.0",
|
||||
"title": "Only numbers allowed."
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "my_text",
|
||||
"title": "Any text here",
|
||||
"default": "some text"
|
||||
},
|
||||
{
|
||||
"type": "range",
|
||||
"name": "my_slider",
|
||||
"title": "Slide away!",
|
||||
"default": "75",
|
||||
"min": "0",
|
||||
"max": "100"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"title": "Select any from these",
|
||||
"name": "my_selection",
|
||||
"default": "select2",
|
||||
"options": [
|
||||
"option1",
|
||||
"select2"
|
||||
"select2",
|
||||
"select4",
|
||||
"select5",
|
||||
"select9"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
- Copy some images under data/images/
|
||||
- Start the docker instance
|
||||
- Open the URL in http://localhost:$EXPOSE
|
||||
|
||||
## Label types
|
||||
|
||||
Label entries require "type" and "name". All labels can include
|
||||
a "title" field, which is added as a hover-on text, and "default" for
|
||||
the default value.
|
||||
|
||||
In some cases more fields required.
|
||||
|
||||
- checkbox: If "default": "on", checkbox is selected. Otherwise it is unselected.
|
||||
- text: Any string, if no default: ""
|
||||
- number: Any number entry, if no default: ""
|
||||
- range: Requires "min" and "max" values. if no default, default = min.
|
||||
- select: Requires a list of "options".
|
||||
- info: Not a selection. Add "title" field to show text instead.
|
||||
|
||||
## With nginx:
|
||||
|
||||
```
|
||||
|
||||
@@ -15,6 +15,7 @@ from flask import (
|
||||
from revprox import ReverseProxied
|
||||
|
||||
# configuration
|
||||
VERSION = "2022.05.27"
|
||||
IMAGEDIR = "/data/images/"
|
||||
LABELDIR = "/data/labels/"
|
||||
CONFIG_FILE = "/data/config.json"
|
||||
@@ -33,6 +34,7 @@ app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||
|
||||
@app.before_request
|
||||
def before_request_func():
|
||||
g.version = app.config["VERSION"]
|
||||
try:
|
||||
with open(app.config["CONFIG_FILE"], "rt") as fp:
|
||||
g.config = json.load(fp)
|
||||
@@ -43,7 +45,7 @@ def before_request_func():
|
||||
logging.warning("config.json could not be read. using defaults.")
|
||||
g.labels = [
|
||||
{"type": "checkbox", "name": "my_check", "default": "off"},
|
||||
{"type": "text", "name": "my_text", "default": "1.0"},
|
||||
{"type": "text", "name": "my_text", "default": "Some text"},
|
||||
{
|
||||
"type": "range",
|
||||
"name": "my_slider",
|
||||
@@ -55,8 +57,14 @@ def before_request_func():
|
||||
g.config = {"title": "Labeler", "labels": g.labels}
|
||||
g.users = ["user"]
|
||||
|
||||
if not "title" in g.config:
|
||||
g.config["title"] = "Labeler"
|
||||
|
||||
for label in g.labels:
|
||||
label["value"] = label["default"]
|
||||
if label["type"] == "range":
|
||||
label["value"] = label.get("default", label.get("min", ""))
|
||||
else:
|
||||
label["value"] = label.get("default", "")
|
||||
|
||||
|
||||
def natural_key(string_):
|
||||
@@ -152,12 +160,7 @@ def main(user=None):
|
||||
|
||||
user_name = get_user()
|
||||
|
||||
return render_template(
|
||||
"main.html",
|
||||
current_user=user_name,
|
||||
users=g.users,
|
||||
title=g.config.get("title", "Labeler"),
|
||||
)
|
||||
return render_template("main.html", current_user=user_name)
|
||||
|
||||
|
||||
@app.route("/image", methods=["GET", "POST"])
|
||||
@@ -194,5 +197,4 @@ def show_image(id=None):
|
||||
labels=labels,
|
||||
id_minus=id_minus,
|
||||
id_plus=id_plus,
|
||||
title=g.config.get("title", "Labeler"),
|
||||
)
|
||||
@@ -1,5 +1,5 @@
|
||||
class ReverseProxied(object):
|
||||
'''Wrap the application in this middleware and configure the
|
||||
"""Wrap the application in this middleware and configure the
|
||||
front-end server to add these headers, to let you quietly bind
|
||||
this to a URL other than / and to an HTTP scheme that is
|
||||
different than what is used locally.
|
||||
@@ -14,19 +14,20 @@ class ReverseProxied(object):
|
||||
}
|
||||
|
||||
:param app: the WSGI application
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
|
||||
script_name = environ.get("HTTP_X_SCRIPT_NAME", "")
|
||||
if script_name:
|
||||
environ['SCRIPT_NAME'] = script_name
|
||||
path_info = environ['PATH_INFO']
|
||||
environ["SCRIPT_NAME"] = script_name
|
||||
path_info = environ["PATH_INFO"]
|
||||
if path_info.startswith(script_name):
|
||||
environ['PATH_INFO'] = path_info[len(script_name):]
|
||||
environ["PATH_INFO"] = path_info[len(script_name) :]
|
||||
|
||||
scheme = environ.get('HTTP_X_SCHEME', '')
|
||||
scheme = environ.get("HTTP_X_SCHEME", "")
|
||||
if scheme:
|
||||
environ['wsgi.url_scheme'] = scheme
|
||||
environ["wsgi.url_scheme"] = scheme
|
||||
return self.app(environ, start_response)
|
||||
|
||||
@@ -1,65 +1,125 @@
|
||||
body { font-family: sans-serif; background: #888; margin: 0px;
|
||||
min-height: 100vh; width: 100vw; overflow-x: hidden; }
|
||||
a, h1, h2 { color: #377ba8; }
|
||||
h1, h2 { font-family: 'Georgia', serif; margin: 0; }
|
||||
h1 { border-bottom: 2px solid #eee; }
|
||||
h2 { font-size: 1.2em; }
|
||||
|
||||
tr,td,tbody { margin: 0px; }
|
||||
|
||||
.page { margin: 0em;
|
||||
padding: 0em; background: #888; min-height: 100vh; width: 100vw;}
|
||||
.entries { list-style: none; margin: 0; padding: 0; width:100%; min-height: 95vh; }
|
||||
|
||||
.large { font-size: 3em; }
|
||||
.right { text-align: right; }
|
||||
.center { text-align: center; }
|
||||
|
||||
#image { position: absolute;
|
||||
left:0px; top:0px;
|
||||
width: calc(100vw - 220px); /*height:95vh;*/ }
|
||||
#img { max-width: calc(100vw - 220px); max-height:95vh;
|
||||
width: auto; height: auto;
|
||||
display: block; margin-left: auto;
|
||||
margin-right: auto; }
|
||||
|
||||
#img_title { overflow-x: hidden;
|
||||
overflow-wrap: anywhere;
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background: #888;
|
||||
margin: 0px;
|
||||
min-height: 100vh;
|
||||
width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.page {
|
||||
margin: 0em;
|
||||
padding: 0em;
|
||||
background: #888;
|
||||
min-height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.entries {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
min-height: 95vh;
|
||||
}
|
||||
|
||||
.large {
|
||||
font-size: 3em;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#home {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
#home a {
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
font-size: large;
|
||||
background-color: #aaa;
|
||||
border-radius: 5px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#image {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: calc(100vw - 220px);
|
||||
/*height:95vh;*/
|
||||
}
|
||||
|
||||
#img {
|
||||
max-width: calc(100vw - 220px);
|
||||
max-height: 95vh;
|
||||
width: auto;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
#img_title {
|
||||
overflow-x: hidden;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#topright {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
/*width: 20vw;*/
|
||||
height: 95vh;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
#topright { position: absolute;
|
||||
right:0px; top:0px;
|
||||
/*width: 20vw;*/
|
||||
height:95vh;
|
||||
font-size: large; }
|
||||
.inputcontainer {
|
||||
z-index:1;
|
||||
background-color: #aaa;
|
||||
padding: 5px;
|
||||
margin-bottom: 3px;
|
||||
border-radius: 5px;
|
||||
width: 210px;
|
||||
z-index: 1;
|
||||
background-color: #aaa;
|
||||
padding: 5px;
|
||||
margin-bottom: 3px;
|
||||
border-radius: 5px;
|
||||
width: 210px;
|
||||
}
|
||||
|
||||
|
||||
input, select {
|
||||
width: 180px;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
|
||||
input[type="text"] {
|
||||
|
||||
input,
|
||||
select {
|
||||
width: 180px;
|
||||
font-size: large;
|
||||
}
|
||||
input[type="submit"] {
|
||||
|
||||
}
|
||||
|
||||
input[type="text"] {}
|
||||
|
||||
input[type="submit"] {}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: 3em;
|
||||
height: 1.5em;
|
||||
width: 3em;
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
output {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: initial;
|
||||
}
|
||||
label { display: block; }
|
||||
output { display: block; font-weight: bold; }
|
||||
|
||||
.button_next {
|
||||
width: 2em;
|
||||
@@ -67,12 +127,15 @@ output { display: block; font-weight: bold; }
|
||||
font-size: large;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.float_left {
|
||||
float: left;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.float_right {
|
||||
float:right;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.button_save {
|
||||
height: 3em;
|
||||
margin-top: 1em;
|
||||
@@ -80,6 +143,7 @@ output { display: block; font-weight: bold; }
|
||||
margin-right: 1em;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.button_continue {
|
||||
height: 4em;
|
||||
margin-top: 3em;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
|
||||
<meta name="viewport" content="width=440" />
|
||||
<script language="javascript" src="{{ url_for('static', filename='script.js') }}" ></script>
|
||||
|
||||
<title>{{ g.config.title }}</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
|
||||
<meta name="viewport" content="width=440">
|
||||
<meta name="VERSION" content="{{ g.version }}">
|
||||
<script language="javascript" src="{{ url_for('static', filename='script.js') }}"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class=page>
|
||||
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
<div class="page">
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,13 +1,12 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
<div class="entries">
|
||||
|
||||
<div class="center inputcontainer">
|
||||
<form action="{{ url_for('main') }}" method=post class=add-entry>
|
||||
<label>Choose user:</label>
|
||||
<select name="user_name">
|
||||
{% for user in users %}
|
||||
<option value="{{user}}" {% if user == current_user %}SELECTED{% endif %}>{{user}}</option>
|
||||
{% for user in g.users %}
|
||||
<option value="{{user}}" {% if user == current_user %}SELECTED{% endif %} >{{user}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<br>
|
||||
|
||||
@@ -11,29 +11,33 @@
|
||||
<input type=hidden value="{{ image_name }}" name=image_name>
|
||||
{% for label in labels %}
|
||||
<div class=inputcontainer>
|
||||
{% if label.type == "info" %}
|
||||
<label class=info>{{ label.title }}</label>
|
||||
{% else %}
|
||||
<label>{{ label.name }}:</label>
|
||||
<div class=center>
|
||||
{% if label.type == "checkbox" %}
|
||||
<input class=center type="checkbox" name="label_{{ label.name }}" {% if label.value == "on" %}checked{% endif %}>
|
||||
<input class=center type="checkbox" name="label_{{ label.name }}" {% if label.value == "on" %}checked{% endif %} title="{{label.title}}">
|
||||
{% endif %}
|
||||
{% if label.type == "text" %}
|
||||
<input type="text" name="label_{{ label.name }}" value="{{ label.value }}">
|
||||
<input type="text" name="label_{{ label.name }}" value="{{ label.value }}" title="{{label.title}}">
|
||||
{% endif %}
|
||||
{% if label.type == "number" %}
|
||||
<input type="number" step="any" name="label_{{ label.name }}" value="{{ label.value }}">
|
||||
<input type="number" step="any" name="label_{{ label.name }}" value="{{ label.value }}" title="{{label.title}}">
|
||||
{% endif %}
|
||||
{% if label.type == "range" %}
|
||||
<input type="range" name="label_{{ label.name }}" value="{{ label.value }}" min="{{ label.min }}" max="{{ label.max }}" oninput="this.nextElementSibling.value = this.value">
|
||||
<input type="range" name="label_{{ label.name }}" value="{{ label.value }}" min="{{ label.min }}" max="{{ label.max }}" oninput="this.nextElementSibling.value = this.value" title="{{label.title}}">
|
||||
<output>{{label.value}}</output>
|
||||
{% endif %}
|
||||
{% if label.type == "select" %}
|
||||
<select name="label_{{ label.name }}">
|
||||
<select name="label_{{ label.name }}" title="{{label.title}}">
|
||||
{% for opt in label.options %}
|
||||
<option value="{{opt}}" {% if opt == label.value %}SELECTED{% endif %}>{{opt}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class=center>
|
||||
@@ -45,8 +49,8 @@
|
||||
<button class="button_next float_right" onclick="location.href='{{ url_for('show_image', id = id_plus) }}';">→</button>
|
||||
<button class="button_continue" onclick="location.href='{{ url_for('show_image') }}';">continue</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="home"><a href="{{ url_for('main') }}" title="Back to user selection">🏠</a></div>
|
||||
</div>
|
||||
|
||||
<img class=preload src="{{ url_for('static', filename=image_plus) }}" >
|
||||
|
||||
Reference in New Issue
Block a user