Merge branch 'Laptop' into 'main'
Laptop merge 28.6. See merge request JanDickmann/masterproject!2
This commit is contained in:
commit
f4bb2959e6
4
.gitignore
vendored
4
.gitignore
vendored
@ -11,4 +11,6 @@ htmlcov/
|
|||||||
|
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
|
||||||
|
*.code-workspace
|
418
slaeforms/app.py
418
slaeforms/app.py
@ -2,16 +2,19 @@ import sys
|
|||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import base64
|
import base64
|
||||||
from flask import Flask, redirect, url_for, request, session, make_response, jsonify, send_from_directory
|
from flask import Flask, redirect, url_for, request, session, make_response, jsonify, send_file
|
||||||
from flask import render_template
|
from flask import render_template
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from sqlalchemy import Integer, String, Column, Float
|
from sqlalchemy import Integer, String, Column, Float
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import uuid
|
import uuid
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy import inspect
|
from sqlalchemy import select, join
|
||||||
from sqlalchemy.orm import DeclarativeBase
|
from sqlalchemy.orm import DeclarativeBase
|
||||||
|
from flask_wtf.csrf import CSRFProtect
|
||||||
import os
|
import os
|
||||||
|
import csv
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
random_order = True
|
random_order = True
|
||||||
# activate environment: cd C:\Users\Jan\Google Drive\Master Stuff\Code\SLAEForms Testing\.venv\Scripts\
|
# activate environment: cd C:\Users\Jan\Google Drive\Master Stuff\Code\SLAEForms Testing\.venv\Scripts\
|
||||||
@ -29,50 +32,29 @@ db = SQLAlchemy(model_class=Base)
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
# configure the database, give it a path (it will be in the instances folder)
|
# configure the database, give it a path (it will be in the instances folder)
|
||||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db"
|
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db"
|
||||||
|
app.config["PERMANENT_SESSION_LIFETIME"] = 10800 #3 Stunden, 10800 sekunden
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
|
|
||||||
#set the secret key (TODO change this for final deployment)
|
#set the secret key (TODO change this for final deployment)
|
||||||
app.secret_key = b"29fe9e8edd407c5491d4f1c05632d9fa33e26ed8734a3f5e080ebac3772a555a"
|
app.secret_key = b"29fe9e8edd407c5491d4f1c05632d9fa33e26ed8734a3f5e080ebac3772a555a"
|
||||||
|
|
||||||
UPLOAD_FOLDER = 'uploads'
|
UPLOAD_FOLDER = 'uploads'
|
||||||
|
EXPORT_FOLDER = 'exports'
|
||||||
|
PASSWORD = '#1ACGmsjd'
|
||||||
|
|
||||||
|
#csrf = CSRFProtect(app) #enable CSRF protection globally
|
||||||
|
|
||||||
|
|
||||||
#---------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 ----------------------------
|
# Setting up DB Models
|
||||||
|
|
||||||
# create the model for the response table
|
# This table is always created, tracks all users
|
||||||
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):
|
class User(db.Model):
|
||||||
user_id = db.Column("user_id",db.UUID(as_uuid=True), primary_key=True, nullable=False)
|
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)
|
device_id = db.Column("device_id",db.UUID(as_uuid=True), nullable=False)
|
||||||
question_order = db.Column("question_order",db.String(60))
|
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
|
date_created = db.Column("date_created",db.DateTime, default=datetime.today()) # todo test if this default works
|
||||||
|
form_completed = db.Column("form_completed",db.Boolean, default=False)
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return "<User %r>" % self.user_id
|
return "<User %r>" % self.user_id
|
||||||
|
|
||||||
@ -84,13 +66,8 @@ except SQLAlchemyError as e:
|
|||||||
print("Error occurred during database creation:", str(e))
|
print("Error occurred during database creation:", str(e))
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
# -------Testing the actual final form from json------------------------------------------------
|
#open, parse and execute json file
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#open the json file with the config
|
#open the json file with the config
|
||||||
configfile = open("default.json", encoding='utf-8') #todo replace with other name
|
configfile = open("default.json", encoding='utf-8') #todo replace with other name
|
||||||
@ -102,10 +79,9 @@ db_tables = {} # contains all dynamically created tables, key = table/classname
|
|||||||
|
|
||||||
#TODO insert code to create all tables
|
#TODO insert code to create all tables
|
||||||
def create_json_tables():
|
def create_json_tables():
|
||||||
print(config)
|
#print(config)
|
||||||
for block_key, block_content in config.items():
|
for block_key, block_content in config.items():
|
||||||
if "database_table" in block_content:
|
if "database_table" in block_content:
|
||||||
print("debug")
|
|
||||||
if not (block_content["database_table"]["table_name"] in db_tables):
|
if not (block_content["database_table"]["table_name"] in db_tables):
|
||||||
print("New table: \n {table}".format(table=block_content["database_table"]["table_name"]))
|
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"])
|
db_tables[block_content["database_table"]["table_name"]]=create_model_class(block_content["database_table"])
|
||||||
@ -115,14 +91,14 @@ def create_json_tables():
|
|||||||
|
|
||||||
def create_model_class(schema):
|
def create_model_class(schema):
|
||||||
class_name = schema["table_name"].capitalize()
|
class_name = schema["table_name"].capitalize()
|
||||||
print("creating table class: {class_name}".format(class_name=class_name))
|
#print("creating table class: {class_name}".format(class_name=class_name))
|
||||||
|
|
||||||
# Define class attributes dynamically
|
# Define class attributes dynamically
|
||||||
attributes = {"__tablename__": schema["table_name"]}
|
attributes = {"__tablename__": schema["table_name"]}
|
||||||
|
|
||||||
# id as key and date as standard fields
|
# id as key and date as standard fields
|
||||||
attributes["id"] = Column("id",db.UUID(as_uuid=True), primary_key=True, nullable=False)
|
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["user_id"] = Column("user_id",db.UUID(as_uuid=True), nullable=False)
|
||||||
attributes["date_created"] = db.Column("date_created",db.DateTime)
|
attributes["date_created"] = db.Column("date_created",db.DateTime)
|
||||||
attributes["stimulus_name"] = db.Column("stimulus_name",db.String(30))
|
attributes["stimulus_name"] = db.Column("stimulus_name",db.String(30))
|
||||||
|
|
||||||
@ -137,7 +113,7 @@ def create_model_class(schema):
|
|||||||
|
|
||||||
attributes[column_name] = Column(column_name,column_type, nullable=column_info["nullable"])
|
attributes[column_name] = Column(column_name,column_type, nullable=column_info["nullable"])
|
||||||
|
|
||||||
print("attributes of the table: ",attributes)
|
#print("attributes of the table: ",attributes)
|
||||||
# Create the model class
|
# Create the model class
|
||||||
return type(class_name, (db.Model,), attributes)
|
return type(class_name, (db.Model,), attributes)
|
||||||
|
|
||||||
@ -153,13 +129,27 @@ try:
|
|||||||
except SQLAlchemyError as e:
|
except SQLAlchemyError as e:
|
||||||
print("Error occurred during database creation:", str(e))
|
print("Error occurred during database creation:", str(e))
|
||||||
|
|
||||||
@app.route("/teststart", methods=["GET", "POST"])
|
|
||||||
def teststartpage():
|
#------------------------------------------------------------------------------
|
||||||
|
#actual page logic with start, form and send
|
||||||
|
|
||||||
|
#@app.route("/popuptest", methods=["GET"])
|
||||||
|
def popuptest():
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"popuptest.html"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/start", methods=["GET", "POST"])
|
||||||
|
def startpage():
|
||||||
|
session.permanent = False
|
||||||
if not "slaeform_device_id" in session:
|
if not "slaeform_device_id" in session:
|
||||||
# If this device was not seen, remember it.
|
# If this device was not seen, remember it.
|
||||||
new_device_id = uuid.uuid4()
|
new_device_id = uuid.uuid4()
|
||||||
session["slaeform_device_id"] = new_device_id
|
session["slaeform_device_id"] = new_device_id
|
||||||
session["agreed_to_tos"] = False
|
session["agreed_to_tos"] = False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
@ -176,11 +166,14 @@ def teststartpage():
|
|||||||
session["current_block_name"] = session["block_names"][session["current_block_index"]]
|
session["current_block_name"] = session["block_names"][session["current_block_index"]]
|
||||||
session["number_of_blocks"] = len(session["block_names"])
|
session["number_of_blocks"] = len(session["block_names"])
|
||||||
current_block = config[session["current_block_name"]]
|
current_block = config[session["current_block_name"]]
|
||||||
|
session["number_of_stimuli"] = 0
|
||||||
|
|
||||||
# if the block has stimuli, get how many
|
# if the block has stimuli, get how many
|
||||||
if "stimuli" in current_block:
|
if "stimuli" in current_block:
|
||||||
session["number_of_stimuli"] = len(list(current_block["stimuli"]["list"]))
|
if current_block["stimuli"]["type"] == "single_video" or current_block["stimuli"]["type"] == "empty":
|
||||||
|
session["number_of_stimuli"] = len(list(current_block["stimuli"]["list"]))
|
||||||
|
elif current_block["stimuli"]["type"] == "double_video":
|
||||||
|
session["number_of_stimuli"] = len(list(current_block["stimuli"]["list_1"]))
|
||||||
|
|
||||||
print("number of blocks: ",len(session["block_names"]))
|
print("number of blocks: ",len(session["block_names"]))
|
||||||
|
|
||||||
@ -188,6 +181,7 @@ def teststartpage():
|
|||||||
print(session["block_names"])
|
print(session["block_names"])
|
||||||
|
|
||||||
for name in block_names:
|
for name in block_names:
|
||||||
|
print("block: ",name)
|
||||||
if config[name]["type"] == "TaskTemplate" and ("stimuli" in current_block):
|
if config[name]["type"] == "TaskTemplate" and ("stimuli" in current_block):
|
||||||
match config[name]["stimuli"]["type"]:
|
match config[name]["stimuli"]["type"]:
|
||||||
case "single_video":
|
case "single_video":
|
||||||
@ -196,6 +190,15 @@ def teststartpage():
|
|||||||
if config[name]["stimuli"]["order"] == "random":
|
if config[name]["stimuli"]["order"] == "random":
|
||||||
random.shuffle(order) #in random order
|
random.shuffle(order) #in random order
|
||||||
session["block_order"][name] = order
|
session["block_order"][name] = order
|
||||||
|
case "double_video":
|
||||||
|
order = [] # order = list of stimuli keys
|
||||||
|
list_1 = list(config[name]["stimuli"]["list_1"])
|
||||||
|
list_2 = list(config[name]["stimuli"]["list_2"])
|
||||||
|
for i in range(len(list(config[name]["stimuli"]["list_1"]))):
|
||||||
|
order.append((list_1[i], list_2[i]))
|
||||||
|
print("order: ",order)
|
||||||
|
#TODO random is not implemented here
|
||||||
|
session["block_order"][name] = order
|
||||||
case "empty":
|
case "empty":
|
||||||
order = list(config[name]["stimuli"]["list"]) # order = list of simuli keys
|
order = list(config[name]["stimuli"]["list"]) # order = list of simuli keys
|
||||||
print("order: ",order)
|
print("order: ",order)
|
||||||
@ -214,11 +217,12 @@ def teststartpage():
|
|||||||
user_id = new_user_id
|
user_id = new_user_id
|
||||||
question_order = str(session["block_order"])
|
question_order = str(session["block_order"])
|
||||||
date = datetime.today()
|
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
|
new_user = User(user_id=user_id, device_id=device_id,question_order=question_order,date_created = date,form_completed=False) #,question_order=question_order
|
||||||
|
|
||||||
db.session.add(new_user)
|
db.session.add(new_user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
print("block order: {order}".format(order=session["block_order"]))
|
print("block order: {order}".format(order=session["block_order"]))
|
||||||
try:
|
try:
|
||||||
db.session.add(new_user)
|
db.session.add(new_user)
|
||||||
@ -226,22 +230,27 @@ def teststartpage():
|
|||||||
|
|
||||||
except:
|
except:
|
||||||
return "There was a problem while adding the user to the Database"
|
return "There was a problem while adding the user to the Database"
|
||||||
return redirect("/jsonform")
|
return redirect("/form")
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"teststartpage.html"
|
"teststartpage.html"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@app.route("/endpage")
|
||||||
|
def endpage():
|
||||||
|
print("Form is done, sent to endpage")
|
||||||
|
return render_template("endpage.html")
|
||||||
|
|
||||||
@app.route("/jsonform")
|
|
||||||
def jsonform():
|
@app.route("/form")
|
||||||
|
def form():
|
||||||
#user is not yet registered and should not be here
|
#user is not yet registered and should not be here
|
||||||
if not "slaeform_user_id" in session:
|
if not "slaeform_user_id" in session:
|
||||||
return redirect("/teststart") #TODO replace this later with actual startpage
|
return redirect("/start") #TODO replace this later with actual startpage
|
||||||
|
|
||||||
|
|
||||||
current_block = config[session["current_block_name"]]
|
current_block = config[session["current_block_name"]]
|
||||||
print("jsonform")
|
print("form")
|
||||||
print("current_block_name: {current_block_name}".format(current_block_name=session["current_block_name"]))
|
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_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_stimulus: {current_stimulus}".format(current_stimulus=session["current_stimulus_index"]))
|
||||||
@ -249,10 +258,14 @@ def jsonform():
|
|||||||
#print("current Blockname: {blockname}, current block index: {blockindex}, current stim index: {stimulusindex}".format(blockname=session["current_block_name"],
|
#print("current Blockname: {blockname}, current block index: {blockindex}, current stim index: {stimulusindex}".format(blockname=session["current_block_name"],
|
||||||
# blockindex=session["current_block_index"],
|
# blockindex=session["current_block_index"],
|
||||||
# stimulusindex=session["current_stimulus_index"] ) )
|
# stimulusindex=session["current_stimulus_index"] ) )
|
||||||
|
infovideo = None
|
||||||
|
if "infovideo" in current_block:
|
||||||
|
infovideo = current_block["infovideo"]
|
||||||
|
|
||||||
# erster Fall: SinglePage
|
# erster Fall: SinglePage
|
||||||
if current_block["type"] == "SinglePage":
|
if current_block["type"] == "SinglePage":
|
||||||
return render_template(current_block["template"])
|
return render_template(current_block["template"])
|
||||||
|
|
||||||
|
|
||||||
#zweiter Fall, empty TaskTemplate
|
#zweiter Fall, empty TaskTemplate
|
||||||
if current_block["type"] == "TaskTemplate" and current_block["stimuli"]["type"] == "empty":
|
if current_block["type"] == "TaskTemplate" and current_block["stimuli"]["type"] == "empty":
|
||||||
@ -265,7 +278,8 @@ def jsonform():
|
|||||||
stimuli=current_block_stimuli,
|
stimuli=current_block_stimuli,
|
||||||
stimulus_type=stimulus_type,
|
stimulus_type=stimulus_type,
|
||||||
current_stimulus=current_stimulus,
|
current_stimulus=current_stimulus,
|
||||||
questions=current_block["questions"]
|
questions=current_block["questions"],
|
||||||
|
infovideo=infovideo
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -284,13 +298,18 @@ def jsonform():
|
|||||||
case "single_video":
|
case "single_video":
|
||||||
stimulus_configuration["video_url"] = config[session["current_block_name"]]["stimuli"]["list"][current_stimulus]
|
stimulus_configuration["video_url"] = config[session["current_block_name"]]["stimuli"]["list"][current_stimulus]
|
||||||
print("-------------videourl: ", stimulus_configuration["video_url"])
|
print("-------------videourl: ", stimulus_configuration["video_url"])
|
||||||
|
case "double_video":
|
||||||
|
stimulus_configuration["video_url1"] = config[session["current_block_name"]]["stimuli"]["list_1"][current_stimulus[0]]
|
||||||
|
stimulus_configuration["video_url2"] = config[session["current_block_name"]]["stimuli"]["list_2"][current_stimulus[1]]
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"standard_template.html",
|
"standard_template.html",
|
||||||
stimuli=current_block_stimuli,
|
stimuli=current_block_stimuli,
|
||||||
stimulus_type=stimulus_type,
|
stimulus_type=stimulus_type,
|
||||||
current_stimulus=current_stimulus,
|
current_stimulus=current_stimulus,
|
||||||
stimulus_configuration=stimulus_configuration,
|
stimulus_configuration=stimulus_configuration,
|
||||||
questions=current_block["questions"]
|
questions=current_block["questions"],
|
||||||
|
infovideo=infovideo
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -299,16 +318,18 @@ def jsonform():
|
|||||||
return "Error, none of the Blocks triggered"
|
return "Error, none of the Blocks triggered"
|
||||||
|
|
||||||
|
|
||||||
@app.route("/send_json", methods=["POST"])
|
@app.route("/send", methods=["POST"])
|
||||||
def sendpage_json():
|
def sendpage():
|
||||||
print("send_json")
|
print("send")
|
||||||
# Do I need to write to a table at all?
|
# 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
|
# 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
|
if not ("database_table" in config[session["current_block_name"]]): #it has no database field, so nothing to receive
|
||||||
# so just move on
|
# so just move on
|
||||||
print("no database table")
|
print("no database table")
|
||||||
update_session()
|
update_session()
|
||||||
return redirect("/jsonform")
|
return redirect("/form")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# now to if it has a database field
|
# now to if it has a database field
|
||||||
|
|
||||||
@ -319,10 +340,26 @@ def sendpage_json():
|
|||||||
session_user_id = session["slaeform_user_id"]
|
session_user_id = session["slaeform_user_id"]
|
||||||
new_id = uuid.uuid4()
|
new_id = uuid.uuid4()
|
||||||
date = datetime.today()
|
date = datetime.today()
|
||||||
stimulus_name = session["current_stimulus_name"]
|
stimulus_name = str(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)
|
new_entry = db_tables[table_name](id=new_id,user_id = session_user_id,date_created = date,stimulus_name=stimulus_name)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
if 'recordedVideo' in request.files:
|
||||||
|
if hasattr(new_entry, "video_upload"):
|
||||||
|
setattr(new_entry, "video_upload", video_name)
|
||||||
|
|
||||||
|
|
||||||
for key, value in request.form.items():
|
for key, value in request.form.items():
|
||||||
print("hasattr key: ", key)
|
print("hasattr key: ", key)
|
||||||
if hasattr(new_entry, key):
|
if hasattr(new_entry, key):
|
||||||
@ -348,22 +385,39 @@ def sendpage_json():
|
|||||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
||||||
video.save(path)
|
video.save(path)
|
||||||
|
|
||||||
|
if (session["current_block_index"] == session["number_of_blocks"]-1) and (session["current_stimulus_index"] >= session["number_of_stimuli"]-1):
|
||||||
|
#update the database entry, the form is completed
|
||||||
|
user = db.session.query(User).filter_by(user_id=session["slaeform_user_id"]).one()
|
||||||
|
user.form_completed = True
|
||||||
|
print("updated db entry for form_completed")
|
||||||
|
# This user is done, so we can remove it from the session
|
||||||
|
session.pop("slaeform_user_id")
|
||||||
|
try:
|
||||||
|
db.session.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print("Error occurred: {e}".format(e=str(e)))
|
||||||
|
return "There was a problem while updating the user"
|
||||||
|
return redirect("/endpage")
|
||||||
|
|
||||||
# Now move to the next stimulus or block
|
# Now move to the next stimulus or block
|
||||||
update_session()
|
update_session()
|
||||||
print("now redirect and reload the page")
|
print("now redirect and reload the page")
|
||||||
return redirect("/jsonform")
|
return redirect("/form")
|
||||||
|
|
||||||
def update_session():
|
def update_session():
|
||||||
|
print("update session")
|
||||||
if "stimuli" in config[session["current_block_name"]]:
|
if "stimuli" in config[session["current_block_name"]]:
|
||||||
# if there are stimuli in this block
|
# if there are stimuli in this block
|
||||||
if session["current_stimulus_index"] < session["number_of_stimuli"]-1:
|
if session["current_stimulus_index"] < session["number_of_stimuli"]-1:
|
||||||
|
print("there are still stimuli left")
|
||||||
# if there are still stimuli left, keep going through them
|
# if there are still stimuli left, keep going through them
|
||||||
session["current_stimulus_index"] += 1
|
session["current_stimulus_index"] += 1
|
||||||
# set the name of the current stimulus
|
# set the name of the current stimulus
|
||||||
session["current_stimulus_name"] = session["block_order"][session["current_block_name"]][session["current_stimulus_index"]]
|
session["current_stimulus_name"] = session["block_order"][session["current_block_name"]][session["current_stimulus_index"]]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
print("here are no stimuli left")
|
||||||
|
session["number_of_stimuli"] = 0
|
||||||
# if there are no stimuli left..
|
# if there are no stimuli left..
|
||||||
if(session["current_block_index"] < session["number_of_blocks"]-1):
|
if(session["current_block_index"] < session["number_of_blocks"]-1):
|
||||||
# go to next block if possible
|
# go to next block if possible
|
||||||
@ -371,7 +425,7 @@ def update_session():
|
|||||||
session["current_block_name"] = session["block_names"][session["current_block_index"]]
|
session["current_block_name"] = session["block_names"][session["current_block_index"]]
|
||||||
session["current_stimulus_index"] = 0
|
session["current_stimulus_index"] = 0
|
||||||
if "stimuli" in config[session["current_block_name"]]:
|
if "stimuli" in config[session["current_block_name"]]:
|
||||||
session["number_of_stimuli"] = len(list(config[session["current_block_name"]]["stimuli"]["list"]))
|
session["number_of_stimuli"] = len(session["block_order"][session["current_block_name"]])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# if there arent any stimuli, go to the next block
|
# if there arent any stimuli, go to the next block
|
||||||
@ -381,152 +435,115 @@ def update_session():
|
|||||||
session["current_block_index"] += 1
|
session["current_block_index"] += 1
|
||||||
session["current_block_name"] = session["block_names"][session["current_block_index"]]
|
session["current_block_name"] = session["block_names"][session["current_block_index"]]
|
||||||
|
|
||||||
|
if "stimuli" in config[session["current_block_name"]]:
|
||||||
print("---Session updated---")
|
# set the name of the current stimulus
|
||||||
|
session["current_stimulus_name"] = session["block_order"][session["current_block_name"]][session["current_stimulus_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_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_block_name: ", session["current_block_name"])
|
||||||
print("current_stimulus_index: ", session["current_stimulus_index"])
|
print("current_stimulus_index: ", session["current_stimulus_index"])
|
||||||
print("Current number_of_stimuli: ", session["number_of_stimuli"])
|
print("Current number_of_stimuli: ", session["number_of_stimuli"])
|
||||||
|
|
||||||
|
#@app.route("/update")
|
||||||
|
def update():
|
||||||
|
|
||||||
|
print("Current Session: ",session)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 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:
|
try:
|
||||||
db.session.add(new_response)
|
user = db.session.query(User).filter_by(user_id=session["slaeform_user_id"]).one()
|
||||||
|
user.form_completed = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return redirect("/form")
|
except Exception as e:
|
||||||
except:
|
print("Error occurred: {e}".format(e=str(e)))
|
||||||
return "There was a problem while adding the response to the Database"
|
return "There was a problem while updating the user"
|
||||||
|
|
||||||
|
return "db entry updated!"
|
||||||
#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------------------------------------------------------------------------------
|
# Database stuff------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def zipdir(path, ziph):
|
||||||
|
# ziph is zipfile handle
|
||||||
|
for root, dirs, files in os.walk(path):
|
||||||
|
for file in files:
|
||||||
|
ziph.write(os.path.join(root, file),
|
||||||
|
os.path.relpath(os.path.join(root, file),
|
||||||
|
os.path.join(path, '..')))
|
||||||
|
|
||||||
|
#create all csvs
|
||||||
|
def create_all_csvs():
|
||||||
|
meta = db.metadata
|
||||||
|
tables = meta.tables.keys()
|
||||||
|
|
||||||
|
for table_name in tables:
|
||||||
|
create_csv(meta.tables[table_name], table_name)
|
||||||
|
|
||||||
|
|
||||||
|
# export CSV
|
||||||
|
@app.route("/export_all_tables")
|
||||||
|
def export_all_tables():
|
||||||
|
if not session.get("logged_in"):
|
||||||
|
return redirect("/login")
|
||||||
|
|
||||||
|
create_all_csvs()
|
||||||
|
|
||||||
|
with ZipFile('zip_exports/all_tables.zip', 'w') as zipf: #no compression, need to add zipfile.ZIP_DEFLATED for compression
|
||||||
|
zipdir('exports/', zipf)
|
||||||
|
|
||||||
|
return send_file("zip_exports/all_tables.zip", as_attachment=False, download_name="all_tables.zip")
|
||||||
|
|
||||||
|
# export CSV
|
||||||
|
@app.route("/export_all_videos")
|
||||||
|
def export_all_videos():
|
||||||
|
if not session.get("logged_in"):
|
||||||
|
return redirect("/login")
|
||||||
|
|
||||||
|
with ZipFile('zip_exports/all_videos.zip', 'w') as zipf: #no compression, need to add zipfile.ZIP_DEFLATED for compression
|
||||||
|
zipdir('uploads/', zipf)
|
||||||
|
|
||||||
|
return send_file("zip_exports/all_videos.zip", as_attachment=False, download_name="all_tables.zip")
|
||||||
|
|
||||||
|
|
||||||
|
def create_csv(table, filename):
|
||||||
|
filename = filename + ".csv"
|
||||||
|
# Query all data from the table
|
||||||
|
data = db.session.query(table).all()
|
||||||
|
|
||||||
|
# Get the column names from the model
|
||||||
|
column_names = [column.name for column in table.columns]
|
||||||
|
|
||||||
|
# Open a CSV file and write data
|
||||||
|
path = os.path.join(EXPORT_FOLDER, filename)
|
||||||
|
with open(path, 'w', newline='') as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
writer.writerow(column_names) # Write header
|
||||||
|
for row in data:
|
||||||
|
writer.writerow([getattr(row, column) for column in column_names])
|
||||||
|
|
||||||
|
|
||||||
|
# export CSV
|
||||||
|
@app.route("/export_csv/<table_name>")
|
||||||
|
def export_csv(table_name):
|
||||||
|
|
||||||
|
meta = db.metadata
|
||||||
|
|
||||||
|
try:
|
||||||
|
create_csv(meta.tables[table_name], table_name)
|
||||||
|
except Exception as e:
|
||||||
|
return f'Error occurred: {str(e)}', 500
|
||||||
|
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
# the contents of all tables
|
# the contents of all tables
|
||||||
@app.route("/table_contents")
|
@app.route("/table_contents")
|
||||||
def table_contents():
|
def table_contents():
|
||||||
|
if not session.get("logged_in"):
|
||||||
|
return redirect("/login")
|
||||||
meta = db.metadata
|
meta = db.metadata
|
||||||
#meta.reflect(db.engine) # Uncomment this to also get the hidden tables, but this crashes rn
|
#meta.reflect(db.engine) # Uncomment this to also get the hidden tables, but this crashes rn
|
||||||
tables = meta.tables.keys()
|
tables = meta.tables.keys()
|
||||||
table_contents = {}
|
table_contents = {}
|
||||||
#print(tables)
|
|
||||||
|
|
||||||
for table_name in tables:
|
for table_name in tables:
|
||||||
table = meta.tables[table_name]
|
table = meta.tables[table_name]
|
||||||
@ -553,13 +570,32 @@ def table_contents():
|
|||||||
|
|
||||||
@app.route('/show_tables')
|
@app.route('/show_tables')
|
||||||
def show_tables():
|
def show_tables():
|
||||||
|
if not session.get("logged_in"):
|
||||||
|
return redirect("/login")
|
||||||
meta = db.metadata
|
meta = db.metadata
|
||||||
meta.reflect(db.engine)
|
meta.reflect(db.engine)
|
||||||
tables = meta.tables
|
tables = meta.tables
|
||||||
return render_template('show_tables.html', tables=tables)
|
return render_template('show_tables.html', tables=tables)
|
||||||
|
|
||||||
|
|
||||||
# Root page -----------------------------
|
# Root page -----------------------------
|
||||||
|
|
||||||
|
@app.route("/login", methods=["GET","POST"])
|
||||||
|
def login():
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
if request.form["password"] == PASSWORD:
|
||||||
|
session["logged_in"] = True
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
return render_template("login.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/logout")
|
||||||
|
def logout():
|
||||||
|
session["logged_in"] = False
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
def has_no_empty_params(rule):
|
def has_no_empty_params(rule):
|
||||||
defaults = rule.defaults if rule.defaults is not None else ()
|
defaults = rule.defaults if rule.defaults is not None else ()
|
||||||
arguments = rule.arguments if rule.arguments is not None else ()
|
arguments = rule.arguments if rule.arguments is not None else ()
|
||||||
@ -579,7 +615,7 @@ def all_links():
|
|||||||
# delete all tables as last link --------------------------
|
# delete all tables as last link --------------------------
|
||||||
|
|
||||||
# Route to delete all entries
|
# Route to delete all entries
|
||||||
@app.route('/delete_json_tables', methods=['GET'])
|
#@app.route('/delete_json_tables', methods=['GET'])
|
||||||
def delete_json_tables():
|
def delete_json_tables():
|
||||||
try:
|
try:
|
||||||
meta = db.metadata
|
meta = db.metadata
|
||||||
@ -603,6 +639,8 @@ def delete_json_tables():
|
|||||||
# Route to delete all entries
|
# Route to delete all entries
|
||||||
@app.route('/delete_all_entries', methods=['GET'])
|
@app.route('/delete_all_entries', methods=['GET'])
|
||||||
def delete_all_entries():
|
def delete_all_entries():
|
||||||
|
if not session.get("logged_in"):
|
||||||
|
return redirect("/login")
|
||||||
# here I could also use a "drop_all()", that works jsut like create all from the creation part
|
# 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
|
# this together with the reflect could drop actually all tables
|
||||||
try:
|
try:
|
||||||
@ -620,4 +658,4 @@ def delete_all_entries():
|
|||||||
db.session.close()
|
db.session.close()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True)
|
app.run()
|
@ -5,6 +5,10 @@ https://jamesalvarez.co.uk/blog/how-to-make-responsive-likert-scales-in-css-(lik
|
|||||||
# Rootpage that shows all defined routes
|
# Rootpage that shows all defined routes
|
||||||
https://stackoverflow.com/questions/13317536/get-list-of-all-routes-defined-in-the-flask-app
|
https://stackoverflow.com/questions/13317536/get-list-of-all-routes-defined-in-the-flask-app
|
||||||
|
|
||||||
|
# Zip file of whole directory
|
||||||
|
https://stackoverflow.com/questions/1855095/how-to-create-a-zip-archive-of-a-directory
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Licenses:
|
Licenses:
|
||||||
|
|
||||||
|
@ -1,20 +1,52 @@
|
|||||||
{
|
{
|
||||||
"Block_1":{
|
"Block 0":{
|
||||||
"type": "SinglePage",
|
|
||||||
"template": "startpage.html",
|
|
||||||
"database_table" :{
|
|
||||||
"TableName": "Datenschutzerklaerung",
|
|
||||||
"Fields": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Block 2":{
|
|
||||||
"type": "TaskTemplate",
|
"type": "TaskTemplate",
|
||||||
"tempalte": "tempaltetest1.html",
|
"tempalte": "standard_template.html",
|
||||||
"name" : "Block2Responses",
|
"stimuli":{
|
||||||
"database_table": {
|
"type":"double_video",
|
||||||
"TableName": "Datenschutzerklaerung",
|
"list_1":{
|
||||||
"Fields": {
|
"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"
|
||||||
|
},
|
||||||
|
"list_2":{
|
||||||
|
"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 am indifferent"
|
||||||
|
},
|
||||||
|
"p3":{
|
||||||
|
"value":"3",
|
||||||
|
"text":"I like it a lot"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"database_table" :{
|
||||||
|
"table_name": "double_video_test",
|
||||||
|
"fields": {
|
||||||
|
"likertscale":{
|
||||||
|
"type": "integer",
|
||||||
|
"nullable": "false"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,89 +1,15 @@
|
|||||||
{
|
{
|
||||||
"Block 4":{
|
"Block 1": {
|
||||||
"type": "TaskTemplate",
|
"type": "TaskTemplate",
|
||||||
"tempalte": "standard_template.html",
|
"tempalte": "standard_template.html",
|
||||||
"number_of_pages":"3",
|
"stimuli": {
|
||||||
"stimuli":{
|
"type": "empty",
|
||||||
"type":"single_video",
|
"list": {
|
||||||
"order": "random",
|
"empty_stimulus": ""
|
||||||
"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":{
|
"questions": {
|
||||||
"question1":{
|
"question1_alter": {
|
||||||
"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",
|
"type": "numberinput",
|
||||||
"name": "alter",
|
"name": "alter",
|
||||||
"text": "Alter:",
|
"text": "Alter:",
|
||||||
@ -91,74 +17,74 @@
|
|||||||
"min": "1",
|
"min": "1",
|
||||||
"max": "120"
|
"max": "120"
|
||||||
},
|
},
|
||||||
"question2_geschlecht":{
|
"question2_geschlecht": {
|
||||||
"type": "dropdowninput",
|
"type": "dropdowninput",
|
||||||
"name": "geschlecht",
|
"name": "geschlecht",
|
||||||
"text": "Geschlecht:",
|
"text": "Geschlecht:",
|
||||||
"required": "true",
|
"required": "true",
|
||||||
"defaulttext": "",
|
"defaulttext": "",
|
||||||
"points":{
|
"points": {
|
||||||
"männlich":{
|
"männlich": {
|
||||||
"value":"Männlich",
|
"value": "Männlich",
|
||||||
"text":"Männlich"
|
"text": "Männlich"
|
||||||
},
|
},
|
||||||
"weiblich":{
|
"weiblich": {
|
||||||
"value":"Weiblich",
|
"value": "Weiblich",
|
||||||
"text":"Weiblich"
|
"text": "Weiblich"
|
||||||
},
|
},
|
||||||
"divers":{
|
"divers": {
|
||||||
"value":"Divers",
|
"value": "Divers",
|
||||||
"text":"Divers"
|
"text": "Divers"
|
||||||
},
|
},
|
||||||
"keine_angabe":{
|
"keine_angabe": {
|
||||||
"value":"keine_angabe",
|
"value": "keine_angabe",
|
||||||
"text":"Keine Angabe"
|
"text": "Keine Angabe"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"question3_hoerstatus":{
|
"question3_hoerstatus": {
|
||||||
"type": "dropdowninput",
|
"type": "dropdowninput",
|
||||||
"name": "hoerstatus",
|
"name": "hoerstatus",
|
||||||
"text": "Hörstatus:",
|
"text": "Hörstatus:",
|
||||||
"required": "true",
|
"required": "true",
|
||||||
"defaulttext": "",
|
"defaulttext": "",
|
||||||
"points":{
|
"points": {
|
||||||
"hörend":{
|
"hörend": {
|
||||||
"value":"Hörend",
|
"value": "Hörend",
|
||||||
"text":"Hörend"
|
"text": "Hörend"
|
||||||
},
|
},
|
||||||
"schwerhörig":{
|
"schwerhörig": {
|
||||||
"value":"Schwerhörig",
|
"value": "Schwerhörig",
|
||||||
"text":"Schwerhörig"
|
"text": "Schwerhörig"
|
||||||
},
|
},
|
||||||
"gehörlos":{
|
"gehörlos": {
|
||||||
"value":"Gehörlos",
|
"value": "Gehörlos",
|
||||||
"text":"Gehörlos"
|
"text": "Gehörlos"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"question4_bevorzugte_kommunikation":{
|
"question4_bevorzugte_kommunikation": {
|
||||||
"type": "dropdowninput",
|
"type": "dropdowninput",
|
||||||
"name": "bevorzugte_kommunikation",
|
"name": "bevorzugte_kommunikation",
|
||||||
"text": "Bevorzugte Kommunikationsform:",
|
"text": "Bevorzugte Kommunikationsform:",
|
||||||
"required": "true",
|
"required": "true",
|
||||||
"defaulttext": "",
|
"defaulttext": "",
|
||||||
"points":{
|
"points": {
|
||||||
"gesprochen":{
|
"gesprochen": {
|
||||||
"value":"Gesprochene Sprache",
|
"value": "Gesprochene Sprache",
|
||||||
"text":"Gesprochene Sprache"
|
"text": "Gesprochene Sprache"
|
||||||
},
|
},
|
||||||
"text":{
|
"text": {
|
||||||
"value":"Text",
|
"value": "Text",
|
||||||
"text":"Text"
|
"text": "Text"
|
||||||
},
|
},
|
||||||
"gebärdensprache":{
|
"gebärdensprache": {
|
||||||
"value":"Gebärdensprache",
|
"value": "Gebärdensprache",
|
||||||
"text":"Gebärdensprache"
|
"text": "Gebärdensprache"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"question5_gebeardenzeitraum":{
|
"question5_gebeardenzeitraum": {
|
||||||
"type": "numberinput",
|
"type": "numberinput",
|
||||||
"name": "gebärdenzeitraum",
|
"name": "gebärdenzeitraum",
|
||||||
"text": "Wie viele Jahre verwenden sie schon Gebärdensprache:",
|
"text": "Wie viele Jahre verwenden sie schon Gebärdensprache:",
|
||||||
@ -167,7 +93,7 @@
|
|||||||
"max": "100",
|
"max": "100",
|
||||||
"step": "0.5"
|
"step": "0.5"
|
||||||
},
|
},
|
||||||
"question6_sprachkompetenz":{
|
"question6_sprachkompetenz": {
|
||||||
"type": "numberinput",
|
"type": "numberinput",
|
||||||
"name": "gebärdensprachkompetenz",
|
"name": "gebärdensprachkompetenz",
|
||||||
"text": "Wie schätzen sie ihre Gebärdensprachkompetenz ein (1-10):",
|
"text": "Wie schätzen sie ihre Gebärdensprachkompetenz ein (1-10):",
|
||||||
@ -176,53 +102,218 @@
|
|||||||
"max": "10"
|
"max": "10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"database_table" :{
|
"infovideo": {
|
||||||
"table_name": "default_demographic_test",
|
"videourl": "https://www.youtube-nocookie.com/embed/F_w50c5Us3Y?si=-H07MmQ4lYOC2Bwh",
|
||||||
|
"infotext": "Hier wird in Zukunft ein Erklärtext stehen, in dem die Fragestellungen erklärt werden. \n Dazu werden alle Fragen der Seite einzeln durchgegangen und einfach erklärt. Zum Beispiel wird hier erklärt, dass man um Feedback zu geben, ein Video aufnehmen kann. Dazu drückt man auf den Knopf mit dem Videokamera Symbol. Danach muss man oben links am Browser bestätigen, dass der Browser auf die Kamera zugreifen darf.",
|
||||||
|
"configuration": {
|
||||||
|
"embed": "yt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"database_table": {
|
||||||
|
"table_name": "demographic_data",
|
||||||
"fields": {
|
"fields": {
|
||||||
"alter":{
|
"alter": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"nullable": "false"
|
"nullable": "false"
|
||||||
},
|
},
|
||||||
"geschlecht":{
|
"geschlecht": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"size": "14",
|
"size": "14",
|
||||||
"nullable": "false"
|
"nullable": "false"
|
||||||
},
|
},
|
||||||
"hoerstatus":{
|
"hoerstatus": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"size": "14",
|
"size": "14",
|
||||||
"nullable": "false"
|
"nullable": "false"
|
||||||
},
|
},
|
||||||
"bevorzugte_kommunikation":{
|
"bevorzugte_kommunikation": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"size": "22",
|
"size": "22",
|
||||||
"nullable": "false"
|
"nullable": "false"
|
||||||
},
|
},
|
||||||
"gebärdenzeitraum":{
|
"gebärdenzeitraum": {
|
||||||
"type": "float",
|
"type": "float",
|
||||||
"nullable": "false"
|
"nullable": "false"
|
||||||
},
|
},
|
||||||
"gebärdensprachkompetenz":{
|
"gebärdensprachkompetenz": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"nullable": "false"
|
"nullable": "false"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Block_1":{
|
"Block 2": {
|
||||||
"type": "SinglePage",
|
"type": "TaskTemplate",
|
||||||
"template": "test_page0.html"
|
"tempalte": "standard_template.html",
|
||||||
},
|
"stimuli": {
|
||||||
"Block_2":{
|
"type": "single_video",
|
||||||
"type": "SinglePage",
|
"order": "random",
|
||||||
"template": "test_page1.html",
|
"list": {
|
||||||
"database_table" :{
|
"video_1": "https://www.youtube-nocookie.com/embed/IqGVT1q1PtM?si=kel7ZWEQl3h-h522",
|
||||||
"table_name": "Datenschutzerklaerung",
|
"video_2": "https://www.youtube-nocookie.com/embed/g9KA72jN5SM?si=O7dfqTXdFCCAScJ-"
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"embed": "yt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"questions": {
|
||||||
|
"question1": {
|
||||||
|
"type": "likert",
|
||||||
|
"name": "likertscale",
|
||||||
|
"text": "Wie würden sie dieses Video bewerten?",
|
||||||
|
"required": "true",
|
||||||
|
"points": {
|
||||||
|
"p1": {
|
||||||
|
"value": "1",
|
||||||
|
"text": "Ich finde es gar nicht gut 🙁👎"
|
||||||
|
},
|
||||||
|
"p2": {
|
||||||
|
"value": "2",
|
||||||
|
"text": "Ich finde es nicht gut 👎"
|
||||||
|
},
|
||||||
|
"p3": {
|
||||||
|
"value": "3",
|
||||||
|
"text": "Ich finde es weder gut noch schlecht"
|
||||||
|
},
|
||||||
|
"p4": {
|
||||||
|
"value": "4",
|
||||||
|
"text": "Ich finde es gut 👍"
|
||||||
|
},
|
||||||
|
"p5": {
|
||||||
|
"value": "5",
|
||||||
|
"text": "Ich finde es sehr gut 😊👍"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"question2": {
|
||||||
|
"type": "textinput",
|
||||||
|
"name": "text_feedback",
|
||||||
|
"text": "Hier können sie uns Feedback geben",
|
||||||
|
"required": "false",
|
||||||
|
"size": "250"
|
||||||
|
},
|
||||||
|
"question3": {
|
||||||
|
"type": "videoinput",
|
||||||
|
"text": "Hier können sie per Video Feedback geben",
|
||||||
|
"name": "video_feedback",
|
||||||
|
"required": "false"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"infovideo": {
|
||||||
|
"videourl": "https://www.youtube-nocookie.com/embed/F_w50c5Us3Y?si=-H07MmQ4lYOC2Bwh",
|
||||||
|
"infotext": "Hier wird in Zukunft ein Erklärtext stehen, in dem die Fragestellungen erklärt werden.\\n Dazu werden alle Fragen der Seite einzeln durchgegangen und einfach erklärt.\\n Zum Beispiel wird hier erklärt, dass man um Feedback zu geben, ein Video aufnehmen kann. Dazu drückt man auf den Knopf mit dem Videokamera Symbol. Danach muss man oben links am Browser bestätigen, dass der Browser auf die Kamera zugreifen darf.",
|
||||||
|
"configuration": {
|
||||||
|
"embed": "yt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"database_table": {
|
||||||
|
"table_name": "single_video_responses",
|
||||||
"fields": {
|
"fields": {
|
||||||
"accepted":{
|
"likertscale": {
|
||||||
"type": "string",
|
"type": "integer",
|
||||||
"size": "7",
|
|
||||||
"nullable": "false"
|
"nullable": "false"
|
||||||
|
},
|
||||||
|
"text_feedback": {
|
||||||
|
"type": "string",
|
||||||
|
"size": "250",
|
||||||
|
"nullable": "true"
|
||||||
|
},
|
||||||
|
"video_upload": {
|
||||||
|
"type": "string",
|
||||||
|
"size": "100",
|
||||||
|
"nullable": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Block 3": {
|
||||||
|
"type": "TaskTemplate",
|
||||||
|
"tempalte": "standard_template.html",
|
||||||
|
"stimuli": {
|
||||||
|
"type": "double_video",
|
||||||
|
"list_1": {
|
||||||
|
"video_1": "https://www.youtube-nocookie.com/embed/IqGVT1q1PtM?si=kel7ZWEQl3h-h522",
|
||||||
|
"video_2": "https://www.youtube-nocookie.com/embed/g9KA72jN5SM?si=O7dfqTXdFCCAScJ-"
|
||||||
|
},
|
||||||
|
"list_2": {
|
||||||
|
"video_2": "https://www.youtube-nocookie.com/embed/g9KA72jN5SM?si=O7dfqTXdFCCAScJ-",
|
||||||
|
"video_1": "https://www.youtube-nocookie.com/embed/IqGVT1q1PtM?si=kel7ZWEQl3h-h522"
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"embed": "yt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"questions": {
|
||||||
|
"question1": {
|
||||||
|
"type": "likert",
|
||||||
|
"name": "likertscale",
|
||||||
|
"text": "Welches Video gefällt ihnen besser?",
|
||||||
|
"required": "true",
|
||||||
|
"points": {
|
||||||
|
"p1": {
|
||||||
|
"value": "1",
|
||||||
|
"text": "Ich finde das linke Video besser"
|
||||||
|
},
|
||||||
|
"p2": {
|
||||||
|
"value": "2",
|
||||||
|
"text": "Ich finde beide Videos gleich gut"
|
||||||
|
},
|
||||||
|
"p3": {
|
||||||
|
"value": "3",
|
||||||
|
"text": "Ich finde das rechte Video besser"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"infovideo": {
|
||||||
|
"videourl": "https://www.youtube-nocookie.com/embed/F_w50c5Us3Y?si=-H07MmQ4lYOC2Bwh",
|
||||||
|
"infotext": "Hier wird in Zukunft ein Erklärtext stehen, in dem die Fragestellungen erklärt werden. Dazu werden alle Fragen der Seite einzeln durchgegangen und einfach erklärt. Zum Beispiel wird hier erklärt, dass man um Feedback zu geben, ein Video aufnehmen kann. Dazu drückt man auf den Knopf mit dem Videokamera Symbol. Danach muss man oben links am Browser bestätigen, dass der Browser auf die Kamera zugreifen darf.",
|
||||||
|
"configuration": {
|
||||||
|
"embed": "yt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"database_table": {
|
||||||
|
"table_name": "double_video_responses",
|
||||||
|
"fields": {
|
||||||
|
"likertscale": {
|
||||||
|
"type": "integer",
|
||||||
|
"nullable": "false"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Block 4": {
|
||||||
|
"type": "TaskTemplate",
|
||||||
|
"tempalte": "standard_template.html",
|
||||||
|
"stimuli": {
|
||||||
|
"type": "empty",
|
||||||
|
"list": {
|
||||||
|
"empty_stimulus": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"questions": {
|
||||||
|
"question1": {
|
||||||
|
"type": "textinput",
|
||||||
|
"name": "formfeedback",
|
||||||
|
"text": "Das war der Prototyp für dieses Studientool. Über Feedback würde ich mich freuen. Entweder hier oder per Email unter: jan.dickmann@web.de",
|
||||||
|
"required": "false",
|
||||||
|
"size": "1000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"infovideo": {
|
||||||
|
"videourl": "https://www.youtube-nocookie.com/embed/F_w50c5Us3Y?si=-H07MmQ4lYOC2Bwh",
|
||||||
|
"infotext": "Hier wird in Zukunft ein Erklärtext stehen, in dem die Fragestellungen erklärt werden. Dazu werden alle Fragen der Seite einzeln durchgegangen und einfach erklärt. Zum Beispiel wird hier erklärt, dass man um Feedback zu geben, ein Video aufnehmen kann. Dazu drückt man auf den Knopf mit dem Videokamera Symbol. Danach muss man oben links am Browser bestätigen, dass der Browser auf die Kamera zugreifen darf.",
|
||||||
|
"configuration": {
|
||||||
|
"embed": "yt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"database_table": {
|
||||||
|
"table_name": "feedback_responses",
|
||||||
|
"fields": {
|
||||||
|
"formfeedback": {
|
||||||
|
"type": "string",
|
||||||
|
"size": "1000",
|
||||||
|
"nullable": "true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
slaeforms/exports/default_block3_test.csv
Normal file
4
slaeforms/exports/default_block3_test.csv
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
id,user_id,date_created,stimulus_name,likertscale,text_feedback
|
||||||
|
e27365db-7e5a-443b-b6e5-996774baf359,662dc5a5-ff08-4270-ad33-444e8dd01514,2024-06-25 12:01:55.709616,"('video_3', 'video_3')",3,
|
||||||
|
9e880318-bc66-410e-bac8-d70adbf98de6,662dc5a5-ff08-4270-ad33-444e8dd01514,2024-06-25 12:01:59.317487,video_3,2,
|
||||||
|
d147ad99-d41e-48a2-91ee-d5610438e862,662dc5a5-ff08-4270-ad33-444e8dd01514,2024-06-25 12:02:01.661348,video_2,5,
|
|
2
slaeforms/exports/default_demographic_test.csv
Normal file
2
slaeforms/exports/default_demographic_test.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
id,user_id,date_created,stimulus_name,alter,geschlecht,hoerstatus,bevorzugte_kommunikation,gebärdenzeitraum,gebärdensprachkompetenz
|
||||||
|
024e75ae-c5b7-4594-8095-b9f360df9d0c,662dc5a5-ff08-4270-ad33-444e8dd01514,2024-06-25 12:01:47.038602,"('video_3', 'video_3')",27,Männlich,Hörend,Gesprochene Sprache,4.0,1
|
|
3
slaeforms/exports/demographic_data.csv
Normal file
3
slaeforms/exports/demographic_data.csv
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
id,user_id,date_created,stimulus_name,alter,geschlecht,hoerstatus,bevorzugte_kommunikation,gebärdenzeitraum,gebärdensprachkompetenz
|
||||||
|
33b2f49f-ccc8-48c5-b7f6-fa4bb9aa1bf3,60c9c65b-b6b7-4510-8fb1-e9f220a7c46e,2024-06-27 17:43:45.381824,empty_stimulus,1,Männlich,Hörend,Gesprochene Sprache,1.0,1
|
||||||
|
70afa75b-d522-4f26-b8f5-1a22ef425ad5,253b881d-cc08-4835-bf03-4d0ffdd8eddd,2024-06-27 17:44:43.877367,empty_stimulus,1,Weiblich,Schwerhörig,Text,1.0,1
|
|
5
slaeforms/exports/double_video_responses.csv
Normal file
5
slaeforms/exports/double_video_responses.csv
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
id,user_id,date_created,stimulus_name,likertscale
|
||||||
|
8240918c-aaf9-4dc8-b868-f4cec9920dfc,60c9c65b-b6b7-4510-8fb1-e9f220a7c46e,2024-06-27 17:43:33.469473,"('video_1', 'video_2')",2
|
||||||
|
36ef34e3-0e47-4980-86f9-e3456b4bf12c,60c9c65b-b6b7-4510-8fb1-e9f220a7c46e,2024-06-27 17:43:35.573252,"('video_2', 'video_1')",2
|
||||||
|
253a2bdd-3c33-47b6-a715-a2b167b11de3,253b881d-cc08-4835-bf03-4d0ffdd8eddd,2024-06-27 17:44:32.383837,"('video_1', 'video_2')",2
|
||||||
|
ce8f86ee-fe37-45db-9a7e-a8cba56cd1da,253b881d-cc08-4835-bf03-4d0ffdd8eddd,2024-06-27 17:44:35.019425,"('video_2', 'video_1')",2
|
|
4
slaeforms/exports/double_video_test.csv
Normal file
4
slaeforms/exports/double_video_test.csv
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
id,user_id,date_created,stimulus_name,likertscale
|
||||||
|
c317333a-e930-4a3a-93e4-5e213df2d2e6,662dc5a5-ff08-4270-ad33-444e8dd01514,2024-06-25 12:01:28.132611,"('video_1', 'video_1')",1
|
||||||
|
860d97ef-4855-4995-9ff3-88177ab494af,662dc5a5-ff08-4270-ad33-444e8dd01514,2024-06-25 12:01:30.220347,"('video_2', 'video_2')",3
|
||||||
|
b59c4adb-b730-404a-9d0b-b5630e4f445b,662dc5a5-ff08-4270-ad33-444e8dd01514,2024-06-25 12:01:36.894157,"('video_3', 'video_3')",2
|
|
5
slaeforms/exports/single_video_responses.csv
Normal file
5
slaeforms/exports/single_video_responses.csv
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
id,user_id,date_created,stimulus_name,likertscale,text_feedback,video_upload
|
||||||
|
9affead9-d952-47dd-be8f-f1f746f9426d,60c9c65b-b6b7-4510-8fb1-e9f220a7c46e,2024-06-27 17:43:51.471157,video_2,3,test,
|
||||||
|
e4c5df2c-0244-4ec6-bd2c-02eff4a4c5f1,60c9c65b-b6b7-4510-8fb1-e9f220a7c46e,2024-06-27 17:43:55.326471,video_1,3,,
|
||||||
|
12313ba8-1f3e-4ea4-a39e-42021a46d0a7,253b881d-cc08-4835-bf03-4d0ffdd8eddd,2024-06-27 17:44:47.661124,video_1,3,,
|
||||||
|
c5cc69d6-d51e-4836-aabd-54d3b0682511,253b881d-cc08-4835-bf03-4d0ffdd8eddd,2024-06-27 17:44:50.468067,video_2,3,,
|
|
4
slaeforms/exports/user.csv
Normal file
4
slaeforms/exports/user.csv
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
user_id,device_id,question_order,date_created,form_completed
|
||||||
|
60c9c65b-b6b7-4510-8fb1-e9f220a7c46e,f9a2bd4b-e1ef-43a2-a358-a74feea0076d,"{'Block 0': [('video_1', 'video_2'), ('video_2', 'video_1')], 'Block 1': ['empty_stimulus'], 'Block 2': ['video_2', 'video_1']}",2024-06-27 17:43:23.511495,False
|
||||||
|
253b881d-cc08-4835-bf03-4d0ffdd8eddd,f9a2bd4b-e1ef-43a2-a358-a74feea0076d,"{'Block 0': [('video_1', 'video_2'), ('video_2', 'video_1')], 'Block 1': ['empty_stimulus'], 'Block 2': ['video_1', 'video_2']}",2024-06-27 17:44:22.713605,True
|
||||||
|
89ad42ff-3310-4548-b179-7df9c138e794,f9a2bd4b-e1ef-43a2-a358-a74feea0076d,"{'Block 0': [('video_1', 'video_2'), ('video_2', 'video_1')], 'Block 1': ['empty_stimulus'], 'Block 2': ['video_2', 'video_1']}",2024-06-27 17:56:57.288776,False
|
|
2
slaeforms/exports/usertable.csv
Normal file
2
slaeforms/exports/usertable.csv
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
user_id,device_id,question_order,date_created
|
||||||
|
662dc5a5-ff08-4270-ad33-444e8dd01514,50bc84f6-6c55-4277-9651-5c3c98e973f5,"{'Block 0': [('video_1', 'video_1'), ('video_2', 'video_2'), ('video_3', 'video_3')], 'Block 1': ['empty_stimulus'], 'Block 2': ['video_1', 'video_3', 'video_2']}",2024-06-25 12:01:22.516505
|
|
BIN
slaeforms/static/icons/info-icon.png
Normal file
BIN
slaeforms/static/icons/info-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
13
slaeforms/static/infoDialogScript.js
Normal file
13
slaeforms/static/infoDialogScript.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const dialog = document.querySelector("dialog");
|
||||||
|
const showButton = document.querySelector("dialog + button");
|
||||||
|
const closeButton = document.querySelector("dialog button");
|
||||||
|
|
||||||
|
// "Show the dialog" button opens the dialog modally
|
||||||
|
showButton.addEventListener("click", () => {
|
||||||
|
dialog.showModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
// "Close" button closes the dialog
|
||||||
|
closeButton.addEventListener("click", () => {
|
||||||
|
dialog.close();
|
||||||
|
});
|
247
slaeforms/static/oldstyle.css
Normal file
247
slaeforms/static/oldstyle.css
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100hv;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form */
|
||||||
|
form {
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Helper */
|
||||||
|
.spacer {
|
||||||
|
clear: both;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* buttons */
|
||||||
|
.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 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.centertext {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2,h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Video recording controls */
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Video recording iframe */
|
||||||
|
|
||||||
|
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%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Likert stuff */
|
||||||
|
.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 display stuff */
|
||||||
|
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;
|
||||||
|
}
|
@ -1,84 +1,219 @@
|
|||||||
html {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
/*align-items: center; this will make the content a square, with edges up and bottom*/
|
|
||||||
background-color: #a4b5ff;
|
background-color: #a4b5ff;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
font-family: Tahoma;
|
font-family: Tahoma;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.centercontent {
|
|
||||||
height: 100%;
|
::backdrop {
|
||||||
|
background-color: grey;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
width: 66%;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog .iframe-container {
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
width: 300px;
|
||||||
|
height: 200px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialogTextContainer {
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialogBtn {
|
||||||
|
width: 90px;
|
||||||
|
height: 90px;
|
||||||
|
padding: 8px;
|
||||||
|
position: fixed;
|
||||||
|
top: 30px;
|
||||||
|
left: min(calc(66vw + 30px + 34vw / 2), calc(1720px + calc(100vw - 1690px) / 2));
|
||||||
|
/*first is the normal case, second if 66vw is more than 1690 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.infoButtonIcon {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
width: 300px;
|
||||||
|
background: #6cd1e1;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
align-content: center;
|
||||||
|
margin-top: 40vh;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-bottom: 50vh;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-elements {
|
||||||
|
display: inline-block;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
#loginbutton {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 12px 24px;
|
||||||
|
/* Increased padding for a bigger button */
|
||||||
|
font-size: 25px;
|
||||||
|
/* Increased font size */
|
||||||
|
border: none;
|
||||||
|
/* Removes default border */
|
||||||
|
border-radius: 8px;
|
||||||
|
/* Optional: rounds the corners of the button */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
height: 100%;
|
margin: 0 auto;
|
||||||
|
/* height: 100vh;*/
|
||||||
width: 80%; /* You can adjust this width as needed */
|
width: 66vw;
|
||||||
max-width: 1200px; /* Maximum width to keep it from getting too wide on large screens */
|
/* You can adjust this width as needed */
|
||||||
|
max-width: 1690px;
|
||||||
|
/* Maximum width to keep it from getting too wide on large screens */
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background-color: #7b8cdb; /* Just for visual differentiation */
|
background-color: #7b8cdb;
|
||||||
|
/* Just for visual differentiation */
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.textblock {
|
||||||
|
width: 60%;
|
||||||
.spacer {
|
margin: auto;
|
||||||
clear: both;
|
text-align: left;
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Form */
|
||||||
|
form {
|
||||||
|
min-width: 300px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.dsgvoform {
|
||||||
|
text-align: right;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
margin: 10px 0 0 0;
|
||||||
|
width: 60%;
|
||||||
|
min-height: 2em;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container input {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 30px;
|
||||||
|
/* Adjust as needed for spacing */
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* buttons */
|
||||||
.buttondisable {
|
.buttondisable {
|
||||||
filter: invert(65%);
|
filter: invert(65%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#submitbutton {
|
#submitbutton {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 10px 20px; /* Increased padding for a bigger button */
|
padding: 12px 24px;
|
||||||
font-size: 20px; /* Increased font size */
|
/* Increased padding for a bigger button */
|
||||||
border: none; /* Removes default border */
|
font-size: 25px;
|
||||||
border-radius: 8px; /* Optional: rounds the corners of the button */
|
/* Increased font size */
|
||||||
|
border: none;
|
||||||
|
/* Removes default border */
|
||||||
|
border-radius: 8px;
|
||||||
|
/* Optional: rounds the corners of the button */
|
||||||
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#submitbutton:hover {
|
#submitbutton:hover {
|
||||||
background-color: #0056b3; /* Darker shade for hover effect */
|
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 {
|
.textarea-container {
|
||||||
display: flex;
|
margin: auto;
|
||||||
flex-direction: column;
|
width: 60%;
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.textarea-label{
|
|
||||||
align-self: flex-start; /* Aligns the label to the start of the container */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.textarea-container textarea {
|
||||||
video {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
.aspect-ratio-16-9 {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
aspect-ratio: 16 / 9;
|
margin: 1.5rem auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.textarea-label {
|
||||||
|
align-self: flex-start;
|
||||||
|
/* Aligns the label to the start of the container */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Helper */
|
||||||
|
.spacer {
|
||||||
|
clear: both;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centertext {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline {
|
||||||
|
display: inline;
|
||||||
|
width: unset;
|
||||||
|
margin: 0 0.5em 0 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compressWidth {
|
||||||
|
width: 60%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Video recording controls */
|
||||||
.videocontrols {
|
.videocontrols {
|
||||||
width: 100px; /* Set a specific width for the buttons */
|
width: 100px;
|
||||||
height: 70px; /* Set a specific height for the buttons */
|
/* Set a specific width for the buttons */
|
||||||
|
height: 70px;
|
||||||
|
/* Set a specific height for the buttons */
|
||||||
background-color: #cae4ff;
|
background-color: #cae4ff;
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
@ -87,9 +222,12 @@ video {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s ease;
|
transition: background-color 0.3s ease;
|
||||||
display: inline-flex; /* Display button contents as a flexbox */
|
display: inline-flex;
|
||||||
justify-content: center; /* Center contents horizontally */
|
/* Display button contents as a flexbox */
|
||||||
align-items: center; /* Center contents vertically */
|
justify-content: center;
|
||||||
|
/* Center contents horizontally */
|
||||||
|
align-items: center;
|
||||||
|
/* Center contents vertically */
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,9 +236,12 @@ video {
|
|||||||
max-height: 65%;
|
max-height: 65%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
width: auto; /* Make the image fill its container */
|
width: auto;
|
||||||
height: auto; /* Make the image fill its container */
|
/* Make the image fill its container */
|
||||||
display: block; /* Remove any extra space around the image */
|
height: auto;
|
||||||
|
/* Make the image fill its container */
|
||||||
|
display: block;
|
||||||
|
/* Remove any extra space around the image */
|
||||||
}
|
}
|
||||||
|
|
||||||
.columncontainer {
|
.columncontainer {
|
||||||
@ -111,26 +252,27 @@ video {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border: 3px solid black;
|
border: 3px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.columnright {
|
.columnright {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 3px solid black;
|
border: 3px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe {
|
/* Video recording video and youtube iframe */
|
||||||
display:block;
|
video {
|
||||||
margin: auto;
|
/* Video should not be bigger than 100% */
|
||||||
}
|
|
||||||
|
|
||||||
.video-container {
|
|
||||||
position: relative;
|
|
||||||
padding-bottom: 56.25%; /* 16:9 aspect ratio */
|
|
||||||
height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
background: #000;
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
margin: auto auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-container iframe {
|
iframe {
|
||||||
|
/* center the iframe, mostly unnecessary */
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iframe-container iframe {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -138,46 +280,62 @@ iframe {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
.iframe-container {
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 56.25%;
|
||||||
|
/* 16:9 */
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container {
|
||||||
|
max-width: 100%;
|
||||||
|
position: relative;
|
||||||
|
padding-bottom: 56.25%;
|
||||||
|
/* 16:9 */
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videomirror {
|
||||||
|
transform: rotateY(180deg);
|
||||||
|
/* Safari and Chrome */
|
||||||
|
-webkit-transform: rotateY(180deg);
|
||||||
|
/* Firefox */
|
||||||
|
-moz-transform: rotateY(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Double Video */
|
||||||
|
.dv_button {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10%;
|
||||||
|
padding: 20px 5px;
|
||||||
|
margin: auto 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dv_half {
|
||||||
|
display: inline-block;
|
||||||
|
width: 45%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.double_video_container {
|
||||||
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.centertext {
|
/* Likert stuff */
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2,h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.likert {
|
.likert {
|
||||||
--likert-rows: 5;
|
/* --likert-rows: 5;*/
|
||||||
margin: auto;
|
margin: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: 1fr;
|
||||||
gap: 1em;
|
gap: 2em;
|
||||||
grid-template-columns: repeat(var(--likert-rows), minmax(0, 1fr));
|
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 {
|
.likert input {
|
||||||
max-width: 250px;
|
max-width: 250px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -185,10 +343,15 @@ input, label {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.likercontainer{
|
.likert label {
|
||||||
|
aspect-ratio: 1.5 / 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.likercontainer {
|
||||||
margin: 30px auto;
|
margin: 30px auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.likert span {
|
.likert span {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -203,13 +366,14 @@ input, label {
|
|||||||
transition: background .2s ease-in-out;
|
transition: background .2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.likert input:checked + span {
|
.likert input:checked+span {
|
||||||
outline: black auto 1px;
|
outline: black auto 1px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.likert input:focus + span {
|
.likert input:focus+span {
|
||||||
outline: auto 1px; /*-webkit-focus-ring-color*/
|
outline: auto 1px;
|
||||||
|
/*-webkit-focus-ring-color*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.likert span:hover {
|
.likert span:hover {
|
||||||
@ -217,18 +381,31 @@ input, label {
|
|||||||
outline: lightgrey auto 0.5px;
|
outline: lightgrey auto 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 680px) {
|
||||||
|
.likert {
|
||||||
|
grid-template-columns: minmax(0, 400px);
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Table display stuff */
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
table-layout: auto; /* Allows columns to adjust to their content */
|
table-layout: auto;
|
||||||
width: auto; /* Adjusts the table width to the content */
|
/* Allows columns to adjust to their content */
|
||||||
|
width: auto;
|
||||||
|
/* Adjusts the table width to the content */
|
||||||
}
|
}
|
||||||
|
|
||||||
th, td {
|
th,
|
||||||
|
td {
|
||||||
border: 1px solid #dddddd;
|
border: 1px solid #dddddd;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
word-wrap: break-word; /* Ensures content wraps and doesn't overflow */
|
word-wrap: break-word;
|
||||||
|
/* Ensures content wraps and doesn't overflow */
|
||||||
}
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
|
@ -6,11 +6,14 @@ const buttonCameraIcon = document.getElementById('buttonCameraIcon');
|
|||||||
const buttonRecordIcon = document.getElementById('buttonRecordIcon');
|
const buttonRecordIcon = document.getElementById('buttonRecordIcon');
|
||||||
const buttonDeleteIcon = document.getElementById('buttonDeleteIcon');
|
const buttonDeleteIcon = document.getElementById('buttonDeleteIcon');
|
||||||
const videoContainer = document.getElementById('videoContainer');
|
const videoContainer = document.getElementById('videoContainer');
|
||||||
|
const videoContainerCss = document.querySelector(".video-container ") //might be unnecessary
|
||||||
var mediaRecorder = null;
|
var mediaRecorder = null;
|
||||||
var stream = null;
|
var stream = null;
|
||||||
let recordedVideoBlob = null;
|
let recordedVideoBlob = null;
|
||||||
let isRecording = false;
|
let isRecording = false;
|
||||||
let videoAccess = false;
|
let videoAccess = false;
|
||||||
|
let videoHeigt = 720;
|
||||||
|
let videoWidth = 1280;
|
||||||
|
|
||||||
// Handle form submission
|
// Handle form submission
|
||||||
document.getElementById("question_form").addEventListener("submit", function (event) {
|
document.getElementById("question_form").addEventListener("submit", function (event) {
|
||||||
@ -32,7 +35,7 @@ document.getElementById("question_form").addEventListener("submit", function (ev
|
|||||||
|
|
||||||
// Use fetch to send the form data and video to the backend
|
// Use fetch to send the form data and video to the backend
|
||||||
console.log("try to send the form and video");
|
console.log("try to send the form and video");
|
||||||
fetch("/send_json", {
|
fetch("/send", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData
|
body: formData
|
||||||
}).then(response => {
|
}).then(response => {
|
||||||
@ -66,14 +69,31 @@ async function cameraButton() {
|
|||||||
console.log("stream is active");
|
console.log("stream is active");
|
||||||
videoAccess = true;
|
videoAccess = true;
|
||||||
|
|
||||||
|
videoHeigt = stream.getVideoTracks()[0].getSettings().height
|
||||||
|
videoWidth = stream.getVideoTracks()[0].getSettings().width
|
||||||
|
|
||||||
|
let aspectratio = (videoHeigt / videoWidth) *100
|
||||||
|
console.log("videoHeigt: ",videoHeigt);
|
||||||
|
console.log("videoWidth: ",videoWidth);
|
||||||
|
console.log("aspect ratio: ",aspectratio);
|
||||||
|
console.log("device: ",stream.getVideoTracks()[0].getSettings().deviceId);
|
||||||
|
videoContainer.style.setProperty("padding-bottom", "min("+videoHeigt+"px,"+aspectratio+"%)");
|
||||||
|
|
||||||
|
if (videoHeigt > videoWidth){ //hochkant video
|
||||||
|
videoContainer.style.setProperty("max-width", "min(80%,576");
|
||||||
|
}else{ //Normal, horizontal
|
||||||
|
videoContainer.style.setProperty("max-width", "100%");
|
||||||
|
}
|
||||||
|
|
||||||
videoDisplay.srcObject = stream;
|
videoDisplay.srcObject = stream;
|
||||||
|
|
||||||
buttonCameraIcon.src = ICON_PATHS.cameraofficon;
|
buttonCameraIcon.src = ICON_PATHS.cameraofficon;
|
||||||
buttonCameraIcon.alt = "Camera-off Icon";
|
buttonCameraIcon.alt = "Camera-off Icon";
|
||||||
buttonRecord.style.display = 'inline-block';
|
buttonRecord.style.display = 'inline-block';
|
||||||
buttonDelete.style.display = 'inline-block';
|
buttonDelete.style.display = 'inline-block';
|
||||||
videoDisplay.style.display = 'inline-block';
|
videoDisplay.style.display = 'block';
|
||||||
videoContainer.style.display = 'inline-block';
|
videoContainer.style.display = 'block';
|
||||||
|
videoDisplay.classList.add("videomirror");
|
||||||
|
|
||||||
mediaRecorder = new MediaRecorder(stream, {
|
mediaRecorder = new MediaRecorder(stream, {
|
||||||
mimeType: "video/webm", // could use different video format
|
mimeType: "video/webm", // could use different video format
|
||||||
@ -127,6 +147,7 @@ function recordButton() {
|
|||||||
if (!isRecording) {
|
if (!isRecording) {
|
||||||
console.log("recordButton pressed case isRecording = false");
|
console.log("recordButton pressed case isRecording = false");
|
||||||
deleteButton();
|
deleteButton();
|
||||||
|
videoDisplay.classList.add("videomirror");
|
||||||
buttonDelete.setAttribute("disabled", "");
|
buttonDelete.setAttribute("disabled", "");
|
||||||
buttonDeleteIcon.classList.add("buttondisable");
|
buttonDeleteIcon.classList.add("buttondisable");
|
||||||
videoDisplay.srcObject = stream;
|
videoDisplay.srcObject = stream;
|
||||||
@ -142,6 +163,7 @@ function recordButton() {
|
|||||||
} else {
|
} else {
|
||||||
console.log("recordButton pressed case isRecording = true");
|
console.log("recordButton pressed case isRecording = true");
|
||||||
mediaRecorder.stop();
|
mediaRecorder.stop();
|
||||||
|
videoDisplay.classList.remove("videomirror");
|
||||||
|
|
||||||
console.log("recording stops");
|
console.log("recording stops");
|
||||||
buttonDelete.removeAttribute("disabled");
|
buttonDelete.removeAttribute("disabled");
|
||||||
@ -154,6 +176,7 @@ function deleteButton() {
|
|||||||
videoDisplay.controls = false;
|
videoDisplay.controls = false;
|
||||||
videoDisplay.srcObject = stream;
|
videoDisplay.srcObject = stream;
|
||||||
recordedVideoBlob = null
|
recordedVideoBlob = null
|
||||||
|
videoDisplay.classList.add("videomirror");
|
||||||
buttonDelete.setAttribute("disabled", "");
|
buttonDelete.setAttribute("disabled", "");
|
||||||
buttonDeleteIcon.classList.add("buttondisable");
|
buttonDeleteIcon.classList.add("buttondisable");
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
|
<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>all links
|
||||||
</title>
|
</title>
|
||||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
21
slaeforms/templates/endpage.html
Normal file
21
slaeforms/templates/endpage.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!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')}}-->
|
||||||
|
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
|
||||||
|
<title>DGS Avatar Study</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h2>Danke für ihre Teilnahme!</h2>
|
||||||
|
|
||||||
|
<div class="textblock">
|
||||||
|
<p>
|
||||||
|
Falls sie noch Fragen oder Rückmeldungen haben, schreiben sie mir unter: jan.dickmann@web.de
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
21
slaeforms/templates/login.html
Normal file
21
slaeforms/templates/login.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<!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>Login Page</title>
|
||||||
|
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="login-container" >
|
||||||
|
<p>Some pages require you to be logged in.</p>
|
||||||
|
<form action="http://localhost:5000/login" method="post">
|
||||||
|
<input class="login-elements" type="password" value="" name="password" placeholder="password">
|
||||||
|
<input class="login-elements" id="loginbutton" type="submit" value="submit";/>
|
||||||
|
</form></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
68
slaeforms/templates/popuptest.html
Normal file
68
slaeforms/templates/popuptest.html
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<!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="container">
|
||||||
|
<h2>Hello! Thank you for participating in our study!</h2>
|
||||||
|
|
||||||
|
<dialog>
|
||||||
|
|
||||||
|
<button class="dialogBtn" autofocus><img class="infoButtonIcon" id="buttonClose" src="{{ url_for('static', filename='icons/x-icon.png')}}"
|
||||||
|
alt="Delete Icon"></button>
|
||||||
|
<div class="iframe-container">
|
||||||
|
<iframe class="center" src="https://www.youtube-nocookie.com/embed/F_w50c5Us3Y?si=-H07MmQ4lYOC2Bwh" title="YouTube video player" frameborder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="dialogTextContainer">
|
||||||
|
<p>Here is the text with the content of the explanation video.
|
||||||
|
Here participants can read the full text that is beeing shown in the video above in sign language.
|
||||||
|
From Feedback with a deaf sign language expert, we concluded that giving the whole text as additional resource
|
||||||
|
would be a good idea, since this would help if a participants looses track of the vide, can not understand the DGS dialect,
|
||||||
|
or just wants to read, because maybe that is what they prefer. This is especially relevant is the participant can not see very well.</p>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
<button class="dialogBtn"><img class="infoButtonIcon" id="buttonInfoIcon" src="{{ url_for('static', filename='icons/info-icon.png')}}"
|
||||||
|
alt="Info Icon"></button>
|
||||||
|
|
||||||
|
<div class="textblock">
|
||||||
|
<p>This is a placeholder EULA and Terms and Condition Text, this should obviously be replaced with some real Text later.
|
||||||
|
For now, be aware that this is a prototype for a sign language avatar study.
|
||||||
|
The Data that you input in this form, will be saved on our servers, but in an annonimized ways, so we can not infer who sent this data.
|
||||||
|
The Video recordings pose a special case. Any videos that are recorded, will be reviewed by sign language experts in our team,
|
||||||
|
the meaning will be transcribed in text form, then the video will be deleted.
|
||||||
|
If you have further questions, please send an email to testemail@notarealemail.deee
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<form class="dsgvoform" 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
|
||||||
|
</label>
|
||||||
|
<div class="button-container">
|
||||||
|
<input id="submitbutton" type = "submit" value = "submit" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const dialog = document.querySelector("dialog");
|
||||||
|
const showButton = document.querySelector("dialog + button");
|
||||||
|
const closeButton = document.querySelector("dialog button");
|
||||||
|
|
||||||
|
// "Show the dialog" button opens the dialog modally
|
||||||
|
showButton.addEventListener("click", () => {
|
||||||
|
dialog.showModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
// "Close" button closes the dialog
|
||||||
|
closeButton.addEventListener("click", () => {
|
||||||
|
dialog.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -3,17 +3,51 @@
|
|||||||
{% macro single_video(video_url, embed="yt", title="",width="560", height="315", class="center", code="<p>No code given
|
{% macro single_video(video_url, embed="yt", title="",width="560", height="315", class="center", code="<p>No code given
|
||||||
</p>") -%}
|
</p>") -%}
|
||||||
{% if (embed == "yt") %}
|
{% if (embed == "yt") %}
|
||||||
<div class={{center}}>
|
|
||||||
{% if (title != "") %}
|
{% if (title != "") %}
|
||||||
<h3>{{title}}</h3>
|
<h3>{{title}}</h3>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="video-container">
|
<div class="iframe-container">
|
||||||
<iframe width={{width}} height={{height}} class="center" src="{{ video_url }}" title="YouTube video player"
|
<iframe class="center" src="{{ video_url }}" title="YouTube video player" frameborder="0"
|
||||||
frameborder="0"
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
allowfullscreen></iframe>
|
||||||
allowfullscreen></iframe>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
{{code}}
|
||||||
|
{% endif %}
|
||||||
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{% macro double_video(video_url1, video_url2, embed="yt", title="",width="560", height="315", class="center", code="<p>
|
||||||
|
No code given
|
||||||
|
</p>") -%}
|
||||||
|
{% if (embed == "yt") %}
|
||||||
|
|
||||||
|
{% if (title != "") %}
|
||||||
|
<h3>{{title}}</h3>
|
||||||
|
{% endif %}
|
||||||
|
<div class="double_video_container">
|
||||||
|
|
||||||
|
<div class="dv_half">
|
||||||
|
<div class="iframe-container">
|
||||||
|
<iframe class="center" src="{{ video_url1 }}" title="YouTube video player" frameborder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div class="dv_half">
|
||||||
|
<div class="iframe-container">
|
||||||
|
<iframe class="center" src="{{ video_url2 }}" title="YouTube video player" frameborder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
{{code}}
|
{{code}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -27,13 +61,13 @@ required
|
|||||||
|
|
||||||
{% macro inputconfig(question) -%}
|
{% macro inputconfig(question) -%}
|
||||||
{% if ("min" in question) %}
|
{% if ("min" in question) %}
|
||||||
min={{question["min"]}}
|
min={{question["min"]}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if ("max" in question) %}
|
{% if ("max" in question) %}
|
||||||
max={{question["max"]}}
|
max={{question["max"]}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if ("step" in question) %}
|
{% if ("step" in question) %}
|
||||||
step={{question["step"]}}
|
step={{question["step"]}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
@ -47,39 +81,59 @@ step={{question["step"]}}
|
|||||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}" />
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}" />
|
||||||
<!-- styles.css {{ url_for('static', filename='styles.css')}}-->
|
<!-- styles.css {{ url_for('static', filename='styles.css')}}-->
|
||||||
<title>Testform</title>
|
<title>Testform</title>
|
||||||
<link rel=" shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
|
||||||
<script>const ICON_PATHS = {
|
<script>const ICON_PATHS = {
|
||||||
cameraofficon: "{{ url_for('static', filename='icons/camera-off-icon.png') }}",
|
cameraofficon: "{{ url_for('static', filename='icons/camera-off-icon.png') }}",
|
||||||
cameraicon: "{{ url_for('static', filename='icons/camera-icon.png') }}",
|
cameraicon: "{{ url_for('static', filename='icons/camera-icon.png') }}",
|
||||||
stopicon: "{{ url_for('static', filename='icons/stop-icon.png') }}",
|
stopicon: "{{ url_for('static', filename='icons/stop-icon.png') }}",
|
||||||
recordicon: "{{ url_for('static', filename='icons/record-icon.png') }}"
|
recordicon: "{{ url_for('static', filename='icons/record-icon.png') }}"
|
||||||
};</script>
|
};</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="centercontent">
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Stimulus part</h2>
|
|
||||||
|
{% if (infovideo) %}
|
||||||
|
<dialog>
|
||||||
|
|
||||||
|
<button class="dialogBtn" autofocus><img class="infoButtonIcon" id="buttonClose" src="{{ url_for('static', filename='icons/x-icon.png')}}"
|
||||||
|
alt="Delete Icon"></button>
|
||||||
|
<div class="iframe-container">
|
||||||
|
<iframe class="center" src="{{ infovideo['videourl'] }}" title="YouTube video player" frameborder="0"
|
||||||
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||||
|
allowfullscreen></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="dialogTextContainer">
|
||||||
|
<p>{{ infovideo["infotext"] }}</p>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
<button class="dialogBtn"><img class="infoButtonIcon" id="buttonInfoIcon" src="{{ url_for('static', filename='icons/info-icon.png')}}"
|
||||||
|
alt="Info Icon"></button>
|
||||||
|
<script src="{{ url_for('static', filename='infoDialogScript.js')}}"></script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% if (stimulus_type == "single_video") %}
|
{% if (stimulus_type == "single_video") %}
|
||||||
{{ single_video(**stimulus_configuration) }}
|
{{ single_video(**stimulus_configuration) }}
|
||||||
|
{% elif (stimulus_type == "double_video") %}
|
||||||
|
{{ double_video(**stimulus_configuration) }}
|
||||||
{% elif (stimulus_type == "empty") %}
|
{% elif (stimulus_type == "empty") %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>Error: Block {{ stimulus["type"] }} could not be loaded!</p>
|
<p>Error: Block {{ stimulus["type"] }} could not be loaded!</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<form class="formlayout" id="question_form" action="http://localhost:5000/send" method="post">
|
||||||
|
|
||||||
<h2>Questions</h2>
|
|
||||||
<form id="question_form" action="http://localhost:5000/send_json" method="post">
|
|
||||||
{% for question in questions %}
|
{% for question in questions %}
|
||||||
{% if (questions[question]["type"] == "likert") %}
|
{% if (questions[question]["type"] == "likert") %}
|
||||||
<div class="likercontainer">
|
<div class="likercontainer">
|
||||||
<div class="likert">
|
<h3>{{ questions[question]['text']}}</h3>
|
||||||
{{ questions[question]['text']}}
|
<div class="likert" style="--likert-rows: {{ questions[question]['points']|length() }}">
|
||||||
{% for point in questions[question]["points"] %}
|
{% for point in questions[question]["points"] %}
|
||||||
<label>
|
<label>
|
||||||
<input name="{{ questions[question]['name']}}" type="radio" id="{{ questions[question]['name'] }}"
|
<input name="{{ questions[question]['name']}}" type="radio"
|
||||||
|
id="{{ questions[question]['name'] }}"
|
||||||
value="{{ questions[question]['points'][point]['value'] }}"
|
value="{{ questions[question]['points'][point]['value'] }}"
|
||||||
{{required(questions[question])}} /><span>{{ questions[question]['points'][point]['text']
|
{{required(questions[question])}} /><span>{{ questions[question]['points'][point]['text']
|
||||||
}}</span>
|
}}</span>
|
||||||
@ -87,63 +141,71 @@ step={{question["step"]}}
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% elif (questions[question]["type"] == "textinput") %}
|
{% elif (questions[question]["type"] == "textinput") %}
|
||||||
<div class="textarea-container">
|
<div class="textarea-container">
|
||||||
<label class="textarea-label">
|
<label class="textarea-label">
|
||||||
{{ questions[question]['text']}}
|
{{ questions[question]['text']}}
|
||||||
<textarea id="{{ questions[question]['name'] }}" name="{{ questions[question]['name'] }}" rows="6"
|
<textarea id="{{ questions[question]['name'] }}" name="{{ questions[question]['name'] }}" rows="6"
|
||||||
cols="60" maxlength="{{ questions[question]['size'] }}"
|
cols="60" maxlength="{{ questions[question]['size'] }}"
|
||||||
{{required(questions[question])}}></textarea>
|
{{required(questions[question])}}></textarea>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% elif (questions[question]["type"] == "dateinput") %}
|
{% elif (questions[question]["type"] == "dateinput") %}
|
||||||
<label>
|
<div class="compressWidth">
|
||||||
{{ questions[question]['text']}}
|
<label>
|
||||||
<input type="date" name="{{ questions[question]['name']}}" id="{{ questions[question]['name'] }}" {{required(questions[question])}}>
|
{{ questions[question]['text']}}<input type="date" name="{{ questions[question]['name']}}"
|
||||||
</label>
|
id="{{ questions[question]['name'] }}" {{required(questions[question])}}>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
{% elif (questions[question]["type"] == "numberinput") %}
|
{% elif (questions[question]["type"] == "numberinput") %}
|
||||||
<label>
|
<div class="compressWidth">
|
||||||
{{ questions[question]['text']}}
|
<label>
|
||||||
<input type="number" name="{{ questions[question]['name']}}" id="{{ questions[question]['name'] }}" {{inputconfig(questions[question])}} {{required(questions[question])}}>
|
{{ questions[question]['text']}}<input type="number" name="{{ questions[question]['name']}}"
|
||||||
</label>
|
id="{{ questions[question]['name'] }}" {{inputconfig(questions[question])}}
|
||||||
|
{{required(questions[question])}}>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% elif (questions[question]["type"] == "emailinput") %}
|
{% elif (questions[question]["type"] == "emailinput") %}
|
||||||
<label>
|
<div class="compressWidth">
|
||||||
{{ questions[question]['text']}}
|
<label>
|
||||||
<input type="email" name="{{ questions[question]['name']}}" id="{{ questions[question]['name'] }}" {{required(questions[question])}}>
|
{{ questions[question]['text']}}<input type="email" name="{{ questions[question]['name']}}"
|
||||||
</label>
|
id="{{ questions[question]['name'] }}" {{required(questions[question])}}>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% elif (questions[question]["type"] == "dropdowninput") %}
|
{% elif (questions[question]["type"] == "dropdowninput") %}
|
||||||
<label>
|
<div class="compressWidth">
|
||||||
{{ questions[question]['text']}}
|
<label>
|
||||||
<select name="{{ questions[question]['name']}}" {{required(questions[question])}}>
|
{{ questions[question]['text']}}<select name="{{ questions[question]['name']}}"
|
||||||
<option value="" disabled selected>{{ questions[question]['defaulttext']}}</option>
|
{{required(questions[question])}}>
|
||||||
{% for point in questions[question]["points"] %}
|
<option value="" disabled selected>{{ questions[question]['defaulttext']}}</option>
|
||||||
|
{% for point in questions[question]["points"] %}
|
||||||
<option name="{{ point }}" id="{{ point }}"
|
<option name="{{ point }}" id="{{ point }}"
|
||||||
value="{{ questions[question]['points'][point]['value'] }}"
|
value="{{ questions[question]['points'][point]['value'] }}"
|
||||||
{{required(questions[question])}}><span>{{ questions[question]['points'][point]['text']
|
{{required(questions[question])}}><span>{{ questions[question]['points'][point]['text']
|
||||||
}}</span></option>
|
}}</span></option>
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% elif (questions[question]["type"] == "videoinput") %}
|
{% elif (questions[question]["type"] == "videoinput") %}
|
||||||
<h2>Gib Feedback als Video</h2>
|
<div class="spacer" aria-hidden="true" style="height:30px"></div>
|
||||||
|
<h3>{{ questions[question]['text']}}</h3>
|
||||||
|
|
||||||
<div class="centertext">
|
<div class="centertext">
|
||||||
{{ questions[question]['text']}}
|
|
||||||
<button type="button" class="videocontrols" id="buttonCamera" onclick="cameraButton()">
|
<button type="button" class="videocontrols" id="buttonCamera" onclick="cameraButton()">
|
||||||
<img id="buttonCameraIcon" src="{{ url_for('static', filename='icons/camera-icon.png')}}"
|
<img id="buttonCameraIcon" src="{{ url_for('static', filename='icons/camera-icon.png')}}"
|
||||||
alt="Camera Icon">
|
alt="Camera Icon">
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="spacer" aria-hidden="true" style="height:30px"></div>
|
<div class="spacer" aria-hidden="true" style="height:30px"></div>
|
||||||
<div class="centertext">
|
|
||||||
|
|
||||||
<button type="button" class="videocontrols" id="buttonRecord" style="display:none"
|
<button type="button" class="videocontrols" id="buttonRecord" style="display:none"
|
||||||
onclick="recordButton()">
|
onclick="recordButton()">
|
||||||
<img id="buttonRecordIcon" src="{{ url_for('static', filename='icons/record-icon.png')}}"
|
<img id="buttonRecordIcon" src="{{ url_for('static', filename='icons/record-icon.png')}}"
|
||||||
@ -155,27 +217,26 @@ step={{question["step"]}}
|
|||||||
<img id="buttonDeleteIcon" src="{{ url_for('static', filename='icons/trash-icon.png')}}"
|
<img id="buttonDeleteIcon" src="{{ url_for('static', filename='icons/trash-icon.png')}}"
|
||||||
alt="Delete Icon" class="buttondisable">
|
alt="Delete Icon" class="buttondisable">
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="spacer" aria-hidden="true" style="height:15px"></div>
|
<div class="spacer" aria-hidden="true" style="height:15px"></div>
|
||||||
<div id="videoContainer" class="centertext, aspect-ratio-16-9" style="display:none">
|
<div id="videoContainer" style="display:none" class="video-container">
|
||||||
<video autoplay muted playsinline id="videoDisplay"></video>
|
<video autoplay muted playsinline id="videoDisplay"></video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="{{ url_for('static', filename='videoscript.js')}}">
|
<script src="{{ url_for('static', filename='videoscript.js')}}">
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>Error: Block {{config["question 1"]["blocks"][block]["type"]}} could not be loaded!</p>
|
<p>Error: Block {{config["question 1"]["blocks"][block]["type"]}} could not be loaded!</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
<p><input id="submitbutton" type="submit" value="submit" ; /></p>
|
<input id="submitbutton" type="submit" value="submit" />
|
||||||
<!-- TODO maybe I want to use this instead: <button id="submitbutton" type="submit">Submit</button> -->
|
<!-- TODO maybe I want to use this instead: <button id="submitbutton" type="submit">Submit</button> -->
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -8,7 +8,6 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="centercontent">
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Hello! Thank you for participating in our study!</h2>
|
<h2>Hello! Thank you for participating in our study!</h2>
|
||||||
<form action="http://localhost:5000/start" method="post">
|
<form action="http://localhost:5000/start" method="post">
|
||||||
@ -19,7 +18,6 @@
|
|||||||
<p><input id="submitbutton" type = "submit" value = "submit" /></p>
|
<p><input id="submitbutton" type = "submit" value = "submit" /></p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -5,18 +5,16 @@
|
|||||||
<meta charset="utf-8" />
|
<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')}}-->
|
<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>
|
<title>Test Page 0 - Nothing</title>
|
||||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="centercontent"></div>
|
|
||||||
<div class="container">
|
<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>
|
<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">
|
<form action="http://localhost:5000/send_json" method="post">
|
||||||
<input type="hidden" name="submittedString" value="Hello, backend!">
|
<input type="hidden" name="submittedString" value="Hello, backend!">
|
||||||
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
|
<input id="submitbutton" type = "submit" value = "submit";/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -9,7 +9,6 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="centercontent">
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<p>This is just a test page for the single page option of the json configuration</p>
|
<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">
|
<form action="http://localhost:5000/send_json" method="post">
|
||||||
@ -22,6 +21,5 @@
|
|||||||
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
|
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,25 +1,45 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<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')}}-->
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
|
||||||
|
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
|
||||||
<title>DGS Avatar Study</title>
|
<title>DGS Avatar Study</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="centercontent">
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2>Hello! Thank you for participating in our study!</h2>
|
<h2>Hallo! Danke, dass sie bei dieser Studie teilnehmen!</h2>
|
||||||
<form action="http://localhost:5000/teststart" method="post">
|
|
||||||
|
<div class="textblock">
|
||||||
|
<p>
|
||||||
|
Wilkommen bei dieser Prototyp Seite für eine Gebärdensprach-Avatar Studie.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Dieses Projekt ist noch in Arbeit und nicht fertiggestellt. Es soll die bisher implementierten Funktionen zeigen und wurde nur zu Präsentationszwecken und um Feedback zu sammeln online gestellt.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Das Ziel dieses Projekt ist, es Wissenschaftlern (und anderen) zu ermöglichen relativ einfach online Studien zu erstellen, die auf die besonderen Anforderungen von Gehörlosen Studienteilnehmern abgestimmt sind.
|
||||||
|
Dazu zählt insbesondere die Möglichkeit Videos aufnehmen zu können um Feedback zu geben, und das Einbinden von Videos die Fragen und Aufgabenstellungen in Gebärdensprache erklären sollen.
|
||||||
|
In diesem Prototyp finden sich aktuell aber nur Platzhaltervideos. Keine der Inhalte sind bereits Teil einer echten Studie.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Wenn sie den Prototyp verwenden und dabei Eingaben machen oder Videos hochladen, sind diese auf dem Server gespeichert. Die Daten werden nach der Testphase gelöscht und können bis dahin nur von dem Admin eingesehen werden.
|
||||||
|
Wenn sie eingegebene oder hochgeladene Daten löschen wollen, wenden sie sich bitte an den Admin unter: jan.dickmann@web.de
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Auch bei sonstigen Fragen, wenden sie sich bitte an: jan.dickmann@web.de
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<form class="dsgvoform" action="http://localhost:5000/start" method="post">
|
||||||
<label for="terms-and-conditions">
|
<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>
|
<input class="inline" id="terms-and-conditions" type="checkbox" required name="terms-and-conditions" /> I accept the +terms and conditions
|
||||||
</label>
|
</label>
|
||||||
|
<div class="button-container">
|
||||||
<p><input id="submitbutton" type = "submit" value = "submit" /></p>
|
<input id="submitbutton" type = "submit" value = "submit" />
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
slaeforms/zip_exports/all_tables.zip
Normal file
BIN
slaeforms/zip_exports/all_tables.zip
Normal file
Binary file not shown.
BIN
slaeforms/zip_exports/all_videos.zip
Normal file
BIN
slaeforms/zip_exports/all_videos.zip
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user