Compare commits
1 Commits
159-design
...
144-update
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7f4afb6f4 |
@@ -170,12 +170,3 @@ Die Konfiguration des Projekts befindet sich im `config/qivip_config.json`.
|
|||||||
| Variable | Bedeutung | Optionen |
|
| Variable | Bedeutung | Optionen |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| ENABLE_RATING_SYSTEM | Soll das Bewertungssystem aktiviert werden? | true [Standard], false |
|
| 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
|
|
||||||
Binary file not shown.
@@ -1,44 +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-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 |
|
|
||||||
@@ -30,35 +30,10 @@ class QuizImageAdmin(admin.ModelAdmin):
|
|||||||
class QuestionImageAdmin(admin.ModelAdmin):
|
class QuestionImageAdmin(admin.ModelAdmin):
|
||||||
list_display = ('image',)
|
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
|
# Registrierung der Modelle im Admin
|
||||||
admin.site.register(QivipQuiz, QivipQuizAdmin)
|
admin.site.register(QivipQuiz, QivipQuizAdmin)
|
||||||
admin.site.register(QivipQuestion, QivipQuestionAdmin)
|
admin.site.register(QivipQuestion, QivipQuestionAdmin)
|
||||||
#admin.site.register(QuizCategory, QuizCategoryAdmin)
|
admin.site.register(QuizCategory, QuizCategoryAdmin)
|
||||||
admin.site.register(QivipQuizFavorite, QivipQuizFavoriteAdmin)
|
admin.site.register(QivipQuizFavorite, QivipQuizFavoriteAdmin)
|
||||||
admin.site.register(QuizImage, QuizImageAdmin)
|
admin.site.register(QuizImage, QuizImageAdmin)
|
||||||
admin.site.register(QuestionImage, QuestionImageAdmin)
|
admin.site.register(QuestionImage, QuestionImageAdmin)
|
||||||
@@ -6,15 +6,6 @@ class QuizForm(forms.ModelForm):
|
|||||||
model = QivipQuiz
|
model = QivipQuiz
|
||||||
fields = ['name', 'description', 'status', 'category','difficulty','credits']
|
fields = ['name', 'description', 'status', 'category','difficulty','credits']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(QuizForm, self).__init__(*args, **kwargs)
|
|
||||||
for field in self.fields.values():
|
|
||||||
field.widget.attrs.update({
|
|
||||||
'class': 'dark:bg-[#2a2f3a]! dark:text-white!'
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class QuestionForm(forms.ModelForm):
|
class QuestionForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = QivipQuestion
|
model = QivipQuestion
|
||||||
@@ -43,5 +34,5 @@ class QuizFilterForm(forms.Form):
|
|||||||
required=False,
|
required=False,
|
||||||
label="Kategorie",
|
label="Kategorie",
|
||||||
widget=forms.Select(attrs={
|
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',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -45,10 +45,6 @@ class QivipQuiz(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
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)
|
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||||
user_id = models.ForeignKey(User, on_delete=models.CASCADE, related_name='quiz')
|
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')
|
#creator = models.ForeignKey(User, on_delete=models.SET_NULL,blank=True, null=True, related_name='creator_quiz')
|
||||||
@@ -152,12 +148,6 @@ class QuizCategory(models.Model):
|
|||||||
name = models.CharField(max_length=100, unique=True)
|
name = models.CharField(max_length=100, unique=True)
|
||||||
description = models.TextField(blank=True, null=True)
|
description = models.TextField(blank=True, null=True)
|
||||||
slug = models.SlugField(unique=True, blank=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):
|
def save(self, *args, **kwargs):
|
||||||
if not self.slug:
|
if not self.slug:
|
||||||
|
|||||||
@@ -20,7 +20,5 @@ urlpatterns = [
|
|||||||
path('detail/copy/<int:pk>/', views.copy_quiz, name='copy_quiz'),
|
path('detail/copy/<int:pk>/', views.copy_quiz, name='copy_quiz'),
|
||||||
path('favorite_quiz/<int:pk>/', views.favorite_quiz, name='favorite_quiz'),
|
path('favorite_quiz/<int:pk>/', views.favorite_quiz, name='favorite_quiz'),
|
||||||
path("quiz-names-json/", views.quiz_names_json, name="quiz_names_json"),
|
path("quiz-names-json/", views.quiz_names_json, name="quiz_names_json"),
|
||||||
#path('quiz/<int:pk>/pdf/', views.pdf_solution, name='quiz_solution'),
|
|
||||||
#path('quiz/<int:pk>/pdf/task', views.pdf_task, name='quiz_task'),
|
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -17,329 +17,6 @@ from django.contrib.auth.models import User
|
|||||||
from urllib.parse import urlparse, urlunparse
|
from urllib.parse import urlparse, urlunparse
|
||||||
# Übersicht aller Quizze
|
# Ü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
|
|
||||||
"""
|
|
||||||
def pdf_solution(request, pk):
|
|
||||||
quiz = get_object_or_404(QivipQuiz, pk=pk)
|
|
||||||
|
|
||||||
questions = QivipQuestion.objects.filter(quiz_id=quiz)
|
|
||||||
|
|
||||||
for question in questions:
|
|
||||||
if question.data:
|
|
||||||
question.data = json.loads(question.data)
|
|
||||||
|
|
||||||
response = HttpResponse(content_type='application/pdf')
|
|
||||||
response['Content-Disposition'] = f'attachment; filename="Lösungen - {quiz.name}.pdf"'
|
|
||||||
|
|
||||||
doc = SimpleDocTemplate(response, pagesize=A4,
|
|
||||||
rightMargin=40, leftMargin=40,
|
|
||||||
topMargin=60, bottomMargin=60)
|
|
||||||
|
|
||||||
styles = getSampleStyleSheet()
|
|
||||||
|
|
||||||
# Stil für Quiz-Titel
|
|
||||||
title_style = ParagraphStyle(
|
|
||||||
'TitleStyle',
|
|
||||||
parent=styles['Title'],
|
|
||||||
alignment=1, # zentriert
|
|
||||||
fontSize=14,
|
|
||||||
spaceAfter=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Stil für Fragen-Box
|
|
||||||
question_box_style = ParagraphStyle(
|
|
||||||
'QuestionBox',
|
|
||||||
fontSize=10,
|
|
||||||
leading=16,
|
|
||||||
fontName='Helvetica-Bold',
|
|
||||||
|
|
||||||
|
|
||||||
spaceAfter=8,
|
|
||||||
leftIndent=6,
|
|
||||||
rightIndent=6,
|
|
||||||
|
|
||||||
alignment=0
|
|
||||||
)
|
|
||||||
|
|
||||||
# Stil für Antwort (richtig)
|
|
||||||
answer_correct_style = ParagraphStyle(
|
|
||||||
'AnswerCorrect',
|
|
||||||
fontSize=11,
|
|
||||||
leading=14,
|
|
||||||
textColor=colors.green,
|
|
||||||
leftIndent=20,
|
|
||||||
spaceAfter=4,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Stil für Antwort (falsch)
|
|
||||||
answer_wrong_style = ParagraphStyle(
|
|
||||||
'AnswerWrong',
|
|
||||||
fontSize=11,
|
|
||||||
leading=14,
|
|
||||||
textColor=colors.red,
|
|
||||||
leftIndent=20,
|
|
||||||
spaceAfter=4,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Stil für normalen Antwort-Text (ohne Antwortanzeige)
|
|
||||||
answer_neutral_style = ParagraphStyle(
|
|
||||||
'AnswerNeutral',
|
|
||||||
fontSize=11,
|
|
||||||
leading=14,
|
|
||||||
textColor=colors.black,
|
|
||||||
leftIndent=20,
|
|
||||||
spaceAfter=4,
|
|
||||||
)
|
|
||||||
date_style = ParagraphStyle(
|
|
||||||
name='RightAligned',
|
|
||||||
parent=styles['Normal'],
|
|
||||||
alignment=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
story = []
|
|
||||||
|
|
||||||
story.append(Paragraph(f"Quiz: {quiz.name}", title_style))
|
|
||||||
story.append(Paragraph(f"- Lösungen -", title_style))
|
|
||||||
story.append(Spacer(1, 20))
|
|
||||||
|
|
||||||
story.append(Paragraph(datetime.today().strftime("%d.%m.%Y"), date_style))
|
|
||||||
story.append(Spacer(1, 20))
|
|
||||||
|
|
||||||
if quiz.description:
|
|
||||||
story.append(Paragraph(quiz.description, styles['Normal']))
|
|
||||||
story.append(Spacer(1, 20))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for i, question in enumerate(questions, start=1):
|
|
||||||
data = question.data
|
|
||||||
frage_text = data.get('question', 'Frage nicht gefunden.')
|
|
||||||
|
|
||||||
story.append(Paragraph(f"Frage {i}: {frage_text}", question_box_style))
|
|
||||||
story.append(Spacer(1, 6))
|
|
||||||
|
|
||||||
rl_image = None
|
|
||||||
if hasattr(question, 'image') and question.image and question.image.image:
|
|
||||||
image_url = request.build_absolute_uri(question.image.image.url)
|
|
||||||
img_response = requests.get(image_url)
|
|
||||||
if img_response.status_code == 200:
|
|
||||||
image_data = BytesIO(img_response.content)
|
|
||||||
pil_image = PILImage.open(image_data)
|
|
||||||
pil_image = ImageOps.exif_transpose(pil_image)
|
|
||||||
orig_width, orig_height = pil_image.size
|
|
||||||
max_width = 80
|
|
||||||
max_height = 80
|
|
||||||
ratio = min(max_width / orig_width, max_height / orig_height)
|
|
||||||
new_width = orig_width * ratio
|
|
||||||
new_height = orig_height * ratio
|
|
||||||
output = BytesIO()
|
|
||||||
pil_image.save(output, format='JPEG')
|
|
||||||
output.seek(0)
|
|
||||||
|
|
||||||
rl_image = Image(output, width=new_width, height=new_height)
|
|
||||||
|
|
||||||
options = data.get('options', [])
|
|
||||||
answer_paragraphs = []
|
|
||||||
for idx, option in enumerate(options, start=1):
|
|
||||||
opt_text = option.get('value', '')
|
|
||||||
if option.get('is_correct', False):
|
|
||||||
text = f"✔ {opt_text}"
|
|
||||||
style = answer_correct_style
|
|
||||||
else:
|
|
||||||
text = f"✘ {opt_text}"
|
|
||||||
style = answer_wrong_style
|
|
||||||
answer_paragraphs.append(Paragraph(text, style))
|
|
||||||
|
|
||||||
if rl_image:
|
|
||||||
# Tabelle mit Bild links und Antworten rechts
|
|
||||||
from reportlab.platypus import Table, TableStyle
|
|
||||||
from reportlab.lib.units import cm
|
|
||||||
data_table = [[rl_image, answer_paragraphs]]
|
|
||||||
table = Table(data_table, colWidths=[6*cm, 10*cm])
|
|
||||||
table.setStyle(TableStyle([
|
|
||||||
('VALIGN', (0,0), (-1,-1), 'TOP'),
|
|
||||||
('LEFTPADDING', (0,0), (-1,-1), 0),
|
|
||||||
('RIGHTPADDING', (0,0), (-1,-1), 10),
|
|
||||||
]))
|
|
||||||
story.append(table)
|
|
||||||
else:
|
|
||||||
# Kein Bild, Antworten einfach nacheinander
|
|
||||||
for p in answer_paragraphs:
|
|
||||||
story.append(p)
|
|
||||||
|
|
||||||
story.append(Spacer(1, 12))
|
|
||||||
story.append(HRFlowable(width="100%", thickness=1, color=colors.grey))
|
|
||||||
story.append(Spacer(1, 12))
|
|
||||||
current_url = request.build_absolute_uri(f"/library/detail/{quiz.pk}/")
|
|
||||||
escaped_url = escape(current_url) # Sicherheitshalber escapen
|
|
||||||
|
|
||||||
link_html = f'<a href="{escaped_url}" color="blue">{escaped_url}</a>'
|
|
||||||
story.append(Paragraph(link_html, styles['Normal']))
|
|
||||||
|
|
||||||
|
|
||||||
doc.build(story)
|
|
||||||
return response
|
|
||||||
def pdf_task(request, pk):
|
|
||||||
quiz = get_object_or_404(QivipQuiz, pk=pk)
|
|
||||||
questions = QivipQuestion.objects.filter(quiz_id=quiz)
|
|
||||||
|
|
||||||
for question in questions:
|
|
||||||
if question.data:
|
|
||||||
question.data = json.loads(question.data)
|
|
||||||
|
|
||||||
response = HttpResponse(content_type='application/pdf')
|
|
||||||
response['Content-Disposition'] = f'attachment; filename="Aufgabenblatt - {quiz.name}.pdf"'
|
|
||||||
|
|
||||||
doc = SimpleDocTemplate(response, pagesize=A4,
|
|
||||||
rightMargin=40, leftMargin=40,
|
|
||||||
topMargin=60, bottomMargin=60)
|
|
||||||
|
|
||||||
styles = getSampleStyleSheet()
|
|
||||||
|
|
||||||
title_style = ParagraphStyle(
|
|
||||||
'TitleStyle',
|
|
||||||
parent=styles['Title'],
|
|
||||||
alignment=1,
|
|
||||||
fontSize=14,
|
|
||||||
spaceAfter=2,
|
|
||||||
fontName='Helvetica-Bold',
|
|
||||||
)
|
|
||||||
|
|
||||||
question_box_style = ParagraphStyle(
|
|
||||||
'QuestionBox',
|
|
||||||
fontSize=10,
|
|
||||||
leading=16,
|
|
||||||
fontName='Helvetica-Bold',
|
|
||||||
spaceAfter=8,
|
|
||||||
leftIndent=6,
|
|
||||||
rightIndent=6,
|
|
||||||
alignment=0
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
answer_neutral_style = ParagraphStyle(
|
|
||||||
'AnswerNeutral',
|
|
||||||
fontSize=11,
|
|
||||||
leading=14,
|
|
||||||
textColor=colors.black,
|
|
||||||
leftIndent=20,
|
|
||||||
spaceAfter=4,
|
|
||||||
)
|
|
||||||
|
|
||||||
date_style = ParagraphStyle(
|
|
||||||
name='RightAligned',
|
|
||||||
parent=styles['Normal'],
|
|
||||||
alignment=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
name_style = ParagraphStyle(
|
|
||||||
name='RightAligned',
|
|
||||||
parent=styles['Normal'],
|
|
||||||
alignment=0,
|
|
||||||
)
|
|
||||||
story = []
|
|
||||||
|
|
||||||
# Titel & Datum
|
|
||||||
story.append(Paragraph(f"Aufgabenblatt: {quiz.name}", title_style))
|
|
||||||
story.append(Spacer(1, 20))
|
|
||||||
story.append(Paragraph(datetime.today().strftime("%d.%m.%Y"), date_style))
|
|
||||||
story.append(Spacer(1, 20))
|
|
||||||
story.append(Paragraph("Name: ", name_style))
|
|
||||||
story.append(Spacer(1, 20))
|
|
||||||
|
|
||||||
if quiz.description:
|
|
||||||
story.append(Paragraph(quiz.description, styles['Normal']))
|
|
||||||
story.append(Spacer(1, 20))
|
|
||||||
|
|
||||||
for i, question in enumerate(questions, start=1):
|
|
||||||
data = question.data
|
|
||||||
frage_text = data.get('question', 'Frage nicht gefunden.')
|
|
||||||
story.append(Paragraph(f"Frage {i}: {frage_text}", question_box_style))
|
|
||||||
story.append(Spacer(1, 6))
|
|
||||||
|
|
||||||
rl_image = None
|
|
||||||
if hasattr(question, 'image') and question.image and question.image.image:
|
|
||||||
image_url = request.build_absolute_uri(question.image.image.url)
|
|
||||||
img_response = requests.get(image_url)
|
|
||||||
if img_response.status_code == 200:
|
|
||||||
image_data = BytesIO(img_response.content)
|
|
||||||
pil_image = PILImage.open(image_data)
|
|
||||||
pil_image = ImageOps.exif_transpose(pil_image)
|
|
||||||
orig_width, orig_height = pil_image.size
|
|
||||||
max_width = 80 # gleich wie bei Lösungen
|
|
||||||
max_height = 80
|
|
||||||
ratio = min(max_width / orig_width, max_height / orig_height)
|
|
||||||
new_width = orig_width * ratio
|
|
||||||
new_height = orig_height * ratio
|
|
||||||
output = BytesIO()
|
|
||||||
pil_image.save(output, format='JPEG')
|
|
||||||
output.seek(0)
|
|
||||||
rl_image = Image(output, width=new_width, height=new_height)
|
|
||||||
|
|
||||||
options = data.get('options', [])
|
|
||||||
|
|
||||||
answer_paragraphs = []
|
|
||||||
for idx, option in enumerate(options, start=1):
|
|
||||||
|
|
||||||
opt_text = option.get('value', '')
|
|
||||||
if data.get("type") !="input":
|
|
||||||
opt_text = option.get('value', '')
|
|
||||||
answer_paragraphs.append(Paragraph(f"{idx}. {opt_text}", answer_neutral_style))
|
|
||||||
else:
|
|
||||||
story.append(Paragraph("Deine Antwort: ", answer_neutral_style))
|
|
||||||
story.append(Spacer(1, 12))
|
|
||||||
|
|
||||||
if rl_image:
|
|
||||||
from reportlab.platypus import Table, TableStyle
|
|
||||||
from reportlab.lib.units import cm
|
|
||||||
data_table = [[rl_image, answer_paragraphs]]
|
|
||||||
table = Table(data_table, colWidths=[6*cm, 10*cm])
|
|
||||||
table.setStyle(TableStyle([
|
|
||||||
('VALIGN', (0,0), (-1,-1), 'TOP'),
|
|
||||||
('LEFTPADDING', (0,0), (-1,-1), 0),
|
|
||||||
('RIGHTPADDING', (0,0), (-1,-1), 10),
|
|
||||||
]))
|
|
||||||
story.append(table)
|
|
||||||
else:
|
|
||||||
for p in answer_paragraphs:
|
|
||||||
story.append(p)
|
|
||||||
|
|
||||||
story.append(Spacer(1, 12))
|
|
||||||
story.append(HRFlowable(width="100%", thickness=1, color=colors.grey))
|
|
||||||
story.append(Spacer(1, 12))
|
|
||||||
current_url = request.build_absolute_uri(f"/library/detail/{quiz.pk}/")
|
|
||||||
escaped_url = escape(current_url) # Sicherheitshalber escapen
|
|
||||||
|
|
||||||
link_html = f'<a href="{escaped_url}" color="blue">{escaped_url}</a>'
|
|
||||||
story.append(Paragraph(link_html, styles['Normal']))
|
|
||||||
doc.build(story)
|
|
||||||
return response
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def quiz_names_json(request):
|
def quiz_names_json(request):
|
||||||
form = QuizFilterForm(request.GET)
|
form = QuizFilterForm(request.GET)
|
||||||
@@ -695,7 +372,8 @@ def new_quiz(request):
|
|||||||
|
|
||||||
|
|
||||||
#quiz.creator = request.user
|
#quiz.creator = request.user
|
||||||
quiz.save()
|
if form.has_changed():
|
||||||
|
quiz.save()
|
||||||
#form.save_m2m() # Speichert die Many-to-Many Beziehungen (Tags)
|
#form.save_m2m() # Speichert die Many-to-Many Beziehungen (Tags)
|
||||||
return redirect('library:detail_quiz', pk=quiz.pk)
|
return redirect('library:detail_quiz', pk=quiz.pk)
|
||||||
|
|
||||||
@@ -734,8 +412,8 @@ def edit_quiz(request, pk):
|
|||||||
status = form.cleaned_data.get('status')
|
status = form.cleaned_data.get('status')
|
||||||
if status == 'öffentlich' and not quiz.published_at:
|
if status == 'öffentlich' and not quiz.published_at:
|
||||||
quiz.published_at = timezone.now()
|
quiz.published_at = timezone.now()
|
||||||
|
if form.has_changed():
|
||||||
form.save()
|
form.save()
|
||||||
for img in QuizImage.objects.all():
|
for img in QuizImage.objects.all():
|
||||||
try:
|
try:
|
||||||
img.delete()
|
img.delete()
|
||||||
@@ -811,9 +489,6 @@ def copy_quiz(request, pk):
|
|||||||
new_quiz.base_quiz_id = pk
|
new_quiz.base_quiz_id = pk
|
||||||
new_quiz.rating_count = 0
|
new_quiz.rating_count = 0
|
||||||
new_quiz.average_rating = 3.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":
|
if original_quiz.status=="öffentlich":
|
||||||
new_quiz.published_at = timezone.now()
|
new_quiz.published_at = timezone.now()
|
||||||
else:
|
else:
|
||||||
@@ -852,7 +527,6 @@ def question_list(request):
|
|||||||
def new_question(request):
|
def new_question(request):
|
||||||
question_type = request.GET.get('type')
|
question_type = request.GET.get('type')
|
||||||
quiz_id = request.GET.get('quiz_id')
|
quiz_id = request.GET.get('quiz_id')
|
||||||
question_data = None
|
|
||||||
|
|
||||||
if not quiz_id:
|
if not quiz_id:
|
||||||
messages.error(request, 'Quiz ID muss angegeben werden.')
|
messages.error(request, 'Quiz ID muss angegeben werden.')
|
||||||
@@ -864,12 +538,12 @@ def new_question(request):
|
|||||||
messages.error(request, 'Quiz nicht gefunden oder keine Berechtigung.')
|
messages.error(request, 'Quiz nicht gefunden oder keine Berechtigung.')
|
||||||
return redirect('library:overview_quiz')
|
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')
|
base_url = reverse('library:new_question')
|
||||||
return redirect(f'{base_url}?type=multiple_choice&quiz_id={quiz_id}')
|
return redirect(f'{base_url}?type=multiple_choice&quiz_id={quiz_id}')
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
question_text = request.POST.get('question', 'Kein Fragentext angegeben')
|
question_text = request.POST.get('question', '')
|
||||||
|
|
||||||
# Handle different question types
|
# Handle different question types
|
||||||
if question_type == 'true_false':
|
if question_type == 'true_false':
|
||||||
@@ -893,7 +567,7 @@ def new_question(request):
|
|||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
elif question_type == 'multiple_choice' :
|
elif question_type == 'multiple_choice':
|
||||||
options = []
|
options = []
|
||||||
i = 1
|
i = 1
|
||||||
# Limit to maximum 6 options
|
# Limit to maximum 6 options
|
||||||
@@ -917,59 +591,6 @@ def new_question(request):
|
|||||||
'options': options
|
'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 = QivipQuestion()
|
||||||
question.data = json.dumps(json_data)
|
question.data = json.dumps(json_data)
|
||||||
question.quiz_id = quiz
|
question.quiz_id = quiz
|
||||||
@@ -986,6 +607,7 @@ def new_question(request):
|
|||||||
question.credits=request.POST.get('credits',"")
|
question.credits=request.POST.get('credits',"")
|
||||||
except:
|
except:
|
||||||
question.credits=""
|
question.credits=""
|
||||||
|
|
||||||
question.save()
|
question.save()
|
||||||
#return modified(request, pk=quiz.pk)
|
#return modified(request, pk=quiz.pk)
|
||||||
return redirect('library:detail_quiz', pk=quiz.pk)
|
return redirect('library:detail_quiz', pk=quiz.pk)
|
||||||
@@ -1024,43 +646,6 @@ def new_question(request):
|
|||||||
}
|
}
|
||||||
standard_time_per_question = 30
|
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'
|
template_name = f'library/question/question_{question_type}.html'
|
||||||
context = {
|
context = {
|
||||||
'question': question_data,
|
'question': question_data,
|
||||||
@@ -1101,13 +686,6 @@ def edit_question(request, pk):
|
|||||||
{'value': 'Falsch', 'is_correct': not correct_answer}
|
{'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:
|
else:
|
||||||
# For multiple choice questions
|
# For multiple choice questions
|
||||||
question_data.setdefault('type', question_type)
|
question_data.setdefault('type', question_type)
|
||||||
@@ -1193,59 +771,6 @@ def edit_question(request, pk):
|
|||||||
'options': options
|
'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.data = json.dumps(json_data)
|
||||||
question.time_per_question = request.POST.get('time_per_question', '30')
|
question.time_per_question = request.POST.get('time_per_question', '30')
|
||||||
try:
|
try:
|
||||||
|
|||||||
573
django/package-lock.json
generated
573
django/package-lock.json
generated
@@ -10,11 +10,7 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/cli": "^4.1.5",
|
"@tailwindcss/cli": "^4.1.5",
|
||||||
"sortablejs": "^1.15.6",
|
|
||||||
"tailwindcss": "^4.1.5"
|
"tailwindcss": "^4.1.5"
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"license-checker": "^25.0.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@parcel/watcher": {
|
"node_modules/@parcel/watcher": {
|
||||||
@@ -541,55 +537,6 @@
|
|||||||
"node": ">= 10"
|
"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": {
|
"node_modules/braces": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
@@ -601,60 +548,6 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/detect-libc": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||||
@@ -666,16 +559,6 @@
|
|||||||
"node": ">=0.10"
|
"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": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.18.1",
|
"version": "5.18.1",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
|
||||||
@@ -688,15 +571,6 @@
|
|||||||
"node": ">=10.13.0"
|
"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": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -708,106 +582,11 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/graceful-fs": {
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
|
"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": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@@ -843,33 +622,6 @@
|
|||||||
"jiti": "lib/jiti-cli.mjs"
|
"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": {
|
"node_modules/lightningcss": {
|
||||||
"version": "1.29.2",
|
"version": "1.29.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
|
||||||
@@ -1107,39 +859,6 @@
|
|||||||
"node": ">=8.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": {
|
"node_modules/mri": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||||
@@ -1148,101 +867,11 @@
|
|||||||
"node": ">=4"
|
"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": {
|
"node_modules/node-addon-api": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
|
"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": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -1259,177 +888,6 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"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": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.5.tgz",
|
||||||
@@ -1453,37 +911,6 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0"
|
"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": "",
|
"description": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/cli": "^4.1.5",
|
"@tailwindcss/cli": "^4.1.5",
|
||||||
"sortablejs": "^1.15.6",
|
|
||||||
"tailwindcss": "^4.1.5"
|
"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 channels.db import database_sync_to_async
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.urls import reverse
|
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 django.conf import settings
|
||||||
from geopy.distance import geodesic
|
|
||||||
|
|
||||||
class GameConsumer(AsyncWebsocketConsumer):
|
class GameConsumer(AsyncWebsocketConsumer):
|
||||||
|
|
||||||
@@ -32,13 +31,6 @@ class GameConsumer(AsyncWebsocketConsumer):
|
|||||||
# Check if game should be deleted
|
# Check if game should be deleted
|
||||||
await self.cleanup_game()
|
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):
|
async def receive(self, text_data):
|
||||||
text_data_json = json.loads(text_data)
|
text_data_json = json.loads(text_data)
|
||||||
message_type = text_data_json['type']
|
message_type = text_data_json['type']
|
||||||
@@ -92,16 +84,14 @@ class GameConsumer(AsyncWebsocketConsumer):
|
|||||||
|
|
||||||
elif message_type == 'submit_answer':
|
elif message_type == 'submit_answer':
|
||||||
participant_id = text_data_json['participant_id']
|
participant_id = text_data_json['participant_id']
|
||||||
answer = text_data_json['answer']
|
try:
|
||||||
|
answer = text_data_json['answer']
|
||||||
|
except:
|
||||||
|
answer=-1
|
||||||
time_remaining = text_data_json.get('time_remaining', 0)
|
time_remaining = text_data_json.get('time_remaining', 0)
|
||||||
|
|
||||||
|
# Save answer and update participant score
|
||||||
question_data = await self.get_question_data()
|
await self.save_answer(participant_id, answer, time_remaining)
|
||||||
if question_data['type'] == "map":
|
|
||||||
await self.save_answer_map(participant_id, answer, time_remaining)
|
|
||||||
else:
|
|
||||||
await self.save_answer(participant_id, answer, time_remaining)
|
|
||||||
|
|
||||||
|
|
||||||
# Notify host about the new answer
|
# Notify host about the new answer
|
||||||
await self.channel_layer.group_send(
|
await self.channel_layer.group_send(
|
||||||
@@ -121,7 +111,7 @@ class GameConsumer(AsyncWebsocketConsumer):
|
|||||||
stats = await self.get_answer_stats()
|
stats = await self.get_answer_stats()
|
||||||
await self.send(text_data=json.dumps({
|
await self.send(text_data=json.dumps({
|
||||||
'type': 'answer_stats',
|
'type': 'answer_stats',
|
||||||
'stats': stats,
|
'stats': stats
|
||||||
}))
|
}))
|
||||||
|
|
||||||
elif message_type == 'update_participants':
|
elif message_type == 'update_participants':
|
||||||
@@ -138,18 +128,8 @@ class GameConsumer(AsyncWebsocketConsumer):
|
|||||||
host_id = text_data_json['host_id']
|
host_id = text_data_json['host_id']
|
||||||
if await self.verify_host(host_id):
|
if await self.verify_host(host_id):
|
||||||
await self.advance_to_next_question()
|
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':
|
elif message_type == 'finish_game':
|
||||||
|
|
||||||
host_id = text_data_json['host_id']
|
host_id = text_data_json['host_id']
|
||||||
if await self.verify_host(host_id):
|
if await self.verify_host(host_id):
|
||||||
await self.finish_game()
|
await self.finish_game()
|
||||||
@@ -194,107 +174,47 @@ class GameConsumer(AsyncWebsocketConsumer):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def save_answer_map(self, participant_id, answer_location, time_remaining):
|
def save_answer(self, participant_id, answer_index, time_remaining):
|
||||||
answer_index = 0
|
|
||||||
try:
|
try:
|
||||||
# Get all necessary data first
|
|
||||||
participant = QuizGameParticipant.objects.get(
|
participant = QuizGameParticipant.objects.get(
|
||||||
quiz_game__join_code=self.join_code,
|
quiz_game__join_code=self.join_code,
|
||||||
participant_id=participant_id
|
participant_id=participant_id
|
||||||
)
|
)
|
||||||
quiz_game = participant.quiz_game
|
quiz_game = participant.quiz_game
|
||||||
current_question = quiz_game.quiz_id.questions.all()[quiz_game.current_question_index]
|
current_question = quiz_game.quiz_id.questions.all()[quiz_game.current_question_index]
|
||||||
|
|
||||||
|
# Check if answer is correct
|
||||||
|
is_correct = False
|
||||||
question_data = json.loads(current_question.data)
|
question_data = json.loads(current_question.data)
|
||||||
target_location = question_data['target_location']
|
print(answer_index)
|
||||||
tolerance_radius = question_data.get('tolerance_radius', 1000) # Default 1km if not set
|
try:
|
||||||
|
if answer_index >= 0 : # -1 means timeout
|
||||||
|
is_correct = question_data['options'][answer_index]['is_correct']
|
||||||
|
print(is_correct)
|
||||||
|
except:
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
# 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
|
# Calculate score based on correctness and time
|
||||||
MAX_DISTANCE_KM = 50 # Maximum distance for scoring (in km)
|
score = 0
|
||||||
DIRECT_HIT_BONUS = 200 # Extra points for direct hit within tolerance
|
if is_correct:
|
||||||
|
base_score = 1000
|
||||||
|
time_factor = time_remaining / int(current_question.time_per_question)/int(1000)
|
||||||
|
score = int(base_score * (0.5 + 0.5 * time_factor))
|
||||||
|
|
||||||
try:
|
# Update or create answer in QuizAnswer table
|
||||||
# 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(
|
answer_obj, created = QuizAnswer.objects.get_or_create(
|
||||||
participant=participant,
|
participant=participant,
|
||||||
question_index=quiz_game.current_question_index,
|
question_index=quiz_game.current_question_index,
|
||||||
@@ -315,162 +235,32 @@ class GameConsumer(AsyncWebsocketConsumer):
|
|||||||
|
|
||||||
participant.last_score = score
|
participant.last_score = score
|
||||||
participant.score += score
|
participant.score += score
|
||||||
participant.coordinate_answer= answer_location_coords
|
|
||||||
participant.last_answer_correct = is_correct
|
participant.last_answer_correct = is_correct
|
||||||
participant.save()
|
participant.save()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except (QuizGameParticipant.DoesNotExist, IndexError):
|
except (QuizGameParticipant.DoesNotExist, IndexError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@database_sync_to_async
|
|
||||||
def save_answer(self, participant_id, answer, time_remaining):
|
|
||||||
try:
|
|
||||||
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)
|
|
||||||
question_type = question_data.get("type")
|
|
||||||
|
|
||||||
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":
|
|
||||||
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
|
|
||||||
except:
|
|
||||||
score_percentage=1
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Antwort speichern oder aktualisieren
|
|
||||||
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()
|
|
||||||
|
|
||||||
return True
|
|
||||||
except (QuizGameParticipant.DoesNotExist, IndexError):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def get_answer_stats(self):
|
def get_answer_stats(self):
|
||||||
try:
|
try:
|
||||||
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
||||||
answers = QuizAnswer.objects.filter(
|
|
||||||
participant__quiz_game=quiz_game,
|
|
||||||
question_index=quiz_game.current_question_index
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
return stats
|
||||||
|
except QuizGame.DoesNotExist:
|
||||||
|
return {}
|
||||||
|
|
||||||
@database_sync_to_async
|
@database_sync_to_async
|
||||||
def save_rating(self, participant_id, rating):
|
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})
|
'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
|
@database_sync_to_async
|
||||||
def _show_scores(self):
|
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
|
@database_sync_to_async
|
||||||
def _finish_game(self):
|
def _finish_game(self):
|
||||||
try:
|
try:
|
||||||
|
|
||||||
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
quiz_game = QuizGame.objects.get(join_code=self.join_code)
|
||||||
|
|
||||||
quiz_game.current_state = 'finished'
|
quiz_game.current_state = 'finished'
|
||||||
quiz_game.save()
|
quiz_game.save()
|
||||||
return True
|
return True
|
||||||
@@ -663,7 +409,6 @@ class GameConsumer(AsyncWebsocketConsumer):
|
|||||||
|
|
||||||
async def finish_game(self):
|
async def finish_game(self):
|
||||||
success = await self._finish_game()
|
success = await self._finish_game()
|
||||||
await self.counter_played_quizzes()
|
|
||||||
if success:
|
if success:
|
||||||
# Notify clients to redirect to finished page
|
# Notify clients to redirect to finished page
|
||||||
await self.channel_layer.group_send(
|
await self.channel_layer.group_send(
|
||||||
@@ -674,48 +419,3 @@ class GameConsumer(AsyncWebsocketConsumer):
|
|||||||
'redirect_url': reverse('play:finished', kwargs={'join_code': self.join_code})
|
'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'),
|
('lobby', 'In Lobby'),
|
||||||
('question', 'Frage läuft'),
|
('question', 'Frage läuft'),
|
||||||
('scores', 'Punkteübersicht'),
|
('scores', 'Punkteübersicht'),
|
||||||
('winner_podest','Siegerpodest'),
|
('finished', 'Beendet')
|
||||||
('finished', 'Beendet'),
|
|
||||||
('scoreboard', 'Scoreboard'),
|
|
||||||
|
|
||||||
]
|
]
|
||||||
single_player_or_multiple=models.CharField(max_length=20, blank=True)
|
|
||||||
host_id = models.CharField(max_length=200, unique=True)
|
host_id = models.CharField(max_length=200, unique=True)
|
||||||
join_code = models.CharField(max_length=6, unique=True, blank=True)
|
join_code = models.CharField(max_length=6, unique=True, blank=True)
|
||||||
quiz_id = models.ForeignKey(QivipQuiz, on_delete=models.CASCADE)
|
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="")
|
avatar = models.CharField(max_length=200, blank=True, default="")
|
||||||
last_heartbeat = models.DateTimeField(auto_now_add=True)
|
last_heartbeat = models.DateTimeField(auto_now_add=True)
|
||||||
last_answer = models.IntegerField(null=True, blank=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)
|
last_answer_correct = models.BooleanField(null=True, blank=True)
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
@@ -67,9 +63,7 @@ class QuizGameParticipant(models.Model):
|
|||||||
class QuizAnswer(models.Model):
|
class QuizAnswer(models.Model):
|
||||||
participant = models.ForeignKey(QuizGameParticipant, on_delete=models.CASCADE, related_name='answers')
|
participant = models.ForeignKey(QuizGameParticipant, on_delete=models.CASCADE, related_name='answers')
|
||||||
question_index = models.IntegerField()
|
question_index = models.IntegerField()
|
||||||
answer_index = models.IntegerField(null=True, blank=True) # für MC, TF
|
answer_index = models.IntegerField()
|
||||||
answer_text = models.TextField(null=True, blank=True) # für Input
|
|
||||||
answers_order = models.JSONField(null=True, blank=True) # NEU für "order"
|
|
||||||
is_correct = models.BooleanField()
|
is_correct = models.BooleanField()
|
||||||
score = models.IntegerField()
|
score = models.IntegerField()
|
||||||
time_remaining = 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>/waiting', views.waiting_room, name='waiting'),
|
||||||
path('game/<str:join_code>/scores', views.scores, name='scores'),
|
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>/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('select_mode/<str:join_code>', views.select_mode, name='select_mode'),
|
||||||
path('selected_mode/<str:join_code>', views.selected_mode, name='selected_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,
|
'question_index': quiz_game.current_question_index,
|
||||||
'total_questions': len(questions)
|
'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):
|
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):
|
def finished(request, join_code):
|
||||||
quiz_game = get_object_or_404(QuizGame, join_code=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')
|
return redirect('home')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def question(request, join_code):
|
def question(request, join_code):
|
||||||
quiz_game = get_object_or_404(QuizGame, join_code=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):
|
def selected_mode(request, join_code):
|
||||||
|
|
||||||
|
|
||||||
mode = request.GET.get('mode','default')
|
mode = request.GET.get('mode','default')
|
||||||
request.session['mode'] = mode
|
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_game = get_object_or_404(QuizGame, join_code=join_code)
|
||||||
quiz = get_object_or_404(QivipQuiz, pk=quiz_game.quiz_id.id)
|
quiz = get_object_or_404(QivipQuiz, pk=quiz_game.quiz_id.id)
|
||||||
QuizGameParticipant.objects.filter(quiz_game=quiz_game).delete()
|
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 = {
|
context = {
|
||||||
'quiz': quiz,
|
'quiz': quiz,
|
||||||
'quiz_game': quiz_game,
|
'quiz_game': quiz_game,
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
qp-container {
|
qp-container {
|
||||||
@apply max-w-xl w-full mx-auto;
|
@apply max-w-xl w-full mx-auto;
|
||||||
}
|
}
|
||||||
.dark-form label {
|
|
||||||
@apply dark:text-white!;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav div ul.qp-nav-list li {
|
nav div ul.qp-nav-list li {
|
||||||
@apply text-white hover:scale-110 hover:border-b-2 hover:border-white transition duration-200;
|
@apply text-white hover:scale-110 hover:border-b-2 hover:border-white transition duration-200;
|
||||||
@@ -56,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 {
|
ul.messages li {
|
||||||
@@ -153,12 +122,3 @@ div.qp-question-container {
|
|||||||
div.qp-question-container img {
|
div.qp-question-container img {
|
||||||
@apply max-w-[50vw] max-h-[40vh] mx-auto;
|
@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' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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">
|
<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"
|
<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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="text-2xl font-semibold text-gray-800 mb-2 dark:text-white ">Seite nicht gefunden</h2>
|
<h2 class="text-2xl font-semibold text-gray-800 mb-2 ">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>
|
<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">
|
<div class="flex justify-center">
|
||||||
<a href="{% url 'homepage:home' %}"
|
<a href="{% url 'homepage:home' %}"
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
|
||||||
<div class="flex justify-center items-center mt-12">
|
<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-full mb-4 flex-col items-center justify-between">
|
<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>
|
<h1 class="text-center m-4 text-3xl lg:text-6xl">Willkommen, <b>{{ request.user.username }}</b>!</h1>
|
||||||
@@ -18,16 +18,16 @@
|
|||||||
<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>
|
<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>
|
||||||
|
|
||||||
|
|
||||||
<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="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="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">
|
<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" />
|
<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>
|
</svg>
|
||||||
Erfolge
|
Erfolge
|
||||||
</div>
|
</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_quizzes_number }}</b> Quizze erstellt</li>
|
||||||
<li><b>{{ my_questions_number }}</b> Fragen insgesamt erstellt</li>
|
<li><b>{{ my_questions_number }}</b> Fragen insgesamt erstellt</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="grid place-content-center h-120 mt-12 mb-12 ">
|
<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">
|
<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">
|
<form class="space-y-4" method="post">
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
<div class=" items-center text-red-600 font-black overflow-x-auto break-words text-center">
|
<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="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">
|
<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">
|
<form class=" space-y-4" method="post">
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="de" class="dark:bg-gray-900 h-full">
|
<html lang="de">
|
||||||
<head>
|
<head>
|
||||||
<link rel="icon" type="image/png" href="{% static 'icons/favicon.png' %}">
|
<link rel="icon" type="image/png" href="{% static 'icons/favicon.png' %}">
|
||||||
<meta charset="UTF-8">
|
<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">
|
<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>
|
<script src="https://cdn.jsdelivr.net/npm/@tsparticles/confetti@3.0.3/tsparticles.confetti.bundle.min.js"></script>
|
||||||
<title>qivip</title>
|
<title>qivip</title>
|
||||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
|
||||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
|
||||||
crossorigin=""/>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="flex flex-col min-h-screen">
|
<body>
|
||||||
<main class="grow">
|
|
||||||
{% include 'partials/_nav.html' %}
|
{% include 'partials/_nav.html' %}
|
||||||
{% block content%}{% endblock %}
|
{% block content%}{% endblock %}
|
||||||
{% block faqs%}{% endblock %}
|
{% block faqs%}{% endblock %}
|
||||||
</main>
|
|
||||||
{% include 'partials/_footer.html' %}
|
{% include 'partials/_footer.html' %}
|
||||||
|
|
||||||
<!-- Toast Container -->
|
<!-- Toast Container -->
|
||||||
@@ -73,12 +68,12 @@
|
|||||||
}, duration);
|
}, duration);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (window.location.pathname !== "/library/") {
|
||||||
|
localStorage.setItem("meine_seite_scroll", 0);}
|
||||||
</script>
|
</script>
|
||||||
{% block extra_js %}{% endblock %}
|
{% block extra_js %}{% endblock %}
|
||||||
<style>html {
|
<style>html {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}</style>
|
}</style>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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="grid place-content-center md:h-screen h-150 w-full -z-50 top-0 p-4">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h2 class="font-bold md:text-8xl text-6xl md:mb-8 text-blue-600">qivip</h2>
|
<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>
|
||||||
<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-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-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>
|
<a class="qp-a-button bg-purple-500 text-white" href="{% url 'library:new_quiz' %}">Erstellen</a>
|
||||||
@@ -25,13 +25,13 @@
|
|||||||
{% if faqs %}
|
{% if faqs %}
|
||||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-8 mb-6">
|
||||||
<div class="flex items-center gap-3">
|
<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 class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||||
</div>
|
</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">
|
<h1 class="font-black text-blue-600 flex justify-between gap-2 lg:text-lg">
|
||||||
|
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for faq in faqs %}
|
{% 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>
|
<details>
|
||||||
<summary class="flex justify-between cursor-pointer font-bold">{{ faq.question }}
|
<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">
|
<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' %}
|
{% extends 'base.html' %}
|
||||||
{% block content%}
|
{% block content%}
|
||||||
|
|
||||||
<div class="flex justify-center items-center mt-12">
|
<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">
|
<div class="grid place-content-center h-120">
|
||||||
|
|
||||||
<h1 class="text-center m-4 text-3xl lg:text-6xl">Impressum</b></h1>
|
<h1 class="text-center m-4 text-3xl lg:text-6xl">Impressum</b></h1>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content%}
|
{% block content%}
|
||||||
<div class="flex justify-center items-center mt-12 ">
|
<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">
|
<div class="grid place-content-center h-120">
|
||||||
|
|
||||||
<h1 class="text-center m-4 text-3xl lg:text-6xl">Datenschutz-</br>erklärung</h1>
|
<h1 class="text-center m-4 text-3xl lg:text-6xl">Datenschutz-</br>erklärung</h1>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,15 +3,15 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="max-w-lg mx-auto mt-10">
|
<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">
|
<div class="text-center">
|
||||||
<svg class="mx-auto h-12 w-12 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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"/>
|
<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>
|
</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 %}
|
{% if object.quiz_id %}
|
||||||
Möchten Sie die Frage "{{ object.data.question }}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.
|
Möchten Sie die Frage "{{ object.data.question }}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -3,21 +3,22 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex flex-wrap space-y-2 items-center justify-between mb-6 dark:text-white">
|
|
||||||
<h1 class="text-2xl font-bold mr-4 dark:text-white ">{{ quiz.name }}</h1>
|
|
||||||
|
|
||||||
<div class=" flex flex-wrap space-x-2 ">
|
<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 flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
{% if quiz.user_id == request.user %}
|
{% if quiz.user_id == request.user %}
|
||||||
<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">
|
<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 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="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" />
|
<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>
|
</svg>
|
||||||
|
|
||||||
</a>
|
</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">
|
<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 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="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" />
|
<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>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
@@ -31,7 +32,7 @@
|
|||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
<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" />
|
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>
|
</svg>
|
||||||
Quiz duplizieren
|
Quiz kopieren
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if quiz.questions.count != 0 %}
|
{% if quiz.questions.count != 0 %}
|
||||||
@@ -50,17 +51,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if quiz.description %}
|
{% if quiz.description %}
|
||||||
<p class="text-gray-600 mb-4 dark:text-gray-400">{{ quiz.description }}</p>
|
<p class="text-gray-600 mb-4">{{ quiz.description }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="flex justify-end mb-4">
|
<div class="flex justify-end mb-4">
|
||||||
{% if detail == True %}
|
{% if detail == True %}
|
||||||
|
|
||||||
<a href="{% url 'library:detail_quiz' quiz.id %}?show_answers=false">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
|
|
||||||
<a href="{% url 'library:detail_quiz' quiz.id %}?show_answers=true">
|
<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">
|
<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="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" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="bg-white rounded-lg shadow-md border-blue-600 border-2 rounded-xl shadow-md 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="border-b border-gray-200 p-4">
|
||||||
<div class="flex justify-between items-center flex-wrap">
|
<div class="flex justify-between items-center flex-wrap">
|
||||||
<h2 class="text-xl font-semibold">Fragen
|
<h2 class="text-xl font-semibold">Fragen
|
||||||
@@ -97,31 +97,20 @@
|
|||||||
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">
|
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
|
Eingabe
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if quiz.user_id == request.user %}
|
||||||
<a href="{% url 'library:new_question' %}?type=multiple_choice&quiz_id={{ quiz.id }}"
|
<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">
|
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
|
Multiple Choice
|
||||||
</a>
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if quiz.user_id == request.user %}
|
||||||
<a href="{% url 'library:new_question' %}?type=true_false&quiz_id={{ quiz.id }}"
|
<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">
|
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
|
Wahr/Falsch
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'library:new_question' %}?type=order&quiz_id={{ quiz.id }}"
|
|
||||||
class=" mt-1 bg-yellow-100 text-yellow-800 px-4 py-2 rounded-md text-xs lg:text-lg hover:border-blue-600 border-2">
|
|
||||||
Reihenfolge
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="{% url 'library:new_question' %}?type=guess&quiz_id={{ quiz.id }}"
|
|
||||||
class=" mt-1 bg-pink-100 text-pink-800 px-4 py-2 rounded-md text-xs lg:text-lg hover:border-blue-600 border-2">
|
|
||||||
Schätzen
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="{% url 'library:new_question' %}?type=map&quiz_id={{ quiz.id }}"
|
|
||||||
class=" mt-1 bg-orange-100 text-orange-800 px-4 py-2 rounded-md text-xs lg:text-lg hover:border-blue-600 border-2">
|
|
||||||
Karte
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,7 +123,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="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>
|
<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 %}
|
{% 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">
|
<div class="flex justify-between items-start">
|
||||||
@@ -142,14 +131,8 @@
|
|||||||
<div class="flex-grow">
|
<div class="flex-grow">
|
||||||
<p class="font-medium">{{ question.data.question }}</p>
|
<p class="font-medium">{{ question.data.question }}</p>
|
||||||
<div class="mt-1">
|
<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 %}">
|
<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
|
{% if question.data.type == 'multiple_choice' %}Multiple Choice{% elif question.data.type == 'input' %}Eingabe{% else %}Wahr/Falsch{% endif %}
|
||||||
{% 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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
@@ -158,10 +141,10 @@
|
|||||||
{% if question.data.type == 'multiple_choice' %}
|
{% if question.data.type == 'multiple_choice' %}
|
||||||
<div class="w-full gap-4 md:flex justify-between ">
|
<div class="w-full gap-4 md:flex justify-between ">
|
||||||
<ul class="space-y-1 ">
|
<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 %}
|
{% 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">
|
<svg class="h-4 w-4 text-green-500 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -169,10 +152,10 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="font-medium text-green-700">{{ option.value }}</span>
|
<span class="font-medium text-green-700">{{ option.value }}</span>
|
||||||
{% else %}
|
{% 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"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 12H6"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-gray-600 dark:text-white">{{ option.value }}</span>
|
<span class="text-gray-600">{{ option.value }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -182,58 +165,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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' %}
|
{% elif question.data.type == 'input' %}
|
||||||
<div class="w-full gap-4 md:flex justify-between ">
|
<div class="w-full gap-4 md:flex justify-between ">
|
||||||
<ul class="space-y-1 ">
|
<ul class="space-y-1 ">
|
||||||
@@ -242,23 +173,7 @@
|
|||||||
<svg class="h-4 w-4 text-green-500 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||||
</svg>
|
</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>
|
|
||||||
{% 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>
|
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -271,7 +186,7 @@
|
|||||||
{% for option in question.data.options %}
|
{% for option in question.data.options %}
|
||||||
{% if option.is_correct %}
|
{% if option.is_correct %}
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
<span class="text-gray-500 dark:text-white">Richtige Antwort:</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>
|
<span class="ml-1 font-medium {% if option.value == "Wahr" %} text-green-700 {% else %}text-red-700{% endif %}">{{ option.value }}</span>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -294,7 +209,7 @@
|
|||||||
<a href=" {% url 'library:delete_question' question.id %}"
|
<a href=" {% url 'library:delete_question' question.id %}"
|
||||||
class=" text-red-700 px-4 py-1 rounded hover:scale-110 ">
|
class=" text-red-700 px-4 py-1 rounded hover:scale-110 ">
|
||||||
{% if quiz.user_id == request.user %}
|
{% 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" />
|
<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>
|
</svg>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -321,125 +236,44 @@
|
|||||||
|
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
{% if quiz.base_quiz %}
|
{% if quiz.base_quiz %}
|
||||||
<p class="text-gray-600 mt-2 dark:text-white">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>
|
<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 %}
|
{% else %}
|
||||||
<p class="text-gray-600 mt-2 dark:text-white">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>
|
<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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
{% if quiz.base_quiz %}
|
{% 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="text-blue-600 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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
{% if quiz.base_quiz %}
|
{% if quiz.base_quiz %}
|
||||||
<p class="text-gray-600 dark:text-white">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>
|
<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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Links müssen wieder ergänzt werden!
|
|
||||||
{% if questions %}
|
|
||||||
<div class="flex flex-col sm:flex-row gap-4 items-start sm:items-center my-6">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<a href=""
|
|
||||||
target="_blank"
|
|
||||||
class=" inline-flex items-center gap-2 px-2 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 text-white shadow-md transition">
|
|
||||||
<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="M12 4v16m8-8H4" />
|
|
||||||
</svg>
|
|
||||||
als Aufgabenblatt herunterladen (PDF)
|
|
||||||
</a>
|
|
||||||
|
|
||||||
|
|
||||||
<a href=""
|
|
||||||
target="_blank"
|
|
||||||
class="inline-flex items-center gap-2 px-2 py-2 rounded-lg bg-green-600 hover:bg-green-700 text-white shadow-md transition">
|
|
||||||
<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="M12 4v16m8-8H4" />
|
|
||||||
</svg>
|
|
||||||
Lösungen herunterladen (PDF)
|
|
||||||
</a>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
-->
|
|
||||||
|
|
||||||
<div class="max-w-full p-4 mt-8 rounded-2xl bg-gray-100 dark:bg-transparent shadow-sm">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="flex flex-col max-w-full">
|
<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:
|
||||||
Credits
|
|
||||||
</p>
|
|
||||||
{% if quiz.credits %}
|
{% if quiz.credits %}
|
||||||
<p class="text-blue-500 mb-2 w-full break-words dark:text-gray-400 ">
|
<p class="text-gray-600 mb-2 w-full break-words">
|
||||||
<span class="break-words italic">{{ quiz.credits }}</span>
|
<span class="font-bold break-words">{{ quiz.credits }}</span>
|
||||||
<span class="font-normal break-words text-gray-600 dark:text-white">(Credits für das Quiz)</span>
|
|
||||||
</p>
|
</p>
|
||||||
{% else %}
|
|
||||||
<p class="text-gray-500 dark:text-gray-400 ">Keine Credits für das Quizformular vorhanden.</p>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="flex flex-col max-w-full">
|
<div class="flex flex-col max-w-full">
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
{% regroup questions by credits as grouped_credits %}
|
{% for question in questions %}
|
||||||
{% if grouped_credits|length == 1 and not grouped_credits.0.grouper %}
|
{% if question.credits %}
|
||||||
<li class="text-gray-500 dark:text-gray-400 ">Keine zusätzlichen Credits für die einzelnen Fragen vorhanden.</li>
|
<li class="text-gray-600 mb-2 w-full break-words font-bold break-words">
|
||||||
{% else %}
|
{{ question.credits }}
|
||||||
{% for question in questions %}
|
<span class="font-medium text-gray-500 break-words">(Frage: {{ forloop.counter }})</span>
|
||||||
{% if question.credits %}
|
</li>
|
||||||
<li class="text-blue-500 mb-2 w-full break-words break-words dark:text-gray-400 ">
|
{% endif %}
|
||||||
<span class="italic">{{ question.credits }}</span>
|
{% endfor %}
|
||||||
<span class="font-normal break-words text-gray-600 dark:text-white">(Frage: {{ forloop.counter }})</span>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<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">
|
|
||||||
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>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="input-group border-blue-600 border-2 rounded-xl shadow-md mt-12">
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
|
||||||
<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 %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
{{ form.as_p }}
|
||||||
|
|
||||||
@@ -20,7 +15,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<label class="font-bold" for="quiz_image">Bild:</label>
|
<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>
|
||||||
|
|
||||||
<div class="text-center text-gray-600 text-sm dislay-flex align-bottom">
|
<div class="text-center text-gray-600 text-sm dislay-flex align-bottom">
|
||||||
@@ -39,5 +34,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -12,14 +12,14 @@
|
|||||||
|
|
||||||
{% block head %}
|
{% block head %}
|
||||||
<div class="container mx-auto px-4 py-4 flex justify-between items-center">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
<span>Filter</span>
|
<span>Filter</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="flex gap-2">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
@@ -35,13 +35,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="filterPanel" class="container mx-auto px-4 {% if request.GET.filter == '1' %}block{% else %}hidden{% endif %}">
|
<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">
|
<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">
|
<input type="hidden" name="filter" id="filter-input">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex gap-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 %}>
|
<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>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
@@ -51,22 +51,22 @@
|
|||||||
max="100"
|
max="100"
|
||||||
step="1"
|
step="1"
|
||||||
value="{{ form.min_amount_questions.value|default_if_none:0 }}"
|
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)'"
|
oninput="document.getElementById('min_amount_value').textContent = this.value + ' Frage(n)'"
|
||||||
{% if not form.use_min_amount_questions.value %}disabled{% endif %}
|
{% 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)
|
{{ form.min_amount_questions.value|default_if_none:0 }} Frage(n)
|
||||||
</span>
|
</span>
|
||||||
<br>
|
<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>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex gap-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 %}>
|
<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>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
@@ -81,22 +81,22 @@
|
|||||||
{% if not form.use_max_amount_questions.value %}disabled{% endif %}
|
{% 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)
|
{{ form.max_amount_questions.value|default_if_none:100 }} Frage(n)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-white">veröffentlicht von User</label>
|
<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 dark:text-white" id="id_user" value="{{ form.user.value|default_if_none:'' }}">
|
<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>
|
||||||
<div class="space-y-2">
|
<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 }}
|
{{ form.categories }}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<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 }}
|
{{ form.search }}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,28 +18,20 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<!-- Alle -->
|
<!-- 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">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
<div class="gap-2 mx-2">
|
<div class="gap-2 mx-2">
|
||||||
{% if user.is_authenticated %}
|
|
||||||
Anzahl aller Ergebnisse:
|
Anzahl aller Ergebnisse:
|
||||||
<span class="font-bold"> {{ total_all_found }}</span>
|
<span class="font-bold"> {{ total_all_found }}</span>
|
||||||
</div>
|
</div>
|
||||||
(verschiedene:
|
(verschiedene:
|
||||||
<span class="font-bold ml-1"> {{ total_all }}</span>)
|
<span class="font-bold ml-1"> {{ total_all }}</span>) </div>
|
||||||
{% else %}
|
|
||||||
Anzahl aller Ergebnisse:
|
|
||||||
<span class="font-bold"> {{ total_all_found }}</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</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">
|
<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>
|
<p class="font-black underline">Alle ({{ total_all }})</p>
|
||||||
<a href="{{ url_my }}?{{ request.GET.urlencode }}">Meine Quizze ({{ total_my }})</a>
|
<a href="{{ url_my }}?{{ request.GET.urlencode }}">Meine Quizze ({{ total_my }})</a>
|
||||||
@@ -48,7 +40,7 @@
|
|||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="flex items-center gap-3">
|
<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 class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -60,7 +52,7 @@
|
|||||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
<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">
|
<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 %}
|
{% for quiz in all_quizzes %}
|
||||||
<article class="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) -->
|
<!-- 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 %}
|
{% if quiz.image %}
|
||||||
@@ -78,17 +70,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
{% if quiz.id not in favorite_quiz_ids %}
|
{% 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 %}">
|
<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" />
|
<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>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% 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 %}">
|
<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">
|
<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" />
|
<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,27 +89,16 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% 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>
|
</div>
|
||||||
|
|
||||||
<!-- Content Section (Bottom) -->
|
<!-- 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 -->
|
<!-- Title -->
|
||||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
<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>
|
</h2>
|
||||||
<!-- Description -->
|
<!-- 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 -->
|
<!-- Quiz Info Grid -->
|
||||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
||||||
@@ -126,7 +107,7 @@
|
|||||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-gray-800 dark:text-white">{{ quiz.difficulty }}</span>
|
<span class="text-gray-800">{{ quiz.difficulty }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Questions Count -->
|
<!-- Questions Count -->
|
||||||
@@ -134,7 +115,7 @@
|
|||||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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" />
|
<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>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
@@ -142,14 +123,11 @@
|
|||||||
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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" />
|
<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>
|
</svg>
|
||||||
<span class="text-gray-800 dark:text-white">{{ quiz.status }}</span>
|
<span class="text-gray-800">{{ quiz.status }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Rating -->
|
<!-- Rating -->
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
{% if quiz.rating_count > 0 %}
|
{% if quiz.rating_count > 0 %}
|
||||||
@@ -160,9 +138,9 @@
|
|||||||
<span class="text-gray-300 text-base">★</span>
|
<span class="text-gray-300 text-base">★</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% 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 %}
|
{% 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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -187,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">
|
<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" />
|
<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>
|
</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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
<!-- 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 %}
|
{% if quiz.questions.count != 0 %}
|
||||||
<a href="{% url 'play:create_game' quiz.id %}"
|
<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">
|
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">
|
||||||
@@ -208,26 +186,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
<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">
|
<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="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" />
|
<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>
|
</svg>
|
||||||
</a>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -247,7 +211,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% 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 %}
|
{% if all_quizzes.has_next %}
|
||||||
<a href="?all_page={{ all_quizzes.next_page_number }}&{{ querystring_all }}"
|
<a href="?all_page={{ all_quizzes.next_page_number }}&{{ querystring_all }}"
|
||||||
@@ -262,7 +226,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-4">
|
<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>
|
</div>
|
||||||
{% endif %}<script>
|
{% endif %}<script>
|
||||||
|
|
||||||
|
|||||||
@@ -22,14 +22,14 @@
|
|||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% 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">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
<div class="gap-2 mx-2 ">
|
<div class="gap-2 mx-2">
|
||||||
Anzahl aller Ergebnisse:
|
Anzahl aller Ergebnisse:
|
||||||
<span class="font-bold"> {{ total_all_found }}</span>
|
<span class="font-bold"> {{ total_all_found }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class="flex items-center gap-3">
|
<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 class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,9 +54,9 @@
|
|||||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
<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">
|
<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 %}
|
{% for quiz in favorite_quizzes %}
|
||||||
<article class="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) -->
|
<!-- 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 %}
|
{% if quiz.image %}
|
||||||
<div class="relative w-full h-full inset-0 bg-cover bg-center">
|
<div class="relative w-full h-full inset-0 bg-cover bg-center">
|
||||||
<!-- Hintergrundbild -->
|
<!-- Hintergrundbild -->
|
||||||
@@ -72,17 +72,17 @@
|
|||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
{% if quiz.favorite == False %}
|
{% 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 %}">
|
<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" />
|
<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>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% 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 %}">
|
<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">
|
<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" />
|
<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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% 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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Content Section (Bottom) -->
|
<!-- 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 -->
|
<!-- Title -->
|
||||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
<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>
|
</h2>
|
||||||
<!-- Description -->
|
<!-- 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 -->
|
<!-- Quiz Info Grid -->
|
||||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
<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">
|
<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" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-gray-800 dark:text-white">{{ quiz.difficulty }}</span>
|
<span class="text-gray-800">{{ quiz.difficulty }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Questions Count -->
|
<!-- Questions Count -->
|
||||||
@@ -131,7 +119,7 @@
|
|||||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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" />
|
<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>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
@@ -139,7 +127,7 @@
|
|||||||
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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" />
|
<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>
|
</svg>
|
||||||
<span class="text-gray-800 dark:text-white">{{ quiz.status }}</span>
|
<span class="text-gray-800">{{ quiz.status }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rating -->
|
<!-- Rating -->
|
||||||
@@ -152,9 +140,9 @@
|
|||||||
<span class="text-gray-300 text-base">★</span>
|
<span class="text-gray-300 text-base">★</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% 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 %}
|
{% 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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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" />
|
<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>
|
</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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -202,7 +190,7 @@
|
|||||||
|
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
<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">
|
<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="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" />
|
<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>
|
</a>
|
||||||
{% if quiz.user_id == request.user %}
|
{% if quiz.user_id == request.user %}
|
||||||
<a href="{% url 'library:edit_quiz' quiz.id %}"
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'library:delete_quiz' quiz.id %}"
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
@@ -230,9 +218,9 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pagination für favorite Quizze -->
|
<!-- Pagination für Meine Quizze -->
|
||||||
{% if favorite_quizzes.paginator.num_pages > 1 %}
|
{% 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 %}
|
{% if favorite_quizzes.has_previous %}
|
||||||
<a href="?favorite_page={{ favorite.previous_page_number }}&{{ querystring_favorite }}"
|
<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">
|
class="px-3 py-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded-lg transition-colors">
|
||||||
@@ -242,7 +230,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% 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 %}
|
{% if favorite_quizzes.has_next %}
|
||||||
<a href="?favorite_page={{ favorite_quizzes.next_page_number }}&{{ querystring_favorite }}"
|
<a href="?favorite_page={{ favorite_quizzes.next_page_number }}&{{ querystring_favorite }}"
|
||||||
@@ -257,7 +245,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-4">
|
<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>
|
</div>
|
||||||
{% endif %}<script>
|
{% endif %}<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% 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">
|
<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">
|
<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>
|
<a href="{{ url_favorite }}?{{ request.GET.urlencode }}">Meine Favoriten ({{ total_favorite }})</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<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 class="h-0.5 flex-grow bg-gradient-to-r from-blue-600 to-transparent rounded-full"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,19 +69,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
{% if quiz.id not in favorite_quiz_ids %}
|
{% 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 %}">
|
<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" />
|
<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>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
{% 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 %}">
|
<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">
|
<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" />
|
<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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% 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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Content Section (Bottom) -->
|
<!-- 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 -->
|
<!-- Title -->
|
||||||
<h2 class="text-xl font-bold text-gray-900 break-words mb-2">
|
<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>
|
</h2>
|
||||||
<!-- Description -->
|
<!-- 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 -->
|
<!-- Quiz Info Grid -->
|
||||||
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-sm">
|
<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">
|
<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" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-gray-800 dark:text-white">{{ quiz.difficulty }}</span>
|
<span class="text-gray-800">{{ quiz.difficulty }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Questions Count -->
|
<!-- Questions Count -->
|
||||||
@@ -128,7 +116,7 @@
|
|||||||
<svg class="w-4 h-4 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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" />
|
<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>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
@@ -136,7 +124,7 @@
|
|||||||
<svg class="w-4 h-4 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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" />
|
<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>
|
</svg>
|
||||||
<span class="text-gray-800 dark:text-white">{{ quiz.status }}</span>
|
<span class="text-gray-800">{{ quiz.status }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Rating -->
|
<!-- Rating -->
|
||||||
@@ -149,9 +137,9 @@
|
|||||||
<span class="text-gray-300 text-base">★</span>
|
<span class="text-gray-300 text-base">★</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% 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 %}
|
{% 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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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" />
|
<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>
|
</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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -200,7 +188,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<a href="{% url 'library:detail_quiz' quiz.id %}"
|
<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">
|
<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="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" />
|
<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>
|
</a>
|
||||||
{% if quiz.user_id == request.user %}
|
{% if quiz.user_id == request.user %}
|
||||||
<a href="{% url 'library:edit_quiz' quiz.id %}"
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'library:delete_quiz' quiz.id %}"
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
@@ -240,7 +228,7 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% 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 %}
|
{% if my_quizzes.has_next %}
|
||||||
<a href="?my_page={{ my_quizzes.next_page_number }}&{{ querystring_my }}"
|
<a href="?my_page={{ my_quizzes.next_page_number }}&{{ querystring_my }}"
|
||||||
@@ -255,7 +243,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8 mt-4">
|
<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>
|
</div>
|
||||||
{% endif %}<script>
|
{% endif %}<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
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 %}
|
{% block content %}
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex justify-between items-center mb-6">
|
<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 %}"
|
<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">
|
class="bg-gray-100 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-200 transition-colors">
|
||||||
Zurück zum Quiz
|
Zurück zum Quiz
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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">
|
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<!-- Question Text -->
|
<!-- Question Text -->
|
||||||
<div>
|
<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">
|
<div class="mt-1">
|
||||||
<textarea name="question" id="question" rows="4"
|
<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>
|
required>{{ question.data.question }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,17 +33,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<label class="font-bold" for="question_image">Bild:</label>
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Eingabe -->
|
<!-- Eingabe -->
|
||||||
{% if question and question.data.options %}
|
{% if question and question.data.options %}
|
||||||
{% for option in question.data.options %}
|
{% for option in question.data.options %}
|
||||||
<div>
|
<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">
|
<div class="flex space-x-4">
|
||||||
<textarea name="correct_answer" rows="2"
|
<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>
|
required>{{ option.value }}</textarea>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -52,10 +52,10 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div>
|
<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">
|
<div class="flex space-x-4">
|
||||||
<textarea name="correct_answer" rows="2"
|
<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>
|
required></textarea>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
|
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<label class="font-bold" for="time_per_question">Zeit pro Frage:</label>
|
<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="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="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="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>
|
<option value="300" {% if standard_time_per_question == "300" or time_per_question == "300" %}selected{% endif %}>5 Minuten</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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">
|
<div class="mt-1">
|
||||||
<textarea name="credits" rows="4"
|
<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>
|
>{{ credits }}</textarea>
|
||||||
</div>
|
</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 %}
|
{% block content %}
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex justify-between items-center mb-6">
|
<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 %}"
|
<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">
|
class="bg-gray-100 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-200 transition-colors">
|
||||||
Zurück zum Quiz
|
Zurück zum Quiz
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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">
|
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<!-- Question Text -->
|
<!-- Question Text -->
|
||||||
<div>
|
<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">
|
<div class="mt-1">
|
||||||
<textarea name="question" id="question" rows="4"
|
<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>
|
required>{{ question.data.question|default:'' }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,24 +33,24 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<label class="font-bold" for="question_image">Bild:</label>
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Options -->
|
<!-- Options -->
|
||||||
<div>
|
<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">
|
<div id="options-container" class="space-y-3">
|
||||||
{% if question and question.data.options %}
|
{% 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">
|
<div class="flex items-center space-x-3">
|
||||||
<textarea name="option_{{ forloop.counter }}" rows="2"
|
<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>
|
required>{{ option.value }}</textarea>
|
||||||
<label class="inline-flex items-center">
|
<label class="inline-flex items-center">
|
||||||
<input type="checkbox" name="correct_{{ forloop.counter }}" value="1"
|
<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 %}>
|
{% 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>
|
</label>
|
||||||
<button type="button" onclick="removeOption(this)"
|
<button type="button" onclick="removeOption(this)"
|
||||||
class="text-red-600 hover:text-red-800 transition-colors">
|
class="text-red-600 hover:text-red-800 transition-colors">
|
||||||
@@ -63,12 +63,12 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<textarea name="option_1" rows="2"
|
<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>
|
required></textarea>
|
||||||
<label class="inline-flex items-center">
|
<label class="inline-flex items-center">
|
||||||
<input type="checkbox" name="correct_1" value="1"
|
<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">
|
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 dark:text-white">Richtig</span>
|
<span class="ml-2 text-sm text-gray-600">Richtig</span>
|
||||||
</label>
|
</label>
|
||||||
<button type="button" onclick="removeOption(this)"
|
<button type="button" onclick="removeOption(this)"
|
||||||
class="text-red-600 hover:text-red-800 transition-colors">
|
class="text-red-600 hover:text-red-800 transition-colors">
|
||||||
@@ -79,12 +79,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<textarea name="option_2" rows="2"
|
<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>
|
required></textarea>
|
||||||
<label class="inline-flex items-center">
|
<label class="inline-flex items-center">
|
||||||
<input type="checkbox" name="correct_2" value="1"
|
<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">
|
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 dark:text-white">Richtig</span>
|
<span class="ml-2 text-sm text-gray-600">Richtig</span>
|
||||||
</label>
|
</label>
|
||||||
<button type="button" onclick="removeOption(this)"
|
<button type="button" onclick="removeOption(this)"
|
||||||
class="text-red-600 hover:text-red-800 transition-colors">
|
class="text-red-600 hover:text-red-800 transition-colors">
|
||||||
@@ -95,12 +95,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<textarea name="option_3" rows="2"
|
<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>
|
required></textarea>
|
||||||
<label class="inline-flex items-center">
|
<label class="inline-flex items-center">
|
||||||
<input type="checkbox" name="correct_3" value="1"
|
<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">
|
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 dark:text-white">Richtig</span>
|
<span class="ml-2 text-sm text-gray-600">Richtig</span>
|
||||||
</label>
|
</label>
|
||||||
<button type="button" onclick="removeOption(this)"
|
<button type="button" onclick="removeOption(this)"
|
||||||
class="text-red-600 hover:text-red-800 transition-colors">
|
class="text-red-600 hover:text-red-800 transition-colors">
|
||||||
@@ -111,12 +111,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<textarea name="option_4" rows="2"
|
<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>
|
required></textarea>
|
||||||
<label class="inline-flex items-center">
|
<label class="inline-flex items-center">
|
||||||
<input type="checkbox" name="correct_4" value="1"
|
<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">
|
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 dark:text-white">Richtig</span>
|
<span class="ml-2 text-sm text-gray-600">Richtig</span>
|
||||||
</label>
|
</label>
|
||||||
<button type="button" onclick="removeOption(this)"
|
<button type="button" onclick="removeOption(this)"
|
||||||
class="text-red-600 hover:text-red-800 transition-colors">
|
class="text-red-600 hover:text-red-800 transition-colors">
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
|
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<label class="font-bold" for="time_per_question">Zeit pro Frage:</label>
|
<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="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="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="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>
|
<option value="300" {% if standard_time_per_question == "300" or time_per_question == "300" %}selected{% endif %}>5 Minuten</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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">
|
<div class="mt-1">
|
||||||
<textarea name="credits" rows="4"
|
<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>
|
>{{ credits }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -182,21 +182,20 @@ function addOption() {
|
|||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'flex items-center space-x-3';
|
div.className = 'flex items-center space-x-3';
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<textarea name="option_${optionCount}" rows="2"
|
<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"
|
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>
|
required></textarea>
|
||||||
<label class="inline-flex items-center">
|
<label class="inline-flex items-center">
|
||||||
<input type="checkbox" name="correct_${optionCount}" value="1"
|
<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"
|
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">Richtig</span>
|
||||||
<span class="ml-2 text-sm text-gray-600 dark:text-white">Richtig</span>
|
</label>
|
||||||
</label>
|
<button type="button" onclick="removeOption(this)"
|
||||||
<button type="button" onclick="removeOption(this)"
|
class="text-red-600 hover:text-red-800 transition-colors">
|
||||||
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">
|
||||||
<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"/>
|
||||||
<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>
|
||||||
</svg>
|
</button>
|
||||||
</button>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
container.appendChild(div);
|
container.appendChild(div);
|
||||||
|
|||||||
@@ -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 %}
|
{% block content %}
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex justify-between items-center mb-6">
|
<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 %}"
|
<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">
|
class="bg-gray-100 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-200 transition-colors">
|
||||||
Zurück zum Quiz
|
Zurück zum Quiz
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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">
|
<form method="post" class="space-y-6" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<!-- Question Text -->
|
<!-- Question Text -->
|
||||||
<div>
|
<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">
|
<div class="mt-1">
|
||||||
<textarea name="question" id="question" rows="4"
|
<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>
|
required>{{ question.data.question }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,31 +33,31 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<label class="font-bold" for="question_image">Bild:</label>
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- True/False Selection -->
|
<!-- True/False Selection -->
|
||||||
<div>
|
<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">
|
<div class="flex space-x-4">
|
||||||
<label class="inline-flex items-center">
|
<label class="inline-flex items-center">
|
||||||
<input type="radio" name="correct_answer" value="true"
|
<input type="radio" name="correct_answer" value="true"
|
||||||
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300"
|
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300"
|
||||||
{% if question.data.correct_answer %}checked{% endif %}>
|
{% 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>
|
||||||
<label class="inline-flex items-center">
|
<label class="inline-flex items-center">
|
||||||
<input type="radio" name="correct_answer" value="false"
|
<input type="radio" name="correct_answer" value="false"
|
||||||
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300"
|
class="focus:ring-blue-500 h-4 w-4 text-blue-600 border-gray-300"
|
||||||
{% if not question.data.correct_answer %}checked{% endif %}>
|
{% 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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<label class="font-bold" for="time_per_question">Zeit pro Frage:</label>
|
<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="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="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="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>
|
<option value="300" {% if standard_time_per_question == "300" or time_per_question == "300" %}selected{% endif %}>5 Minuten</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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">
|
<div class="mt-1">
|
||||||
<textarea name="credits" rows="4"
|
<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>
|
>{{ credits }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +1,7 @@
|
|||||||
<footer class="w-full bg-blue-600 text-white shadow-xl py-6 mt-24 break-words">
|
<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>
|
||||||
<div class="flex items-center justify-center flex-col sm:flex-row sm:gap-32">
|
<p class="text-sm font-extralight"><a href="https://edugit.org/enrichment-2024/qivip">Repository</a></p>
|
||||||
|
|
||||||
<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>
|
</div>
|
||||||
</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>
|
|
||||||
@@ -9,10 +9,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Desktop Search (centered on screen) - Hidden on mobile -->
|
<!-- 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 ">
|
<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">
|
<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 }}"
|
<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">
|
<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">
|
<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" />
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile Navigation Menu - Hidden by default - FULLWIDTH DROPDOWN -->
|
<!-- 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">
|
<div class="bg-blue-500 pt-3 pb-4 rounded-b-lg shadow-inner">
|
||||||
<!-- Search in mobile menu -->
|
<!-- Search in mobile menu -->
|
||||||
<form method="get" action="{% url link|default:'library:overview_quiz' %}" class="px-4 mb-4 ">
|
<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">
|
<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 }}"
|
<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">
|
<button class="text-blue-600">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
stroke="currentColor" class="size-5">
|
stroke="currentColor" class="size-5">
|
||||||
|
|||||||
@@ -4,16 +4,16 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="max-w-2xl mx-auto">
|
<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">
|
<div class="text-center mb-8">
|
||||||
<h1 class="text-4xl font-bold text-blue-600 mb-4">Quiz beendet!</h1>
|
<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>
|
</div>
|
||||||
|
|
||||||
{% if not is_host %}
|
{% if not is_host %}
|
||||||
{% if rating_enabled %}
|
{% if rating_enabled %}
|
||||||
<div class="bg-blue-50 rounded-lg p-6 mb-8 dark:bg-transparent ">
|
<div class="bg-blue-50 rounded-lg p-6 mb-8">
|
||||||
<h2 class="text-2xl font-bold text-blue-800 mb-4 text-center dark:text-white">Wie hat dir das Quiz gefallen?</h2>
|
<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">
|
<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="1"></i>
|
||||||
<i class="fas fa-star text-3xl transition-all duration-200 hover:scale-110" data-rating="2"></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' %}
|
{% extends 'base.html' %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% load static %}
|
|
||||||
<script src="{% static 'js/sortablejs/Sortable.min.js' %}"></script>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
@@ -13,17 +11,17 @@
|
|||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% 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">
|
<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>
|
</button>
|
||||||
</div>
|
</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 justify-between mb-2">
|
||||||
<div class="flex gap-1 text-sm"><p class="text-sm">Frage</p>
|
<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-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>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -50,7 +48,7 @@
|
|||||||
|
|
||||||
|
|
||||||
</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">
|
<div class="grid grid-cols-2 gap-4" id="options-container">
|
||||||
{% for option in question_data.options %}
|
{% for option in question_data.options %}
|
||||||
<button
|
<button
|
||||||
@@ -83,201 +81,25 @@
|
|||||||
onclick="submitAnswer({{ forloop.counter0 }});" {% endif %}>
|
onclick="submitAnswer({{ forloop.counter0 }});" {% endif %}>
|
||||||
<p class="text-lg">{{ option.value }}</p>
|
<p class="text-lg">{{ option.value }}</p>
|
||||||
</button>
|
</button>
|
||||||
|
{% endfor %}
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
{% 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" %}
|
{% if request.session.mode == "learn" %}
|
||||||
<form action="">
|
<form action="">
|
||||||
<input id="textanswer" type="text" placeholder="DEINE ANTWORT"
|
<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
|
<button {% if request.session.mode == "learn" %}
|
||||||
onclick="submitAnswer( -1 );"
|
onclick="submitAnswer( -1 );" {% endif %}
|
||||||
class="px-6 py-2 bg-blue-500 hover:bg-blue-600 text-white font-semibold rounded-lg transition-colors duration-200 answer-option "
|
class="mt-2 text-center p-2 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option">
|
||||||
>
|
abschicken
|
||||||
Abschicken
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endif %}
|
{% 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 %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% 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' &&
|
} else if (data.type === 'game_state_update' &&
|
||||||
data.action === 'show_scores' &&
|
data.action === 'show_scores' &&
|
||||||
data.redirect_url) {
|
data.redirect_url) {
|
||||||
|
window.location.href = data.redirect_url;
|
||||||
|
submitAnswer( -1 );
|
||||||
setTimeout(() => {
|
|
||||||
submitAnswer(-1);
|
|
||||||
window.location.href = data.redirect_url;
|
|
||||||
}, 1000);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -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',
|
'type': 'advance_to_scores',
|
||||||
'host_id': hostId
|
'host_id': hostId
|
||||||
}));
|
}));
|
||||||
}, 0);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timer
|
// 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) {
|
if (remaining === 0) {
|
||||||
advanceToScores();
|
advanceToScores();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
requestAnimationFrame(updateTimer);
|
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>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,23 +1,19 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{% load static %}
|
|
||||||
<script src="{% static 'js/sortablejs/Sortable.min.js' %}"></script>
|
|
||||||
|
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex justify-end mb-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">
|
<button onclick="leaveGame()" class="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded">
|
||||||
Spiel verlassen
|
Spiel verlassen
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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 justify-between mb-2">
|
||||||
<div class="flex gap-1 text-sm"><p class="text-sm">Frage</p>
|
<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-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>
|
||||||
</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>
|
<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 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 id="time-bar" class="bg-blue-600 h-4 rounded-full transition-all duration-100 linear" style="width: 100%;"></div>
|
||||||
</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">
|
<div class="grid grid-cols-2 gap-4" id="options-container">
|
||||||
{% for option in question_data.options %}
|
{% for option in question_data.options %}
|
||||||
<button data-index="{{ forloop.counter0 }}"
|
<button
|
||||||
data-value="{{ option.value }}"
|
|
||||||
class="
|
class="
|
||||||
{% if forloop.counter0 == 0 %}
|
{% if forloop.counter0 == 0 %}
|
||||||
bg-green-500
|
bg-green-500
|
||||||
@@ -60,165 +56,16 @@
|
|||||||
</button>
|
</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
|
||||||
{% 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="">
|
<form action="">
|
||||||
<input id="textanswer" type="text" placeholder="DEINE ANTWORT"
|
<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
|
<button
|
||||||
onclick="submitAnswer( -1 );"
|
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 "
|
class="mt-2 text-center p-2 bg-gray-100 hover:bg-blue-100 rounded-lg transition-colors duration-200 answer-option">
|
||||||
>
|
abschicken
|
||||||
Abschicken
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</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 %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -242,11 +89,7 @@
|
|||||||
data.action === 'show_scores' &&
|
data.action === 'show_scores' &&
|
||||||
data.redirect_url) {
|
data.redirect_url) {
|
||||||
window.location.href = data.redirect_url;
|
window.location.href = data.redirect_url;
|
||||||
{% if question_data.type == "order" %}
|
submitAnswer( -1 );
|
||||||
submitOrderAnswer();
|
|
||||||
{% elif question_data.type != "map" %}
|
|
||||||
submitAnswer(-1);
|
|
||||||
{% endif %}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -254,22 +97,7 @@
|
|||||||
wsUtil.handleClose(e);
|
wsUtil.handleClose(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
function submitAnswer(optionIndex=0, has_options=true) {
|
function submitAnswer(optionIndex) {
|
||||||
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
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (hasAnswered) return;
|
if (hasAnswered) return;
|
||||||
let input = ""; // Declare input here
|
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
|
// Timer
|
||||||
function updateTimer() {
|
function updateTimer() {
|
||||||
@@ -366,13 +159,7 @@
|
|||||||
|
|
||||||
if (remaining === 0 && !hasAnswered) {
|
if (remaining === 0 && !hasAnswered) {
|
||||||
// Auto-submit timeout answer
|
// Auto-submit timeout answer
|
||||||
{% if question_data.type == "order" %}
|
|
||||||
submitOrderAnswer();
|
|
||||||
{% else %}
|
|
||||||
submitAnswer(-1);
|
submitAnswer(-1);
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
} else if (remaining > 0) {
|
} else if (remaining > 0) {
|
||||||
requestAnimationFrame(updateTimer);
|
requestAnimationFrame(updateTimer);
|
||||||
}
|
}
|
||||||
@@ -389,75 +176,5 @@
|
|||||||
|
|
||||||
// Start timer when page loads
|
// Start timer when page loads
|
||||||
updateTimer();
|
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>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -2,242 +2,35 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto px-4">
|
<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>
|
<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>
|
<h2 class="text-xl font-bold mb-4">Aktuelle Frage</h2>
|
||||||
<p class="text-lg mb-2">{{ question_data.question }}</p>
|
<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">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
{% for option in question_data.options %}
|
{% 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 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">
|
<div class="relative z-10">
|
||||||
<p class="text-lg ">{{ option.value }}</p>
|
<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-gray-600" id="option-count-{{ forloop.counter0 }}">0 Antworten</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
{% if request.session.mode == "learn" %}
|
|
||||||
<h2 class="text-xl font-bold mb-4">Punktestand</h2>
|
<h2 class="text-xl font-bold mb-4">Punktestand</h2>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div id="scoreboard" class="leading-relaxed">
|
<div id="scoreboard" class="leading-relaxed">
|
||||||
{% for participant in participants %}
|
{% for participant in participants %}
|
||||||
{% if request.session.mode == "learn" %}
|
|
||||||
{% if participant == my_participant or is_host %}
|
{% 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="flex flex-col items-start gap-1">
|
||||||
<div class="text-lg font-bold flex items-center gap-2">
|
<div class="text-lg font-bold flex items-center gap-2">
|
||||||
{% if forloop.counter == 1 %}
|
{% if forloop.counter == 1 %}
|
||||||
@@ -249,116 +42,17 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
{{ forloop.counter }}.
|
{{ forloop.counter }}.
|
||||||
{% endif %}
|
{% 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">
|
</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"%}
|
{% if participant == my_participant or request.session.mode == "learn"%}
|
||||||
<p class="text-blue-600 font-bold text-sm">+{{ participant.last_score }} Punkte</p>
|
<p class="text-blue-600 font-bold text-sm">+{{ participant.last_score }} Punkte</p>
|
||||||
|
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
|
||||||
|
|
||||||
{% 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>
|
|
||||||
|
|
||||||
{% 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");
|
elements=document.getElementsByClassName("show-score");
|
||||||
for (let i = 0; i < elements.length; i++) {
|
for (let i = 0; i < elements.length; i++) {
|
||||||
@@ -393,17 +87,153 @@
|
|||||||
|
|
||||||
|
|
||||||
{% if is_last_question %}
|
{% if is_last_question %}
|
||||||
start();
|
|
||||||
bounce();
|
bounce();
|
||||||
{% if request.session.mode == "learn" %}
|
|
||||||
triggerConfetti();
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
start();
|
start();
|
||||||
|
|
||||||
|
|
||||||
{% endif %}
|
{% 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 = [
|
const richtigeAntworten = [
|
||||||
"Deine Antwort ist nicht falsch. Sie ist richtig!",
|
"Deine Antwort ist nicht falsch. Sie ist richtig!",
|
||||||
"Ja, du kannst es!",
|
"Ja, du kannst es!",
|
||||||
@@ -477,11 +307,10 @@ if (antwortRichtig) {
|
|||||||
gameSocket.onmessage = function(e) {
|
gameSocket.onmessage = function(e) {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
if (data.type === 'game_state_update' &&
|
if (data.type === 'game_state_update' &&
|
||||||
data.redirect_url &&
|
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;
|
window.location.href = data.redirect_url;
|
||||||
}
|
} else if (data.type === 'answer_stats') {
|
||||||
else if (data.type === 'answer_stats') {
|
|
||||||
updateAnswerStats(data.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 %}
|
{% 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');
|
var nextButton = document.getElementById('next-button');
|
||||||
if (nextButton) {
|
if (nextButton) {
|
||||||
nextButton.addEventListener('click', function() {
|
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' %}
|
{% extends 'base.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto px-4 py-8">
|
<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>
|
<h1 class="text-2xl font-bold mb-6 text-center text-blue-600">Spieler einrichten</h1>
|
||||||
|
|
||||||
<div class="text-center mb-6">
|
<div class="text-center mb-6">
|
||||||
@@ -18,20 +17,20 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="join_code" value="{{join_code}}">
|
<input type="hidden" name="join_code" value="{{join_code}}">
|
||||||
|
|
||||||
<div class="bg-gray-50 p-4 rounded-lg mb-4 dark:bg-transparent">
|
<div class="bg-gray-50 p-4 rounded-lg mb-4">
|
||||||
<p class="text-sm text-gray-600 mb-2 dark:text-white">Wähle einen Anzeigenamen für das Spiel:</p>
|
<p class="text-sm text-gray-600 mb-2">Wähle einen Anzeigenamen für das Spiel:</p>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="display_name"
|
name="display_name"
|
||||||
required
|
required
|
||||||
maxlength="20"
|
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"
|
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">
|
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>
|
||||||
|
|
||||||
<div class="flex justify-center space-x-4">
|
<div class="flex justify-center space-x-4">
|
||||||
<a href="/"
|
<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
|
Abbrechen
|
||||||
</a>
|
</a>
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="grid place-items-center min-h-[calc(100vh-5rem)] flex justify-center py-6 m-2">
|
<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 dark:bg-transparent dark:text-white">
|
<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>
|
<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">
|
<form action="{% url 'play:join_game' %}" method="post" class="flex flex-col gap-4">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="flex gap-3 items-center justify-center">
|
<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>
|
<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>
|
<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">
|
<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 %}
|
{% block content %}
|
||||||
<div class="container mx-auto px-4 py-8">
|
<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 -->
|
<!-- Quiz Info -->
|
||||||
<div class="text-center mb-8">
|
<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 request.session.mode != "learn" %}
|
||||||
{% if host_id %}
|
{% if host_id %}
|
||||||
<div class="bg-blue-100 rounded-lg p-4 mb-4 dark:bg-transparent">
|
<div class="bg-blue-100 rounded-lg p-4 mb-4">
|
||||||
<h1 class="text-4xl font-bold text-blue-800 dark:text-white">{{ quiz_game.join_code }}</h1>
|
<h1 class="text-4xl font-bold text-blue-800">{{ quiz_game.join_code }}</h1>
|
||||||
<div class="w-full flex justify-center my-2">
|
<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">
|
<img src="data:image/png;base64,{{ qr_code_base64 }}" class="h-1/2 w-1/2" alt="QR-Code">
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -23,23 +23,23 @@
|
|||||||
<!-- Participant Info -->
|
<!-- Participant Info -->
|
||||||
{% if participant %}
|
{% if participant %}
|
||||||
<div class="mb-6 text-center">
|
<div class="mb-6 text-center">
|
||||||
<div class="inline-flex items-center bg-green-100 px-4 py-2 rounded-full dark:text-black">
|
<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 class="w-2 h-2 bg-green-500 rounded-full mr-2"></span>
|
||||||
<span>Angemeldet als <b>{{ participant.display_name }}</b></span>
|
<span>Angemeldet als <b>{{ participant.display_name }}</b></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Participants List -->
|
<!-- 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">
|
<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">
|
<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" />
|
<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>
|
</svg>
|
||||||
Teilnehmer
|
Teilnehmer
|
||||||
</h3>
|
</h3>
|
||||||
<ul id="participants-list" class="space-y-2 max-h-60 ">
|
<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>
|
<li class="text-gray-500 p-3 bg-gray-50 rounded-lg text-center">Warte auf Teilnehmer...</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!participants || participants.length === 0) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,13 +298,13 @@
|
|||||||
'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" ' +
|
'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" ' +
|
||||||
'd="M6 18L18 6M6 6l12 12"/>' +
|
'd="M6 18L18 6M6 6l12 12"/>' +
|
||||||
'</svg>' +
|
'</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 ' +
|
'bg-gray-800 text-white text-xs rounded opacity-0 group-hover:opacity-100 ' +
|
||||||
'transition-opacity duration-200 whitespace-nowrap">Spieler kicken</span>' +
|
'transition-opacity duration-200 whitespace-nowrap">Spieler kicken</span>' +
|
||||||
'</button>';
|
'</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="flex items-center">' +
|
||||||
'<div class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center mr-3">' +
|
'<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>' +
|
'<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="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">
|
<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">
|
<div class="grid sm:grid-cols-2 place-items-stretch text-center gap-4 p-4">
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,15 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="max-w-lg mx-auto mt-10">
|
<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">
|
<div class="text-center">
|
||||||
<svg class="mx-auto h-12 w-12 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<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"/>
|
<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>
|
</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.
|
Möchten Sie ihren Account wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% block title %}Passwort ändern{% endblock %}
|
{% block title %}Passwort ändern{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="grid place-content-center h-120 mt-12 mb-12 ">
|
<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>
|
<h2 class="text-2xl text-center p-4 font-black">Passwort ändern</h2>
|
||||||
<form class="space-y-4" method="post">
|
<form class="space-y-4" method="post">
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
@@ -21,17 +21,17 @@
|
|||||||
<div class="register">
|
<div class="register">
|
||||||
|
|
||||||
{{ form.old_password.label_tag }}
|
{{ 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>
|
||||||
|
|
||||||
<div class="register">
|
<div class="register">
|
||||||
{{ form.new_password1.label_tag }}
|
{{ 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>
|
||||||
<div class="register">
|
<div class="register">
|
||||||
{{ form.new_password2.label_tag }}
|
{{ 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>
|
</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="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>
|
<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 title %}Passwort zurückgesetzt{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="grid place-content-center h-120 mt-12 mb-12 ">
|
<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>
|
<h2 class="text-2xl text-center p-4 font-black">Passwort zurückgesetzt</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
Das Passwort wurde erfolgreich zurückgesetzt! Sie können sich nun mit dem neuen Passwort anmelden!
|
Das Passwort wurde erfolgreich zurückgesetzt! Sie können sich nun mit dem neuen Passwort anmelden!
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="grid place-content-center h-120 mt-12 mb-12 ">
|
<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">
|
<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 %}
|
{% if validlink %}
|
||||||
<form class="space-y-4" method="post">
|
<form class="space-y-4" method="post">
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
@@ -21,12 +21,11 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="register">
|
<div class="register">
|
||||||
|
{{ form.new_password1.label_tag }}
|
||||||
<span class="dark:text-white">{{ form.new_password1.label_tag }} </span>
|
|
||||||
<input type="password" name="new_password1" placeholder="NEUES PASSWORT" />
|
<input type="password" name="new_password1" placeholder="NEUES PASSWORT" />
|
||||||
</div>
|
</div>
|
||||||
<div class="register ">
|
<div class="register">
|
||||||
<span class="dark:text-white">{{ form.new_password2.label_tag }} </span>
|
{{ form.new_password2.label_tag }}
|
||||||
<input type="password" name="new_password2" placeholder="NEUES PASSWORT WIEDERHOLEN" />
|
<input type="password" name="new_password2" placeholder="NEUES PASSWORT WIEDERHOLEN" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="grid place-content-center h-120 mt-12 mb-12">
|
<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">
|
<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 dark:text-white">E-Mail wurde gesendet</h2>
|
<h2 class="text-2xl font-black mb-4">E-Mail wurde gesendet</h2>
|
||||||
<p class="text-gray-700 mb-6 dark:text-white">
|
<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.
|
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>
|
<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 title %}Passwort ändern{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="grid place-content-center h-120 mt-12 mb-12 ">
|
<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>
|
<h2 class="text-2xl text-center p-4 font-black">Passwort zurücksetzen</h2>
|
||||||
<form class="space-y-4" method="post">
|
<form class="space-y-4" method="post">
|
||||||
Gib hier deine E-Mail an und erhalte eine E-Mail, um das Passwort zurückzusetzen.
|
Gib hier deine E-Mail an und erhalte eine E-Mail, um das Passwort zurückzusetzen.
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<div class="register">
|
<div class="register">
|
||||||
{{ form.email.label_tag }}
|
{{ 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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,3 @@ redis
|
|||||||
channels_redis
|
channels_redis
|
||||||
python-dotenv # Production server safety
|
python-dotenv # Production server safety
|
||||||
qrcode~=7.4.2
|
qrcode~=7.4.2
|
||||||
reportlab~=4.2.0
|
|
||||||
pip-licenses ~= 5.0.0
|
|
||||||
requests
|
|
||||||
requests
|
|
||||||
geopy
|
|
||||||
|
|||||||
Reference in New Issue
Block a user