Compare commits
1 Commits
master
...
144-update
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7f4afb6f4 |
@@ -170,12 +170,3 @@ Die Konfiguration des Projekts befindet sich im `config/qivip_config.json`.
|
||||
| Variable | Bedeutung | Optionen |
|
||||
| --- | --- | --- |
|
||||
| ENABLE_RATING_SYSTEM | Soll das Bewertungssystem aktiviert werden? | true [Standard], false |
|
||||
|
||||
|
||||
## Lizenzen für node_modules
|
||||
1. Installation: npm install license-checker --save-dev
|
||||
|
||||
npx license-checker --markdown > THIRD_PARTY_LICENSES.md
|
||||
## für Python Packete:
|
||||
|
||||
pip-licenses --format=markdown > THIRD_PARTY_LICENSES_PYTHON.md
|
||||
@@ -1,45 +0,0 @@
|
||||
| Name | Version | License |
|
||||
|---------------------------|--------------|---------------------------------------------------------|
|
||||
| Automat | 25.4.16 | MIT License |
|
||||
| Django | 5.1.13 | BSD License |
|
||||
| Twisted | 25.5.0 | MIT License |
|
||||
| asgiref | 3.10.0 | BSD License |
|
||||
| attrs | 25.4.0 | UNKNOWN |
|
||||
| autobahn | 25.10.1 | UNKNOWN |
|
||||
| certifi | 2025.10.5 | Mozilla Public License 2.0 (MPL 2.0) |
|
||||
| cffi | 2.0.0 | UNKNOWN |
|
||||
| channels | 4.2.2 | BSD License |
|
||||
| channels_redis | 4.3.0 | BSD |
|
||||
| chardet | 5.2.0 | GNU Lesser General Public License v2 or later (LGPLv2+) |
|
||||
| charset-normalizer | 3.4.4 | MIT |
|
||||
| colorama | 0.4.6 | BSD License |
|
||||
| constantly | 23.10.4 | MIT License |
|
||||
| cryptography | 46.0.3 | UNKNOWN |
|
||||
| daphne | 4.1.2 | BSD License |
|
||||
| django-autocomplete-light | 3.12.1 | MIT License |
|
||||
| django-cleanup | 9.0.0 | MIT License |
|
||||
| geographiclib | 2.1 | MIT |
|
||||
| geopy | 2.4.1 | MIT License |
|
||||
| hyperlink | 21.0.0 | MIT License |
|
||||
| idna | 3.11 | UNKNOWN |
|
||||
| incremental | 24.7.2 | MIT License |
|
||||
| msgpack | 1.1.2 | UNKNOWN |
|
||||
| pillow | 11.1.0 | CMU License (MIT-CMU) |
|
||||
| pyOpenSSL | 25.3.0 | Apache Software License |
|
||||
| pyasn1 | 0.6.1 | BSD License |
|
||||
| pyasn1_modules | 0.4.2 | BSD License |
|
||||
| pycparser | 2.23 | BSD License |
|
||||
| pypng | 0.20220715.0 | MIT License |
|
||||
| python-dotenv | 1.1.1 | BSD License |
|
||||
| qrcode | 7.4.2 | BSD License |
|
||||
| redis | 6.4.0 | MIT License |
|
||||
| reportlab | 4.2.5 | BSD License |
|
||||
| requests | 2.32.5 | Apache Software License |
|
||||
| service-identity | 24.2.0 | MIT License |
|
||||
| sqlparse | 0.5.3 | BSD License |
|
||||
| txaio | 25.9.2 | UNKNOWN |
|
||||
| typing_extensions | 4.15.0 | UNKNOWN |
|
||||
| tzdata | 2025.2 | Apache Software License |
|
||||
| urllib3 | 2.5.0 | UNKNOWN |
|
||||
| whitenoise | 6.6.0 | MIT License |
|
||||
| zope.interface | 8.0.1 | Zope Public License |
|
||||
Binary file not shown.
@@ -7,20 +7,14 @@ from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import logout
|
||||
from library.models import QivipQuiz, QivipQuestion
|
||||
from django.db.models import Count
|
||||
|
||||
|
||||
# Create your views here.
|
||||
@login_required
|
||||
def home(request):
|
||||
own_critisized_quizzes = QivipQuiz.objects.annotate(
|
||||
criticism_count=Count('criticism_quiz')
|
||||
).filter(criticism_count__gt=0).filter(user_id=request.user)
|
||||
|
||||
|
||||
my_quizzes_number=QivipQuiz.objects.filter(user_id=request.user).count()
|
||||
my_questions_number = QivipQuestion.objects.filter(quiz_id__user_id=request.user).count()
|
||||
context = {
|
||||
'own_critisized_quizzes':own_critisized_quizzes,
|
||||
'my_quizzes_number': my_quizzes_number,
|
||||
'my_questions_number': my_questions_number,
|
||||
}
|
||||
|
||||
@@ -57,8 +57,6 @@ INSTALLED_APPS = [
|
||||
'channels',
|
||||
'django_cleanup',
|
||||
'django.contrib.humanize',
|
||||
'dal',
|
||||
'dal_select2',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
from django.contrib import admin
|
||||
from .models import QivipFaq, QivipDescription
|
||||
from .models import QivipFaq
|
||||
# Register your models here.
|
||||
class QivipFaqAdmin(admin.ModelAdmin):
|
||||
list_display = ('question','answer',)
|
||||
|
||||
class QivipDescriptionAdmin(admin.ModelAdmin):
|
||||
list_display = ('title','description',)
|
||||
|
||||
# Registrierung der Modelle im Admin
|
||||
admin.site.register(QivipFaq, QivipFaqAdmin)
|
||||
admin.site.register(QivipDescription, QivipDescriptionAdmin)
|
||||
@@ -1,21 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 17:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('homepage', '0003_alter_qivipfaq_answer_alter_qivipfaq_question'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='QivipInfos',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.TextField(max_length=200)),
|
||||
('description', models.TextField(max_length=1000)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 17:42
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('homepage', '0004_qivipinfos'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='QivipInfos',
|
||||
new_name='QivipInfo',
|
||||
),
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 17:55
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('homepage', '0005_rename_qivipinfos_qivipinfo'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='QivipDescription',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.TextField(help_text='Funktion von qivip', max_length=200)),
|
||||
('description', models.TextField(help_text='Beschreibung derFunktion von qivip', max_length=1000)),
|
||||
],
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='QivipInfo',
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 17:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('homepage', '0006_qivipdescription_delete_qivipinfo'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='qivipdescription',
|
||||
name='description',
|
||||
field=models.TextField(help_text='Beschreibung der Funktion von qivip', max_length=1000),
|
||||
),
|
||||
]
|
||||
@@ -4,7 +4,3 @@ from django.db import models
|
||||
class QivipFaq(models.Model):
|
||||
question = models.TextField(max_length=200)
|
||||
answer = models.TextField(max_length=1000)
|
||||
|
||||
class QivipDescription(models.Model): # Was kann man mit qivip machen ...
|
||||
title = models.TextField(max_length=200, help_text="Funktion von qivip")
|
||||
description= models.TextField(max_length=1000, help_text="Beschreibung der Funktion von qivip")
|
||||
@@ -1,10 +1,9 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from .models import QivipFaq, QivipDescription
|
||||
from .models import QivipFaq
|
||||
|
||||
def home(request):
|
||||
faqs=QivipFaq.objects.all()
|
||||
descriptions=QivipDescription.objects.all()
|
||||
context={"faqs": faqs, "descriptions": descriptions}
|
||||
context={"faqs": faqs}
|
||||
return render(request, 'homepage/home.html', context)
|
||||
|
||||
def impress(request):
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
from django.contrib import admin
|
||||
from .models import QivipQuiz, QivipQuestion,QivipQuizFavorite, QuestionImage, QuizCategory, QuizImage, QuizRating, QuizCriticism
|
||||
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuestionImage, QuizCategory, QuizImage, QuizRating
|
||||
|
||||
# Für das QivipQuiz Modell
|
||||
class QivipQuizAdmin(admin.ModelAdmin):
|
||||
list_display = ('name','published_at', 'user_id', 'status', 'category', 'creation_date', 'update_date') # Welche Felder sollen in der Übersicht angezeigt werden
|
||||
search_fields = ('name', 'user_id__username', 'category__name') # Suchfelder
|
||||
list_filter = ('status', 'category') # Filteroptionen
|
||||
autocomplete_fields = ("reference_quizzes",)
|
||||
|
||||
|
||||
class QuizCriticismAdmin(admin.ModelAdmin):
|
||||
list_display = ('quiz','question_criticism', 'user','criticism') # Welche Felder sollen in der Übersicht angezeigt werden
|
||||
search_fields = ('quiz','question_criticism', 'user','criticism') # Suchfelder
|
||||
list_filter=('quiz','question_criticism', 'user','criticism')
|
||||
|
||||
|
||||
|
||||
|
||||
# Für das QivipQuestion Modell
|
||||
class QivipQuestionAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'quiz_id', 'data', 'creation_date', 'update_date', 'credits')
|
||||
@@ -41,35 +30,10 @@ class QuizImageAdmin(admin.ModelAdmin):
|
||||
class QuestionImageAdmin(admin.ModelAdmin):
|
||||
list_display = ('image',)
|
||||
|
||||
|
||||
from django import forms
|
||||
from .models import QuizCategory
|
||||
|
||||
class QuizCategoryForm(forms.ModelForm):
|
||||
color = forms.CharField(
|
||||
widget=forms.TextInput(attrs={'type': 'color'})
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = QuizCategory
|
||||
fields = '__all__'
|
||||
from django.utils.html import format_html
|
||||
@admin.register(QuizCategory)
|
||||
class QuizCategoryAdmin(admin.ModelAdmin):
|
||||
form = QuizCategoryForm
|
||||
list_display = ('name', 'color_preview', 'color')
|
||||
|
||||
def color_preview(self, obj):
|
||||
return format_html(
|
||||
'<div style="width: 24px; height: 24px; background-color: {}; border: 2px solid #000;"></div>',
|
||||
obj.color
|
||||
)
|
||||
color_preview.short_description = "Farbe"
|
||||
|
||||
# Registrierung der Modelle im Admin
|
||||
admin.site.register(QivipQuiz, QivipQuizAdmin)
|
||||
admin.site.register(QivipQuestion, QivipQuestionAdmin)
|
||||
admin.site.register(QuizCriticism, QuizCriticismAdmin)
|
||||
admin.site.register(QuizCategory, QuizCategoryAdmin)
|
||||
admin.site.register(QivipQuizFavorite, QivipQuizFavoriteAdmin)
|
||||
admin.site.register(QuizImage, QuizImageAdmin)
|
||||
admin.site.register(QuestionImage, QuestionImageAdmin)
|
||||
@@ -1,33 +1,10 @@
|
||||
from django import forms
|
||||
from .models import QivipQuiz, QivipQuestion, QuizCategory
|
||||
from dal import autocomplete
|
||||
|
||||
class QuizForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = QivipQuiz
|
||||
fields = ['name', 'description', 'status', 'category','difficulty','reference_quizzes','credits']
|
||||
widgets = {
|
||||
'reference_quizzes': autocomplete.ModelSelect2Multiple(
|
||||
url='library:quiz-autocomplete',
|
||||
forward=['pk']
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(QuizForm, self).__init__(*args, **kwargs)
|
||||
for field in self.fields.values():
|
||||
field.widget.attrs.update({
|
||||
|
||||
})
|
||||
self.fields["reference_quizzes"].queryset = QivipQuiz.objects.filter(
|
||||
status="öffentlich").exclude(pk=self.instance.pk)
|
||||
self.fields["reference_quizzes"].label_from_instance = lambda obj: f"{obj.name} (ID: {obj.pk}) (User: {obj.user_id})"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
fields = ['name', 'description', 'status', 'category','difficulty','credits']
|
||||
|
||||
class QuestionForm(forms.ModelForm):
|
||||
class Meta:
|
||||
@@ -57,5 +34,5 @@ class QuizFilterForm(forms.Form):
|
||||
required=False,
|
||||
label="Kategorie",
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'border-2 border-gray-300 rounded-lg p-2 w-full dark:bg-[#2a2f3a] dark:text-white'
|
||||
'class': 'border-2 border-gray-300 rounded-lg p-2 w-full'
|
||||
}))
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-07-02 11:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0056_alter_quizrating_unique_together'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizcategory',
|
||||
name='color',
|
||||
field=models.CharField(default='#000000', help_text='Farbe als Hex-Code, z.B. #ff0000', max_length=7),
|
||||
),
|
||||
]
|
||||
@@ -1,33 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-14 14:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0057_quizcategory_color'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='qivipquiz',
|
||||
name='quiz_played_Lernmodus',
|
||||
field=models.IntegerField(blank=True, default=0, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='qivipquiz',
|
||||
name='quiz_played_Moderiermodus',
|
||||
field=models.IntegerField(blank=True, default=0, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='qivipquiz',
|
||||
name='quiz_played_Moderiermodus_players',
|
||||
field=models.IntegerField(blank=True, default=0, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='qivipquiz',
|
||||
name='single_player_or_multiple',
|
||||
field=models.CharField(blank=True, max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-14 15:04
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0058_qivipquiz_quiz_played_lernmodus_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='qivipquiz',
|
||||
name='single_player_or_multiple',
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 18:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0059_remove_qivipquiz_single_player_or_multiple'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='qivipquiz',
|
||||
name='similar_quizzes',
|
||||
field=models.ManyToManyField(blank=True, related_name='similar_to', to='library.qivipquiz'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 18:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0060_qivipquiz_similar_quizzes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='qivipquiz',
|
||||
name='similar_quizzes',
|
||||
field=models.ManyToManyField(blank=True, to='library.qivipquiz'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-27 19:54
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0061_alter_qivipquiz_similar_quizzes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='qivipquiz',
|
||||
name='similar_quizzes',
|
||||
field=models.ManyToManyField(blank=True, to='library.qivipquiz', verbose_name='Verweis auf andere Quizze'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-03 11:02
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0062_alter_qivipquiz_similar_quizzes'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='qivipquiz',
|
||||
old_name='similar_quizzes',
|
||||
new_name='reference_quizzes',
|
||||
),
|
||||
]
|
||||
@@ -1,29 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-30 13:36
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0063_rename_similar_quizzes_qivipquiz_reference_quizzes'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='QuizCriticism',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('criticism', models.CharField(max_length=256, verbose_name='Kritik')),
|
||||
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='criticism_question', to='library.qivipquestion')),
|
||||
('quiz', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='criticism_quiz', to='library.qivipquiz')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('quiz', 'user', 'question')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-30 13:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0064_quizcriticism'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizcriticism',
|
||||
name='creation_date',
|
||||
field=models.DateTimeField(auto_now_add=True, default='2025-12-30 12:00:00'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-30 13:59
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0065_quizcriticism_creation_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='quizcriticism',
|
||||
name='creation_date',
|
||||
),
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-30 14:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0066_remove_quizcriticism_creation_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizcriticism',
|
||||
name='creation_date',
|
||||
field=models.DateTimeField(auto_now_add=True, default='2025-12-30 12:00:00'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-31 11:56
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0067_quizcriticism_creation_date'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='quizcriticism',
|
||||
old_name='question',
|
||||
new_name='question_criticism',
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='quizcriticism',
|
||||
unique_together={('quiz', 'user', 'question_criticism', 'criticism')},
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-31 12:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0068_rename_question_quizcriticism_question_criticism_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='quizcriticism',
|
||||
name='question_criticism',
|
||||
field=models.CharField(max_length=1024),
|
||||
),
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-31 14:03
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0069_alter_quizcriticism_question_criticism'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizcriticism',
|
||||
name='question',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='library.qivipquestion'),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-12-31 14:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0070_quizcriticism_question'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizcriticism',
|
||||
name='id_question',
|
||||
field=models.IntegerField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,22 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2026-01-17 11:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('library', '0071_quizcriticism_id_question'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='qivipquestion',
|
||||
options={'ordering': ['order']},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='qivipquestion',
|
||||
name='order',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
]
|
||||
@@ -43,20 +43,11 @@ class QivipQuiz(models.Model):
|
||||
on_delete=models.PROTECT,
|
||||
related_name="quizzes"
|
||||
)
|
||||
reference_quizzes = models.ManyToManyField(
|
||||
'self',
|
||||
blank=True,
|
||||
symmetrical=False,
|
||||
verbose_name="Verweis auf andere Quizze"
|
||||
)
|
||||
|
||||
|
||||
quiz_played_Lernmodus = models.IntegerField(null=True, blank=True, default=0)
|
||||
quiz_played_Moderiermodus= models.IntegerField(null=True, blank=True, default=0)
|
||||
quiz_played_Moderiermodus_players= models.IntegerField(null=True, blank=True, default=0)
|
||||
|
||||
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name='quiz')
|
||||
#creator = models.ForeignKey(User, on_delete=models.SET_NULL,blank=True, null=True, related_name='creator_quiz')
|
||||
base_quiz = models.ForeignKey('self', on_delete=models.SET_NULL,blank=True, null=True, related_name='child_quizzes')
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
update_date = models.DateTimeField(auto_now=True)
|
||||
@@ -86,7 +77,6 @@ class QivipQuizFavorite(models.Model):
|
||||
unique_together = ('user', 'quiz') # Sicherstellen, dass es nur ein Favorit pro User und Quiz gibt
|
||||
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class QivipQuestion(models.Model):
|
||||
|
||||
@@ -113,29 +103,10 @@ class QivipQuestion(models.Model):
|
||||
related_name="questions"
|
||||
)
|
||||
credits=models.TextField(blank=True, editable=True)
|
||||
order = models.PositiveIntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order'] # Fragen nach Reihenfolge sortieren
|
||||
|
||||
def __str__(self):
|
||||
return self.data[:50]
|
||||
|
||||
class QuizCriticism(models.Model):
|
||||
quiz= models.ForeignKey(QivipQuiz, on_delete=models.CASCADE, related_name='criticism_quiz')
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
question_criticism=models.CharField(max_length=1024)
|
||||
criticism=models.CharField(max_length=256, verbose_name="Kritik")
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
question = models.ForeignKey(QivipQuestion, on_delete=models.CASCADE,null=True, blank=True)
|
||||
id_question=models.IntegerField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('quiz', 'user', 'question_criticism','criticism')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.question_criticism}"
|
||||
|
||||
|
||||
|
||||
class QuizRating(models.Model):
|
||||
@@ -177,12 +148,6 @@ class QuizCategory(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
slug = models.SlugField(unique=True, blank=True)
|
||||
color= models.CharField(
|
||||
max_length=7,
|
||||
default="#000000",
|
||||
help_text="Farbe als Hex-Code, z.B. #ff0000"
|
||||
)
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.slug:
|
||||
|
||||
@@ -3,13 +3,13 @@ from django.urls import path
|
||||
from django.conf import settings
|
||||
from . import views
|
||||
from django.conf.urls.static import static
|
||||
from .views import QuizAutocomplete
|
||||
app_name = 'library'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.overview_quiz, name='overview_quiz'),
|
||||
path('my/', views.overview_quiz_my, name='overview_quiz_my'),
|
||||
path('favorites/', views.overview_quiz_favorites, name='overview_quiz_favorites'),
|
||||
|
||||
path('new/', views.new_quiz, name='new_quiz'),
|
||||
path('edit/<int:pk>/', views.edit_quiz, name='edit_quiz'),
|
||||
path('delete/<int:pk>/', views.delete_quiz, name='delete_quiz'),
|
||||
@@ -19,16 +19,6 @@ urlpatterns = [
|
||||
path('question/delete/<int:pk>/', views.delete_question, name='delete_question'),
|
||||
path('detail/copy/<int:pk>/', views.copy_quiz, name='copy_quiz'),
|
||||
path('favorite_quiz/<int:pk>/', views.favorite_quiz, name='favorite_quiz'),
|
||||
path('critique/delete/<int:critique_id>/<int:quiz_id>', views.delete_critique, name='delete_critique'),
|
||||
path('quiz/reorder/', views.reorder_questions, name='reorder_questions'),
|
||||
|
||||
path("quiz-names-json/", views.quiz_names_json, name="quiz_names_json"),
|
||||
path(
|
||||
"autocomplete/quiz",
|
||||
QuizAutocomplete.as_view(),
|
||||
name="quiz-autocomplete"
|
||||
),
|
||||
|
||||
|
||||
|
||||
]
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.urls import reverse
|
||||
from django.contrib import messages
|
||||
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuestionImage, QuizCriticism, QuizImage
|
||||
from .models import QivipQuiz, QivipQuestion, QivipQuizFavorite, QuestionImage, QuizImage
|
||||
from .forms import QuizForm, QuestionForm
|
||||
import json
|
||||
from django.core.paginator import Paginator
|
||||
@@ -17,61 +17,6 @@ from django.contrib.auth.models import User
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
# Übersicht aller Quizze
|
||||
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
|
||||
from reportlab.lib import colors
|
||||
from django.http import HttpResponse
|
||||
import json
|
||||
from datetime import datetime
|
||||
from reportlab.platypus import Image
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
import json
|
||||
from io import BytesIO
|
||||
import requests
|
||||
from django.core.files.storage import default_storage
|
||||
from PIL import Image as PILImage, ImageOps
|
||||
from django.conf import settings
|
||||
import os
|
||||
from django.utils.html import escape
|
||||
from .models import QivipQuiz
|
||||
from django.db.models import Value, CharField
|
||||
from django.db.models.functions import Concat
|
||||
# views.py
|
||||
from dal import autocomplete
|
||||
from .models import QivipQuiz
|
||||
|
||||
class QuizAutocomplete(autocomplete.Select2QuerySetView):
|
||||
|
||||
def get_queryset(self):
|
||||
qs = QivipQuiz.objects.filter(status="öffentlich")
|
||||
|
||||
pk = self.forwarded.get('pk', None)
|
||||
if pk:
|
||||
qs = qs.exclude(pk=pk)
|
||||
|
||||
if self.q:
|
||||
qs = qs.annotate(
|
||||
search_text=Concat(
|
||||
'name',
|
||||
Value(' (ID: '), 'pk', Value(') (User: '), 'user_id', Value(')'),
|
||||
output_field=CharField()
|
||||
)
|
||||
).filter(search_text__icontains=self.q)
|
||||
return qs
|
||||
|
||||
def get_result_label(self, result):
|
||||
# z.B. Name + ID + User
|
||||
return f"{result.name} (ID: {result.pk}) (User: {result.user_id})"
|
||||
|
||||
|
||||
|
||||
|
||||
def quiz_names_json(request):
|
||||
form = QuizFilterForm(request.GET)
|
||||
@@ -94,17 +39,6 @@ def quiz_names_json(request):
|
||||
return JsonResponse(names, safe=False)
|
||||
|
||||
|
||||
|
||||
def reorder_questions(request):
|
||||
if request.method == "POST":
|
||||
data = json.loads(request.body)
|
||||
for item in data['order']:
|
||||
QivipQuestion.objects.filter(id=item['id']).update(order=item['position'])
|
||||
return JsonResponse({'status': 'ok'})
|
||||
return JsonResponse({'status': 'error'}, status=400)
|
||||
|
||||
|
||||
|
||||
def apply_quiz_filters(queryset, form):
|
||||
|
||||
if not form.is_valid():
|
||||
@@ -142,84 +76,7 @@ def apply_quiz_filters(queryset, form):
|
||||
return queryset.filter(**filters)
|
||||
|
||||
|
||||
|
||||
def critisize_quiz(request, pk):
|
||||
|
||||
quiz= get_object_or_404(QivipQuiz, pk=pk)
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
|
||||
question_id = request.POST.get("question_id") or "whole_quiz"
|
||||
|
||||
if question_id!="whole_quiz":
|
||||
question = quiz.questions.get(id=question_id)
|
||||
data = json.loads(question.data)
|
||||
question_text = data["question"]
|
||||
|
||||
else:
|
||||
question_text="Quiz(formular) kritisiert"
|
||||
question = None
|
||||
question_id = None
|
||||
|
||||
|
||||
critic_type = request.POST.get("critic")
|
||||
CRITICISM_MAP = {
|
||||
"content_error": "inhaltlicher Fehler",
|
||||
"spelling_error": "Rechtschreibfehler oder Grammatikfehler",
|
||||
"logic_error": "umständliche oder unsinnige Formulierung",
|
||||
"inappropriate_error": "unangemessen",
|
||||
"not_completed_error": "unvollständig",
|
||||
"source_error": "Quellen unvollständig",
|
||||
"quiz_error": "Quiz als \"öffentliches Quiz\" ungeeignet"}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
exists = QuizCriticism.objects.filter(
|
||||
id_question=question_id,
|
||||
question=question,
|
||||
quiz=quiz,
|
||||
user=request.user,
|
||||
question_criticism=question_text,
|
||||
criticism=CRITICISM_MAP[critic_type],
|
||||
).exists()
|
||||
|
||||
if exists:
|
||||
messages.error(request, "Diese Kritik wurde bereits abgegeben.") # nur Kritik abgeben, wenn noch nicht erstellt
|
||||
else:
|
||||
QuizCriticism.objects.create(
|
||||
question=question,
|
||||
id_question=question_id,
|
||||
quiz=quiz,
|
||||
user=request.user,
|
||||
question_criticism=question_text,
|
||||
criticism=CRITICISM_MAP[critic_type],
|
||||
)
|
||||
|
||||
|
||||
|
||||
def delete_critique(request,critique_id,quiz_id):
|
||||
|
||||
critique = get_object_or_404(QuizCriticism, id=critique_id)
|
||||
critique.delete()
|
||||
return redirect('library:detail_quiz', pk=quiz_id)
|
||||
|
||||
|
||||
|
||||
|
||||
def get_critisized_quizzes():
|
||||
critisized_quizzes = QivipQuiz.objects.annotate(
|
||||
criticism_count=Count('criticism_quiz')
|
||||
).filter(criticism_count__gt=0)
|
||||
return critisized_quizzes
|
||||
|
||||
|
||||
def overview_quiz(request, connect="all"):
|
||||
|
||||
critisized_quizzes = get_critisized_quizzes()
|
||||
form = QuizFilterForm(request.GET)
|
||||
|
||||
user = request.user if request.user.is_authenticated else None
|
||||
@@ -291,7 +148,6 @@ def overview_quiz(request, connect="all"):
|
||||
favorite_quizzes = paginator_fav.page(1)
|
||||
|
||||
context = {
|
||||
'critisized_quizzes':critisized_quizzes,
|
||||
'form': form,
|
||||
'my_quizzes': my_quizzes,
|
||||
'all_quizzes': all_quizzes,
|
||||
@@ -342,6 +198,117 @@ def overview_quiz_my(request):
|
||||
|
||||
def overview_quiz_favorites(request):
|
||||
return overview_quiz(request, connect="favorite")
|
||||
"""
|
||||
def overview_quiz(request):
|
||||
# Filter
|
||||
form = QuizFilterForm(request.GET)
|
||||
try:
|
||||
favorite_quizzes_data = QivipQuizFavorite.objects.filter(user=request.user, favorite=True).order_by("-favorite_date")
|
||||
favorite_quiz_ids = favorite_quizzes_data.values_list('quiz', flat=True)
|
||||
favorite_quizzes = QivipQuiz.objects.filter(id__in=favorite_quiz_ids)
|
||||
except:
|
||||
favorite_quizzes_data = QivipQuizFavorite.objects.none()
|
||||
favorite_quizzes = QivipQuizFavorite.objects.none()
|
||||
favorite_quiz_ids = favorite_quizzes.none()
|
||||
favorite_quizzes = QivipQuiz.objects.none()
|
||||
|
||||
# Initialize querysets
|
||||
if request.user.is_authenticated:
|
||||
filter_my_quizzes = QivipQuiz.objects.filter(user_id=request.user).annotate(max_amount_questions=Count('questions'))
|
||||
favorite_quizzes = favorite_quizzes.filter(id__in=favorite_quiz_ids).annotate(max_amount_questions=Count('questions'))
|
||||
filter_all_quizzes = QivipQuiz.objects.exclude(user_id=request.user)
|
||||
else:
|
||||
favorite_quizzes = QivipQuiz.objects.none()
|
||||
filter_my_quizzes = QivipQuiz.objects.none()
|
||||
filter_all_quizzes = QivipQuiz.objects.all()
|
||||
|
||||
# Apply common filters to all quizzes
|
||||
filter_all_quizzes = filter_all_quizzes.annotate(max_amount_questions=Count('questions'))
|
||||
filter_all_quizzes = filter_all_quizzes.filter(max_amount_questions__gte=1, status='öffentlich')
|
||||
|
||||
if form.is_valid():
|
||||
|
||||
search = form.cleaned_data.get('search')
|
||||
username= form.cleaned_data.get('user')
|
||||
max_amount_questions= form.cleaned_data.get('max_amount_questions')
|
||||
min_amount_questions= form.cleaned_data.get('min_amount_questions')
|
||||
|
||||
filters = {}
|
||||
if username:
|
||||
try:
|
||||
user = User.objects.get(username=username)
|
||||
filters['user_id'] = user.id
|
||||
except User.DoesNotExist:
|
||||
favorite_quizzes = QivipQuiz.objects.none()
|
||||
filter_my_quizzes = QivipQuiz.objects.none()
|
||||
filter_all_quizzes=QivipQuiz.objects.none()
|
||||
|
||||
if search:
|
||||
filters['name__icontains'] = search
|
||||
if min_amount_questions:
|
||||
filters['max_amount_questions__gte'] = min_amount_questions
|
||||
if max_amount_questions:
|
||||
filters['max_amount_questions__lte'] = max_amount_questions
|
||||
|
||||
try:
|
||||
filter_all_quizzes = filter_all_quizzes.filter(**filters)
|
||||
except:
|
||||
filter_all_quizzes=QivipQuiz.objects.none()
|
||||
try:
|
||||
filter_my_quizzes = filter_my_quizzes.filter(**filters)
|
||||
except:
|
||||
filter_my_quizzes=QivipQuiz.objects.none()
|
||||
|
||||
try:
|
||||
favorite_quizzes = favorite_quizzes.filter(**filters)
|
||||
except:
|
||||
favorite_quizzes=QivipQuiz.objects.none()
|
||||
|
||||
|
||||
|
||||
# Pagination for my quizzes
|
||||
my_quizzes_paginator = Paginator(filter_my_quizzes.order_by('-creation_date'), 8) # 8 quizzes per page
|
||||
try:
|
||||
my_quizzes = my_quizzes_paginator.page(request.GET.get('my_page', 1))
|
||||
except:
|
||||
my_quizzes = my_quizzes_paginator.page(1)
|
||||
|
||||
# Pagination for all quizzes
|
||||
all_quizzes_paginator = Paginator(filter_all_quizzes.order_by('-creation_date'), 8) # 8 quizzes per page
|
||||
try:
|
||||
all_quizzes = all_quizzes_paginator.page(request.GET.get('all_page', 1))
|
||||
except:
|
||||
all_quizzes = all_quizzes_paginator.page(1)
|
||||
|
||||
|
||||
|
||||
try:
|
||||
favorite_quizzes = favorite_quizzes.filter(
|
||||
favorites__user=request.user, # Filtere nach dem User
|
||||
favorites__favorite=True # Filtere nach favorisierten Quizzes
|
||||
).order_by('-favorites__favorite_date')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# Pagination for favorite_quizzes
|
||||
favorite_quizzes_paginator = Paginator(favorite_quizzes, 8) # 9 quizzes per page
|
||||
try:
|
||||
favorite_quizzes = favorite_quizzes_paginator.page(request.GET.get('favorite_page', 1))
|
||||
except:
|
||||
favorite_quizzes = favorite_quizzes_paginator.page(1)
|
||||
|
||||
context = {
|
||||
'show_search': True,
|
||||
'favorite_quiz_ids': favorite_quiz_ids,
|
||||
'favorite_quizzes':favorite_quizzes,
|
||||
'my_quizzes': my_quizzes,
|
||||
'all_quizzes': all_quizzes,
|
||||
'form': form,
|
||||
}
|
||||
|
||||
return render(request, 'library/overview_quiz.html', context)
|
||||
"""
|
||||
|
||||
@login_required
|
||||
def favorite_quiz(request, pk):
|
||||
@@ -405,6 +372,7 @@ def new_quiz(request):
|
||||
|
||||
|
||||
#quiz.creator = request.user
|
||||
if form.has_changed():
|
||||
quiz.save()
|
||||
#form.save_m2m() # Speichert die Many-to-Many Beziehungen (Tags)
|
||||
return redirect('library:detail_quiz', pk=quiz.pk)
|
||||
@@ -444,7 +412,7 @@ def edit_quiz(request, pk):
|
||||
status = form.cleaned_data.get('status')
|
||||
if status == 'öffentlich' and not quiz.published_at:
|
||||
quiz.published_at = timezone.now()
|
||||
|
||||
if form.has_changed():
|
||||
form.save()
|
||||
for img in QuizImage.objects.all():
|
||||
try:
|
||||
@@ -481,11 +449,8 @@ def delete_quiz(request, pk):
|
||||
return redirect('library:overview_quiz')
|
||||
return render(request, 'library/delete_confirmation.html', {'object': quiz})
|
||||
|
||||
|
||||
|
||||
# Quiz anzeigen
|
||||
def detail_quiz(request, pk):
|
||||
critisize_quiz(request, pk)
|
||||
quiz = get_object_or_404(QivipQuiz, pk=pk)
|
||||
show_answers = request.GET.get('show_answers', 'false').lower() == 'true'
|
||||
questions = QivipQuestion.objects.filter(quiz_id=quiz)
|
||||
@@ -524,9 +489,6 @@ def copy_quiz(request, pk):
|
||||
new_quiz.base_quiz_id = pk
|
||||
new_quiz.rating_count = 0
|
||||
new_quiz.average_rating = 3.0
|
||||
new_quiz.quiz_played_Lernmodus = 0
|
||||
new_quiz.quiz_played_Moderiermodus = 0
|
||||
new_quiz.quiz_played_Moderiermodus_players = 0
|
||||
if original_quiz.status=="öffentlich":
|
||||
new_quiz.published_at = timezone.now()
|
||||
else:
|
||||
@@ -565,7 +527,6 @@ def question_list(request):
|
||||
def new_question(request):
|
||||
question_type = request.GET.get('type')
|
||||
quiz_id = request.GET.get('quiz_id')
|
||||
question_data = None
|
||||
|
||||
if not quiz_id:
|
||||
messages.error(request, 'Quiz ID muss angegeben werden.')
|
||||
@@ -577,12 +538,12 @@ def new_question(request):
|
||||
messages.error(request, 'Quiz nicht gefunden oder keine Berechtigung.')
|
||||
return redirect('library:overview_quiz')
|
||||
|
||||
if question_type not in ['multiple_choice', 'true_false','input','order','guess','map']:
|
||||
if question_type not in ['multiple_choice', 'true_false','input']:
|
||||
base_url = reverse('library:new_question')
|
||||
return redirect(f'{base_url}?type=multiple_choice&quiz_id={quiz_id}')
|
||||
|
||||
if request.method == 'POST':
|
||||
question_text = request.POST.get('question', 'Kein Fragentext angegeben')
|
||||
question_text = request.POST.get('question', '')
|
||||
|
||||
# Handle different question types
|
||||
if question_type == 'true_false':
|
||||
@@ -606,7 +567,7 @@ def new_question(request):
|
||||
|
||||
]
|
||||
}
|
||||
elif question_type == 'multiple_choice' :
|
||||
elif question_type == 'multiple_choice':
|
||||
options = []
|
||||
i = 1
|
||||
# Limit to maximum 6 options
|
||||
@@ -630,59 +591,6 @@ def new_question(request):
|
||||
'options': options
|
||||
}
|
||||
|
||||
elif question_type == 'order' :
|
||||
options = []
|
||||
i = 1
|
||||
# Limit to maximum 6 options
|
||||
while request.POST.get(f'option_{i}') and i <= 6:
|
||||
is_correct = request.POST.get(f'correct_{i}') == '1'
|
||||
options.append({
|
||||
'order': i,
|
||||
'value': request.POST.get(f'option_{i}'),
|
||||
|
||||
})
|
||||
i += 1
|
||||
|
||||
|
||||
# Validate minimum 2 options
|
||||
if len(options) < 2:
|
||||
messages.error(request, 'Mindestens zwei Optionen müssen angegeben werden.')
|
||||
return redirect(request.get_full_path())
|
||||
|
||||
json_data = {
|
||||
'type': question_type,
|
||||
'question': question_text,
|
||||
'options': options
|
||||
}
|
||||
|
||||
|
||||
elif question_type == 'guess':
|
||||
correct_answer = request.POST.get('correct_answer')
|
||||
min_value = request.POST.get('min')
|
||||
max_value = request.POST.get('max')
|
||||
tolerance = request.POST.get('tolerance')
|
||||
json_data = {
|
||||
'type': question_type,
|
||||
'question': question_text,
|
||||
'correct_answer': correct_answer,
|
||||
'min': min_value,
|
||||
'max': max_value,
|
||||
'tolerance': tolerance,
|
||||
'options': [
|
||||
{'value': correct_answer, 'is_correct': True}
|
||||
]
|
||||
}
|
||||
|
||||
elif question_type == 'map':
|
||||
target_location = request.POST.get('location')
|
||||
tolerance_radius = request.POST.get('tolerance_radius')
|
||||
|
||||
json_data = {
|
||||
'type': question_type,
|
||||
'question': question_text,
|
||||
'target_location': target_location,
|
||||
'tolerance_radius': tolerance_radius
|
||||
}
|
||||
question = QivipQuestion()
|
||||
question.data = json.dumps(json_data)
|
||||
question.quiz_id = quiz
|
||||
@@ -699,6 +607,7 @@ def new_question(request):
|
||||
question.credits=request.POST.get('credits',"")
|
||||
except:
|
||||
question.credits=""
|
||||
|
||||
question.save()
|
||||
#return modified(request, pk=quiz.pk)
|
||||
return redirect('library:detail_quiz', pk=quiz.pk)
|
||||
@@ -737,43 +646,6 @@ def new_question(request):
|
||||
}
|
||||
standard_time_per_question = 30
|
||||
|
||||
elif question_type == 'order':
|
||||
question_data = {
|
||||
'type': question_type,
|
||||
'question': '',
|
||||
'options': [
|
||||
{'value': ''},
|
||||
{'value': ''}
|
||||
]
|
||||
}
|
||||
standard_time_per_question = 30
|
||||
elif question_type == 'guess':
|
||||
correct_answer = request.POST.get('correct_answer')
|
||||
min_value = request.POST.get('min')
|
||||
max_value = request.POST.get('max')
|
||||
tolerance = request.POST.get('tolerance')
|
||||
json_data = {
|
||||
'type': question_type,
|
||||
'question': '',
|
||||
'correct_answer': correct_answer,
|
||||
'min': min_value,
|
||||
'max': max_value,
|
||||
'tolerance': tolerance,
|
||||
'options': [
|
||||
{'value': correct_answer, 'is_correct': True}
|
||||
]
|
||||
}
|
||||
standard_time_per_question = 30
|
||||
|
||||
elif question_type == 'map':
|
||||
question_data = {
|
||||
'type': question_type,
|
||||
'question': '',
|
||||
'target_location': "",
|
||||
'tolerance_radius': 90000
|
||||
}
|
||||
standard_time_per_question = 30
|
||||
|
||||
template_name = f'library/question/question_{question_type}.html'
|
||||
context = {
|
||||
'question': question_data,
|
||||
@@ -814,13 +686,6 @@ def edit_question(request, pk):
|
||||
{'value': 'Falsch', 'is_correct': not correct_answer}
|
||||
]
|
||||
}
|
||||
elif question_type == 'map':
|
||||
question_data = {
|
||||
'type': question_type,
|
||||
'question': question_data.get('question', ''),
|
||||
'target_location': question_data.get('target_location', ''),
|
||||
'tolerance_radius': question_data.get('tolerance_radius', 90000)
|
||||
}
|
||||
else:
|
||||
# For multiple choice questions
|
||||
question_data.setdefault('type', question_type)
|
||||
@@ -906,59 +771,6 @@ def edit_question(request, pk):
|
||||
'options': options
|
||||
}
|
||||
|
||||
elif question_type == 'order':
|
||||
options = []
|
||||
i = 1
|
||||
# Limit to maximum 6 options
|
||||
while request.POST.get(f'option_{i}') and i <= 6:
|
||||
options.append({
|
||||
'order': i,
|
||||
'value': request.POST.get(f'option_{i}')
|
||||
})
|
||||
i += 1
|
||||
|
||||
# Validate minimum 2 options
|
||||
if len(options) < 2:
|
||||
messages.error(request, 'Mindestens zwei Optionen müssen angegeben werden.')
|
||||
return redirect(request.get_full_path())
|
||||
|
||||
# Validate maximum 6 options
|
||||
if len(options) > 6:
|
||||
messages.error(request, 'Maximal 6 Optionen sind erlaubt.')
|
||||
return redirect(request.get_full_path())
|
||||
|
||||
json_data = {
|
||||
'type': question_type,
|
||||
'question': question_text,
|
||||
'options': options
|
||||
}
|
||||
elif question_type == 'guess':
|
||||
correct_answer = request.POST.get('correct_answer')
|
||||
min_value = request.POST.get('min')
|
||||
max_value = request.POST.get('max')
|
||||
tolerance = request.POST.get('tolerance')
|
||||
json_data = {
|
||||
'type': question_type,
|
||||
'question': question_text,
|
||||
'correct_answer': correct_answer,
|
||||
'min': min_value,
|
||||
'max': max_value,
|
||||
'tolerance': tolerance,
|
||||
'options': [
|
||||
{'value': correct_answer, 'is_correct': True}
|
||||
]
|
||||
}
|
||||
|
||||
elif question_type == 'map':
|
||||
target_location = request.POST.get('location')
|
||||
tolerance_radius = request.POST.get('tolerance_radius')
|
||||
|
||||
json_data = {
|
||||
'type': question_type,
|
||||
'question': question_text,
|
||||
'target_location': target_location,
|
||||
'tolerance_radius': tolerance_radius
|
||||
}
|
||||
question.data = json.dumps(json_data)
|
||||
question.time_per_question = request.POST.get('time_per_question', '30')
|
||||
try:
|
||||
|
||||
573
django/package-lock.json
generated
573
django/package-lock.json
generated
@@ -10,11 +10,7 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@tailwindcss/cli": "^4.1.5",
|
||||
"sortablejs": "^1.15.6",
|
||||
"tailwindcss": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"license-checker": "^25.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
@@ -541,55 +537,6 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/array-find-index": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
|
||||
"integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
@@ -601,60 +548,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/debuglog": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz",
|
||||
"integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
@@ -666,16 +559,6 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
|
||||
"integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asap": "^2.0.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.18.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||
@@ -688,15 +571,6 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -708,106 +582,11 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hosted-git-info": {
|
||||
"version": "2.8.9",
|
||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
|
||||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
@@ -843,33 +622,6 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/license-checker": {
|
||||
"version": "25.0.1",
|
||||
"resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz",
|
||||
"integrity": "sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "^2.4.1",
|
||||
"debug": "^3.1.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nopt": "^4.0.1",
|
||||
"read-installed": "~4.0.3",
|
||||
"semver": "^5.5.0",
|
||||
"spdx-correct": "^3.0.0",
|
||||
"spdx-expression-parse": "^3.0.0",
|
||||
"spdx-satisfies": "^4.0.0",
|
||||
"treeify": "^1.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"license-checker": "bin/license-checker"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
|
||||
@@ -1107,39 +859,6 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.6"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mri": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||
@@ -1148,101 +867,11 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
||||
"integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"abbrev": "1",
|
||||
"osenv": "^0.1.4"
|
||||
},
|
||||
"bin": {
|
||||
"nopt": "bin/nopt.js"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-package-data": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
|
||||
"integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"hosted-git-info": "^2.1.4",
|
||||
"resolve": "^1.10.0",
|
||||
"semver": "2 || 3 || 4 || 5",
|
||||
"validate-npm-package-license": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-normalize-package-bin": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
|
||||
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/os-homedir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
|
||||
"integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/osenv": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
|
||||
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"os-homedir": "^1.0.0",
|
||||
"os-tmpdir": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -1259,177 +888,6 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/read-installed": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz",
|
||||
"integrity": "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debuglog": "^1.0.1",
|
||||
"read-package-json": "^2.0.0",
|
||||
"readdir-scoped-modules": "^1.0.0",
|
||||
"semver": "2 || 3 || 4 || 5",
|
||||
"slide": "~1.1.3",
|
||||
"util-extend": "^1.0.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/read-package-json": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz",
|
||||
"integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==",
|
||||
"deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.1",
|
||||
"json-parse-even-better-errors": "^2.3.0",
|
||||
"normalize-package-data": "^2.0.0",
|
||||
"npm-normalize-package-bin": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdir-scoped-modules": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz",
|
||||
"integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==",
|
||||
"deprecated": "This functionality has been moved to @npmcli/fs",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debuglog": "^1.0.1",
|
||||
"dezalgo": "^1.0.0",
|
||||
"graceful-fs": "^4.1.2",
|
||||
"once": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.16.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/slide": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
|
||||
"integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/sortablejs": {
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.6.tgz",
|
||||
"integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A=="
|
||||
},
|
||||
"node_modules/spdx-compare": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz",
|
||||
"integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"array-find-index": "^1.0.2",
|
||||
"spdx-expression-parse": "^3.0.0",
|
||||
"spdx-ranges": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-correct": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
|
||||
"integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"spdx-expression-parse": "^3.0.0",
|
||||
"spdx-license-ids": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-exceptions": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
|
||||
"integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/spdx-expression-parse": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
|
||||
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"spdx-exceptions": "^2.1.0",
|
||||
"spdx-license-ids": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/spdx-license-ids": {
|
||||
"version": "3.0.21",
|
||||
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz",
|
||||
"integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/spdx-ranges": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz",
|
||||
"integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/spdx-satisfies": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-4.0.1.tgz",
|
||||
"integrity": "sha512-WVzZ/cXAzoNmjCWiEluEA3BjHp5tiUmmhn9MK+X0tBbR9sOqtC6UQwmgCNrAIZvNlMuBUYAaHYfb2oqlF9SwKA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"spdx-compare": "^1.0.0",
|
||||
"spdx-expression-parse": "^3.0.0",
|
||||
"spdx-ranges": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.5.tgz",
|
||||
@@ -1453,37 +911,6 @@
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/treeify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz",
|
||||
"integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/util-extend": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz",
|
||||
"integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/validate-npm-package-license": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"spdx-correct": "^3.0.0",
|
||||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,6 @@
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@tailwindcss/cli": "^4.1.5",
|
||||
"sortablejs": "^1.15.6",
|
||||
"tailwindcss": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"license-checker": "^25.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,8 @@ from channels.generic.websocket import AsyncWebsocketConsumer
|
||||
from channels.db import database_sync_to_async
|
||||
from django.utils import timezone
|
||||
from django.urls import reverse
|
||||
from play.models import QuizGame, QuizGameParticipant, QuizAnswer,QivipQuiz
|
||||
from play.models import QuizGame, QuizGameParticipant, QuizAnswer
|
||||
from django.conf import settings
|
||||
from geopy.distance import geodesic
|
||||
|
||||
class GameConsumer(AsyncWebsocketConsumer):
|
||||
|
||||
@@ -32,13 +31,6 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
# Check if game should be deleted
|
||||
await self.cleanup_game()
|
||||
|
||||
|
||||
@database_sync_to_async
|
||||
def get_question_data(self):
|
||||
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
||||
current_question = quiz_game.quiz_id.questions.all()[quiz_game.current_question_index]
|
||||
return json.loads(current_question.data)
|
||||
|
||||
async def receive(self, text_data):
|
||||
text_data_json = json.loads(text_data)
|
||||
message_type = text_data_json['type']
|
||||
@@ -92,17 +84,15 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
|
||||
elif message_type == 'submit_answer':
|
||||
participant_id = text_data_json['participant_id']
|
||||
try:
|
||||
answer = text_data_json['answer']
|
||||
except:
|
||||
answer=-1
|
||||
time_remaining = text_data_json.get('time_remaining', 0)
|
||||
|
||||
|
||||
question_data = await self.get_question_data()
|
||||
if question_data['type'] == "map":
|
||||
await self.save_answer_map(participant_id, answer, time_remaining)
|
||||
else:
|
||||
# Save answer and update participant score
|
||||
await self.save_answer(participant_id, answer, time_remaining)
|
||||
|
||||
|
||||
# Notify host about the new answer
|
||||
await self.channel_layer.group_send(
|
||||
self.game_group_name,
|
||||
@@ -121,7 +111,7 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
stats = await self.get_answer_stats()
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'answer_stats',
|
||||
'stats': stats,
|
||||
'stats': stats
|
||||
}))
|
||||
|
||||
elif message_type == 'update_participants':
|
||||
@@ -138,18 +128,8 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
host_id = text_data_json['host_id']
|
||||
if await self.verify_host(host_id):
|
||||
await self.advance_to_next_question()
|
||||
elif message_type == 'winner_podest':
|
||||
host_id = text_data_json['host_id']
|
||||
if await self.verify_host(host_id):
|
||||
await self.show_winner_podest()
|
||||
|
||||
elif message_type == 'scoreboard':
|
||||
host_id = text_data_json['host_id']
|
||||
if await self.verify_host(host_id):
|
||||
await self.show_scoreboard()
|
||||
|
||||
elif message_type == 'finish_game':
|
||||
|
||||
host_id = text_data_json['host_id']
|
||||
if await self.verify_host(host_id):
|
||||
await self.finish_game()
|
||||
@@ -194,139 +174,7 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
return False
|
||||
|
||||
@database_sync_to_async
|
||||
def save_answer_map(self, participant_id, answer_location, time_remaining):
|
||||
answer_index = 0
|
||||
try:
|
||||
# Get all necessary data first
|
||||
participant = QuizGameParticipant.objects.get(
|
||||
quiz_game__join_code=self.join_code,
|
||||
participant_id=participant_id
|
||||
)
|
||||
quiz_game = participant.quiz_game
|
||||
current_question = quiz_game.quiz_id.questions.all()[quiz_game.current_question_index]
|
||||
question_data = json.loads(current_question.data)
|
||||
target_location = question_data['target_location']
|
||||
tolerance_radius = question_data.get('tolerance_radius', 1000) # Default 1km if not set
|
||||
|
||||
|
||||
# Parse coordinates if they are strings
|
||||
def parse_coords(coord):
|
||||
if isinstance(coord, str):
|
||||
try:
|
||||
# Handle both comma and space separated coordinates
|
||||
if ',' in coord:
|
||||
lat, lng = map(float, [x.strip() for x in coord.split(',')])
|
||||
else:
|
||||
# Try splitting by space if no comma
|
||||
parts = coord.strip().split()
|
||||
if len(parts) >= 2:
|
||||
lat, lng = map(float, parts[:2])
|
||||
else:
|
||||
raise ValueError("Invalid coordinate format")
|
||||
return (lat, lng)
|
||||
except (ValueError, AttributeError) as e:
|
||||
print(f"Error parsing coordinates {coord}: {e}")
|
||||
return None
|
||||
elif isinstance(coord, (list, tuple)) and len(coord) >= 2:
|
||||
return (float(coord[0]), float(coord[1]))
|
||||
return None
|
||||
|
||||
# Parse the coordinates
|
||||
target_location = parse_coords(target_location)
|
||||
answer_location_coords = answer_location
|
||||
answer_location = parse_coords(answer_location)
|
||||
|
||||
|
||||
if not target_location or not answer_location:
|
||||
print("Invalid coordinates, cannot calculate score")
|
||||
is_correct = False
|
||||
score = 0
|
||||
else:
|
||||
|
||||
# Configuration - Make these easily adjustable
|
||||
MAX_DISTANCE_KM = 50 # Maximum distance for scoring (in km)
|
||||
DIRECT_HIT_BONUS = 200 # Extra points for direct hit within tolerance
|
||||
|
||||
try:
|
||||
# Calculate distance
|
||||
distance_km = geodesic(answer_location, target_location).kilometers
|
||||
tolerance_km = float(tolerance_radius) / 1000 # Convert meters to kilometers
|
||||
|
||||
print(f"Distance: {distance_km} km, Tolerance: {tolerance_km} km")
|
||||
|
||||
# Calculate base score with non-linear decrease
|
||||
if distance_km <= tolerance_km:
|
||||
# Direct hit - full points + bonus
|
||||
base_score = 1000 + DIRECT_HIT_BONUS
|
||||
is_correct = True
|
||||
else:
|
||||
if distance_km > MAX_DISTANCE_KM:
|
||||
# Beyond max distance - 0 points
|
||||
base_score = 0
|
||||
is_correct = False
|
||||
else:
|
||||
# Non-linear decrease using exponential falloff
|
||||
progress = (distance_km - tolerance_km) / (MAX_DISTANCE_KM - tolerance_km)
|
||||
falloff = progress ** 1.5 # 1.5 makes the falloff steeper at the beginning
|
||||
base_score = 1000 * (1 - falloff)
|
||||
is_correct = True
|
||||
|
||||
# Apply time penalty (reduces score based on time taken)
|
||||
if is_correct and base_score > 0:
|
||||
# Calculate time taken as a fraction of total time (0-1)
|
||||
time_taken_sec = (int(current_question.time_per_question) * 1000 - time_remaining) / 1000
|
||||
max_time_sec = int(current_question.time_per_question)
|
||||
time_fraction = min(1.0, time_taken_sec / max_time_sec) if max_time_sec > 0 else 1.0
|
||||
|
||||
# Reduce points based on time taken (up to 50% reduction for answering at the last second)
|
||||
time_penalty = base_score * 0.5 * time_fraction # Up to 50% penalty
|
||||
final_score = max(1, base_score - time_penalty) # At least 1 point if correct
|
||||
else:
|
||||
final_score = base_score # 0 points for incorrect answers
|
||||
|
||||
# Apply max score limit after all calculations (including bonus)
|
||||
final_score = min(final_score, 1200) # Cap at 1200 (1000 + 200 bonus)
|
||||
|
||||
score = int(round(final_score))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error calculating score: {e}")
|
||||
is_correct = False
|
||||
score = 0
|
||||
|
||||
# Save the answer
|
||||
answer_obj, created = QuizAnswer.objects.get_or_create(
|
||||
participant=participant,
|
||||
question_index=quiz_game.current_question_index,
|
||||
defaults={
|
||||
'answer_index': answer_index,
|
||||
'is_correct': is_correct,
|
||||
'score': score,
|
||||
'time_remaining': time_remaining
|
||||
}
|
||||
)
|
||||
|
||||
if not created:
|
||||
answer_obj.answer_index = answer_index
|
||||
answer_obj.is_correct = is_correct
|
||||
answer_obj.score = score
|
||||
answer_obj.time_remaining = time_remaining
|
||||
answer_obj.save()
|
||||
|
||||
participant.last_score = score
|
||||
participant.score += score
|
||||
participant.coordinate_answer= answer_location_coords
|
||||
participant.last_answer_correct = is_correct
|
||||
participant.save()
|
||||
|
||||
return True
|
||||
|
||||
except (QuizGameParticipant.DoesNotExist, IndexError):
|
||||
return False
|
||||
|
||||
|
||||
@database_sync_to_async
|
||||
def save_answer(self, participant_id, answer, time_remaining):
|
||||
def save_answer(self, participant_id, answer_index, time_remaining):
|
||||
try:
|
||||
participant = QuizGameParticipant.objects.get(
|
||||
quiz_game__join_code=self.join_code,
|
||||
@@ -335,95 +183,58 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
quiz_game = participant.quiz_game
|
||||
current_question = quiz_game.quiz_id.questions.all()[quiz_game.current_question_index]
|
||||
|
||||
question_data = json.loads(current_question.data)
|
||||
question_type = question_data.get("type")
|
||||
|
||||
# Check if answer is correct
|
||||
is_correct = False
|
||||
answer_index = None
|
||||
answers_order = None
|
||||
answer_text = None
|
||||
|
||||
if question_type in ["multiple_choice", "true_false"]:
|
||||
# answer ist hier eine Integer-Antwort (Index)
|
||||
answer_index = int(answer)
|
||||
if answer_index >= 0:
|
||||
is_correct = question_data['options'][answer_index].get('is_correct', False)
|
||||
|
||||
elif question_type == "input":
|
||||
user_input = str(answer).strip().lower().replace(" ", "")
|
||||
correct_value = str(question_data['options'][0].get('value', '')).strip().lower().replace(" ", "")
|
||||
is_correct = (user_input == correct_value)
|
||||
answer_text = user_input
|
||||
answer_index = 0 if is_correct else 1
|
||||
|
||||
elif question_type == "order":
|
||||
# answer ist hier eine Liste von Strings mit der Reihenfolge
|
||||
answers_order = answer
|
||||
correct_sequence = sorted(question_data["options"], key=lambda x: x["order"])
|
||||
correct_values = [opt["value"] for opt in correct_sequence]
|
||||
is_correct = (answers_order == correct_values)
|
||||
|
||||
elif question_type == "guess":
|
||||
question_data = json.loads(current_question.data)
|
||||
print(answer_index)
|
||||
try:
|
||||
user_val = float(answer)
|
||||
correct_val = float(question_data['options'][0].get('value', 0))
|
||||
tolerance = float(question_data.get('tolerance', 0))
|
||||
percentage = 1 - abs(user_val - correct_val) / (abs(correct_val) + 1e-5)
|
||||
|
||||
|
||||
score_percentage= min(round(percentage, 3), 1.0)
|
||||
if score_percentage>= abs(tolerance/100):
|
||||
is_correct=True
|
||||
else:
|
||||
is_correct=False
|
||||
|
||||
print(score_percentage)
|
||||
|
||||
|
||||
except ValueError:
|
||||
is_correct = False # falls keine Zahl eingegeben wurde
|
||||
answer_index = 0 if is_correct else 1
|
||||
|
||||
|
||||
# Score berechnen
|
||||
try:
|
||||
if score_percentage==None:
|
||||
score_percentage=1
|
||||
if answer_index >= 0 : # -1 means timeout
|
||||
is_correct = question_data['options'][answer_index]['is_correct']
|
||||
print(is_correct)
|
||||
except:
|
||||
score_percentage=1
|
||||
user_input = str(answer_index).strip().lower().replace(" ", "")
|
||||
correct_value = str(question_data['options'][0].get('value', '')).strip().lower().replace(" ", "")
|
||||
print("Richtig",correct_value)
|
||||
print(user_input)
|
||||
if user_input == correct_value:
|
||||
is_correct=True
|
||||
answer_index=int(0)
|
||||
print(is_correct)
|
||||
else:
|
||||
answer_index=int(1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Calculate score based on correctness and time
|
||||
score = 0
|
||||
if is_correct:
|
||||
base_score = 1000
|
||||
time_factor = time_remaining / int(current_question.time_per_question) / 1000
|
||||
score = int(base_score * (0.75 + 0.25 * time_factor))*score_percentage
|
||||
print(score)
|
||||
time_factor = time_remaining / int(current_question.time_per_question)/int(1000)
|
||||
score = int(base_score * (0.5 + 0.5 * time_factor))
|
||||
|
||||
# Antwort speichern oder aktualisieren
|
||||
# Update or create answer in QuizAnswer table
|
||||
answer_obj, created = QuizAnswer.objects.get_or_create(
|
||||
participant=participant,
|
||||
question_index=quiz_game.current_question_index,
|
||||
defaults={
|
||||
'answer_index': answer_index,
|
||||
'answers_order': answers_order,
|
||||
'is_correct': is_correct,
|
||||
'score': score,
|
||||
'time_remaining': time_remaining
|
||||
}
|
||||
)
|
||||
|
||||
if not created:
|
||||
answer_obj.answer_index = answer_index
|
||||
answer_obj.answers_order = answers_order
|
||||
answer_obj.is_correct = is_correct
|
||||
answer_obj.score = score
|
||||
answer_obj.time_remaining = time_remaining
|
||||
answer_obj.save()
|
||||
|
||||
# Teilnehmer aktualisieren
|
||||
participant.last_score = score
|
||||
participant.score += score
|
||||
participant.last_answer=answer_index
|
||||
participant.last_answer_correct = is_correct
|
||||
participant.save()
|
||||
|
||||
@@ -431,46 +242,25 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
except (QuizGameParticipant.DoesNotExist, IndexError):
|
||||
return False
|
||||
|
||||
|
||||
@database_sync_to_async
|
||||
def get_answer_stats(self):
|
||||
try:
|
||||
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
||||
|
||||
# Count answers for each option in the current question
|
||||
stats = {}
|
||||
answers = QuizAnswer.objects.filter(
|
||||
participant__quiz_game=quiz_game,
|
||||
question_index=quiz_game.current_question_index
|
||||
)
|
||||
|
||||
for answer in answers:
|
||||
if answer.answer_index >= 0: # Ignore timeouts (-1)
|
||||
stats[answer.answer_index] = stats.get(answer.answer_index, 0) + 1
|
||||
|
||||
|
||||
|
||||
current_question = quiz_game.quiz_id.questions.all()[quiz_game.current_question_index]
|
||||
|
||||
question_data = json.loads(current_question.data)
|
||||
question_type = question_data.get("type")
|
||||
stats = {}
|
||||
|
||||
for single_answer in answers:
|
||||
|
||||
|
||||
if question_type == "order":
|
||||
correct_answer=[str(opt["value"]) for opt in sorted(question_data["options"], key=lambda x: x["order"])]
|
||||
|
||||
if single_answer.answers_order== correct_answer:
|
||||
key = 0 # richtig
|
||||
else:
|
||||
key = 1 # falsch
|
||||
stats[key] = stats.get(key, 0) + 1
|
||||
else:
|
||||
key = str(single_answer.answer_index if single_answer.answer_index is not None else "unanswered")
|
||||
stats[key] = stats.get(key, 0) + 1
|
||||
return stats
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
except QuizGame.DoesNotExist:
|
||||
return {}
|
||||
|
||||
@database_sync_to_async
|
||||
def save_rating(self, participant_id, rating):
|
||||
@@ -583,26 +373,6 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
'redirect_url': reverse('play:question', kwargs={'join_code': self.join_code})
|
||||
}
|
||||
)
|
||||
elif result == 'winner_podest':
|
||||
# Notify clients to redirect to next question
|
||||
await self.channel_layer.group_send(
|
||||
self.game_group_name,
|
||||
{
|
||||
'type': 'game_state_update',
|
||||
'action': 'show_winner_podest',
|
||||
'redirect_url': reverse('play:winner_podest', kwargs={'join_code': self.join_code})
|
||||
}
|
||||
)
|
||||
elif result == 'scoreboard':
|
||||
# Notify clients to redirect to next question
|
||||
await self.channel_layer.group_send(
|
||||
self.game_group_name,
|
||||
{
|
||||
'type': 'game_state_update',
|
||||
'action': 'show_scoreboard',
|
||||
'redirect_url': reverse('play:scoreboard', kwargs={'join_code': self.join_code})
|
||||
}
|
||||
)
|
||||
|
||||
@database_sync_to_async
|
||||
def _show_scores(self):
|
||||
@@ -627,34 +397,10 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
}
|
||||
)
|
||||
|
||||
@database_sync_to_async
|
||||
def counter_played_quizzes(self): # Zähler, wie oft ein Quiz gespielt worden ist.
|
||||
|
||||
quiz = QuizGame.objects.get(join_code=self.join_code).quiz_id
|
||||
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if quiz_game.single_player_or_multiple=="multiple":
|
||||
quiz.quiz_played_Moderiermodus+=1
|
||||
|
||||
active_participants = QuizGameParticipant.objects.filter(
|
||||
quiz_game=quiz_game
|
||||
).count()
|
||||
quiz.quiz_played_Moderiermodus_players+=active_participants
|
||||
|
||||
else:
|
||||
quiz.quiz_played_Lernmodus+=1
|
||||
quiz.save()
|
||||
|
||||
@database_sync_to_async
|
||||
def _finish_game(self):
|
||||
try:
|
||||
|
||||
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
||||
|
||||
quiz_game.current_state = 'finished'
|
||||
quiz_game.save()
|
||||
return True
|
||||
@@ -663,7 +409,6 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
|
||||
async def finish_game(self):
|
||||
success = await self._finish_game()
|
||||
await self.counter_played_quizzes()
|
||||
if success:
|
||||
# Notify clients to redirect to finished page
|
||||
await self.channel_layer.group_send(
|
||||
@@ -674,48 +419,3 @@ class GameConsumer(AsyncWebsocketConsumer):
|
||||
'redirect_url': reverse('play:finished', kwargs={'join_code': self.join_code})
|
||||
}
|
||||
)
|
||||
@database_sync_to_async
|
||||
def _show_winner_podest(self):
|
||||
try:
|
||||
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
||||
quiz_game.current_state = 'winner_podest'
|
||||
quiz_game.save()
|
||||
return True
|
||||
except QuizGame.DoesNotExist:
|
||||
return False
|
||||
|
||||
async def show_winner_podest(self):
|
||||
success = await self._show_winner_podest()
|
||||
if success:
|
||||
# Notify clients to redirect to finished page
|
||||
await self.channel_layer.group_send(
|
||||
self.game_group_name,
|
||||
{
|
||||
'type': 'game_state_update',
|
||||
'action': 'show_winner_podest',
|
||||
'redirect_url': reverse('play:winner_podest', kwargs={'join_code': self.join_code})
|
||||
}
|
||||
)
|
||||
|
||||
@database_sync_to_async
|
||||
def _show_scoreboard(self):
|
||||
try:
|
||||
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
||||
quiz_game.current_state = 'scoreboard'
|
||||
quiz_game.save()
|
||||
return True
|
||||
except QuizGame.DoesNotExist:
|
||||
return False
|
||||
|
||||
async def show_scoreboard(self):
|
||||
success = await self._show_scoreboard()
|
||||
if success:
|
||||
# Notify clients to redirect to finished page
|
||||
await self.channel_layer.group_send(
|
||||
self.game_group_name,
|
||||
{
|
||||
'type': 'game_state_update',
|
||||
'action': 'show_scoreboard',
|
||||
'redirect_url': reverse('play:scoreboard', kwargs={'join_code': self.join_code})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-07-18 13:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('play', '0012_quizgameparticipant_last_score'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizanswer',
|
||||
name='answer_text',
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='quizanswer',
|
||||
name='answers_order',
|
||||
field=models.JSONField(blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='quizanswer',
|
||||
name='answer_index',
|
||||
field=models.IntegerField(blank=True, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-07-25 16:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('play', '0013_quizanswer_answer_text_quizanswer_answers_order_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='quizgame',
|
||||
name='current_state',
|
||||
field=models.CharField(choices=[('lobby', 'In Lobby'), ('question', 'Frage läuft'), ('scores', 'Punkteübersicht'), ('winner_podest', 'Siegerpodest'), ('finished', 'Beendet')], default='lobby', max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-07-28 13:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('play', '0014_alter_quizgame_current_state'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='quizgame',
|
||||
name='current_state',
|
||||
field=models.CharField(choices=[('lobby', 'In Lobby'), ('question', 'Frage läuft'), ('scores', 'Punkteübersicht'), ('winner_podest', 'Siegerpodest'), ('finished', 'Beendet'), ('scoreboard', 'Scoreboard')], default='lobby', max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-01 15:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('play', '0015_alter_quizgame_current_state'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizgameparticipant',
|
||||
name='coordinate_answer',
|
||||
field=models.CharField(blank=True, max_length=256, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,28 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-14 13:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('play', '0016_quizgameparticipant_coordinate_answer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizgame',
|
||||
name='quiz_played_Lernmodus',
|
||||
field=models.IntegerField(blank=True, default=0, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='quizgame',
|
||||
name='quiz_played_Moderiermodus',
|
||||
field=models.IntegerField(blank=True, default=0, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='quizgame',
|
||||
name='quiz_played_Moderiermodus_players',
|
||||
field=models.IntegerField(blank=True, default=0, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-14 14:11
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('play', '0017_quizgame_quiz_played_lernmodus_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizgame',
|
||||
name='single_player_or_multiple',
|
||||
field=models.CharField(blank=True, max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -1,29 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-14 14:50
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('play', '0018_quizgame_single_player_or_multiple'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='quizgame',
|
||||
name='quiz_played_Lernmodus',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='quizgame',
|
||||
name='quiz_played_Moderiermodus',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='quizgame',
|
||||
name='quiz_played_Moderiermodus_players',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='quizgame',
|
||||
name='single_player_or_multiple',
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.1.7 on 2025-11-14 15:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('play', '0019_remove_quizgame_quiz_played_lernmodus_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='quizgame',
|
||||
name='single_player_or_multiple',
|
||||
field=models.CharField(blank=True, max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -10,12 +10,9 @@ class QuizGame(models.Model):
|
||||
('lobby', 'In Lobby'),
|
||||
('question', 'Frage läuft'),
|
||||
('scores', 'Punkteübersicht'),
|
||||
('winner_podest','Siegerpodest'),
|
||||
('finished', 'Beendet'),
|
||||
('scoreboard', 'Scoreboard'),
|
||||
|
||||
('finished', 'Beendet')
|
||||
]
|
||||
single_player_or_multiple=models.CharField(max_length=20, blank=True)
|
||||
|
||||
host_id = models.CharField(max_length=200, unique=True)
|
||||
join_code = models.CharField(max_length=6, unique=True, blank=True)
|
||||
quiz_id = models.ForeignKey(QivipQuiz, on_delete=models.CASCADE)
|
||||
@@ -50,7 +47,6 @@ class QuizGameParticipant(models.Model):
|
||||
avatar = models.CharField(max_length=200, blank=True, default="")
|
||||
last_heartbeat = models.DateTimeField(auto_now_add=True)
|
||||
last_answer = models.IntegerField(null=True, blank=True)
|
||||
coordinate_answer = models.CharField(null=True, blank=True,max_length=256)
|
||||
last_answer_correct = models.BooleanField(null=True, blank=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
@@ -67,9 +63,7 @@ class QuizGameParticipant(models.Model):
|
||||
class QuizAnswer(models.Model):
|
||||
participant = models.ForeignKey(QuizGameParticipant, on_delete=models.CASCADE, related_name='answers')
|
||||
question_index = models.IntegerField()
|
||||
answer_index = models.IntegerField(null=True, blank=True) # für MC, TF
|
||||
answer_text = models.TextField(null=True, blank=True) # für Input
|
||||
answers_order = models.JSONField(null=True, blank=True) # NEU für "order"
|
||||
answer_index = models.IntegerField()
|
||||
is_correct = models.BooleanField()
|
||||
score = models.IntegerField()
|
||||
time_remaining = models.IntegerField()
|
||||
|
||||
@@ -12,8 +12,6 @@ urlpatterns = [
|
||||
path('game/<str:join_code>/waiting', views.waiting_room, name='waiting'),
|
||||
path('game/<str:join_code>/scores', views.scores, name='scores'),
|
||||
path('game/<str:join_code>/finished', views.finished, name='finished'),
|
||||
path('game/<str:join_code>/winner_podest', views.winner_podest, name='winner_podest'),
|
||||
path('game/<str:join_code>/scoreboard', views.scoreboard, name='scoreboard'),
|
||||
path('select_mode/<str:join_code>', views.select_mode, name='select_mode'),
|
||||
path('selected_mode/<str:join_code>', views.selected_mode, name='selected_mode'),
|
||||
]
|
||||
@@ -188,49 +188,6 @@ def scores(request, join_code):
|
||||
'question_index': quiz_game.current_question_index,
|
||||
'total_questions': len(questions)
|
||||
})
|
||||
|
||||
|
||||
def scoreboard(request, join_code):
|
||||
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
|
||||
|
||||
# Verify game state
|
||||
if quiz_game.current_state != 'scoreboard':
|
||||
return redirect('play:lobby', join_code=join_code)
|
||||
|
||||
# Get participants sorted by score
|
||||
participants = QuizGameParticipant.objects.filter(quiz_game=quiz_game).order_by('-score')
|
||||
|
||||
# Check if user is host
|
||||
is_host = False
|
||||
if 'host_id' in request.COOKIES and request.COOKIES['host_id'] == quiz_game.host_id:
|
||||
is_host = True
|
||||
|
||||
# Get current question for context
|
||||
questions = quiz_game.quiz_id.questions.all()
|
||||
current_question = questions[quiz_game.current_question_index]
|
||||
question_data = json.loads(current_question.data)
|
||||
|
||||
try:
|
||||
my_participant = request.session.get('my_participant-participant_id')
|
||||
participant = QuizGameParticipant.objects.get(participant_id=my_participant)
|
||||
my_participant=participant
|
||||
except:
|
||||
participant = QuizGameParticipant.objects.none()
|
||||
|
||||
return render(request, 'play/game/scoreboard.html', {
|
||||
'quiz_game': quiz_game,
|
||||
'participants': participants,
|
||||
'participant': participant,
|
||||
'my_participant':my_participant,
|
||||
'is_host': is_host,
|
||||
'host_id': quiz_game.host_id if is_host else None,
|
||||
'is_last_question': quiz_game.current_question_index + 1 >= len(questions),
|
||||
'question_data': question_data,
|
||||
'question_index': quiz_game.current_question_index,
|
||||
'total_questions': len(questions)
|
||||
})
|
||||
|
||||
|
||||
"""
|
||||
def finished(request, join_code):
|
||||
|
||||
@@ -268,45 +225,6 @@ def finished(request, join_code):
|
||||
"""
|
||||
|
||||
|
||||
def winner_podest(request, join_code):
|
||||
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
|
||||
|
||||
# Verify game state
|
||||
if quiz_game.current_state != 'winner_podest':
|
||||
return redirect('play:lobby', join_code=join_code)
|
||||
|
||||
# Get participants sorted by score
|
||||
participants = QuizGameParticipant.objects.filter(quiz_game=quiz_game).order_by('-score')
|
||||
|
||||
# Check if user is host
|
||||
is_host = False
|
||||
if 'host_id' in request.COOKIES and request.COOKIES['host_id'] == quiz_game.host_id:
|
||||
is_host = True
|
||||
|
||||
# Get current question for context
|
||||
questions = quiz_game.quiz_id.questions.all()
|
||||
current_question = questions[quiz_game.current_question_index]
|
||||
question_data = json.loads(current_question.data)
|
||||
|
||||
try:
|
||||
my_participant = request.session.get('my_participant-participant_id')
|
||||
participant = QuizGameParticipant.objects.get(participant_id=my_participant)
|
||||
my_participant=participant
|
||||
except:
|
||||
participant = QuizGameParticipant.objects.none()
|
||||
|
||||
return render(request, 'play/game/winner_podest.html', {
|
||||
'quiz_game': quiz_game,
|
||||
'participants': participants,
|
||||
'participant': participant,
|
||||
'my_participant':my_participant,
|
||||
'is_host': is_host,
|
||||
'host_id': quiz_game.host_id if is_host else None,
|
||||
'is_last_question': quiz_game.current_question_index + 1 >= len(questions),
|
||||
'question_data': question_data,
|
||||
'question_index': quiz_game.current_question_index,
|
||||
'total_questions': len(questions)
|
||||
})
|
||||
|
||||
def finished(request, join_code):
|
||||
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
|
||||
@@ -344,6 +262,7 @@ def finished(request, join_code):
|
||||
return redirect('home')
|
||||
|
||||
|
||||
|
||||
def question(request, join_code):
|
||||
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
|
||||
|
||||
@@ -420,38 +339,13 @@ def waiting_room(request, join_code):
|
||||
|
||||
def selected_mode(request, join_code):
|
||||
|
||||
|
||||
mode = request.GET.get('mode','default')
|
||||
request.session['mode'] = mode
|
||||
|
||||
quiz_game = QuizGame.objects.get(join_code=join_code)
|
||||
if mode=="learn":
|
||||
|
||||
quiz_game.single_player_or_multiple="single"
|
||||
else:
|
||||
|
||||
quiz_game.single_player_or_multiple="multiple"
|
||||
|
||||
quiz_game.save()
|
||||
|
||||
|
||||
quiz_game = get_object_or_404(QuizGame, join_code=join_code)
|
||||
quiz = get_object_or_404(QivipQuiz, pk=quiz_game.quiz_id.id)
|
||||
QuizGameParticipant.objects.filter(quiz_game=quiz_game).delete()
|
||||
|
||||
# Hier werden die Fragen gemischt!
|
||||
import random
|
||||
questions = quiz_game.quiz_id.questions.all()
|
||||
for question in questions:
|
||||
try:
|
||||
data = json.loads(question.data)
|
||||
if (data.get('type') == 'order' or data.get('type') == 'multiple_choice') and 'options' in data:
|
||||
|
||||
random.shuffle(data['options'])
|
||||
question.data = json.dumps(data)
|
||||
question.save(update_fields=['data'])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
context = {
|
||||
'quiz': quiz,
|
||||
'quiz_game': quiz_game,
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
.select2-container {
|
||||
@apply font-black!;
|
||||
}
|
||||
|
||||
|
||||
|
||||
qp-container {
|
||||
@apply max-w-xl w-full mx-auto;
|
||||
}
|
||||
.dark-form label {
|
||||
@apply dark:text-white!;
|
||||
}
|
||||
|
||||
nav div ul.qp-nav-list li {
|
||||
@apply text-white hover:scale-110 hover:border-b-2 hover:border-white transition duration-200;
|
||||
@@ -62,34 +53,6 @@ a.qp-a-button-small {
|
||||
}
|
||||
|
||||
|
||||
.field-group form p {
|
||||
@apply flex flex-col gap-2;
|
||||
}
|
||||
|
||||
.field-group form p label {
|
||||
@apply text-gray-900 font-semibold text-sm;
|
||||
}
|
||||
|
||||
.field-group form p input {
|
||||
@apply p-3 rounded-full bg-gray-200 focus:scale-105 transition duration-200 font-black
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-400 focus:bg-blue-100;
|
||||
}
|
||||
|
||||
.field-group form p select {
|
||||
@apply p-3 rounded-md bg-gray-200 focus:scale-105 transition duration-200 font-black
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-400 focus:bg-blue-100;
|
||||
}
|
||||
|
||||
.field-group form p textarea {
|
||||
@apply p-3 rounded-xl bg-gray-200 focus:scale-105 transition duration-200 font-black
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-400 focus:bg-blue-100 min-h-[100px];
|
||||
}
|
||||
|
||||
.field-group form p button {
|
||||
@apply p-3 rounded-full bg-indigo-500 hover:bg-indigo-600 text-white focus:scale-105
|
||||
transition duration-200 font-black shadow-md hover:shadow-lg;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ul.messages li {
|
||||
@@ -159,12 +122,3 @@ div.qp-question-container {
|
||||
div.qp-question-container img {
|
||||
@apply max-w-[50vw] max-h-[40vh] mx-auto;
|
||||
}
|
||||
|
||||
div#mapquestion-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
div#map {
|
||||
@apply md:w-[600px] lg:w-[900px] h-[400px]
|
||||
}
|
||||
2
django/static/js/sortablejs/Sortable.min.js
vendored
2
django/static/js/sortablejs/Sortable.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-2xl mx-auto mt-20 p-6 bg-white shadow-xl rounded-2xl text-center dark:bg-transparent">
|
||||
<div class="max-w-2xl mx-auto mt-20 p-6 bg-white shadow-xl rounded-2xl text-center">
|
||||
|
||||
<div class="flex justify-center items-center gap-4">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
||||
@@ -21,8 +21,8 @@
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h2 class="text-2xl font-semibold text-gray-800 mb-2 dark:text-white ">Seite nicht gefunden</h2>
|
||||
<p class="text-lg text-gray-600 mb-6 dark:text-white">Ups, die angeforderte Seite wurde nicht gefunden. Überprüfen Sie die URL oder kehren Sie zur Startseite zurück.</p>
|
||||
<h2 class="text-2xl font-semibold text-gray-800 mb-2 ">Seite nicht gefunden</h2>
|
||||
<p class="text-lg text-gray-600 mb-6">Ups, die angeforderte Seite wurde nicht gefunden. Überprüfen Sie die URL oder kehren Sie zur Startseite zurück.</p>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<a href="{% url 'homepage:home' %}"
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class="flex justify-center items-center mt-12 mx-auto px-10">
|
||||
<div class="input-group p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md dark:bg-transparent! dark:text-white! ">
|
||||
<div class="flex justify-center items-center mt-12 ">
|
||||
<div class="input-group p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md">
|
||||
<div class="grid place-content-center h-full mb-4 flex-col items-center justify-between">
|
||||
|
||||
<h1 class="text-center m-4 text-3xl lg:text-6xl">Willkommen, <b>{{ request.user.username }}</b>!</h1>
|
||||
@@ -17,35 +17,17 @@
|
||||
<button class="text-center text-sm w-full mt-4 font-light text-blue-600"><a href="{% url 'accounts:password_change' %}">Passwort ändern</a></button>
|
||||
<button class="text-center text-sm w-full mt-4 font-light text-red-600"><a href="{% url 'accounts:delete_account' %}">Account löschen</a></button>
|
||||
|
||||
<!-- Kritik eigener Quizze -->
|
||||
{% if own_critisized_quizzes %}
|
||||
<div class="border-2 border-red-600 p-6 rounded-xl shadow-inner bg-red-50 flex flex-col gap-4 items-start mt-8 dark:bg-transparent">
|
||||
<div class="flex items-center gap-2 text-xl font-semibold text-red-800 dark:text-red-600">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
|
||||
</svg>
|
||||
Kritik
|
||||
</div>
|
||||
<p class="text-xs opacity-50 dark:text-white">
|
||||
Diese Quizze wurden von anderen Nutzern kritisiert, da sie als fehlerhaft oder nicht angemessen
|
||||
wahrgenommen wurden. Bitte überprüfen und verbessern Sie Ihre Quizze so schnell wie möglich.</p>
|
||||
{% for own_critisized_quiz in own_critisized_quizzes %}
|
||||
<a href="{% url 'library:detail_quiz' own_critisized_quiz.id %}"> <span class="font-bold text-blue-600 hover:underline">{{ own_critisized_quiz }}</span> </a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="border-2 border-blue-600 p-6 rounded-xl shadow-inner bg-blue-50 flex flex-col gap-4 items-start mt-8">
|
||||
|
||||
<div class="border-2 border-blue-600 p-6 rounded-xl shadow-inner bg-blue-50 flex flex-col gap-4 items-start mt-8 dark:bg-transparent">
|
||||
|
||||
<div class="flex items-center gap-2 text-xl font-semibold text-blue-800 dark:text-blue-600">
|
||||
<div class="flex items-center gap-2 text-xl font-semibold text-blue-800">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-yellow-500">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 18.75h-9m9 0a3 3 0 0 1 3 3h-15a3 3 0 0 1 3-3m9 0v-3.375c0-.621-.503-1.125-1.125-1.125h-.871M7.5 18.75v-3.375c0-.621.504-1.125 1.125-1.125h.872m5.007 0H9.497m5.007 0a7.454 7.454 0 0 1-.982-3.172M9.497 14.25a7.454 7.454 0 0 0 .981-3.172M5.25 4.236c-.982.143-1.954.317-2.916.52A6.003 6.003 0 0 0 7.73 9.728M5.25 4.236V4.5c0 2.108.966 3.99 2.48 5.228M5.25 4.236V2.721C7.456 2.41 9.71 2.25 12 2.25c2.291 0 4.545.16 6.75.47v1.516M7.73 9.728a6.726 6.726 0 0 0 2.748 1.35m8.272-6.842V4.5c0 2.108-.966 3.99-2.48 5.228m2.48-5.492a46.32 46.32 0 0 1 2.916.52 6.003 6.003 0 0 1-5.395 4.972m0 0a6.726 6.726 0 0 1-2.749 1.35m0 0a6.772 6.772 0 0 1-3.044 0" />
|
||||
</svg>
|
||||
Erfolge
|
||||
</div>
|
||||
|
||||
<ul class="list-disc pl-6 text-base text-gray-700 dark:text-white">
|
||||
<ul class="list-disc pl-6 text-base text-gray-700">
|
||||
<li><b>{{ my_quizzes_number }}</b> Quizze erstellt</li>
|
||||
<li><b>{{ my_questions_number }}</b> Fragen insgesamt erstellt</li>
|
||||
</ul>
|
||||
@@ -55,7 +37,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<span class="text-[clamp(0.6rem,5vw,1rem)] font-black overflow-x-auto mt-1 ml-0.75 text-blue-600 p-0.75 bg-white dark:bg-transparent">qivip
|
||||
<span class="text-[clamp(0.6rem,5vw,1rem)] font-black overflow-x-auto mt-1 ml-0.75 text-blue-600 p-0.75 bg-white">qivip
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% block content %}
|
||||
<div class="grid place-content-center h-120 mt-12 mb-12 ">
|
||||
<div class="w-80 p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md lg:w-90">
|
||||
<h2 class="text-2xl text-center p-4 font-black dark:text-white">Anmeldung</h2>
|
||||
<h2 class="text-2xl text-center p-4 font-black">Anmeldung</h2>
|
||||
<form class="space-y-4" method="post">
|
||||
{% if form.errors %}
|
||||
<div class=" items-center text-red-600 font-black overflow-x-auto break-words text-center">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="grid place-content-center h-120 mt-12 mb-12">
|
||||
|
||||
<div class="w-80 p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md lg:w-90">
|
||||
<h2 class="text-2xl text-center p-4 font-black dark:text-white">Registrieren</h2>
|
||||
<h2 class="text-2xl text-center p-4 font-black">Registrieren</h2>
|
||||
|
||||
<form class=" space-y-4" method="post">
|
||||
{% if form.errors %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="de" class="dark:bg-gray-900 h-full">
|
||||
<html lang="de">
|
||||
<head>
|
||||
<link rel="icon" type="image/png" href="{% static 'icons/favicon.png' %}">
|
||||
<meta charset="UTF-8">
|
||||
@@ -9,16 +9,11 @@
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tsparticles/confetti@3.0.3/tsparticles.confetti.bundle.min.js"></script>
|
||||
<title>qivip</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin=""/>
|
||||
</head>
|
||||
<body class="flex flex-col min-h-screen">
|
||||
<main class="grow">
|
||||
<body>
|
||||
{% include 'partials/_nav.html' %}
|
||||
{% block content%}{% endblock %}
|
||||
{% block faqs%}{% endblock %}
|
||||
</main>
|
||||
{% include 'partials/_footer.html' %}
|
||||
|
||||
<!-- Toast Container -->
|
||||
@@ -73,12 +68,12 @@
|
||||
}, duration);
|
||||
}
|
||||
};
|
||||
|
||||
if (window.location.pathname !== "/library/") {
|
||||
localStorage.setItem("meine_seite_scroll", 0);}
|
||||
</script>
|
||||
{% block extra_js %}{% endblock %}
|
||||
<style>html {
|
||||
overflow-y: scroll;
|
||||
}</style>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,9 +4,9 @@
|
||||
<div class="grid place-content-center md:h-screen h-150 w-full -z-50 top-0 p-4">
|
||||
<div class="text-center">
|
||||
<h2 class="font-bold md:text-8xl text-6xl md:mb-8 text-blue-600">qivip</h2>
|
||||
<h3 class="mt-4 italic font-light sm:text-4xl text-md bg-gradient-to-r from-pink-500 via-purple-600 to-blue-700 bg-clip-text text-transparent">weil Lernen auch Spaß <br> machen kann!</h3>
|
||||
<h3 class="mt-4 italic font-extralight sm:text-4xl text-md">weil Lernen auch Spaß <br> machen kann!</h3>
|
||||
</div>
|
||||
<div class="grid sm:grid-cols-3 place-items-stretch text-center mt-8 gap-4 p-4 border-3 bg-blue-100 border-blue-100 rounded-md dark:bg-gray-900 dark:border-0">
|
||||
<div class="grid sm:grid-cols-3 place-items-stretch text-center mt-8 gap-4 p-4 border-3 bg-blue-100 border-blue-100 rounded-md">
|
||||
<a class="qp-a-button bg-green-500 text-white" href="{% url 'play:join_game' %}">Teilnehmen</a>
|
||||
<a class="qp-a-button bg-indigo-500 text-white" href="{% url 'library:overview_quiz' %}">Moderieren</a>
|
||||
<a class="qp-a-button bg-purple-500 text-white" href="{% url 'library:new_quiz' %}">Erstellen</a>
|
||||
@@ -22,45 +22,16 @@
|
||||
|
||||
</style>
|
||||
|
||||
{% if descriptions %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">Was kann qivip?</h2>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mb-16">
|
||||
|
||||
<div class="max-w-full p-4 mt-8 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<p class="text-lg font-bold text-gray-800 dark:text-white dark:bg-transparent mb-3">
|
||||
Mit qivip kann man:
|
||||
</p>
|
||||
|
||||
<div class="space-y-2 text-gray-700 dark:text-white ">
|
||||
{% for description in descriptions %}
|
||||
<p>
|
||||
<li class="ml-4"><span class="font-bold">{{ description.title }}</span> {{ description.description }}</li>
|
||||
</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
{% if faqs %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">FAQs</h2>
|
||||
<h2 class="text-2xl font-bold text-gray-900">FAQs</h2>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="input-group !max-w-4xl tracking-wide dark:!bg-gray-900 dark:!text-white">
|
||||
<div class="input-group !max-w-4xl tracking-wide">
|
||||
<h1 class="font-black text-blue-600 flex justify-between gap-2 lg:text-lg">
|
||||
|
||||
|
||||
@@ -72,7 +43,7 @@
|
||||
</div>
|
||||
|
||||
{% for faq in faqs %}
|
||||
<div class="input-group mt-2.5 !max-w-4xl hover:bg-gray-50 dark:!bg-gray-900 dark:!text-white ">
|
||||
<div class="input-group mt-2.5 !max-w-4xl hover:!bg-gray-50">
|
||||
<details>
|
||||
<summary class="flex justify-between cursor-pointer font-bold">{{ faq.question }}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-7">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content%}
|
||||
|
||||
<div class="flex justify-center items-center mt-12">
|
||||
<div class="input-group p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md dark:!bg-transparent dark:!text-white ">
|
||||
<div class="flex justify-center items-center mt-12 ">
|
||||
<div class="input-group p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md">
|
||||
<div class="grid place-content-center h-120">
|
||||
|
||||
<h1 class="text-center m-4 text-3xl lg:text-6xl">Impressum</b></h1>
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<span class="text-[clamp(0.6rem,5vw,1rem)] font-black overflow-x-auto mt-1 ml-0.75 text-blue-600 p-0.75 bg-white dark:bg-transparent ">qivip
|
||||
<span class="text-[clamp(0.6rem,5vw,1rem)] font-black overflow-x-auto mt-1 ml-0.75 text-blue-600 p-0.75 bg-white">qivip
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block content%}
|
||||
<div class="flex justify-center items-center mt-12 ">
|
||||
<div class="input-group p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md dark:!bg-transparent dark:!text-white">
|
||||
<div class="input-group p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md">
|
||||
<div class="grid place-content-center h-120">
|
||||
|
||||
<h1 class="text-center m-4 text-3xl lg:text-6xl">Datenschutz-</br>erklärung</h1>
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<span class="text-[clamp(0.6rem,5vw,1rem)] font-black overflow-x-auto mt-1 ml-0.75 text-blue-600 p-0.75 bg-white dark:!bg-transparent ">qivip
|
||||
<span class="text-[clamp(0.6rem,5vw,1rem)] font-black overflow-x-auto mt-1 ml-0.75 text-blue-600 p-0.75 bg-white">qivip
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="max-w-lg mx-auto mt-10">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md dark:text-white dark:bg-transparent">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md">
|
||||
<div class="text-center">
|
||||
<svg class="mx-auto h-12 w-12 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
</svg>
|
||||
|
||||
<h2 class="mt-4 text-xl font-bold text-gray-900 dark:text-white">Löschen bestätigen</h2>
|
||||
<h2 class="mt-4 text-xl font-bold text-gray-900">Löschen bestätigen</h2>
|
||||
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-white">
|
||||
<p class="mt-2 text-sm text-gray-600">
|
||||
{% if object.quiz_id %}
|
||||
Möchten Sie die Frage "{{ object.data.question }}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.
|
||||
{% else %}
|
||||
|
||||
@@ -1,88 +1,66 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
{% load static %}
|
||||
<script src="{% static 'js/sortablejs/Sortable.min.js' %}"></script>
|
||||
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="flex flex-wrap space-y-2 items-center justify-between mb-8 dark:text-white">
|
||||
<div class="container mx-auto">
|
||||
<div class="container mx-auto px-4">
|
||||
|
||||
<h1 class="text-2xl mt-4 font-bold dark:text-white trucate">
|
||||
{{ quiz.name }}
|
||||
</h1>
|
||||
<div class="flex flex-wrap space-y-2 items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-bold mr-4 ">{{ quiz.name }}</h1>
|
||||
|
||||
<div class=" flex flex-wrap space-x-2 ">
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="flex items-center justify-center lg:justify-start gap-4 mt-4 flex-wrap">
|
||||
|
||||
<a href="{% url 'library:copy_quiz' quiz.id %}"
|
||||
class="inline-flex bg-green-100 text-green-800 items-center py-2 px-2 gap-2 rounded-md text-sm lg:text-md hover:border-blue-600 border-2">
|
||||
duplizieren
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75" />
|
||||
</svg>
|
||||
|
||||
</a>
|
||||
|
||||
{% if quiz.questions.count %}
|
||||
<a href="{% url 'play:create_game' quiz.id %}"
|
||||
class="inline-flex bg-blue-100 text-blue-800 items-center py-2 px-2 gap-2 rounded-md text-sm lg:text-md hover:border-blue-600 border-2">
|
||||
spielen
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 0 1 0 1.972l-11.54 6.347a1.125 1.125 0 0 1-1.667-.986V5.653Z" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
<div class="flex flex-wrap">
|
||||
{% if quiz.user_id == request.user %}
|
||||
<!-- Edit -->
|
||||
|
||||
<a href="{% url 'library:edit_quiz' quiz.id %}"
|
||||
class="inline-flex bg-orange-100 text-orange-800 items-center py-2 px-2 gap-2 rounded-md text-sm lg:text-md hover:border-blue-600 border-2">
|
||||
bearbeiten
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<a href="{% url 'library:edit_quiz' quiz.id %}" class=" text-white px-2 py-2 rounded-md text-sm lg:text-lg hover:scale-110 transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-7 text-gray-600">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<!-- Delete -->
|
||||
<a href="{% url 'library:delete_quiz' quiz.id %}"
|
||||
class="inline-flex bg-red-100 text-red-800 items-center py-2 px-2 gap-2 rounded-md text-sm lg:text-md hover:border-blue-600 border-2">
|
||||
löschen
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
</a>
|
||||
<a href="{% url 'library:delete_quiz' quiz.id %}" class=" text-white px-2 py-2 rounded-md text-sm lg:text-lg hover:scale-110 transition-colors">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-7 text-gray-600">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="{% url 'library:copy_quiz' quiz.id %}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors duration-200">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m8.9-4.414c.376.023.75.05 1.124.08 1.131.094 1.976 1.057 1.976 2.192V16.5A2.25 2.25 0 0 1 18 18.75h-2.25m-7.5-10.5H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V18.75m-7.5-10.5h6.375c.621 0 1.125.504 1.125 1.125v9.375m-8.25-3 1.5 1.5 3-3.75" />
|
||||
</svg>
|
||||
Quiz kopieren
|
||||
</a>
|
||||
|
||||
{% if quiz.questions.count != 0 %}
|
||||
<a href="{% url 'play:create_game' quiz.id %}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors duration-200">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Spielen
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<summary class="list-none">
|
||||
<div class="flex items-center gap-3 mt-12">
|
||||
<p class="text-lg font-bold text-blue-600 dark:text-white">Fragen des Quizzes</p>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</summary>
|
||||
|
||||
<details open>
|
||||
{% if quiz.description %}
|
||||
<p class="text-gray-600 mb-4">{{ quiz.description }}</p>
|
||||
{% endif %}
|
||||
<div class="flex justify-end mb-4">
|
||||
{% if detail == True %}
|
||||
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}?show_answers=false">
|
||||
<div class="flex items-center mr-2 dark:text-white">
|
||||
<div class="flex items-center mr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88" />
|
||||
</svg>
|
||||
@@ -93,7 +71,7 @@
|
||||
{% else %}
|
||||
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}?show_answers=true">
|
||||
<div class="flex items-center mr-2 dark:text-white">
|
||||
<div class="flex items-center mr-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
@@ -107,87 +85,54 @@
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="bg-white rounded-xl shadow-md border-2 border-blue-600 dark:!bg-transparent dark:text-white">
|
||||
<div class="bg-white rounded-lg shadow-md border-blue-600 border-2 rounded-xl shadow-md">
|
||||
<div class="border-b border-gray-200 p-4">
|
||||
<div class="flex justify-between items-center flex-wrap">
|
||||
<!-- Titel -->
|
||||
|
||||
<h2 class="text-xl font-semibold">
|
||||
Fragen
|
||||
{% for question in questions %}{% if forloop.last %}({{ forloop.counter }}) {% endif %}{% endfor %}
|
||||
<h2 class="text-xl font-semibold">Fragen
|
||||
{% for question in questions %}{% if forloop.last %}({{ forloop.counter }}) {% endif %} {% endfor %}
|
||||
</h2>
|
||||
|
||||
|
||||
|
||||
<!-- Neue Frage Dropdown -->
|
||||
<div class="flex space-x-2 flex-wrap">
|
||||
{% if quiz.user_id == request.user %}
|
||||
<div class="relative inline-block text-left mt-2 sm:mt-0">
|
||||
<button type="button" class="inline-flex justify-center w-full rounded-md border border-blue-600 shadow-sm px-4 py-2 bg-blue-600 text-white text-sm font-medium hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" id="menu-button" aria-expanded="true" aria-haspopup="true">
|
||||
Neue Frage
|
||||
<!-- Pfeil nach unten -->
|
||||
<svg class="-mr-1 ml-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.584l3.71-4.354a.75.75 0 011.08 1.04l-4.25 5a.75.75 0 01-1.08 0l-4.25-5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<a href="{% url 'library:new_question' %}?type=input&quiz_id={{ quiz.id }}"
|
||||
class=" mt-1 bg-green-100 text-green-800 px-4 py-2 rounded-md text-xs lg:text-lg hover:border-blue-600 border-2">
|
||||
Eingabe
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<!-- Dropdown Menu -->
|
||||
<div class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 focus:outline-none hidden group" id="dropdown-menu">
|
||||
<div class="py-1 flex flex-col" role="menu" aria-orientation="vertical" aria-labelledby="menu-button">
|
||||
<a href="{% url 'library:new_question' %}?type=input&quiz_id={{ quiz.id }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" role="menuitem">Eingabe</a>
|
||||
<a href="{% url 'library:new_question' %}?type=multiple_choice&quiz_id={{ quiz.id }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" role="menuitem">Multiple Choice</a>
|
||||
<a href="{% url 'library:new_question' %}?type=true_false&quiz_id={{ quiz.id }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" role="menuitem">Wahr/Falsch</a>
|
||||
<a href="{% url 'library:new_question' %}?type=order&quiz_id={{ quiz.id }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" role="menuitem">Reihenfolge</a>
|
||||
<a href="{% url 'library:new_question' %}?type=guess&quiz_id={{ quiz.id }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" role="menuitem">Schätzen</a>
|
||||
<a href="{% url 'library:new_question' %}?type=map&quiz_id={{ quiz.id }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700" role="menuitem">Karte</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:new_question' %}?type=multiple_choice&quiz_id={{ quiz.id }}"
|
||||
class=" mt-1 bg-blue-100 text-blue-800 px-4 py-2 rounded-md text-xs lg:text-lg hover:border-blue-600 border-2">
|
||||
Multiple Choice
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:new_question' %}?type=true_false&quiz_id={{ quiz.id }}"
|
||||
class=" mt-1 bg-purple-100 text-purple-800 px-4 py-2 rounded-md text-xs lg:text-lg hover:border-blue-600 border-2">
|
||||
Wahr/Falsch
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dropdown Toggle Script -->
|
||||
<script>
|
||||
const btn = document.getElementById('menu-button');
|
||||
const menu = document.getElementById('dropdown-menu');
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
menu.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
window.addEventListener('click', (e) => {
|
||||
if (!btn.contains(e.target) && !menu.contains(e.target)) {
|
||||
menu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
{% if questions %}
|
||||
|
||||
<div class="divide-y divide-gray-200" id="questions-list">
|
||||
<div class="divide-y divide-gray-200">
|
||||
{% for question in questions %}
|
||||
|
||||
|
||||
|
||||
<div data-question-id="{{ question.id }}" class="question-item w-full rounded-xl p-4 hover:bg-gray-50 dark:hover:bg-[#2a2f3a] dark:text-white">
|
||||
|
||||
<div class="w-full rounded-xl p-4 hover:bg-gray-50">
|
||||
<p>{{ forloop.counter }}.</p>
|
||||
{% if quiz.user_id == request.user %} <a class="block flex h-full w-full" href="{% url 'library:edit_question' question.id %}" > {% endif %}
|
||||
<div class="flex justify-between items-start">
|
||||
<p class="question-counter pr-4 ">{{ forloop.counter }}.</p>
|
||||
|
||||
<div class="flex-grow">
|
||||
<p class="font-medium">{{ question.data.question }}</p>
|
||||
<div class="mt-1">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium {% if question.data.type == 'multiple_choice' %}bg-blue-100 text-blue-800 {% elif question.data.type == 'input' %} bg-green-100 text-green-800 {% elif question.data.type == 'map' %}bg-orange-100 text-orange-800 {% elif question.data.type == 'true_false' %}bg-purple-100 text-purple-800{% elif question.data.type == 'order' %}bg-yellow-100 text-yellow-800{% else %}bg-pink-100 text-pink-800 {% endif %}">
|
||||
{% if question.data.type == 'multiple_choice' %}Multiple Choice
|
||||
{% elif question.data.type == 'input' %}Eingabe
|
||||
{% elif question.data.type == 'map' %}Karte
|
||||
{% elif question.data.type == 'true_false' %}Wahr/Falsch
|
||||
{% elif question.data.type == 'order' %}Reihenfolge
|
||||
{% else %}Schätzen
|
||||
{% endif %}
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium {% if question.data.type == 'multiple_choice' %}bg-blue-100 text-blue-800 {% elif question.data.type == 'input' %} bg-green-100 text-green-800 {% else %}bg-purple-100 text-purple-800{% endif %}">
|
||||
{% if question.data.type == 'multiple_choice' %}Multiple Choice{% elif question.data.type == 'input' %}Eingabe{% else %}Wahr/Falsch{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
@@ -196,10 +141,10 @@
|
||||
{% if question.data.type == 'multiple_choice' %}
|
||||
<div class="w-full gap-4 md:flex justify-between ">
|
||||
<ul class="space-y-1 ">
|
||||
{% for option in question.data.options|dictsort:"order" %}
|
||||
{% for option in question.data.options %}
|
||||
|
||||
|
||||
<li class="flex items-center text-sm ">
|
||||
<li class="flex items-center text-sm">
|
||||
|
||||
{% if option.is_correct %}
|
||||
<svg class="h-4 w-4 text-green-500 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -207,10 +152,10 @@
|
||||
</svg>
|
||||
<span class="font-medium text-green-700">{{ option.value }}</span>
|
||||
{% else %}
|
||||
<svg class="h-4 w-4 text-gray-400 mr-1.5 dark:text-white " fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="h-4 w-4 text-gray-400 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 12H6"/>
|
||||
</svg>
|
||||
<span class="text-gray-600 dark:text-white">{{ option.value }}</span>
|
||||
<span class="text-gray-600">{{ option.value }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
@@ -220,58 +165,6 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% elif question.data.type == 'map' and detail %}
|
||||
<div class="mt-3">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-300">
|
||||
<span class="font-medium">Zielort:</span>
|
||||
<span id="target-location-{{ forloop.counter }}">
|
||||
{% if question.data.target_location %}
|
||||
{{ question.data.target_location }}
|
||||
{% else %}
|
||||
Kein Zielort festgelegt
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% if question.data.target_location %}
|
||||
<script>
|
||||
// Format the coordinates to 4 decimal places
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const targetEl = document.getElementById('target-location-{{ forloop.counter }}');
|
||||
if (targetEl) {
|
||||
const coords = targetEl.textContent.trim().split(',');
|
||||
if (coords.length === 2) {
|
||||
const lat = parseFloat(coords[0]).toFixed(4);
|
||||
const lng = parseFloat(coords[1]).toFixed(4);
|
||||
targetEl.textContent = `${lat}, ${lng}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% if question.data.tolerance_radius %}
|
||||
<div class="text-sm text-gray-600 dark:text-gray-300 mt-1">
|
||||
<span class="font-medium">Toleranzradius:</span> {{ question.data.tolerance_radius }} Meter
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if question.image.image.url %}
|
||||
<img src="{{ question.image.image.url }}" alt="Bild der Frage" class="w-[200px] rounded-lg shadow-md border-blue-600 border-2 rounded-xl shadow-md object-contain mt-4" />
|
||||
{% endif %}
|
||||
</div>
|
||||
{% elif question.data.type == 'order' %}
|
||||
<div class="w-full gap-4 md:flex justify-between ">
|
||||
<ul class="space-y-1 ">
|
||||
{% for option in question.data.options|dictsort:"order" %}
|
||||
<li class="flex items-center text-sm">
|
||||
{{ forloop.counter }}.
|
||||
<span class="font-medium text-green-700 dark:text-white">{{ option.value }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if question.image.image.url %}
|
||||
<img src="{{ question.image.image.url }}" alt="Bild der Frage" class="w-[200px] rounded-lg shadow-md border-blue-600 border-2 rounded-xl shadow-md object-contain mt-4 md:mt-0" />
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% elif question.data.type == 'input' %}
|
||||
<div class="w-full gap-4 md:flex justify-between ">
|
||||
<ul class="space-y-1 ">
|
||||
@@ -280,23 +173,7 @@
|
||||
<svg class="h-4 w-4 text-green-500 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
<span class="font-medium text-green-700 dark:text-white">{{ option.value }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if question.image.image.url %}
|
||||
<img src="{{ question.image.image.url }}" alt="Bild der Frage" class="w-[200px] rounded-lg shadow-md border-blue-600 border-2 rounded-xl shadow-md object-contain mt-4 md:mt-0" />
|
||||
{% endif %}
|
||||
</div>
|
||||
{% elif question.data.type == 'guess' %}
|
||||
<div class="w-full gap-4 md:flex justify-between ">
|
||||
<ul class="space-y-1 ">
|
||||
{% for option in question.data.options %}
|
||||
<li class="flex items-center text-sm">
|
||||
<svg class="h-4 w-4 text-green-500 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
<span class="font-medium text-green-700 dark:text-white">{{ option.value }}</span>
|
||||
<span class="font-medium text-green-700">{{ option.value }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@@ -309,8 +186,8 @@
|
||||
{% for option in question.data.options %}
|
||||
{% if option.is_correct %}
|
||||
<p class="text-sm">
|
||||
<span class="text-gray-500 dark:text-white">Richtige Antwort:</span>
|
||||
<span class="ml-1 font-medium {% if option.value == 'Wahr' %} text-green-700 {% else %}text-red-700{% endif %}">{{ option.value }}</span>
|
||||
<span class="text-gray-500">Richtige Antwort:</span>
|
||||
<span class="ml-1 font-medium {% if option.value == "Wahr" %} text-green-700 {% else %}text-red-700{% endif %}">{{ option.value }}</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -332,7 +209,7 @@
|
||||
<a href=" {% url 'library:delete_question' question.id %}"
|
||||
class=" text-red-700 px-4 py-1 rounded hover:scale-110 ">
|
||||
{% if quiz.user_id == request.user %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="dark:text-white size-7 text-gray-600">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-7 text-gray-600">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
||||
</svg>
|
||||
{% endif %}
|
||||
@@ -341,7 +218,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</a>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -356,354 +233,48 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</details>
|
||||
|
||||
|
||||
<!-- Kritik zum Quiz -->
|
||||
<div class="max-w-full">
|
||||
{% if quiz.criticism_quiz.all %}
|
||||
|
||||
|
||||
<summary class="list-none">
|
||||
<div class="flex items-center gap-3 mt-12">
|
||||
<p class="text-lg font-bold text-red-800 dark:text-white"> Kritik zum Quiz</p>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-red-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</summary>
|
||||
<details>
|
||||
|
||||
{% for critique in quiz.criticism_quiz.all %}
|
||||
|
||||
|
||||
<div class="max-w-full p-4 mt-4 rounded-2xl bg-gray-100 dark:bg-[#2a2f3a] shadow-sm">
|
||||
|
||||
{% if critique.id_question != None %}
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:edit_question' critique.id_question %}" class="text-blue-600 font-bold hover:underline">{{critique.question_criticism}}</a>:
|
||||
{% else %}
|
||||
<span class="font-bold">{{critique.question_criticism}}</span>:
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<span class="dark:text-white dark:bg-transparent">
|
||||
{{ critique.criticism }}
|
||||
</span>
|
||||
<p class="text-sm italic dark:text-white dark:bg-transparent ">
|
||||
Kritik von User {{ critique.user }}
|
||||
</p>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<p class="text-xs italic dark:text-white dark:bg-transparent ">
|
||||
{{ critique.creation_date }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if quiz.user_id == request.user %}
|
||||
<form method="POST" class="space-y-6" enctype="multipart/form-data" action="{% url 'library:delete_critique' critique.id quiz.id %}">
|
||||
{% csrf_token %}
|
||||
<div class="flex border-t-2 border-gray-200 mt-2">
|
||||
<button type="submit" class=" mt-2
|
||||
px-4 py-2 border border-transparent shadow-sm text-sm
|
||||
font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 cursor-pointer
|
||||
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Fehler von mir behoben!
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
</details>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<summary class="list-none">
|
||||
<div class="flex items-center gap-3 mt-12">
|
||||
<p class="text-lg font-bold text-blue-600 dark:text-white">Informationen über das Quiz</p>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
|
||||
</summary>
|
||||
<details>
|
||||
|
||||
<div class="max-w-full p-4 mt-4 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<p class="text-lg font-bold text-gray-800 dark:text-white dark:bg-transparent mb-3">
|
||||
Quiz-Infos
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<div class="flex">
|
||||
{% if quiz.description %}
|
||||
<p class="text-gray-600 dark:text-white">Beschreibung: {{ quiz.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
{% if quiz.base_quiz %}
|
||||
<p class="text-gray-600 dark:text-white">Bearbeitung:<span class="font-bold"> <a href="{% url 'library:overview_quiz' %}?filter=1&user={{ quiz.user_id }}">{{ quiz.user_id }}</a></span></p>
|
||||
<p class="text-gray-600 mt-2 ">Bearbeitung:<span class="text-blue-600 font-bold"> <a href="{% url 'library:overview_quiz' %}?filter=1&user={{ quiz.user_id }}">{{ quiz.user_id }}</a></span></p>
|
||||
{% else %}
|
||||
<p class="text-gray-600 dark:text-white">Autor:<span class="font-bold"> <a href="{% url 'library:overview_quiz' %}?filter=1&user={{ quiz.user_id }}">{{ quiz.user_id }}</a></span></p>
|
||||
<p class="text-gray-600 mt-2 ">Autor:<span class="text-blue-600 font-bold"> <a href="{% url 'library:overview_quiz' %}?filter=1&user={{ quiz.user_id }}">{{ quiz.user_id }}</a></span></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<div class="flex justify-end">
|
||||
{% if quiz.base_quiz %}
|
||||
<p class="text-gray-600 dark:text-white ">Kopie von: <a href="{% url 'library:detail_quiz' quiz.base_quiz.id %}"><span class="font-bold">{{ quiz.base_quiz}}</span></a></p>
|
||||
<p class="text-gray-600 ">Kopie von: <a href="{% url 'library:detail_quiz' quiz.base_quiz.id %}"><span class="text-blue-600 font-bold">{{ quiz.base_quiz}}</span></a></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="flex justify-end">
|
||||
{% if quiz.base_quiz %}
|
||||
<p class="text-gray-600 dark:text-white">Autor:<span class="font-bold"> <a href="{% url 'library:overview_quiz' %}?filter=1&user={{ quiz.base_quiz.user_id }}">{{ quiz.base_quiz.user_id }}</a></span></p>
|
||||
<p class="text-gray-600 ">Autor:<span class="text-blue-600 font-bold"> <a href="{% url 'library:overview_quiz' %}?filter=1&user={{ quiz.base_quiz.user_id }}">{{ quiz.base_quiz.user_id }}</a></span></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex">
|
||||
|
||||
<p class="text-gray-600 dark:text-white">Quiz ID:<span class="font-bold"> {{ quiz.pk }}</span></p>
|
||||
|
||||
</div>
|
||||
|
||||
{% if quiz.reference_quizzes.all %}
|
||||
<div class="flex">
|
||||
<p class="text-gray-600 dark:text-white">Verweis auf andere Quizze:
|
||||
<span class="font-bold">
|
||||
{% for reference in quiz.reference_quizzes.all %}
|
||||
<a href="{% url 'library:detail_quiz' reference.id %}">{{ reference.name}}</a>{% if not forloop.last %}, {% endif %}
|
||||
{% empty %}
|
||||
|
||||
{% endfor %}
|
||||
</span></p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="max-w-full p-4 mt-4 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<div class="flex flex-col max-w-full">
|
||||
<p class="text-lg font-bold text-gray-800 dark:text-white dark:bg-transparent mb-3">
|
||||
Credits
|
||||
</p>
|
||||
Credits:
|
||||
{% if quiz.credits %}
|
||||
<p class="text-blue-500 mb-2 w-full break-words dark:text-gray-400 ">
|
||||
<span class="break-words italic">{{ quiz.credits }}</span>
|
||||
<span class="font-normal break-words text-gray-600 dark:text-white">(Credits für das Quiz)</span>
|
||||
<p class="text-gray-600 mb-2 w-full break-words">
|
||||
<span class="font-bold break-words">{{ quiz.credits }}</span>
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="text-gray-500 dark:text-gray-400 ">Keine Credits für das Quizformular vorhanden.</p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="flex flex-col max-w-full">
|
||||
<ul class="space-y-2">
|
||||
{% regroup questions by credits as grouped_credits %}
|
||||
{% if grouped_credits|length == 1 and not grouped_credits.0.grouper %}
|
||||
<li class="text-gray-500 dark:text-gray-400 ">Keine zusätzlichen Credits für die einzelnen Fragen vorhanden.</li>
|
||||
{% else %}
|
||||
{% for question in questions %}
|
||||
{% if question.credits %}
|
||||
<li class="text-blue-500 mb-2 w-full break-words break-words dark:text-gray-400 ">
|
||||
<span class="italic">{{ question.credits }}</span>
|
||||
<span class="font-normal break-words text-gray-600 dark:text-white">(Frage: {{ forloop.counter }})</span>
|
||||
<li class="text-gray-600 mb-2 w-full break-words font-bold break-words">
|
||||
{{ question.credits }}
|
||||
<span class="font-medium text-gray-500 break-words">(Frage: {{ forloop.counter }})</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="max-w-full p-4 mt-4 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<p class="text-lg font-bold text-gray-800 dark:text-white dark:bg-transparent mb-3">
|
||||
Quiz-Statistik
|
||||
</p>
|
||||
|
||||
<div class="space-y-2 text-gray-700 dark:text-white ">
|
||||
<p>
|
||||
<span class="font-semibold">{{ quiz.quiz_played_Lernmodus }}</span>
|
||||
mal im Lernmodus gespielt
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="font-semibold">{{ quiz.quiz_played_Moderiermodus }}</span>
|
||||
mal im Moderiermodus gespielt
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
||||
(insg. {{ quiz.quiz_played_Moderiermodus_players }} Spieler)
|
||||
</span>
|
||||
</p>
|
||||
<p class="text-xs opacity-50">
|
||||
* zählt nur, wenn das Quiz vollständig beendet wird
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
|
||||
{% if quiz.user_id != request.user %}
|
||||
|
||||
<summary class="list-none">
|
||||
<div class="flex items-center gap-3 mt-12">
|
||||
<p class="text-lg font-bold text-red-800 dark:text-white">Das Quiz kritisieren</p>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-red-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</summary>
|
||||
<details>
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="max-w-full p-4 mt-4 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<p class="text-lg font-bold text-red-800 dark:text-white dark:bg-transparent ">
|
||||
Quiz kritisieren
|
||||
</p>
|
||||
<p class="text-xs opacity-50 mb-3 dark:text-white dark:bg-transparent">
|
||||
Sie haben einen Fehler in dem Quiz entdeckt oder halten eine Frage für nicht angemessen? <br>
|
||||
Dann weisen Sie den Autor gern darauf hin.
|
||||
</p>
|
||||
<select name="question_id" required
|
||||
class="w-full rounded-lg border-gray-300 p-2 dark:bg-gray-800 dark:text-white">
|
||||
<option value="">Kritik an einer bestimmten Frage oder am Quiz insgesamt?</option>
|
||||
|
||||
<option value="whole_quiz">
|
||||
allgemeine Kritik am Quiz {{ quiz.name|truncatechars:60 }}
|
||||
</option>
|
||||
|
||||
{% for question in questions %}
|
||||
<option value="{{ question.id }}">
|
||||
Frage {{ forloop.counter }}: {{ question.data.question|truncatechars:60 }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select name="critic" required
|
||||
class="w-full rounded-lg border-gray-300 p-2 dark:bg-gray-800 dark:text-white mb-4">
|
||||
<option value="">Was ist Ihre Kritik?</option>
|
||||
|
||||
<option value="content_error">
|
||||
inhaltlicher Fehler
|
||||
</option>
|
||||
|
||||
<option value="spelling_error">
|
||||
Rechtschreibfehler oder Grammatikfehler
|
||||
</option>
|
||||
|
||||
<option value="logic_error">
|
||||
umständliche oder unsinnige Formulierung
|
||||
</option>
|
||||
|
||||
<option value="inappropriate_error">
|
||||
unangemessen
|
||||
</option>
|
||||
<option value="not_completed_error">
|
||||
unvollständig
|
||||
</option>
|
||||
<option value="source_error">
|
||||
Quellen unvollständig
|
||||
</option>
|
||||
<option value="quiz_error">
|
||||
Quiz als "öffentliches Quiz" ungeeignet
|
||||
</option>
|
||||
|
||||
</select>
|
||||
|
||||
<button type="submit" class="inline-flex items-center
|
||||
px-4 py-2 border border-transparent shadow-sm text-sm
|
||||
font-medium rounded-md text-white bg-red-600 hover:bg-red-700
|
||||
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500">
|
||||
Abschicken
|
||||
</button>
|
||||
{% elif not user.is_authenticated %}
|
||||
|
||||
<div class="max-w-full p-4 mt-8 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
||||
<p class="text-lg font-bold text-red-800 dark:text-white dark:bg-transparent ">
|
||||
Quiz kritisieren
|
||||
</p>
|
||||
<div class="text-xs opacity-50 mb-3 dark:text-white dark:bg-transparent">
|
||||
Sie haben einen Fehler in dem Quiz entdeckt oder halten eine Frage für nicht angemessen? <br>
|
||||
Dann weisen Sie den Autor gern darauf hin.
|
||||
Sie können nur Quizze anderer User kritisieren.
|
||||
</div>
|
||||
<a class="inline-flex items-center
|
||||
px-4 py-2 border border-transparent shadow-sm text-sm
|
||||
font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700
|
||||
focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500" href="{% url 'accounts:login' %}">Anmelden</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
{% if quiz.user_id == request.user %}
|
||||
<style>.sortable-drag {
|
||||
position: fixed !important;
|
||||
z-index: 99999 !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
Sortable.create(document.getElementById('questions-list'), {
|
||||
animation: 300,
|
||||
swapThreshold: 0.5,
|
||||
|
||||
delay: 100,
|
||||
delayOnTouchOnly: true,
|
||||
touchStartThreshold: 6,
|
||||
|
||||
forceFallback: true,
|
||||
fallbackTolerance: 6,
|
||||
fallbackOnBody: true,
|
||||
|
||||
|
||||
|
||||
onStart: () => {
|
||||
if (navigator.vibrate) {
|
||||
navigator.vibrate(75);
|
||||
}
|
||||
},
|
||||
|
||||
onEnd: () => {
|
||||
|
||||
const items = Array.from(document.querySelectorAll('#questions-list .question-item'));
|
||||
|
||||
// Counter im Browser updaten
|
||||
items.forEach((el, i) => {
|
||||
const counterEl = el.querySelector('.question-counter');
|
||||
if (counterEl) counterEl.textContent = `${i+1}.`;
|
||||
});
|
||||
// Reihenfolge sammeln
|
||||
const order = Array.from(document.querySelectorAll('#questions-list .question-item'))
|
||||
.map((el, i) => ({id: el.dataset.questionId, position: i+1}));
|
||||
|
||||
|
||||
// an Django senden
|
||||
fetch("{% url 'library:reorder_questions' %}", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
},
|
||||
body: JSON.stringify({order})
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,97 +1,37 @@
|
||||
{% extends 'base.html' %} {% block content %}
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
{{ form.media }}
|
||||
<style>
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="input-group border-blue-600 border-2 rounded-xl shadow-md mt-12">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
|
||||
.select2-container {
|
||||
width: 100% !important;
|
||||
min-width: 0; /* verhindert Überlaufen bei kleinen Screens */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Das Auswahlfeld */
|
||||
.select2-selection {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Angezeigter Text im Auswahlfeld */
|
||||
.select2-selection__rendered {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.select2-selection__choice {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<input type="hidden" name="pk" value="{{ form.instance.pk }}" />
|
||||
|
||||
<div class="container mx-auto px-4 py-4">
|
||||
<div
|
||||
class="max-w-lg field-group mx-auto bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md dark:bg-transparent dark:text-white"
|
||||
>
|
||||
<form
|
||||
method="post"
|
||||
enctype="multipart/form-data"
|
||||
class="dark-form space-y-4"
|
||||
>
|
||||
{% csrf_token %} {{ form.as_p }} {% if form.instance.image %}
|
||||
<img
|
||||
src="{{ form.instance.image.image.url }}"
|
||||
style="max-width: 100px"
|
||||
alt="Bild der Frage"
|
||||
/>
|
||||
{% if form.instance.image %}
|
||||
<img src="{{ form.instance.image.image.url }}" style="max-width: 100px;" alt="Bild der Frage">
|
||||
<label for="reset_image">
|
||||
<input type="checkbox" name="reset_image" id="reset_image" />
|
||||
<input type="checkbox" name="reset_image" id="reset_image">
|
||||
Bild zurücksetzen
|
||||
</label>
|
||||
{% endif %}
|
||||
<div>
|
||||
<label class="font-bold" for="quiz_image">Bild:</label>
|
||||
<input
|
||||
class="max-w-full bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400 dark:bg-transparent"
|
||||
type="file"
|
||||
name="quiz_image"
|
||||
id="quiz_image"
|
||||
/>
|
||||
<input class="bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400" type="file" name="quiz_image" id="quiz_image">
|
||||
</div>
|
||||
|
||||
<div class="text-center text-gray-600 text-sm dislay-flex align-bottom">
|
||||
<label class=""
|
||||
>Mit der Erstellung von diesem Quiz stimmen Sie der
|
||||
<a class="text-blue-600" href="/privacy">Datenschutzerklärung </a>
|
||||
zu.</label
|
||||
>
|
||||
<label class=''>Mit der Erstellung von diesem Quiz stimmen Sie der <a class="text-blue-600" href="/privacy">Datenschutzerklärung </a> zu.</label>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center space-x-3">
|
||||
<a
|
||||
href="{% if object.quiz_id %}{% url 'library:detail_quiz' object.quiz_id.id %}{% else %}{% url 'library:overview_quiz' %}{% endif %}"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
<a href="{% if object.quiz_id %}{% url 'library:detail_quiz' object.quiz_id.id %}{% else %}{% url 'library:overview_quiz' %}{% endif %}"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Abbrechen
|
||||
</a>
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
<button type="submit"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -12,14 +12,14 @@
|
||||
|
||||
{% block head %}
|
||||
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
|
||||
<button id="filterButton" class="dark:text-white dark:hover:bg-[#2a2f3a] p-2 hover:bg-gray-100 rounded-lg transition-colors duration-200 flex items-center gap-2 text-gray-700">
|
||||
<button id="filterButton" class="p-2 hover:bg-gray-100 rounded-lg transition-colors duration-200 flex items-center gap-2 text-gray-700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
|
||||
</svg>
|
||||
<span>Filter</span>
|
||||
</button>
|
||||
<div class="flex gap-2">
|
||||
<a href="{% url link %}" class="dark:bg-transparent dark:text-white dark:hover:bg-[#2a2f3a] inline-flex items-center px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors duration-200">
|
||||
<a href="{% url link %}" class="inline-flex items-center px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors duration-200">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
@@ -35,13 +35,13 @@
|
||||
</div>
|
||||
|
||||
<div id="filterPanel" class="container mx-auto px-4 {% if request.GET.filter == '1' %}block{% else %}hidden{% endif %}">
|
||||
<div class="bg-white rounded-xl shadow-lg p-6 border border-gray-200 dark:bg-transparent dark:text-white ">
|
||||
<div class="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
|
||||
<form action="{% url link %}" method="get" class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<input type="hidden" name="filter" id="filter-input">
|
||||
<div class="space-y-2">
|
||||
<div class="flex gap-2">
|
||||
<input type="checkbox" name="use_min_amount_questions" id="use_min_amount_questions" {% if form.use_min_amount_questions.value %}checked{% endif %}>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-white">minimale Anzahl an Fragen</label>
|
||||
<label class="block text-sm font-medium text-gray-700">minimale Anzahl an Fragen</label>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
@@ -51,22 +51,22 @@
|
||||
max="100"
|
||||
step="1"
|
||||
value="{{ form.min_amount_questions.value|default_if_none:0 }}"
|
||||
class="w-full accent-blue-600 dark:text-white"
|
||||
class="w-full accent-blue-600"
|
||||
oninput="document.getElementById('min_amount_value').textContent = this.value + ' Frage(n)'"
|
||||
{% if not form.use_min_amount_questions.value %}disabled{% endif %}
|
||||
>
|
||||
|
||||
<span id="min_amount_value" class="text-sm font-semibold text-gray-800 dark:text-white">
|
||||
<span id="min_amount_value" class="text-sm font-semibold text-gray-800">
|
||||
{{ form.min_amount_questions.value|default_if_none:0 }} Frage(n)
|
||||
</span>
|
||||
<br>
|
||||
<span class="text-xs text-gray-600 dark:text-white"> Quizze anderer User werden erst ab min. 1 Frage angezeigt.</span>
|
||||
<span class="text-xs text-gray-600"> Quizze anderer User werden erst ab min. 1 Frage angezeigt.</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="flex gap-2">
|
||||
<input type="checkbox" name="use_max_amount_questions" id="use_max_amount_questions" {% if form.use_max_amount_questions.value %}checked{% endif %}>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-white">maximale Anzahl an Fragen</label>
|
||||
<label class="block text-sm font-medium text-gray-700">maximale Anzahl an Fragen</label>
|
||||
</div>
|
||||
<input
|
||||
type="range"
|
||||
@@ -81,22 +81,22 @@
|
||||
{% if not form.use_max_amount_questions.value %}disabled{% endif %}
|
||||
>
|
||||
|
||||
<span id="max_amount_value" class="text-sm font-semibold text-gray-800 dark:text-white">
|
||||
<span id="max_amount_value" class="text-sm font-semibold text-gray-800">
|
||||
{{ form.max_amount_questions.value|default_if_none:100 }} Frage(n)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-white">veröffentlicht von User</label>
|
||||
<input type="text" name="user" class="border-2 border-gray-300 rounded-lg p-2 w-full dark:text-white" id="id_user" value="{{ form.user.value|default_if_none:'' }}">
|
||||
<label class="block text-sm font-medium text-gray-700">veröffentlicht von User</label>
|
||||
<input type="text" name="user" class="border-2 border-gray-300 rounded-lg p-2 w-full" id="id_user" value="{{ form.user.value|default_if_none:'' }}">
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-white">Kategorie</label>
|
||||
<label class="block text-sm font-medium text-gray-700">Kategorie</label>
|
||||
{{ form.categories }}
|
||||
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-white">Suche</label>
|
||||
<label class="block text-sm font-medium text-gray-700">Suche</label>
|
||||
{{ form.search }}
|
||||
|
||||
</div>
|
||||
|
||||
672
django/templates/library/overview_quiz(old).html
Normal file
672
django/templates/library/overview_quiz(old).html
Normal file
@@ -0,0 +1,672 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
<!-- Die gesamte Seite wird eigentlich nicht mehr genutzt. WICHTIG: ggf. kann Code aber hiervon noch genutzt werden. -->
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
|
||||
|
||||
.custom-outline {
|
||||
-webkit-text-stroke: 0.2px rgb(0, 0, 0);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
|
||||
<button id="filterButton" class="p-2 hover:bg-gray-100 rounded-lg transition-colors duration-200 flex items-center gap-2 text-gray-700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
|
||||
</svg>
|
||||
<span>Filter</span>
|
||||
</button>
|
||||
<div class="flex gap-2">
|
||||
<a href="{% url 'library:overview_quiz' %}" class="inline-flex items-center px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors duration-200">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
Zurücksetzen
|
||||
</a>
|
||||
<a href="{% url 'library:new_quiz' %}" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-200">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Neues Quiz
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="filterPanel" class="container mx-auto px-4">
|
||||
<div class="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
|
||||
<form action="{% url 'library:overview_quiz' %}" method="get" class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<input type="hidden" name="filter" value="1">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700">minimale Anzahl an Fragen</label>
|
||||
<input type="number" name="min_amount_questions" class="border-2 border-gray-300 rounded-lg p-2 w-full" min="1" id="id_min_amount_questions" value="{{ form.min_amount_questions.value|default_if_none:'' }}">
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700">maximale Anzahl an Fragen</label>
|
||||
<input type="number" name="max_amount_questions" class="border-2 border-gray-300 rounded-lg p-2 w-full" min="1" id="id_max_amount_questions" value="{{ form.max_amount_questions.value|default_if_none:'' }}">
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-medium text-gray-700">veröffentlicht von User</label>
|
||||
<input type="text" name="user" class="border-2 border-gray-300 rounded-lg p-2 w-full" id="id_user" value="{{ form.user.value|default_if_none:'' }}">
|
||||
</div>
|
||||
<div class="md:col-span-3 flex justify-end">
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors duration-200">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"></path>
|
||||
</svg>
|
||||
Filtern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 id="my-quizzes" class="text-2xl font-bold text-gray-900">Meine Quizze</h2>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 auto-rows-fr">
|
||||
{% for quiz in my_quizzes %}
|
||||
<article class="relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col">
|
||||
<!-- Image Section (Top) -->
|
||||
<div class="h-48 w-full relative flex-shrink-0">
|
||||
{% if quiz.image %}
|
||||
<div class="relative w-full h-full inset-0 bg-cover bg-center">
|
||||
<!-- Hintergrundbild -->
|
||||
<div class="absolute inset-0 bg-cover bg-center"
|
||||
style="background-image: url({{ quiz.image.image.url }});">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% else %}
|
||||
<div class="h-full bg-gradient-to-r from-blue-500 to-blue-600 flex items-center justify-center font-black text-white text-4xl px-4 overflow-auto break-words hyphens-auto ">{{ quiz.name }}</div>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
{% if quiz.id not in favorite_quiz_ids %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
|
||||
<a href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-black size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
<a href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-yellow-400 size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Content Section (Bottom) -->
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3">
|
||||
<!-- Title -->
|
||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}" class="hover:text-blue-600 transition-colors">{{ quiz.name }}</a>
|
||||
</h2>
|
||||
<!-- Description -->
|
||||
<p class="text-sm text-gray-700 break-words mb-3">{{ quiz.description }}</p>
|
||||
|
||||
<!-- Quiz Info Grid -->
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||
<!-- Difficulty -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.difficulty }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Questions Count -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.questions.count }} Fragen</span>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.status }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Rating -->
|
||||
<div class="flex items-center gap-1">
|
||||
{% if quiz.rating_count > 0 %}
|
||||
{% for i in '12345'|make_list %}
|
||||
{% if forloop.counter <= quiz.average_rating|floatformat:0|add:0 %}
|
||||
<span class="text-yellow-500 text-base">★</span>
|
||||
{% else %}
|
||||
<span class="text-gray-300 text-base">★</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<span class="text-gray-600 text-sm">({{ quiz.rating_count }})</span>
|
||||
{% else %}
|
||||
<span class="text-gray-600 text-sm">Noch keine Bewertungen</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Authors -->
|
||||
{% if quiz.authors.all %}
|
||||
<div class="flex items-center gap-2 text-sm mt-2">
|
||||
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-600">
|
||||
{% for author in quiz.authors.all %}
|
||||
{{ author.username }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-between items-center gap-2 mt-3 border-t pt-3">
|
||||
{% if quiz.questions.count != 0 %}
|
||||
<a href="{% url 'play:create_game' quiz.id %}"
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors duration-200 flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Spielen
|
||||
</a>
|
||||
{% else %}
|
||||
Fürs Spielen ist min. 1 Frage nötig!
|
||||
{% endif %}
|
||||
<div class="flex gap-1">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</a>
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:edit_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="{% url 'library:delete_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Pagination für Meine Quizze -->
|
||||
{% if my_quizzes.paginator.num_pages > 1 %}
|
||||
<div class="flex justify-center space-x-2 mt-6 mb-8">
|
||||
{% if my_quizzes.has_previous %}
|
||||
<a href="?my_page={{ my_quizzes.previous_page_number }}{% if request.GET.all_page %}&all_page={{ request.GET.all_page }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="px-3 py-1 text-gray-600">Seite {{ my_quizzes.number }} von {{ my_quizzes.paginator.num_pages }}</span>
|
||||
|
||||
{% if my_quizzes.has_next %}
|
||||
<a href="?my_page={{ my_quizzes.next_page_number }}{% if request.GET.all_page %}&all_page={{ request.GET.all_page }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 id="my-quizzes" class="text-2xl font-bold text-gray-900">Meine Favoriten</h2>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 auto-rows-fr">
|
||||
{% for quiz in favorite_quizzes %}
|
||||
<article class="relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col">
|
||||
<!-- Image Section (Top) -->
|
||||
<div class="h-48 w-full relative flex-shrink-0">
|
||||
{% if quiz.image %}
|
||||
<div class="relative w-full h-full inset-0 bg-cover bg-center">
|
||||
<!-- Hintergrundbild -->
|
||||
<div class="absolute inset-0 bg-cover bg-center"
|
||||
style="background-image: url({{ quiz.image.image.url }});">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% else %}
|
||||
<div class="h-full bg-gradient-to-r from-blue-500 to-blue-600 flex items-center justify-center font-black text-white text-4xl px-4 overflow-auto break-words hyphens-auto ">{{ quiz.name }}</div>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
{% if quiz.favorite == False %}
|
||||
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
|
||||
<a href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-black size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
<a href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-yellow-400 size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Content Section (Bottom) -->
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3">
|
||||
<!-- Title -->
|
||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}" class="hover:text-blue-600 transition-colors">{{ quiz.name }}</a>
|
||||
</h2>
|
||||
<!-- Description -->
|
||||
<p class="text-sm text-gray-700 break-words mb-3">{{ quiz.description }}</p>
|
||||
|
||||
<!-- Quiz Info Grid -->
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||
<!-- Difficulty -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.difficulty }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Questions Count -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.questions.count }} Fragen</span>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.status }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Rating -->
|
||||
<div class="flex items-center gap-1">
|
||||
{% if quiz.rating_count > 0 %}
|
||||
{% for i in '12345'|make_list %}
|
||||
{% if forloop.counter <= quiz.average_rating|floatformat:0|add:0 %}
|
||||
<span class="text-yellow-500 text-base">★</span>
|
||||
{% else %}
|
||||
<span class="text-gray-300 text-base">★</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<span class="text-gray-600 text-sm">({{ quiz.rating_count }})</span>
|
||||
{% else %}
|
||||
<span class="text-gray-600 text-sm">Noch keine Bewertungen</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Authors -->
|
||||
{% if quiz.authors.all %}
|
||||
<div class="flex items-center gap-2 text-sm mt-2">
|
||||
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-600">
|
||||
{% for author in quiz.authors.all %}
|
||||
{{ author.username }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-between items-center gap-2 mt-3 border-t pt-3">
|
||||
{% if quiz.questions.count != 0 %}
|
||||
<a href="{% url 'play:create_game' quiz.id %}"
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors duration-200 flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Spielen
|
||||
</a>
|
||||
{% else %}
|
||||
Fürs Spielen ist min. 1 Frage nötig!
|
||||
{% endif %}
|
||||
|
||||
<div class="flex gap-1">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</a>
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:edit_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="{% url 'library:delete_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Pagination für Meine Quizze -->
|
||||
{% if favorite_quizzes.paginator.num_pages > 1 %}
|
||||
<div class="flex justify-center space-x-2 mt-6 mb-8">
|
||||
{% if favorite_quizzes.has_previous %}
|
||||
<a href="?my_page={{ favorite.previous_page_number }}{% if request.GET.all_page %}&all_page={{ request.GET.all_page }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="px-3 py-1 text-gray-600">Seite {{ favorite_quizzes.number }} von {{ favorite_quizzes.paginator.num_pages }}</span>
|
||||
|
||||
{% if favorite_quizzes.has_next %}
|
||||
<a href="?favorite_page={{ favorite_quizzes.next_page_number }}{% if request.GET.all_page %}&all_page={{ request.GET.all_page }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Alle -->
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 id="all-quizzes" class="text-2xl font-bold text-gray-900">Alle</h2>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if all_quizzes %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 auto-rows-fr">
|
||||
{% for quiz in all_quizzes %}
|
||||
<article class="relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col">
|
||||
<!-- Image Section (Top) -->
|
||||
<div class="h-48 w-full relative flex-shrink-0">
|
||||
{% if quiz.image %}
|
||||
<div class="relative w-full h-full inset-0 bg-cover bg-center">
|
||||
<!-- Hintergrundbild -->
|
||||
<div class="absolute inset-0 bg-cover bg-center"
|
||||
style="background-image: url({{ quiz.image.image.url }});">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% else %}
|
||||
<div class="h-full bg-gradient-to-r from-blue-500 to-blue-600 flex items-center justify-center font-black text-white text-4xl px-4 overflow-auto break-words hyphens-auto ">{{ quiz.name }}</div>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
{% if quiz.id not in favorite_quiz_ids %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
|
||||
<a href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-black size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
<a href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-yellow-400 size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Content Section (Bottom) -->
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3">
|
||||
<!-- Title -->
|
||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}" class="hover:text-blue-600 transition-colors">{{ quiz.name }}</a>
|
||||
</h2>
|
||||
<!-- Description -->
|
||||
<p class="text-sm text-gray-700 break-words mb-3">{{ quiz.description }}</p>
|
||||
|
||||
<!-- Quiz Info Grid -->
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||
<!-- Difficulty -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.difficulty }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Questions Count -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.questions.count }} Fragen</span>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="flex items-center gap-2">
|
||||
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800">{{ quiz.status }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Rating -->
|
||||
<div class="flex items-center gap-1">
|
||||
{% if quiz.rating_count > 0 %}
|
||||
{% for i in '12345'|make_list %}
|
||||
{% if forloop.counter <= quiz.average_rating|floatformat:0|add:0 %}
|
||||
<span class="text-yellow-500 text-base">★</span>
|
||||
{% else %}
|
||||
<span class="text-gray-300 text-base">★</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<span class="text-gray-600 text-sm">({{ quiz.rating_count }})</span>
|
||||
{% else %}
|
||||
<span class="text-gray-600 text-sm">Noch keine Bewertungen</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Authors -->
|
||||
{% if quiz.authors.all %}
|
||||
<div class="flex items-center gap-2 text-sm mt-2">
|
||||
<svg class="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-600">
|
||||
{% for author in quiz.authors.all %}
|
||||
{{ author.username }}{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-between items-center gap-2 mt-3 border-t pt-3">
|
||||
{% if quiz.questions.count != 0 %}
|
||||
<a href="{% url 'play:create_game' quiz.id %}"
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors duration-200 flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Spielen
|
||||
</a>
|
||||
{% else %}
|
||||
Fürs Spielen ist min. 1 Frage nötig!
|
||||
{% endif %}
|
||||
<div class="flex gap-1">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination für andere Quiz -->
|
||||
{% if all_quizzes.paginator.num_pages > 1 %}
|
||||
<div class="flex justify-center space-x-2 mt-6 mb-8">
|
||||
{% if all_quizzes.has_previous %}
|
||||
<a href="?all_page={{ all_quizzes.previous_page_number }}{% if request.GET.my_page %}&my_page={{ request.GET.my_page }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="px-3 py-1 text-gray-600">Seite {{ all_quizzes.number }} von {{ all_quizzes.paginator.num_pages }}</span>
|
||||
|
||||
{% if all_quizzes.has_next %}
|
||||
<a href="?all_page={{ all_quizzes.next_page_number }}{% if request.GET.my_page %}&my_page={{ request.GET.my_page }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-4">
|
||||
<p class="text-gray-600 text-center">Keine öffentlichen Quiz gefunden.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('filter') === '1') {
|
||||
document.getElementById('filterPanel').classList.add('show');
|
||||
}
|
||||
|
||||
document.getElementById('filterButton').addEventListener('click', function() {
|
||||
document.getElementById('filterPanel').classList.toggle('show');
|
||||
|
||||
// URL beim Öffnen aktualisieren
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (document.getElementById('filterPanel').classList.contains('show')) {
|
||||
params.set('filter', '1');
|
||||
} else {
|
||||
params.delete('filter');
|
||||
}
|
||||
history.replaceState(null, '', `${location.pathname}?${params}`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
if (window.location.pathname.includes("/library/")) {
|
||||
window.addEventListener("beforeunload", function () {
|
||||
localStorage.setItem("scroll-position", window.scrollY);
|
||||
});
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
const scrollPos = localStorage.getItem("scroll-position");
|
||||
if (scrollPos !== null) {
|
||||
window.scrollTo(0, parseInt(scrollPos));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock content %}
|
||||
@@ -18,28 +18,20 @@
|
||||
{% endblock %}
|
||||
|
||||
<!-- Alle -->
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6 dark:text-white">
|
||||
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
{% if user.is_authenticated %}
|
||||
|
||||
<div class="flex justify-end text-sm mb-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 text-blue-600">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
|
||||
</svg>
|
||||
<div class="gap-2 mx-2">
|
||||
{% if user.is_authenticated %}
|
||||
Anzahl aller Ergebnisse:
|
||||
<span class="font-bold"> {{ total_all_found }}</span>
|
||||
</div>
|
||||
(verschiedene:
|
||||
<span class="font-bold ml-1"> {{ total_all }}</span>)
|
||||
{% else %}
|
||||
Anzahl aller Ergebnisse:
|
||||
<span class="font-bold"> {{ total_all_found }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<span class="font-bold ml-1"> {{ total_all }}</span>) </div>
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<div class="flex flex-wrap justify-end gap-4 font-bold text-blue-600 whitespace-nowrap text-sm mb-2 md:mb-0">
|
||||
<p class="font-black underline">Alle ({{ total_all }})</p>
|
||||
<a href="{{ url_my }}?{{ request.GET.urlencode }}">Meine Quizze ({{ total_my }})</a>
|
||||
@@ -48,7 +40,7 @@
|
||||
|
||||
{% endif %}
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 id="all-quizzes" class="text-2xl font-bold text-gray-900 dark:text-white">Alle</h2>
|
||||
<h2 id="all-quizzes" class="text-2xl font-bold text-gray-900">Alle</h2>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
|
||||
@@ -60,7 +52,7 @@
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 auto-rows-fr">
|
||||
{% for quiz in all_quizzes %}
|
||||
<article class="{% if quiz in critisized_quizzes %} border-4 border-red-600 {% endif %} relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden flex flex-col">
|
||||
<article class="relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col">
|
||||
<!-- Image Section (Top) -->
|
||||
<div class="h-48 w-full relative flex-shrink-0">
|
||||
{% if quiz.image %}
|
||||
@@ -78,17 +70,17 @@
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
{% if quiz.id not in favorite_quiz_ids %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg dark:bg-[#2a2f3a]">
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
|
||||
<a class="favorite-link" href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-black size-6 dark:text-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-black size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg dark:bg-[#2a2f3a]">
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
<a class="favorite-link" href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-yellow-400 size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
@@ -97,28 +89,16 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="dark:bg-[#2a2f3a] max-w-6/10 h-max-12 truncate absolute top-4 left-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg ">
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="rounded-full w-4 h-4 flex-shrink-0" style="background-color: {{ quiz.category.color }};"></span>
|
||||
<a class=" text-gray-800 truncate text-sm dark:text-white" title="{{ quiz.category }}" href="{% url 'library:overview_quiz' %}?filter=1&categories={{ quiz.category.id }}">{{ quiz.category }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Content Section (Bottom) -->
|
||||
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3 dark:bg-[#2a2f3a] dark:text-white" >
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3">
|
||||
<!-- Title -->
|
||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}" class="hover:text-blue-600 transition-colors dark:text-white">{{ quiz.name }}</a>
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}" class="hover:text-blue-600 transition-colors">{{ quiz.name }}</a>
|
||||
</h2>
|
||||
<!-- Description -->
|
||||
<p class="text-sm text-gray-700 break-words mb-3 dark:text-white">{{ quiz.description }}</p>
|
||||
<p class="text-sm text-gray-700 break-words mb-3">{{ quiz.description }}</p>
|
||||
|
||||
<!-- Quiz Info Grid -->
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||
@@ -127,7 +107,7 @@
|
||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-800 dark:text-white">{{ quiz.difficulty }}</span>
|
||||
<span class="text-gray-800">{{ quiz.difficulty }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Questions Count -->
|
||||
@@ -135,7 +115,7 @@
|
||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800 dark:text-white">{{ quiz.questions.count }} Fragen</span>
|
||||
<span class="text-gray-800">{{ quiz.questions.count }} Fragen</span>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
@@ -143,14 +123,11 @@
|
||||
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800 dark:text-white">{{ quiz.status }}</span>
|
||||
<span class="text-gray-800">{{ quiz.status }}</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- Rating -->
|
||||
<div class="flex items-center gap-1">
|
||||
{% if quiz.rating_count > 0 %}
|
||||
@@ -161,9 +138,9 @@
|
||||
<span class="text-gray-300 text-base">★</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<span class="text-gray-600 text-sm dark:text-white">({{ quiz.rating_count }})</span>
|
||||
<span class="text-gray-600 text-sm">({{ quiz.rating_count }})</span>
|
||||
{% else %}
|
||||
<span class="text-gray-600 text-sm dark:text-white">Noch keine Bewertungen</span>
|
||||
<span class="text-gray-600 text-sm">Noch keine Bewertungen</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,13 +165,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="text-green-400 size-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
<span class="text-gray-800 text-sm dark:text-white"><span class="font-medium dark:text-white">{{ quiz.published_at |naturaltime }}</span> <span class="text-sm">erst. veröffentlicht</span> </span>
|
||||
<span class="text-gray-800 text-sm "><span class="font-medium">{{ quiz.published_at |naturaltime }}</span> <span class="text-sm">erst. veröffentlicht</span> </span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-between items-center gap-2 mt-3 border-t pt-3" >
|
||||
<div class="flex justify-between items-center gap-2 mt-3 border-t pt-3">
|
||||
{% if quiz.questions.count != 0 %}
|
||||
<a href="{% url 'play:create_game' quiz.id %}"
|
||||
class="px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-colors duration-200 flex items-center gap-2">
|
||||
@@ -209,26 +186,12 @@
|
||||
{% endif %}
|
||||
<div class="flex gap-1">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200 dark:bg-transparent dark:text-white dark:hover:bg-gray-700 ">
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
</svg>
|
||||
</a>
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:edit_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200 dark:bg-transparent dark:text-white dark:hover:bg-gray-700">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="{% url 'library:delete_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors duration-200 dark:bg-transparent dark:text-white dark:hover:bg-gray-700">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -248,7 +211,7 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="px-3 py-1 text-gray-600 dark:text-white">Seite {{ all_quizzes.number }} von {{ all_quizzes.paginator.num_pages }}</span>
|
||||
<span class="px-3 py-1 text-gray-600">Seite {{ all_quizzes.number }} von {{ all_quizzes.paginator.num_pages }}</span>
|
||||
|
||||
{% if all_quizzes.has_next %}
|
||||
<a href="?all_page={{ all_quizzes.next_page_number }}&{{ querystring_all }}"
|
||||
@@ -263,7 +226,7 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-4">
|
||||
<p class="text-gray-600 text-center dark:text-white">Keine Quiz gefunden.</p>
|
||||
<p class="text-gray-600 text-center">Keine öffentlichen Quiz gefunden.</p>
|
||||
</div>
|
||||
{% endif %}<script>
|
||||
|
||||
|
||||
@@ -22,14 +22,14 @@
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6 dark:text-white">
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
|
||||
|
||||
<div class="flex justify-end text-sm mb-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 text-blue-600">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
|
||||
</svg>
|
||||
<div class="gap-2 mx-2 ">
|
||||
<div class="gap-2 mx-2">
|
||||
Anzahl aller Ergebnisse:
|
||||
<span class="font-bold"> {{ total_all_found }}</span>
|
||||
</div>
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 id="my-quizzes" class="text-2xl font-bold text-gray-900 dark:text-white">Meine Favoriten</h2>
|
||||
<h2 id="my-quizzes" class="text-2xl font-bold text-gray-900">Meine Favoriten</h2>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,9 +54,9 @@
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 auto-rows-fr">
|
||||
{% for quiz in favorite_quizzes %}
|
||||
<article class="{% if quiz in critisized_quizzes %} border-4 border-red-600 {% endif %} relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col ">
|
||||
<article class="relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col">
|
||||
<!-- Image Section (Top) -->
|
||||
<div class="h-48 w-full relative flex-shrink-0 ">
|
||||
<div class="h-48 w-full relative flex-shrink-0">
|
||||
{% if quiz.image %}
|
||||
<div class="relative w-full h-full inset-0 bg-cover bg-center">
|
||||
<!-- Hintergrundbild -->
|
||||
@@ -72,17 +72,17 @@
|
||||
{% if user.is_authenticated %}
|
||||
{% if quiz.favorite == False %}
|
||||
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg dark:bg-[#2a2f3a]">
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
|
||||
<a class="favorite-link" href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-black size-6 dark:text-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-black size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg dark:bg-[#2a2f3a]">
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
<a class="favorite-link" href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-yellow-400 size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
@@ -91,30 +91,18 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="dark:bg-[#2a2f3a] max-w-6/10 h-max-12 truncate absolute top-4 left-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg ">
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="rounded-full w-4 h-4 flex-shrink-0" style="background-color: {{ quiz.category.color }};"></span>
|
||||
|
||||
<a class=" text-gray-800 truncate text-sm dark:text-white" title="{{ quiz.category }}" href="{% url 'library:overview_quiz' %}?filter=1&categories={{ quiz.category.id }}">{{ quiz.category }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Content Section (Bottom) -->
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3 dark:bg-[#2a2f3a] dark:text-white">
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3">
|
||||
<!-- Title -->
|
||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}" class="hover:text-blue-600 transition-colors dark:text-white">{{ quiz.name }}</a>
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}" class="hover:text-blue-600 transition-colors">{{ quiz.name }}</a>
|
||||
</h2>
|
||||
<!-- Description -->
|
||||
<p class="text-sm text-gray-700 break-words mb-3 dark:text-white">{{ quiz.description }}</p>
|
||||
<p class="text-sm text-gray-700 break-words mb-3">{{ quiz.description }}</p>
|
||||
|
||||
<!-- Quiz Info Grid -->
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||
@@ -123,7 +111,7 @@
|
||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-800 dark:text-white">{{ quiz.difficulty }}</span>
|
||||
<span class="text-gray-800">{{ quiz.difficulty }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Questions Count -->
|
||||
@@ -131,7 +119,7 @@
|
||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800 dark:text-white">{{ quiz.questions.count }} Fragen</span>
|
||||
<span class="text-gray-800">{{ quiz.questions.count }} Fragen</span>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
@@ -139,7 +127,7 @@
|
||||
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800 dark:text-white">{{ quiz.status }}</span>
|
||||
<span class="text-gray-800">{{ quiz.status }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Rating -->
|
||||
@@ -152,9 +140,9 @@
|
||||
<span class="text-gray-300 text-base">★</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<span class="text-gray-600 text-sm dark:text-white">({{ quiz.rating_count }})</span>
|
||||
<span class="text-gray-600 text-sm">({{ quiz.rating_count }})</span>
|
||||
{% else %}
|
||||
<span class="text-gray-600 text-sm dark:text-white">Noch keine Bewertungen</span>
|
||||
<span class="text-gray-600 text-sm">Noch keine Bewertungen</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -181,7 +169,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="text-green-400 size-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
<span class="text-gray-800 text-sm dark:text-white "><span class="font-medium dark:text-white">{{ quiz.published_at |naturaltime }}</span> <span class="text-sm">erst. veröffentlicht</span> </span>
|
||||
<span class="text-gray-800 text-sm "><span class="font-medium">{{ quiz.published_at |naturaltime }}</span> <span class="text-sm">erst. veröffentlicht</span> </span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -202,7 +190,7 @@
|
||||
|
||||
<div class="flex gap-1">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200 dark:bg-transparent dark:text-white dark:hover:bg-gray-700">
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
@@ -210,13 +198,13 @@
|
||||
</a>
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:edit_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200 dark:bg-transparent dark:text-white dark:hover:bg-gray-700">
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="{% url 'library:delete_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors duration-200 dark:bg-transparent dark:text-white dark:hover:bg-gray-700">
|
||||
class="p-1.5 bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
@@ -230,9 +218,9 @@
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Pagination für favorite Quizze -->
|
||||
<!-- Pagination für Meine Quizze -->
|
||||
{% if favorite_quizzes.paginator.num_pages > 1 %}
|
||||
<div class="flex justify-center space-x-2 mt-6 mb-8 ">
|
||||
<div class="flex justify-center space-x-2 mt-6 mb-8">
|
||||
{% if favorite_quizzes.has_previous %}
|
||||
<a href="?favorite_page={{ favorite.previous_page_number }}&{{ querystring_favorite }}"
|
||||
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
@@ -242,7 +230,7 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="dark:text-white px-3 py-1 text-gray-600">Seite {{ favorite_quizzes.number }} von {{ favorite_quizzes.paginator.num_pages }}</span>
|
||||
<span class="px-3 py-1 text-gray-600">Seite {{ favorite_quizzes.number }} von {{ favorite_quizzes.paginator.num_pages }}</span>
|
||||
|
||||
{% if favorite_quizzes.has_next %}
|
||||
<a href="?favorite_page={{ favorite_quizzes.next_page_number }}&{{ querystring_favorite }}"
|
||||
@@ -257,7 +245,7 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-4">
|
||||
<p class="text-gray-600 text-center dark:text-white">Keine favorisierten Quiz gefunden.</p>
|
||||
<p class="text-gray-600 text-center">Keine favorisierten Quiz gefunden.</p>
|
||||
</div>
|
||||
{% endif %}<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6 dark:text-white">
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||
|
||||
<div class="flex justify-end text-sm mb-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 text-blue-600">
|
||||
@@ -41,7 +41,7 @@
|
||||
<a href="{{ url_favorite }}?{{ request.GET.urlencode }}">Meine Favoriten ({{ total_favorite }})</a>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 id="my-quizzes" class="text-2xl font-bold text-gray-900 dark:text-white">Meine Quizze</h2>
|
||||
<h2 id="my-quizzes" class="text-2xl font-bold text-gray-900">Meine Quizze</h2>
|
||||
<div class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -52,7 +52,7 @@
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 auto-rows-fr">
|
||||
{% for quiz in my_quizzes %}
|
||||
<article class="{% if quiz in critisized_quizzes %} border-4 border-red-600 {% endif %} relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col">
|
||||
<article class="relative min-h-[20rem] rounded-xl shadow-lg overflow-hidden bg-white flex flex-col">
|
||||
<!-- Image Section (Top) -->
|
||||
<div class="h-48 w-full relative flex-shrink-0">
|
||||
{% if quiz.image %}
|
||||
@@ -69,19 +69,17 @@
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
{% if quiz.id not in favorite_quiz_ids %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg dark:bg-[#2a2f3a]">
|
||||
|
||||
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
|
||||
<a class="favorite-link" href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-black size-6 dark:text-white">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-black size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg dark:bg-[#2a2f3a]">
|
||||
<div class="absolute top-4 right-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
<a class="favorite-link" href="{% url 'library:favorite_quiz' quiz.pk %}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="text-yellow-400 size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 0 0-.182.557l1.285 5.385a.562.562 0 0 1-.84.61l-4.725-2.885a.562.562 0 0 0-.586 0L6.982 20.54a.562.562 0 0 1-.84-.61l1.285-5.386a.562.562 0 0 0-.182-.557l-4.204-3.602a.562.562 0 0 1 .321-.988l5.518-.442a.563.563 0 0 0 .475-.345L11.48 3.5Z" />
|
||||
@@ -90,28 +88,18 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="dark:bg-[#2a2f3a] max-w-6/10 h-max-12 truncate absolute top-4 left-4 bg-white bg-opacity-60 text-white px-4 py-2 rounded-lg shadow-lg ">
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="rounded-full w-4 h-4 flex-shrink-0" style="background-color: {{ quiz.category.color }};"></span>
|
||||
<a class=" text-gray-800 truncate text-sm dark:text-white" title="{{ quiz.category }}" href="{% url 'library:overview_quiz' %}?filter=1&categories={{ quiz.category.id }}">{{ quiz.category }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Content Section (Bottom) -->
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3 dark:bg-[#2a2f3a] dark:text-white">
|
||||
<div class="flex-grow bg-white p-4 flex flex-col gap-3">
|
||||
<!-- Title -->
|
||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}" class="hover:text-blue-600 transition-colors dark:text-white">{{ quiz.name }}</a>
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}" class="hover:text-blue-600 transition-colors">{{ quiz.name }}</a>
|
||||
</h2>
|
||||
<!-- Description -->
|
||||
<p class="text-sm text-gray-700 break-words mb-3 dark:text-white">{{ quiz.description }}</p>
|
||||
<p class="text-sm text-gray-700 break-words mb-3">{{ quiz.description }}</p>
|
||||
|
||||
<!-- Quiz Info Grid -->
|
||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||
@@ -120,7 +108,7 @@
|
||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<span class="text-gray-800 dark:text-white">{{ quiz.difficulty }}</span>
|
||||
<span class="text-gray-800">{{ quiz.difficulty }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Questions Count -->
|
||||
@@ -128,7 +116,7 @@
|
||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800 dark:text-white">{{ quiz.questions.count }} Fragen</span>
|
||||
<span class="text-gray-800">{{ quiz.questions.count }} Fragen</span>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
@@ -136,7 +124,7 @@
|
||||
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span class="text-gray-800 dark:text-white">{{ quiz.status }}</span>
|
||||
<span class="text-gray-800">{{ quiz.status }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Rating -->
|
||||
@@ -149,9 +137,9 @@
|
||||
<span class="text-gray-300 text-base">★</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<span class="text-gray-600 text-sm dark:text-white">({{ quiz.rating_count }})</span>
|
||||
<span class="text-gray-600 text-sm">({{ quiz.rating_count }})</span>
|
||||
{% else %}
|
||||
<span class="text-gray-600 text-sm dark:text-white">Noch keine Bewertungen</span>
|
||||
<span class="text-gray-600 text-sm">Noch keine Bewertungen</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -180,7 +168,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="text-green-400 size-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
<span class="text-gray-800 text-sm dark:text-white"><span class="font-medium dark:text-white">{{ quiz.published_at |naturaltime }}</span> <span class="text-sm">erst. veröffentlicht</span> </span>
|
||||
<span class="text-gray-800 text-sm "><span class="font-medium">{{ quiz.published_at |naturaltime }}</span> <span class="text-sm">erst. veröffentlicht</span> </span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -200,7 +188,7 @@
|
||||
{% endif %}
|
||||
<div class="flex gap-1">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200 dark:bg-transparent dark:text-white dark:hover:bg-gray-700">
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||
@@ -208,13 +196,13 @@
|
||||
</a>
|
||||
{% if quiz.user_id == request.user %}
|
||||
<a href="{% url 'library:edit_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200 dark:bg-transparent dark:text-white dark:hover:bg-gray-700">
|
||||
class="p-1.5 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="{% url 'library:delete_quiz' quiz.id %}"
|
||||
class="p-1.5 bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors duration-200 dark:bg-transparent dark:text-white dark:hover:bg-gray-700">
|
||||
class="p-1.5 bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition-colors duration-200">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
@@ -240,7 +228,7 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="px-3 py-1 text-gray-600 dark:text-white">Seite {{ my_quizzes.number }} von {{ my_quizzes.paginator.num_pages }}</span>
|
||||
<span class="px-3 py-1 text-gray-600">Seite {{ my_quizzes.number }} von {{ my_quizzes.paginator.num_pages }}</span>
|
||||
|
||||
{% if my_quizzes.has_next %}
|
||||
<a href="?my_page={{ my_quizzes.next_page_number }}&{{ querystring_my }}"
|
||||
@@ -255,7 +243,7 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-4">
|
||||
<p class="text-gray-600 text-center dark:text-white">Keine eigenen Quiz gefunden.</p>
|
||||
<p class="text-gray-600 text-center">Keine eigenen Quiz gefunden.</p>
|
||||
</div>
|
||||
{% endif %}<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold dark:text-white">{% if question %}Frage bearbeiten{% else %}Neue Schätzfrage{% endif %}</h1>
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="bg-gray-100 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-200 transition-colors">
|
||||
Zurück zum Quiz
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md dark:bg-transparent dark:text-white">
|
||||
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Question Text -->
|
||||
<div>
|
||||
<label for="question" class="block text-sm font-medium text-gray-700 dark:text-white">Frage</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="question" id="question" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
required>{{ question.data.question }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if image %}
|
||||
<img src="{{ image.image.url }}" style="max-width: 100px;" alt="Bild der Frage">
|
||||
<label for="reset_image">
|
||||
<input type="checkbox" name="reset_ques_image" id="reset_ques_image">
|
||||
Bild zurücksetzen
|
||||
</label>
|
||||
{% endif %}
|
||||
<div>
|
||||
<label class="font-bold" for="question_image">Bild:</label>
|
||||
<input class="max-w-full bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400 dark:bg-transparent" type="file" name="question_image" id="question_image">
|
||||
</div>
|
||||
|
||||
<!-- Eingabe -->
|
||||
{% if question and question.data.options %}
|
||||
{% for option in question.data.options %}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Richtige Antwort</label>
|
||||
<div class="flex space-x-4">
|
||||
<input name="correct_answer" type="number" value="{{ question.data.correct_answer|default:'' }}"
|
||||
class="p-3 dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></input>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Minimale Zahlenauswahl</label>
|
||||
<div class="flex space-x-4">
|
||||
<input name="min" type="number" value="{{ question.data.min|default:'' }}"
|
||||
class="p-3 dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></input>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Maximale Zahlenauswahl</label>
|
||||
<div class="flex space-x-4">
|
||||
<input name="max" type="number" value="{{ question.data.max|default:'' }}"
|
||||
class="p-3 dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></input>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Punkte ab Genauigkeit von ... (in %)</label>
|
||||
<div class="flex space-x-4">
|
||||
<input name="tolerance" type="number" value="{{ question.data.tolerance|default:'' }}" max="100" min="1" value="75"
|
||||
class=" p-3 dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></input>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Richtige Antwort</label>
|
||||
<div class="flex space-x-4">
|
||||
<input name="correct_answer" type="number"
|
||||
class="p-3 dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></input>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Minimale Zahlenauswahl</label>
|
||||
<div class="flex space-x-4">
|
||||
<input name="min" type="number" value="0"
|
||||
class="p-3 dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></input>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Maximale Zahlenauswahl</label>
|
||||
<div class="flex space-x-4">
|
||||
<input name="max" type="number"
|
||||
class="p-3 dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></input>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Punkte ab Genauigkeit von ... (in %)</label>
|
||||
<div class="flex space-x-4">
|
||||
<input name="tolerance" type="number" max="100" min="1" value="75"
|
||||
class=" p-3 dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></input>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="p-4">
|
||||
<label class="font-bold" for="time_per_question">Zeit pro Frage:</label>
|
||||
<select name="time_per_question" id="time_per_question" class="dark:text-white dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<option value="10" {% if standard_time_per_question == "10" or time_per_question == "10" %}selected{% endif %}>10 Sekunden</option>
|
||||
<option value="20" {% if standard_time_per_question == "20" or time_per_question == "20" %}selected{% endif %}>20 Sekunden</option>
|
||||
<option value="30" {% if standard_time_per_question == "30" or time_per_question == "30" %}selected{% endif %}>30 Sekunden</option>
|
||||
<option value="45" {% if standard_time_per_question == "45" or time_per_question == "45" %}selected{% endif %}>45 Sekunden</option>
|
||||
<option value="60" {% if standard_time_per_question == "60" or time_per_question == "60" %}selected{% endif %}>1 Minute</option>
|
||||
<option value="120" {% if standard_time_per_question == "120" or time_per_question == "120" %}selected{% endif %}>2 Minuten</option>
|
||||
<option value="300" {% if standard_time_per_question == "300" or time_per_question == "300" %}selected{% endif %}>5 Minuten</option>
|
||||
</select>
|
||||
</div>
|
||||
<label for="credits" class="block text-sm font-medium text-gray-700 dark:text-white">Credits</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="credits" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
>{{ credits }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3 mt-6">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Abbrechen
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -3,23 +3,23 @@
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold dark:text-white">{% if question %}Frage bearbeiten{% else %}Neue Eingabe Frage{% endif %}</h1>
|
||||
<h1 class="text-2xl font-bold">{% if question %}Frage bearbeiten{% else %}Neue Eingabe Frage{% endif %}</h1>
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="bg-gray-100 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-200 transition-colors">
|
||||
Zurück zum Quiz
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md dark:bg-transparent dark:text-white">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md">
|
||||
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Question Text -->
|
||||
<div>
|
||||
<label for="question" class="block text-sm font-medium text-gray-700 dark:text-white">Frage</label>
|
||||
<label for="question" class="block text-sm font-medium text-gray-700">Frage</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="question" id="question" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
required>{{ question.data.question }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,17 +33,17 @@
|
||||
{% endif %}
|
||||
<div>
|
||||
<label class="font-bold" for="question_image">Bild:</label>
|
||||
<input class="max-w-full bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400 dark:bg-transparent" type="file" name="question_image" id="question_image">
|
||||
<input class="bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400" type="file" name="question_image" id="question_image">
|
||||
</div>
|
||||
|
||||
<!-- Eingabe -->
|
||||
{% if question and question.data.options %}
|
||||
{% for option in question.data.options %}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Richtige Antwort</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Richtige Antwort</label>
|
||||
<div class="flex space-x-4">
|
||||
<textarea name="correct_answer" rows="2"
|
||||
class="dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required>{{ option.value }}</textarea>
|
||||
|
||||
</div>
|
||||
@@ -52,10 +52,10 @@
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Richtige Antwort</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Richtige Antwort</label>
|
||||
<div class="flex space-x-4">
|
||||
<textarea name="correct_answer" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></textarea>
|
||||
|
||||
</div>
|
||||
@@ -66,7 +66,7 @@
|
||||
|
||||
<div class="p-4">
|
||||
<label class="font-bold" for="time_per_question">Zeit pro Frage:</label>
|
||||
<select name="time_per_question" id="time_per_question" class="dark:text-white dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<select name="time_per_question" id="time_per_question">
|
||||
<option value="10" {% if standard_time_per_question == "10" or time_per_question == "10" %}selected{% endif %}>10 Sekunden</option>
|
||||
<option value="20" {% if standard_time_per_question == "20" or time_per_question == "20" %}selected{% endif %}>20 Sekunden</option>
|
||||
<option value="30" {% if standard_time_per_question == "30" or time_per_question == "30" %}selected{% endif %}>30 Sekunden</option>
|
||||
@@ -76,10 +76,10 @@
|
||||
<option value="300" {% if standard_time_per_question == "300" or time_per_question == "300" %}selected{% endif %}>5 Minuten</option>
|
||||
</select>
|
||||
</div>
|
||||
<label for="credits" class="block text-sm font-medium text-gray-700 dark:text-white">Credits</label>
|
||||
<label for="credits" class="block text-sm font-medium text-gray-700">Credits</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="credits" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
>{{ credits }}</textarea>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||
crossorigin=""></script>
|
||||
<div id="mapquestion-container">
|
||||
</div>
|
||||
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold dark:text-white">{% if question %}Frage bearbeiten{% else %}Neue Karten Frage{% endif %}</h1>
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="bg-gray-100 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-200 transition-colors">
|
||||
Zurück zum Quiz
|
||||
</a>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md dark:bg-transparent dark:text-white">
|
||||
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Question Text -->
|
||||
<div>
|
||||
<label for="question" class="block text-sm font-medium text-gray-700 dark:text-white">Frage</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="question" id="question" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
required>{{ question.data.question }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if image %}
|
||||
<img src="{{ image.image.url }}" style="max-width: 100px;" alt="Bild der Frage">
|
||||
<label for="reset_image">
|
||||
<input type="checkbox" name="reset_ques_image" id="reset_ques_image">
|
||||
Bild zurücksetzen
|
||||
</label>
|
||||
{% endif %}
|
||||
<div>
|
||||
<label class="font-bold" for="question_image">Bild:</label>
|
||||
<input class="max-w-full bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400 dark:bg-transparent" type="file" name="question_image" id="question_image">
|
||||
</div>
|
||||
|
||||
<label for="map" class="block text-sm font-medium text-gray-700 dark:text-white">Ziel auswählen</label>
|
||||
<div id="map"></div>
|
||||
|
||||
<label for="location" class="block text-sm font-medium text-gray-700 dark:text-white">Koordinaten</label>
|
||||
<div class="mt-1">
|
||||
<textarea id="location" name="location" rows="2" required
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md "
|
||||
>{{ question.data.target_location }}</textarea>
|
||||
</div>
|
||||
|
||||
<label for="tolerance_radius" class="block text-sm font-medium text-gray-700 dark:text-white">Radius (Meter) für volle Punktzahl</label>
|
||||
<div class="mt-1">
|
||||
<textarea id="tolerance_radius" name="tolerance_radius" rows="2" required
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md "
|
||||
>{{ question.data.tolerance_radius }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="p-4">
|
||||
<label class="font-bold" for="time_per_question">Zeit pro Frage:</label>
|
||||
<select name="time_per_question" id="time_per_question" class="dark:text-white dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<option value="10" {% if standard_time_per_question == "10" or time_per_question == "10" %}selected{% endif %}>10 Sekunden</option>
|
||||
<option value="20" {% if standard_time_per_question == "20" or time_per_question == "20" %}selected{% endif %}>20 Sekunden</option>
|
||||
<option value="30" {% if standard_time_per_question == "30" or time_per_question == "30" %}selected{% endif %}>30 Sekunden</option>
|
||||
<option value="45" {% if standard_time_per_question == "45" or time_per_question == "45" %}selected{% endif %}>45 Sekunden</option>
|
||||
<option value="60" {% if standard_time_per_question == "60" or time_per_question == "60" %}selected{% endif %}>1 Minute</option>
|
||||
<option value="120" {% if standard_time_per_question == "120" or time_per_question == "120" %}selected{% endif %}>2 Minuten</option>
|
||||
<option value="300" {% if standard_time_per_question == "300" or time_per_question == "300" %}selected{% endif %}>5 Minuten</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<label for="credits" class="block text-sm font-medium text-gray-700 dark:text-white">Credits</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="credits" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md "
|
||||
>{{ credits }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Abbrechen
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
{% if question %}Speichern{% else %}Erstellen{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
let marker = null;
|
||||
let circle = null;
|
||||
const locationInput = document.getElementById('location');
|
||||
const radiusInput = document.getElementById('tolerance_radius');
|
||||
|
||||
const map = L.map('map').setView([51.72, 10.45], 3);
|
||||
|
||||
// Prüfe, ob Dark Mode aktiv ist
|
||||
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
// Wähle das passende Karten-Design
|
||||
const tileUrl = isDarkMode
|
||||
? 'https://{s}.basemaps.cartocdn.com/rastertiles/dark_all/{z}/{x}/{y}.png'
|
||||
: 'https://{s}.basemaps.cartocdn.com/rastertiles/light_all/{z}/{x}/{y}.png';
|
||||
|
||||
L.tileLayer(tileUrl, {
|
||||
maxZoom: 19,
|
||||
attribution: '© OpenStreetMap © CARTO'
|
||||
}).addTo(map);
|
||||
|
||||
function updateCircle(latlng) {
|
||||
const radius = parseInt(radiusInput.value) || 100;
|
||||
if (circle) {
|
||||
map.removeLayer(circle);
|
||||
}
|
||||
circle = L.circle(latlng, {
|
||||
color: 'blue',
|
||||
fillColor: '#87CEEB',
|
||||
fillOpacity: 0.2,
|
||||
radius: radius
|
||||
}).addTo(map);
|
||||
}
|
||||
|
||||
function updateInput(latlng) {
|
||||
locationInput.value = `${latlng.lat.toFixed(6)},${latlng.lng.toFixed(6)}`;
|
||||
if (marker) {
|
||||
updateCircle(marker.getLatLng());
|
||||
}
|
||||
}
|
||||
|
||||
function placeOrMoveMarker(latlng) {
|
||||
if (marker) {
|
||||
marker.setLatLng(latlng);
|
||||
} else {
|
||||
marker = L.marker(latlng, { draggable: true }).addTo(map);
|
||||
|
||||
marker.on('dragend', () => {
|
||||
updateInput(marker.getLatLng());
|
||||
});
|
||||
}
|
||||
updateInput(latlng);
|
||||
}
|
||||
|
||||
// Update circle when radius changes
|
||||
radiusInput.addEventListener('input', () => {
|
||||
if (marker) {
|
||||
updateCircle(marker.getLatLng());
|
||||
}
|
||||
});
|
||||
|
||||
// Place marker at existing coordinates if available
|
||||
const existingCoords = '{{ question.data.target_location }}';
|
||||
if (existingCoords) {
|
||||
const [lat, lng] = existingCoords.split(',').map(Number);
|
||||
placeOrMoveMarker({ lat, lng });
|
||||
map.setView([lat, lng], 6); // Zoom in to show the marker
|
||||
}
|
||||
|
||||
map.on('click', e => placeOrMoveMarker(e.latlng));
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -3,22 +3,22 @@
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold dark:text-white">{% if question %}Frage bearbeiten{% else %}Neue Multiple Choice Frage{% endif %}</h1>
|
||||
<h1 class="text-2xl font-bold">{% if question %}Frage bearbeiten{% else %}Neue Multiple Choice Frage{% endif %}</h1>
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="bg-gray-100 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-200 transition-colors">
|
||||
Zurück zum Quiz
|
||||
</a>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md dark:bg-transparent dark:text-white">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md">
|
||||
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Question Text -->
|
||||
<div>
|
||||
<label for="question" class="block text-sm font-medium text-gray-700 dark:text-white">Frage</label>
|
||||
<label for="question" class="block text-sm font-medium text-gray-700">Frage</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="question" id="question" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
required>{{ question.data.question|default:'' }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,24 +33,24 @@
|
||||
{% endif %}
|
||||
<div>
|
||||
<label class="font-bold" for="question_image">Bild:</label>
|
||||
<input value="image.url" class="max-w-full bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400 dark:bg-transparent" type="file" name="question_image" id="question_image">
|
||||
<input value="image.url" class="bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400" type="file" name="question_image" id="question_image">
|
||||
</div>
|
||||
|
||||
<!-- Options -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Antwortmöglichkeiten</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Antwortmöglichkeiten</label>
|
||||
<div id="options-container" class="space-y-3">
|
||||
{% if question and question.data.options %}
|
||||
{% for option in question.data.options|dictsort:"order" %}
|
||||
{% for option in question.data.options %}
|
||||
<div class="flex items-center space-x-3">
|
||||
<textarea name="option_{{ forloop.counter }}" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required>{{ option.value }}</textarea>
|
||||
<label class="inline-flex items-center">
|
||||
<input type="checkbox" name="correct_{{ forloop.counter }}" value="1"
|
||||
class=" focus:outline-none focus:ring-2 focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||||
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||||
{% if option.is_correct %}checked{% endif %}>
|
||||
<span class="ml-2 text-sm text-gray-600 dark:text-white dark:text-white">Richtig</span>
|
||||
<span class="ml-2 text-sm text-gray-600">Richtig</span>
|
||||
</label>
|
||||
<button type="button" onclick="removeOption(this)"
|
||||
class="text-red-600 hover:text-red-800 transition-colors">
|
||||
@@ -63,12 +63,12 @@
|
||||
{% else %}
|
||||
<div class="flex items-center space-x-3">
|
||||
<textarea name="option_1" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></textarea>
|
||||
<label class="inline-flex items-center">
|
||||
<input type="checkbox" name="correct_1" value="1"
|
||||
class=" focus:outline-none focus:ring-2 focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm text-gray-600 dark:text-white">Richtig</span>
|
||||
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm text-gray-600">Richtig</span>
|
||||
</label>
|
||||
<button type="button" onclick="removeOption(this)"
|
||||
class="text-red-600 hover:text-red-800 transition-colors">
|
||||
@@ -79,12 +79,12 @@
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<textarea name="option_2" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></textarea>
|
||||
<label class="inline-flex items-center">
|
||||
<input type="checkbox" name="correct_2" value="1"
|
||||
class=" focus:outline-none focus:ring-2 focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm text-gray-600 dark:text-white">Richtig</span>
|
||||
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm text-gray-600">Richtig</span>
|
||||
</label>
|
||||
<button type="button" onclick="removeOption(this)"
|
||||
class="text-red-600 hover:text-red-800 transition-colors">
|
||||
@@ -95,12 +95,12 @@
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<textarea name="option_3" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></textarea>
|
||||
<label class="inline-flex items-center">
|
||||
<input type="checkbox" name="correct_3" value="1"
|
||||
class=" focus:outline-none focus:ring-2 focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm text-gray-600 dark:text-white">Richtig</span>
|
||||
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm text-gray-600">Richtig</span>
|
||||
</label>
|
||||
<button type="button" onclick="removeOption(this)"
|
||||
class="text-red-600 hover:text-red-800 transition-colors">
|
||||
@@ -111,12 +111,12 @@
|
||||
</div>
|
||||
<div class="flex items-center space-x-3">
|
||||
<textarea name="option_4" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></textarea>
|
||||
<label class="inline-flex items-center">
|
||||
<input type="checkbox" name="correct_4" value="1"
|
||||
class=" focus:outline-none focus:ring-2 focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm text-gray-600 dark:text-white">Richtig</span>
|
||||
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm text-gray-600">Richtig</span>
|
||||
</label>
|
||||
<button type="button" onclick="removeOption(this)"
|
||||
class="text-red-600 hover:text-red-800 transition-colors">
|
||||
@@ -138,7 +138,7 @@
|
||||
|
||||
<div class="p-4">
|
||||
<label class="font-bold" for="time_per_question">Zeit pro Frage:</label>
|
||||
<select name="time_per_question" id="time_per_question" class="dark:text-white dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<select name="time_per_question" id="time_per_question">
|
||||
<option value="10" {% if standard_time_per_question == "10" or time_per_question == "10" %}selected{% endif %}>10 Sekunden</option>
|
||||
<option value="20" {% if standard_time_per_question == "20" or time_per_question == "20" %}selected{% endif %}>20 Sekunden</option>
|
||||
<option value="30" {% if standard_time_per_question == "30" or time_per_question == "30" %}selected{% endif %}>30 Sekunden</option>
|
||||
@@ -148,10 +148,10 @@
|
||||
<option value="300" {% if standard_time_per_question == "300" or time_per_question == "300" %}selected{% endif %}>5 Minuten</option>
|
||||
</select>
|
||||
</div>
|
||||
<label for="credits" class="block text-sm font-medium text-gray-700 dark:text-white">Credits</label>
|
||||
<label for="credits" class="block text-sm font-medium text-gray-700">Credits</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="credits" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md "
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
>{{ credits }}</textarea>
|
||||
</div>
|
||||
|
||||
@@ -183,13 +183,12 @@ function addOption() {
|
||||
div.className = 'flex items-center space-x-3';
|
||||
div.innerHTML = `
|
||||
<textarea name="option_${optionCount}" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required>{{ option.value }}</textarea>
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></textarea>
|
||||
<label class="inline-flex items-center">
|
||||
<input type="checkbox" name="correct_${optionCount}" value="1"
|
||||
class=" focus:outline-none focus:ring-2 focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded"
|
||||
{% if option.is_correct %}checked{% endif %}>
|
||||
<span class="ml-2 text-sm text-gray-600 dark:text-white">Richtig</span>
|
||||
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300 rounded">
|
||||
<span class="ml-2 text-sm text-gray-600">Richtig</span>
|
||||
</label>
|
||||
<button type="button" onclick="removeOption(this)"
|
||||
class="text-red-600 hover:text-red-800 transition-colors">
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold dark:text-white">{% if question %}Frage bearbeiten{% else %}Reihenfolge Frage{% endif %}</h1>
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="bg-gray-100 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-200 transition-colors">
|
||||
Zurück zum Quiz
|
||||
</a>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md dark:bg-transparent dark:text-white">
|
||||
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Question Text -->
|
||||
<div>
|
||||
<label for="question" class="block text-sm font-medium text-gray-700 dark:text-white">Frage</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="question" id="question" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
required>{{ question.data.question|default:'' }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% if image %}
|
||||
<img src="{{ image.image.url }}" style="max-width: 100px;" alt="Bild der Frage">
|
||||
<label for="reset_image">
|
||||
<input type="checkbox" name="reset_ques_image" id="reset_ques_image">
|
||||
Bild zurücksetzen
|
||||
</label>
|
||||
{% endif %}
|
||||
<div>
|
||||
<label class="font-bold" for="question_image">Bild:</label>
|
||||
<input value="image.url" class="max-w-full bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400 dark:bg-transparent" type="file" name="question_image" id="question_image">
|
||||
</div>
|
||||
|
||||
<!-- Options -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">richtige Reihenfolge</label>
|
||||
<div id="options-container" class="space-y-3">
|
||||
{% if question and question.data.options %}
|
||||
{% for option in question.data.options|dictsort:"order" %}
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class="number bg-blue-500 p-2 rounded-lg text-white">{{ forloop.counter }}.</span>
|
||||
<textarea name="option_{{ forloop.counter }}" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required>{{ option.value }}</textarea>
|
||||
<label class="inline-flex items-center">
|
||||
</label>
|
||||
<button type="button" onclick="removeOption(this)"
|
||||
class="text-red-600 hover:text-red-800 transition-colors">
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for i in "1234" %}
|
||||
<div class="flex items-center space-x-3">
|
||||
<span class="number bg-blue-500 p-2 rounded-lg text-white">{{ forloop.counter }}.</span>
|
||||
<textarea name="option_{{ forloop.counter }}" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></textarea>
|
||||
<label class="inline-flex items-center">
|
||||
|
||||
</label>
|
||||
<button type="button" onclick="removeOption(this)"
|
||||
class="text-red-600 hover:text-red-800 transition-colors">
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<button type="button" onclick="addOption()"
|
||||
class="mt-3 inline-flex items-center px-3 py-1.5 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
<svg class="h-4 w-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
||||
</svg>
|
||||
Option hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="p-4">
|
||||
<label class="font-bold" for="time_per_question">Zeit pro Frage:</label>
|
||||
<select name="time_per_question" id="time_per_question" class="dark:text-white dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<option value="10" {% if standard_time_per_question == "10" or time_per_question == "10" %}selected{% endif %}>10 Sekunden</option>
|
||||
<option value="20" {% if standard_time_per_question == "20" or time_per_question == "20" %}selected{% endif %}>20 Sekunden</option>
|
||||
<option value="30" {% if standard_time_per_question == "30" or time_per_question == "30" %}selected{% endif %}>30 Sekunden</option>
|
||||
<option value="45" {% if standard_time_per_question == "45" or time_per_question == "45" %}selected{% endif %}>45 Sekunden</option>
|
||||
<option value="60" {% if standard_time_per_question == "60" or time_per_question == "60" %}selected{% endif %}>1 Minute</option>
|
||||
<option value="120" {% if standard_time_per_question == "120" or time_per_question == "120" %}selected{% endif %}>2 Minuten</option>
|
||||
<option value="300" {% if standard_time_per_question == "300" or time_per_question == "300" %}selected{% endif %}>5 Minuten</option>
|
||||
</select>
|
||||
</div>
|
||||
<label for="credits" class="block text-sm font-medium text-gray-700 dark:text-white">Credits</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="credits" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md "
|
||||
>{{ credits }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end space-x-3">
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
Abbrechen
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||
{% if question %}Speichern{% else %}Erstellen{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
|
||||
function addOption() {
|
||||
const container = document.getElementById('options-container');
|
||||
const optionCount = container.children.length + 1;
|
||||
|
||||
if (optionCount > 6) {
|
||||
alert('Maximal 6 Optionen sind erlaubt.');
|
||||
return;
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flex items-center space-x-3';
|
||||
div.innerHTML = `
|
||||
|
||||
|
||||
|
||||
<span class="number bg-blue-500 p-2 rounded-lg text-white">${optionCount}.</span>
|
||||
<textarea name="option_${optionCount}" rows="2"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 flex-grow sm:text-sm border-gray-300 rounded-md"
|
||||
required></textarea>
|
||||
<label class="inline-flex items-center">
|
||||
|
||||
</label>
|
||||
<button type="button" onclick="removeOption(this)"
|
||||
class="text-red-600 hover:text-red-800 transition-colors">
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
`;
|
||||
|
||||
container.appendChild(div);
|
||||
}
|
||||
|
||||
function removeOption(button) {
|
||||
|
||||
const container = document.getElementById('options-container');
|
||||
if (container.children.length > 2) {
|
||||
button.parentElement.remove();
|
||||
updateOptionNumbers();
|
||||
} else {
|
||||
alert('Mindestens zwei Optionen müssen vorhanden sein.');
|
||||
}
|
||||
}
|
||||
|
||||
function updateOptionNumbers() {
|
||||
const container = document.getElementById('options-container');
|
||||
const options = container.children;
|
||||
Array.from(options).forEach((option, index) => {
|
||||
const number = index + 1;
|
||||
const textInput = option.querySelector('textarea');
|
||||
|
||||
|
||||
|
||||
textInput.name = `option_${number}`;
|
||||
const numberSpan = option.querySelector('span.number');
|
||||
if (numberSpan) {
|
||||
numberSpan.textContent = `${number}.`;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -3,23 +3,23 @@
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h1 class="text-2xl font-bold dark:text-white">{% if question %}Frage bearbeiten{% else %}Neue Wahr/Falsch Frage{% endif %}</h1>
|
||||
<h1 class="text-2xl font-bold">{% if question %}Frage bearbeiten{% else %}Neue Wahr/Falsch Frage{% endif %}</h1>
|
||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
||||
class="bg-gray-100 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-200 transition-colors">
|
||||
Zurück zum Quiz
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md dark:bg-transparent dark:text-white">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md">
|
||||
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Question Text -->
|
||||
<div>
|
||||
<label for="question" class="block text-sm font-medium text-gray-700 dark:text-white">Frage</label>
|
||||
<label for="question" class="block text-sm font-medium text-gray-700">Frage</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="question" id="question" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
required>{{ question.data.question }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,31 +33,31 @@
|
||||
{% endif %}
|
||||
<div>
|
||||
<label class="font-bold" for="question_image">Bild:</label>
|
||||
<input class="max-w-full bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400 dark:bg-transparent" type="file" name="question_image" id="question_image">
|
||||
<input class="bg-blue-200 p-2 m-2 rounded-lg border-2 border-blue-400" type="file" name="question_image" id="question_image">
|
||||
</div>
|
||||
|
||||
<!-- True/False Selection -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2 dark:text-white">Richtige Antwort</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">Richtige Antwort</label>
|
||||
<div class="flex space-x-4">
|
||||
<label class="inline-flex items-center">
|
||||
<input type="radio" name="correct_answer" value="true"
|
||||
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300"
|
||||
{% if question.data.correct_answer %}checked{% endif %}>
|
||||
<span class="ml-2 text-sm text-gray-600 dark:text-white">Wahr</span>
|
||||
<span class="ml-2 text-sm text-gray-600">Wahr</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center">
|
||||
<input type="radio" name="correct_answer" value="false"
|
||||
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300"
|
||||
{% if not question.data.correct_answer %}checked{% endif %}>
|
||||
<span class="ml-2 text-sm text-gray-600 dark:text-white">Falsch</span>
|
||||
<span class="ml-2 text-sm text-gray-600">Falsch</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4">
|
||||
<label class="font-bold" for="time_per_question">Zeit pro Frage:</label>
|
||||
<select name="time_per_question" id="time_per_question" class="dark:text-white dark:bg-[#2a2f3a] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||
<select name="time_per_question" id="time_per_question">
|
||||
<option value="10" {% if standard_time_per_question == "10" or time_per_question == "10" %}selected{% endif %}>10 Sekunden</option>
|
||||
<option value="20" {% if standard_time_per_question == "20" or time_per_question == "20" %}selected{% endif %}>20 Sekunden</option>
|
||||
<option value="30" {% if standard_time_per_question == "30" or time_per_question == "30" %}selected{% endif %}>30 Sekunden</option>
|
||||
@@ -67,10 +67,10 @@
|
||||
<option value="300" {% if standard_time_per_question == "300" or time_per_question == "300" %}selected{% endif %}>5 Minuten</option>
|
||||
</select>
|
||||
</div>
|
||||
<label for="credits" class="block text-sm font-medium text-gray-700 dark:text-white">Credits</label>
|
||||
<label for="credits" class="block text-sm font-medium text-gray-700">Credits</label>
|
||||
<div class="mt-1">
|
||||
<textarea name="credits" rows="4"
|
||||
class="dark:bg-[#2a2f3a] shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
class="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
>{{ credits }}</textarea>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,48 +1,7 @@
|
||||
<footer class="w-full bg-blue-600 text-white shadow-xl py-6 mt-24 break-words">
|
||||
|
||||
|
||||
|
||||
<div class="flex items-center justify-center flex-col sm:flex-row sm:gap-32">
|
||||
|
||||
<div class="mb-2 sm:m-0">
|
||||
<h1 class="flex justify-center text-md font-bold font-sans tracking-wider gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v17.25m0 0c-1.472 0-2.882.265-4.185.75M12 20.25c1.472 0 2.882.265 4.185.75M18.75 4.97A48.416 48.416 0 0 0 12 4.5c-2.291 0-4.545.16-6.75.47m13.5 0c1.01.143 2.01.317 3 .52m-3-.52 2.62 10.726c.122.499-.106 1.028-.589 1.202a5.988 5.988 0 0 1-2.031.352 5.988 5.988 0 0 1-2.031-.352c-.483-.174-.711-.703-.59-1.202L18.75 4.971Zm-16.5.52c.99-.203 1.99-.377 3-.52m0 0 2.62 10.726c.122.499-.106 1.028-.589 1.202a5.989 5.989 0 0 1-2.031.352 5.989 5.989 0 0 1-2.031-.352c-.483-.174-.711-.703-.59-1.202L5.25 4.971Z" />
|
||||
</svg>
|
||||
Rechtliches:</h1>
|
||||
|
||||
<div class="left-0 w-full p-2 flex justify-center space-x-2 text-white">
|
||||
<p class="text-xs tracking-wide"><a class= "hover:underline" href="/impress">Impressum</a></p>
|
||||
<p class="text-xs tracking-wide"><a class= "hover:underline" href="/privacy">Datenschutz</a></p>
|
||||
<p class="text-xs tracking-wide"><a class= "hover:underline" href="https://edugit.org/enrichment-2024/qivip">Repository</a></p>
|
||||
<div class="grid place-content-center gap-2 w-full mt-4">
|
||||
<div class="flex space-x-2 pb-4">
|
||||
<p class="text-sm font-extralight"><a href="/impress">Impressum</a></p>
|
||||
<p class="text-sm font-extralight"><a href="/privacy">Datenschutz</a></p>
|
||||
<p class="text-sm font-extralight"><a href="https://edugit.org/enrichment-2024/qivip">Repository</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="flex justify-center text-md font-bold font-sans tracking-widerd gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 7.5h1.5m-1.5 3h1.5m-7.5 3h7.5m-7.5 3h7.5m3-9h3.375c.621 0 1.125.504 1.125 1.125V18a2.25 2.25 0 0 1-2.25 2.25M16.5 7.5V18a2.25 2.25 0 0 0 2.25 2.25M16.5 7.5V4.875c0-.621-.504-1.125-1.125-1.125H4.125C3.504 3.75 3 4.254 3 4.875V18a2.25 2.25 0 0 0 2.25 2.25h13.5M6 7.5h3v3H6v-3Z" />
|
||||
</svg>
|
||||
|
||||
Erfahren Sie mehr:</h2>
|
||||
<div class="left-0 w-full p-2 flex justify-center space-x-2 text-white">
|
||||
<p class="text-xs tracking-wide"><a class= "hover:underline" href="https://katharineum.de/qivip-unsere-neue-quiz-software/">
|
||||
|
||||
mehr über uns</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex justify-center">
|
||||
<div class="h-0.25 w-200 bg-white opacity-40 rounded-full mt-2">
|
||||
</div></div>
|
||||
|
||||
|
||||
<p class="text-xs text-center opacity-80 mt-2">
|
||||
|
||||
<a class="font-bold" href="/">qivip</a> - Ein Projekt entstanden während des Enrichment-Programms.
|
||||
</p>
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
@@ -9,10 +9,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Desktop Search (centered on screen) - Hidden on mobile -->
|
||||
<div class="hidden md:flex justify-center items-center absolute left-1/2 transform -translate-x-1/2 min-w-40 ">
|
||||
<form method="get" action="{% url link|default:'library:overview_quiz' %}" class="mr-2 ml-2 flex items-center justify-center w-screen max-w-sm bg-white rounded-full px-4 py-1 shadow-md dark:bg-gray-900">
|
||||
<div class="hidden md:flex justify-center items-center absolute left-1/2 transform -translate-x-1/2 min-w-40">
|
||||
<form method="get" action="{% url link|default:'library:overview_quiz' %}" class="mr-2 ml-2 flex items-center justify-center w-screen max-w-sm bg-white rounded-full px-4 py-1 shadow-md">
|
||||
<input id="quiz-search-desktop" list="quiz-names-desktop" type="text" name="search" placeholder="Suche ..." value="{{ request.GET.search }}"
|
||||
class="text-sm w-full px-3 py-1 text-black bg-transparent focus:outline-none lg:text-base dark:text-white">
|
||||
class="text-sm w-full px-3 py-1 text-black bg-transparent focus:outline-none lg:text-base">
|
||||
<button class="py-1 text-blue-600">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5 md:size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
|
||||
@@ -48,13 +48,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Mobile Navigation Menu - Hidden by default - FULLWIDTH DROPDOWN -->
|
||||
<div id="mobile-menu" class="hidden absolute w-full z-50 md:hidden opacity-0 transform -translate-y-2 transition-all duration-300 ">
|
||||
<div id="mobile-menu" class="hidden absolute w-full z-50 md:hidden opacity-0 transform -translate-y-2 transition-all duration-300">
|
||||
<div class="bg-blue-500 pt-3 pb-4 rounded-b-lg shadow-inner">
|
||||
<!-- Search in mobile menu -->
|
||||
<form method="get" action="{% url link|default:'library:overview_quiz' %}" class="px-4 mb-4 ">
|
||||
<div class="flex items-center justify-center bg-white rounded-full px-4 py-2 shadow-md dark:bg-gray-900">
|
||||
<form method="get" action="{% url link|default:'library:overview_quiz' %}" class="px-4 mb-4">
|
||||
<div class="flex items-center justify-center bg-white rounded-full px-4 py-2 shadow-md">
|
||||
<input id="quiz-search-mobile" list="quiz-names-mobile" type="text" name="search" placeholder="Suche ..." value="{{ request.GET.search }}"
|
||||
class="w-full text-sm px-2 text-black bg-transparent focus:outline-none dark:text-white">
|
||||
class="w-full text-sm px-2 text-black bg-transparent focus:outline-none">
|
||||
<button class="text-blue-600">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" class="size-5">
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<div class="bg-white rounded-lg shadow-md p-8 mb-6 dark:bg-transparent dark:text-white">
|
||||
<div class="bg-white rounded-lg shadow-md p-8 mb-6">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-4xl font-bold text-blue-600 mb-4">Quiz beendet!</h1>
|
||||
<p class="text-xl text-gray-600 dark:text-white">Vielen Dank fürs Spielen!</p>
|
||||
<p class="text-xl text-gray-600">Vielen Dank fürs Spielen!</p>
|
||||
</div>
|
||||
|
||||
{% if not is_host %}
|
||||
{% if rating_enabled %}
|
||||
<div class="bg-blue-50 rounded-lg p-6 mb-8 dark:bg-transparent ">
|
||||
<h2 class="text-2xl font-bold text-blue-800 mb-4 text-center dark:text-white">Wie hat dir das Quiz gefallen?</h2>
|
||||
<div class="bg-blue-50 rounded-lg p-6 mb-8">
|
||||
<h2 class="text-2xl font-bold text-blue-800 mb-4 text-center">Wie hat dir das Quiz gefallen?</h2>
|
||||
<div class="stars flex justify-center space-x-4 mb-4">
|
||||
<i class="fas fa-star text-3xl transition-all duration-200 hover:scale-110" data-rating="1"></i>
|
||||
<i class="fas fa-star text-3xl transition-all duration-200 hover:scale-110" data-rating="2"></i>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
|
||||
{% load static %}
|
||||
<script src="{% static 'js/sortablejs/Sortable.min.js' %}"></script>
|
||||
|
||||
|
||||
<div class="container mx-auto px-4">
|
||||
@@ -13,17 +11,17 @@
|
||||
</button>
|
||||
{% endif %}
|
||||
<button onclick="this.onclick=null; advanceToScores();" class="mx-2 bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded">
|
||||
überspringen
|
||||
weiter
|
||||
</button>
|
||||
</div>
|
||||
<div id="form_host" class="bg-white rounded-lg shadow-md p-4 mb-6 dark:bg-transparent dark:text-white">
|
||||
<div id="form_host" class="bg-white rounded-lg shadow-md p-4 mb-6">
|
||||
<div class="flex justify-between mb-2">
|
||||
<div class="flex gap-1 text-sm"><p class="text-sm">Frage</p>
|
||||
<p class="text-gray-700 font-bold text-sm dark:text-white">{{ question_index|add:1 }}</p>
|
||||
<p class="text-gray-700 font-bold text-sm">{{ question_index|add:1 }}</p>
|
||||
<p class="text-sm">von</p>
|
||||
<p class="text-gray-700 font-bold text-sm dark:text-white">{{ total_questions }}</p>
|
||||
<p class="text-gray-700 font-bold text-sm">{{ total_questions }}</p>
|
||||
</div>
|
||||
<div class="flex gap-1 mb-2"><p class="text-sm">Antworten:</p> <p id="answer-count" class="text-gray-700 font-bold text-sm dark:text-white"></p></div>
|
||||
<div class="flex gap-1 mb-2"><p class="text-sm">Antworten:</p> <p id="answer-count" class="text-gray-700 font-bold text-sm"></p></div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -50,7 +48,7 @@
|
||||
|
||||
|
||||
</div>
|
||||
{% if question_data.type == "multiple_choice" or question_data.type == "true_false" %}
|
||||
{% if question_data.type != "input" %}
|
||||
<div class="grid grid-cols-2 gap-4" id="options-container">
|
||||
{% for option in question_data.options %}
|
||||
<button
|
||||
@@ -83,201 +81,25 @@
|
||||
onclick="submitAnswer({{ forloop.counter0 }});" {% endif %}>
|
||||
<p class="text-lg">{{ option.value }}</p>
|
||||
</button>
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
{% elif question_data.type == "order" %}
|
||||
|
||||
<div id="sortable-options" class=" mb-4 flex flex-col items-center">
|
||||
{% for option in question_data.options %}
|
||||
|
||||
<button data-index="{{ forloop.counter0 }}"
|
||||
data-value="{{ option.value }}"
|
||||
|
||||
class=" my-2 w-3/4 max-w-xl
|
||||
{% if forloop.counter0 == 0 %}
|
||||
bg-green-500
|
||||
hover:bg-green-600
|
||||
{% elif forloop.counter0 == 1 %}
|
||||
bg-red-500
|
||||
hover:bg-red-600
|
||||
{% elif forloop.counter0 == 2 %}
|
||||
bg-blue-500
|
||||
hover:bg-blue-600
|
||||
{% elif forloop.counter0 == 3 %}
|
||||
bg-purple-500
|
||||
hover:bg-purple-600
|
||||
{% elif forloop.counter0 == 4 %}
|
||||
bg-orange-500
|
||||
hover:bg-orange-600
|
||||
{% elif forloop.counter0 == 5 %}
|
||||
bg-yellow-500
|
||||
hover:bg-yellow-600
|
||||
{% else %}
|
||||
bg-gray-500
|
||||
hover:bg-gray-600
|
||||
{% endif %}
|
||||
p-4 rounded-lg text-white transition-colors duration-200 answer-option overflow-auto break-words hyphens-auto "
|
||||
data-index="{{ forloop.counter0 }}"
|
||||
|
||||
>
|
||||
|
||||
<p class="text-lg flex items-center">
|
||||
<span class="order-number inline-block w-8 h-8 rounded-full bg-white text-gray-800 font-bold text-center leading-8 mr-3">1</span>
|
||||
|
||||
{{ option.value }}
|
||||
</p>
|
||||
|
||||
</button>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
{% if request.session.mode == "learn" %}
|
||||
<button
|
||||
onclick="submitOrderAnswer()"
|
||||
id="send_order" class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors duration-200 answer-option "
|
||||
>
|
||||
Abschicken
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
{% elif question_data.type == "input" %}
|
||||
{% if request.session.mode == "learn" %}
|
||||
<form action="">
|
||||
<input id="textanswer" type="text" placeholder="DEINE ANTWORT"
|
||||
class="answer-option text-center mb-4 p-2 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option lg:w-80 dark:bg-transparent dark:text-white dark:hover:bg-[#2a2f3a] ">
|
||||
class="answer-option text-center p-2 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option lg:w-80 ">
|
||||
|
||||
<button
|
||||
onclick="submitAnswer( -1 );"
|
||||
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors duration-200 answer-option "
|
||||
>
|
||||
Abschicken
|
||||
<button {% if request.session.mode == "learn" %}
|
||||
onclick="submitAnswer( -1 );" {% endif %}
|
||||
class="mt-2 text-center p-2 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option">
|
||||
abschicken
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{% endif %}
|
||||
{% elif question_data.type == "map" and request.session.mode == "learn" %}
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||
crossorigin=""></script>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mb-2">Ziel auswählen</div>
|
||||
<div id="map" class="h-96"></div>
|
||||
<input type="hidden" value="" id="singleanswer" name="singleanswer">
|
||||
<div class="mt-4">
|
||||
<button onclick="submitAnswerMap()" class="bg-blue-400 text-white px-4 py-2 rounded-md hover:bg-blue-500 transition-colors">
|
||||
Abgeben
|
||||
</button>
|
||||
</div>
|
||||
<input type="hidden" id="location" name="location" value="">
|
||||
<script>
|
||||
let marker = null;
|
||||
const locationInput = document.getElementById('location');
|
||||
const map = L.map('map').setView([51.72, 10.45], 3);
|
||||
|
||||
// Prüfe, ob Dark Mode aktiv ist
|
||||
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
// Wähle das passende Karten-Design
|
||||
const tileUrl = isDarkMode
|
||||
? 'https://{s}.basemaps.cartocdn.com/rastertiles/dark_nolabels/{z}/{x}/{y}.png'
|
||||
: 'https://{s}.basemaps.cartocdn.com/rastertiles/light_nolabels/{z}/{x}/{y}.png';
|
||||
|
||||
L.tileLayer(tileUrl, {
|
||||
maxZoom: 19,
|
||||
attribution: '© OpenStreetMap © CARTO'
|
||||
}).addTo(map);
|
||||
|
||||
function updateInput(latlng) {
|
||||
locationInput.value = `${latlng.lat.toFixed(6)},${latlng.lng.toFixed(6)}`;
|
||||
}
|
||||
|
||||
function placeOrMoveMarker(latlng) {
|
||||
if (marker) {
|
||||
marker.setLatLng(latlng);
|
||||
} else {
|
||||
marker = L.marker(latlng, { draggable: true }).addTo(map);
|
||||
marker.on('dragend', () => {
|
||||
updateInput(marker.getLatLng());
|
||||
});
|
||||
}
|
||||
updateInput(latlng);
|
||||
}
|
||||
|
||||
map.on('click', e => placeOrMoveMarker(e.latlng));
|
||||
|
||||
function submitAnswerMap() {
|
||||
const answer = document.getElementById("location").value;
|
||||
|
||||
if (answer === "") {
|
||||
alert("Bitte einen Ort auf der Karte auswählen!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add grey overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.position = 'fixed';
|
||||
overlay.style.top = '0';
|
||||
overlay.style.left = '0';
|
||||
overlay.style.width = '100%';
|
||||
overlay.style.height = '100%';
|
||||
overlay.style.backgroundColor = 'rgba(128, 128, 128, 0.5)';
|
||||
overlay.style.zIndex = '1000';
|
||||
overlay.style.display = 'flex';
|
||||
overlay.style.justifyContent = 'center';
|
||||
overlay.style.alignItems = 'center';
|
||||
overlay.style.color = 'white';
|
||||
overlay.style.fontSize = '24px';
|
||||
overlay.style.fontWeight = 'bold';
|
||||
overlay.textContent = 'Antwort übermittelt';
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const now = Date.now();
|
||||
const timeRemaining = Math.max(0, questionDuration - (now - questionStartTime));
|
||||
|
||||
// Submit the answer
|
||||
submitAnswer(answer);
|
||||
}
|
||||
</script>
|
||||
{% elif question_data.type == "map" and request.session.mode != "learn" %}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2 text-center">Bitte den gesuchten Ort auf der Karte auswählen</p>
|
||||
{% elif question_data.type == "guess" %}
|
||||
{% if request.session.mode == "learn" %}
|
||||
<form class="flex flex-col items-center gap-4 mt-4">
|
||||
<!-- Slider mit Beschriftung -->
|
||||
<div class="w-full max-w-xl text-center">
|
||||
<input
|
||||
id="textanswer"
|
||||
type="range"
|
||||
min="{{ question_data.min }}"
|
||||
max="{{ question_data.max }}"
|
||||
value="{{ question_data.min }}"
|
||||
step="1"
|
||||
oninput="document.getElementById('range-value').textContent = 'Wert: ' + this.value;"
|
||||
class="w-full answer-option bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 dark:bg-transparent dark:text-white dark:hover:bg-[#2a2f3a]"
|
||||
>
|
||||
<p id="range-value" class="mt-2 font-bold text-gray-800 dark:text-white">Wert: {{ question_data.min }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Abschicken-Button -->
|
||||
|
||||
<button
|
||||
onclick="submitAnswer(-1);"
|
||||
|
||||
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors duration-200 answer-option "
|
||||
>
|
||||
Abschicken
|
||||
</button>
|
||||
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -309,12 +131,8 @@ class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-
|
||||
} else if (data.type === 'game_state_update' &&
|
||||
data.action === 'show_scores' &&
|
||||
data.redirect_url) {
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
submitAnswer(-1);
|
||||
window.location.href = data.redirect_url;
|
||||
}, 1000);
|
||||
submitAnswer( -1 );
|
||||
}
|
||||
};
|
||||
|
||||
@@ -362,7 +180,7 @@ class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-
|
||||
'type': 'advance_to_scores',
|
||||
'host_id': hostId
|
||||
}));
|
||||
}, 0);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Timer
|
||||
@@ -381,7 +199,6 @@ class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-
|
||||
|
||||
if (remaining === 0) {
|
||||
advanceToScores();
|
||||
|
||||
} else {
|
||||
requestAnimationFrame(updateTimer);
|
||||
}
|
||||
@@ -464,74 +281,5 @@ class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-
|
||||
}));
|
||||
}
|
||||
}
|
||||
let initialOrder = [];
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const container = document.getElementById('sortable-options');
|
||||
if (container) {
|
||||
initialOrder = Array.from(container.children).map(div => div.dataset.index);
|
||||
|
||||
Sortable.create(container, {
|
||||
animation: 150,
|
||||
onUpdate: updateOrderNumbers // <--- Nummern aktualisieren nach Sortieren
|
||||
});
|
||||
|
||||
updateOrderNumbers(); // initial setzen
|
||||
}
|
||||
});
|
||||
|
||||
function updateOrderNumbers() {
|
||||
const container = document.getElementById('sortable-options');
|
||||
const children = container.children;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const label = children[i].querySelector('.order-number');
|
||||
if (label) {
|
||||
label.textContent = `${i + 1}.`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function submitOrderAnswer() {
|
||||
const order_button=document.getElementById("send_order")
|
||||
order_button.disabled=true;
|
||||
order_button.classList.add('opacity-50');
|
||||
const orderedDivs = document.querySelectorAll('#sortable-options button');
|
||||
|
||||
const selectedOrder = Array.from(orderedDivs).map(div => div.dataset.value);
|
||||
|
||||
|
||||
const now = Date.now();
|
||||
let timeRemaining = Math.max(0, questionDuration - (now - questionStartTime));
|
||||
{% if request.session.mode == "learn" %}
|
||||
timeRemaining = questionDuration;
|
||||
{% endif %}
|
||||
|
||||
gameSocket.send(JSON.stringify({
|
||||
type: 'submit_answer',
|
||||
participant_id: '{{ host_id }}',
|
||||
answer: selectedOrder,
|
||||
time_remaining: timeRemaining
|
||||
}));
|
||||
|
||||
hasAnswered = true;
|
||||
}
|
||||
|
||||
function resetOrderSelection() {
|
||||
const container = document.getElementById('sortable-options');
|
||||
const divs = Array.from(container.children);
|
||||
const indexMap = {};
|
||||
divs.forEach(div => {
|
||||
indexMap[div.dataset.index] = div;
|
||||
});
|
||||
|
||||
// Sort children back to initial order
|
||||
container.innerHTML = '';
|
||||
initialOrder.forEach(i => {
|
||||
container.appendChild(indexMap[i]);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,23 +1,19 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% load static %}
|
||||
<script src="{% static 'js/sortablejs/Sortable.min.js' %}"></script>
|
||||
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex justify-end mb-4">
|
||||
<button onclick="leaveGame()" class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded">
|
||||
Spiel verlassen
|
||||
</button>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6 dark:bg-transparent dark:text-white">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
|
||||
<div class="flex justify-between mb-2">
|
||||
<div class="flex gap-1 text-sm"><p class="text-sm">Frage</p>
|
||||
<p class="text-gray-700 font-bold text-sm dark:text-white">{{ question_index|add:1 }}</p>
|
||||
<p class="text-gray-700 font-bold text-sm">{{ question_index|add:1 }}</p>
|
||||
<p class="text-sm">von</p>
|
||||
<p class="text-gray-700 font-bold text-sm dark:text-white">{{ total_questions }}</p>
|
||||
<p class="text-gray-700 font-bold text-sm">{{ total_questions }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="text-xl font-black mb-2 border-blue-500 border-4 p-4 text-center rounded-lg md:text-2xl lg:text-4xl">{{ question_data.question}}</h1>
|
||||
@@ -25,11 +21,11 @@
|
||||
<div class="w-full bg-gray-300 rounded-full h-4 my-4">
|
||||
<div id="time-bar" class="bg-blue-600 h-4 rounded-full transition-all duration-100 linear" style="width: 100%;"></div>
|
||||
</div>
|
||||
{% if question_data.type == "multiple_choice" or question_data.type == "true_false" %}
|
||||
|
||||
{% if question_data.type != "input" %}
|
||||
<div class="grid grid-cols-2 gap-4" id="options-container">
|
||||
{% for option in question_data.options %}
|
||||
<button data-index="{{ forloop.counter0 }}"
|
||||
data-value="{{ option.value }}"
|
||||
<button
|
||||
class="
|
||||
{% if forloop.counter0 == 0 %}
|
||||
bg-green-500
|
||||
@@ -60,165 +56,16 @@
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% elif question_data.type == "order" %}
|
||||
|
||||
<div id="sortable-options" class=" mb-4 flex flex-col items-center">
|
||||
{% for option in question_data.options %}
|
||||
|
||||
<button data-index="{{ forloop.counter0 }}"
|
||||
data-value="{{ option.value }}"
|
||||
|
||||
class=" my-2 w-3/4 max-w-xl
|
||||
{% if forloop.counter0 == 0 %}
|
||||
bg-green-500
|
||||
hover:bg-green-600
|
||||
{% elif forloop.counter0 == 1 %}
|
||||
bg-red-500
|
||||
hover:bg-red-600
|
||||
{% elif forloop.counter0 == 2 %}
|
||||
bg-blue-500
|
||||
hover:bg-blue-600
|
||||
{% elif forloop.counter0 == 3 %}
|
||||
bg-purple-500
|
||||
hover:bg-purple-600
|
||||
{% elif forloop.counter0 == 4 %}
|
||||
bg-orange-500
|
||||
hover:bg-orange-600
|
||||
{% elif forloop.counter0 == 5 %}
|
||||
bg-yellow-500
|
||||
hover:bg-yellow-600
|
||||
{% else %}
|
||||
bg-gray-500
|
||||
hover:bg-gray-600
|
||||
{% endif %}
|
||||
p-4 rounded-lg text-white transition-colors duration-200 answer-option overflow-auto break-words hyphens-auto "
|
||||
data-index="{{ forloop.counter0 }}"
|
||||
|
||||
>
|
||||
|
||||
<p class="text-lg flex items-center">
|
||||
<span class="order-number inline-block w-8 h-8 rounded-full bg-white text-gray-800 font-bold text-center leading-8 mr-3">1</span>
|
||||
|
||||
{{ option.value }}
|
||||
</p>
|
||||
|
||||
</button>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button id="send_order"
|
||||
onclick="submitOrderAnswer()"
|
||||
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors duration-200 answer-option "
|
||||
>
|
||||
Abschicken
|
||||
</button>
|
||||
|
||||
{% elif question_data.type == "guess" %}
|
||||
<form class="flex flex-col items-center gap-4 mt-4">
|
||||
<!-- Slider mit Beschriftung -->
|
||||
<div class="w-full max-w-xl text-center">
|
||||
<input
|
||||
id="textanswer"
|
||||
type="range"
|
||||
min="{{ question_data.min }}"
|
||||
max="{{ question_data.max }}"
|
||||
value="{{ question_data.min }}"
|
||||
step="1"
|
||||
oninput="document.getElementById('range-value').textContent = 'Wert: ' + this.value;"
|
||||
class="w-full answer-option bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 dark:bg-transparent dark:text-white dark:hover:bg-[#2a2f3a]"
|
||||
>
|
||||
<p id="range-value" class="mt-2 font-bold text-gray-800 dark:text-white">Wert: {{ question_data.min }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Abschicken-Button -->
|
||||
<button
|
||||
|
||||
onclick="submitAnswer(-1);"
|
||||
|
||||
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors duration-200 answer-option "
|
||||
>
|
||||
Abschicken
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{% elif question_data.type == "input" %}
|
||||
<form action="">
|
||||
<input id="textanswer" type="text" placeholder="DEINE ANTWORT"
|
||||
class="answer-option text-center p-2 mb-4 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option lg:w-80 dark:bg-transparent dark:text-white dark:hover:bg-[#2a2f3a]">
|
||||
class="answer-option text-center p-2 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option lg:w-80">
|
||||
<button
|
||||
onclick="submitAnswer( -1 );"
|
||||
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors duration-200 answer-option "
|
||||
>
|
||||
Abschicken
|
||||
class="mt-2 text-center p-2 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option">
|
||||
abschicken
|
||||
</button>
|
||||
</form>
|
||||
{% elif question_data.type == "map" %}
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||
crossorigin=""></script>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mb-2">Ziel auswählen</div>
|
||||
<div id="map" class="h-96"></div>
|
||||
<input type="hidden" value="" id="location" name="location">
|
||||
<div class="mt-4">
|
||||
<button onclick="submitAnswerMap()" class="bg-blue-400 text-white px-4 py-2 rounded-md hover:bg-blue-500 transition-colors">
|
||||
Abgeben
|
||||
</button>
|
||||
</div>
|
||||
<script>
|
||||
let marker = null;
|
||||
const locationInput = document.getElementById('location');
|
||||
|
||||
const map = L.map('map').setView([51.72, 10.45], 3);
|
||||
|
||||
// Prüfe, ob Dark Mode aktiv ist
|
||||
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
// Wähle das passende Karten-Design
|
||||
const tileUrl = isDarkMode
|
||||
? 'https://{s}.basemaps.cartocdn.com/rastertiles/dark_nolabels/{z}/{x}/{y}.png'
|
||||
: 'https://{s}.basemaps.cartocdn.com/rastertiles/light_nolabels/{z}/{x}/{y}.png';
|
||||
|
||||
L.tileLayer(tileUrl, {
|
||||
maxZoom: 19,
|
||||
attribution: '© OpenStreetMap © CARTO'
|
||||
}).addTo(map);
|
||||
|
||||
function updateInput(latlng) {
|
||||
locationInput.value = `${latlng.lat.toFixed(6)},${latlng.lng.toFixed(6)}`;
|
||||
}
|
||||
|
||||
function placeOrMoveMarker(latlng) {
|
||||
if (marker) {
|
||||
marker.setLatLng(latlng);
|
||||
} else {
|
||||
marker = L.marker(latlng, { draggable: true }).addTo(map);
|
||||
|
||||
marker.on('dragend', () => {
|
||||
updateInput(marker.getLatLng());
|
||||
});
|
||||
}
|
||||
updateInput(latlng);
|
||||
}
|
||||
|
||||
map.on('click', e => placeOrMoveMarker(e.latlng));
|
||||
</script>
|
||||
{% else %}
|
||||
<p>aaaaah gibts nicht</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -242,11 +89,7 @@
|
||||
data.action === 'show_scores' &&
|
||||
data.redirect_url) {
|
||||
window.location.href = data.redirect_url;
|
||||
{% if question_data.type == "order" %}
|
||||
submitOrderAnswer();
|
||||
{% elif question_data.type != "map" %}
|
||||
submitAnswer(-1);
|
||||
{% endif %}
|
||||
submitAnswer( -1 );
|
||||
}
|
||||
};
|
||||
|
||||
@@ -254,22 +97,7 @@
|
||||
wsUtil.handleClose(e);
|
||||
};
|
||||
|
||||
function submitAnswer(optionIndex=0, has_options=true) {
|
||||
if (!has_options){
|
||||
answer = document.getElementById("singleanswer").value;
|
||||
|
||||
if (answer == ""){
|
||||
alert("Bitte eine Antwort eingeben!");
|
||||
return;
|
||||
}
|
||||
|
||||
gameSocket.send(JSON.stringify({
|
||||
type: 'submit_answer',
|
||||
participant_id: participantId,
|
||||
answer:answer,
|
||||
time_remaining: questionDuration
|
||||
}));
|
||||
}
|
||||
function submitAnswer(optionIndex) {
|
||||
if (hasAnswered) return;
|
||||
let input = ""; // Declare input here
|
||||
|
||||
@@ -315,42 +143,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function submitAnswerMap() {
|
||||
answer = document.getElementById("location").value;
|
||||
|
||||
if (answer == ""){
|
||||
alert("Bitte eine Antwort eingeben!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add grey overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.position = 'fixed';
|
||||
overlay.style.top = '0';
|
||||
overlay.style.left = '0';
|
||||
overlay.style.width = '100%';
|
||||
overlay.style.height = '100%';
|
||||
overlay.style.backgroundColor = 'rgba(128, 128, 128, 0.5)';
|
||||
overlay.style.zIndex = '1000';
|
||||
overlay.style.display = 'flex';
|
||||
overlay.style.justifyContent = 'center';
|
||||
overlay.style.alignItems = 'center';
|
||||
overlay.style.color = 'white';
|
||||
overlay.style.fontSize = '24px';
|
||||
overlay.style.fontWeight = 'bold';
|
||||
overlay.textContent = 'Antwort übermittelt';
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const now = Date.now();
|
||||
const timeRemaining = Math.max(0, questionDuration - (now - questionStartTime));
|
||||
|
||||
gameSocket.send(JSON.stringify({
|
||||
type: 'submit_answer',
|
||||
participant_id: participantId,
|
||||
answer: answer,
|
||||
time_remaining: timeRemaining
|
||||
}));
|
||||
}
|
||||
|
||||
// Timer
|
||||
function updateTimer() {
|
||||
@@ -366,13 +159,7 @@
|
||||
|
||||
if (remaining === 0 && !hasAnswered) {
|
||||
// Auto-submit timeout answer
|
||||
{% if question_data.type == "order" %}
|
||||
submitOrderAnswer();
|
||||
{% else %}
|
||||
submitAnswer(-1);
|
||||
{% endif %}
|
||||
|
||||
|
||||
} else if (remaining > 0) {
|
||||
requestAnimationFrame(updateTimer);
|
||||
}
|
||||
@@ -389,75 +176,5 @@
|
||||
|
||||
// Start timer when page loads
|
||||
updateTimer();
|
||||
|
||||
let initialOrder = [];
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const container = document.getElementById('sortable-options');
|
||||
if (container) {
|
||||
initialOrder = Array.from(container.children).map(div => div.dataset.index);
|
||||
|
||||
Sortable.create(container, {
|
||||
animation: 150,
|
||||
onUpdate: updateOrderNumbers // <--- Nummern aktualisieren nach Sortieren
|
||||
});
|
||||
|
||||
updateOrderNumbers(); // initial setzen
|
||||
}
|
||||
});
|
||||
|
||||
function updateOrderNumbers() {
|
||||
const container = document.getElementById('sortable-options');
|
||||
const children = container.children;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const label = children[i].querySelector('.order-number');
|
||||
if (label) {
|
||||
label.textContent = `${i + 1}.`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function submitOrderAnswer() {
|
||||
if (hasAnswered) return;
|
||||
const order_button=document.getElementById("send_order")
|
||||
order_button.disabled=true;
|
||||
order_button.classList.add('opacity-50');
|
||||
const orderedDivs = document.querySelectorAll('#sortable-options button');
|
||||
|
||||
const selectedOrder = Array.from(orderedDivs).map(div => div.dataset.value);
|
||||
|
||||
|
||||
const now = Date.now();
|
||||
let timeRemaining = Math.max(0, questionDuration - (now - questionStartTime));
|
||||
{% if request.session.mode == "learn" %}
|
||||
timeRemaining = questionDuration;
|
||||
{% endif %}
|
||||
|
||||
gameSocket.send(JSON.stringify({
|
||||
type: 'submit_answer',
|
||||
participant_id: participantId,
|
||||
answer: selectedOrder,
|
||||
time_remaining: timeRemaining
|
||||
}));
|
||||
|
||||
hasAnswered = true;
|
||||
}
|
||||
|
||||
function resetOrderSelection() {
|
||||
const container = document.getElementById('sortable-options');
|
||||
const divs = Array.from(container.children);
|
||||
const indexMap = {};
|
||||
divs.forEach(div => {
|
||||
indexMap[div.dataset.index] = div;
|
||||
});
|
||||
|
||||
// Sort children back to initial order
|
||||
container.innerHTML = '';
|
||||
initialOrder.forEach(i => {
|
||||
container.appendChild(indexMap[i]);
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -2,242 +2,35 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6 dark:bg-transparent dark:text-white ">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<h1 class="text-3xl font-bold mb-6">Ergebnisse</h1>
|
||||
{% if request.session.mode == "learn" or is_host %}
|
||||
<div class="mb-6">
|
||||
|
||||
<div class="mb-6">
|
||||
<h2 class="text-xl font-bold mb-4">Aktuelle Frage</h2>
|
||||
<p class="text-lg mb-2">{{ question_data.question }}</p>
|
||||
|
||||
{% if question_data.type == "map" %}
|
||||
<!-- Map Question Display -->
|
||||
<div class="mb-4">
|
||||
<div id="map" class="h-96 w-full rounded-lg border border-gray-300 dark:border-gray-600"></div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
|
||||
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
|
||||
crossorigin=""></script>
|
||||
<script>
|
||||
|
||||
const participantAnswers = [
|
||||
{% for participant in participants %}
|
||||
{% if participant.coordinate_answer %}
|
||||
"{{ participant.coordinate_answer }}",
|
||||
{% endif %}
|
||||
{% endfor %}];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Parse the target location from the string format "lat,lng"
|
||||
const targetCoords = '{{ question_data.target_location|escapejs }}'.split(',');
|
||||
let targetLat, targetLng;
|
||||
|
||||
if (targetCoords.length === 2) {
|
||||
targetLat = parseFloat(targetCoords[0].trim());
|
||||
targetLng = parseFloat(targetCoords[1].trim());
|
||||
|
||||
// Normalize longitude to be within -180 to 180
|
||||
targetLng = ((targetLng % 360) + 540) % 360 - 180;
|
||||
|
||||
// Clamp latitude to valid range (-90 to 90)
|
||||
targetLat = Math.max(-90, Math.min(90, targetLat));
|
||||
|
||||
console.log('Normalized coordinates:', { lat: targetLat, lng: targetLng });
|
||||
}
|
||||
|
||||
// Validate coordinates
|
||||
if (isNaN(targetLat) || isNaN(targetLng)) {
|
||||
console.warn('Invalid coordinates, hiding map');
|
||||
document.getElementById('map-container').style.display = 'none';
|
||||
return; // Exit the function early
|
||||
}
|
||||
|
||||
const targetLocation = [targetLat, targetLng];
|
||||
console.log('Using coordinates:', targetLocation);
|
||||
|
||||
// Initialize map centered on target location
|
||||
const map = L.map('map').setView(targetLocation, 7);
|
||||
|
||||
// Check for dark mode
|
||||
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
|
||||
// Choose appropriate map style
|
||||
const tileUrl = isDarkMode
|
||||
? 'https://{s}.basemaps.cartocdn.com/rastertiles/dark_all/{z}/{x}/{y}.png'
|
||||
: 'https://{s}.basemaps.cartocdn.com/rastertiles/light_all/{z}/{x}/{y}.png';
|
||||
|
||||
L.tileLayer(tileUrl, {
|
||||
maxZoom: 19,
|
||||
attribution: '© OpenStreetMap © CARTO',
|
||||
noWrap: true
|
||||
}).addTo(map);
|
||||
|
||||
participantAnswers.forEach(coordStr => {
|
||||
const coords = coordStr.split(',').map(Number);
|
||||
if (coords.length === 2 && !coords.some(isNaN)) {
|
||||
const marker = L.marker(coords).addTo(map).bindPopup('Antwort eines Teilnehmers');
|
||||
|
||||
// Linie vom Ziel zum Teilnehmermarker
|
||||
L.polyline([targetLocation, coords], {
|
||||
color: '#f59e0b',
|
||||
weight: 2, // Linienstärke
|
||||
dashArray: '5,5', // gestrichelte Linie
|
||||
opacity: 0.6
|
||||
}).addTo(map);
|
||||
}
|
||||
});
|
||||
|
||||
// Add target marker
|
||||
L.marker(targetLocation, {
|
||||
icon: L.divIcon({
|
||||
html: '🎯',
|
||||
className: 'text-3xl',
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 32],
|
||||
popupAnchor: [0, -32]
|
||||
})
|
||||
}).addTo(map)
|
||||
.bindPopup('Zielort')
|
||||
.openPopup();
|
||||
|
||||
// Add tolerance radius circle if available
|
||||
{% if question_data.tolerance_radius %}
|
||||
(function() {
|
||||
try {
|
||||
const toleranceRadius = parseFloat('{{ question_data.tolerance_radius|escapejs }}'.replace(',', '.'));
|
||||
if (!isNaN(toleranceRadius) && toleranceRadius > 0) {
|
||||
L.circle(targetLocation, {
|
||||
color: isDarkMode ? '#3b82f6' : '#2563eb',
|
||||
fillColor: isDarkMode ? '#3b82f6' : '#2563eb',
|
||||
fillOpacity: 0.2,
|
||||
radius: toleranceRadius
|
||||
}).addTo(map);
|
||||
console.log('Added tolerance radius:', toleranceRadius);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error adding tolerance radius:', e);
|
||||
}
|
||||
})();
|
||||
{% endif %}
|
||||
});
|
||||
</script>
|
||||
|
||||
{% elif question_data.type != "order" and question_data.type != "guess" %}
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
{% for option in question_data.options %}
|
||||
|
||||
<div id="option-box-{{ forloop.counter0 }}" class=" dark:bg-transparent relative p-4 rounded-lg overflow-auto break-words hyphens-auto {% if option.is_correct %} border-2 border-green-500{% else %}border-2 border-red-500{% endif %}">
|
||||
<div id="option-box-{{ forloop.counter0 }}" class="bg-gray-100 relative p-4 rounded-lg overflow-auto break-words hyphens-auto {% if option.is_correct %} border-2 border-green-500{% else %}border-2 border-red-500{% endif %}">
|
||||
|
||||
<div id="option-fill-{{ forloop.counter0 }}" class="absolute left-0 top-0 h-full {% if option.is_correct %}bg-green-300 {% else %}bg-red-300 {% endif %} opacity-50 z-0 transition-all duration-1500 " style="width: 0%;"></div>
|
||||
<div class="relative z-10">
|
||||
<p class="text-lg ">{{ option.value }}</p>
|
||||
<p class="text-gray-600 dark:text-gray-400" id="option-count-{{ forloop.counter0 }}">0 Antworten</p>
|
||||
<p class="text-lg">{{ option.value }}</p>
|
||||
<p class="text-gray-600" id="option-count-{{ forloop.counter0 }}">0 Antworten</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% elif question_data.type == "guess" %}
|
||||
<div id="option-box-0" class="relative p-4 rounded-lg border-2 border-green-500 overflow-auto">
|
||||
<div id="option-fill-0" class="absolute left-0 top-0 h-full bg-green-300 opacity-50 z-0 transition-all duration-1500" style="width: 0%;"></div>
|
||||
<div class="relative z-10">
|
||||
<p class="text-lg">Richtige Anwort: {% for option in question_data.options%} {{ option.value }}{% endfor %}</p>
|
||||
<p class="text-sm">(prozentuale Punktevergabe ab Genauigkeit von {{ question_data.tolerance }}%)</p>
|
||||
|
||||
<p class="text-gray-600" id="option-count-0">0 Antworten</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="option-box-1" class="relative p-4 rounded-lg border-2 border-red-500 overflow-auto mt-2">
|
||||
<div id="option-fill-1" class="absolute left-0 top-0 h-full bg-red-300 opacity-50 z-0 transition-all duration-1500" style="width: 0%;"></div>
|
||||
<div class="relative z-10">
|
||||
<p class="text-lg">Schätzung zu ungenau</p>
|
||||
<p class="text-gray-600" id="option-count-1">0 Antworten</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% else %}
|
||||
<div id="option-box-0" class="relative p-4 rounded-lg border-2 border-green-500 overflow-auto">
|
||||
<div id="option-fill-0" class="absolute left-0 top-0 h-full bg-green-300 opacity-50 z-0 transition-all duration-1500" style="width: 0%;"></div>
|
||||
<div class="relative z-10">
|
||||
<p class="text-lg">Richtige Reihenfolge</p>
|
||||
<ul class="">
|
||||
{% for option in question_data.options|dictsort:"order" %}
|
||||
<li class="text-lg"> {{ forloop.counter }}. <span class="">{{ option.value }}</span> </li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="text-gray-600" id="option-count-0">0 Antworten</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="option-box-1" class="relative p-4 rounded-lg border-2 border-red-500 overflow-auto mt-2">
|
||||
<div id="option-fill-1" class="absolute left-0 top-0 h-full bg-red-300 opacity-50 z-0 transition-all duration-1500" style="width: 0%;"></div>
|
||||
<div class="relative z-10">
|
||||
<p class="text-lg">Falsche Reihenfolge</p>
|
||||
<p class="text-gray-600" id="option-count-1">0 Antworten</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% else %}
|
||||
|
||||
|
||||
<div class="flex flex-col items-center justify-center text-center pt-4">
|
||||
{% if participant.last_answer_correct %}
|
||||
<!-- Richtig-Symbol -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor"
|
||||
class="text-green-500 size-40">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
|
||||
<!-- Feedback-Text -->
|
||||
<div id="antwort-richtig"
|
||||
class="pt-3 text-gray-600 dark:text-white font-bold max-w-xs md:max-w-sm">
|
||||
Hier erscheint gleich dein Feedback...
|
||||
</div>
|
||||
|
||||
{% elif participant.last_answer_correct == False %}
|
||||
<!-- Falsch-Symbol -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor"
|
||||
class="text-red-500 size-40">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
|
||||
<!-- Feedback-Text -->
|
||||
<div id="antwort-falsch"
|
||||
class="pt-3 text-gray-600 dark:text-white font-bold max-w-xs md:max-w-sm">
|
||||
Hier erscheint gleich dein Feedback...
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-blue-600 font-bold text-md">+{{ participant.last_score }} Punkte</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
{% if request.session.mode == "learn" %}
|
||||
<h2 class="text-xl font-bold mb-4">Punktestand</h2>
|
||||
{% endif %}
|
||||
|
||||
<div id="scoreboard" class="leading-relaxed">
|
||||
{% for participant in participants %}
|
||||
{% if request.session.mode == "learn" %}
|
||||
{% if participant == my_participant or is_host %}
|
||||
<div class="dark:bg-transparent mt-2 shadow-sm p-4 bg-gray-100 rounded-lg border-2 border {% if forloop.counter <= 3 %}border-yellow-500{% else %}border-transparent{% endif %}">
|
||||
<div class="mt-2 shadow-sm p-4 bg-gray-100 rounded-lg border-2 border {% if forloop.counter <= 3 %}border-yellow-500{% else %}border-transparent{% endif %}">
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<div class="text-lg font-bold flex items-center gap-2">
|
||||
{% if forloop.counter == 1 %}
|
||||
@@ -249,115 +42,16 @@
|
||||
{% else %}
|
||||
{{ forloop.counter }}.
|
||||
{% endif %}
|
||||
<span class="display_name text-gray-600 font-black text-2xl dark:text-white " id="display_name-{{ forloop.counter0 }}">{{ participant.display_name }}</span>
|
||||
<span class="display_name text-gray-600 font-black text-2xl " id="display_name-{{ forloop.counter0 }}">{{ participant.display_name }}</span>
|
||||
</div class="flex flex-col items-start">
|
||||
|
||||
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="dark:text-white text-gray-600 font-bold show-score" id="score-{{ forloop.counter0 }}">{{ participant.score }} Punkte</p>
|
||||
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="text-gray-600 font-bold show-score" id="score-{{ forloop.counter0 }}">{{ participant.score }} Punkte</p>
|
||||
{% if participant == my_participant or request.session.mode == "learn"%}
|
||||
<p class="text-blue-600 font-bold text-sm">+{{ participant.last_score }} Punkte</p>
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
{% if participant == my_participant or request.session.mode == "learn" %}
|
||||
{% if participant.last_answer_correct %}
|
||||
|
||||
<div class="mt-1 flex justify-center border-t-2 border-gray-300 gap-1">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="pt-2 text-center text-green-500 font-black size-10">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
|
||||
|
||||
<div class="dark:text-white pt-3 text-center text-gray-600 font-bold" id="antwort-richtig">
|
||||
Hier erscheint gleich dein Feedback...
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% elif participant.last_answer_correct == False %}
|
||||
|
||||
|
||||
<div class="mt-1 flex justify-center border-t-2 border-gray-300 gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="pt-2 text-center text-red-500 font-black size-10">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
|
||||
|
||||
<div class="dark:text-white pt-3 text-center text-gray-600 font-bold" id="antwort-falsch">
|
||||
Hier erscheint gleich dein Feedback...
|
||||
</div>
|
||||
<div class="flex justify-center border-t-2 border-gray-500 gap-1">
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<script>
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if is_host %}
|
||||
{% if is_last_question %}
|
||||
{% if is_last_question and request.session.mode != "learn" %}
|
||||
<button id="winner-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||
Siegerpodest
|
||||
</button>
|
||||
|
||||
{% else %}
|
||||
<button id="finish-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||
Quiz beenden
|
||||
</button>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if request.session.mode == "learn" %}
|
||||
<button id="next-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||
Nächste Frage
|
||||
|
||||
</button>
|
||||
{% else %}
|
||||
<button id="scoreboard-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||
Punkteübersicht
|
||||
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% if is_last_question %}
|
||||
|
||||
{% endif %}
|
||||
{% include 'play/game/common_scripts.html' %}
|
||||
{% if is_host %}
|
||||
{% include 'play/game/sound.html' %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
function triggerConfetti() {
|
||||
const end = Date.now() + 30 * 1000;
|
||||
(function frame() {
|
||||
confetti({ particleCount: 2, angle: 60, spread: 55, origin: { x: 0 } });
|
||||
confetti({ particleCount: 2, angle: 120, spread: 55, origin: { x: 1 } });
|
||||
if (Date.now() < end) requestAnimationFrame(frame);
|
||||
})();
|
||||
}
|
||||
async function start() {
|
||||
|
||||
elements=document.getElementsByClassName("show-score");
|
||||
@@ -393,17 +87,153 @@
|
||||
|
||||
|
||||
{% if is_last_question %}
|
||||
start();
|
||||
bounce();
|
||||
{% if request.session.mode == "learn" %}
|
||||
triggerConfetti();
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% else %}
|
||||
start();
|
||||
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
{% if participant == my_participant or request.session.mode == "learn" %}
|
||||
{% if participant.last_answer_correct %}
|
||||
|
||||
<div class="mt-1 flex justify-center border-t-2 border-gray-300 gap-1">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="pt-2 text-center text-green-500 font-black size-10">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
|
||||
|
||||
<div class=" pt-3 text-center text-gray-600 font-bold" id="antwort-richtig">
|
||||
Hier erscheint gleich dein Feedback...
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% elif participant.last_answer_correct == False %}
|
||||
|
||||
|
||||
<div class="mt-1 flex justify-center border-t-2 border-gray-300 gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="pt-2 text-center text-red-500 font-black size-10">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
|
||||
|
||||
<div class=" pt-3 text-center text-gray-600 font-bold" id="antwort-falsch">
|
||||
Hier erscheint gleich dein Feedback...
|
||||
</div>
|
||||
<div class="flex justify-center border-t-2 border-gray-500 gap-1">
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if is_host %}
|
||||
{% if is_last_question %}
|
||||
<button id="finish-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||
Quiz beenden
|
||||
</button>
|
||||
{% else %}
|
||||
<button id="next-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||
Nächste Frage
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% if is_last_question %}
|
||||
|
||||
<script>
|
||||
// Hilfsfunktion für Pausen
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
const items = document.querySelectorAll('#scoreboard div');
|
||||
const top3 = Array.from(items).slice(0, 3); // Nur die ersten 3 animieren
|
||||
|
||||
// Sofort Startzustände für Top3 setzen, die anderen sofort sichtbar
|
||||
items.forEach((el, index) => {
|
||||
if (index >= 3) {
|
||||
el.style.opacity = '1';
|
||||
el.style.transform = 'translateY(0)';
|
||||
} else {
|
||||
el.style.opacity = '0';
|
||||
el.style.transform = 'translateY(20px)';
|
||||
}
|
||||
});
|
||||
try{
|
||||
document.getElementById("finish-button").style.display = 'none';}
|
||||
catch{}
|
||||
|
||||
// Warten bevor Animation startet
|
||||
await sleep(500); // <<< hier die Pause
|
||||
|
||||
|
||||
// Transition-Eigenschaft setzen nach Render-Zyklus
|
||||
requestAnimationFrame(() => {
|
||||
top3.forEach(el => {
|
||||
el.style.transition = 'all 0.8s ease';
|
||||
});
|
||||
|
||||
// Animation für Top 3 starten – von Platz 3 zu 1
|
||||
top3.reverse().forEach((el, index) => {
|
||||
setTimeout(() => {
|
||||
el.style.opacity = '1';
|
||||
el.style.transform = 'translateY(0)';
|
||||
|
||||
if (index === top3.length - 1) {
|
||||
triggerConfetti();
|
||||
try{
|
||||
|
||||
document.getElementById("finish-button").style.display = 'block';}
|
||||
catch{}
|
||||
|
||||
}
|
||||
}, index * 1000); // Zeitversetzt aufdecken
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function triggerConfetti() {
|
||||
const end = Date.now() + 15 * 1000;
|
||||
(function frame() {
|
||||
confetti({ particleCount: 2, angle: 60, spread: 55, origin: { x: 0 } });
|
||||
confetti({ particleCount: 2, angle: 120, spread: 55, origin: { x: 1 } });
|
||||
if (Date.now() < end) requestAnimationFrame(frame);
|
||||
})();
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% include 'play/game/common_scripts.html' %}
|
||||
{% if is_host %}
|
||||
{% include 'play/game/sound.html' %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
const richtigeAntworten = [
|
||||
"Deine Antwort ist nicht falsch. Sie ist richtig!",
|
||||
"Ja, du kannst es!",
|
||||
@@ -478,10 +308,9 @@ if (antwortRichtig) {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.type === 'game_state_update' &&
|
||||
data.redirect_url &&
|
||||
(data.action === 'next_question' || data.action === 'finish_game' || data.action === 'show_winner_podest'|| data.action === 'show_scoreboard')) {
|
||||
(data.action === 'next_question' || data.action === 'finish_game')) {
|
||||
window.location.href = data.redirect_url;
|
||||
}
|
||||
else if (data.type === 'answer_stats') {
|
||||
} else if (data.type === 'answer_stats') {
|
||||
updateAnswerStats(data.stats);
|
||||
}
|
||||
};
|
||||
@@ -507,30 +336,7 @@ if (antwortRichtig) {
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
var podestButton = document.getElementById('winner-button');
|
||||
if (podestButton) {
|
||||
podestButton.addEventListener('click', function() {
|
||||
gameSocket.send(JSON.stringify({
|
||||
'type': 'winner_podest',
|
||||
'host_id': hostId
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
{% else %}
|
||||
|
||||
var scoreboardButton = document.getElementById('scoreboard-button');
|
||||
if (scoreboardButton) {
|
||||
scoreboardButton.addEventListener('click', function() {
|
||||
gameSocket.send(JSON.stringify({
|
||||
'type': 'scoreboard',
|
||||
'host_id': hostId
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var nextButton = document.getElementById('next-button');
|
||||
if (nextButton) {
|
||||
nextButton.addEventListener('click', function() {
|
||||
|
||||
@@ -1,306 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6 dark:bg-transparent dark:text-white">
|
||||
<h1 class="text-3xl font-bold mb-6">Ergebnisse</h1>
|
||||
|
||||
|
||||
|
||||
<div class="mb-6">
|
||||
{% if request.session.mode != "learn" %}
|
||||
<h2 class="text-xl font-bold mb-4">Punktestand</h2>
|
||||
{% endif %}
|
||||
|
||||
<div id="scoreboard" class="leading-relaxed">
|
||||
{% for participant in participants %}
|
||||
{% if request.session.mode != "learn" %}
|
||||
{% if participant == my_participant or is_host %}
|
||||
<div class="dark:bg-transparent mt-2 shadow-sm p-4 bg-gray-100 rounded-lg border-2 border
|
||||
{% if forloop.counter <= 3 %}
|
||||
{% if forloop.counter == 1 %}border-yellow-400{% endif %}
|
||||
{% if forloop.counter == 2 %}border-gray-400{% endif %}
|
||||
{% if forloop.counter == 3 %}border-orange-400{% endif %}
|
||||
{% else %}
|
||||
border-transparent{% endif %}">
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<div class="text-4xl font-bold flex items-center gap-2">
|
||||
{% if forloop.counter == 1 %}
|
||||
🥇
|
||||
{% elif forloop.counter == 2 %}
|
||||
🥈
|
||||
{% elif forloop.counter == 3 %}
|
||||
🥉
|
||||
{% else %}
|
||||
{{ forloop.counter }}.
|
||||
{% endif %}
|
||||
<span class="display_name text-gray-600 font-black text-3xl dark:text-white " id="display_name-{{ forloop.counter0 }}">{{ participant.display_name }}</span>
|
||||
</div>
|
||||
|
||||
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="dark:text-white text-gray-600 font-bold show-score text-xl" id="score-{{ forloop.counter0 }}">{{ participant.score }} Punkte</p>
|
||||
{% if participant == my_participant or request.session.mode == "learn"%}
|
||||
<p class="text-blue-600 font-bold text-md">+{{ participant.last_score }} Punkte</p>
|
||||
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if is_host %}
|
||||
{% if is_last_question %}
|
||||
{% if is_last_question and request.session.mode != "learn" %}
|
||||
<button id="winner-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||
Siegerpodest
|
||||
</button>
|
||||
|
||||
{% else %}
|
||||
<button id="finish-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||
Quiz beenden
|
||||
</button>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
|
||||
<button id="next-button" class="mt-4 w-full p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||
Nächste Frage
|
||||
|
||||
</button>
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% if is_last_question %}
|
||||
|
||||
{% endif %}
|
||||
{% include 'play/game/common_scripts.html' %}
|
||||
{% if is_host %}
|
||||
{% include 'play/game/sound.html' %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
async function start() {
|
||||
|
||||
elements=document.getElementsByClassName("show-score");
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const el = elements[i];
|
||||
|
||||
let counter=Number(el.getAttribute('data-score'))-Number(el.getAttribute('data-last-score'));
|
||||
let goal=Number(el.getAttribute('data-score'));
|
||||
let interval= setInterval(function(){
|
||||
el.innerHTML = counter + " Punkte";
|
||||
|
||||
|
||||
if(counter<=goal){
|
||||
el.innerHTML = counter + " Punkte";
|
||||
counter+=8;
|
||||
|
||||
}
|
||||
else{
|
||||
|
||||
el.innerHTML = goal + " Punkte";
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
}, 20);}}
|
||||
|
||||
function bounce(){
|
||||
|
||||
const names = document.getElementsByClassName("display_name");
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
names[i].classList.add("animate-bounce");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{% if is_last_question %}
|
||||
start();
|
||||
bounce();
|
||||
|
||||
|
||||
{% else %}
|
||||
start();
|
||||
{% endif %}
|
||||
|
||||
const richtigeAntworten = [
|
||||
"Deine Antwort ist nicht falsch. Sie ist richtig!",
|
||||
"Ja, du kannst es!",
|
||||
"So kann es weiter gehen.",
|
||||
"Ja, deine Antwort ist korrekt. Was soll ich dazu noch sagen? SUPER!",
|
||||
"SUPER GUT.",
|
||||
"Volltreffer!",
|
||||
"Bravo! Das sitzt!",
|
||||
"Na bitte, geht doch!",
|
||||
"Schön. Einfach schön.",
|
||||
"Exzellent kombiniert, Sherlock!",
|
||||
"Punkt für dich!",
|
||||
"Das war meisterhaft.",
|
||||
"Korrekt - du scheinst das zu können!",
|
||||
"So sieht Erfolg aus!",
|
||||
"Glänzend gelöst!",
|
||||
"Das war keine Glückssache - das war Können!",
|
||||
"Treffer, versenkt!",
|
||||
"Du hast es drauf!",
|
||||
"Da merkt man: Profi am Werk.",
|
||||
"Richtige Antwort - dafür gibt es viele Punkte!",
|
||||
"Weiter so!",
|
||||
"Wow - das war richtig gut!"
|
||||
];
|
||||
|
||||
const falscheAntworten = [
|
||||
"Oops! War wohl nicht dein Tag.",
|
||||
"Knapp daneben ist auch vorbei!",
|
||||
"Das war... kreativ!",
|
||||
"Wenn man schon alles wüsste, wäre das Quiz doch sinnlos.",
|
||||
"Deine Antwort war einzigartig - leider falsch!",
|
||||
"Na gut, wenigstens hast du es probiert.",
|
||||
"Das war eine interessante Theorie...",
|
||||
"Ein Genie irrt sich auch mal.",
|
||||
"Eine gute Antwort - aber nicht auf diese Frage.",
|
||||
"Das ist leider falsch. Mehr gibt es nicht zu sagen.",
|
||||
"Gute Idee! Aber leider nicht richtig.",
|
||||
"Oh weh! Ein Fehler. Naja, einer ist keiner.",
|
||||
"Die Antwort klang richtig, sie ist es aber nicht.",
|
||||
"Interessanter Ansatz. Nicht hilfreich, aber interessant.",
|
||||
"Nenn das lieber „alternative Fakten“.",
|
||||
"Du hast die richtige Antwort doch gewusst: 'Die Seite hat nur nicht geladen.'",
|
||||
"Jeder fängt mal klein an!",
|
||||
"Kopf hoch - weiter geht's!",
|
||||
"Nobody is perfect!",
|
||||
"Du schaffst das - vielleicht ab jetzt.",
|
||||
"Falsche Antwort, aber guter Wille!",
|
||||
"Übung macht den Quiz-Meister.",
|
||||
"Beim nächsten Mal triffst du!",
|
||||
"Das war nur zum Aufwärmen, oder?",
|
||||
"Kein Problem - weiter geht's.",
|
||||
"Gute Frage - aber falsche Antwort."
|
||||
];
|
||||
|
||||
|
||||
const antwortRichtig = document.getElementById("antwort-richtig");
|
||||
const antwortFalsch = document.getElementById("antwort-falsch");
|
||||
|
||||
if (antwortRichtig) {
|
||||
antwortRichtig.textContent = richtigeAntworten[Math.floor(Math.random() * richtigeAntworten.length)];
|
||||
} else if (antwortFalsch) {
|
||||
antwortFalsch.textContent = falscheAntworten[Math.floor(Math.random() * falscheAntworten.length)];
|
||||
}
|
||||
const joinCode = '{{ quiz_game.join_code }}';
|
||||
const hostId = '{{ host_id }}';
|
||||
const wsScheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const gameSocket = new WebSocket(
|
||||
`${wsScheme}://${window.location.host}/ws/game/${joinCode}/`
|
||||
);
|
||||
|
||||
gameSocket.onmessage = function(e) {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.type === 'game_state_update' &&
|
||||
data.redirect_url &&
|
||||
(data.action === 'next_question' || data.action === 'finish_game' || data.action === 'show_winner_podest'|| data.action === 'show_scoreboard')) {
|
||||
window.location.href = data.redirect_url;
|
||||
}
|
||||
else if (data.type === 'answer_stats') {
|
||||
updateAnswerStats(data.stats);
|
||||
}
|
||||
};
|
||||
|
||||
gameSocket.onclose = function(e) {
|
||||
// Normal closure (code 1000) oder Navigation zu einer anderen Seite (code 1001)
|
||||
if (e.code === 1000 || e.code === 1001) {
|
||||
console.log('Game socket closed normally');
|
||||
} else {
|
||||
console.error('Game socket closed unexpectedly', e.code);
|
||||
}
|
||||
};
|
||||
|
||||
{% if is_host %}
|
||||
// Add click handlers for host buttons
|
||||
{% if is_last_question %}
|
||||
var finishButton = document.getElementById('finish-button');
|
||||
if (finishButton) {
|
||||
finishButton.addEventListener('click', function() {
|
||||
gameSocket.send(JSON.stringify({
|
||||
'type': 'finish_game',
|
||||
'host_id': hostId
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
var podestButton = document.getElementById('winner-button');
|
||||
if (podestButton) {
|
||||
podestButton.addEventListener('click', function() {
|
||||
gameSocket.send(JSON.stringify({
|
||||
'type': 'winner_podest',
|
||||
'host_id': hostId
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
{% else %}
|
||||
|
||||
var scoreboardButton = document.getElementById('scoreboard-button');
|
||||
if (scoreboardButton) {
|
||||
scoreboardButton.addEventListener('click', function() {
|
||||
gameSocket.send(JSON.stringify({
|
||||
'type': 'scoreboard',
|
||||
'host_id': hostId
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var nextButton = document.getElementById('next-button');
|
||||
if (nextButton) {
|
||||
nextButton.addEventListener('click', function() {
|
||||
gameSocket.send(JSON.stringify({
|
||||
'type': 'next_question',
|
||||
'host_id': hostId
|
||||
}));
|
||||
}, { once: true });
|
||||
}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
function updateAnswerStats(stats) {
|
||||
const total = Object.values(stats).reduce((a, b) => a + b, 0) || 1;
|
||||
|
||||
for (var optionIndex in stats) {
|
||||
if (stats.hasOwnProperty(optionIndex)) {
|
||||
var countElement = document.getElementById('option-count-' + optionIndex);
|
||||
var fillElement = document.getElementById('option-fill-' + optionIndex);
|
||||
|
||||
if (countElement) {
|
||||
countElement.textContent = stats[optionIndex] + ' Antwort(en)';
|
||||
}
|
||||
|
||||
if (fillElement) {
|
||||
const percentage = Math.round((stats[optionIndex] / total) * 100);
|
||||
fillElement.style.width = percentage + '%';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Request answer stats when page loads
|
||||
gameSocket.onopen = function(e) {
|
||||
gameSocket.send(JSON.stringify({
|
||||
'type': 'get_answer_stats'
|
||||
}));
|
||||
};
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,594 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if is_last_question and is_host %}
|
||||
<!-- Siegerpodest -->
|
||||
<div class="min-h-screen">
|
||||
<h2 class="w-full flex justify-center dark:text-white font-bold text-2xl ">🏆 Siegerpodest</h2>
|
||||
<h4 class="w-full flex justify-center dark:text-white font-bold">Wer ist hier der Quiz-Master? </h4>
|
||||
|
||||
{% with participant=participants.0 %}
|
||||
<div class="w-full flex justify-center">
|
||||
<h1 id="winner-text" class="bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-600 bg-clip-text text-transparent font-bold hidden animate-ping px-2 max-w-screen-sm text-center break-words">
|
||||
Glückwunsch, {{ participant.display_name }}!
|
||||
</h1>
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div id="podest-container" class="w-full absolute bottom-0 flex justify-center items-end gap-2 md:gap-4 mb-2 dark:text-white">
|
||||
|
||||
|
||||
<!-- Platz 2 links -->
|
||||
{% with participant=participants.1 %}
|
||||
<div class="flex flex-col shrink items-center break-words max-w-[6rem] md:max-w-[8rem] lg:max-w-[10rem] overflow-x-auto pt-10">
|
||||
<span class="display_name p-2 text-gray-600 font-black text-xl md:text-2xl lg:text-3xl opacity-0 transition-opacity duration-1000 dark:text-white hyphens-auto" id="display_name-1">{{ participant.display_name }}</span>
|
||||
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="opacity-0 transition-opacity duration-1000 dark:text-white font-bold show-score text-sm md:text-lg lg:text-xl text-gray-400" id="score-1">{{ participant.score }}</p>
|
||||
<div data-final-height="33" id="podium-1" class="border-2 border-gray-200 transition-all duration-1000 ease-out overflow-hidden podium h-24 md:h-[12rem] lg:h-[16rem] w-24 md:w-32 lg:w-40 rounded-t-md font-bold text-center text-base shadow-lg bg-gradient-to-b from-gray-400 via-gray-300 to-gray-300 flex flex-col items-center justify-center text-black dark:text-white cursor-pointer leading-tight">
|
||||
<span class="size-10 md:size-18 rounded-full bg-white text-black text-center flex items-center justify-center font-bold text-3xl md:text-5xl lg:text-6xl">🥈</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
||||
<!-- Platz 1 mitte -->
|
||||
{% with participant=participants.0 %}
|
||||
<div class="flex flex-col shrink items-center break-words max-w-[6rem] md:max-w-[8rem] lg:max-w-[10rem] overflow-x-auto pt-10">
|
||||
<div id="resultFirst"></div>
|
||||
<span class="display_name p-2 text-gray-600 font-black text-xl md:text-3xl lg:text-4xl opacity-0 transition-opacity duration-1000 bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-600 bg-clip-text text-transparent hyphens-auto" id="display_name-0">{{ participant.display_name }}</span>
|
||||
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="opacity-0 transition-opacity duration-1000 dark:text-white text-yellow-500 font-bold show-score text-sm md:text-lg lg:text-xl" id="score-0">{{ participant.score }}</p>
|
||||
<div data-final-height="45" id="podium-0" class="border-2 border-yellow-500 transition-all duration-1000 ease-out overflow-hidden podium h-32 md:h-[16rem] lg:h-[22rem] w-24 md:w-32 lg:w-40 rounded-t-md font-bold text-center text-base shadow-lg bg-gradient-to-b from-yellow-400 via-yellow-300 to-yellow-600 flex flex-col items-center justify-center text-black dark:text-white cursor-pointer leading-tight">
|
||||
<span id="firstMedal" class="size-12 md:size-22 rounded-full bg-white text-black text-center flex items-center justify-center font-bold text-4xl md:text-6xl lg:text-7xl">🥇</span><br>
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
||||
<!-- Platz 3 rechts -->
|
||||
{% with participant=participants.2 %}
|
||||
<div class="flex flex-col shrink items-center break-words max-w-[6rem] md:max-w-[8rem] lg:max-w-[10rem] overflow-x-auto pt-10">
|
||||
<span class="display_name p-2 text-gray-600 font-black text-xl md:text-2xl lg:text-3xl dark:text-white opacity-0 transition-opacity duration-1000 hyphens-auto" id="display_name-2">{{ participant.display_name }}</span>
|
||||
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="opacity-0 transition-opacity duration-1000 dark:text-white text-orange-500 font-bold show-score text-sm md:text-lg lg:text-xl" id="score-2">{{ participant.score }}</p>
|
||||
<div data-final-height="25" id="podium-2" class="border-2 border-orange-500 transition-all duration-1000 ease-out overflow-hidden podium h-20 md:h-[10rem] lg:h-[14rem] w-24 md:w-32 lg:w-40 rounded-t-md font-bold text-center text-base shadow-lg bg-gradient-to-b from-orange-500 via-orange-400 to-orange-700 flex flex-col items-center justify-center text-black dark:text-white cursor-pointer leading-tight">
|
||||
<span class="size-10 md:size-18 rounded-full bg-white text-black text-center flex items-center justify-center font-bold text-3xl md:text-5xl lg:text-6xl">🥉</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div id="thanks" class="hidden -mt-10">
|
||||
<div class="w-full flex justify-center">
|
||||
<h1 class="bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-600 bg-clip-text text-transparent font-bold px-2 max-w-screen-sm text-center break-words">
|
||||
Vielen Dank fürs Spielen!
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<button id="finish-button" class=" w-3/4 md:w-1/2 bottom-0 left-0 p-3 rounded-full bg-blue-600 text-white font-bold hover:bg-blue-700 transition-colors duration-200">
|
||||
Quiz beenden
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
|
||||
|
||||
<div class="container mx-auto px-4">
|
||||
|
||||
|
||||
{% with participant=participants.0 %}
|
||||
<div class="w-full flex justify-center">
|
||||
<h1 id="winner-text" class="bg-gradient-to-r from-yellow-400 via-yellow-300 to-yellow-600 bg-clip-text text-transparent font-bold hidden animate-ping px-2 max-w-screen-sm text-center break-words">
|
||||
Glückwunsch, {{ participant.display_name }}!
|
||||
</h1>
|
||||
</div>
|
||||
{% endwith %}
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6 dark:bg-transparent dark:text-white">
|
||||
<h1 id="result-text" class="text-3xl w-full flex justify-center dark:text-white font-bold">🏆 Wer ist hier der Quiz-Master?</h1>
|
||||
|
||||
<h4 id="info" class="mt-4 w-full flex justify-center dark:text-white font-bold">Nach der Siegerehrung siehst du deine Platzierung!</h4>
|
||||
|
||||
|
||||
|
||||
<div id="participant-score" class="mb-6 ">
|
||||
|
||||
|
||||
<div id="scoreboard" class="leading-relaxed">
|
||||
{% for participant in participants %}
|
||||
{% if request.session.mode != "learn" %}
|
||||
{% if participant == my_participant or is_host %}
|
||||
<div id="player_score" class="hidden dark:bg-transparent mt-2 shadow-sm p-4 bg-gray-100 rounded-lg border-2 border {% if forloop.counter <= 3 %}
|
||||
{% if forloop.counter == 1 %}border-yellow-400{% endif %}
|
||||
{% if forloop.counter == 2 %}border-gray-400{% endif %}
|
||||
{% if forloop.counter == 3 %}border-orange-400{% endif %}
|
||||
{% else %}
|
||||
border-transparent{% endif %}">
|
||||
<div class="flex flex-col items-start gap-1">
|
||||
<div class="text-4xl font-bold flex items-center gap-2">
|
||||
{% if forloop.counter == 1 %}
|
||||
🥇
|
||||
{% elif forloop.counter == 2 %}
|
||||
🥈
|
||||
{% elif forloop.counter == 3 %}
|
||||
🥉
|
||||
{% else %}
|
||||
{{ forloop.counter }}.
|
||||
{% endif %}
|
||||
<span class="display_name text-gray-600 font-black text-3xl dark:text-white " id="display_name-{{ forloop.counter0 }}">{{ participant.display_name }}</span>
|
||||
</div class="flex flex-col items-start">
|
||||
|
||||
<p data-score="{{ participant.score }}" data-last-score="{{ participant.last_score }}" class="dark:text-white text-gray-600 font-bold show-score text-xl" id="score-{{ forloop.counter0 }}">{{ participant.score }} Punkte</p>
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
.podium {
|
||||
height: 0;
|
||||
|
||||
}
|
||||
.spotlight-wrapper::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -800px; /* Start oben außerhalb */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 15rem;
|
||||
height: 700px;
|
||||
pointer-events: none;
|
||||
|
||||
background: radial-gradient(
|
||||
ellipse 60% 100% at center bottom,
|
||||
rgba(254, 254, 178, 0.9) 0%,
|
||||
rgba(252, 236, 132, 0.7) 25%,
|
||||
rgba(255, 230, 130, 0.5) 55%,
|
||||
rgba(255, 210, 100, 0.1) 85%,
|
||||
rgba(255, 180, 70, 0) 100%
|
||||
);
|
||||
|
||||
filter: blur(24px) brightness(1.2);
|
||||
opacity: 0; /* Start unsichtbar */
|
||||
animation: slideInOutSpotlight 6s ease forwards , fadeOutSpotlight 6s 6s ease forwards; /* Dauer anpassen */
|
||||
z-index: -1;
|
||||
|
||||
box-shadow: 0 0 60px 30px rgba(255, 240, 150, 0.15);
|
||||
}
|
||||
|
||||
@keyframes slideInOutSpotlight {
|
||||
0% {
|
||||
top: -800px;
|
||||
opacity: 0;
|
||||
}
|
||||
20% {
|
||||
top: -640px; /* Endposition */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: -640px;
|
||||
opacity: 1;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes fadeOutSpotlight {
|
||||
0% {
|
||||
top: -640px;
|
||||
opacity: 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
100% {
|
||||
top: -640px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
{% block extra_js %}
|
||||
{% if is_last_question %}
|
||||
|
||||
<script>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
const animationDuration = 1500; // Dauer der Wachstumsanimation
|
||||
const waitTime=3000;
|
||||
const podiums = [
|
||||
|
||||
{ id: 'podium-2', delay: 2000 }, // immer vorheriges von Bedeutung
|
||||
{ id: 'podium-1', delay: 2000 },
|
||||
{ id: 'podium-0', delay: 0 },
|
||||
];
|
||||
|
||||
|
||||
let totalDelay = 0;
|
||||
|
||||
podiums.forEach(({ delay }) => {
|
||||
totalDelay += delay + waitTime;
|
||||
});
|
||||
const totalAnimationTime = totalDelay + animationDuration;
|
||||
setTimeout(() => {
|
||||
show_score();
|
||||
}, totalAnimationTime);
|
||||
|
||||
totalDelay = 0;
|
||||
|
||||
|
||||
|
||||
podiums.forEach(({ id, delay }) => {
|
||||
|
||||
const podium = document.getElementById(id);
|
||||
if (!podium) return;
|
||||
|
||||
const finalHeight = parseInt(podium.dataset.finalHeight) || 160;
|
||||
podium.style.transition = `height ${animationDuration}ms ease-in-out`;
|
||||
podium.style.height = '0px';
|
||||
|
||||
// Wachstum starten nach gesamter Wartezeit
|
||||
setTimeout(() => {
|
||||
podium.style.height = `${finalHeight}vh`;
|
||||
}, totalDelay);
|
||||
|
||||
// Name & Score anzeigen nach Balken-Animation
|
||||
setTimeout(() => {
|
||||
|
||||
if (id === 'podium-0') {
|
||||
let name = document.getElementById(`display_name-0`);
|
||||
let score = document.getElementById(`score-0`);
|
||||
let winnerText= document.getElementById(`winner-text`);
|
||||
|
||||
let thanks= document.getElementById(`thanks`);
|
||||
// 1. Lichtkegel anzeigen
|
||||
if (resultFirst) {
|
||||
resultFirst.classList.add('spotlight-wrapper');
|
||||
score.classList.add('opacity-0');
|
||||
name.classList.add('opacity-0');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 2. Nach kurzer Wartezeit Name & Score anzeigen
|
||||
setTimeout(() => {
|
||||
|
||||
if (name) {
|
||||
name.classList.add('opacity-100', 'animate-bounce','spotlight-text'); // z. B. sichtbar + Effekt
|
||||
}
|
||||
|
||||
if (winnerText) {
|
||||
winnerText.classList.remove('hidden');
|
||||
}
|
||||
|
||||
if (thanks) {
|
||||
thanks.classList.remove('hidden');
|
||||
}
|
||||
|
||||
if (score) {
|
||||
score.classList.add('opacity-100'); // z. B. sichtbar + Effekt
|
||||
}
|
||||
triggerConfetti();
|
||||
|
||||
}, 2000);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
document.body.style.overflow = 'auto';
|
||||
return;
|
||||
}
|
||||
|
||||
const index = id.split('-')[1];
|
||||
const name = document.getElementById(`display_name-${index}`);
|
||||
const score = document.getElementById(`score-${index}`);
|
||||
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
if (name && id !== 'podium-0') setTimeout(() => {
|
||||
name.classList.add('opacity-100');
|
||||
name.classList.add('animate-bounce');
|
||||
|
||||
if (score) score.classList.add('opacity-100');
|
||||
}, waitTime);
|
||||
|
||||
}, totalDelay + animationDuration);
|
||||
|
||||
|
||||
totalDelay += delay + waitTime; // wichtig: aufsummieren
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Hilfsfunktion für Pausen
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
|
||||
function triggerConfetti() {
|
||||
const end = Date.now() + 30 * 1000;
|
||||
(function frame() {
|
||||
confetti({ particleCount: 2, angle: 60, spread: 55, origin: { x: 0 } });
|
||||
confetti({ particleCount: 2, angle: 120, spread: 55, origin: { x: 1 } });
|
||||
if (Date.now() < end) requestAnimationFrame(frame);
|
||||
})();
|
||||
}
|
||||
|
||||
function show_score() {
|
||||
|
||||
let player_score=document.getElementById('player_score');
|
||||
player_score.classList.remove('hidden');
|
||||
info=document.getElementById('info');
|
||||
info.classList.add('hidden');
|
||||
resultText=document.getElementById('result-text');
|
||||
resultText.textContent="Dein Ergebnis";
|
||||
|
||||
|
||||
|
||||
const myName = "{{ my_participant.id|escapejs }}";
|
||||
const firstPlaceName = "{{ participants.0.id|escapejs }}";
|
||||
|
||||
if (myName === firstPlaceName) {
|
||||
let winnerText= document.getElementById(`winner-text`);
|
||||
winnerText.classList.remove('hidden');
|
||||
triggerConfetti();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% endif %}
|
||||
{% include 'play/game/common_scripts.html' %}
|
||||
{% if is_host %}
|
||||
{% include 'play/game/sound.html' %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
async function start() {
|
||||
|
||||
elements=document.getElementsByClassName("show-score");
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const el = elements[i];
|
||||
|
||||
let counter=Number(el.getAttribute('data-score'))-Number(el.getAttribute('data-last-score'));
|
||||
let goal=Number(el.getAttribute('data-score'));
|
||||
let interval= setInterval(function(){
|
||||
el.innerHTML = counter + " Punkte";
|
||||
|
||||
|
||||
if(counter<=goal){
|
||||
el.innerHTML = counter + " Punkte";
|
||||
counter+=8;
|
||||
|
||||
}
|
||||
else{
|
||||
|
||||
el.innerHTML = goal + " Punkte";
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
}, 20);}}
|
||||
|
||||
function bounce(){
|
||||
|
||||
const names = document.getElementsByClassName("display_name");
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
names[i].classList.add("animate-bounce");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{% if is_last_question %}
|
||||
start();
|
||||
bounce();
|
||||
|
||||
|
||||
{% else %}
|
||||
start();
|
||||
{% endif %}
|
||||
|
||||
const richtigeAntworten = [
|
||||
"Deine Antwort ist nicht falsch. Sie ist richtig!",
|
||||
"Ja, du kannst es!",
|
||||
"So kann es weiter gehen.",
|
||||
"Ja, deine Antwort ist korrekt. Was soll ich dazu noch sagen? SUPER!",
|
||||
"SUPER GUT.",
|
||||
"Volltreffer!",
|
||||
"Bravo! Das sitzt!",
|
||||
"Na bitte, geht doch!",
|
||||
"Schön. Einfach schön.",
|
||||
"Exzellent kombiniert, Sherlock!",
|
||||
"Punkt für dich!",
|
||||
"Das war meisterhaft.",
|
||||
"Korrekt - du scheinst das zu können!",
|
||||
"So sieht Erfolg aus!",
|
||||
"Glänzend gelöst!",
|
||||
"Das war keine Glückssache - das war Können!",
|
||||
"Treffer, versenkt!",
|
||||
"Du hast es drauf!",
|
||||
"Da merkt man: Profi am Werk.",
|
||||
"Richtige Antwort - dafür gibt es viele Punkte!",
|
||||
"Weiter so!",
|
||||
"Wow - das war richtig gut!"
|
||||
];
|
||||
|
||||
const falscheAntworten = [
|
||||
"Oops! War wohl nicht dein Tag.",
|
||||
"Knapp daneben ist auch vorbei!",
|
||||
"Das war... kreativ!",
|
||||
"Wenn man schon alles wüsste, wäre das Quiz doch sinnlos.",
|
||||
"Deine Antwort war einzigartig - leider falsch!",
|
||||
"Na gut, wenigstens hast du es probiert.",
|
||||
"Das war eine interessante Theorie...",
|
||||
"Ein Genie irrt sich auch mal.",
|
||||
"Eine gute Antwort - aber nicht auf diese Frage.",
|
||||
"Das ist leider falsch. Mehr gibt es nicht zu sagen.",
|
||||
"Gute Idee! Aber leider nicht richtig.",
|
||||
"Oh weh! Ein Fehler. Naja, einer ist keiner.",
|
||||
"Die Antwort klang richtig, sie ist es aber nicht.",
|
||||
"Interessanter Ansatz. Nicht hilfreich, aber interessant.",
|
||||
"Nenn das lieber „alternative Fakten“.",
|
||||
"Du hast die richtige Antwort doch gewusst: 'Die Seite hat nur nicht geladen.'",
|
||||
"Jeder fängt mal klein an!",
|
||||
"Kopf hoch - weiter geht's!",
|
||||
"Nobody is perfect!",
|
||||
"Du schaffst das - vielleicht ab jetzt.",
|
||||
"Falsche Antwort, aber guter Wille!",
|
||||
"Übung macht den Quiz-Meister.",
|
||||
"Beim nächsten Mal triffst du!",
|
||||
"Das war nur zum Aufwärmen, oder?",
|
||||
"Kein Problem - weiter geht's.",
|
||||
"Gute Frage - aber falsche Antwort."
|
||||
];
|
||||
|
||||
|
||||
const antwortRichtig = document.getElementById("antwort-richtig");
|
||||
const antwortFalsch = document.getElementById("antwort-falsch");
|
||||
|
||||
if (antwortRichtig) {
|
||||
antwortRichtig.textContent = richtigeAntworten[Math.floor(Math.random() * richtigeAntworten.length)];
|
||||
} else if (antwortFalsch) {
|
||||
antwortFalsch.textContent = falscheAntworten[Math.floor(Math.random() * falscheAntworten.length)];
|
||||
}
|
||||
const joinCode = '{{ quiz_game.join_code }}';
|
||||
const hostId = '{{ host_id }}';
|
||||
const wsScheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const gameSocket = new WebSocket(
|
||||
`${wsScheme}://${window.location.host}/ws/game/${joinCode}/`
|
||||
);
|
||||
|
||||
gameSocket.onmessage = function(e) {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.type === 'game_state_update' &&
|
||||
data.redirect_url &&
|
||||
(data.action === 'next_question' || data.action === 'finish_game' || data.action === 'show_winner_podest')) {
|
||||
window.location.href = data.redirect_url;
|
||||
}
|
||||
else if (data.type === 'answer_stats') {
|
||||
updateAnswerStats(data.stats);
|
||||
}
|
||||
};
|
||||
|
||||
gameSocket.onclose = function(e) {
|
||||
// Normal closure (code 1000) oder Navigation zu einer anderen Seite (code 1001)
|
||||
if (e.code === 1000 || e.code === 1001) {
|
||||
console.log('Game socket closed normally');
|
||||
} else {
|
||||
console.error('Game socket closed unexpectedly', e.code);
|
||||
}
|
||||
};
|
||||
|
||||
{% if is_host %}
|
||||
// Add click handlers for host buttons
|
||||
{% if is_last_question %}
|
||||
var finishButton = document.getElementById('finish-button');
|
||||
if (finishButton) {
|
||||
finishButton.addEventListener('click', function() {
|
||||
gameSocket.send(JSON.stringify({
|
||||
'type': 'finish_game',
|
||||
'host_id': hostId
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
var podestButton = document.getElementById('winner-button');
|
||||
if (podestButton) {
|
||||
podestButton.addEventListener('click', function() {
|
||||
gameSocket.send(JSON.stringify({
|
||||
'type': 'winner_podest',
|
||||
'host_id': hostId
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
{% else %}
|
||||
|
||||
var nextButton = document.getElementById('next-button');
|
||||
if (nextButton) {
|
||||
nextButton.addEventListener('click', function() {
|
||||
gameSocket.send(JSON.stringify({
|
||||
'type': 'next_question',
|
||||
'host_id': hostId
|
||||
}));
|
||||
}, { once: true });
|
||||
}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
function updateAnswerStats(stats) {
|
||||
const total = Object.values(stats).reduce((a, b) => a + b, 0) || 1;
|
||||
|
||||
for (var optionIndex in stats) {
|
||||
if (stats.hasOwnProperty(optionIndex)) {
|
||||
var countElement = document.getElementById('option-count-' + optionIndex);
|
||||
var fillElement = document.getElementById('option-fill-' + optionIndex);
|
||||
|
||||
if (countElement) {
|
||||
countElement.textContent = stats[optionIndex] + ' Antwort(en)';
|
||||
}
|
||||
|
||||
if (fillElement) {
|
||||
const percentage = Math.round((stats[optionIndex] / total) * 100);
|
||||
fillElement.style.width = percentage + '%';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Request answer stats when page loads
|
||||
gameSocket.onopen = function(e) {
|
||||
gameSocket.send(JSON.stringify({
|
||||
'type': 'get_answer_stats'
|
||||
}));
|
||||
};
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,10 +1,9 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="max-w-md mx-auto bg-white rounded-lg shadow-lg p-6 dark:bg-transparent dark:border-2 dark:text-white">
|
||||
<div class="max-w-md mx-auto bg-white rounded-lg shadow-lg p-6">
|
||||
<h1 class="text-2xl font-bold mb-6 text-center text-blue-600">Spieler einrichten</h1>
|
||||
|
||||
<div class="text-center mb-6">
|
||||
@@ -18,20 +17,20 @@
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="join_code" value="{{join_code}}">
|
||||
|
||||
<div class="bg-gray-50 p-4 rounded-lg mb-4 dark:bg-transparent">
|
||||
<p class="text-sm text-gray-600 mb-2 dark:text-white">Wähle einen Anzeigenamen für das Spiel:</p>
|
||||
<div class="bg-gray-50 p-4 rounded-lg mb-4">
|
||||
<p class="text-sm text-gray-600 mb-2">Wähle einen Anzeigenamen für das Spiel:</p>
|
||||
<input type="text"
|
||||
name="display_name"
|
||||
required
|
||||
maxlength="20"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
placeholder="Dein Spielername">
|
||||
<p class="text-xs text-gray-500 mt-2 dark:text-white">Maximal 20 Zeichen</p>
|
||||
<p class="text-xs text-gray-500 mt-2">Maximal 20 Zeichen</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center space-x-4">
|
||||
<a href="/"
|
||||
class="px-6 py-2 bg-gray-200 hover:bg-gray-300 rounded-lg transition-colors duration-200 dark:bg-transparent dark:hover:bg-[#2a2f3a]">
|
||||
class="px-6 py-2 bg-gray-200 hover:bg-gray-300 rounded-lg transition-colors duration-200">
|
||||
Abbrechen
|
||||
</a>
|
||||
<button type="submit"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid place-items-center min-h-[calc(100vh-5rem)] flex justify-center py-6 m-2">
|
||||
<div class="max-w-md w-full p-8 rounded-2xl border-2 border-blue-400 bg-white dark:bg-transparent dark:text-white">
|
||||
<h1 class="text-2xl text-center font-bold mb-4 ">Spiel beitreten</h1>
|
||||
<div class="sm:grid sm:place-items-center sm:min-h-[calc(100vh-5rem)] flex justify-center py-6 m-2">
|
||||
<div class="max-w-md w-full p-8 rounded-2xl border-2 border-blue-400 bg-white">
|
||||
<h1 class="text-2xl text-center font-bold mb-4">Spiel beitreten</h1>
|
||||
<form action="{% url 'play:join_game' %}" method="post" class="flex flex-col gap-4">
|
||||
{% csrf_token %}
|
||||
<div class="flex gap-3 items-center justify-center">
|
||||
<input type="number" name="game_code" placeholder="123456" maxlength="6" class="w-32 p-3 rounded-full bg-gray-200 focus:scale-105 transition duration-200 font-black focus:outline-none focus:ring-2 focus:ring-blue-400 focus:bg-blue-100 text-center tracking-wider text-black" autofocus>
|
||||
<input type="number" name="game_code" placeholder="123456" maxlength="6" class="w-32 p-3 rounded-full bg-gray-200 focus:scale-105 transition duration-200 font-black focus:outline-none focus:ring-2 focus:ring-blue-400 focus:bg-blue-100 text-center tracking-wider" autofocus>
|
||||
<button type="submit" class="h-12 px-6 rounded-full bg-blue-600 text-white font-black hover:scale-105 transition duration-200 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-blue-700 active:scale-95 group" disabled>
|
||||
<span class="hidden sm:block">Beitreten</span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5 transition-transform group-hover:translate-x-1">
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
|
||||
<div class="max-w-2xl mx-auto bg-white rounded-lg shadow-lg p-6 dark:bg-transparent dark:text-white">
|
||||
<div class="max-w-2xl mx-auto bg-white rounded-lg shadow-lg p-6">
|
||||
<!-- Quiz Info -->
|
||||
<div class="text-center mb-8">
|
||||
<h3 class="text-xl text-gray-600 mb-2 dark:text-white">{{ quiz.name }}</h3>
|
||||
<h3 class="text-xl text-gray-600 mb-2">{{ quiz.name }}</h3>
|
||||
{% if request.session.mode != "learn" %}
|
||||
{% if host_id %}
|
||||
<div class="bg-blue-100 rounded-lg p-4 mb-4 dark:bg-transparent">
|
||||
<h1 class="text-4xl font-bold text-blue-800 dark:text-white">{{ quiz_game.join_code }}</h1>
|
||||
<div class="bg-blue-100 rounded-lg p-4 mb-4">
|
||||
<h1 class="text-4xl font-bold text-blue-800">{{ quiz_game.join_code }}</h1>
|
||||
<div class="w-full flex justify-center my-2">
|
||||
<img src="data:image/png;base64,{{ qr_code_base64 }}" class="h-1/2 w-1/2" alt="QR-Code">
|
||||
</div>
|
||||
<p class="text-sm text-blue-600 mt-1 dark:text-white">Spiel-Code</p>
|
||||
<p class="text-sm text-blue-600 mt-1">Spiel-Code</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -23,23 +23,23 @@
|
||||
<!-- Participant Info -->
|
||||
{% if participant %}
|
||||
<div class="mb-6 text-center">
|
||||
<div class="inline-flex items-center bg-green-100 px-4 py-2 rounded-full dark:text-black">
|
||||
<span class="w-2 h-2 bg-green-500 rounded-full mr-2 "></span>
|
||||
<div class="inline-flex items-center bg-green-100 px-4 py-2 rounded-full">
|
||||
<span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
|
||||
<span>Angemeldet als <b>{{ participant.display_name }}</b></span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Participants List -->
|
||||
<div class="mb-6 overflow-y-scroll max-h-96 overflow-x-hidden">
|
||||
<div class="mb-6 overflow-y-scroll max-h-96">
|
||||
<h3 class="font-bold text-lg mb-3 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||
</svg>
|
||||
Teilnehmer
|
||||
</h3>
|
||||
<ul id="participants-list" class="space-y-2 max-h-60 ">
|
||||
<li class=" p-3 bg-gray-50 rounded-lg text-center dark:bg-transparent">Warte auf Teilnehmer...</li>
|
||||
<ul id="participants-list" class="space-y-2 max-h-60">
|
||||
<li class="text-gray-500 p-3 bg-gray-50 rounded-lg text-center">Warte auf Teilnehmer...</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -248,7 +248,7 @@
|
||||
}
|
||||
|
||||
if (!participants || participants.length === 0) {
|
||||
list.innerHTML = '<li class="text-gray-500 p-3 bg-gray-50 rounded-lg text-center dark:bg-transparent">Noch keine Teilnehmer...</li>';
|
||||
list.innerHTML = '<li class="text-gray-500 p-3 bg-gray-50 rounded-lg text-center">Noch keine Teilnehmer...</li>';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -298,13 +298,13 @@
|
||||
'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" ' +
|
||||
'd="M6 18L18 6M6 6l12 12"/>' +
|
||||
'</svg>' +
|
||||
'<span class="absolute pr-8 -top-8 left-1/2 transform -translate-x-1/2 px-2 py-1 ' +
|
||||
'<span class="absolute -top-8 left-1/2 transform -translate-x-1/2 px-2 py-1 ' +
|
||||
'bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 ' +
|
||||
'transition-opacity duration-200 whitespace-nowrap">Spieler kicken</span>' +
|
||||
'</button>';
|
||||
}
|
||||
|
||||
return '<li class="dark:bg-transparent p-3 bg-white border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition duration-200 flex items-center justify-between animate-fade-in">' +
|
||||
return '<li class="p-3 bg-white border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition duration-200 flex items-center justify-between animate-fade-in">' +
|
||||
'<div class="flex items-center">' +
|
||||
'<div class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center mr-3">' +
|
||||
'<span class="text-blue-600 font-bold">' + initial.toUpperCase() + '</span>' +
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<div class="grid place-content-center md:h-screen h-150 w-full -z-50 top-0 p-8 ">
|
||||
<div class="rounded-md rounded-xl shadow-lg border-3 border-blue-100 p-10">
|
||||
<span class="flex justify-center font-bold p-4 text-center text-1xl lg:text-2xl dark:text-white">Bitte wählen Sie einen Spielmodus aus:</span>
|
||||
<span class="flex justify-center font-bold p-4 text-center text-1xl lg:text-2xl">Bitte wählen Sie einen Spielmodus aus:</span>
|
||||
|
||||
<div class="grid sm:grid-cols-2 place-items-stretch text-center gap-4 p-4">
|
||||
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="max-w-lg mx-auto mt-10">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md dark:text-white dark:bg-transparent">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 border-blue-600 border-2 rounded-xl shadow-md">
|
||||
<div class="text-center">
|
||||
<svg class="mx-auto h-12 w-12 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
||||
</svg>
|
||||
|
||||
<h2 class="mt-4 text-xl font-bold text-gray-900 dark:text-white">Löschen bestätigen</h2>
|
||||
<h2 class="mt-4 text-xl font-bold text-gray-900">Löschen bestätigen</h2>
|
||||
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-white">
|
||||
<p class="mt-2 text-sm text-gray-600">
|
||||
Möchten Sie ihren Account wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block title %}Passwort ändern{% endblock %}
|
||||
{% block content %}
|
||||
<div class="grid place-content-center h-120 mt-12 mb-12 ">
|
||||
<div class="w-80 p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md lg:w-90 dark:text-white!">
|
||||
<div class="w-80 p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md lg:w-90">
|
||||
<h2 class="text-2xl text-center p-4 font-black">Passwort ändern</h2>
|
||||
<form class="space-y-4" method="post">
|
||||
{% if form.errors %}
|
||||
@@ -21,17 +21,17 @@
|
||||
<div class="register">
|
||||
|
||||
{{ form.old_password.label_tag }}
|
||||
<input type="password" name="old_password" placeholder="ALTES PASSWORT" class="dark:text-black" />
|
||||
<input type="password" name="old_password" placeholder="ALTES PASSWORT" />
|
||||
|
||||
</div>
|
||||
|
||||
<div class="register">
|
||||
{{ form.new_password1.label_tag }}
|
||||
<input type="password" name="new_password1" placeholder="NEUES PASSWORT" class="dark:text-black" />
|
||||
<input type="password" name="new_password1" placeholder="NEUES PASSWORT" />
|
||||
</div>
|
||||
<div class="register">
|
||||
{{ form.new_password2.label_tag }}
|
||||
<input type="password" name="new_password2" placeholder="NEUES PASSWORT WIEDERHOLEN" class="dark:text-black" />
|
||||
<input type="password" name="new_password2" placeholder="NEUES PASSWORT WIEDERHOLEN" />
|
||||
</div>
|
||||
<button class="w-full p-3 rounded-full bg-blue-600 text-white font-black hover:scale-105 transition duration-200" type="submit">Bestätigen</button>
|
||||
<button class="text-center text-sm w-full mt-2 font-light text-blue-600"><a href="{% url 'accounts:home' %}" class="register_link">Doch nicht ändern?</a></button>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block title %}Passwort zurückgesetzt{% endblock %}
|
||||
{% block content %}
|
||||
<div class="grid place-content-center h-120 mt-12 mb-12 ">
|
||||
<div class="w-80 p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md lg:w-90 dark:text-white">
|
||||
<div class="w-80 p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md lg:w-90">
|
||||
<h2 class="text-2xl text-center p-4 font-black">Passwort zurückgesetzt</h2>
|
||||
<div class="space-y-4">
|
||||
Das Passwort wurde erfolgreich zurückgesetzt! Sie können sich nun mit dem neuen Passwort anmelden!
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% block content %}
|
||||
<div class="grid place-content-center h-120 mt-12 mb-12 ">
|
||||
<div class="w-80 p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md lg:w-90">
|
||||
<h2 class="text-2xl text-center p-4 font-black dark:text-white">Passwort zurücksetzen</h2>
|
||||
<h2 class="text-2xl text-center p-4 font-black">Passwort zurücksetzen</h2>
|
||||
{% if validlink %}
|
||||
<form class="space-y-4" method="post">
|
||||
{% if form.errors %}
|
||||
@@ -21,12 +21,11 @@
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="register">
|
||||
|
||||
<span class="dark:text-white">{{ form.new_password1.label_tag }} </span>
|
||||
{{ form.new_password1.label_tag }}
|
||||
<input type="password" name="new_password1" placeholder="NEUES PASSWORT" />
|
||||
</div>
|
||||
<div class="register ">
|
||||
<span class="dark:text-white">{{ form.new_password2.label_tag }} </span>
|
||||
<div class="register">
|
||||
{{ form.new_password2.label_tag }}
|
||||
<input type="password" name="new_password2" placeholder="NEUES PASSWORT WIEDERHOLEN" />
|
||||
|
||||
</div>
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="grid place-content-center h-120 mt-12 mb-12">
|
||||
<div class="w-80 p-6 m-2 border-blue-600 border-2 rounded-xl shadow-md lg:w-90 text-center dark:text-white">
|
||||
<h2 class="text-2xl font-black mb-4 dark:text-white">E-Mail wurde gesendet</h2>
|
||||
<p class="text-gray-700 mb-6 dark:text-white">
|
||||
<div class="w-80 p-6 m-2 border-blue-600 border-2 rounded-xl shadow-md lg:w-90 text-center">
|
||||
<h2 class="text-2xl font-black mb-4">E-Mail wurde gesendet</h2>
|
||||
<p class="text-gray-700 mb-6">
|
||||
Wenn ein Konto mit dieser E-Mail-Adresse existiert, haben wir dir eine Nachricht mit einem Link zum Zurücksetzen des Passworts gesendet.
|
||||
|
||||
</p><p class="text-gray-700 mb-6 font-bold dark:text-white"> Tipp: Gegebenenfalls Spam überprüfen.</p>
|
||||
</p><p class="text-gray-700 mb-6 font-bold"> Tipp: Gegebenenfalls Spam überprüfen.</p>
|
||||
|
||||
|
||||
<a href="{% url 'accounts:login' %}" class="block w-full p-3 rounded-full bg-blue-600 text-white font-black hover:scale-105 transition duration-200">Zur Anmeldung</a>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block title %}Passwort ändern{% endblock %}
|
||||
{% block content %}
|
||||
<div class="grid place-content-center h-120 mt-12 mb-12 ">
|
||||
<div class="w-80 p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md lg:w-90 dark:text-white">
|
||||
<div class="w-80 p-4 m-2 border-blue-600 border-2 rounded-xl shadow-md lg:w-90">
|
||||
<h2 class="text-2xl text-center p-4 font-black">Passwort zurücksetzen</h2>
|
||||
<form class="space-y-4" method="post">
|
||||
Gib hier deine E-Mail an und erhalte eine E-Mail, um das Passwort zurückzusetzen.
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
<div class="register">
|
||||
{{ form.email.label_tag }}
|
||||
<input type="email" name="email" placeholder="E-MAIL" class="dark:text-black" />
|
||||
<input type="email" name="email" placeholder="E-MAIL" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -9,9 +9,3 @@ redis
|
||||
channels_redis
|
||||
python-dotenv # Production server safety
|
||||
qrcode~=7.4.2
|
||||
reportlab~=4.2.0
|
||||
pip-licenses ~= 5.0.0
|
||||
requests
|
||||
requests
|
||||
geopy
|
||||
django-autocomplete-light
|
||||
|
||||
Reference in New Issue
Block a user