diff --git a/app.py b/app.py index 8b13789..74403b6 100644 --- a/app.py +++ b/app.py @@ -1 +1,152 @@ +from flask import Flask, render_template, request, jsonify +from models import db, Progress, Statistic +import os +import random +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.db' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + +# Initialisiere die Datenbank +db.init_app(app) + +# Beispieltexte für den Tipptrainer +EXAMPLE_TEXTS = [ + "Der schnelle braune Fuchs springt über den faulen Hund.", + "In der Sonne tanzen die Blüten im Garten.", + "Ein guter Tipptrainer hilft beim schnellen Lernen.", + "Die moderne Technik macht unser Leben einfacher.", + "Programmieren ist eine spannende Herausforderung." +] + +@app.before_first_request +def create_tables(): + db.create_all() + + # Prüfe, ob bereits Daten in der Datenbank vorhanden sind + if not Progress.query.first(): + # Füge Beispieltexte zur Datenbank hinzu, wenn keine vorhanden sind + progress = Progress(current_text_index=0, current_position=0, last_text=EXAMPLE_TEXTS[0]) + db.session.add(progress) + db.session.commit() + +@app.route('/') +def index(): + # Hole den aktuellen Fortschritt aus der Datenbank + progress = Progress.query.first() + + if not progress: + # Falls keine Daten vorhanden sind, initialisiere mit ersten Text + progress = Progress(current_text_index=0, current_position=0, last_text=EXAMPLE_TEXTS[0]) + db.session.add(progress) + db.session.commit() + + # Hole den aktuellen Text + current_text = EXAMPLE_TEXTS[progress.current_text_index] + + return render_template('index.html', + current_text=current_text, + current_position=progress.current_position, + last_text=progress.last_text) + +@app.route('/update_progress', methods=['POST']) +def update_progress(): + data = request.get_json() + user_input = data.get('user_input', '') + current_text = data.get('current_text', '') + + # Hole den aktuellen Fortschritt + progress = Progress.query.first() + + # Aktualisiere den Fortschritt + progress.current_position = len(user_input) + progress.last_text = user_input + + # Speichere in der Datenbank + db.session.commit() + + # Berechne Statistiken + stats = calculate_statistics(user_input, current_text) + + return jsonify(stats) + +def calculate_statistics(user_input, current_text): + # Berechne die Anzahl der korrekten und falschen Zeichen + correct_chars = 0 + incorrect_chars = 0 + total_chars = len(user_input) + + for i, char in enumerate(user_input): + if i < len(current_text) and char == current_text[i]: + correct_chars += 1 + else: + incorrect_chars += 1 + + # Berechne Fehlerquote + error_rate = 0 + if total_chars > 0: + error_rate = (incorrect_chars / total_chars) * 100 + + # Berechne durchschnittliche Tippgeschwindigkeit (Zeichen pro Sekunde) + # Für diese einfache Version: 100 Zeichen pro Sekunde als Beispiel + typing_speed = 100 # Zeichen pro Minute + + # Dauer pro Zeichen (in Sekunden) + duration_per_char = 1.0 / (typing_speed / 60) if typing_speed > 0 else 0 + + return { + 'correct_chars': correct_chars, + 'incorrect_chars': incorrect_chars, + 'total_chars': total_chars, + 'error_rate': round(error_rate, 2), + 'typing_speed': typing_speed, + 'duration_per_char': round(duration_per_char, 3) + } + +@app.route('/next_text', methods=['POST']) +def next_text(): + progress = Progress.query.first() + + # Wechsel zum nächsten Text + if progress.current_text_index < len(EXAMPLE_TEXTS) - 1: + progress.current_text_index += 1 + else: + # Wenn wir am Ende sind, starten wir von vorne + progress.current_text_index = 0 + + progress.current_position = 0 + progress.last_text = EXAMPLE_TEXTS[progress.current_text_index] + + db.session.commit() + + return jsonify({ + 'text': EXAMPLE_TEXTS[progress.current_text_index] + }) + +@app.route('/end_session', methods=['POST']) +def end_session(): + # Speichere den aktuellen Fortschritt + progress = Progress.query.first() + + # Erstelle oder aktualisiere Statistik + statistic = Statistic.query.first() + if not statistic: + statistic = Statistic( + total_chars=0, + correct_chars=0, + incorrect_chars=0, + error_rate=0, + typing_speed=0, + duration_per_char=0 + ) + db.session.add(statistic) + + # Aktualisiere Statistik (in einer echten Anwendung würden wir hier die Werte berechnen) + db.session.commit() + + return jsonify({'status': 'success'}) + +if __name__ == '__main__': + with app.app_context(): + db.create_all() + app.run(debug=True) diff --git a/models.py b/models.py new file mode 100644 index 0000000..8e543de --- /dev/null +++ b/models.py @@ -0,0 +1,18 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() + +class Progress(db.Model): + id = db.Column(db.Integer, primary_key=True) + current_text_index = db.Column(db.Integer, default=0) + current_position = db.Column(db.Integer, default=0) + last_text = db.Column(db.Text, default='') + +class Statistic(db.Model): + id = db.Column(db.Integer, primary_key=True) + total_chars = db.Column(db.Integer, default=0) + correct_chars = db.Column(db.Integer, default=0) + incorrect_chars = db.Column(db.Integer, default=0) + error_rate = db.Column(db.Float, default=0.0) + typing_speed = db.Column(db.Float, default=0.0) + duration_per_char = db.Column(db.Float, default=0.0) diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..7f96b5d --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,108 @@ +body { + background-color: #f8f9fa; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +.card { + border: none; + border-radius: 10px; + overflow: hidden; +} + +.card-header { + border-radius: 10px 10px 0 0 !important; +} + +#target-text { + min-height: 100px; + font-size: 1.2rem; + font-weight: 500; + line-height: 1.6; + letter-spacing: 0.5px; +} + +#user-input { + font-size: 1.1rem; + line-height: 1.6; + letter-spacing: 0.5px; + transition: border-color 0.3s; +} + +#user-input:focus { + border-color: #0d6efd; + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); +} + +.highlight-correct { + color: #28a745; + font-weight: bold; +} + +.highlight-incorrect { + color: #dc3545; + text-decoration: underline; + font-weight: bold; +} + +.badge { + font-size: 0.9rem; + padding: 0.5em 0.75em; +} + +.list-group-item { + border: none; + padding: 0.75rem 1rem; + border-bottom: 1px solid rgba(0,0,0,.125); +} + +.list-group-item:last-child { + border-bottom: none; +} + +.card-title { + margin-bottom: 1rem; + font-weight: 600; +} + +.card-body { + padding: 1.5rem; +} + +.btn { + font-weight: 500; + padding: 0.5rem 1rem; + border-radius: 50px; + transition: all 0.3s; +} + +.btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} + +.btn:active { + transform: translateY(0); +} + +.container { + max-width: 900px; +} + +@media (max-width: 768px) { + .container { + padding: 0 10px; + } + + .card-body { + padding: 1rem; + } + + #target-text { + min-height: 80px; + font-size: 1rem; + } + + #user-input { + font-size: 1rem; + } +} diff --git a/static/js/script.js b/static/js/script.js new file mode 100644 index 0000000..97ec793 --- /dev/null +++ b/static/js/script.js @@ -0,0 +1,109 @@ +document.addEventListener('DOMContentLoaded', function() { + const userInput = document.getElementById('user-input'); + const targetText = document.getElementById('target-text'); + const nextBtn = document.getElementById('next-btn'); + const endBtn = document.getElementById('end-btn'); + + // Statistik-Elemente + const speedElement = document.getElementById('speed'); + const durationElement = document.getElementById('duration'); + const errorRateElement = document.getElementById('error-rate'); + const correctElement = document.getElementById('correct'); + const incorrectElement = document.getElementById('incorrect'); + const totalElement = document.getElementById('total'); + + // Aktuelle Textdaten + let currentText = document.querySelector('#target-text').textContent; + + // Aktualisiere die Statistik bei Eingabe + userInput.addEventListener('input', function() { + const userValue = this.value; + updateStatistics(userValue, currentText); + }); + + // Nächste Zeile Button + nextBtn.addEventListener('click', function() { + fetch('/next_text', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => response.json()) + .then(data => { + currentText = data.text; + document.getElementById('target-text').textContent = currentText; + userInput.value = ''; + updateStatistics('', currentText); + }) + .catch(error => console.error('Fehler:', error)); + }); + + // Beenden Button + endBtn.addEventListener('click', function() { + fetch('/end_session', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => response.json()) + .then(data => { + alert('Sitzung beendet. Fortschritt gespeichert.'); + window.close(); + }) + .catch(error => console.error('Fehler:', error)); + }); + + // Statistik aktualisieren + function updateStatistics(userInput, currentText) { + // Sende Daten an den Server + fetch('/update_progress', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + user_input: userInput, + current_text: currentText + }) + }) + .then(response => response.json()) + .then(data => { + // Aktualisiere die Anzeige + speedElement.textContent = data.typing_speed; + durationElement.textContent = data.duration_per_char + 's'; + errorRateElement.textContent = data.error_rate + '%'; + correctElement.textContent = data.correct_chars; + incorrectElement.textContent = data.incorrect_chars; + totalElement.textContent = data.total_chars; + + // Aktualisiere die Textfarben + updateTextHighlighting(userInput, currentText); + }) + .catch(error => console.error('Fehler:', error)); + } + + // Text hervorheben + function updateTextHighlighting(userInput, currentText) { + const targetElement = document.getElementById('target-text'); + let highlightedText = ''; + + for (let i = 0; i < currentText.length; i++) { + if (i < userInput.length) { + if (userInput[i] === currentText[i]) { + highlightedText += `${currentText[i]}`; + } else { + highlightedText += `${currentText[i]}`; + } + } else { + highlightedText += currentText[i]; + } + } + + targetElement.innerHTML = highlightedText; + } + + // Initialisiere Statistik + updateStatistics('', currentText); +}); diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..27c5f48 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,106 @@ + + +
+ + +