Merge branch 'working' into 'main'
Merge Working 17.6.24 See merge request JanDickmann/masterproject!1
14
.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
.venv/
|
||||||
|
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
instance/
|
||||||
|
|
||||||
|
.pytest_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.egg-info/
|
10
SLAEForms Testing.code-workspace
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"liveServer.settings.multiRootWorkspaceName": "CSS Testing"
|
||||||
|
}
|
||||||
|
}
|
623
slaeforms/app.py
Normal 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)
|
59
slaeforms/attributions.txt
Normal 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
@ -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
@ -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
@ -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
@ -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
|
61
slaeforms/documentation.json
Normal 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)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
slaeforms/jsonparsecode.py
Normal 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"]
|
||||||
|
)
|
17
slaeforms/pairwiseformconfig.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
63
slaeforms/singleformconfig.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
slaeforms/static/icons/camera-icon.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
slaeforms/static/icons/camera-off-icon.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
slaeforms/static/icons/camera-reels-icon.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
slaeforms/static/icons/check-icon.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
slaeforms/static/icons/favicon.ico
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
slaeforms/static/icons/record-icon.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
slaeforms/static/icons/stop-icon.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
slaeforms/static/icons/trash-icon.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
slaeforms/static/icons/x-icon.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
236
slaeforms/static/styles.css
Normal 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;
|
||||||
|
}
|
159
slaeforms/static/videoscript.js
Normal 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");
|
||||||
|
}
|
31
slaeforms/templates/all_links.html
Normal 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>
|
64
slaeforms/templates/layout2.html
Normal 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>
|
142
slaeforms/templates/myvideotemplate.html
Normal 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>
|
64
slaeforms/templates/old_unused/Study1.html
Normal 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>
|
49
slaeforms/templates/old_unused/layout1.html
Normal 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>
|
24
slaeforms/templates/old_unused/templatetest1.html
Normal 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>
|
65
slaeforms/templates/old_unused/videorecorder.html
Normal 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>
|
84
slaeforms/templates/old_unused/videorecorder2.html
Normal 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>
|
177
slaeforms/templates/old_unused/videorecorder3.html
Normal 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>
|
26
slaeforms/templates/show_tables.html
Normal 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>
|
181
slaeforms/templates/standard_template.html
Normal 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>
|
25
slaeforms/templates/startpage.html
Normal 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>
|
33
slaeforms/templates/table_contents.html
Normal 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>
|
22
slaeforms/templates/test_page0.html
Normal 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>
|
27
slaeforms/templates/test_page1.html
Normal 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>
|
25
slaeforms/templates/teststartpage.html
Normal 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
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|