feat(vocab): synchronize SRS session progress and update related UI elements
This commit is contained in:
@@ -472,6 +472,9 @@ export default {
|
|||||||
session.correctCount = this.correctCount;
|
session.correctCount = this.correctCount;
|
||||||
session.wrongCount = this.wrongCount;
|
session.wrongCount = this.wrongCount;
|
||||||
localStorage.setItem(key, JSON.stringify(session));
|
localStorage.setItem(key, JSON.stringify(session));
|
||||||
|
window.dispatchEvent(new CustomEvent('yourpart:vocab:srsSession:changed', {
|
||||||
|
detail: { courseId: this.openParams.courseId }
|
||||||
|
}));
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// ignore storage issues (private mode, quota, etc.)
|
// ignore storage issues (private mode, quota, etc.)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -870,9 +870,9 @@
|
|||||||
"reviewTimeNow": "jetzt",
|
"reviewTimeNow": "jetzt",
|
||||||
"reviewTimeTomorrow": "morgen",
|
"reviewTimeTomorrow": "morgen",
|
||||||
"reviewTimeInDays": "in {count} Tagen",
|
"reviewTimeInDays": "in {count} Tagen",
|
||||||
"srsDueStat": "Tageswiederholung: {scheduled} von {total}",
|
"srsDueStat": "Tageswiederholung: {scheduled} Begriffe",
|
||||||
"srsEyebrow": "Langzeitgedächtnis",
|
"srsEyebrow": "Langzeitgedächtnis",
|
||||||
"srsTitle": "Heute: {scheduled} von {total} fälligen Begriffen",
|
"srsTitle": "Heute: {scheduled} fällige Begriffe",
|
||||||
"srsIntro": "Diese Wiederholung kommt aus dem SRS-Plan einzelner Begriffe. Pro Tag werden höchstens 50 Begriffe eingeplant; weitere fällige Begriffe bleiben für die nächste Tageswiederholung erhalten.",
|
"srsIntro": "Diese Wiederholung kommt aus dem SRS-Plan einzelner Begriffe. Pro Tag werden höchstens 50 Begriffe eingeplant; weitere fällige Begriffe bleiben für die nächste Tageswiederholung erhalten.",
|
||||||
"srsStart": "Tageswiederholung starten",
|
"srsStart": "Tageswiederholung starten",
|
||||||
"courseTodayPlanIntroSrs": "Didaktische Reihenfolge: Zuerst die fällige SRS-Tageswiederholung einzelner Begriffe. Danach kommen Kurz-Wiederholungen, Blockfortschritt und ggf. intensive Wiederholung. So wird altes Material stabilisiert, bevor neues Material dazukommt."
|
"courseTodayPlanIntroSrs": "Didaktische Reihenfolge: Zuerst die fällige SRS-Tageswiederholung einzelner Begriffe. Danach kommen Kurz-Wiederholungen, Blockfortschritt und ggf. intensive Wiederholung. So wird altes Material stabilisiert, bevor neues Material dazukommt."
|
||||||
|
|||||||
@@ -870,9 +870,9 @@
|
|||||||
"reviewTimeNow": "now",
|
"reviewTimeNow": "now",
|
||||||
"reviewTimeTomorrow": "tomorrow",
|
"reviewTimeTomorrow": "tomorrow",
|
||||||
"reviewTimeInDays": "in {count} days",
|
"reviewTimeInDays": "in {count} days",
|
||||||
"srsDueStat": "Daily review: {scheduled} of {total}",
|
"srsDueStat": "Daily review: {scheduled} terms",
|
||||||
"srsEyebrow": "Long-term memory",
|
"srsEyebrow": "Long-term memory",
|
||||||
"srsTitle": "Today: {scheduled} of {total} due terms",
|
"srsTitle": "Today: {scheduled} due terms",
|
||||||
"srsIntro": "This review comes from the SRS schedule of individual terms. At most 50 terms are planned per day; any additional due terms remain for the next daily review.",
|
"srsIntro": "This review comes from the SRS schedule of individual terms. At most 50 terms are planned per day; any additional due terms remain for the next daily review.",
|
||||||
"srsStart": "Start daily review",
|
"srsStart": "Start daily review",
|
||||||
"courseTodayPlanIntroSrs": "Pedagogical order: start with the due SRS daily review for individual terms. Then quick reviews, block progress, and intensive review if needed. This stabilizes old material before new material is added."
|
"courseTodayPlanIntroSrs": "Pedagogical order: start with the due SRS daily review for individual terms. Then quick reviews, block progress, and intensive review if needed. This stabilizes old material before new material is added."
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="srsDueCount > 0" class="course-srs-plan">
|
<div v-if="srsDailyCount > 0" class="course-srs-plan">
|
||||||
<div>
|
<div>
|
||||||
<span class="course-srs-plan__eyebrow">{{ $t('socialnetwork.vocab.courses.srsEyebrow') }}</span>
|
<span class="course-srs-plan__eyebrow">{{ $t('socialnetwork.vocab.courses.srsEyebrow') }}</span>
|
||||||
<h4>{{ $t('socialnetwork.vocab.courses.srsTitle', { scheduled: srsDailyCount, total: srsDueCount }) }}</h4>
|
<h4>{{ $t('socialnetwork.vocab.courses.srsTitle', { scheduled: srsDailyCount, total: srsDueCount }) }}</h4>
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
<div v-if="todayRecommendedSteps.length > 0" class="course-today-plan">
|
<div v-if="todayRecommendedSteps.length > 0" class="course-today-plan">
|
||||||
<h4 class="course-today-plan__title">{{ $t('socialnetwork.vocab.courses.courseTodayPlanTitle') }}</h4>
|
<h4 class="course-today-plan__title">{{ $t('socialnetwork.vocab.courses.courseTodayPlanTitle') }}</h4>
|
||||||
<p class="course-today-plan__intro">
|
<p class="course-today-plan__intro">
|
||||||
{{ srsDueCount > 0
|
{{ srsDailyCount > 0
|
||||||
? $t('socialnetwork.vocab.courses.courseTodayPlanIntroSrs')
|
? $t('socialnetwork.vocab.courses.courseTodayPlanIntroSrs')
|
||||||
: dueReviewLessons.length > 0
|
: dueReviewLessons.length > 0
|
||||||
? $t('socialnetwork.vocab.courses.courseTodayPlanIntro')
|
? $t('socialnetwork.vocab.courses.courseTodayPlanIntro')
|
||||||
@@ -354,6 +354,8 @@ import { confirmAction, showApiError, showInfo, showSuccess } from '@/utils/feed
|
|||||||
import VocabPracticeDialog from '@/dialogues/socialnetwork/VocabPracticeDialog.vue';
|
import VocabPracticeDialog from '@/dialogues/socialnetwork/VocabPracticeDialog.vue';
|
||||||
import { localizeVocabCourseTitle } from '@/utils/vocabCourseTitle.js';
|
import { localizeVocabCourseTitle } from '@/utils/vocabCourseTitle.js';
|
||||||
|
|
||||||
|
const SRS_SESSION_STORAGE_VERSION = 2;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'VocabCourseView',
|
name: 'VocabCourseView',
|
||||||
components: { VocabPracticeDialog },
|
components: { VocabPracticeDialog },
|
||||||
@@ -372,6 +374,7 @@ export default {
|
|||||||
srsDueItems: [],
|
srsDueItems: [],
|
||||||
srsDueTotal: 0,
|
srsDueTotal: 0,
|
||||||
srsDailyLimit: 50,
|
srsDailyLimit: 50,
|
||||||
|
srsTodayRemaining: null,
|
||||||
srsLoading: false,
|
srsLoading: false,
|
||||||
showAddLessonDialog: false,
|
showAddLessonDialog: false,
|
||||||
assistantSettings: null,
|
assistantSettings: null,
|
||||||
@@ -422,6 +425,9 @@ export default {
|
|||||||
return Array.isArray(this.srsDueItems) ? this.srsDueItems.length : 0;
|
return Array.isArray(this.srsDueItems) ? this.srsDueItems.length : 0;
|
||||||
},
|
},
|
||||||
srsDailyCount() {
|
srsDailyCount() {
|
||||||
|
if (Number.isFinite(Number(this.srsTodayRemaining))) {
|
||||||
|
return Math.max(0, Number(this.srsTodayRemaining));
|
||||||
|
}
|
||||||
return Math.min(this.srsDueCount, this.srsDailyLimit);
|
return Math.min(this.srsDueCount, this.srsDailyLimit);
|
||||||
},
|
},
|
||||||
hardVocabCount() {
|
hardVocabCount() {
|
||||||
@@ -563,6 +569,29 @@ export default {
|
|||||||
displayCourseTitle(course) {
|
displayCourseTitle(course) {
|
||||||
return localizeVocabCourseTitle(course?.title, this.$i18n?.locale) || '';
|
return localizeVocabCourseTitle(course?.title, this.$i18n?.locale) || '';
|
||||||
},
|
},
|
||||||
|
getLocalDateKey() {
|
||||||
|
const date = new Date();
|
||||||
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
||||||
|
},
|
||||||
|
syncSrsSessionProgress() {
|
||||||
|
const key = `yourpart:vocab:srsSession:v${SRS_SESSION_STORAGE_VERSION}:${this.courseId}:${this.getLocalDateKey()}`;
|
||||||
|
try {
|
||||||
|
const session = JSON.parse(localStorage.getItem(key) || 'null');
|
||||||
|
const isCurrentSession = session
|
||||||
|
&& session.version === SRS_SESSION_STORAGE_VERSION
|
||||||
|
&& String(session.courseId) === String(this.courseId)
|
||||||
|
&& session.dateKey === this.getLocalDateKey();
|
||||||
|
if (!isCurrentSession) {
|
||||||
|
this.srsTodayRemaining = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const total = Math.max(0, Number(session.initialTotalDue) || 0);
|
||||||
|
const done = new Set(Array.isArray(session.doneIds) ? session.doneIds : []).size;
|
||||||
|
this.srsTodayRemaining = Math.max(0, total - done);
|
||||||
|
} catch (_) {
|
||||||
|
this.srsTodayRemaining = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
async refreshHardVocabList() {
|
async refreshHardVocabList() {
|
||||||
if (!this.courseId) {
|
if (!this.courseId) {
|
||||||
this.hardVocabList = [];
|
this.hardVocabList = [];
|
||||||
@@ -586,6 +615,12 @@ export default {
|
|||||||
},
|
},
|
||||||
handleWindowFocus() {
|
handleWindowFocus() {
|
||||||
this.refreshHardVocabList();
|
this.refreshHardVocabList();
|
||||||
|
this.syncSrsSessionProgress();
|
||||||
|
},
|
||||||
|
handleSrsSessionChanged(event) {
|
||||||
|
if (String(event?.detail?.courseId || '') === String(this.courseId)) {
|
||||||
|
this.syncSrsSessionProgress();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
handleHardVocabChanged(event) {
|
handleHardVocabChanged(event) {
|
||||||
const detailCourseId = event?.detail?.courseId;
|
const detailCourseId = event?.detail?.courseId;
|
||||||
@@ -635,6 +670,7 @@ export default {
|
|||||||
this.srsDailyLimit = 50;
|
this.srsDailyLimit = 50;
|
||||||
} finally {
|
} finally {
|
||||||
this.srsLoading = false;
|
this.srsLoading = false;
|
||||||
|
this.syncSrsSessionProgress();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async loadChapters() {
|
async loadChapters() {
|
||||||
@@ -1009,10 +1045,13 @@ export default {
|
|||||||
this.loadAssistantSettings()
|
this.loadAssistantSettings()
|
||||||
]);
|
]);
|
||||||
this.refreshHardVocabList();
|
this.refreshHardVocabList();
|
||||||
|
this.syncSrsSessionProgress();
|
||||||
window.addEventListener('focus', this.handleWindowFocus);
|
window.addEventListener('focus', this.handleWindowFocus);
|
||||||
|
window.addEventListener('yourpart:vocab:srsSession:changed', this.handleSrsSessionChanged);
|
||||||
},
|
},
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
window.removeEventListener('focus', this.handleWindowFocus);
|
window.removeEventListener('focus', this.handleWindowFocus);
|
||||||
|
window.removeEventListener('yourpart:vocab:srsSession:changed', this.handleSrsSessionChanged);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user