Metronom läuft, Symbole installiert ...
This commit is contained in:
parent
c9cce9207f
commit
9ef38475ac
4 changed files with 414 additions and 86 deletions
32
app.py
32
app.py
|
|
@ -1,12 +1,13 @@
|
|||
import os
|
||||
import json
|
||||
import re
|
||||
from flask import Flask, render_template, request, jsonify
|
||||
from flask import Flask, render_template, request, jsonify, session
|
||||
from models import db, Progress, Statistic, LessonStatistic
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///typewriter.db'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.secret_key = 'your-secret-key-here' # Für Session verwenden
|
||||
db.init_app(app)
|
||||
|
||||
# Fallback-Lektionen für den Fall, dass die JSON-Datei nicht geladen werden kann
|
||||
|
|
@ -229,21 +230,33 @@ def index():
|
|||
lesson = lessons[progress.current_text_index]
|
||||
current_lesson_title = lesson.get('title', f"Lektion {progress.current_text_index + 1}")
|
||||
|
||||
# Metronom-Einstellungen aus Session laden oder Standardwerte setzen
|
||||
metronome_enabled = session.get('metronome_enabled', False)
|
||||
metronome_bpm = session.get('metronome_bpm', 60)
|
||||
metronome_sound = session.get('metronome_sound', 'beep')
|
||||
|
||||
return render_template('index.html',
|
||||
current_text=current_text,
|
||||
current_position=progress.current_position,
|
||||
last_text=progress.last_text,
|
||||
lessons=lessons,
|
||||
current_lesson_title=current_lesson_title,
|
||||
current_lesson_index=progress.current_text_index)
|
||||
current_lesson_index=progress.current_text_index,
|
||||
metronome_enabled=metronome_enabled,
|
||||
metronome_bpm=metronome_bpm,
|
||||
metronome_sound=metronome_sound)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler in index(): {e}")
|
||||
# Auch im Fehlerfall Metronom-Standardwerte übergeben
|
||||
return render_template('index.html',
|
||||
current_text="Fehler beim Laden der Lektionen",
|
||||
current_position=0,
|
||||
last_text="",
|
||||
lessons=[])
|
||||
lessons=[],
|
||||
metronome_enabled=False,
|
||||
metronome_bpm=60,
|
||||
metronome_sound='beep')
|
||||
|
||||
@app.route('/debug')
|
||||
def debug():
|
||||
|
|
@ -569,6 +582,19 @@ def get_lesson_statistics(lesson_index):
|
|||
print(f"Fehler in get_lesson_statistics: {e}")
|
||||
return jsonify({'error': 'Interner Serverfehler'}), 500
|
||||
|
||||
@app.route('/set_metronome', methods=['POST'])
|
||||
def set_metronome():
|
||||
"""Setzt die Metronom-Einstellungen in der Session"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
session['metronome_enabled'] = data.get('enabled', False)
|
||||
session['metronome_bpm'] = data.get('bpm', 60)
|
||||
session['metronome_sound'] = data.get('sound', 'beep')
|
||||
return jsonify({'status': 'success'})
|
||||
except Exception as e:
|
||||
print(f"Fehler in set_metronome: {e}")
|
||||
return jsonify({'error': 'Interner Serverfehler'}), 500
|
||||
|
||||
@app.route('/end_session', methods=['POST'])
|
||||
def end_session():
|
||||
"""Beendet eine Tipp-Session und speichert Statistiken"""
|
||||
|
|
|
|||
|
|
@ -29,6 +29,18 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(0, 150, 255, 0.7);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(0, 150, 255, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(0, 150, 255, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark Theme Anpassungen für Cursor */
|
||||
[data-bs-theme="dark"] .cursor-char::after {
|
||||
background-color: #0cf;
|
||||
|
|
|
|||
|
|
@ -15,23 +15,244 @@ function toggleTheme() {
|
|||
}
|
||||
|
||||
function updateThemeButton(theme) {
|
||||
const button = document.getElementById('theme-toggle');
|
||||
const sunIcon = document.getElementById('theme-toggle-sun');
|
||||
const moonIcon = document.getElementById('theme-toggle-moon');
|
||||
if (theme === 'dark') {
|
||||
button.innerHTML = '<i class="fas fa-sun"></i>';
|
||||
button.classList.remove('btn-outline-light');
|
||||
button.classList.add('btn-outline-warning');
|
||||
sunIcon.style.display = 'none';
|
||||
moonIcon.style.display = 'block';
|
||||
} else {
|
||||
button.innerHTML = '<i class="fas fa-cog"></i>';
|
||||
button.classList.remove('btn-outline-warning');
|
||||
button.classList.add('btn-outline-light');
|
||||
sunIcon.style.display = 'block';
|
||||
moonIcon.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Lucide Icons initialisieren
|
||||
lucide.createIcons();
|
||||
|
||||
// Theme initialisieren
|
||||
initializeTheme();
|
||||
document.getElementById('theme-toggle').addEventListener('click', toggleTheme);
|
||||
|
||||
// Aktuelle Textdaten - mit Fallback falls undefiniert
|
||||
let fullText = window.fullText || "Kein Text verfügbar. Bitte laden Sie die Seite neu.";
|
||||
let currentCharIndex = window.currentCharIndex || 0;
|
||||
let lastStartTime = null;
|
||||
let totalElapsedTime = 0;
|
||||
let isPaused = false;
|
||||
let timerInterval = null;
|
||||
let userInput = ''; // Gesamte Eingabe
|
||||
let keyStrokeCount = 0; // Zähler für alle Tastaturanschläge (inkl. Backspace)
|
||||
let lines = fullText.split('\n');
|
||||
let userScrolled = false; // Flag für manuelles Scrollen
|
||||
|
||||
// Volume-Toggle initialisieren - Standardmäßig Ton an (nicht stumm)
|
||||
let metronomeMuted = false;
|
||||
const volumeOn = document.getElementById('volume-on');
|
||||
const volumeOff = document.getElementById('volume-off');
|
||||
// Initiale Anzeige: volume-2 anzeigen, volume-x verstecken
|
||||
volumeOn.style.display = 'block';
|
||||
volumeOff.style.display = 'none';
|
||||
|
||||
document.getElementById('volume-toggle').addEventListener('click', function() {
|
||||
metronomeMuted = !metronomeMuted;
|
||||
if (metronomeMuted) {
|
||||
volumeOn.style.display = 'none';
|
||||
volumeOff.style.display = 'block';
|
||||
} else {
|
||||
volumeOn.style.display = 'block';
|
||||
volumeOff.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// ErrorLock State - Standardmäßig geöffnet (errorLock = false)
|
||||
let errorLock = false;
|
||||
const lockClosed = document.getElementById('lock-closed');
|
||||
const lockOpen = document.getElementById('lock-open');
|
||||
// Initiale Anzeige: offenes Schloss anzeigen, geschlossenes verstecken
|
||||
lockClosed.style.display = 'none';
|
||||
lockOpen.style.display = 'block';
|
||||
|
||||
// Metronom State - Initialisierung mit Werten von Flask
|
||||
let metronomeEnabled = window.metronomeEnabled || false;
|
||||
let metronomeInterval = null;
|
||||
let audioContext = null;
|
||||
let metronomeSound = window.metronomeSound || 'beep';
|
||||
let metronomeBPM = window.metronomeBPM || 60;
|
||||
const metronomeToggle = document.getElementById('metronome-toggle');
|
||||
const metronomeOn = document.getElementById('metronome-on');
|
||||
const metronomeOff = document.getElementById('metronome-off');
|
||||
|
||||
// Metronom-Funktionen
|
||||
function createBeepSound(vol, freq, duration) {
|
||||
if (!audioContext) {
|
||||
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
}
|
||||
const oscillator = audioContext.createOscillator();
|
||||
const gainNode = audioContext.createGain();
|
||||
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
gainNode.gain.value = vol;
|
||||
oscillator.frequency.value = freq;
|
||||
oscillator.type = 'sine';
|
||||
|
||||
oscillator.start();
|
||||
setTimeout(function() {
|
||||
oscillator.stop();
|
||||
}, duration);
|
||||
}
|
||||
|
||||
function createClickSound() {
|
||||
if (!audioContext) {
|
||||
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
}
|
||||
const bufferSize = audioContext.sampleRate * 0.01;
|
||||
const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
|
||||
const data = buffer.getChannelData(0);
|
||||
|
||||
for (let i = 0; i < bufferSize; i++) {
|
||||
data[i] = Math.random() * 2 - 1;
|
||||
}
|
||||
|
||||
const source = audioContext.createBufferSource();
|
||||
source.buffer = buffer;
|
||||
|
||||
const gainNode = audioContext.createGain();
|
||||
gainNode.gain.setValueAtTime(0.1, audioContext.currentTime);
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 0.1);
|
||||
|
||||
source.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
source.start();
|
||||
}
|
||||
|
||||
function playMetronomeSound() {
|
||||
// Wenn der Ton stummgeschaltet ist, keinen Sound abspielen
|
||||
if (metronomeMuted) {
|
||||
// Aber visuelles Feedback trotzdem anzeigen
|
||||
const cursorElement = document.querySelector('.cursor-char');
|
||||
if (cursorElement) {
|
||||
cursorElement.style.animation = 'pulse 0.3s';
|
||||
setTimeout(() => {
|
||||
cursorElement.style.animation = '';
|
||||
}, 300);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (metronomeSound === 'beep') {
|
||||
createBeepSound(0.1, 800, 50);
|
||||
} else {
|
||||
createClickSound();
|
||||
}
|
||||
|
||||
// Visuelles Feedback: Cursor pulsieren lassen
|
||||
const cursorElement = document.querySelector('.cursor-char');
|
||||
if (cursorElement) {
|
||||
cursorElement.style.animation = 'pulse 0.3s';
|
||||
setTimeout(() => {
|
||||
cursorElement.style.animation = '';
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
function startMetronome() {
|
||||
stopMetronome();
|
||||
const interval = (60 / metronomeBPM) * 1000;
|
||||
playMetronomeSound(); // Sofort abspielen
|
||||
metronomeInterval = setInterval(playMetronomeSound, interval);
|
||||
}
|
||||
|
||||
function stopMetronome() {
|
||||
if (metronomeInterval) {
|
||||
clearInterval(metronomeInterval);
|
||||
metronomeInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialen Zustand des Metronom-Toggles setzen
|
||||
if (metronomeEnabled) {
|
||||
metronomeOn.style.display = 'block';
|
||||
metronomeOff.style.display = 'none';
|
||||
if (!isPaused) {
|
||||
startMetronome();
|
||||
}
|
||||
} else {
|
||||
metronomeOn.style.display = 'none';
|
||||
metronomeOff.style.display = 'block';
|
||||
}
|
||||
|
||||
// Lock-Toggle initialisieren
|
||||
document.getElementById('lock-toggle').addEventListener('click', function() {
|
||||
if (errorLock) {
|
||||
// Wechsel zu unlocked
|
||||
errorLock = false;
|
||||
lockClosed.style.display = 'none';
|
||||
lockOpen.style.display = 'block';
|
||||
} else {
|
||||
// Wechsel zu locked
|
||||
errorLock = true;
|
||||
lockClosed.style.display = 'block';
|
||||
lockOpen.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Metronom-Toggle initialisieren
|
||||
metronomeToggle.addEventListener('click', function() {
|
||||
metronomeEnabled = !metronomeEnabled;
|
||||
if (metronomeEnabled) {
|
||||
metronomeOn.style.display = 'block';
|
||||
metronomeOff.style.display = 'none';
|
||||
startMetronome();
|
||||
} else {
|
||||
metronomeOn.style.display = 'none';
|
||||
metronomeOff.style.display = 'block';
|
||||
stopMetronome();
|
||||
}
|
||||
// Einstellungen an Server senden
|
||||
fetch('/set_metronome', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
enabled: metronomeEnabled,
|
||||
bpm: metronomeBPM,
|
||||
sound: metronomeSound
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
// Play/Pause-Toggle initialisieren
|
||||
const playPauseToggle = document.getElementById('play-pause-toggle');
|
||||
if (playPauseToggle) {
|
||||
playPauseToggle.addEventListener('click', function() {
|
||||
if (isPaused) {
|
||||
// Fortsetzen
|
||||
isPaused = false;
|
||||
lastStartTime = new Date();
|
||||
document.getElementById('play-pause-pause').style.display = 'block';
|
||||
document.getElementById('play-pause-play').style.display = 'none';
|
||||
if (metronomeEnabled) {
|
||||
startMetronome();
|
||||
}
|
||||
} else {
|
||||
// Pausieren
|
||||
isPaused = true;
|
||||
if (lastStartTime !== null) {
|
||||
totalElapsedTime += (new Date() - lastStartTime);
|
||||
lastStartTime = null;
|
||||
}
|
||||
document.getElementById('play-pause-pause').style.display = 'none';
|
||||
document.getElementById('play-pause-play').style.display = 'block';
|
||||
stopMetronome();
|
||||
}
|
||||
updateStatistics();
|
||||
});
|
||||
}
|
||||
|
||||
const targetTextElement = document.getElementById('target-text');
|
||||
const nextBtn = document.getElementById('next-btn');
|
||||
const endBtn = document.getElementById('end-btn');
|
||||
|
|
@ -44,16 +265,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
const totalElement = document.getElementById('total');
|
||||
const wpmElement = document.getElementById('wpm');
|
||||
|
||||
// Aktuelle Textdaten
|
||||
let fullText = window.fullText;
|
||||
let currentCharIndex = window.currentCharIndex;
|
||||
let startTime = null;
|
||||
let timerInterval = null;
|
||||
let userInput = ''; // Gesamte Eingabe
|
||||
let keyStrokeCount = 0; // Zähler für alle Tastaturanschläge (inkl. Backspace)
|
||||
let lines = fullText.split('\n');
|
||||
let userScrolled = false; // Flag für manuelles Scrollen
|
||||
|
||||
// Setze Fokus auf das Dokument, um Tastatureingaben zu erfassen
|
||||
document.addEventListener('click', function() {
|
||||
document.body.focus();
|
||||
|
|
@ -109,13 +320,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Behandle Pfeiltasten
|
||||
// Im Pause-Modus sind keine Tasten erlaubt (Cursor-Position bleibt erhalten)
|
||||
if (isPaused) {
|
||||
// Blockiere alle Tasten im Pause-Modus
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// Behandle Pfeiltasten (nur im Play-Modus)
|
||||
if (event.key === 'ArrowLeft') {
|
||||
event.preventDefault();
|
||||
if (currentCharIndex > 0) {
|
||||
keyStrokeCount++;
|
||||
if (!startTime) {
|
||||
startTime = new Date();
|
||||
if (!lastStartTime && !isPaused) {
|
||||
lastStartTime = new Date();
|
||||
}
|
||||
currentCharIndex--;
|
||||
updateTextDisplay();
|
||||
|
|
@ -128,8 +346,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
event.preventDefault();
|
||||
if (currentCharIndex < userInput.length) {
|
||||
keyStrokeCount++;
|
||||
if (!startTime) {
|
||||
startTime = new Date();
|
||||
if (!lastStartTime && !isPaused) {
|
||||
lastStartTime = new Date();
|
||||
}
|
||||
currentCharIndex++;
|
||||
updateTextDisplay();
|
||||
|
|
@ -143,8 +361,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
event.preventDefault();
|
||||
if (userInput.length > 0 && currentCharIndex > 0) {
|
||||
keyStrokeCount++;
|
||||
if (!startTime) {
|
||||
startTime = new Date();
|
||||
if (!lastStartTime && !isPaused) {
|
||||
lastStartTime = new Date();
|
||||
}
|
||||
userInput = userInput.slice(0, currentCharIndex - 1) + userInput.slice(currentCharIndex);
|
||||
currentCharIndex--;
|
||||
|
|
@ -161,8 +379,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
userInput = userInput.slice(0, currentCharIndex) + '\n' + userInput.slice(currentCharIndex);
|
||||
currentCharIndex++;
|
||||
keyStrokeCount++;
|
||||
if (!startTime) {
|
||||
startTime = new Date();
|
||||
if (!lastStartTime && !isPaused) {
|
||||
lastStartTime = new Date();
|
||||
}
|
||||
updateTextDisplay();
|
||||
updateStatistics();
|
||||
|
|
@ -175,6 +393,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Prüfe auf ErrorLock: Wenn aktiviert und currentCharIndex nicht am Ende des korrekten Präfixes, blockiere Eingabe
|
||||
if (errorLock && currentCharIndex !== getCorrectPrefixLength()) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// Behandle normale Zeichen
|
||||
event.preventDefault();
|
||||
if (currentCharIndex < fullText.length) {
|
||||
|
|
@ -189,8 +413,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
}
|
||||
currentCharIndex++;
|
||||
keyStrokeCount++;
|
||||
if (!startTime) {
|
||||
startTime = new Date();
|
||||
if (!lastStartTime && !isPaused) {
|
||||
lastStartTime = new Date();
|
||||
}
|
||||
updateTextDisplay();
|
||||
updateStatistics();
|
||||
|
|
@ -274,12 +498,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
fullText = data.text;
|
||||
currentCharIndex = 0;
|
||||
userInput = '';
|
||||
startTime = null;
|
||||
lastStartTime = null;
|
||||
totalElapsedTime = 0;
|
||||
isPaused = false;
|
||||
keyStrokeCount = 0; // Zurücksetzen des Tastaturanschlagszählers
|
||||
lines = fullText.split('\n');
|
||||
userScrolled = false; // Reset Scroll-Flag
|
||||
updateTextDisplay();
|
||||
document.getElementById('lesson-title').textContent = data.title;
|
||||
// Setze Play/Pause-Toggle zurück auf Pause
|
||||
document.getElementById('play-pause-pause').style.display = 'block';
|
||||
document.getElementById('play-pause-play').style.display = 'none';
|
||||
updateStatistics();
|
||||
// Aktualisiere die Markierung der aktuellen Lektion
|
||||
updateLessonHighlight(lessonIndex);
|
||||
|
|
@ -290,64 +519,84 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
});
|
||||
|
||||
// Nächster Text Button
|
||||
nextBtn.addEventListener('click', function() {
|
||||
// Stoppe Timer und setze Zeit zurück
|
||||
if (timerInterval) {
|
||||
clearInterval(timerInterval);
|
||||
timerInterval = null;
|
||||
}
|
||||
startTime = null;
|
||||
|
||||
fetch('/next_text', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
if (nextBtn) {
|
||||
nextBtn.addEventListener('click', function() {
|
||||
// Stoppe Timer und setze Zeit zurück
|
||||
if (timerInterval) {
|
||||
clearInterval(timerInterval);
|
||||
timerInterval = null;
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
fullText = data.text;
|
||||
currentCharIndex = 0;
|
||||
userInput = '';
|
||||
startTime = null;
|
||||
keyStrokeCount = 0; // Zurücksetzen des Tastaturanschlagszählers
|
||||
lines = fullText.split('\n');
|
||||
userScrolled = false; // Reset Scroll-Flag
|
||||
updateTextDisplay();
|
||||
// Aktualisiere den Lektionstitel
|
||||
document.getElementById('lesson-title').textContent = data.title;
|
||||
updateStatistics();
|
||||
// Aktualisiere die Markierung der aktuellen Lektion
|
||||
updateLessonHighlight(data.index);
|
||||
})
|
||||
.catch(error => console.error('Fehler:', error));
|
||||
});
|
||||
|
||||
fetch('/next_text', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
alert('Fehler: ' + data.error);
|
||||
return;
|
||||
}
|
||||
fullText = data.text;
|
||||
currentCharIndex = 0;
|
||||
userInput = '';
|
||||
lastStartTime = null;
|
||||
totalElapsedTime = 0;
|
||||
isPaused = false;
|
||||
keyStrokeCount = 0; // Zurücksetzen des Tastaturanschlagszählers
|
||||
lines = fullText.split('\n');
|
||||
userScrolled = false; // Reset Scroll-Flag
|
||||
updateTextDisplay();
|
||||
// Aktualisiere den Lektionstitel
|
||||
document.getElementById('lesson-title').textContent = data.title;
|
||||
// Setze Play/Pause-Toggle zurück auf Pause
|
||||
document.getElementById('play-pause-pause').style.display = 'block';
|
||||
document.getElementById('play-pause-play').style.display = 'none';
|
||||
updateStatistics();
|
||||
// Aktualisiere die Markierung der aktuellen Lektion
|
||||
updateLessonHighlight(data.index);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler:', error);
|
||||
alert('Fehler beim Laden des nächsten Textes: ' + error.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Beenden Button
|
||||
endBtn.addEventListener('click', function() {
|
||||
// Hier könnten wir die aktuelle Statistik speichern
|
||||
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));
|
||||
});
|
||||
if (endBtn) {
|
||||
endBtn.addEventListener('click', function() {
|
||||
// Hier könnten wir die aktuelle Statistik speichern
|
||||
fetch('/end_session', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
alert('Sitzung beendet. Fortschritt gespeichert.');
|
||||
// window.close(); // Funktioniert nicht in allen Browsern
|
||||
// Stattdessen: Zur Startseite navigieren oder Seite neu laden
|
||||
window.location.href = '/';
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler:', error);
|
||||
alert('Fehler beim Beenden der Sitzung: ' + error.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Statistik aktualisieren
|
||||
function updateStatistics() {
|
||||
// Berechne verstrichene Zeit nur wenn der Benutzer tippt
|
||||
let elapsedTime = 0;
|
||||
if (startTime) {
|
||||
const now = new Date();
|
||||
elapsedTime = (now - startTime) / 1000; // in Sekunden
|
||||
// Berechne verstrichene Zeit
|
||||
let elapsedTime = totalElapsedTime;
|
||||
if (lastStartTime !== null) {
|
||||
elapsedTime += (new Date() - lastStartTime);
|
||||
}
|
||||
elapsedTime = elapsedTime / 1000; // in Sekunden
|
||||
|
||||
// Berechne Statistiken
|
||||
let totalKeyStrokes = keyStrokeCount; // Verwende jetzt die Anzahl aller Tastaturanschläge (inkl. Backspace)
|
||||
|
|
@ -535,4 +784,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||
|
||||
// Initialisiere Toggle-Funktionalität
|
||||
initializeProgressStatsToggle();
|
||||
|
||||
// Setze Fokus auf den Body für Tastatureingaben
|
||||
document.body.focus();
|
||||
});
|
||||
|
||||
// Fehlerbehandlung für globale Ereignisse
|
||||
window.addEventListener('error', function(e) {
|
||||
console.error('JavaScript Fehler:', e.error);
|
||||
});
|
||||
|
||||
// Sicherstellen, dass der Body fokussiert ist
|
||||
document.addEventListener('click', function() {
|
||||
if (document.activeElement !== document.body) {
|
||||
document.body.focus();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
<title>Typewriter Trainer</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
|
||||
</head>
|
||||
<body class="bg-light" data-bs-theme="light">
|
||||
<div class="container py-4">
|
||||
|
|
@ -15,9 +16,30 @@
|
|||
<div class="card-header bg-primary text-white">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h2 class="mb-0">Typewriter Trainer</h2>
|
||||
<button id="theme-toggle" class="btn btn-outline-light rounded-circle" style="width: 32px; height: 32px; padding: 0;">
|
||||
<i class="fas fa-cog"></i>
|
||||
</button>
|
||||
<div class="d-flex flex-row-reverse align-items-center gap-2">
|
||||
<i data-lucide="settings" class="icon-placeholder" style="width: 24px; height: 24px; cursor: pointer;"></i>
|
||||
<div id="theme-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;">
|
||||
<i data-lucide="sun" id="theme-toggle-sun" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0;"></i>
|
||||
<i data-lucide="moon" id="theme-toggle-moon" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0; display: none;"></i>
|
||||
</div>
|
||||
<div id="lock-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;">
|
||||
<i data-lucide="lock" id="lock-closed" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0;"></i>
|
||||
<i data-lucide="unlock" id="lock-open" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0; display: none;"></i>
|
||||
</div>
|
||||
<div id="volume-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;">
|
||||
<i data-lucide="volume-2" id="volume-on" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0;"></i>
|
||||
<i data-lucide="volume-x" id="volume-off" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0; display: none;"></i>
|
||||
</div>
|
||||
<div id="play-pause-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;">
|
||||
<i data-lucide="pause" id="play-pause-pause" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0;"></i>
|
||||
<i data-lucide="play" id="play-pause-play" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0; display: none;"></i>
|
||||
</div>
|
||||
<div id="metronome-toggle" style="width: 24px; height: 24px; cursor: pointer; position: relative;">
|
||||
<i data-lucide="metronome" id="metronome-on" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0; display: none;"></i>
|
||||
<i data-lucide="circle" id="metronome-off" style="width: 24px; height: 24px; position: absolute; top: 0; left: 0;"></i>
|
||||
</div>
|
||||
<i data-lucide="help-circle" class="icon-placeholder" style="width: 24px; height: 24px; cursor: pointer;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
|
@ -47,6 +69,10 @@
|
|||
<script>
|
||||
window.fullText = {{ current_text|tojson }};
|
||||
window.currentCharIndex = 0;
|
||||
// Metronom-Einstellungen von Flask übernehmen
|
||||
window.metronomeEnabled = {{ metronome_enabled|tojson }};
|
||||
window.metronomeBPM = {{ metronome_bpm|tojson }};
|
||||
window.metronomeSound = {{ metronome_sound|tojson }};
|
||||
</script>
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue