Merge branch 'working' into 'main'

Merge Working 17.6.24

See merge request JanDickmann/masterproject!1
This commit is contained in:
Jan Dickmann 2024-06-17 09:20:50 +00:00
commit 4f0591d346
45 changed files with 2815 additions and 0 deletions

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
.venv/
*.pyc
__pycache__/
instance/
.pytest_cache/
.coverage
htmlcov/
dist/
build/
*.egg-info/

View File

@ -0,0 +1,10 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"liveServer.settings.multiRootWorkspaceName": "CSS Testing"
}
}

623
slaeforms/app.py Normal file
View File

@ -0,0 +1,623 @@
import sys
import json
import random
import base64
from flask import Flask, redirect, url_for, request, session, make_response, jsonify, send_from_directory
from flask import render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Integer, String, Column, Float
from datetime import datetime
import uuid
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import inspect
from sqlalchemy.orm import DeclarativeBase
import os
random_order = True
# activate environment: cd C:\Users\Jan\Google Drive\Master Stuff\Code\SLAEForms Testing\.venv\Scripts\
# then this: activate
#SETUP--------------------------------------------------
#Set up sqlalchemy
class Base(DeclarativeBase):
pass
db = SQLAlchemy(model_class=Base)
#create the app
app = Flask(__name__)
# configure the database, give it a path (it will be in the instances folder)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db"
db.init_app(app)
#set the secret key (TODO change this for final deployment)
app.secret_key = b"29fe9e8edd407c5491d4f1c05632d9fa33e26ed8734a3f5e080ebac3772a555a"
UPLOAD_FOLDER = 'uploads'
#---------testing JSON Stuff
#open the json file with the config
testconfigfile = open("singleformconfig.json", encoding='utf-8')
#convert it to dict
testconfig = json.load(testconfigfile)
testconfigfile.close()
# get the questions: Questions is a list that contains the keys of the dictionary
questions = list(testconfig)
#------------------------------------------------------------------------
# Setting up DB Models, response -> should be removed eventually and replaced by json files and automatic generation ----------------------------
# create the model for the response table
class Response(db.Model):
id = db.Column("id",db.UUID(as_uuid=True), primary_key=True, nullable=False)
user_id = db.Column("user_id",db.UUID(as_uuid=True), nullable=False)
question_title = db.Column("question_title",db.String(30))
likert_result = db.Column("likert_result",db.Integer, nullable=False)
notes = db.Column("notes",db.String(200))
date_created = db.Column("date_created",db.DateTime)
def __repr__(self) -> str:
return "<Response %r>" % self.id
# This table is always created
class User(db.Model):
user_id = db.Column("user_id",db.UUID(as_uuid=True), primary_key=True, nullable=False)
device_id = db.Column("device_id",db.UUID(as_uuid=True), nullable=False)
question_order = db.Column("question_order",db.String(60))
date_created = db.Column("date_created",db.DateTime, default=datetime.today()) # todo test if this default works
def __repr__(self) -> str:
return "<User %r>" % self.user_id
# create the table (existing tables are not overwritten)
try:
with app.app_context():
db.create_all()
except SQLAlchemyError as e:
print("Error occurred during database creation:", str(e))
# -------Testing the actual final form from json------------------------------------------------
#open the json file with the config
configfile = open("default.json", encoding='utf-8') #todo replace with other name
#convert it to dict
config = json.load(configfile)
configfile.close()
db_tables = {} # contains all dynamically created tables, key = table/classname
#TODO insert code to create all tables
def create_json_tables():
print(config)
for block_key, block_content in config.items():
if "database_table" in block_content:
print("debug")
if not (block_content["database_table"]["table_name"] in db_tables):
print("New table: \n {table}".format(table=block_content["database_table"]["table_name"]))
db_tables[block_content["database_table"]["table_name"]]=create_model_class(block_content["database_table"])
print("created table: {table}".format(table=db_tables[block_content["database_table"]["table_name"]]))
print("tables in db_tables: \n {db_tables}".format(db_tables=db_tables))
def create_model_class(schema):
class_name = schema["table_name"].capitalize()
print("creating table class: {class_name}".format(class_name=class_name))
# Define class attributes dynamically
attributes = {"__tablename__": schema["table_name"]}
# id as key and date as standard fields
attributes["id"] = Column("id",db.UUID(as_uuid=True), primary_key=True, nullable=False)
attributes["user_id"] = Column("user_id",db.UUID(as_uuid=True), primary_key=True, nullable=False)
attributes["date_created"] = db.Column("date_created",db.DateTime)
attributes["stimulus_name"] = db.Column("stimulus_name",db.String(30))
for column_name, column_info in schema["fields"].items():
if column_info["type"] == "integer":
column_type = Integer
elif column_info["type"] == "string":
column_type = String(int(column_info["size"]))
if column_info["type"] == "float":
column_type = Float
attributes[column_name] = Column(column_name,column_type, nullable=column_info["nullable"])
print("attributes of the table: ",attributes)
# Create the model class
return type(class_name, (db.Model,), attributes)
create_json_tables()
# create the table (existing tables are not overwritten)
try:
print("try to create tables")
with app.app_context():
db.create_all()
print("successfully created all tables")
except SQLAlchemyError as e:
print("Error occurred during database creation:", str(e))
@app.route("/teststart", methods=["GET", "POST"])
def teststartpage():
if not "slaeform_device_id" in session:
# If this device was not seen, remember it.
new_device_id = uuid.uuid4()
session["slaeform_device_id"] = new_device_id
session["agreed_to_tos"] = False
if request.method == "POST":
#right now if a user that has an active session goes to the startpage again and accepts tos
#it will just start another session and discard the previous session
#config is the dict with the info
#get the block names
block_names = config.keys()
session["block_names"] = list(block_names)
session["block_order"] = {} # only for templates, for each block the list of keys for stimuli
session["current_block_index"] = 0
session["current_stimulus_index"] = 0
session["current_block_name"] = session["block_names"][session["current_block_index"]]
session["number_of_blocks"] = len(session["block_names"])
current_block = config[session["current_block_name"]]
# if the block has stimuli, get how many
if "stimuli" in current_block:
session["number_of_stimuli"] = len(list(current_block["stimuli"]["list"]))
print("number of blocks: ",len(session["block_names"]))
print("Startpage post")
print(session["block_names"])
for name in block_names:
if config[name]["type"] == "TaskTemplate" and ("stimuli" in current_block):
match config[name]["stimuli"]["type"]:
case "single_video":
order = list(config[name]["stimuli"]["list"]) # order = list of simuli keys
print("order: ",order)
if config[name]["stimuli"]["order"] == "random":
random.shuffle(order) #in random order
session["block_order"][name] = order
case "empty":
order = list(config[name]["stimuli"]["list"]) # order = list of simuli keys
print("order: ",order)
session["block_order"][name] = order
if "stimuli" in current_block:
#get the name of the current stimulus
session["current_stimulus_name"] = session["block_order"][session["current_block_name"]][session["current_stimulus_index"]]
#save the new user to the database and the session
session["agreed_to_tos"] = True
new_user_id = uuid.uuid4()
session["slaeform_user_id"] = new_user_id
device_id = session["slaeform_device_id"]
user_id = new_user_id
question_order = str(session["block_order"])
date = datetime.today()
new_user = User(user_id=user_id, device_id=device_id,question_order=question_order,date_created = date) #,question_order=question_order
db.session.add(new_user)
db.session.commit()
print("block order: {order}".format(order=session["block_order"]))
try:
db.session.add(new_user)
db.session.commit()
except:
return "There was a problem while adding the user to the Database"
return redirect("/jsonform")
return render_template(
"teststartpage.html"
)
@app.route("/jsonform")
def jsonform():
#user is not yet registered and should not be here
if not "slaeform_user_id" in session:
return redirect("/teststart") #TODO replace this later with actual startpage
current_block = config[session["current_block_name"]]
print("jsonform")
print("current_block_name: {current_block_name}".format(current_block_name=session["current_block_name"]))
print("current_block_index: {current_block_order}".format(current_block_order=session["current_block_index"]))
print("current_stimulus: {current_stimulus}".format(current_stimulus=session["current_stimulus_index"]))
#print("current Blockname: {blockname}, current block index: {blockindex}, current stim index: {stimulusindex}".format(blockname=session["current_block_name"],
# blockindex=session["current_block_index"],
# stimulusindex=session["current_stimulus_index"] ) )
# erster Fall: SinglePage
if current_block["type"] == "SinglePage":
return render_template(current_block["template"])
#zweiter Fall, empty TaskTemplate
if current_block["type"] == "TaskTemplate" and current_block["stimuli"]["type"] == "empty":
current_block_order = session["block_order"][session["current_block_name"]]
current_block_stimuli = current_block["stimuli"]
current_stimulus = current_block_order[session["current_stimulus_index"]]
stimulus_type=current_block["stimuli"]["type"]
return render_template(
"standard_template.html",
stimuli=current_block_stimuli,
stimulus_type=stimulus_type,
current_stimulus=current_stimulus,
questions=current_block["questions"]
)
# ansonsten, templates:
current_block_order = session["block_order"][session["current_block_name"]]
current_block_stimuli = current_block["stimuli"]
current_stimulus = current_block_order[session["current_stimulus_index"]]
stimulus_type=current_block["stimuli"]["type"]
stimulus_configuration = current_block["stimuli"]["configuration"] # dict with the config
if current_block["type"] == "TaskTemplate":
print("case: TaskTemplate")
match stimulus_type:
case "single_video":
stimulus_configuration["video_url"] = config[session["current_block_name"]]["stimuli"]["list"][current_stimulus]
print("-------------videourl: ", stimulus_configuration["video_url"])
return render_template(
"standard_template.html",
stimuli=current_block_stimuli,
stimulus_type=stimulus_type,
current_stimulus=current_stimulus,
stimulus_configuration=stimulus_configuration,
questions=current_block["questions"]
)
return "Error, none of the Blocks triggered"
@app.route("/send_json", methods=["POST"])
def sendpage_json():
print("send_json")
# Do I need to write to a table at all?
# I can figure it out by checking if the current block has a database field, that is best
if not ("database_table" in config[session["current_block_name"]]): #it has no database field, so nothing to receive
# so just move on
print("no database table")
update_session()
return redirect("/jsonform")
# now to if it has a database field
# find out which table we need to write to
table_name = config[session["current_block_name"]]["database_table"]["table_name"]
print("Form posted: {rqform}".format(rqform=request.form))
print("Writing to table: {table_name}".format(table_name=table_name))
session_user_id = session["slaeform_user_id"]
new_id = uuid.uuid4()
date = datetime.today()
stimulus_name = session["current_stimulus_name"]
new_entry = db_tables[table_name](id=new_id,user_id = session_user_id,date_created = date,stimulus_name=stimulus_name)
for key, value in request.form.items():
print("hasattr key: ", key)
if hasattr(new_entry, key):
print("key exists: ", key)
setattr(new_entry, key, value)
print("setattr value: ", value)
print("entry: ", new_entry)
try:
db.session.add(new_entry)
db.session.commit()
except Exception as e:
print("Error occurred: {e}".format(e=str(e)))
return "There was a problem while adding the response to the Database"
# handle possible Video that was send
if 'recordedVideo' in request.files:
video = request.files['recordedVideo']
formatted_date = date.strftime("%Y.%m.%d %H-%M-%S")
print("date: ", date)
video_name = str(session_user_id) + "_" + session["current_block_name"] + "_" + session["current_stimulus_name"] + "_" + str(formatted_date) + ".webm"
path = os.path.join(UPLOAD_FOLDER, video_name)
print("path: ",path)
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
video.save(path)
# Now move to the next stimulus or block
update_session()
print("now redirect and reload the page")
return redirect("/jsonform")
def update_session():
if "stimuli" in config[session["current_block_name"]]:
# if there are stimuli in this block
if session["current_stimulus_index"] < session["number_of_stimuli"]-1:
# if there are still stimuli left, keep going through them
session["current_stimulus_index"] += 1
# set the name of the current stimulus
session["current_stimulus_name"] = session["block_order"][session["current_block_name"]][session["current_stimulus_index"]]
else:
# if there are no stimuli left..
if(session["current_block_index"] < session["number_of_blocks"]-1):
# go to next block if possible
session["current_block_index"] += 1
session["current_block_name"] = session["block_names"][session["current_block_index"]]
session["current_stimulus_index"] = 0
if "stimuli" in config[session["current_block_name"]]:
session["number_of_stimuli"] = len(list(config[session["current_block_name"]]["stimuli"]["list"]))
else:
# if there arent any stimuli, go to the next block
session["number_of_stimuli"] = 0
session["current_stimulus_index"] = 0
if(session["current_block_index"] < session["number_of_blocks"]-1):
session["current_block_index"] += 1
session["current_block_name"] = session["block_names"][session["current_block_index"]]
print("---Session updated---")
print("current_block_index / number_of_blocks: {current_block_index} / {number_of_blocks}".format(current_block_index=session["current_block_index"],number_of_blocks=session["number_of_blocks"]))
print("current_block_name: ", session["current_block_name"])
print("current_stimulus_index: ", session["current_stimulus_index"])
print("Current number_of_stimuli: ", session["number_of_stimuli"])
# Actual main code for Form etc --------------------------------------------------------------
@app.route("/video")
def videopage():
return render_template(
#"videorecorder3.html"
"myvideotemplate.html"
)
@app.route("/send_video", methods=["POST"])
def send_video():
data_url = request.json['dataUrl']
data = data_url.split(',')[1]
with open('video.webm', 'wb') as f:
f.write(base64.b64decode(data))
return jsonify({'message': 'Video saved successfully'})
@app.route("/send", methods=["POST"])
def sendpage():
session_user_id = session["slaeform_user_id"]
likert_score = request.form["likertscale"]
text_input = request.form["feedback"]
question_title = session["current_question"]
new_id = uuid.uuid4()
date = datetime.today()
print("new idea: {new_id} ".format(new_id=new_id))
new_response = Response(id=new_id,user_id = session_user_id, question_title = question_title,likert_result = likert_score,notes = text_input, date_created = date)
try:
db.session.add(new_response)
db.session.commit()
return redirect("/form")
except:
return "There was a problem while adding the response to the Database"
#TODO this is bugged rn, it will skip to the next question when refreshed.
#Move the removal of the first element to the send function to fix this
@app.route("/form", methods=["GET", "POST"])
def formpage():
#user is not yet registered and should not be here
if not "slaeform_user_id" in session:
return redirect("/start")
#TODO fill in code that determins at which question the user is
print("form starts, the sessionorder rn:")
print(session["question_order"])
if not session["question_order"]:
print("---------------question order is empty------------")
return redirect("/data")
print("pop the first element")
current_question = session["question_order"].pop(0)
session["current_question"] = current_question
print(current_question)
print("the new sessionorder rn:")
print(session["question_order"])
return render_template(
"layout2.html",
config=testconfig,
current_question = current_question,
videotype=testconfig[current_question]["type"],
video_url= testconfig[current_question]["video1"],
blocks = testconfig[current_question]["blocks"]
)
@app.route("/start", methods=["GET", "POST"])
def startpage():
if not "slaeform_device_id" in session:
# If this device was not seen, remember it.
new_device_id = uuid.uuid4()
session["slaeform_device_id"] = new_device_id
session["agreed_to_tos"] = False
if request.method == "POST":
#right now if a user that has an active session goes to the startpage again and accepts tos
#it will just start another session and discard the previous session
# get a random question order
if random_order:
order = questions
random.shuffle(order)
else:
order = questions
session["question_order"] = order
#save the new user to the database and the session
session["agreed_to_tos"] = True
new_user_id = uuid.uuid4()
session["slaeform_user_id"] = new_user_id
device_id = session["slaeform_device_id"]
user_id = new_user_id
question_order = str(order)
date = datetime.today()
new_user = User(user_id=user_id, device_id=device_id,question_order=question_order,date_created = date) #,question_order=question_order
db.session.add(new_user)
db.session.commit()
return redirect("/form")
"""
try:
db.session.add(new_user)
db.session.commit()
return redirect("/form")
except:
return "There was a problem while adding the user to the Database" """
return render_template(
"startpage.html"
)
# Database stuff------------------------------------------------------------------------------
# the contents of all tables
@app.route("/table_contents")
def table_contents():
meta = db.metadata
#meta.reflect(db.engine) # Uncomment this to also get the hidden tables, but this crashes rn
tables = meta.tables.keys()
table_contents = {}
#print(tables)
for table_name in tables:
table = meta.tables[table_name]
columns = table.columns.keys()
#print(table)
rows = db.session.query(table)
try:
rows = rows.all()
except Exception as e:
print("Error occurred: {e}".format(e=str(e)))
print("Values:\n Table = {table}\n Tabletype = {tabletype}\n".format(table=table,tabletype=type(table)))
table_contents[table_name] = {
'columns': columns,
'rows': rows
}
return render_template(
"table_contents.html",
table_contents=table_contents,
)
@app.route('/show_tables')
def show_tables():
meta = db.metadata
meta.reflect(db.engine)
tables = meta.tables
return render_template('show_tables.html', tables=tables)
# Root page -----------------------------
def has_no_empty_params(rule):
defaults = rule.defaults if rule.defaults is not None else ()
arguments = rule.arguments if rule.arguments is not None else ()
return len(defaults) >= len(arguments)
@app.route("/")
def all_links():
links = []
for rule in app.url_map.iter_rules():
# Filter out rules we can't navigate to in a browser
# and rules that require parameters
if "GET" in rule.methods and has_no_empty_params(rule):
url = url_for(rule.endpoint, **(rule.defaults or {}))
links.append((url, rule.endpoint))
return render_template("all_links.html", links=links)
# delete all tables as last link --------------------------
# Route to delete all entries
@app.route('/delete_json_tables', methods=['GET'])
def delete_json_tables():
try:
meta = db.metadata
meta.reflect(db.engine)
for table in reversed(meta.sorted_tables): # Iterate through tables in reverse order to handle foreign key constraints
if table.name != "user" and table.name != "response":
print("Deleting Table: {name}".format(name=table.name))
db.session.execute(table.delete())
db.session.commit()
return 'All entries deleted successfully'
except Exception as e:
# Rollback changes if any error occurs
db.session.rollback()
return f'Error occurred: {str(e)}', 500
finally:
# Close the session
db.session.close()
# Route to delete all entries
@app.route('/delete_all_entries', methods=['GET'])
def delete_all_entries():
# here I could also use a "drop_all()", that works jsut like create all from the creation part
# this together with the reflect could drop actually all tables
try:
meta = db.metadata
for table in reversed(meta.sorted_tables): # Iterate through tables in reverse order to handle foreign key constraints
db.session.execute(table.delete())
db.session.commit()
return 'All entries deleted successfully'
except Exception as e:
# Rollback changes if any error occurs
db.session.rollback()
return f'Error occurred: {str(e)}', 500
finally:
# Close the session
db.session.close()
if __name__ == '__main__':
app.run(debug=True)

View File

@ -0,0 +1,59 @@
# Likert Scale
https://jamesalvarez.co.uk/blog/how-to-make-responsive-likert-scales-in-css-(like-qualtrics)/
# Rootpage that shows all defined routes
https://stackoverflow.com/questions/13317536/get-list-of-all-routes-defined-in-the-flask-app
Licenses:
Open Iconic Icon Set (https://github.com/iconic/open-iconic)
The MIT License (MIT)
Copyright (c) 2014 Waybury
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Bootstrap Icons https://github.com/twbs/icons
The MIT License (MIT)
Copyright (c) 2019-2024 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

32
slaeforms/currenturls.txt Normal file
View File

@ -0,0 +1,32 @@
"/" Blank page
'/delete_all_entries' delete entries from database (not actually all, hardcoded)
"/data" Show Data of some tables (Responses and users)

21
slaeforms/defaul_wip.json Normal file
View File

@ -0,0 +1,21 @@
{
"Block_1":{
"type": "SinglePage",
"template": "startpage.html",
"database_table" :{
"TableName": "Datenschutzerklaerung",
"Fields": {
}
}
},
"Block 2":{
"type": "TaskTemplate",
"tempalte": "tempaltetest1.html",
"name" : "Block2Responses",
"database_table": {
"TableName": "Datenschutzerklaerung",
"Fields": {
}
}
}
}

230
slaeforms/default.json Normal file
View File

@ -0,0 +1,230 @@
{
"Block 4":{
"type": "TaskTemplate",
"tempalte": "standard_template.html",
"number_of_pages":"3",
"stimuli":{
"type":"single_video",
"order": "random",
"list":{
"video_1":"https://www.youtube-nocookie.com/embed/VtnwHmabyzo?si=H3rrG-GHtlSymR70",
"video_2":"https://www.youtube-nocookie.com/embed/EL76Ok4r0aQ?si=hqUm8eUUfX39NN4L",
"video_3":"https://www.youtube-nocookie.com/embed/XTMIomsXxKM?si=r2zB6OKERH6Jdpi6"
},
"configuration":{
"embed":"yt"
}
},
"questions":{
"question1":{
"type": "likert",
"name": "likertscale",
"text": "How would you rate this video?",
"required": "true",
"points":{
"p1":{
"value":"1",
"text":"I dont like it at all"
},
"p2":{
"value":"2",
"text":"I dont like it"
},
"p3":{
"value":"3",
"text":"I am indifferent"
},
"p4":{
"value":"4",
"text":"I like it"
},
"p5":{
"value":"5",
"text":"I like it a lot"
}
}
},
"question2":{
"type": "textinput",
"name": "text_feedback",
"text": "Here you can give us Feedback",
"required": "false",
"size": "250"
},
"question3":{
"type": "videoinput",
"text": "Here you can give us Feedback as video",
"name": "video_feedback",
"required": "false"
}
},
"database_table" :{
"table_name": "default_block3_test",
"fields": {
"likertscale":{
"type": "integer",
"nullable": "false"
},
"text_feedback":{
"type": "string",
"size": "250",
"nullable": "true"
}
}
}
},
"Block 3":{
"type": "TaskTemplate",
"tempalte": "standard_template.html",
"stimuli":{
"type":"empty",
"list":{
"empty_stimulus":""
}
},
"questions":{
"question1_alter":{
"type": "numberinput",
"name": "alter",
"text": "Alter:",
"required": "true",
"min": "1",
"max": "120"
},
"question2_geschlecht":{
"type": "dropdowninput",
"name": "geschlecht",
"text": "Geschlecht:",
"required": "true",
"defaulttext": "",
"points":{
"männlich":{
"value":"Männlich",
"text":"Männlich"
},
"weiblich":{
"value":"Weiblich",
"text":"Weiblich"
},
"divers":{
"value":"Divers",
"text":"Divers"
},
"keine_angabe":{
"value":"keine_angabe",
"text":"Keine Angabe"
}
}
},
"question3_hoerstatus":{
"type": "dropdowninput",
"name": "hoerstatus",
"text": "Hörstatus:",
"required": "true",
"defaulttext": "",
"points":{
"hörend":{
"value":"Hörend",
"text":"Hörend"
},
"schwerhörig":{
"value":"Schwerhörig",
"text":"Schwerhörig"
},
"gehörlos":{
"value":"Gehörlos",
"text":"Gehörlos"
}
}
},
"question4_bevorzugte_kommunikation":{
"type": "dropdowninput",
"name": "bevorzugte_kommunikation",
"text": "Bevorzugte Kommunikationsform:",
"required": "true",
"defaulttext": "",
"points":{
"gesprochen":{
"value":"Gesprochene Sprache",
"text":"Gesprochene Sprache"
},
"text":{
"value":"Text",
"text":"Text"
},
"gebärdensprache":{
"value":"Gebärdensprache",
"text":"Gebärdensprache"
}
}
},
"question5_gebeardenzeitraum":{
"type": "numberinput",
"name": "gebärdenzeitraum",
"text": "Wie viele Jahre verwenden sie schon Gebärdensprache:",
"required": "true",
"min": "0",
"max": "100",
"step": "0.5"
},
"question6_sprachkompetenz":{
"type": "numberinput",
"name": "gebärdensprachkompetenz",
"text": "Wie schätzen sie ihre Gebärdensprachkompetenz ein (1-10):",
"required": "true",
"min": "1",
"max": "10"
}
},
"database_table" :{
"table_name": "default_demographic_test",
"fields": {
"alter":{
"type": "integer",
"nullable": "false"
},
"geschlecht":{
"type": "string",
"size": "14",
"nullable": "false"
},
"hoerstatus":{
"type": "string",
"size": "14",
"nullable": "false"
},
"bevorzugte_kommunikation":{
"type": "string",
"size": "22",
"nullable": "false"
},
"gebärdenzeitraum":{
"type": "float",
"nullable": "false"
},
"gebärdensprachkompetenz":{
"type": "integer",
"nullable": "false"
}
}
}
},
"Block_1":{
"type": "SinglePage",
"template": "test_page0.html"
},
"Block_2":{
"type": "SinglePage",
"template": "test_page1.html",
"database_table" :{
"table_name": "Datenschutzerklaerung",
"fields": {
"accepted":{
"type": "string",
"size": "7",
"nullable": "false"
}
}
}
}
}

133
slaeforms/dicttablecode.py Normal file
View File

@ -0,0 +1,133 @@
import sys
import json
import random
import base64
from flask import Flask, redirect, url_for, request, session, make_response, jsonify, send_from_directory
from flask import render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Integer, String, Column
from sqlalchemy.orm import Mapped, mapped_column
from sqlalchemy.dialects.postgresql import UUID
from datetime import datetime
import uuid
from sqlalchemy.exc import SQLAlchemyError
random_order = True
# activate environment: cd C:\Users\Jan\Google Drive\Master Stuff\Code\SLAEForms Testing\.venv\Scripts\
# then this: activate
#create the app
app = Flask(__name__)
# configure the database, give it a path (it will be in the instances folder)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db"
db = SQLAlchemy(app)
#set the secret key (TODO change this for final deployment)
app.secret_key = b"29fe9e8edd407c5491d4f1c05632d9fa33e26ed8734a3f5e080ebac3772a555a"
#--------temporär der code für dict to db test
database_schema = {
"table_name": "userstest",
"id": {"type": "integer", "nullable": False},
"username": {"type": "string", "nullable": False, "size": 20},
"age": {"type": "integer", "nullable": True}
}
tablename = database_schema["table_name"].capitalize()
# test function to create tables from dicts
def create_model_class(schema):
class_name = schema["table_name"].capitalize()
# Define class attributes dynamically
attributes = {"__tablename__": schema["table_name"]}
attributes["uid"] = Column(db.UUID(as_uuid=True), primary_key=True, nullable=False)
for column_name, column_info in schema.items():
if column_name != "table_name":
if column_info["type"] == "integer":
column_type = Integer
elif column_info["type"] == "string":
column_type = String(column_info["size"])
attributes[column_name] = Column(column_type, nullable=column_info["nullable"])
# Create the model class
return type(class_name, (db.Model,), attributes)
print("creating the userstest table")
customtable = create_model_class(database_schema)
try:
with app.app_context():
print("try to create tables")
db.create_all()
except SQLAlchemyError as e:
print("Error occurred during database creation:", str(e))
@app.route("/customsendtest", methods=["POST"])
def customsendtest():
# Extract form data
form_data = request.form
# Create a new instance of the dynamically generated model
new_user = customtable()
# Assign form data to model attributes
for key, value in form_data.items():
if hasattr(new_user, key):
setattr(new_user, key, value)
new_id = uuid.uuid4()
setattr(new_user, "uid",new_id)
# Add new user to the database session and commit changes
try:
#print("new idea: {new_id} ".format(new_id=new_id))
db.session.add(new_user)
db.session.commit()
return 'Data submitted successfully!'
except SQLAlchemyError as e:
print("Error occurred during database commit:", str(e))
return 'Data not submitted successfully!'
@app.route("/customsend/<inputid>", methods=["POST"])
def customsend():
session_user_id = session["slaeform_user_id"]
likert_score = request.form["likertscale"]
text_input = request.form["feedback"]
question_title = session["current_question"]
new_id = uuid.uuid4()
date = datetime.today()
print("new idea: {new_id} ".format(new_id=new_id))
new_response = Response(id=new_id,user_id = session_user_id, question_title = question_title,likert_result = likert_score,notes = text_input, date_created = date)
try:
db.session.add(new_response)
db.session.commit()
return redirect("/form")
except:
return "There was a problem while adding the response to the Database"
# End testing-------------------------------------
# create the model for the response table
class Response(db.Model):
id = db.Column(db.UUID(as_uuid=True), primary_key=True, nullable=False)
user_id = db.Column(db.UUID(as_uuid=True), nullable=False)
question_title = db.Column(db.String(30))
likert_result = db.Column(db.Integer, nullable=False)
notes = db.Column(db.String(200))
date_created = db.Column(db.DateTime)
def __repr__(self) -> str:
return "<Response %r>" % self.id

View File

@ -0,0 +1,61 @@
{
"Block_1":{
"type": "single_page",
"html_template": "the jinja templat in the template folder that should be used",
"database_table" :{
"table_name": "Name of the table in the database",
"fields": {
"answer_1": {
"type": "string or integer",
"nullable": "true (must have a value) or false (must not have a value)",
"size" : "max number of chars in the string (only usable with type:string)"
}
}
}
},
"Block 2":{
"type": "template",
"html_template": "the jinja templat in the template folder that should be used",
"template_blocks":{
"likert_block_1":{
"type":"likert",
"scale_size":"7",
"text_left":"bad",
"text_right":"good"
}
},
"content":{
"item that should be given to the template":{
"type":"list",
"values":{
"1":"whatever",
"2":"also whatever"
}
}
},
"database_table" :{
"table_name": "Name of the table in the database",
"fields": {
"answer_1": {
"type": "string or integer",
"nullable": "true (must have a value) or false (must not have a value)",
"size" : "max number of chars in the string (only usable with type:string)"
}
}
}
},
"Block 3":{
"type": "custom_template",
"html_template": "the jinja templat in the template folder that should be used",
"database_table" :{
"table_name": "Name of the table in the database",
"fields": {
"answer_1": {
"type": "string or integer",
"nullable": "true (must have a value) or false (must not have a value)",
"size" : "max number of chars in the string (only usable with type:string)"
}
}
}
}
}

View File

@ -0,0 +1,80 @@
import sys
import json
import random
import base64
from flask import Flask, redirect, url_for, request, session, make_response, jsonify, send_from_directory
from flask import render_template
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Integer, String, Column
from datetime import datetime
import uuid
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import inspect
from sqlalchemy.orm import DeclarativeBase
random_order = True
# activate environment: cd C:\Users\Jan\Google Drive\Master Stuff\Code\SLAEForms Testing\.venv\Scripts\
# then this: activate
#SETUP--------------------------------------------------
#Set up sqlalchemy
class Base(DeclarativeBase):
pass
db = SQLAlchemy(model_class=Base)
#create the app
app = Flask(__name__)
# configure the database, give it a path (it will be in the instances folder)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db"
db.init_app(app)
#set the secret key (TODO change this for final deployment)
app.secret_key = b"29fe9e8edd407c5491d4f1c05632d9fa33e26ed8734a3f5e080ebac3772a555a"
#---------testing
#open the json file with the config
configfile = open("singleformconfig.json")
#convert it to dict
config = json.load(configfile)
configfile.close()
# get the questions: Questions is a list that contains the keys of the dictionary
questions = list(config)
@app.route("/form", methods=["GET", "POST"])
def formpage():
#user is not yet registered and should not be here
if not "slaeform_user_id" in session:
return redirect("/start")
#TODO fill in code that determins at which question the user is
print("form starts, the sessionorder rn:")
print(session["question_order"])
if not session["question_order"]:
print("---------------question order is empty------------")
return redirect("/data")
print("pop the first element")
current_question = session["question_order"].pop(0)
session["current_question"] = current_question
print(current_question)
print("the new sessionorder rn:")
print(session["question_order"])
session["question_order"] = session["question_order"]
print("has this changed sth?:")
print(session["question_order"])
return render_template(
"layout2.html",
config=config,
current_question = current_question,
videotype=config[current_question]["type"],
video_url= config[current_question]["video1"],
blocks = config[current_question]["blocks"]
)

View File

@ -0,0 +1,17 @@
{
"question 1":{
"type": "pairwise",
"video1": "https://www.youtube-nocookie.com/embed/VtnwHmabyzo?si=H3rrG-GHtlSymR70",
"video2": "https://www.youtube-nocookie.com/embed/PdvSPBdX2T0?si=8R2ZAMnPuoe50X-7"
},
"question 2":{
"type": "pairwise",
"video1": "https://www.youtube-nocookie.com/embed/EL76Ok4r0aQ?si=hqUm8eUUfX39NN4L",
"video2": "https://www.youtube-nocookie.com/embed/xIkdJeXkQIU?si=7a5WmlVtZy00JaNX"
},
"question 3":{
"type": "pairwise",
"video1": "https://www.youtube-nocookie.com/embed/XTMIomsXxKM?si=r2zB6OKERH6Jdpi6",
"video2": "https://www.youtube-nocookie.com/embed/keEKlr2dG-I?si=NZ4Q-aL56d3baz0t"
}
}

View File

@ -0,0 +1,63 @@
{
"question 1":{
"type": "single",
"video1": "https://www.youtube-nocookie.com/embed/VtnwHmabyzo?si=H3rrG-GHtlSymR70",
"blocks": {
"block1":{
"type": "likert",
"numberofpoints": "5",
"points":{
"point1": "1",
"point2": "2",
"point3": "3",
"point4": "4",
"point5": "5"
}
},
"block2":{
"type": "textinput",
"length": "200"
}
}
},
"question 2":{
"type": "single",
"video1": "https://www.youtube-nocookie.com/embed/EL76Ok4r0aQ?si=hqUm8eUUfX39NN4L",
"blocks": {
"block1":{
"type": "likert",
"numberofpoints": "5",
"points":{
"point1": "1",
"point2": "2",
"point3": "3",
"point4": "4",
"point5": "5"
}
},
"block2":{
"type": "textinput",
"length": "200"
}
}
},
"question 3":{
"type": "single",
"video1": "https://www.youtube-nocookie.com/embed/XTMIomsXxKM?si=r2zB6OKERH6Jdpi6",
"blocks": {
"block1":{
"type": "likert",
"numberofpoints": "3",
"points":{
"point1": "left",
"point2": "none",
"point3": "right"
}
},
"block2":{
"type": "textinput",
"length": "200"
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

236
slaeforms/static/styles.css Normal file
View File

@ -0,0 +1,236 @@
html {
height: 100%;
}
body {
width: 100%;
height: 100%;
margin: 0;
/*align-items: center; this will make the content a square, with edges up and bottom*/
background-color: #a4b5ff;
color: #000000;
font-family: Tahoma;
font-size: 16px;
}
.centercontent {
height: 100%;
display: flex;
justify-content: center;
}
.container {
height: 100%;
width: 80%; /* You can adjust this width as needed */
max-width: 1200px; /* Maximum width to keep it from getting too wide on large screens */
padding: 20px;
background-color: #7b8cdb; /* Just for visual differentiation */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.spacer {
clear: both;
box-sizing: border-box;
}
.buttondisable {
filter: invert(65%);
}
#submitbutton {
cursor: pointer;
padding: 10px 20px; /* Increased padding for a bigger button */
font-size: 20px; /* Increased font size */
border: none; /* Removes default border */
border-radius: 8px; /* Optional: rounds the corners of the button */
}
#submitbutton:hover {
background-color: #0056b3; /* Darker shade for hover effect */
}
.button-container {
display: flex;
justify-content: flex-end;
margin-top: 20px; /* Adjust as needed for spacing */
}
.textarea-container {
display: flex;
flex-direction: column;
align-items: center;
}
.textarea-label{
align-self: flex-start; /* Aligns the label to the start of the container */
}
video {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
}
.aspect-ratio-16-9 {
width: 100%;
aspect-ratio: 16 / 9;
}
.videocontrols {
width: 100px; /* Set a specific width for the buttons */
height: 70px; /* Set a specific height for the buttons */
background-color: #cae4ff;
border: none;
color: white;
padding: 10px 20px;
margin: 0 10px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
display: inline-flex; /* Display button contents as a flexbox */
justify-content: center; /* Center contents horizontally */
align-items: center; /* Center contents vertically */
text-align: center;
}
.videocontrols img {
max-width: 65%;
max-height: 65%;
text-align: center;
margin: auto;
width: auto; /* Make the image fill its container */
height: auto; /* Make the image fill its container */
display: block; /* Remove any extra space around the image */
}
.columncontainer {
display: flex;
}
.columnleft {
width: 100%;
border: 3px solid black;
}
.columnright {
width: 100%;
border: 3px solid black;
}
iframe {
display:block;
margin: auto;
}
.video-container {
position: relative;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
height: 0;
overflow: hidden;
max-width: 100%;
background: #000;
}
.video-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
form {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
}
.centertext {
text-align: center;
}
h2,h3 {
text-align: center;
}
.likert {
--likert-rows: 5;
margin: auto;
text-align: center;
display: inline-grid;
max-width: 900px;
grid-auto-rows: 1fr;
gap: 1em;
grid-template-columns: repeat(var(--likert-rows), minmax(0, 1fr));
}
@media only screen and (max-width: 680px) {
.likert {
grid-template-columns: minmax(0, 400px);
justify-content: center;
}
}
input, label {
display: block;
margin: 0.5rem 0;
}
.likert input {
max-width: 250px;
position: fixed;
opacity: 0;
pointer-events: none;
}
.likercontainer{
margin: 30px auto;
text-align: center;
}
.likert span {
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
box-sizing: border-box;
width: 100%;
height: 100%;
padding: 20px;
background: #dcdcdc;
transition: background .2s ease-in-out;
}
.likert input:checked + span {
outline: black auto 1px;
background: transparent;
}
.likert input:focus + span {
outline: auto 1px; /*-webkit-focus-ring-color*/
}
.likert span:hover {
background: #f1f1f1;
outline: lightgrey auto 0.5px;
}
table {
border-collapse: collapse;
margin: 20px 0;
table-layout: auto; /* Allows columns to adjust to their content */
width: auto; /* Adjusts the table width to the content */
}
th, td {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
word-wrap: break-word; /* Ensures content wraps and doesn't overflow */
}
th {
background-color: #f2f2f2;
}

View File

@ -0,0 +1,159 @@
const buttonCamera = document.getElementById('buttonCamera');
const buttonRecord = document.getElementById('buttonRecord');
const buttonDelete = document.getElementById('buttonDelete');
const videoDisplay = document.getElementById('videoDisplay');
const buttonCameraIcon = document.getElementById('buttonCameraIcon');
const buttonRecordIcon = document.getElementById('buttonRecordIcon');
const buttonDeleteIcon = document.getElementById('buttonDeleteIcon');
const videoContainer = document.getElementById('videoContainer');
var mediaRecorder = null;
var stream = null;
let recordedVideoBlob = null;
let isRecording = false;
let videoAccess = false;
// Handle form submission
document.getElementById("question_form").addEventListener("submit", function (event) {
event.preventDefault(); // Prevent the default form submission
console.log("submit button pressed");
// Create a FormData object
const formData = new FormData(event.target);
console.log("form data: ",formData);
// Append the recorded video Blob to the FormData object
if (recordedVideoBlob) {
console.log("video is available: ", recordedVideoBlob);
formData.append("recordedVideo", recordedVideoBlob, "recordedVideo.webm");
}
console.log("Data to be submitted: ",formData);
// Use fetch to send the form data and video to the backend
console.log("try to send the form and video");
fetch("/send_json", {
method: "POST",
body: formData
}).then(response => {
console.log('Response:', response);
// Check if response is a redirect (HTTP 3xx status)
if (response.redirected) {
console.log("Redirecting to:", response.url);
window.location.href = response.url; // Redirect to the new page
} else {
console.log("Non-redirect response received.");
// Handle other responses if needed
}
})
.catch(error => {
console.error('Error:', error);
// Handle errors if fetch fails
});
});
async function cameraButton() {
if (!videoAccess) {
console.log("cameraButton case videoAccess = false");
try {
stream = await navigator.mediaDevices.getUserMedia({
video: true,
});
} catch (error) {
console.log("Error: ", error);
return;
}
console.log("stream is active");
videoAccess = true;
videoDisplay.srcObject = stream;
buttonCameraIcon.src = ICON_PATHS.cameraofficon;
buttonCameraIcon.alt = "Camera-off Icon";
buttonRecord.style.display = 'inline-block';
buttonDelete.style.display = 'inline-block';
videoDisplay.style.display = 'inline-block';
videoContainer.style.display = 'inline-block';
mediaRecorder = new MediaRecorder(stream, {
mimeType: "video/webm", // could use different video format
// videoBitsPerSecond: 5000000, // Standard bitrate for video is 2,5 mbps
});
mediaRecorder.addEventListener("dataavailable", (event) => {
console.log("Data available Event triggered");
if (event.data.size > 0) {
recordedVideoBlob = event.data;
console.log("Data available:", recordedVideoBlob);
} else {
console.log("No Data available");
}
});
mediaRecorder.addEventListener("stop", () => {
if (recordedVideoBlob) {
videoDisplay.srcObject = null;
videoDisplay.src = URL.createObjectURL(recordedVideoBlob);
videoDisplay.controls = true;
videoDisplay.pause();
buttonRecordIcon.src = ICON_PATHS.recordicon;
buttonRecordIcon.alt = 'Record Icon';
isRecording = false;
console.log('Recording stopped');
console.log("Src path:", videoDisplay.src);
}
});
} else {
console.log("cameraButton case videoAccess = true");
stream.getTracks().forEach(track => track.stop()); // Stop all tracks to release the camera
stream = null;
videoAccess = false;
buttonCameraIcon.src = ICON_PATHS.cameraicon;
buttonCameraIcon.alt = "Camera Icon";
buttonRecord.style.display = 'none';
buttonDelete.style.display = 'none';
videoDisplay.style.display = 'none';
videoContainer.style.display = 'none';
}
console.log("camera button function ends");
}
function recordButton() {
console.log("recordButton pressed");
if (!isRecording) {
console.log("recordButton pressed case isRecording = false");
deleteButton();
buttonDelete.setAttribute("disabled", "");
buttonDeleteIcon.classList.add("buttondisable");
videoDisplay.srcObject = stream;
videoDisplay.src = null;
videoDisplay.controls = false;
mediaRecorder.start();
console.log("video bitrate = ",mediaRecorder.videoBitsPerSecond);
buttonRecordIcon.src = ICON_PATHS.stopicon;
buttonRecordIcon.alt = 'Stop Icon';
isRecording = true;
console.log('Recording started');
} else {
console.log("recordButton pressed case isRecording = true");
mediaRecorder.stop();
console.log("recording stops");
buttonDelete.removeAttribute("disabled");
buttonDeleteIcon.classList.remove("buttondisable");
}
}
function deleteButton() {
// TODO delete data
videoDisplay.controls = false;
videoDisplay.srcObject = stream;
recordedVideoBlob = null
buttonDelete.setAttribute("disabled", "");
buttonDeleteIcon.classList.add("buttondisable");
}

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>all links
</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>
<body>
<div class="container">
<h2>All available pages</h2>
<ul>
<p>--------------------------------------------</p>
{% for url, endpoint in links %}
{% if endpoint == "delete_all_entries" %}
<p>-</p>
<p>-</p>
<p>-</p>
<p>- make sure you really want this -</p>
{% endif %}
<li><a href="{{ url }}">{{ endpoint }}</a></li>
<p>--------------------------------------------</p>
{% endfor %}
</ul>
</div>
</body>
</html>

View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>Testform</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>
<body>
<h2>Question: {{ current_question }}</h2>
{% if (videotype == "single")%} <!-- first figure out what video type we have -->
<div class="center">
<h3>Video 1</h3>
<iframe width="560" height="315" class="center" src="{{ video_url }}" title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
</div>
{% elif (videotype == "pairwise")%}
<div class="columncontainer">
<div class="columnleft center">
<h3>Video 1</h3>
<iframe width="560" height="315" class="center" src="{{ video_url_left }}" title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
</div>
<div class="columnright">
<h3>Video 2</h3>
<iframe width="560" height="315" src="{{ video_url_right }}" title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
</div>
</div>
{% else %}
<p>Error: No Videotype could be matched or was given!</p>
{% endif %}
<form action="http://localhost:5000/send" method="post">
{% for block in config["question 1"]["blocks"] %}
{% if (config["question 1"]["blocks"][block]["type"] == "likert") %}
<div class="likercontainer">
<div class="likert">
<label><input name="likertscale" type="radio" value="1" /><span>I dont like it at all</span></label>
<label><input name="likertscale" type="radio" value="2" /><span>I dont like it</span></label>
<label><input name="likertscale" type="radio" value="3" /><span>I am indifferent</span></label>
<label><input name="likertscale" type="radio" value="4" /><span>I like it</span></label>
<label><input name="likertscale" type="radio" value="5" /><span>I like it a lot</span></label>
</div>
</div>
{% elif (config["question 1"]["blocks"][block]["type"] == "textinput") %}
<label for="feedback">Additional Feedback: </label>
<textarea id="feedback" name="feedback" rows="3" cols="30" maxlength="200"></textarea>
{% else %}
<p>Error: A block could not be loaded!</p>
{% endif %}
{% endfor %}
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
</form>
</body>
</html>

View File

@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}" />
<!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>Testform</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
</head>
<body>
<div class="container">
<h2>Gib Feedback als Video</h2>
<div class="centertext">
<button type="button" class="videocontrols" id="buttonCamera" onclick="cameraButton()">
<img id="buttonCameraIcon" src="{{ url_for('static', filename='icons/camera-icon.png')}}" alt="Camera Icon">
</button>
</div>
<div class="spacer" aria-hidden="true" style="height:30px"></div>
<div class="centertext">
<button type="button" class="videocontrols" id="buttonRecord" style="display:none" onclick="recordButton()">
<img id="buttonRecordIcon" src="{{ url_for('static', filename='icons/record-icon.png')}}" alt="Camera Icon">
</button>
<button type="button" class="videocontrols" id="buttonDelete" style="display:none" disabled
onclick="deleteButton()">
<img id="buttonDeleteIcon" src="{{ url_for('static', filename='icons/trash-icon.png')}}" alt="Delete Icon"
class="buttondisable">
</button>
</div>
<div class="spacer" aria-hidden="true" style="height:15px"></div>
<div class="centertext">
<video autoplay muted playsinline id="videoDisplay"></video>
</div>
<script>
const buttonCamera = document.getElementById('buttonCamera');
const buttonRecord = document.getElementById('buttonRecord');
const buttonDelete = document.getElementById('buttonDelete');
const videoDisplay = document.getElementById('videoDisplay');
const buttonCameraIcon = document.getElementById('buttonCameraIcon');
const buttonRecordIcon = document.getElementById('buttonRecordIcon');
//const buttonDeleteIcon = document.getElementById('buttonDeleteIcon');
var mediaRecorder = null;
var stream = null;
let recordedVideoBlob = null;
let isRecording = false;
let videoAccess = false;
async function cameraButton() {
if (!videoAccess) { //test what happens if user doesnt give the permission
console.log("cameraButton case videoAccess = false");
// maybe a try catch block?
try {
stream = await navigator.mediaDevices.getUserMedia({
video: true,
});
} catch (error) {
//TODO when this occurs the user should get a hint to reload the page or to allow it next to the url
newerror = error
console.log("Error: ", error);
return
}
console.log("stream is active");
videoAccess = true
videoDisplay.srcObject = stream
buttonCameraIcon.src = "{{ url_for('static', filename='icons/camera-off-icon.png') }}"; //todo, not sure if this works
buttonCameraIcon.alt = "Camera-off Icon";
buttonRecord.style.display = 'inline-block';
buttonDelete.style.display = 'inline-block';
videoDisplay.style.display = 'inline-block';
mediaRecorder = new MediaRecorder(stream, {
mimeType: "video/webm", //could use other video format: https://www.iana.org/assignments/media-types/media-types.xhtml#video
// I probably want to change the bitrate: https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder
});
} else {
console.log("cameraButton case videoAccess = true");
stream = null;
videoAccess = false
buttonCameraIcon.src = "{{ url_for('static', filename='icons/camera-icon.png') }}"; //todo, not sure if this works
buttonCameraIcon.alt = "Camera Icon";
buttonRecord.style.display = 'none';
buttonDelete.style.display = 'none';
videoDisplay.style.display = 'none';
}
console.log("camera button function ends");
}
mediaRecorder.addEventListener("dataavailable", (event) => {
videoRecorded.src = URL.createObjectURL(event.data);
recordedVideoBlob = event.data;
});
function recordButton() {
console.log("recordButton pressed");
if (!isRecording) {
console.log("recordButton pressed case isRecording = false");
deleteButton();
buttonDelete.setAttribute("disabled", "");
buttonDeleteIcon.classList.add("buttondisable");
mediaRecorder.start();
buttonRecordIcon.src = "{{ url_for('static', filename='icons/stop-icon.png') }}";
buttonRecordIcon.alt = 'record icon';
isRecording = true;
console.log('Recording started');
} else {
console.log("recordButton pressed case isRecording = true");
mediaRecorder.stop();
buttonRecordIcon.src = "{{ url_for('static', filename='icons/record-icon.png') }}";
buttonRecordIcon.alt = 'Stop Icon';
isRecording = false;
console.log('Recording stopped');
buttonDelete.removeAttribute("disabled");
buttonDeleteIcon.classList.remove("buttondisable");
}
}
function deleteButton() {
//todo delete data
buttonDelete.setAttribute("disabled", "");
buttonDeleteIcon.classList.add("buttondisable");
}
</script>
</div>
</body>
</html>

View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>Testform</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
</head>
<body>
<h2>Question: {{ current_question }}</h2>
{% if (videotype == "single")%} <!-- first figure out what video type we have -->
<div class="center">
<h3>Video 1</h3>
<iframe width="560" height="315" class="center" src="{{ video_url }}" title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
</div>
{% elif (videotype == "pairwise")%}
<div class="columncontainer">
<div class="columnleft center">
<h3>Video 1</h3>
<iframe width="560" height="315" class="center" src="{{ video_url_left }}" title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
</div>
<div class="columnright">
<h3>Video 2</h3>
<iframe width="560" height="315" src="{{ video_url_right }}" title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
</div>
</div>
{% else %}
<p>Error: No Videotype could be matched or was given!</p>
{% endif %}
<form action="http://localhost:5000/send" method="post">
{% for block in config["question 1"]["blocks"] %}
{% if (config["question 1"]["blocks"][block]["type"] == "likert") %}
<div class="likercontainer">
<div class="likert">
<label><input name="likertscale" type="radio" value="1" /><span>I dont like it at all</span></label>
<label><input name="likertscale" type="radio" value="2" /><span>I dont like it</span></label>
<label><input name="likertscale" type="radio" value="3" /><span>I am indifferent</span></label>
<label><input name="likertscale" type="radio" value="4" /><span>I like it</span></label>
<label><input name="likertscale" type="radio" value="5" /><span>I like it a lot</span></label>
</div>
</div>
{% elif (config["question 1"]["blocks"][block]["type"] == "textinput") %}
<label for="feedback">Additional Feedback: </label>
<textarea id="feedback" name="feedback" rows="3" cols="30" maxlength="200"></textarea>
{% else %}
<p>Error: A block could not be loaded!</p>
{% endif %}
{% endfor %}
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
</form>
</body>
</html>

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>Testform</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>
<body>
<h2>Task number 1</h2>
<div class="columncontainer">
<div class="columnleft center">
<h3>Video 1</h3>
<iframe width="560" height="315" class="center"
src="https://www.youtube-nocookie.com/embed/VtnwHmabyzo?si=H3rrG-GHtlSymR70"
title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
</div>
<div class="columnright">
<h3>Video 2</h3>
<iframe width="560" height="315"
src="https://www.youtube-nocookie.com/embed/PdvSPBdX2T0?si=8R2ZAMnPuoe50X-7"
title="YouTube video player" frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
</div>
</div>
<form action = "http://localhost:5000/" method = "post">
<div class="likercontainer">
<div class="likert">
<label><input name="likertscale" type="radio" value="1" /><span>I dont like it at all</span></label>
<label><input name="likertscale" type="radio" value="2" /><span>I dont like it</span></label>
<label><input name="likertscale" type="radio" value="3" /><span>I am indifferent</span></label>
<label><input name="likertscale" type="radio" value="4" /><span>I like it</span></label>
<label><input name="likertscale" type="radio" value="5" /><span>I like it a lot</span></label>
</div>
</div>
<label for="feedback">Additional Feedback: </label>
<textarea id="feedback" name="feedback" rows="3" cols="30" maxlength="200"></textarea>
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
</form>
</body>
</html>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>Testform</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
</head>
<body>
<form action = "http://localhost:5000/customsendtest" method = "post">
<label for="id">id:</label>
<input type="number" id="id" name="id" min="0" max="999" required>
<label for="feedback">Additional Feedback: </label>
<textarea id="username" name="username" rows="1" cols="30" maxlength="20" required></textarea>
<label for="age">age:</label>
<input type="number" id="age" name="age" min="0" max="999">
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
</form>
</body>
</html>

View File

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webcam Recorder</title>
</head>
<body>
<video id="video" width="640" height="480" autoplay></video>
<button id="startRecord">Start Recording</button>
<button id="stopRecord">Stop Recording</button>
<button id="save">Save</button>
<canvas id="canvas" style="display: none;"></canvas>
<script>
let video = document.getElementById('video');
let startRecordBtn = document.getElementById('startRecord');
let stopRecordBtn = document.getElementById('stopRecord');
let saveBtn = document.getElementById('save');
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let stream;
navigator.mediaDevices.getUserMedia({ video: true })
.then(function (mediaStream) {
video.srcObject = mediaStream;
stream = mediaStream;
})
.catch(function (err) {
console.error('Could not access the webcam: ', err);
});
startRecordBtn.onclick = function () {
console.log('Recording testprint')
stream.getVideoTracks()[0].requestRecord({ mimeType: 'video/webm' })
.then(() => console.log('Recording started'))
.catch(err => console.error('Failed to start recording: ', err));
};
stopRecordBtn.onclick = function () {
stream.getVideoTracks()[0].stopRecord()
.then(blob => {
console.log('Recording stopped');
let videoURL = URL.createObjectURL(blob);
video.src = videoURL;
})
.catch(err => console.error('Failed to stop recording: ', err));
};
saveBtn.onclick = function () {
let data = video.toDataURL('image/png');
fetch('/send_video', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ image: data })
})
.then(response => response.json())
.then(data => console.log('Video saved successfully:', data))
.catch(error => console.error('Error saving video:', error));
};
</script>
</body>
</html>

View File

@ -0,0 +1,84 @@
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webcam Recorder</title>
</head>
<body>
<video id="video" width="640" height="480" autoplay></video>
<button id="startRecord">Start Recording</button>
<button id="stopRecord">Stop Recording</button>
<button id="save">Save</button>
<canvas id="canvas" style="display: none;"></canvas>
<div id="messageArea"></div>
<script>
let video = document.getElementById('video');
let startRecordBtn = document.getElementById('startRecord');
let stopRecordBtn = document.getElementById('stopRecord');
let saveBtn = document.getElementById('save');
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let stream;
let messageArea = document.getElementById('messageArea');
showMessage('Start', 'success')
navigator.mediaDevices.getUserMedia({ video: true })
.then(function (mediaStream) {
video.srcObject = mediaStream;
stream = mediaStream;
})
.catch(function (err) {
showMessage('Could not access the webcam: ' + err, 'error');
});
startRecordBtn.onclick = function () {
showMessage('Recording started', 'success')
stream.getVideoTracks()[0].requestRecord({ mimeType: 'video/webm' })
.then(() => showMessage('Recording started', 'success'))
.catch(err => showMessage('Failed to start recording: ' + err, 'error'));
};
stopRecordBtn.onclick = function () {
stream.getVideoTracks()[0].stopRecord()
.then(blob => {
showMessage('Recording stopped', 'success');
let videoURL = URL.createObjectURL(blob);
video.src = videoURL;
})
.catch(err => showMessage('Failed to stop recording: ' + err, 'error'));
};
saveBtn.onclick = function () {
let data = canvas.toDataURL('image/png');
fetch('/send_video', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ image: data })
})
.then(response => response.json())
.then(data => showMessage('Video saved successfully', 'success'))
.catch(error => showMessage('Error saving video: ' + error, 'error'));
};
function showMessage(message, type) {
let messageDiv = document.createElement('div');
messageDiv.textContent = message;
messageDiv.classList.add(type);
messageArea.appendChild(messageDiv);
}
</script>
<style>
.success {
color: green;
}
.error {
color: red;
}
</style>
</body>
</html>

View File

@ -0,0 +1,177 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>
Videorecorder test
</title>
</head>
<body>
<h1>Videorecordertest</h1>
<div>
<button type="button" id="buttonStart">Start</button>
<button type="button" id="buttonStop" disabled>Stop</button>
<button type="button" id="buttonUploadBlob" disabled>
Upload (Blob)
</button>
<button type="button" id="buttonUploadDataURL" disabled>
Upload (DataURL)
</button>
<div id="messageArea"></div>
</div>
<div>
<video autoplay muted playsinline width="1280" height="720" id="videoLive"></video>
</div>
<div>
<video controls playsinline width="1280" height="720" id="videoRecorded"></video>
</div>
<script>
function showMessage(message, type) {
let messageDiv = document.createElement('div');
messageDiv.textContent = message;
messageDiv.classList.add(type);
messageArea.appendChild(messageDiv);
}
async function main() {
const buttonStart = document.querySelector("#buttonStart");
const buttonStop = document.querySelector("#buttonStop");
const buttonUploadBlob = document.querySelector("#buttonUploadBlob");
const buttonUploadDataURL = document.querySelector(
"#buttonUploadDataURL"
);
const videoLive = document.querySelector("#videoLive");
const videoRecorded = document.querySelector("#videoRecorded");
let recordedVideoBlob = null;
let messageArea = document.getElementById('messageArea');
const search = new URLSearchParams({
extname: ".webm",
}).toString();
// this string is literally jsut "extname=.webm", WHY?
showMessage('const search:', 'success')
showMessage(search, 'success')
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
});
videoLive.srcObject = stream;
const mediaRecorder = new MediaRecorder(stream, {
mimeType: "video/webm", //could use other video format: https://www.iana.org/assignments/media-types/media-types.xhtml#video
// I probably want to influence the bitrate: https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder
});
buttonStart.addEventListener("click", () => {
mediaRecorder.start();
buttonStart.setAttribute("disabled", "");
buttonStop.removeAttribute("disabled");
});
buttonStop.addEventListener("click", () => {
mediaRecorder.stop();
buttonStart.removeAttribute("disabled");
buttonStop.setAttribute("disabled", "");
buttonUploadBlob.removeAttribute("disabled");
buttonUploadDataURL.removeAttribute("disabled");
});
mediaRecorder.addEventListener("dataavailable", (event) => {
videoRecorded.src = URL.createObjectURL(event.data);
recordedVideoBlob = event.data;
});
buttonUploadBlob.addEventListener("click", async () => {
try {
const search = new URLSearchParams({
extname: ".webm",
}).toString();
// this string is literally just "extname=.webm", WHY?
const url = "/api/upload/blob?" + search;
console.log(recordedVideoBlob);
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/octet-stream",
},
body: recordedVideoBlob,
});
if (response.status !== 201) {
console.warn(response.status);
console.warn(await response.text());
}
} catch (err) {
console.error(err);
}
});
buttonUploadDataURL.addEventListener("click", async () => {
try {
const url = "/send_video";
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json; charset=UTF-8",
},
body: JSON.stringify({
extname: ".webm",
dataUrl: await convertBlob(recordedVideoBlob),
}),
});
if (response.status !== 201) {
console.warn(response.status);
console.warn(await response.text());
}
} catch (err) {
console.error(err);
}
});
}
async function convertBlob(blob) {
return await new Promise((resolve, reject) => {
const fileReader = new FileReader();
const subscribe = () => {
fileReader.addEventListener("abort", onAbort);
fileReader.addEventListener("error", onError);
fileReader.addEventListener("load", onLoad);
};
const unsubscribe = () => {
fileReader.removeEventListener("abort", onAbort);
fileReader.removeEventListener("error", onError);
fileReader.removeEventListener("load", onLoad);
};
const onAbort = () => {
unsubscribe();
reject(new Error("abort"));
};
const onError = (event) => {
unsubscribe();
reject(event.target.error);
};
const onLoad = (event) => {
unsubscribe();
resolve(event.target.result);
};
subscribe();
fileReader.readAsDataURL(blob);
});
}
main();
</script>
</body>
</html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>Database Tables</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}" />
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
</head>
<body>
<h1>Database Tables</h1>
<ul>
{% for key, value in tables.items() %}
<li>
<strong>{{ key }}</strong>:<br>
<ul>
{% for column in value.columns %}
<li>{{ column.name }} - {{ column.type }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
</body>
</html>
<!DOCTYPE html>
<html>

View File

@ -0,0 +1,181 @@
<!DOCTYPE html>
<html>
{% macro single_video(video_url, embed="yt", title="",width="560", height="315", class="center", code="<p>No code given
</p>") -%}
{% if (embed == "yt") %}
<div class={{center}}>
{% if (title != "") %}
<h3>{{title}}</h3>
{% endif %}
<div class="video-container">
<iframe width={{width}} height={{height}} class="center" src="{{ video_url }}" title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen></iframe>
</div>
</div>
{% else %}
{{code}}
{% endif %}
{%- endmacro %}
{% macro required(question) -%}
{% if (question["required"] == "true") %}
required
{% endif %}
{%- endmacro %}
{% macro inputconfig(question) -%}
{% if ("min" in question) %}
min={{question["min"]}}
{% endif %}
{% if ("max" in question) %}
max={{question["max"]}}
{% endif %}
{% if ("step" in question) %}
step={{question["step"]}}
{% endif %}
{%- endmacro %}
{% macro input(name, value='', type='text', size=20) -%}
<input type="{{ type }}" name="{{ name }}" value="{{
value|e }}" size="{{ size }}">
{%- endmacro %}
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}" />
<!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>Testform</title>
<link rel=" shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<script>const ICON_PATHS = {
cameraofficon: "{{ url_for('static', filename='icons/camera-off-icon.png') }}",
cameraicon: "{{ url_for('static', filename='icons/camera-icon.png') }}",
stopicon: "{{ url_for('static', filename='icons/stop-icon.png') }}",
recordicon: "{{ url_for('static', filename='icons/record-icon.png') }}"
};</script>
</head>
<body>
<div class="centercontent">
<div class="container">
<h2>Stimulus part</h2>
{% if (stimulus_type == "single_video") %}
{{ single_video(**stimulus_configuration) }}
{% elif (stimulus_type == "empty") %}
{% else %}
<p>Error: Block {{ stimulus["type"] }} could not be loaded!</p>
{% endif %}
<h2>Questions</h2>
<form id="question_form" action="http://localhost:5000/send_json" method="post">
{% for question in questions %}
{% if (questions[question]["type"] == "likert") %}
<div class="likercontainer">
<div class="likert">
{{ questions[question]['text']}}
{% for point in questions[question]["points"] %}
<label>
<input name="{{ questions[question]['name']}}" type="radio" id="{{ questions[question]['name'] }}"
value="{{ questions[question]['points'][point]['value'] }}"
{{required(questions[question])}} /><span>{{ questions[question]['points'][point]['text']
}}</span>
</label>
{% endfor %}
</div>
</div>
{% elif (questions[question]["type"] == "textinput") %}
<div class="textarea-container">
<label class="textarea-label">
{{ questions[question]['text']}}
<textarea id="{{ questions[question]['name'] }}" name="{{ questions[question]['name'] }}" rows="6"
cols="60" maxlength="{{ questions[question]['size'] }}"
{{required(questions[question])}}></textarea>
</label>
</div>
{% elif (questions[question]["type"] == "dateinput") %}
<label>
{{ questions[question]['text']}}
<input type="date" name="{{ questions[question]['name']}}" id="{{ questions[question]['name'] }}" {{required(questions[question])}}>
</label>
{% elif (questions[question]["type"] == "numberinput") %}
<label>
{{ questions[question]['text']}}
<input type="number" name="{{ questions[question]['name']}}" id="{{ questions[question]['name'] }}" {{inputconfig(questions[question])}} {{required(questions[question])}}>
</label>
{% elif (questions[question]["type"] == "emailinput") %}
<label>
{{ questions[question]['text']}}
<input type="email" name="{{ questions[question]['name']}}" id="{{ questions[question]['name'] }}" {{required(questions[question])}}>
</label>
{% elif (questions[question]["type"] == "dropdowninput") %}
<label>
{{ questions[question]['text']}}
<select name="{{ questions[question]['name']}}" {{required(questions[question])}}>
<option value="" disabled selected>{{ questions[question]['defaulttext']}}</option>
{% for point in questions[question]["points"] %}
<option name="{{ point }}" id="{{ point }}"
value="{{ questions[question]['points'][point]['value'] }}"
{{required(questions[question])}}><span>{{ questions[question]['points'][point]['text']
}}</span></option>
{% endfor %}
</select>
</label>
{% elif (questions[question]["type"] == "videoinput") %}
<h2>Gib Feedback als Video</h2>
<div class="centertext">
{{ questions[question]['text']}}
<button type="button" class="videocontrols" id="buttonCamera" onclick="cameraButton()">
<img id="buttonCameraIcon" src="{{ url_for('static', filename='icons/camera-icon.png')}}"
alt="Camera Icon">
</button>
</div>
<div class="spacer" aria-hidden="true" style="height:30px"></div>
<div class="centertext">
<button type="button" class="videocontrols" id="buttonRecord" style="display:none"
onclick="recordButton()">
<img id="buttonRecordIcon" src="{{ url_for('static', filename='icons/record-icon.png')}}"
alt="Camera Icon">
</button>
<button type="button" class="videocontrols" id="buttonDelete" style="display:none" disabled
onclick="deleteButton()">
<img id="buttonDeleteIcon" src="{{ url_for('static', filename='icons/trash-icon.png')}}"
alt="Delete Icon" class="buttondisable">
</button>
</div>
<div class="spacer" aria-hidden="true" style="height:15px"></div>
<div id="videoContainer" class="centertext, aspect-ratio-16-9" style="display:none">
<video autoplay muted playsinline id="videoDisplay"></video>
</div>
<script src="{{ url_for('static', filename='videoscript.js')}}">
</script>
{% else %}
<p>Error: Block {{config["question 1"]["blocks"][block]["type"]}} could not be loaded!</p>
{% endif %}
{% endfor %}
<div class="button-container">
<p><input id="submitbutton" type="submit" value="submit" ; /></p>
<!-- TODO maybe I want to use this instead: <button id="submitbutton" type="submit">Submit</button> -->
</div>
</form>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>DGS Avatar Study</title>
</head>
<body>
<div class="centercontent">
<div class="container">
<h2>Hello! Thank you for participating in our study!</h2>
<form action="http://localhost:5000/start" method="post">
<label for="terms-and-conditions">
<input class="inline" id="terms-and-conditions" type="checkbox" required name="terms-and-conditions" /> I accept the +terms and conditions</a>
</label>
<p><input id="submitbutton" type = "submit" value = "submit" /></p>
</form>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,33 @@
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}" />
<title>Database Tables</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
</head>
<body>
<h1>Database Tables</h1>
{% for table_name, contents in table_contents.items() %}
<h2>{{table_name}}</h2>
<table>
<thead>
<tr>
{% for column in contents['columns'] %}
<th>{{column}}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in contents['rows'] %}
<tr>
{% for column in contents['columns'] %}
<td>{{row[column]}}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
</body>
</html>

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>Test Page 0 - Nothing</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>
<body>
<div class="centercontent"></div>
<div class="container">
<p>This is just a test page for the single page option of the json configuration, but without something to submit</p>
<form action="http://localhost:5000/send_json" method="post">
<input type="hidden" name="submittedString" value="Hello, backend!">
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
</form>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>Test Page 1 - Datenschutzerklaerung</title>
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
</head>
<body>
<div class="centercontent">
<div class="container">
<p>This is just a test page for the single page option of the json configuration</p>
<form action="http://localhost:5000/send_json" method="post">
<input
type="checkbox"
id="accepted"
name="accepted"
value="checked" />
<label for="accepted">Akzeptieren sie die Datenschutzerklaerung</label>
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
</form>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
<title>DGS Avatar Study</title>
</head>
<body>
<div class="centercontent">
<div class="container">
<h2>Hello! Thank you for participating in our study!</h2>
<form action="http://localhost:5000/teststart" method="post">
<label for="terms-and-conditions">
<input class="inline" id="terms-and-conditions" type="checkbox" required name="terms-and-conditions" /> I accept the +terms and conditions</a>
</label>
<p><input id="submitbutton" type = "submit" value = "submit" /></p>
</form>
</div>
</div>
</body>
</html>

38
slaeforms/test.json Normal file
View File

@ -0,0 +1,38 @@
{
"Block 1":{
"type": "SinglePage",
"template": "startpage.html"
},
"Block 2":{
"type": "TaskTemplate",
"tempalte": "tempaltetest1.html",
"name" : "Block2Responses",
"databasetable": {
"question_title" : {
"type":"likert"
}
}
},
"question 3":{
"type": "single",
"video1": "https://www.youtube-nocookie.com/embed/XTMIomsXxKM?si=r2zB6OKERH6Jdpi6",
"scales": {
"block1":{
"type": "likert",
"numberofpoints": "3",
"points":{
"point1": "left",
"point2": "none",
"point3": "right"
}
},
"block2":{
"type": "textinput",
"length": "200"
},
"block3":{
"type": "video"
}
}
}
}