mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-16 13:55:51 -07:00
perf(stats): calculate vocabulary totals off main thread
This commit is contained in:
@@ -98,24 +98,26 @@ export function VocabularyTab({
|
||||
<div className="grid grid-cols-2 xl:grid-cols-4 gap-3">
|
||||
<StatCard
|
||||
label="Unique Words"
|
||||
value={formatNumber(displayedSummary.uniqueWords)}
|
||||
value={summary ? formatNumber(displayedSummary.uniqueWords) : '…'}
|
||||
color="text-ctp-blue"
|
||||
/>
|
||||
{displayedSummary.knownWordCount !== null && (
|
||||
{displayedSummary.knownWordCount !== null ? (
|
||||
<StatCard
|
||||
label="Known Words"
|
||||
value={`${formatNumber(displayedSummary.knownWordCount)} (${displayedSummary.uniqueWords > 0 ? Math.round((displayedSummary.knownWordCount / displayedSummary.uniqueWords) * 100) : 0}%)`}
|
||||
color="text-ctp-green"
|
||||
/>
|
||||
)}
|
||||
) : knownWords.size > 0 ? (
|
||||
<StatCard label="Known Words" value="…" color="text-ctp-green" />
|
||||
) : null}
|
||||
<StatCard
|
||||
label="Unique Kanji"
|
||||
value={formatNumber(summary?.uniqueKanji ?? 0)}
|
||||
value={summary ? formatNumber(summary.uniqueKanji) : '…'}
|
||||
color="text-ctp-teal"
|
||||
/>
|
||||
<StatCard
|
||||
label="New This Week"
|
||||
value={`+${formatNumber(displayedSummary.newThisWeek)}`}
|
||||
value={summary ? `+${formatNumber(displayedSummary.newThisWeek)}` : '…'}
|
||||
color="text-ctp-mauve"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -17,14 +17,10 @@ export function useVocabulary() {
|
||||
let cancelled = false;
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setSummary(null);
|
||||
const client = getStatsClient();
|
||||
Promise.allSettled([
|
||||
client.getVocabulary(500),
|
||||
client.getKanji(200),
|
||||
client.getKnownWords(),
|
||||
client.getVocabularySummary(),
|
||||
])
|
||||
.then(([wordsResult, kanjiResult, knownResult, summaryResult]) => {
|
||||
Promise.allSettled([client.getVocabulary(500), client.getKanji(200), client.getKnownWords()])
|
||||
.then(([wordsResult, kanjiResult, knownResult]) => {
|
||||
if (cancelled) return;
|
||||
const errors: string[] = [];
|
||||
|
||||
@@ -44,12 +40,6 @@ export function useVocabulary() {
|
||||
setKnownWords(new Set(knownResult.value));
|
||||
}
|
||||
|
||||
if (summaryResult.status === 'fulfilled') {
|
||||
setSummary(summaryResult.value);
|
||||
} else {
|
||||
errors.push(summaryResult.reason.message);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
setError(errors.join('; '));
|
||||
}
|
||||
@@ -58,6 +48,14 @@ export function useVocabulary() {
|
||||
if (cancelled) return;
|
||||
setLoading(false);
|
||||
});
|
||||
void client
|
||||
.getVocabularySummary()
|
||||
.then((nextSummary) => {
|
||||
if (!cancelled) setSummary(nextSummary);
|
||||
})
|
||||
.catch((summaryError: unknown) => {
|
||||
console.error('Failed to load vocabulary summary', summaryError);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
|
||||
const VOCABULARY_TAB_PATH = fileURLToPath(
|
||||
new URL('../components/vocabulary/VocabularyTab.tsx', import.meta.url),
|
||||
);
|
||||
const VOCABULARY_HOOK_PATH = fileURLToPath(new URL('../hooks/useVocabulary.ts', import.meta.url));
|
||||
|
||||
test('VocabularyTab declares all hooks before loading and error early returns', () => {
|
||||
const source = fs.readFileSync(VOCABULARY_TAB_PATH, 'utf8');
|
||||
@@ -33,5 +34,15 @@ test('VocabularyTab uses database-wide summary totals for its stat cards', () =>
|
||||
);
|
||||
assert.match(source, /uniqueWords: summary\?\.uniqueWordsWithoutNames \?\? 0/);
|
||||
assert.match(source, /uniqueWords: summary\?\.uniqueWords \?\? 0/);
|
||||
assert.match(source, /value=\{formatNumber\(summary\?\.uniqueKanji \?\? 0\)\}/);
|
||||
assert.match(source, /value=\{summary \? formatNumber\(summary\.uniqueKanji\) : '…'\}/);
|
||||
});
|
||||
|
||||
test('useVocabulary loads exact card totals without holding up the vocabulary tables', () => {
|
||||
const source = fs.readFileSync(VOCABULARY_HOOK_PATH, 'utf8');
|
||||
|
||||
assert.match(
|
||||
source,
|
||||
/Promise\.allSettled\(\[\s*client\.getVocabulary\(500\),\s*client\.getKanji\(200\),\s*client\.getKnownWords\(\),?\s*\]\)/,
|
||||
);
|
||||
assert.match(source, /void client\s*\.getVocabularySummary\(\)\s*\.then\(/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user