Luca448 commited on
Commit
f5a2dbc
·
1 Parent(s): e9ef090

Savestate: Implement deck details, manual flashcard addition, and free practice mode

Browse files
Files changed (3) hide show
  1. index.html +1 -1
  2. src/db/database.js +19 -29
  3. src/views/learn/learn-view.js +84 -9
index.html CHANGED
@@ -51,7 +51,7 @@
51
  </head>
52
  <body class="dark-theme in-chat-view">
53
 
54
- <div id="live-version-badge" style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); font-size: 10px; color: #4ade80; opacity: 0.9; z-index: 99999; pointer-events: none; font-weight: bold; font-family: monospace; letter-spacing: 1px; background: rgba(0,0,0,0.5); padding: 4px 12px; border-radius: 10px; border: 1px solid rgba(74,222,128,0.5); box-shadow: 0 0 10px rgba(74,222,128,0.2);">LIVE UPDATE FUNKTIONIERT! 🚀</div>
55
 
56
  <!-- SVG Filters for Liquid Glass Refraction -->
57
  <svg width="0" height="0" style="position: absolute; pointer-events: none;">
 
51
  </head>
52
  <body class="dark-theme in-chat-view">
53
 
54
+ <div id="live-version-badge" style="position: fixed; top: 50px; left: 50%; transform: translateX(-50%); font-size: 10px; color: var(--text-secondary); opacity: 0.6; z-index: 99999; pointer-events: none; font-weight: 500; font-family: monospace; letter-spacing: 1px; background: rgba(0,0,0,0.2); padding: 2px 8px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.05);">BUILD 1.1.48</div>
55
 
56
  <!-- SVG Filters for Liquid Glass Refraction -->
57
  <svg width="0" height="0" style="position: absolute; pointer-events: none;">
src/db/database.js CHANGED
@@ -346,7 +346,6 @@ class Database {
346
  [id, originalPath, isDir ? 1 : 0, now]
347
  );
348
  }
349
-
350
  removeTrashItem(id) {
351
  if (!this.db) return;
352
  this.exec("DELETE FROM trash_metadata WHERE id = ?", [id]);
@@ -355,23 +354,16 @@ class Database {
355
 
356
  getDecks() {
357
  try {
358
- const res = this.db.exec("SELECT * FROM flashcard_decks ORDER BY created_at DESC");
359
- if (res.length === 0) return [];
360
-
361
- const decks = res[0].values.map(row => {
362
- const deck = {};
363
- res[0].columns.forEach((col, i) => deck[col] = row[i]);
364
- return deck;
365
- });
366
 
367
  // Get due cards count for each deck
368
  const now = new Date().toISOString();
369
  decks.forEach(deck => {
370
- const countRes = this.db.exec(`SELECT COUNT(*) FROM flashcards WHERE deck_id = '${deck.id}' AND next_review <= '${now}'`);
371
- deck.due_count = countRes.length > 0 ? countRes[0].values[0][0] : 0;
372
 
373
- const totalRes = this.db.exec(`SELECT COUNT(*) FROM flashcards WHERE deck_id = '${deck.id}'`);
374
- deck.total_count = totalRes.length > 0 ? totalRes[0].values[0][0] : 0;
375
  });
376
 
377
  return decks;
@@ -388,14 +380,12 @@ class Database {
388
  "INSERT INTO flashcard_decks (id, title, description, color, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
389
  [id, title, description, color, now, now]
390
  );
391
- this.saveToStorage();
392
  return { id, title, description, color, created_at: now, updated_at: now, due_count: 0, total_count: 0 };
393
  }
394
 
395
  deleteDeck(deckId) {
396
  this.exec("DELETE FROM flashcards WHERE deck_id = ?", [deckId]);
397
  this.exec("DELETE FROM flashcard_decks WHERE id = ?", [deckId]);
398
- this.saveToStorage();
399
  }
400
 
401
  addFlashcard(deckId, front, back, imageUrl = null) {
@@ -408,31 +398,32 @@ class Database {
408
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
409
  [id, deckId, front, back, imageUrl, now, 2.5, 0, 0, now, now]
410
  );
411
- this.saveToStorage();
412
  return id;
413
  }
414
 
415
  getDueCards(deckId) {
416
  const now = new Date().toISOString();
417
- const res = this.db.exec(
418
- `SELECT * FROM flashcards WHERE deck_id = '${deckId}' AND next_review <= '${now}' ORDER BY next_review ASC LIMIT 50`
 
 
 
 
 
 
 
 
 
419
  );
420
- if (res.length === 0) return [];
421
-
422
- return res[0].values.map(row => {
423
- const card = {};
424
- res[0].columns.forEach((col, i) => card[col] = row[i]);
425
- return card;
426
- });
427
  }
428
 
429
  // SM-2 Algorithm update
430
  updateCardProgress(cardId, quality) {
431
  // quality: 0 (Again), 1 (Hard), 2 (Good), 3 (Easy)
432
- const res = this.db.exec(`SELECT ease_factor, interval, repetitions FROM flashcards WHERE id = '${cardId}'`);
433
  if (res.length === 0) return;
434
 
435
- let [ease_factor, interval, repetitions] = res[0].values[0];
436
 
437
  if (quality === 0) {
438
  repetitions = 0;
@@ -464,10 +455,9 @@ class Database {
464
  const now = new Date().toISOString();
465
 
466
  this.exec(
467
- `UPDATE flashcards SET next_review = ?, ease_factor = ?, interval = ?, repetitions = ?, updated_at = ? WHERE id = ?`,
468
  [next_review_iso, ease_factor, interval, repetitions, now, cardId]
469
  );
470
- this.saveToStorage();
471
  }
472
 
473
  }
 
346
  [id, originalPath, isDir ? 1 : 0, now]
347
  );
348
  }
 
349
  removeTrashItem(id) {
350
  if (!this.db) return;
351
  this.exec("DELETE FROM trash_metadata WHERE id = ?", [id]);
 
354
 
355
  getDecks() {
356
  try {
357
+ const decks = this.exec("SELECT * FROM flashcard_decks ORDER BY created_at DESC");
 
 
 
 
 
 
 
358
 
359
  // Get due cards count for each deck
360
  const now = new Date().toISOString();
361
  decks.forEach(deck => {
362
+ const countRes = this.exec("SELECT COUNT(*) as count FROM flashcards WHERE deck_id = ? AND next_review <= ?", [deck.id, now]);
363
+ deck.due_count = countRes.length > 0 ? countRes[0].count : 0;
364
 
365
+ const totalRes = this.exec("SELECT COUNT(*) as count FROM flashcards WHERE deck_id = ?", [deck.id]);
366
+ deck.total_count = totalRes.length > 0 ? totalRes[0].count : 0;
367
  });
368
 
369
  return decks;
 
380
  "INSERT INTO flashcard_decks (id, title, description, color, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
381
  [id, title, description, color, now, now]
382
  );
 
383
  return { id, title, description, color, created_at: now, updated_at: now, due_count: 0, total_count: 0 };
384
  }
385
 
386
  deleteDeck(deckId) {
387
  this.exec("DELETE FROM flashcards WHERE deck_id = ?", [deckId]);
388
  this.exec("DELETE FROM flashcard_decks WHERE id = ?", [deckId]);
 
389
  }
390
 
391
  addFlashcard(deckId, front, back, imageUrl = null) {
 
398
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
399
  [id, deckId, front, back, imageUrl, now, 2.5, 0, 0, now, now]
400
  );
 
401
  return id;
402
  }
403
 
404
  getDueCards(deckId) {
405
  const now = new Date().toISOString();
406
+ return this.exec(
407
+ "SELECT * FROM flashcards WHERE deck_id = ? AND next_review <= ? ORDER BY next_review ASC LIMIT 50",
408
+ [deckId, now]
409
+ );
410
+ }
411
+
412
+ getAllCards(deckId) {
413
+ // For free practice, shuffle them or just order randomly
414
+ return this.exec(
415
+ "SELECT * FROM flashcards WHERE deck_id = ? ORDER BY RANDOM() LIMIT 100",
416
+ [deckId]
417
  );
 
 
 
 
 
 
 
418
  }
419
 
420
  // SM-2 Algorithm update
421
  updateCardProgress(cardId, quality) {
422
  // quality: 0 (Again), 1 (Hard), 2 (Good), 3 (Easy)
423
+ const res = this.exec("SELECT ease_factor, interval, repetitions FROM flashcards WHERE id = ?", [cardId]);
424
  if (res.length === 0) return;
425
 
426
+ let { ease_factor, interval, repetitions } = res[0];
427
 
428
  if (quality === 0) {
429
  repetitions = 0;
 
455
  const now = new Date().toISOString();
456
 
457
  this.exec(
458
+ "UPDATE flashcards SET next_review = ?, ease_factor = ?, interval = ?, repetitions = ?, updated_at = ? WHERE id = ?",
459
  [next_review_iso, ease_factor, interval, repetitions, now, cardId]
460
  );
 
461
  }
462
 
463
  }
src/views/learn/learn-view.js CHANGED
@@ -44,7 +44,7 @@ export class LearnView {
44
  decksHtml = `
45
  <div class="decks-grid">
46
  ${this.decks.map(deck => `
47
- <div class="deck-card" onclick="window.learnView.startStudy('${deck.id}')">
48
  <div class="deck-icon-wrapper" style="background: ${deck.color}20; color: ${deck.color};">
49
  ${deck.title.charAt(0).toUpperCase()}
50
  </div>
@@ -61,7 +61,7 @@ export class LearnView {
61
 
62
  this.container.innerHTML = `
63
  <div class="learn-view">
64
- <div style="display: flex; justify-content: space-between; align-items: center;">
65
  <h2 style="margin: 0; font-size: 28px; color: var(--text-primary);">Lernen</h2>
66
  <button id="add-deck-btn" class="icon-button" style="background: var(--surface-2); border-radius: 50%; padding: 8px;">
67
  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
@@ -83,16 +83,91 @@ export class LearnView {
83
  }
84
  }
85
 
86
- startStudy(deckId) {
87
  this.activeDeckId = deckId;
88
- this.dueCards = db.getDueCards(deckId);
89
- this.currentCardIndex = 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
 
 
 
 
 
91
  if (this.dueCards.length === 0) {
92
  alert('Für heute hast du alle Karten in diesem Stapel gelernt!');
93
  return;
94
  }
95
-
 
 
 
 
 
 
 
 
 
 
 
 
96
  this.renderStudyMode();
97
  }
98
 
@@ -102,8 +177,8 @@ export class LearnView {
102
  <div class="learn-view" style="align-items: center; justify-content: center;">
103
  <div style="font-size: 60px; margin-bottom: 20px;">🎉</div>
104
  <h2 style="margin: 0 0 10px 0;">Stapel abgeschlossen!</h2>
105
- <p style="color: var(--text-secondary); margin-bottom: 30px;">Du hast alle fälligen Karten gelernt.</p>
106
- <button onclick="window.learnView.renderOverview()" style="padding: 12px 24px; background: var(--primary); border-radius: 20px; color: white; border: none; font-weight: 600; cursor: pointer;">Zurück zur Übersicht</button>
107
  </div>
108
  `;
109
  return;
@@ -116,7 +191,7 @@ export class LearnView {
116
  this.container.innerHTML = `
117
  <div class="learn-view study-container">
118
  <div style="width: 100%; max-width: 380px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
119
- <button onclick="window.learnView.renderOverview()" style="background: none; border: none; color: var(--text-secondary); cursor: pointer;">
120
  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
121
  </button>
122
  <span style="color: var(--text-secondary); font-weight: 600;">${this.currentCardIndex + 1} / ${this.dueCards.length}</span>
 
44
  decksHtml = `
45
  <div class="decks-grid">
46
  ${this.decks.map(deck => `
47
+ <div class="deck-card" onclick="window.learnView.renderDeckDetails('${deck.id}')">
48
  <div class="deck-icon-wrapper" style="background: ${deck.color}20; color: ${deck.color};">
49
  ${deck.title.charAt(0).toUpperCase()}
50
  </div>
 
61
 
62
  this.container.innerHTML = `
63
  <div class="learn-view">
64
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
65
  <h2 style="margin: 0; font-size: 28px; color: var(--text-primary);">Lernen</h2>
66
  <button id="add-deck-btn" class="icon-button" style="background: var(--surface-2); border-radius: 50%; padding: 8px;">
67
  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg>
 
83
  }
84
  }
85
 
86
+ renderDeckDetails(deckId) {
87
  this.activeDeckId = deckId;
88
+ const deck = this.decks.find(d => d.id === deckId);
89
+ if (!deck) return this.renderOverview();
90
+
91
+ // Re-fetch counts
92
+ const dueCards = db.getDueCards(deckId);
93
+ const allCards = db.getAllCards(deckId);
94
+ deck.due_count = dueCards.length;
95
+ deck.total_count = allCards.length;
96
+
97
+ this.container.innerHTML = `
98
+ <div class="learn-view">
99
+ <div style="display: flex; align-items: center; margin-bottom: 24px; gap: 12px;">
100
+ <button onclick="window.learnView.renderOverview()" class="icon-button" style="padding: 8px; border-radius: 50%; background: var(--surface-2);">
101
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
102
+ </button>
103
+ <h2 style="margin: 0; font-size: 24px;">${deck.title}</h2>
104
+ </div>
105
+
106
+ <div style="background: var(--surface-1); border-radius: 20px; padding: 24px; text-align: center; margin-bottom: 24px; border: 1px solid var(--border);">
107
+ <div style="font-size: 48px; margin-bottom: 12px;">${deck.title.charAt(0).toUpperCase()}</div>
108
+ <p style="color: var(--text-secondary); margin-bottom: 24px;">${deck.total_count} Karten insgesamt</p>
109
+
110
+ <div style="display: flex; flex-direction: column; gap: 12px;">
111
+ <button onclick="window.learnView.startStudyDue('${deckId}')" style="padding: 16px; background: var(--primary); color: white; border: none; border-radius: 12px; font-weight: bold; font-size: 16px; cursor: pointer; display: flex; justify-content: space-between; align-items: center;">
112
+ <span>Fällige Karten lernen</span>
113
+ ${deck.due_count > 0 ? `<span style="background: rgba(255,255,255,0.2); padding: 4px 10px; border-radius: 20px; font-size: 14px;">${deck.due_count}</span>` : `<span style="opacity: 0.7; font-size: 14px;">Fertig</span>`}
114
+ </button>
115
+ <button onclick="window.learnView.startStudyAll('${deckId}')" style="padding: 16px; background: var(--surface-2); color: var(--text-primary); border: 1px solid var(--border); border-radius: 12px; font-weight: bold; font-size: 16px; cursor: pointer; display: flex; justify-content: space-between; align-items: center;">
116
+ <span>Alle Karten frei üben</span>
117
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>
118
+ </button>
119
+ </div>
120
+ </div>
121
+
122
+ <h3 style="margin-bottom: 16px; font-size: 18px;">Karten verwalten</h3>
123
+ <div style="background: var(--surface-1); border-radius: 20px; padding: 20px; border: 1px solid var(--border);">
124
+ <div style="display: flex; flex-direction: column; gap: 12px;">
125
+ <input type="text" id="new-card-front" placeholder="Vorderseite (Frage)" style="background: var(--background); border: 1px solid var(--border); color: var(--text-primary); padding: 12px; border-radius: 10px; font-size: 16px;">
126
+ <input type="text" id="new-card-back" placeholder="Rückseite (Antwort)" style="background: var(--background); border: 1px solid var(--border); color: var(--text-primary); padding: 12px; border-radius: 10px; font-size: 16px;">
127
+ <button onclick="window.learnView.addCard('${deckId}')" style="padding: 12px; background: var(--primary); color: white; border: none; border-radius: 10px; font-weight: bold; cursor: pointer;">Hinzufügen</button>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ `;
132
+ }
133
+
134
+ addCard(deckId) {
135
+ const frontEl = document.getElementById('new-card-front');
136
+ const backEl = document.getElementById('new-card-back');
137
+ const front = frontEl.value.trim();
138
+ const back = backEl.value.trim();
139
+ if (!front || !back) {
140
+ alert('Bitte fülle Vorder- und Rückseite aus.');
141
+ return;
142
+ }
143
+ db.addFlashcard(deckId, front, back);
144
+
145
+ // Clear inputs and show success
146
+ frontEl.value = '';
147
+ backEl.value = '';
148
 
149
+ this.renderDeckDetails(deckId); // Refresh stats
150
+ }
151
+
152
+ startStudyDue(deckId) {
153
+ this.dueCards = db.getDueCards(deckId);
154
  if (this.dueCards.length === 0) {
155
  alert('Für heute hast du alle Karten in diesem Stapel gelernt!');
156
  return;
157
  }
158
+ this.isFreePractice = false;
159
+ this.currentCardIndex = 0;
160
+ this.renderStudyMode();
161
+ }
162
+
163
+ startStudyAll(deckId) {
164
+ this.dueCards = db.getAllCards(deckId);
165
+ if (this.dueCards.length === 0) {
166
+ alert('Dieser Stapel ist noch leer.');
167
+ return;
168
+ }
169
+ this.isFreePractice = true;
170
+ this.currentCardIndex = 0;
171
  this.renderStudyMode();
172
  }
173
 
 
177
  <div class="learn-view" style="align-items: center; justify-content: center;">
178
  <div style="font-size: 60px; margin-bottom: 20px;">🎉</div>
179
  <h2 style="margin: 0 0 10px 0;">Stapel abgeschlossen!</h2>
180
+ <p style="color: var(--text-secondary); margin-bottom: 30px;">Du hast alle anstehenden Karten gelernt.</p>
181
+ <button onclick="window.learnView.renderDeckDetails('${this.activeDeckId}')" style="padding: 12px 24px; background: var(--primary); border-radius: 20px; color: white; border: none; font-weight: 600; cursor: pointer;">Zurück zum Stapel</button>
182
  </div>
183
  `;
184
  return;
 
191
  this.container.innerHTML = `
192
  <div class="learn-view study-container">
193
  <div style="width: 100%; max-width: 380px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
194
+ <button onclick="window.learnView.renderDeckDetails('${this.activeDeckId}')" style="background: none; border: none; color: var(--text-secondary); cursor: pointer;">
195
  <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
196
  </button>
197
  <span style="color: var(--text-secondary); font-weight: 600;">${this.currentCardIndex + 1} / ${this.dueCards.length}</span>