Compare commits
10 Commits
ccf02f2e16
...
645fac2e9d
Author | SHA1 | Date | |
---|---|---|---|
645fac2e9d | |||
3918d81495 | |||
a75efd7a5f | |||
3e482d2e49 | |||
f4bb2959e6 | |||
11f051c7d9 | |||
18ec49903b | |||
46f5979aef | |||
2f6a42d39a | |||
75202fe7a5 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,6 +5,8 @@ __pycache__/
|
||||
|
||||
instance/
|
||||
|
||||
uploads/
|
||||
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
91
README.md
91
README.md
@ -1,93 +1,8 @@
|
||||
# masterproject
|
||||
|
||||
|
||||
build the dockerfile with: docker build -t slaeforms .
|
||||
|
||||
## Getting started
|
||||
run the container on port 8000 with: docker run -d -p 8000:8000 slaeforms
|
||||
|
||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
||||
|
||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
||||
|
||||
## Add your files
|
||||
|
||||
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
||||
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
|
||||
|
||||
```
|
||||
cd existing_repo
|
||||
git remote add origin https://gitlab.com/JanDickmann/masterproject.git
|
||||
git branch -M main
|
||||
git push -uf origin main
|
||||
```
|
||||
|
||||
## Integrate with your tools
|
||||
|
||||
- [ ] [Set up project integrations](https://gitlab.com/JanDickmann/masterproject/-/settings/integrations)
|
||||
|
||||
## Collaborate with your team
|
||||
|
||||
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
||||
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
||||
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
||||
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
||||
- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
|
||||
|
||||
## Test and Deploy
|
||||
|
||||
Use the built-in continuous integration in GitLab.
|
||||
|
||||
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
|
||||
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
||||
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
||||
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
||||
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
||||
|
||||
***
|
||||
|
||||
# Editing this README
|
||||
|
||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
||||
|
||||
## Suggestions for a good README
|
||||
|
||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
||||
|
||||
## Name
|
||||
Choose a self-explaining name for your project.
|
||||
|
||||
## Description
|
||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
||||
|
||||
## Badges
|
||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
||||
|
||||
## Visuals
|
||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
||||
|
||||
## Installation
|
||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
||||
|
||||
## Usage
|
||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
||||
|
||||
## Support
|
||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
||||
|
||||
## Roadmap
|
||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
||||
|
||||
## Contributing
|
||||
State if you are open to contributions and what your requirements are for accepting them.
|
||||
|
||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
||||
|
||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
||||
|
||||
## Authors and acknowledgment
|
||||
Show your appreciation to those who have contributed to the project.
|
||||
|
||||
## License
|
||||
For open source projects, say how it is licensed.
|
||||
|
||||
## Project status
|
||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
||||
export the container: docker save -o slaeforms.tar slaforms:latest
|
134
slaeforms/app.py
134
slaeforms/app.py
@ -40,6 +40,7 @@ app.secret_key = b"29fe9e8edd407c5491d4f1c05632d9fa33e26ed8734a3f5e080ebac3772a5
|
||||
|
||||
UPLOAD_FOLDER = 'uploads'
|
||||
EXPORT_FOLDER = 'exports'
|
||||
PASSWORD = '#1ACGmsjd'
|
||||
|
||||
#csrf = CSRFProtect(app) #enable CSRF protection globally
|
||||
|
||||
@ -53,6 +54,7 @@ class User(db.Model):
|
||||
device_id = db.Column("device_id",db.UUID(as_uuid=True), nullable=False)
|
||||
question_order = db.Column("question_order",db.String(60))
|
||||
date_created = db.Column("date_created",db.DateTime, default=datetime.today()) # todo test if this default works
|
||||
form_completed = db.Column("form_completed",db.Boolean, default=False)
|
||||
def __repr__(self) -> str:
|
||||
return "<User %r>" % self.user_id
|
||||
|
||||
@ -131,7 +133,7 @@ except SQLAlchemyError as e:
|
||||
#------------------------------------------------------------------------------
|
||||
#actual page logic with start, form and send
|
||||
|
||||
@app.route("/popuptest", methods=["GET"])
|
||||
#@app.route("/popuptest", methods=["GET"])
|
||||
def popuptest():
|
||||
|
||||
return render_template(
|
||||
@ -147,6 +149,7 @@ def startpage():
|
||||
new_device_id = uuid.uuid4()
|
||||
session["slaeform_device_id"] = new_device_id
|
||||
session["agreed_to_tos"] = False
|
||||
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
@ -178,6 +181,7 @@ def startpage():
|
||||
print(session["block_names"])
|
||||
|
||||
for name in block_names:
|
||||
print("block: ",name)
|
||||
if config[name]["type"] == "TaskTemplate" and ("stimuli" in current_block):
|
||||
match config[name]["stimuli"]["type"]:
|
||||
case "single_video":
|
||||
@ -188,9 +192,9 @@ def startpage():
|
||||
session["block_order"][name] = order
|
||||
case "double_video":
|
||||
order = [] # order = list of stimuli keys
|
||||
list_1 = list(current_block["stimuli"]["list_1"])
|
||||
list_2 = list(current_block["stimuli"]["list_2"])
|
||||
for i in range(len(list(current_block["stimuli"]["list_1"]))):
|
||||
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
|
||||
@ -213,11 +217,12 @@ def startpage():
|
||||
user_id = new_user_id
|
||||
question_order = str(session["block_order"])
|
||||
date = datetime.today()
|
||||
new_user = User(user_id=user_id, device_id=device_id,question_order=question_order,date_created = date) #,question_order=question_order
|
||||
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.commit()
|
||||
|
||||
|
||||
print("block order: {order}".format(order=session["block_order"]))
|
||||
try:
|
||||
db.session.add(new_user)
|
||||
@ -231,6 +236,11 @@ def startpage():
|
||||
"teststartpage.html"
|
||||
)
|
||||
|
||||
@app.route("/endpage")
|
||||
def endpage():
|
||||
print("Form is done, sent to endpage")
|
||||
return render_template("endpage.html")
|
||||
|
||||
|
||||
@app.route("/form")
|
||||
def form():
|
||||
@ -248,10 +258,14 @@ def form():
|
||||
#print("current Blockname: {blockname}, current block index: {blockindex}, current stim index: {stimulusindex}".format(blockname=session["current_block_name"],
|
||||
# blockindex=session["current_block_index"],
|
||||
# stimulusindex=session["current_stimulus_index"] ) )
|
||||
infovideo = None
|
||||
if "infovideo" in current_block:
|
||||
infovideo = current_block["infovideo"]
|
||||
|
||||
# erster Fall: SinglePage
|
||||
if current_block["type"] == "SinglePage":
|
||||
return render_template(current_block["template"])
|
||||
|
||||
|
||||
#zweiter Fall, empty TaskTemplate
|
||||
if current_block["type"] == "TaskTemplate" and current_block["stimuli"]["type"] == "empty":
|
||||
@ -264,7 +278,8 @@ def form():
|
||||
stimuli=current_block_stimuli,
|
||||
stimulus_type=stimulus_type,
|
||||
current_stimulus=current_stimulus,
|
||||
questions=current_block["questions"]
|
||||
questions=current_block["questions"],
|
||||
infovideo=infovideo
|
||||
)
|
||||
|
||||
|
||||
@ -293,7 +308,8 @@ def form():
|
||||
stimulus_type=stimulus_type,
|
||||
current_stimulus=current_stimulus,
|
||||
stimulus_configuration=stimulus_configuration,
|
||||
questions=current_block["questions"]
|
||||
questions=current_block["questions"],
|
||||
infovideo=infovideo
|
||||
)
|
||||
|
||||
|
||||
@ -369,23 +385,39 @@ def sendpage():
|
||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
||||
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
|
||||
update_session()
|
||||
print("now redirect and reload the page")
|
||||
return redirect("/form")
|
||||
|
||||
def update_session():
|
||||
print("update session")
|
||||
if "stimuli" in config[session["current_block_name"]]:
|
||||
# if there are stimuli in this block
|
||||
if session["current_stimulus_index"] < session["number_of_stimuli"]-1:
|
||||
|
||||
print("there are still stimuli left")
|
||||
# if there are still stimuli left, keep going through them
|
||||
session["current_stimulus_index"] += 1
|
||||
# set the name of the current stimulus
|
||||
session["current_stimulus_name"] = session["block_order"][session["current_block_name"]][session["current_stimulus_index"]]
|
||||
|
||||
else:
|
||||
print("here are no stimuli left")
|
||||
session["number_of_stimuli"] = 0
|
||||
# if there are no stimuli left..
|
||||
if(session["current_block_index"] < session["number_of_blocks"]-1):
|
||||
# go to next block if possible
|
||||
@ -393,7 +425,7 @@ def update_session():
|
||||
session["current_block_name"] = session["block_names"][session["current_block_index"]]
|
||||
session["current_stimulus_index"] = 0
|
||||
if "stimuli" in config[session["current_block_name"]]:
|
||||
session["number_of_stimuli"] = len(list(config[session["current_block_name"]]["stimuli"]["list"]))
|
||||
session["number_of_stimuli"] = len(session["block_order"][session["current_block_name"]])
|
||||
|
||||
else:
|
||||
# if there arent any stimuli, go to the next block
|
||||
@ -406,15 +438,27 @@ def update_session():
|
||||
if "stimuli" in config[session["current_block_name"]]:
|
||||
# set the name of the current stimulus
|
||||
session["current_stimulus_name"] = session["block_order"][session["current_block_name"]][session["current_stimulus_index"]]
|
||||
|
||||
if (session["current_block_index"] == session["number_of_blocks"]-1) and (session["current_stimulus_index"] >= session["number_of_stimuli"]-1):
|
||||
session.pop("slaeform_user_id")
|
||||
print("---Session updated---")
|
||||
|
||||
print("---Session updated-----------------------------------------------")
|
||||
print("current_block_index / number_of_blocks: {current_block_index} / {number_of_blocks}".format(current_block_index=session["current_block_index"],number_of_blocks=session["number_of_blocks"]))
|
||||
print("current_block_name: ", session["current_block_name"])
|
||||
print("current_stimulus_index: ", session["current_stimulus_index"])
|
||||
print("Current number_of_stimuli: ", session["number_of_stimuli"])
|
||||
|
||||
#@app.route("/update")
|
||||
def update():
|
||||
|
||||
print("Current Session: ",session)
|
||||
try:
|
||||
user = db.session.query(User).filter_by(user_id=session["slaeform_user_id"]).one()
|
||||
user.form_completed = True
|
||||
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 "db entry updated!"
|
||||
|
||||
|
||||
|
||||
# Database stuff------------------------------------------------------------------------------
|
||||
@ -439,6 +483,9 @@ def create_all_csvs():
|
||||
# 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
|
||||
@ -449,7 +496,9 @@ def export_all_tables():
|
||||
# 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)
|
||||
|
||||
@ -489,24 +538,13 @@ def export_csv(table_name):
|
||||
# the contents of all tables
|
||||
@app.route("/table_contents")
|
||||
def table_contents():
|
||||
if not session.get("logged_in"):
|
||||
return redirect("/login")
|
||||
meta = db.metadata
|
||||
#meta.reflect(db.engine) # Uncomment this to also get the hidden tables, but this crashes rn
|
||||
tables = meta.tables.keys()
|
||||
table_contents = {}
|
||||
|
||||
#Testing querys for exporting etc
|
||||
print("tables: ",tables)
|
||||
print("testquerys:")
|
||||
qtable = meta.tables["default_demographic_test"]
|
||||
query1 = select(qtable).where(qtable.c.alter == 78)
|
||||
print("Query 1: ", query1)
|
||||
print("Query 1 result: ")
|
||||
result = db.session.execute(query1)
|
||||
print("Columns: ", result.columns)
|
||||
for row in result:
|
||||
print(row)
|
||||
#Test End
|
||||
|
||||
for table_name in tables:
|
||||
table = meta.tables[table_name]
|
||||
columns = table.columns.keys()
|
||||
@ -532,27 +570,32 @@ def table_contents():
|
||||
|
||||
@app.route('/show_tables')
|
||||
def show_tables():
|
||||
if not session.get("logged_in"):
|
||||
return redirect("/login")
|
||||
meta = db.metadata
|
||||
meta.reflect(db.engine)
|
||||
tables = meta.tables
|
||||
return render_template('show_tables.html', tables=tables)
|
||||
|
||||
# Control Panel ---------------------------------------------------------
|
||||
|
||||
@app.route("/upload_configs")
|
||||
def upload_configs():
|
||||
links = []
|
||||
for rule in app.url_map.iter_rules():
|
||||
# Filter out rules we can't navigate to in a browser
|
||||
# and rules that require parameters
|
||||
if "GET" in rule.methods and has_no_empty_params(rule):
|
||||
url = url_for(rule.endpoint, **(rule.defaults or {}))
|
||||
links.append((url, rule.endpoint))
|
||||
return render_template("all_links.html", links=links)
|
||||
|
||||
|
||||
# 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):
|
||||
defaults = rule.defaults if rule.defaults is not None else ()
|
||||
arguments = rule.arguments if rule.arguments is not None else ()
|
||||
@ -572,7 +615,7 @@ def all_links():
|
||||
# delete all tables as last link --------------------------
|
||||
|
||||
# Route to delete all entries
|
||||
@app.route('/delete_json_tables', methods=['GET'])
|
||||
#@app.route('/delete_json_tables', methods=['GET'])
|
||||
def delete_json_tables():
|
||||
try:
|
||||
meta = db.metadata
|
||||
@ -596,6 +639,8 @@ def delete_json_tables():
|
||||
# Route to delete all entries
|
||||
@app.route('/delete_all_entries', methods=['GET'])
|
||||
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
|
||||
# this together with the reflect could drop actually all tables
|
||||
try:
|
||||
@ -612,5 +657,8 @@ def delete_all_entries():
|
||||
# Close the session
|
||||
db.session.close()
|
||||
|
||||
def create_app():
|
||||
return app
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
||||
app.run()
|
||||
|
@ -1,66 +1,15 @@
|
||||
{
|
||||
"Block 0":{
|
||||
"Block 1": {
|
||||
"type": "TaskTemplate",
|
||||
"tempalte": "standard_template.html",
|
||||
"stimuli":{
|
||||
"type":"double_video",
|
||||
"list_1":{
|
||||
"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"
|
||||
"stimuli": {
|
||||
"type": "empty",
|
||||
"list": {
|
||||
"empty_stimulus": ""
|
||||
}
|
||||
},
|
||||
"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_responses",
|
||||
"fields": {
|
||||
"likertscale":{
|
||||
"type": "integer",
|
||||
"nullable": "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Block 1":{
|
||||
"type": "TaskTemplate",
|
||||
"tempalte": "standard_template.html",
|
||||
"stimuli":{
|
||||
"type":"empty",
|
||||
"list":{
|
||||
"empty_stimulus":""
|
||||
}
|
||||
},
|
||||
"questions":{
|
||||
"question1_alter":{
|
||||
"questions": {
|
||||
"question1_alter": {
|
||||
"type": "numberinput",
|
||||
"name": "alter",
|
||||
"text": "Alter:",
|
||||
@ -68,74 +17,74 @@
|
||||
"min": "1",
|
||||
"max": "120"
|
||||
},
|
||||
"question2_geschlecht":{
|
||||
"question2_geschlecht": {
|
||||
"type": "dropdowninput",
|
||||
"name": "geschlecht",
|
||||
"text": "Geschlecht:",
|
||||
"required": "true",
|
||||
"defaulttext": "",
|
||||
"points":{
|
||||
"männlich":{
|
||||
"value":"Männlich",
|
||||
"text":"Männlich"
|
||||
"points": {
|
||||
"männlich": {
|
||||
"value": "Männlich",
|
||||
"text": "Männlich"
|
||||
},
|
||||
"weiblich":{
|
||||
"value":"Weiblich",
|
||||
"text":"Weiblich"
|
||||
"weiblich": {
|
||||
"value": "Weiblich",
|
||||
"text": "Weiblich"
|
||||
},
|
||||
"divers":{
|
||||
"value":"Divers",
|
||||
"text":"Divers"
|
||||
"divers": {
|
||||
"value": "Divers",
|
||||
"text": "Divers"
|
||||
},
|
||||
"keine_angabe":{
|
||||
"value":"keine_angabe",
|
||||
"text":"Keine Angabe"
|
||||
"keine_angabe": {
|
||||
"value": "keine_angabe",
|
||||
"text": "Keine Angabe"
|
||||
}
|
||||
}
|
||||
},
|
||||
"question3_hoerstatus":{
|
||||
"question3_hoerstatus": {
|
||||
"type": "dropdowninput",
|
||||
"name": "hoerstatus",
|
||||
"text": "Hörstatus:",
|
||||
"required": "true",
|
||||
"defaulttext": "",
|
||||
"points":{
|
||||
"hörend":{
|
||||
"value":"Hörend",
|
||||
"text":"Hörend"
|
||||
"points": {
|
||||
"hörend": {
|
||||
"value": "Hörend",
|
||||
"text": "Hörend"
|
||||
},
|
||||
"schwerhörig":{
|
||||
"value":"Schwerhörig",
|
||||
"text":"Schwerhörig"
|
||||
"schwerhörig": {
|
||||
"value": "Schwerhörig",
|
||||
"text": "Schwerhörig"
|
||||
},
|
||||
"gehörlos":{
|
||||
"value":"Gehörlos",
|
||||
"text":"Gehörlos"
|
||||
"gehörlos": {
|
||||
"value": "Gehörlos",
|
||||
"text": "Gehörlos"
|
||||
}
|
||||
}
|
||||
},
|
||||
"question4_bevorzugte_kommunikation":{
|
||||
"question4_bevorzugte_kommunikation": {
|
||||
"type": "dropdowninput",
|
||||
"name": "bevorzugte_kommunikation",
|
||||
"text": "Bevorzugte Kommunikationsform:",
|
||||
"required": "true",
|
||||
"defaulttext": "",
|
||||
"points":{
|
||||
"gesprochen":{
|
||||
"value":"Gesprochene Sprache",
|
||||
"text":"Gesprochene Sprache"
|
||||
"points": {
|
||||
"gesprochen": {
|
||||
"value": "Gesprochene Sprache",
|
||||
"text": "Gesprochene Sprache"
|
||||
},
|
||||
"text":{
|
||||
"value":"Text",
|
||||
"text":"Text"
|
||||
"text": {
|
||||
"value": "Text",
|
||||
"text": "Text"
|
||||
},
|
||||
"gebärdensprache":{
|
||||
"value":"Gebärdensprache",
|
||||
"text":"Gebärdensprache"
|
||||
"gebärdensprache": {
|
||||
"value": "Gebärdensprache",
|
||||
"text": "Gebärdensprache"
|
||||
}
|
||||
}
|
||||
},
|
||||
"question5_gebeardenzeitraum":{
|
||||
"question5_gebeardenzeitraum": {
|
||||
"type": "numberinput",
|
||||
"name": "gebärdenzeitraum",
|
||||
"text": "Wie viele Jahre verwenden sie schon Gebärdensprache:",
|
||||
@ -144,7 +93,7 @@
|
||||
"max": "100",
|
||||
"step": "0.5"
|
||||
},
|
||||
"question6_sprachkompetenz":{
|
||||
"question6_sprachkompetenz": {
|
||||
"type": "numberinput",
|
||||
"name": "gebärdensprachkompetenz",
|
||||
"text": "Wie schätzen sie ihre Gebärdensprachkompetenz ein (1-10):",
|
||||
@ -153,110 +102,123 @@
|
||||
"max": "10"
|
||||
}
|
||||
},
|
||||
"database_table" :{
|
||||
"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. 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": {
|
||||
"alter":{
|
||||
"alter": {
|
||||
"type": "integer",
|
||||
"nullable": "false"
|
||||
},
|
||||
"geschlecht":{
|
||||
"geschlecht": {
|
||||
"type": "string",
|
||||
"size": "14",
|
||||
"nullable": "false"
|
||||
},
|
||||
"hoerstatus":{
|
||||
"hoerstatus": {
|
||||
"type": "string",
|
||||
"size": "14",
|
||||
"nullable": "false"
|
||||
},
|
||||
"bevorzugte_kommunikation":{
|
||||
"bevorzugte_kommunikation": {
|
||||
"type": "string",
|
||||
"size": "22",
|
||||
"nullable": "false"
|
||||
},
|
||||
"gebärdenzeitraum":{
|
||||
"gebärdenzeitraum": {
|
||||
"type": "float",
|
||||
"nullable": "false"
|
||||
},
|
||||
"gebärdensprachkompetenz":{
|
||||
"gebärdensprachkompetenz": {
|
||||
"type": "integer",
|
||||
"nullable": "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Block 2":{
|
||||
"Block 2": {
|
||||
"type": "TaskTemplate",
|
||||
"tempalte": "standard_template.html",
|
||||
"stimuli":{
|
||||
"type":"single_video",
|
||||
"stimuli": {
|
||||
"type": "single_video",
|
||||
"order": "random",
|
||||
"list":{
|
||||
"video_1":"https://www.youtube-nocookie.com/embed/VtnwHmabyzo?si=H3rrG-GHtlSymR70",
|
||||
"video_2":"https://www.youtube-nocookie.com/embed/EL76Ok4r0aQ?si=hqUm8eUUfX39NN4L",
|
||||
"video_3":"https://www.youtube-nocookie.com/embed/XTMIomsXxKM?si=r2zB6OKERH6Jdpi6"
|
||||
"list": {
|
||||
"video_1": "https://www.youtube-nocookie.com/embed/IqGVT1q1PtM?si=kel7ZWEQl3h-h522",
|
||||
"video_2": "https://www.youtube-nocookie.com/embed/g9KA72jN5SM?si=O7dfqTXdFCCAScJ-"
|
||||
},
|
||||
"configuration":{
|
||||
"embed":"yt"
|
||||
"configuration": {
|
||||
"embed": "yt"
|
||||
}
|
||||
},
|
||||
"questions":{
|
||||
"question1":{
|
||||
"questions": {
|
||||
"question1": {
|
||||
"type": "likert",
|
||||
"name": "likertscale",
|
||||
"text": "How would you rate this video?",
|
||||
"text": "Wie würden sie dieses Video bewerten?",
|
||||
"required": "true",
|
||||
"points":{
|
||||
"p1":{
|
||||
"value":"1",
|
||||
"text":"I dont like it at all"
|
||||
"points": {
|
||||
"p1": {
|
||||
"value": "1",
|
||||
"text": "Ich finde es gar nicht gut 🙁👎"
|
||||
},
|
||||
"p2":{
|
||||
"value":"2",
|
||||
"text":"I dont like it"
|
||||
"p2": {
|
||||
"value": "2",
|
||||
"text": "Ich finde es nicht gut 👎"
|
||||
},
|
||||
"p3":{
|
||||
"value":"3",
|
||||
"text":"I am indifferent"
|
||||
"p3": {
|
||||
"value": "3",
|
||||
"text": "Ich finde es weder gut noch schlecht"
|
||||
},
|
||||
"p4":{
|
||||
"value":"4",
|
||||
"text":"I like it"
|
||||
"p4": {
|
||||
"value": "4",
|
||||
"text": "Ich finde es gut 👍"
|
||||
},
|
||||
"p5":{
|
||||
"value":"5",
|
||||
"text":"I like it a lot"
|
||||
"p5": {
|
||||
"value": "5",
|
||||
"text": "Ich finde es sehr gut 😊👍"
|
||||
}
|
||||
}
|
||||
},
|
||||
"question2":{
|
||||
"question2": {
|
||||
"type": "textinput",
|
||||
"name": "text_feedback",
|
||||
"text": "Here you can give us Feedback",
|
||||
"text": "Hier können sie uns Feedback geben",
|
||||
"required": "false",
|
||||
"size": "250"
|
||||
},
|
||||
"question3":{
|
||||
"question3": {
|
||||
"type": "videoinput",
|
||||
"text": "Here you can give us Feedback as video",
|
||||
"text": "Hier können sie per Video Feedback geben",
|
||||
"name": "video_feedback",
|
||||
"required": "false"
|
||||
}
|
||||
},
|
||||
"database_table" :{
|
||||
"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": {
|
||||
"likertscale":{
|
||||
"likertscale": {
|
||||
"type": "integer",
|
||||
"nullable": "false"
|
||||
},
|
||||
"text_feedback":{
|
||||
"text_feedback": {
|
||||
"type": "string",
|
||||
"size": "250",
|
||||
"nullable": "true"
|
||||
},
|
||||
"video_upload":{
|
||||
"video_upload": {
|
||||
"type": "string",
|
||||
"size": "100",
|
||||
"nullable": "true"
|
||||
@ -264,8 +226,96 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Block_3":{
|
||||
"type": "SinglePage",
|
||||
"template": "endpage.html"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
slaeforms/dockerfile
Normal file
17
slaeforms/dockerfile
Normal file
@ -0,0 +1,17 @@
|
||||
# Use the official Python image from the Docker Hub
|
||||
FROM python:3.10-slim
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /slaeforms
|
||||
|
||||
# Copy the rest of the application code to the working directory
|
||||
COPY . .
|
||||
|
||||
# Install the dependencies
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Expose the port on which the app will run
|
||||
EXPOSE 8000
|
||||
|
||||
# Define the command to run the application using Gunicorn
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:create_app()"]
|
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
|
|
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,,
|
|
@ -1,2 +1,4 @@
|
||||
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
|
||||
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
|
||||
|
|
16
slaeforms/requirements.txt
Normal file
16
slaeforms/requirements.txt
Normal file
@ -0,0 +1,16 @@
|
||||
blinker==1.8.2
|
||||
click==8.1.7
|
||||
colorama==0.4.6
|
||||
Flask==3.0.3
|
||||
Flask-SQLAlchemy==3.1.1
|
||||
Flask-WTF==1.2.1
|
||||
greenlet==3.0.3
|
||||
gunicorn==22.0.0
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.4
|
||||
MarkupSafe==2.1.5
|
||||
packaging==24.1
|
||||
SQLAlchemy==2.0.30
|
||||
typing_extensions==4.12.2
|
||||
Werkzeug==3.0.3
|
||||
WTForms==3.1.2
|
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();
|
||||
});
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #a4b5ff;
|
||||
@ -18,7 +16,7 @@ dialog {
|
||||
width: 66%;
|
||||
}
|
||||
|
||||
dialog .iframe-container{
|
||||
dialog .iframe-container {
|
||||
border-top: 1px solid #ccc;
|
||||
margin-top: 20px;
|
||||
}
|
||||
@ -40,8 +38,9 @@ dialog .iframe-container{
|
||||
height: 90px;
|
||||
padding: 8px;
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 80vw;
|
||||
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 {
|
||||
@ -50,22 +49,52 @@ dialog .iframe-container{
|
||||
}
|
||||
|
||||
|
||||
.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 {
|
||||
margin: 0 auto;
|
||||
/* height: 100vh;*/
|
||||
width: 60vw; /* You can adjust this width as needed */
|
||||
max-width: 1200px; /* Maximum width to keep it from getting too wide on large screens */
|
||||
width: 66vw;
|
||||
/* You can adjust this width as needed */
|
||||
max-width: 1690px;
|
||||
/* Maximum width to keep it from getting too wide on large screens */
|
||||
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);
|
||||
}
|
||||
|
||||
.textblock {
|
||||
width: 60%;
|
||||
display: flex;
|
||||
margin: auto;
|
||||
text-align: left;
|
||||
}
|
||||
@ -101,10 +130,12 @@ select {
|
||||
.button-container input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 30px; /* Adjust as needed for spacing */
|
||||
margin-top: 30px;
|
||||
/* Adjust as needed for spacing */
|
||||
margin-right: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
margin: 0 auto;
|
||||
width: 60%;
|
||||
@ -117,14 +148,20 @@ select {
|
||||
|
||||
#submitbutton {
|
||||
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 */
|
||||
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 */
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#submitbutton:hover {
|
||||
background-color: #0056b3; /* Darker shade for hover effect */
|
||||
background-color: #0056b3;
|
||||
/* Darker shade for hover effect */
|
||||
}
|
||||
|
||||
|
||||
@ -132,13 +169,15 @@ select {
|
||||
margin: auto;
|
||||
width: 60%;
|
||||
}
|
||||
.textarea-container textarea{
|
||||
|
||||
.textarea-container textarea {
|
||||
width: 100%;
|
||||
margin: 1.5rem auto;
|
||||
}
|
||||
|
||||
.textarea-label{
|
||||
align-self: flex-start; /* Aligns the label to the start of the container */
|
||||
.textarea-label {
|
||||
align-self: flex-start;
|
||||
/* Aligns the label to the start of the container */
|
||||
}
|
||||
|
||||
/* Helper */
|
||||
@ -151,7 +190,8 @@ select {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h2,h3 {
|
||||
h2,
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -160,7 +200,7 @@ h2,h3 {
|
||||
width: unset;
|
||||
margin: 0 0.5em 0 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.compressWidth {
|
||||
width: 60%;
|
||||
@ -170,8 +210,10 @@ h2,h3 {
|
||||
|
||||
/* Video recording controls */
|
||||
.videocontrols {
|
||||
width: 100px; /* Set a specific width for the buttons */
|
||||
height: 70px; /* Set a specific height for the buttons */
|
||||
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;
|
||||
@ -180,9 +222,12 @@ h2,h3 {
|
||||
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 */
|
||||
display: inline-flex;
|
||||
/* Display button contents as a flexbox */
|
||||
justify-content: center;
|
||||
/* Center contents horizontally */
|
||||
align-items: center;
|
||||
/* Center contents vertically */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -191,9 +236,12 @@ h2,h3 {
|
||||
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 */
|
||||
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 {
|
||||
@ -204,21 +252,24 @@ h2,h3 {
|
||||
width: 100%;
|
||||
border: 3px solid black;
|
||||
}
|
||||
|
||||
.columnright {
|
||||
width: 100%;
|
||||
border: 3px solid black;
|
||||
}
|
||||
|
||||
/* Video recording video and youtube iframe */
|
||||
video { /* Video should not be bigger than 100% */
|
||||
video {
|
||||
/* Video should not be bigger than 100% */
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: auto auto;
|
||||
}
|
||||
|
||||
iframe { /* center the iframe, mostly unnecessary */
|
||||
display:block;
|
||||
iframe {
|
||||
/* center the iframe, mostly unnecessary */
|
||||
display: block;
|
||||
}
|
||||
|
||||
.iframe-container iframe {
|
||||
@ -231,14 +282,16 @@ iframe { /* center the iframe, mostly unnecessary */
|
||||
|
||||
.iframe-container {
|
||||
position: relative;
|
||||
padding-bottom: 56.25%; /* 16:9 */
|
||||
padding-bottom: 56.25%;
|
||||
/* 16:9 */
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
padding-bottom: 56.25%; /* 16:9 */
|
||||
padding-bottom: 56.25%;
|
||||
/* 16:9 */
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -249,9 +302,9 @@ iframe { /* center the iframe, mostly unnecessary */
|
||||
-webkit-transform: rotateY(180deg);
|
||||
/* Firefox */
|
||||
-moz-transform: rotateY(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Double Video */
|
||||
/* Double Video */
|
||||
.dv_button {
|
||||
display: inline-block;
|
||||
width: 10%;
|
||||
@ -259,11 +312,13 @@ iframe { /* center the iframe, mostly unnecessary */
|
||||
margin: auto 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dv_half {
|
||||
display: inline-block;
|
||||
width: 45%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.double_video_container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@ -292,10 +347,11 @@ iframe { /* center the iframe, mostly unnecessary */
|
||||
aspect-ratio: 1.5 / 1;
|
||||
}
|
||||
|
||||
.likercontainer{
|
||||
.likercontainer {
|
||||
margin: 30px auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.likert span {
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
@ -310,13 +366,14 @@ iframe { /* center the iframe, mostly unnecessary */
|
||||
transition: background .2s ease-in-out;
|
||||
}
|
||||
|
||||
.likert input:checked + span {
|
||||
.likert input:checked+span {
|
||||
outline: black auto 1px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.likert input:focus + span {
|
||||
outline: auto 1px; /*-webkit-focus-ring-color*/
|
||||
.likert input:focus+span {
|
||||
outline: auto 1px;
|
||||
/*-webkit-focus-ring-color*/
|
||||
}
|
||||
|
||||
.likert span:hover {
|
||||
@ -336,15 +393,19 @@ iframe { /* center the iframe, mostly unnecessary */
|
||||
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 */
|
||||
table-layout: auto;
|
||||
/* Allows columns to adjust to their content */
|
||||
width: auto;
|
||||
/* Adjusts the table width to the content */
|
||||
}
|
||||
|
||||
th, td {
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #dddddd;
|
||||
text-align: left;
|
||||
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 {
|
||||
|
@ -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')}}-->
|
||||
<title>all links
|
||||
</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>
|
||||
|
||||
<body>
|
||||
|
@ -3,18 +3,17 @@
|
||||
<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>Thank you for participating in our study!</h2>
|
||||
<h2>Danke für ihre Teilnahme!</h2>
|
||||
|
||||
<div class="textblock">
|
||||
<p>
|
||||
If you liked this study, we would be grateful if you share it and invite other people to also participate.
|
||||
Anyone with some level of sign language understanding can participate.
|
||||
If you have further questions, please send an email to testemail@notarealemail.deee
|
||||
Falls sie noch Fragen oder Rückmeldungen haben, schreiben sie mir unter: jan.dickmann@web.de
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,64 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
|
||||
<title>Testform</title>
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Question: {{ current_question }}</h2>
|
||||
{% if (videotype == "single")%} <!-- first figure out what video type we have -->
|
||||
<div class="center">
|
||||
<h3>Video 1</h3>
|
||||
<iframe width="560" height="315" class="center" src="{{ video_url }}" title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
{% elif (videotype == "pairwise")%}
|
||||
<div class="columncontainer">
|
||||
<div class="columnleft center">
|
||||
<h3>Video 1</h3>
|
||||
<iframe width="560" height="315" class="center" src="{{ video_url_left }}" title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
<div class="columnright">
|
||||
<h3>Video 2</h3>
|
||||
<iframe width="560" height="315" src="{{ video_url_right }}" title="YouTube video player" frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<p>Error: No Videotype could be matched or was given!</p>
|
||||
{% endif %}
|
||||
<form action="http://localhost:5000/send" method="post">
|
||||
{% for block in config["question 1"]["blocks"] %}
|
||||
{% if (config["question 1"]["blocks"][block]["type"] == "likert") %}
|
||||
<div class="likercontainer">
|
||||
<div class="likert">
|
||||
<label><input name="likertscale" type="radio" value="1" /><span>I dont like it at all</span></label>
|
||||
<label><input name="likertscale" type="radio" value="2" /><span>I dont like it</span></label>
|
||||
<label><input name="likertscale" type="radio" value="3" /><span>I am indifferent</span></label>
|
||||
<label><input name="likertscale" type="radio" value="4" /><span>I like it</span></label>
|
||||
<label><input name="likertscale" type="radio" value="5" /><span>I like it a lot</span></label>
|
||||
</div>
|
||||
</div>
|
||||
{% elif (config["question 1"]["blocks"][block]["type"] == "textinput") %}
|
||||
<label for="feedback">Additional Feedback: </label>
|
||||
<textarea id="feedback" name="feedback" rows="3" cols="30" maxlength="200"></textarea>
|
||||
{% else %}
|
||||
<p>Error: A block could not be loaded!</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
|
||||
</form>
|
||||
</body>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
|
||||
<title>Testform</title>
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2>Question: {{ current_question }}</h2>
|
||||
{% if (videotype == "single")%} <!-- first figure out what video type we have -->
|
||||
<div class="center">
|
||||
<h3>Video 1</h3>
|
||||
<iframe width="560" height="315" class="center" src="{{ video_url }}" title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
{% elif (videotype == "pairwise")%}
|
||||
<div class="columncontainer">
|
||||
<div class="columnleft center">
|
||||
<h3>Video 1</h3>
|
||||
<iframe width="560" height="315" class="center" src="{{ video_url_left }}" title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
<div class="columnright">
|
||||
<h3>Video 2</h3>
|
||||
<iframe width="560" height="315" src="{{ video_url_right }}" title="YouTube video player" frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<p>Error: No Videotype could be matched or was given!</p>
|
||||
{% endif %}
|
||||
<form action="http://localhost:5000/send" method="post">
|
||||
{% for block in config["question 1"]["blocks"] %}
|
||||
{% if (config["question 1"]["blocks"][block]["type"] == "likert") %}
|
||||
<div class="likercontainer">
|
||||
<div class="likert">
|
||||
<label><input name="likertscale" type="radio" value="1" /><span>I dont like it at all</span></label>
|
||||
<label><input name="likertscale" type="radio" value="2" /><span>I dont like it</span></label>
|
||||
<label><input name="likertscale" type="radio" value="3" /><span>I am indifferent</span></label>
|
||||
<label><input name="likertscale" type="radio" value="4" /><span>I like it</span></label>
|
||||
<label><input name="likertscale" type="radio" value="5" /><span>I like it a lot</span></label>
|
||||
</div>
|
||||
</div>
|
||||
{% elif (config["question 1"]["blocks"][block]["type"] == "textinput") %}
|
||||
<label for="feedback">Additional Feedback: </label>
|
||||
<textarea id="feedback" name="feedback" rows="3" cols="30" maxlength="200"></textarea>
|
||||
{% else %}
|
||||
<p>Error: A block could not be loaded!</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
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="{{ url_for('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>
|
@ -1,142 +1,142 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}" />
|
||||
<!-- styles.css {{ url_for('static', filename='styles.css')}}-->
|
||||
<title>Testform</title>
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>Gib Feedback als Video</h2>
|
||||
<div class="centertext">
|
||||
|
||||
<button type="button" class="videocontrols" id="buttonCamera" onclick="cameraButton()">
|
||||
<img id="buttonCameraIcon" src="{{ url_for('static', filename='icons/camera-icon.png')}}" alt="Camera Icon">
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="spacer" aria-hidden="true" style="height:30px"></div>
|
||||
<div class="centertext">
|
||||
|
||||
<button type="button" class="videocontrols" id="buttonRecord" style="display:none" onclick="recordButton()">
|
||||
<img id="buttonRecordIcon" src="{{ url_for('static', filename='icons/record-icon.png')}}" alt="Camera Icon">
|
||||
</button>
|
||||
|
||||
<button type="button" class="videocontrols" id="buttonDelete" style="display:none" disabled
|
||||
onclick="deleteButton()">
|
||||
<img id="buttonDeleteIcon" src="{{ url_for('static', filename='icons/trash-icon.png')}}" alt="Delete Icon"
|
||||
class="buttondisable">
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="spacer" aria-hidden="true" style="height:15px"></div>
|
||||
<div class="centertext">
|
||||
<video autoplay muted playsinline id="videoDisplay"></video>
|
||||
</div>
|
||||
<script>
|
||||
const buttonCamera = document.getElementById('buttonCamera');
|
||||
const buttonRecord = document.getElementById('buttonRecord');
|
||||
const buttonDelete = document.getElementById('buttonDelete');
|
||||
const videoDisplay = document.getElementById('videoDisplay');
|
||||
const buttonCameraIcon = document.getElementById('buttonCameraIcon');
|
||||
const buttonRecordIcon = document.getElementById('buttonRecordIcon');
|
||||
//const buttonDeleteIcon = document.getElementById('buttonDeleteIcon');
|
||||
var mediaRecorder = null;
|
||||
var stream = null;
|
||||
let recordedVideoBlob = null;
|
||||
let isRecording = false;
|
||||
let videoAccess = false;
|
||||
|
||||
async function cameraButton() {
|
||||
if (!videoAccess) { //test what happens if user doesnt give the permission
|
||||
console.log("cameraButton case videoAccess = false");
|
||||
// maybe a try catch block?
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: true,
|
||||
});
|
||||
} catch (error) {
|
||||
//TODO when this occurs the user should get a hint to reload the page or to allow it next to the url
|
||||
newerror = error
|
||||
console.log("Error: ", error);
|
||||
return
|
||||
}
|
||||
console.log("stream is active");
|
||||
videoAccess = true
|
||||
|
||||
videoDisplay.srcObject = stream
|
||||
|
||||
buttonCameraIcon.src = "{{ url_for('static', filename='icons/camera-off-icon.png') }}"; //todo, not sure if this works
|
||||
buttonCameraIcon.alt = "Camera-off Icon";
|
||||
buttonRecord.style.display = 'inline-block';
|
||||
buttonDelete.style.display = 'inline-block';
|
||||
videoDisplay.style.display = 'inline-block';
|
||||
|
||||
mediaRecorder = new MediaRecorder(stream, {
|
||||
mimeType: "video/webm", //could use other video format: https://www.iana.org/assignments/media-types/media-types.xhtml#video
|
||||
// I probably want to change the bitrate: https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log("cameraButton case videoAccess = true");
|
||||
|
||||
stream = null;
|
||||
videoAccess = false
|
||||
|
||||
buttonCameraIcon.src = "{{ url_for('static', filename='icons/camera-icon.png') }}"; //todo, not sure if this works
|
||||
buttonCameraIcon.alt = "Camera Icon";
|
||||
buttonRecord.style.display = 'none';
|
||||
buttonDelete.style.display = 'none';
|
||||
videoDisplay.style.display = 'none';
|
||||
}
|
||||
console.log("camera button function ends");
|
||||
}
|
||||
|
||||
mediaRecorder.addEventListener("dataavailable", (event) => {
|
||||
videoRecorded.src = URL.createObjectURL(event.data);
|
||||
recordedVideoBlob = event.data;
|
||||
});
|
||||
|
||||
function recordButton() {
|
||||
console.log("recordButton pressed");
|
||||
if (!isRecording) {
|
||||
console.log("recordButton pressed case isRecording = false");
|
||||
deleteButton();
|
||||
buttonDelete.setAttribute("disabled", "");
|
||||
buttonDeleteIcon.classList.add("buttondisable");
|
||||
|
||||
mediaRecorder.start();
|
||||
|
||||
buttonRecordIcon.src = "{{ url_for('static', filename='icons/stop-icon.png') }}";
|
||||
buttonRecordIcon.alt = 'record icon';
|
||||
isRecording = true;
|
||||
console.log('Recording started');
|
||||
|
||||
} else {
|
||||
console.log("recordButton pressed case isRecording = true");
|
||||
mediaRecorder.stop();
|
||||
|
||||
buttonRecordIcon.src = "{{ url_for('static', filename='icons/record-icon.png') }}";
|
||||
buttonRecordIcon.alt = 'Stop Icon';
|
||||
isRecording = false;
|
||||
console.log('Recording stopped');
|
||||
|
||||
buttonDelete.removeAttribute("disabled");
|
||||
buttonDeleteIcon.classList.remove("buttondisable");
|
||||
}
|
||||
}
|
||||
function deleteButton() {
|
||||
//todo delete data
|
||||
|
||||
buttonDelete.setAttribute("disabled", "");
|
||||
buttonDeleteIcon.classList.add("buttondisable");
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}" />
|
||||
<!-- styles.css {{ url_for('static', filename='styles.css')}}-->
|
||||
<title>Testform</title>
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>Gib Feedback als Video</h2>
|
||||
<div class="centertext">
|
||||
|
||||
<button type="button" class="videocontrols" id="buttonCamera" onclick="cameraButton()">
|
||||
<img id="buttonCameraIcon" src="{{ url_for('static', filename='icons/camera-icon.png')}}" alt="Camera Icon">
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="spacer" aria-hidden="true" style="height:30px"></div>
|
||||
<div class="centertext">
|
||||
|
||||
<button type="button" class="videocontrols" id="buttonRecord" style="display:none" onclick="recordButton()">
|
||||
<img id="buttonRecordIcon" src="{{ url_for('static', filename='icons/record-icon.png')}}" alt="Camera Icon">
|
||||
</button>
|
||||
|
||||
<button type="button" class="videocontrols" id="buttonDelete" style="display:none" disabled
|
||||
onclick="deleteButton()">
|
||||
<img id="buttonDeleteIcon" src="{{ url_for('static', filename='icons/trash-icon.png')}}" alt="Delete Icon"
|
||||
class="buttondisable">
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div class="spacer" aria-hidden="true" style="height:15px"></div>
|
||||
<div class="centertext">
|
||||
<video autoplay muted playsinline id="videoDisplay"></video>
|
||||
</div>
|
||||
<script>
|
||||
const buttonCamera = document.getElementById('buttonCamera');
|
||||
const buttonRecord = document.getElementById('buttonRecord');
|
||||
const buttonDelete = document.getElementById('buttonDelete');
|
||||
const videoDisplay = document.getElementById('videoDisplay');
|
||||
const buttonCameraIcon = document.getElementById('buttonCameraIcon');
|
||||
const buttonRecordIcon = document.getElementById('buttonRecordIcon');
|
||||
//const buttonDeleteIcon = document.getElementById('buttonDeleteIcon');
|
||||
var mediaRecorder = null;
|
||||
var stream = null;
|
||||
let recordedVideoBlob = null;
|
||||
let isRecording = false;
|
||||
let videoAccess = false;
|
||||
|
||||
async function cameraButton() {
|
||||
if (!videoAccess) { //test what happens if user doesnt give the permission
|
||||
console.log("cameraButton case videoAccess = false");
|
||||
// maybe a try catch block?
|
||||
try {
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: true,
|
||||
});
|
||||
} catch (error) {
|
||||
//TODO when this occurs the user should get a hint to reload the page or to allow it next to the url
|
||||
newerror = error
|
||||
console.log("Error: ", error);
|
||||
return
|
||||
}
|
||||
console.log("stream is active");
|
||||
videoAccess = true
|
||||
|
||||
videoDisplay.srcObject = stream
|
||||
|
||||
buttonCameraIcon.src = "{{ url_for('static', filename='icons/camera-off-icon.png') }}"; //todo, not sure if this works
|
||||
buttonCameraIcon.alt = "Camera-off Icon";
|
||||
buttonRecord.style.display = 'inline-block';
|
||||
buttonDelete.style.display = 'inline-block';
|
||||
videoDisplay.style.display = 'inline-block';
|
||||
|
||||
mediaRecorder = new MediaRecorder(stream, {
|
||||
mimeType: "video/webm", //could use other video format: https://www.iana.org/assignments/media-types/media-types.xhtml#video
|
||||
// I probably want to change the bitrate: https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log("cameraButton case videoAccess = true");
|
||||
|
||||
stream = null;
|
||||
videoAccess = false
|
||||
|
||||
buttonCameraIcon.src = "{{ url_for('static', filename='icons/camera-icon.png') }}"; //todo, not sure if this works
|
||||
buttonCameraIcon.alt = "Camera Icon";
|
||||
buttonRecord.style.display = 'none';
|
||||
buttonDelete.style.display = 'none';
|
||||
videoDisplay.style.display = 'none';
|
||||
}
|
||||
console.log("camera button function ends");
|
||||
}
|
||||
|
||||
mediaRecorder.addEventListener("dataavailable", (event) => {
|
||||
videoRecorded.src = URL.createObjectURL(event.data);
|
||||
recordedVideoBlob = event.data;
|
||||
});
|
||||
|
||||
function recordButton() {
|
||||
console.log("recordButton pressed");
|
||||
if (!isRecording) {
|
||||
console.log("recordButton pressed case isRecording = false");
|
||||
deleteButton();
|
||||
buttonDelete.setAttribute("disabled", "");
|
||||
buttonDeleteIcon.classList.add("buttondisable");
|
||||
|
||||
mediaRecorder.start();
|
||||
|
||||
buttonRecordIcon.src = "{{ url_for('static', filename='icons/stop-icon.png') }}";
|
||||
buttonRecordIcon.alt = 'record icon';
|
||||
isRecording = true;
|
||||
console.log('Recording started');
|
||||
|
||||
} else {
|
||||
console.log("recordButton pressed case isRecording = true");
|
||||
mediaRecorder.stop();
|
||||
|
||||
buttonRecordIcon.src = "{{ url_for('static', filename='icons/record-icon.png') }}";
|
||||
buttonRecordIcon.alt = 'Stop Icon';
|
||||
isRecording = false;
|
||||
console.log('Recording stopped');
|
||||
|
||||
buttonDelete.removeAttribute("disabled");
|
||||
buttonDeleteIcon.classList.remove("buttondisable");
|
||||
}
|
||||
}
|
||||
function deleteButton() {
|
||||
//todo delete data
|
||||
|
||||
buttonDelete.setAttribute("disabled", "");
|
||||
buttonDeleteIcon.classList.add("buttondisable");
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -15,7 +15,7 @@
|
||||
<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/IqGVT1q1PtM?si=kel7ZWEQl3h-h522" title="YouTube video player" frameborder="0"
|
||||
<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>
|
||||
@ -39,7 +39,7 @@
|
||||
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">
|
||||
<form class="dsgvoform" action="{{ url_for('startpage') }}" 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>
|
||||
|
@ -36,7 +36,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="dv_button">Play Videos</button>
|
||||
|
||||
|
||||
<div class="dv_half">
|
||||
<div class="iframe-container">
|
||||
@ -81,7 +81,7 @@ step={{question["step"]}}
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}" />
|
||||
<!-- styles.css {{ url_for('static', filename='styles.css')}}-->
|
||||
<title>Testform</title>
|
||||
<link rel=" shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
<link rel=" shortcut icon" href="{{ url_for('static', filename='icons/favicon.ico') }}">
|
||||
<script>const ICON_PATHS = {
|
||||
cameraofficon: "{{ url_for('static', filename='icons/camera-off-icon.png') }}",
|
||||
cameraicon: "{{ url_for('static', filename='icons/camera-icon.png') }}",
|
||||
@ -92,6 +92,27 @@ step={{question["step"]}}
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
{% 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") %}
|
||||
{{ single_video(**stimulus_configuration) }}
|
||||
{% elif (stimulus_type == "double_video") %}
|
||||
@ -102,7 +123,7 @@ step={{question["step"]}}
|
||||
<p>Error: Block {{ stimulus["type"] }} could not be loaded!</p>
|
||||
{% endif %}
|
||||
|
||||
<form class="formlayout" id="question_form" action="http://localhost:5000/send" method="post">
|
||||
<form class="formlayout" id="question_form" action="{{ url_for('sendpage') }}" method="post">
|
||||
|
||||
{% for question in questions %}
|
||||
{% if (questions[question]["type"] == "likert") %}
|
||||
@ -120,7 +141,6 @@ step={{question["step"]}}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% elif (questions[question]["type"] == "textinput") %}
|
||||
<div class="textarea-container">
|
||||
<label class="textarea-label">
|
||||
@ -174,7 +194,6 @@ step={{question["step"]}}
|
||||
|
||||
{% elif (questions[question]["type"] == "videoinput") %}
|
||||
<div class="spacer" aria-hidden="true" style="height:30px"></div>
|
||||
<h2>Gib Feedback als Video</h2>
|
||||
<h3>{{ questions[question]['text']}}</h3>
|
||||
|
||||
<div class="centertext">
|
||||
|
@ -10,7 +10,7 @@
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>Hello! Thank you for participating in our study!</h2>
|
||||
<form action="http://localhost:5000/start" method="post">
|
||||
<form action="{{ url_for('startpage') }}" method="post">
|
||||
<label for="terms-and-conditions">
|
||||
<input class="inline" id="terms-and-conditions" type="checkbox" required name="terms-and-conditions" /> I accept the +terms and conditions</a>
|
||||
</label>
|
||||
|
@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
|
||||
<title>Test Page 0 - Nothing</title>
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<p>This is just a test page for the single page option of the json configuration, but without something to submit</p>
|
||||
<form action="http://localhost:5000/send_json" method="post">
|
||||
<input type="hidden" name="submittedString" value="Hello, backend!">
|
||||
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,25 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css')}}"" /> <!-- styles.css {{ url_for('static', filename='styles.css')}}-->
|
||||
<title>Test Page 1 - Datenschutzerklaerung</title>
|
||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<p>This is just a test page for the single page option of the json configuration</p>
|
||||
<form action="http://localhost:5000/send_json" method="post">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="accepted"
|
||||
name="accepted"
|
||||
value="checked" />
|
||||
<label for="accepted">Akzeptieren sie die Datenschutzerklaerung</label>
|
||||
<p><input id="submitbutton" type = "submit" value = "submit";/></p>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -3,23 +3,36 @@
|
||||
<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>Hello! Thank you for participating in our study!</h2>
|
||||
<h2>Hallo! Danke, dass sie bei dieser Studie teilnehmen!</h2>
|
||||
|
||||
<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>
|
||||
<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">
|
||||
<form class="dsgvoform" action="{{ url_for('startpage') }}" 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>
|
||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user