qwerty-cli 0.0.1-alpha.7 → 0.0.1-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +333 -254
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Command as Command7 } from "commander";
|
|
|
4
4
|
// package.json
|
|
5
5
|
var package_default = {
|
|
6
6
|
name: "qwerty-cli",
|
|
7
|
-
version: "0.0.1-alpha.
|
|
7
|
+
version: "0.0.1-alpha.8",
|
|
8
8
|
description: "Terminal clone of qwerty-learner: typing practice for English vocabulary, with chapters, dictation, mistake book, and audio.",
|
|
9
9
|
type: "module",
|
|
10
10
|
bin: {
|
|
@@ -1018,6 +1018,9 @@ var en = {
|
|
|
1018
1018
|
chapterDone: "chapter done",
|
|
1019
1019
|
resumeHint: "Enter resume \xB7 Esc menu",
|
|
1020
1020
|
nextHint: "Enter next \xB7 Esc menu",
|
|
1021
|
+
pausedHintRight: "Enter resume",
|
|
1022
|
+
nextHintRight: "Enter next",
|
|
1023
|
+
infoChipLabel: "info",
|
|
1021
1024
|
infoFmt: (dict, chapter, completed, total, wpm, accPct) => `${dict} \xB7 ${chapter} \xB7 ${completed}/${total} \xB7 ${wpm} wpm \xB7 ${accPct}%`
|
|
1022
1025
|
}
|
|
1023
1026
|
};
|
|
@@ -1243,6 +1246,9 @@ var zh = {
|
|
|
1243
1246
|
chapterDone: "chapter done",
|
|
1244
1247
|
resumeHint: "Enter resume \xB7 Esc menu",
|
|
1245
1248
|
nextHint: "Enter next \xB7 Esc menu",
|
|
1249
|
+
pausedHintRight: "Enter \u7EE7\u7EED",
|
|
1250
|
+
nextHintRight: "Enter \u4E0B\u4E00\u7AE0",
|
|
1251
|
+
infoChipLabel: "\u4FE1\u606F",
|
|
1246
1252
|
infoFmt: (dict, chapter, completed, total, wpm, accPct) => `${dict} \xB7 ${chapter} \xB7 ${completed}/${total} \xB7 ${wpm} wpm \xB7 ${accPct}%`
|
|
1247
1253
|
}
|
|
1248
1254
|
};
|
|
@@ -1339,8 +1345,8 @@ import { useState as useState6 } from "react";
|
|
|
1339
1345
|
import { Box as Box3, Text as Text2, useApp, useInput } from "ink";
|
|
1340
1346
|
|
|
1341
1347
|
// src/ui/components/BigWord.tsx
|
|
1342
|
-
import { Box as Box2, Text
|
|
1343
|
-
import { jsx as jsx7
|
|
1348
|
+
import { Box as Box2, Text } from "ink";
|
|
1349
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1344
1350
|
var PALETTE = {
|
|
1345
1351
|
accent: "#5eead4",
|
|
1346
1352
|
muted: "#6b7280",
|
|
@@ -1351,31 +1357,14 @@ var PALETTE = {
|
|
|
1351
1357
|
error: "#f87171"
|
|
1352
1358
|
};
|
|
1353
1359
|
function BigWord({ target, typed, error = false, hideTarget = false }) {
|
|
1354
|
-
const { stdout } = useStdout2();
|
|
1355
|
-
const cols = stdout?.columns ?? 80;
|
|
1356
1360
|
const chars = [...target];
|
|
1357
1361
|
const typedChars = [...typed];
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
return /* @__PURE__ */ jsxs(Text, { bold: true, color, children: [
|
|
1365
|
-
display,
|
|
1366
|
-
i < chars.length - 1 ? sep : ""
|
|
1367
|
-
] }, i);
|
|
1368
|
-
}) }),
|
|
1369
|
-
/* @__PURE__ */ jsx7(Box2, { children: chars.map((ch, i) => {
|
|
1370
|
-
const isTyped = i < typedChars.length;
|
|
1371
|
-
const trackChar = isTyped ? "\u2501" : "\u2500";
|
|
1372
|
-
const color = error ? PALETTE.error : isTyped ? PALETTE.accent : PALETTE.muted;
|
|
1373
|
-
return /* @__PURE__ */ jsxs(Text, { color, children: [
|
|
1374
|
-
trackChar,
|
|
1375
|
-
i < chars.length - 1 ? sep : ""
|
|
1376
|
-
] }, i);
|
|
1377
|
-
}) })
|
|
1378
|
-
] });
|
|
1362
|
+
return /* @__PURE__ */ jsx7(Box2, { paddingY: 4, justifyContent: "center", children: chars.map((ch, i) => {
|
|
1363
|
+
const isTyped = i < typedChars.length;
|
|
1364
|
+
const display = hideTarget && !isTyped ? "_" : isTyped ? typedChars[i] : ch;
|
|
1365
|
+
const color = error ? PALETTE.error : isTyped ? PALETTE.accent : PALETTE.muted;
|
|
1366
|
+
return /* @__PURE__ */ jsx7(Text, { bold: true, color, children: display }, i);
|
|
1367
|
+
}) });
|
|
1379
1368
|
}
|
|
1380
1369
|
|
|
1381
1370
|
// src/util/text.ts
|
|
@@ -1409,7 +1398,7 @@ function truncateName(name, max) {
|
|
|
1409
1398
|
}
|
|
1410
1399
|
|
|
1411
1400
|
// src/ui/screens/MainMenu.tsx
|
|
1412
|
-
import { jsx as jsx8, jsxs
|
|
1401
|
+
import { jsx as jsx8, jsxs } from "react/jsx-runtime";
|
|
1413
1402
|
function MainMenu({ cfg }) {
|
|
1414
1403
|
const [selected, setSelected] = useState6(0);
|
|
1415
1404
|
const { exit } = useApp();
|
|
@@ -1479,10 +1468,10 @@ function MainMenu({ cfg }) {
|
|
|
1479
1468
|
}
|
|
1480
1469
|
}
|
|
1481
1470
|
});
|
|
1482
|
-
return /* @__PURE__ */
|
|
1483
|
-
/* @__PURE__ */
|
|
1471
|
+
return /* @__PURE__ */ jsxs(Box3, { flexDirection: "column", paddingX: 2, paddingY: 1, width: "100%", children: [
|
|
1472
|
+
/* @__PURE__ */ jsxs(Box3, { children: [
|
|
1484
1473
|
/* @__PURE__ */ jsx8(Text2, { bold: true, color: PALETTE.accent, children: t.app.title }),
|
|
1485
|
-
/* @__PURE__ */
|
|
1474
|
+
/* @__PURE__ */ jsxs(Text2, { color: PALETTE.muted, children: [
|
|
1486
1475
|
" \xB7 ",
|
|
1487
1476
|
t.app.subtitle
|
|
1488
1477
|
] })
|
|
@@ -1490,22 +1479,22 @@ function MainMenu({ cfg }) {
|
|
|
1490
1479
|
/* @__PURE__ */ jsx8(Box3, { marginTop: 2, flexDirection: "column", children: items.map((it, i) => {
|
|
1491
1480
|
const active = i === selected;
|
|
1492
1481
|
const pad = " ".repeat(Math.max(0, labelW - visibleWidth2(it.label)));
|
|
1493
|
-
return /* @__PURE__ */
|
|
1482
|
+
return /* @__PURE__ */ jsxs(Box3, { children: [
|
|
1494
1483
|
/* @__PURE__ */ jsx8(Text2, { color: active ? PALETTE.accent : PALETTE.muted, children: active ? "\u258C " : " " }),
|
|
1495
|
-
/* @__PURE__ */
|
|
1484
|
+
/* @__PURE__ */ jsxs(Text2, { color: active ? PALETTE.accent : PALETTE.muted, children: [
|
|
1496
1485
|
"[",
|
|
1497
1486
|
it.key,
|
|
1498
1487
|
"]"
|
|
1499
1488
|
] }),
|
|
1500
1489
|
/* @__PURE__ */ jsx8(Text2, { children: " " }),
|
|
1501
|
-
/* @__PURE__ */
|
|
1490
|
+
/* @__PURE__ */ jsxs(Text2, { bold: active, color: active ? PALETTE.text : PALETTE.muted, children: [
|
|
1502
1491
|
it.label,
|
|
1503
1492
|
pad
|
|
1504
1493
|
] }),
|
|
1505
1494
|
/* @__PURE__ */ jsx8(Text2, { color: PALETTE.muted, children: it.hint })
|
|
1506
1495
|
] }, it.key);
|
|
1507
1496
|
}) }),
|
|
1508
|
-
/* @__PURE__ */ jsx8(Box3, { marginTop: 2, children: /* @__PURE__ */
|
|
1497
|
+
/* @__PURE__ */ jsx8(Box3, { marginTop: 2, children: /* @__PURE__ */ jsxs(Text2, { color: PALETTE.muted, children: [
|
|
1509
1498
|
t.mainMenu.hint,
|
|
1510
1499
|
" \xB7 ",
|
|
1511
1500
|
t.mainMenu.helpHint
|
|
@@ -1876,6 +1865,12 @@ function sparkline(values) {
|
|
|
1876
1865
|
return SPARK[Math.max(0, Math.min(SPARK.length - 1, idx))];
|
|
1877
1866
|
}).join("");
|
|
1878
1867
|
}
|
|
1868
|
+
function dailyValues(sessions, days, metric, now = /* @__PURE__ */ new Date()) {
|
|
1869
|
+
const buckets = dailyBuckets(sessions, days, now);
|
|
1870
|
+
if (metric === "wpm") return buckets.map((b) => b.wpm);
|
|
1871
|
+
if (metric === "accuracy") return buckets.map((b) => b.accuracy * 100);
|
|
1872
|
+
return buckets.map((b) => b.sessions);
|
|
1873
|
+
}
|
|
1879
1874
|
function dailyBuckets(sessions, days, now = /* @__PURE__ */ new Date()) {
|
|
1880
1875
|
const out = [];
|
|
1881
1876
|
const byDay = /* @__PURE__ */ new Map();
|
|
@@ -1974,71 +1969,105 @@ function useSessionPersistence(meta) {
|
|
|
1974
1969
|
}
|
|
1975
1970
|
|
|
1976
1971
|
// src/ui/screens/StealthPracticeLayout.tsx
|
|
1977
|
-
import { Box as Box4, Text as Text3 } from "ink";
|
|
1978
|
-
import { jsx as jsx9, jsxs as
|
|
1972
|
+
import { Box as Box4, Text as Text3, useStdout as useStdout2 } from "ink";
|
|
1973
|
+
import { jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1979
1974
|
var TYPED = "#d4d4d4";
|
|
1980
1975
|
var UNTYPED = "#808080";
|
|
1981
1976
|
var DIM = "#6b6b6b";
|
|
1977
|
+
var RIGHT_WIDTH = 28;
|
|
1982
1978
|
function fmtTime(ms) {
|
|
1983
1979
|
const total = Math.floor(ms / 1e3);
|
|
1984
1980
|
const m = Math.floor(total / 60);
|
|
1985
1981
|
const s = total % 60;
|
|
1986
1982
|
return `${m}:${String(s).padStart(2, "0")}`;
|
|
1987
1983
|
}
|
|
1984
|
+
function useLeftWidth() {
|
|
1985
|
+
const { stdout } = useStdout2();
|
|
1986
|
+
const cols = stdout?.columns ?? 80;
|
|
1987
|
+
return Math.max(20, cols - RIGHT_WIDTH);
|
|
1988
|
+
}
|
|
1989
|
+
function Row({ left, right }) {
|
|
1990
|
+
const leftWidth = useLeftWidth();
|
|
1991
|
+
return /* @__PURE__ */ jsxs2(Box4, { children: [
|
|
1992
|
+
/* @__PURE__ */ jsx9(Box4, { width: leftWidth, children: left }),
|
|
1993
|
+
/* @__PURE__ */ jsx9(Box4, { width: RIGHT_WIDTH, justifyContent: "flex-end", children: right })
|
|
1994
|
+
] });
|
|
1995
|
+
}
|
|
1988
1996
|
function StealthTyping(props) {
|
|
1989
1997
|
const t = useStrings();
|
|
1990
1998
|
const target = [...props.target];
|
|
1991
1999
|
const typed = [...props.typed];
|
|
1992
|
-
|
|
1993
|
-
/* @__PURE__ */ jsx9(
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
/* @__PURE__ */
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2000
|
+
const wordCell = /* @__PURE__ */ jsxs2(Box4, { children: [
|
|
2001
|
+
/* @__PURE__ */ jsx9(Text3, { color: UNTYPED, children: "[" }),
|
|
2002
|
+
target.map((ch, i) => {
|
|
2003
|
+
const isTyped = i < typed.length;
|
|
2004
|
+
const display = props.hideTarget && !isTyped ? "_" : isTyped ? typed[i] : ch;
|
|
2005
|
+
const color = isTyped ? TYPED : UNTYPED;
|
|
2006
|
+
return /* @__PURE__ */ jsx9(Text3, { color, inverse: props.error && isTyped && i === typed.length - 1, children: display }, i);
|
|
2007
|
+
}),
|
|
2008
|
+
/* @__PURE__ */ jsx9(Text3, { color: UNTYPED, children: "]" })
|
|
2009
|
+
] });
|
|
2010
|
+
const phoneticTransCell = /* @__PURE__ */ jsxs2(Box4, { children: [
|
|
2011
|
+
props.phonetic && /* @__PURE__ */ jsx9(Text3, { color: DIM, children: props.phonetic }),
|
|
2012
|
+
props.phonetic && props.translation.length > 0 && /* @__PURE__ */ jsx9(Text3, { color: DIM, children: " \xB7 " }),
|
|
2013
|
+
props.translation.length > 0 && /* @__PURE__ */ jsx9(Text3, { color: DIM, children: props.translation.slice(0, 1).join("") })
|
|
2014
|
+
] });
|
|
2015
|
+
const info = props.info;
|
|
2016
|
+
const accFmt = Number.isInteger(info.accPct) ? `${info.accPct}` : info.accPct.toFixed(1);
|
|
2017
|
+
const right1 = info.visible ? /* @__PURE__ */ jsx9(Text3, { color: DIM, children: `${info.dictName} \xB7 ${info.chapterLabel}` }) : /* @__PURE__ */ jsxs2(Text3, { color: DIM, children: [
|
|
2018
|
+
"Ctrl+I ",
|
|
2019
|
+
t.stealth.infoChipLabel
|
|
2020
|
+
] });
|
|
2021
|
+
const right2 = info.visible ? /* @__PURE__ */ jsx9(Text3, { color: DIM, children: `${info.completed}/${info.total} \xB7 ${info.wpm}wpm \xB7 ${accFmt}%` }) : /* @__PURE__ */ jsxs2(Text3, { color: DIM, children: [
|
|
2022
|
+
"Esc ",
|
|
2023
|
+
t.common.back
|
|
2024
|
+
] });
|
|
2025
|
+
const right3 = info.visible ? /* @__PURE__ */ jsx9(Text3, { color: DIM, children: fmtTime(info.elapsedMs) }) : /* @__PURE__ */ jsx9(Text3, { children: " " });
|
|
2026
|
+
return /* @__PURE__ */ jsxs2(Box4, { flexDirection: "column", children: [
|
|
2027
|
+
/* @__PURE__ */ jsx9(Row, { left: wordCell, right: right1 }),
|
|
2028
|
+
/* @__PURE__ */ jsx9(Row, { left: phoneticTransCell, right: right2 }),
|
|
2029
|
+
/* @__PURE__ */ jsx9(Row, { left: /* @__PURE__ */ jsx9(Text3, { children: " " }), right: right3 })
|
|
2018
2030
|
] });
|
|
2019
2031
|
}
|
|
2020
2032
|
function StealthPaused() {
|
|
2021
2033
|
const t = useStrings();
|
|
2022
|
-
return /* @__PURE__ */
|
|
2023
|
-
/* @__PURE__ */ jsx9(
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2034
|
+
return /* @__PURE__ */ jsxs2(Box4, { flexDirection: "column", children: [
|
|
2035
|
+
/* @__PURE__ */ jsx9(
|
|
2036
|
+
Row,
|
|
2037
|
+
{
|
|
2038
|
+
left: /* @__PURE__ */ jsx9(Text3, { color: UNTYPED, children: t.stealth.paused }),
|
|
2039
|
+
right: /* @__PURE__ */ jsx9(Text3, { color: DIM, children: t.stealth.pausedHintRight })
|
|
2040
|
+
}
|
|
2041
|
+
),
|
|
2042
|
+
/* @__PURE__ */ jsx9(Row, { left: /* @__PURE__ */ jsx9(Text3, { children: " " }), right: /* @__PURE__ */ jsxs2(Text3, { color: DIM, children: [
|
|
2043
|
+
"Esc ",
|
|
2044
|
+
t.common.back
|
|
2045
|
+
] }) }),
|
|
2046
|
+
/* @__PURE__ */ jsx9(Row, { left: /* @__PURE__ */ jsx9(Text3, { children: " " }), right: /* @__PURE__ */ jsx9(Text3, { children: " " }) })
|
|
2027
2047
|
] });
|
|
2028
2048
|
}
|
|
2029
2049
|
function StealthSummary(props) {
|
|
2030
2050
|
const t = useStrings();
|
|
2031
|
-
const
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
/* @__PURE__ */ jsx9(
|
|
2035
|
-
|
|
2036
|
-
|
|
2051
|
+
const accFmt = Number.isInteger(props.accPct) ? `${props.accPct}` : props.accPct.toFixed(1);
|
|
2052
|
+
const line = `${t.stealth.chapterDone} \xB7 ${props.wordCount}w \xB7 ${props.wpm}wpm \xB7 ${accFmt}% \xB7 ${fmtTime(props.durationMs)}`;
|
|
2053
|
+
return /* @__PURE__ */ jsxs2(Box4, { flexDirection: "column", children: [
|
|
2054
|
+
/* @__PURE__ */ jsx9(
|
|
2055
|
+
Row,
|
|
2056
|
+
{
|
|
2057
|
+
left: /* @__PURE__ */ jsx9(Text3, { color: UNTYPED, children: line }),
|
|
2058
|
+
right: /* @__PURE__ */ jsx9(Text3, { color: DIM, children: t.stealth.nextHintRight })
|
|
2059
|
+
}
|
|
2060
|
+
),
|
|
2061
|
+
/* @__PURE__ */ jsx9(Row, { left: /* @__PURE__ */ jsx9(Text3, { children: " " }), right: /* @__PURE__ */ jsxs2(Text3, { color: DIM, children: [
|
|
2062
|
+
"Esc ",
|
|
2063
|
+
t.common.back
|
|
2064
|
+
] }) }),
|
|
2065
|
+
/* @__PURE__ */ jsx9(Row, { left: /* @__PURE__ */ jsx9(Text3, { children: " " }), right: /* @__PURE__ */ jsx9(Text3, { children: " " }) })
|
|
2037
2066
|
] });
|
|
2038
2067
|
}
|
|
2039
2068
|
|
|
2040
2069
|
// src/ui/screens/PracticeScreen.tsx
|
|
2041
|
-
import { jsx as jsx10, jsxs as
|
|
2070
|
+
import { jsx as jsx10, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2042
2071
|
function PracticeScreen({ params }) {
|
|
2043
2072
|
const { dictId, chapterIndex, mode } = params;
|
|
2044
2073
|
const { cfg } = useAppState();
|
|
@@ -2129,6 +2158,12 @@ function PracticeRunner({
|
|
|
2129
2158
|
const lastEffectRef = useRef3(null);
|
|
2130
2159
|
const lastIndexRef = useRef3(-1);
|
|
2131
2160
|
const [infoVisible, setInfoVisible] = useState8(false);
|
|
2161
|
+
const [infoShownAt, setInfoShownAt] = useState8(null);
|
|
2162
|
+
useEffect6(() => {
|
|
2163
|
+
if (infoShownAt === null) return;
|
|
2164
|
+
const id = setTimeout(() => setInfoVisible(false), 2e3);
|
|
2165
|
+
return () => clearTimeout(id);
|
|
2166
|
+
}, [infoShownAt]);
|
|
2132
2167
|
const { session, lastEffect, tick } = useWordLoop({
|
|
2133
2168
|
playlist: loaded.playlist,
|
|
2134
2169
|
enabled: phase === "typing",
|
|
@@ -2173,7 +2208,8 @@ function PracticeRunner({
|
|
|
2173
2208
|
useInput3(
|
|
2174
2209
|
(input, key) => {
|
|
2175
2210
|
if (key.ctrl && input === "i") {
|
|
2176
|
-
setInfoVisible(
|
|
2211
|
+
setInfoVisible(true);
|
|
2212
|
+
setInfoShownAt(Date.now());
|
|
2177
2213
|
return;
|
|
2178
2214
|
}
|
|
2179
2215
|
},
|
|
@@ -2274,7 +2310,8 @@ function PracticeRunner({
|
|
|
2274
2310
|
completed,
|
|
2275
2311
|
total: loaded.playlist.length,
|
|
2276
2312
|
wpm,
|
|
2277
|
-
accPct
|
|
2313
|
+
accPct,
|
|
2314
|
+
elapsedMs
|
|
2278
2315
|
}
|
|
2279
2316
|
}
|
|
2280
2317
|
);
|
|
@@ -2342,7 +2379,7 @@ function fmtTime2(ms) {
|
|
|
2342
2379
|
function TypingLayout(props) {
|
|
2343
2380
|
const t = useStrings();
|
|
2344
2381
|
const progressFrac = props.total === 0 ? 0 : props.completed / props.total;
|
|
2345
|
-
return /* @__PURE__ */
|
|
2382
|
+
return /* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", paddingX: 2, paddingY: 1, width: "100%", height: "100%", children: [
|
|
2346
2383
|
/* @__PURE__ */ jsx10(
|
|
2347
2384
|
StatusBar,
|
|
2348
2385
|
{
|
|
@@ -2356,7 +2393,7 @@ function TypingLayout(props) {
|
|
|
2356
2393
|
elapsedMs: props.elapsedMs
|
|
2357
2394
|
}
|
|
2358
2395
|
),
|
|
2359
|
-
/* @__PURE__ */
|
|
2396
|
+
/* @__PURE__ */ jsxs3(Box5, { flexGrow: 1, flexDirection: "column", alignItems: "center", justifyContent: "center", children: [
|
|
2360
2397
|
/* @__PURE__ */ jsx10(
|
|
2361
2398
|
BigWord,
|
|
2362
2399
|
{
|
|
@@ -2366,12 +2403,12 @@ function TypingLayout(props) {
|
|
|
2366
2403
|
hideTarget: props.hideTarget
|
|
2367
2404
|
}
|
|
2368
2405
|
),
|
|
2369
|
-
props.phonetic && /* @__PURE__ */ jsx10(Box5, { marginTop:
|
|
2370
|
-
props.translation.length > 0 && /* @__PURE__ */ jsx10(Box5, { marginTop:
|
|
2406
|
+
props.phonetic && /* @__PURE__ */ jsx10(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text4, { italic: true, color: PALETTE.muted, children: props.phonetic }) }),
|
|
2407
|
+
props.translation.length > 0 && /* @__PURE__ */ jsx10(Box5, { marginTop: 1, flexDirection: "column", alignItems: "center", children: props.translation.slice(0, 2).map((tr, i) => /* @__PURE__ */ jsx10(Text4, { color: PALETTE.primary, children: tr }, i)) })
|
|
2371
2408
|
] }),
|
|
2372
|
-
/* @__PURE__ */
|
|
2409
|
+
/* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", children: [
|
|
2373
2410
|
/* @__PURE__ */ jsx10(ProgressBar, { frac: progressFrac }),
|
|
2374
|
-
/* @__PURE__ */ jsx10(Box5, { justifyContent: "center", marginTop: 1, children: /* @__PURE__ */
|
|
2411
|
+
/* @__PURE__ */ jsx10(Box5, { justifyContent: "center", marginTop: 1, children: /* @__PURE__ */ jsxs3(Text4, { color: PALETTE.muted, children: [
|
|
2375
2412
|
props.completed,
|
|
2376
2413
|
"/",
|
|
2377
2414
|
props.total,
|
|
@@ -2397,7 +2434,7 @@ function StatusBar(props) {
|
|
|
2397
2434
|
const name = truncateName(props.dictName, 20);
|
|
2398
2435
|
const left = props.mode === "review" ? `${name} \xB7 ${t.practice.reviewLabel} \xB7 ${accentName}` : `${name} \xB7 ${t.practice.chapterLabel(props.chapterIndex + 1, props.totalChapters)} \xB7 ${modeName} \xB7 ${accentName}`;
|
|
2399
2436
|
const right = `${props.completed}/${props.total} \xB7 ${fmtTime2(props.elapsedMs)}`;
|
|
2400
|
-
return /* @__PURE__ */
|
|
2437
|
+
return /* @__PURE__ */ jsxs3(Box5, { children: [
|
|
2401
2438
|
/* @__PURE__ */ jsx10(Text4, { color: PALETTE.muted, children: left }),
|
|
2402
2439
|
/* @__PURE__ */ jsx10(Box5, { flexGrow: 1 }),
|
|
2403
2440
|
/* @__PURE__ */ jsx10(Text4, { color: PALETTE.muted, children: right })
|
|
@@ -2408,7 +2445,7 @@ function ProgressBar({ frac }) {
|
|
|
2408
2445
|
const width = Math.max(20, Math.min(72, cols - 16));
|
|
2409
2446
|
const filled = Math.round(width * Math.max(0, Math.min(1, frac)));
|
|
2410
2447
|
const empty = width - filled;
|
|
2411
|
-
return /* @__PURE__ */
|
|
2448
|
+
return /* @__PURE__ */ jsxs3(Box5, { justifyContent: "center", children: [
|
|
2412
2449
|
/* @__PURE__ */ jsx10(Text4, { color: PALETTE.accent, children: "\u2501".repeat(filled) }),
|
|
2413
2450
|
/* @__PURE__ */ jsx10(Text4, { color: PALETTE.muted, children: "\u2500".repeat(empty) })
|
|
2414
2451
|
] });
|
|
@@ -2417,7 +2454,7 @@ function PausedView(props) {
|
|
|
2417
2454
|
const t = useStrings();
|
|
2418
2455
|
const frac = props.total === 0 ? 0 : props.completed / props.total;
|
|
2419
2456
|
const subtitle = props.mode === "review" ? `${truncateName(props.dictName, 20)} \xB7 ${t.practice.reviewLabel}` : `${truncateName(props.dictName, 20)} \xB7 ${t.practice.pause.chapter(props.chapterIndex + 1, props.totalChapters)}`;
|
|
2420
|
-
return /* @__PURE__ */
|
|
2457
|
+
return /* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", children: [
|
|
2421
2458
|
/* @__PURE__ */ jsx10(Text4, { bold: true, color: PALETTE.warning, children: t.practice.pause.title }),
|
|
2422
2459
|
/* @__PURE__ */ jsx10(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text4, { color: PALETTE.muted, children: subtitle }) }),
|
|
2423
2460
|
/* @__PURE__ */ jsx10(Box5, { marginTop: 2, children: /* @__PURE__ */ jsx10(ProgressBar, { frac }) }),
|
|
@@ -2427,9 +2464,9 @@ function PausedView(props) {
|
|
|
2427
2464
|
}
|
|
2428
2465
|
function ErrorView({ msg }) {
|
|
2429
2466
|
const t = useStrings();
|
|
2430
|
-
return /* @__PURE__ */
|
|
2467
|
+
return /* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", children: [
|
|
2431
2468
|
/* @__PURE__ */ jsx10(Text4, { color: PALETTE.error, children: msg }),
|
|
2432
|
-
/* @__PURE__ */ jsx10(Box5, { marginTop: 2, children: /* @__PURE__ */
|
|
2469
|
+
/* @__PURE__ */ jsx10(Box5, { marginTop: 2, children: /* @__PURE__ */ jsxs3(Text4, { color: PALETTE.muted, children: [
|
|
2433
2470
|
"Esc ",
|
|
2434
2471
|
t.common.back
|
|
2435
2472
|
] }) }),
|
|
@@ -2459,10 +2496,10 @@ function SummaryView(props) {
|
|
|
2459
2496
|
const subtitle = props.mode === "review" ? `${name} \xB7 ${t.practice.reviewLabel}` : `${name} \xB7 ${t.practice.chapterLabel(props.chapterIndex + 1, props.totalChapters)} \xB7 ${modeName}`;
|
|
2460
2497
|
const nextLabel = props.mode === "loop" ? t.practice.summary.loopAgain : props.mode === "review" || props.chapterIndex + 1 >= props.totalChapters ? t.practice.summary.backMenu : t.practice.summary.nextChapter;
|
|
2461
2498
|
const footer = `Enter ${nextLabel} \xB7 m ${t.practice.summary.reviewMistakes} \xB7 Esc ${t.practice.summary.backMenu}`;
|
|
2462
|
-
return /* @__PURE__ */
|
|
2499
|
+
return /* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", alignItems: "center", justifyContent: "center", paddingY: 1, width: "100%", height: "100%", children: [
|
|
2463
2500
|
/* @__PURE__ */ jsx10(Text4, { bold: true, color: PALETTE.success, children: t.practice.chapterComplete }),
|
|
2464
2501
|
/* @__PURE__ */ jsx10(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text4, { color: PALETTE.muted, children: subtitle }) }),
|
|
2465
|
-
/* @__PURE__ */
|
|
2502
|
+
/* @__PURE__ */ jsxs3(Box5, { marginTop: 3, flexDirection: "row", justifyContent: "center", children: [
|
|
2466
2503
|
/* @__PURE__ */ jsx10(StatCard, { label: t.practice.statCards.words, value: String(summary.wordCount), color: PALETTE.text }),
|
|
2467
2504
|
/* @__PURE__ */ jsx10(
|
|
2468
2505
|
StatCard,
|
|
@@ -2481,7 +2518,7 @@ function SummaryView(props) {
|
|
|
2481
2518
|
] });
|
|
2482
2519
|
}
|
|
2483
2520
|
function StatCard({ label, value, color }) {
|
|
2484
|
-
return /* @__PURE__ */
|
|
2521
|
+
return /* @__PURE__ */ jsxs3(Box5, { flexDirection: "column", alignItems: "center", marginX: 3, children: [
|
|
2485
2522
|
/* @__PURE__ */ jsx10(Text4, { bold: true, color, children: value }),
|
|
2486
2523
|
/* @__PURE__ */ jsx10(Text4, { color: PALETTE.muted, children: label })
|
|
2487
2524
|
] });
|
|
@@ -2494,7 +2531,7 @@ import { Box as Box7, Text as Text6, useInput as useInput5, useStdout as useStdo
|
|
|
2494
2531
|
// src/ui/components/ActionPanel.tsx
|
|
2495
2532
|
import { useState as useState9 } from "react";
|
|
2496
2533
|
import { Box as Box6, Text as Text5, useInput as useInput4 } from "ink";
|
|
2497
|
-
import { jsx as jsx11, jsxs as
|
|
2534
|
+
import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2498
2535
|
function ActionPanel({ title, items, onClose }) {
|
|
2499
2536
|
const enabledIndices = items.map((it, i) => it.disabled ? -1 : i).filter((i) => i >= 0);
|
|
2500
2537
|
const initial = enabledIndices[0] ?? 0;
|
|
@@ -2534,7 +2571,7 @@ function ActionPanel({ title, items, onClose }) {
|
|
|
2534
2571
|
});
|
|
2535
2572
|
const maxLabel = Math.max(...items.map((it) => it.label.length));
|
|
2536
2573
|
const width = Math.max(maxLabel + 8, title.length + 4, 24);
|
|
2537
|
-
return /* @__PURE__ */
|
|
2574
|
+
return /* @__PURE__ */ jsxs4(
|
|
2538
2575
|
Box6,
|
|
2539
2576
|
{
|
|
2540
2577
|
flexDirection: "column",
|
|
@@ -2548,7 +2585,7 @@ function ActionPanel({ title, items, onClose }) {
|
|
|
2548
2585
|
items.map((it, i) => {
|
|
2549
2586
|
const active = i === selected;
|
|
2550
2587
|
const color = it.disabled ? PALETTE.muted : active ? PALETTE.text : PALETTE.muted;
|
|
2551
|
-
return /* @__PURE__ */
|
|
2588
|
+
return /* @__PURE__ */ jsxs4(Box6, { children: [
|
|
2552
2589
|
/* @__PURE__ */ jsx11(Text5, { color: active ? PALETTE.accent : PALETTE.muted, children: active ? "\u258C " : " " }),
|
|
2553
2590
|
/* @__PURE__ */ jsx11(Text5, { bold: active, color, children: it.label })
|
|
2554
2591
|
] }, i);
|
|
@@ -2560,7 +2597,7 @@ function ActionPanel({ title, items, onClose }) {
|
|
|
2560
2597
|
}
|
|
2561
2598
|
|
|
2562
2599
|
// src/ui/screens/DictBrowser.tsx
|
|
2563
|
-
import { Fragment, jsx as jsx12, jsxs as
|
|
2600
|
+
import { Fragment, jsx as jsx12, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2564
2601
|
function DictBrowser({ params }) {
|
|
2565
2602
|
const nav = useNav();
|
|
2566
2603
|
const { cfg, setCfg } = useAppState();
|
|
@@ -2734,22 +2771,22 @@ function DictBrowser({ params }) {
|
|
|
2734
2771
|
}
|
|
2735
2772
|
) });
|
|
2736
2773
|
}
|
|
2737
|
-
return /* @__PURE__ */
|
|
2738
|
-
/* @__PURE__ */
|
|
2774
|
+
return /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", paddingX: 2, paddingY: 1, width: "100%", height: "100%", children: [
|
|
2775
|
+
/* @__PURE__ */ jsxs5(Box7, { children: [
|
|
2739
2776
|
/* @__PURE__ */ jsx12(Text6, { bold: true, color: PALETTE.accent, children: t.dict.title }),
|
|
2740
2777
|
/* @__PURE__ */ jsx12(Box7, { flexGrow: 1 }),
|
|
2741
2778
|
/* @__PURE__ */ jsx12(Text6, { color: PALETTE.muted, children: filter ? `${t.dict.filterPlaceholder}: ${filter}_` : `${t.dict.filterPlaceholder}_` }),
|
|
2742
|
-
/* @__PURE__ */
|
|
2779
|
+
/* @__PURE__ */ jsxs5(Text6, { color: PALETTE.muted, children: [
|
|
2743
2780
|
" ",
|
|
2744
2781
|
t.dict.entries(filtered.length)
|
|
2745
2782
|
] })
|
|
2746
2783
|
] }),
|
|
2747
|
-
/* @__PURE__ */
|
|
2784
|
+
/* @__PURE__ */ jsxs5(Box7, { marginTop: 1, flexGrow: 1, children: [
|
|
2748
2785
|
/* @__PURE__ */ jsx12(Box7, { flexDirection: "column", width: "75%", paddingRight: 1, children: filtered.slice(start2, end).map((row, vi) => {
|
|
2749
2786
|
const i = start2 + vi;
|
|
2750
2787
|
const active = i === safeSelected;
|
|
2751
2788
|
const isDefault = cfg.defaultDict === row.entry.id;
|
|
2752
|
-
return /* @__PURE__ */
|
|
2789
|
+
return /* @__PURE__ */ jsxs5(Box7, { children: [
|
|
2753
2790
|
/* @__PURE__ */ jsx12(Box7, { width: 2, children: /* @__PURE__ */ jsx12(Text6, { color: active ? PALETTE.accent : PALETTE.muted, children: active ? "\u258C " : " " }) }),
|
|
2754
2791
|
/* @__PURE__ */ jsx12(Box7, { width: 2, children: /* @__PURE__ */ jsx12(Text6, { color: row.local ? PALETTE.accent : PALETTE.muted, children: row.local ? "\u25CF" : "\u25CB" }) }),
|
|
2755
2792
|
/* @__PURE__ */ jsx12(Box7, { width: 2, children: /* @__PURE__ */ jsx12(Text6, { color: isDefault ? PALETTE.success : PALETTE.muted, children: isDefault ? "\u2605" : " " }) }),
|
|
@@ -2757,10 +2794,10 @@ function DictBrowser({ params }) {
|
|
|
2757
2794
|
/* @__PURE__ */ jsx12(Box7, { width: 6, children: /* @__PURE__ */ jsx12(Text6, { color: PALETTE.muted, children: String(row.entry.length).padStart(5) }) })
|
|
2758
2795
|
] }, row.entry.id);
|
|
2759
2796
|
}) }),
|
|
2760
|
-
/* @__PURE__ */ jsx12(Box7, { flexDirection: "column", width: "25%", paddingLeft: 1, children: current && /* @__PURE__ */
|
|
2797
|
+
/* @__PURE__ */ jsx12(Box7, { flexDirection: "column", width: "25%", paddingLeft: 1, children: current && /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
2761
2798
|
/* @__PURE__ */ jsx12(Text6, { bold: true, color: PALETTE.text, wrap: "wrap", children: current.entry.name }),
|
|
2762
2799
|
/* @__PURE__ */ jsx12(Text6, { color: PALETTE.muted, children: current.entry.id }),
|
|
2763
|
-
/* @__PURE__ */ jsx12(Box7, { marginTop: 1, children: /* @__PURE__ */
|
|
2800
|
+
/* @__PURE__ */ jsx12(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: PALETTE.muted, wrap: "wrap", children: [
|
|
2764
2801
|
current.entry.language,
|
|
2765
2802
|
" \xB7 ",
|
|
2766
2803
|
current.entry.category
|
|
@@ -2772,10 +2809,10 @@ function DictBrowser({ params }) {
|
|
|
2772
2809
|
cfg.defaultDict === current.entry.id && /* @__PURE__ */ jsx12(Box7, { children: /* @__PURE__ */ jsx12(Text6, { color: PALETTE.success, children: t.dict.defaultMark }) })
|
|
2773
2810
|
] }) })
|
|
2774
2811
|
] }),
|
|
2775
|
-
pending && /* @__PURE__ */
|
|
2812
|
+
pending && /* @__PURE__ */ jsxs5(Box7, { marginTop: 1, children: [
|
|
2776
2813
|
pending.kind === "pulling" && /* @__PURE__ */ jsx12(Text6, { color: PALETTE.warning, children: t.dict.pulling(pending.id) }),
|
|
2777
2814
|
pending.kind === "removing" && /* @__PURE__ */ jsx12(Text6, { color: PALETTE.warning, children: t.dict.removing(pending.id) }),
|
|
2778
|
-
pending.kind === "refreshing" && /* @__PURE__ */
|
|
2815
|
+
pending.kind === "refreshing" && /* @__PURE__ */ jsxs5(Text6, { color: PALETTE.warning, children: [
|
|
2779
2816
|
t.dict.command.refreshList,
|
|
2780
2817
|
"\u2026"
|
|
2781
2818
|
] }),
|
|
@@ -2788,7 +2825,7 @@ function DictBrowser({ params }) {
|
|
|
2788
2825
|
// src/ui/screens/ConfigEditor.tsx
|
|
2789
2826
|
import { useState as useState11 } from "react";
|
|
2790
2827
|
import { Box as Box8, Text as Text7, useInput as useInput6 } from "ink";
|
|
2791
|
-
import { jsx as jsx13, jsxs as
|
|
2828
|
+
import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2792
2829
|
var FIELDS = [
|
|
2793
2830
|
{ kind: "dictRef", path: "defaultDict", labelKey: "defaultDict" },
|
|
2794
2831
|
{ kind: "enum", path: "defaultMode", labelKey: "defaultMode", options: ["order", "dictation", "review", "random", "loop"] },
|
|
@@ -2901,7 +2938,7 @@ function ConfigEditor() {
|
|
|
2901
2938
|
}
|
|
2902
2939
|
});
|
|
2903
2940
|
const labelW = Math.max(...FIELDS.map((f) => visibleWidth2(t.config.fields[f.labelKey]))) + 4;
|
|
2904
|
-
return /* @__PURE__ */
|
|
2941
|
+
return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", paddingX: 2, paddingY: 1, width: "100%", height: "100%", children: [
|
|
2905
2942
|
/* @__PURE__ */ jsx13(Text7, { bold: true, color: PALETTE.accent, children: t.config.title }),
|
|
2906
2943
|
/* @__PURE__ */ jsx13(Box8, { marginTop: 1, flexDirection: "column", flexGrow: 1, children: FIELDS.map((f, i) => {
|
|
2907
2944
|
const active = i === selected;
|
|
@@ -2915,16 +2952,16 @@ function ConfigEditor() {
|
|
|
2915
2952
|
);
|
|
2916
2953
|
const label = t.config.fields[f.labelKey];
|
|
2917
2954
|
const pad = " ".repeat(Math.max(0, labelW - visibleWidth2(label)));
|
|
2918
|
-
return /* @__PURE__ */
|
|
2955
|
+
return /* @__PURE__ */ jsxs6(Box8, { children: [
|
|
2919
2956
|
/* @__PURE__ */ jsx13(Text7, { color: active ? PALETTE.accent : PALETTE.muted, children: active ? "\u258C " : " " }),
|
|
2920
|
-
/* @__PURE__ */
|
|
2957
|
+
/* @__PURE__ */ jsxs6(Text7, { bold: active, color: active ? PALETTE.text : PALETTE.muted, children: [
|
|
2921
2958
|
label,
|
|
2922
2959
|
pad
|
|
2923
2960
|
] }),
|
|
2924
2961
|
/* @__PURE__ */ jsx13(Text7, { color: active ? PALETTE.accent : PALETTE.muted, children: display })
|
|
2925
2962
|
] }, f.path);
|
|
2926
2963
|
}) }),
|
|
2927
|
-
error && /* @__PURE__ */ jsx13(Box8, { marginTop: 1, children: /* @__PURE__ */
|
|
2964
|
+
error && /* @__PURE__ */ jsx13(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: PALETTE.error, children: [
|
|
2928
2965
|
"! ",
|
|
2929
2966
|
error
|
|
2930
2967
|
] }) }),
|
|
@@ -2958,8 +2995,82 @@ function hintFor(field, editing, t) {
|
|
|
2958
2995
|
|
|
2959
2996
|
// src/ui/screens/StatsViewer.tsx
|
|
2960
2997
|
import { useEffect as useEffect8, useState as useState12 } from "react";
|
|
2961
|
-
import { Box as
|
|
2962
|
-
|
|
2998
|
+
import { Box as Box10, Text as Text9, useInput as useInput7 } from "ink";
|
|
2999
|
+
|
|
3000
|
+
// src/ui/components/Heatmap.tsx
|
|
3001
|
+
import { Box as Box9, Text as Text8 } from "ink";
|
|
3002
|
+
|
|
3003
|
+
// src/util/heatmap.ts
|
|
3004
|
+
function bucketDays(values, days, todayWeekday) {
|
|
3005
|
+
if (days <= 0 || values.length !== days) {
|
|
3006
|
+
return Array.from({ length: 7 }, () => []);
|
|
3007
|
+
}
|
|
3008
|
+
const cols = Math.ceil((days - todayWeekday - 1) / 7) + 1;
|
|
3009
|
+
const grid = Array.from({ length: 7 }, () => Array(cols).fill(null));
|
|
3010
|
+
let col = cols - 1;
|
|
3011
|
+
let row = todayWeekday;
|
|
3012
|
+
for (let i = days - 1; i >= 0; i--) {
|
|
3013
|
+
grid[row][col] = values[i];
|
|
3014
|
+
if (row === 0) {
|
|
3015
|
+
row = 6;
|
|
3016
|
+
col -= 1;
|
|
3017
|
+
} else {
|
|
3018
|
+
row -= 1;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
return grid;
|
|
3022
|
+
}
|
|
3023
|
+
function intensity(value, max) {
|
|
3024
|
+
if (max <= 0 || value <= 0) return 0;
|
|
3025
|
+
const frac = value / max;
|
|
3026
|
+
if (frac <= 0.25) return 1;
|
|
3027
|
+
if (frac <= 0.5) return 2;
|
|
3028
|
+
if (frac <= 0.75) return 3;
|
|
3029
|
+
return 4;
|
|
3030
|
+
}
|
|
3031
|
+
function todayWeekdayMon0(now = /* @__PURE__ */ new Date()) {
|
|
3032
|
+
const d = now.getUTCDay();
|
|
3033
|
+
return d === 0 ? 6 : d - 1;
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
// src/ui/components/Heatmap.tsx
|
|
3037
|
+
import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3038
|
+
var COLORS = ["#1b1f23", "#0e4429", "#006d32", "#26a641", "#39d353"];
|
|
3039
|
+
var ROW_LABELS = ["M", "T", "W", "T", "F", "S", "S"];
|
|
3040
|
+
function Heatmap({ label, values, days, suffix = "" }) {
|
|
3041
|
+
const todayDow = todayWeekdayMon0();
|
|
3042
|
+
const grid = bucketDays(values, days, todayDow);
|
|
3043
|
+
const max = values.length === 0 ? 0 : Math.max(...values);
|
|
3044
|
+
const cols = grid[0]?.length ?? 0;
|
|
3045
|
+
const maxLabel = max > 0 ? `max ${Math.round(max)}${suffix}` : "";
|
|
3046
|
+
return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", children: [
|
|
3047
|
+
/* @__PURE__ */ jsxs7(Box9, { children: [
|
|
3048
|
+
/* @__PURE__ */ jsx14(Box9, { width: 12, children: /* @__PURE__ */ jsx14(Text8, { color: PALETTE.muted, children: label }) }),
|
|
3049
|
+
/* @__PURE__ */ jsx14(Box9, { flexGrow: 1 }),
|
|
3050
|
+
maxLabel && /* @__PURE__ */ jsx14(Text8, { color: PALETTE.muted, children: maxLabel })
|
|
3051
|
+
] }),
|
|
3052
|
+
grid.map((row, r) => /* @__PURE__ */ jsxs7(Box9, { children: [
|
|
3053
|
+
/* @__PURE__ */ jsx14(Box9, { width: 3, children: /* @__PURE__ */ jsx14(Text8, { color: PALETTE.muted, children: ROW_LABELS[r] }) }),
|
|
3054
|
+
row.map((cell, c) => {
|
|
3055
|
+
if (cell === null) {
|
|
3056
|
+
return /* @__PURE__ */ jsxs7(Text8, { children: [
|
|
3057
|
+
" ",
|
|
3058
|
+
c < cols - 1 ? " " : ""
|
|
3059
|
+
] }, c);
|
|
3060
|
+
}
|
|
3061
|
+
const lvl = intensity(cell, max);
|
|
3062
|
+
const sym = lvl === 0 ? "\xB7" : "\u25A0";
|
|
3063
|
+
return /* @__PURE__ */ jsxs7(Text8, { color: COLORS[lvl], children: [
|
|
3064
|
+
sym,
|
|
3065
|
+
c < cols - 1 ? " " : ""
|
|
3066
|
+
] }, c);
|
|
3067
|
+
})
|
|
3068
|
+
] }, r))
|
|
3069
|
+
] });
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
// src/ui/screens/StatsViewer.tsx
|
|
3073
|
+
import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2963
3074
|
var DAY_WINDOWS = [7, 14, 30, 90];
|
|
2964
3075
|
function StatsViewer() {
|
|
2965
3076
|
const nav = useNav();
|
|
@@ -2983,20 +3094,19 @@ function StatsViewer() {
|
|
|
2983
3094
|
if (key.leftArrow) setWindowIdx((i) => (i - 1 + DAY_WINDOWS.length) % DAY_WINDOWS.length);
|
|
2984
3095
|
});
|
|
2985
3096
|
if (!sessions || !book) {
|
|
2986
|
-
return /* @__PURE__ */
|
|
3097
|
+
return /* @__PURE__ */ jsx15(Box10, { alignItems: "center", justifyContent: "center", width: "100%", height: "100%", children: /* @__PURE__ */ jsx15(Text9, { color: PALETTE.muted, children: t.stats.loading }) });
|
|
2987
3098
|
}
|
|
2988
3099
|
if (sessions.length === 0) {
|
|
2989
|
-
return /* @__PURE__ */ jsxs8(
|
|
2990
|
-
/* @__PURE__ */
|
|
2991
|
-
/* @__PURE__ */
|
|
2992
|
-
/* @__PURE__ */
|
|
3100
|
+
return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", children: [
|
|
3101
|
+
/* @__PURE__ */ jsx15(Text9, { color: PALETTE.muted, children: t.stats.none }),
|
|
3102
|
+
/* @__PURE__ */ jsx15(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text9, { color: PALETTE.muted, children: t.stats.nonePractice }) }),
|
|
3103
|
+
/* @__PURE__ */ jsx15(Box10, { marginTop: 2, children: /* @__PURE__ */ jsxs8(Text9, { color: PALETTE.muted, children: [
|
|
2993
3104
|
"Esc ",
|
|
2994
3105
|
t.common.back
|
|
2995
3106
|
] }) })
|
|
2996
3107
|
] });
|
|
2997
3108
|
}
|
|
2998
3109
|
const days = DAY_WINDOWS[windowIdx];
|
|
2999
|
-
const buckets = dailyBuckets(sessions, days);
|
|
3000
3110
|
const streak = dailyStreak(sessions);
|
|
3001
3111
|
const totalWords = sessions.reduce((a, s) => a + s.wordCount, 0);
|
|
3002
3112
|
const totalErrors = sessions.reduce((a, s) => a + s.errors, 0);
|
|
@@ -3009,59 +3119,37 @@ function StatsViewer() {
|
|
|
3009
3119
|
const overallAcc = totalWords === 0 ? 1 : firstTryWords / totalWords;
|
|
3010
3120
|
const recent = sessions.slice(-5).reverse();
|
|
3011
3121
|
const top = topN(book, 8);
|
|
3012
|
-
const wpms =
|
|
3013
|
-
const accs =
|
|
3014
|
-
const ses =
|
|
3015
|
-
return /* @__PURE__ */ jsxs8(
|
|
3016
|
-
/* @__PURE__ */
|
|
3017
|
-
/* @__PURE__ */ jsxs8(
|
|
3018
|
-
/* @__PURE__ */
|
|
3019
|
-
/* @__PURE__ */ jsxs8(
|
|
3020
|
-
/* @__PURE__ */
|
|
3021
|
-
/* @__PURE__ */
|
|
3022
|
-
/* @__PURE__ */
|
|
3023
|
-
/* @__PURE__ */
|
|
3024
|
-
/* @__PURE__ */
|
|
3025
|
-
/* @__PURE__ */
|
|
3122
|
+
const wpms = dailyValues(sessions, days, "wpm");
|
|
3123
|
+
const accs = dailyValues(sessions, days, "accuracy");
|
|
3124
|
+
const ses = dailyValues(sessions, days, "sessions");
|
|
3125
|
+
return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", paddingX: 2, paddingY: 1, width: "100%", height: "100%", children: [
|
|
3126
|
+
/* @__PURE__ */ jsx15(Text9, { bold: true, color: PALETTE.accent, children: t.stats.title }),
|
|
3127
|
+
/* @__PURE__ */ jsxs8(Box10, { marginTop: 1, flexDirection: "column", children: [
|
|
3128
|
+
/* @__PURE__ */ jsx15(Text9, { color: PALETTE.muted, children: t.stats.lifetime }),
|
|
3129
|
+
/* @__PURE__ */ jsxs8(Box10, { marginTop: 1, children: [
|
|
3130
|
+
/* @__PURE__ */ jsx15(Stat, { label: t.stats.sessions, value: String(sessions.length) }),
|
|
3131
|
+
/* @__PURE__ */ jsx15(Stat, { label: t.stats.words, value: String(totalWords) }),
|
|
3132
|
+
/* @__PURE__ */ jsx15(Stat, { label: t.stats.errors, value: String(totalErrors) }),
|
|
3133
|
+
/* @__PURE__ */ jsx15(Stat, { label: t.stats.wpm, value: String(overallWpm), accent: true }),
|
|
3134
|
+
/* @__PURE__ */ jsx15(Stat, { label: t.stats.accuracy, value: `${Math.round(overallAcc * 1e3) / 10}%`, accent: true }),
|
|
3135
|
+
/* @__PURE__ */ jsx15(Stat, { label: t.stats.streak, value: `${streak}d`, accent: true })
|
|
3026
3136
|
] })
|
|
3027
3137
|
] }),
|
|
3028
|
-
/* @__PURE__ */ jsxs8(
|
|
3029
|
-
/* @__PURE__ */
|
|
3030
|
-
/* @__PURE__ */ jsxs8(
|
|
3031
|
-
/* @__PURE__ */
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
label: t.stats.wpm,
|
|
3035
|
-
values: wpms,
|
|
3036
|
-
maxLabel: t.stats.maxLabel
|
|
3037
|
-
}
|
|
3038
|
-
),
|
|
3039
|
-
/* @__PURE__ */ jsx14(
|
|
3040
|
-
SparkRow,
|
|
3041
|
-
{
|
|
3042
|
-
label: t.stats.accuracy,
|
|
3043
|
-
values: accs,
|
|
3044
|
-
maxLabel: t.stats.maxLabel,
|
|
3045
|
-
suffix: "%"
|
|
3046
|
-
}
|
|
3047
|
-
),
|
|
3048
|
-
/* @__PURE__ */ jsx14(
|
|
3049
|
-
SparkRow,
|
|
3050
|
-
{
|
|
3051
|
-
label: t.stats.sessions,
|
|
3052
|
-
values: ses,
|
|
3053
|
-
maxLabel: t.stats.maxLabel
|
|
3054
|
-
}
|
|
3055
|
-
)
|
|
3138
|
+
/* @__PURE__ */ jsxs8(Box10, { marginTop: 2, flexDirection: "column", children: [
|
|
3139
|
+
/* @__PURE__ */ jsx15(Text9, { color: PALETTE.muted, children: t.stats.last(days) }),
|
|
3140
|
+
/* @__PURE__ */ jsxs8(Box10, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: PALETTE.muted, paddingX: 1, paddingY: 0, children: [
|
|
3141
|
+
/* @__PURE__ */ jsx15(Heatmap, { label: t.stats.wpm, values: wpms, days }),
|
|
3142
|
+
/* @__PURE__ */ jsx15(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx15(Heatmap, { label: t.stats.accuracy, values: accs, days, suffix: "%" }) }),
|
|
3143
|
+
/* @__PURE__ */ jsx15(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx15(Heatmap, { label: t.stats.sessions, values: ses, days }) })
|
|
3056
3144
|
] })
|
|
3057
3145
|
] }),
|
|
3058
|
-
/* @__PURE__ */ jsxs8(
|
|
3059
|
-
/* @__PURE__ */
|
|
3060
|
-
recent.map((s, i) => /* @__PURE__ */
|
|
3146
|
+
/* @__PURE__ */ jsxs8(Box10, { marginTop: 2, flexDirection: "column", children: [
|
|
3147
|
+
/* @__PURE__ */ jsx15(Text9, { color: PALETTE.muted, children: t.stats.recent }),
|
|
3148
|
+
recent.map((s, i) => /* @__PURE__ */ jsx15(RecentRow, { session: s, units: t.stats.recentUnits }, i))
|
|
3061
3149
|
] }),
|
|
3062
|
-
top.length > 0 && /* @__PURE__ */ jsxs8(
|
|
3063
|
-
/* @__PURE__ */
|
|
3064
|
-
top.map(([word, entry]) => /* @__PURE__ */
|
|
3150
|
+
top.length > 0 && /* @__PURE__ */ jsxs8(Box10, { marginTop: 2, flexDirection: "column", children: [
|
|
3151
|
+
/* @__PURE__ */ jsx15(Text9, { color: PALETTE.muted, children: t.stats.topMistakes }),
|
|
3152
|
+
top.map(([word, entry]) => /* @__PURE__ */ jsx15(
|
|
3065
3153
|
MistakeRow,
|
|
3066
3154
|
{
|
|
3067
3155
|
word,
|
|
@@ -3072,26 +3160,8 @@ function StatsViewer() {
|
|
|
3072
3160
|
word
|
|
3073
3161
|
))
|
|
3074
3162
|
] }),
|
|
3075
|
-
/* @__PURE__ */
|
|
3076
|
-
/* @__PURE__ */
|
|
3077
|
-
] });
|
|
3078
|
-
}
|
|
3079
|
-
function SparkRow({
|
|
3080
|
-
label,
|
|
3081
|
-
values,
|
|
3082
|
-
maxLabel,
|
|
3083
|
-
suffix = ""
|
|
3084
|
-
}) {
|
|
3085
|
-
const max = values.length === 0 ? 0 : Math.max(...values);
|
|
3086
|
-
const maxDisplay = `${Math.round(max)}${suffix}`;
|
|
3087
|
-
return /* @__PURE__ */ jsxs8(Box9, { children: [
|
|
3088
|
-
/* @__PURE__ */ jsx14(Box9, { width: 10, children: /* @__PURE__ */ jsx14(Text8, { color: PALETTE.muted, children: label }) }),
|
|
3089
|
-
/* @__PURE__ */ jsx14(Box9, { flexGrow: 1, children: /* @__PURE__ */ jsx14(Text8, { color: PALETTE.accent, children: sparkline(values) }) }),
|
|
3090
|
-
/* @__PURE__ */ jsx14(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsxs8(Text8, { color: PALETTE.muted, children: [
|
|
3091
|
-
maxLabel,
|
|
3092
|
-
" ",
|
|
3093
|
-
maxDisplay
|
|
3094
|
-
] }) })
|
|
3163
|
+
/* @__PURE__ */ jsx15(Box10, { flexGrow: 1 }),
|
|
3164
|
+
/* @__PURE__ */ jsx15(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text9, { color: PALETTE.muted, children: t.stats.footer }) })
|
|
3095
3165
|
] });
|
|
3096
3166
|
}
|
|
3097
3167
|
function RecentRow({
|
|
@@ -3100,14 +3170,14 @@ function RecentRow({
|
|
|
3100
3170
|
}) {
|
|
3101
3171
|
const name = useDictName(session.dictId);
|
|
3102
3172
|
const display = truncateName(name, 14);
|
|
3103
|
-
return /* @__PURE__ */ jsxs8(
|
|
3104
|
-
/* @__PURE__ */ jsxs8(
|
|
3173
|
+
return /* @__PURE__ */ jsxs8(Box10, { children: [
|
|
3174
|
+
/* @__PURE__ */ jsxs8(Text9, { color: PALETTE.muted, children: [
|
|
3105
3175
|
" ",
|
|
3106
3176
|
session.ts.replace("T", " ").slice(0, 16),
|
|
3107
3177
|
" "
|
|
3108
3178
|
] }),
|
|
3109
|
-
/* @__PURE__ */
|
|
3110
|
-
/* @__PURE__ */ jsxs8(
|
|
3179
|
+
/* @__PURE__ */ jsx15(Text9, { color: PALETTE.text, children: display.padEnd(14) }),
|
|
3180
|
+
/* @__PURE__ */ jsxs8(Text9, { color: PALETTE.muted, children: [
|
|
3111
3181
|
" ",
|
|
3112
3182
|
"ch",
|
|
3113
3183
|
String(session.chapter + 1).padStart(3),
|
|
@@ -3137,34 +3207,34 @@ function MistakeRow({
|
|
|
3137
3207
|
const firstId = dictIds[0] ?? "";
|
|
3138
3208
|
const firstName = useDictName(firstId);
|
|
3139
3209
|
const suffix = dictIds.length > 1 ? multiSuffix(dictIds.length - 1) : "";
|
|
3140
|
-
return /* @__PURE__ */ jsxs8(
|
|
3141
|
-
/* @__PURE__ */ jsxs8(
|
|
3210
|
+
return /* @__PURE__ */ jsxs8(Box10, { children: [
|
|
3211
|
+
/* @__PURE__ */ jsxs8(Text9, { color: PALETTE.error, children: [
|
|
3142
3212
|
" ",
|
|
3143
3213
|
String(count).padStart(3),
|
|
3144
3214
|
" "
|
|
3145
3215
|
] }),
|
|
3146
|
-
/* @__PURE__ */
|
|
3147
|
-
/* @__PURE__ */ jsxs8(
|
|
3216
|
+
/* @__PURE__ */ jsx15(Text9, { color: PALETTE.text, children: word.padEnd(20) }),
|
|
3217
|
+
/* @__PURE__ */ jsxs8(Text9, { color: PALETTE.muted, children: [
|
|
3148
3218
|
truncateName(firstName, 20),
|
|
3149
3219
|
suffix
|
|
3150
3220
|
] })
|
|
3151
3221
|
] });
|
|
3152
3222
|
}
|
|
3153
3223
|
function Stat({ label, value, accent = false }) {
|
|
3154
|
-
return /* @__PURE__ */ jsxs8(
|
|
3155
|
-
/* @__PURE__ */ jsxs8(
|
|
3224
|
+
return /* @__PURE__ */ jsxs8(Box10, { marginRight: 3, children: [
|
|
3225
|
+
/* @__PURE__ */ jsxs8(Text9, { color: PALETTE.muted, children: [
|
|
3156
3226
|
label,
|
|
3157
3227
|
" "
|
|
3158
3228
|
] }),
|
|
3159
|
-
/* @__PURE__ */
|
|
3229
|
+
/* @__PURE__ */ jsx15(Text9, { bold: true, color: accent ? PALETTE.accent : PALETTE.text, children: value })
|
|
3160
3230
|
] });
|
|
3161
3231
|
}
|
|
3162
3232
|
|
|
3163
3233
|
// src/ui/screens/WordLookup.tsx
|
|
3164
3234
|
import { useEffect as useEffect9, useState as useState13 } from "react";
|
|
3165
|
-
import { Box as
|
|
3235
|
+
import { Box as Box11, Text as Text10, useInput as useInput8 } from "ink";
|
|
3166
3236
|
import { readdir } from "fs/promises";
|
|
3167
|
-
import { Fragment as Fragment2, jsx as
|
|
3237
|
+
import { Fragment as Fragment2, jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3168
3238
|
async function listLocalDictIds() {
|
|
3169
3239
|
try {
|
|
3170
3240
|
const files = await readdir(paths.dictsDir);
|
|
@@ -3221,77 +3291,77 @@ function WordLookup() {
|
|
|
3221
3291
|
}
|
|
3222
3292
|
});
|
|
3223
3293
|
if (loading) {
|
|
3224
|
-
return /* @__PURE__ */
|
|
3294
|
+
return /* @__PURE__ */ jsx16(Box11, { alignItems: "center", justifyContent: "center", width: "100%", height: "100%", children: /* @__PURE__ */ jsx16(Text10, { color: PALETTE.muted, children: t.word.indexing }) });
|
|
3225
3295
|
}
|
|
3226
3296
|
if (allWords.length === 0) {
|
|
3227
|
-
return /* @__PURE__ */ jsxs9(
|
|
3228
|
-
/* @__PURE__ */
|
|
3229
|
-
/* @__PURE__ */
|
|
3230
|
-
/* @__PURE__ */
|
|
3297
|
+
return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", children: [
|
|
3298
|
+
/* @__PURE__ */ jsx16(Text10, { color: PALETTE.muted, children: t.word.none }),
|
|
3299
|
+
/* @__PURE__ */ jsx16(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text10, { color: PALETTE.muted, children: t.word.pullFirst }) }),
|
|
3300
|
+
/* @__PURE__ */ jsx16(Box11, { marginTop: 2, children: /* @__PURE__ */ jsxs9(Text10, { color: PALETTE.muted, children: [
|
|
3231
3301
|
"[Esc] ",
|
|
3232
3302
|
t.common.back
|
|
3233
3303
|
] }) })
|
|
3234
3304
|
] });
|
|
3235
3305
|
}
|
|
3236
3306
|
const current = filtered[selected];
|
|
3237
|
-
return /* @__PURE__ */ jsxs9(
|
|
3238
|
-
/* @__PURE__ */ jsxs9(
|
|
3239
|
-
/* @__PURE__ */
|
|
3240
|
-
/* @__PURE__ */
|
|
3241
|
-
/* @__PURE__ */
|
|
3307
|
+
return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", paddingX: 2, paddingY: 1, width: "100%", height: "100%", children: [
|
|
3308
|
+
/* @__PURE__ */ jsxs9(Box11, { children: [
|
|
3309
|
+
/* @__PURE__ */ jsx16(Text10, { bold: true, color: PALETTE.accent, children: t.word.title }),
|
|
3310
|
+
/* @__PURE__ */ jsx16(Box11, { flexGrow: 1 }),
|
|
3311
|
+
/* @__PURE__ */ jsx16(Text10, { color: PALETTE.muted, children: t.word.countAcross(allWords.length) })
|
|
3242
3312
|
] }),
|
|
3243
|
-
/* @__PURE__ */ jsxs9(
|
|
3244
|
-
/* @__PURE__ */
|
|
3245
|
-
/* @__PURE__ */
|
|
3246
|
-
/* @__PURE__ */
|
|
3313
|
+
/* @__PURE__ */ jsxs9(Box11, { marginTop: 1, children: [
|
|
3314
|
+
/* @__PURE__ */ jsx16(Text10, { color: PALETTE.muted, children: "> " }),
|
|
3315
|
+
/* @__PURE__ */ jsx16(Text10, { color: PALETTE.text, children: query }),
|
|
3316
|
+
/* @__PURE__ */ jsx16(Text10, { color: PALETTE.accent, children: "_" })
|
|
3247
3317
|
] }),
|
|
3248
|
-
/* @__PURE__ */ jsxs9(
|
|
3249
|
-
/* @__PURE__ */ jsxs9(
|
|
3250
|
-
filtered.map((h, i) => /* @__PURE__ */
|
|
3251
|
-
filtered.length === 0 && q && /* @__PURE__ */
|
|
3318
|
+
/* @__PURE__ */ jsxs9(Box11, { marginTop: 1, flexGrow: 1, children: [
|
|
3319
|
+
/* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", width: "40%", children: [
|
|
3320
|
+
filtered.map((h, i) => /* @__PURE__ */ jsx16(HitRow, { hit: h, active: i === selected }, `${h.dictId}-${h.word.name}-${i}`)),
|
|
3321
|
+
filtered.length === 0 && q && /* @__PURE__ */ jsx16(Text10, { color: PALETTE.muted, children: t.word.noMatches(query) })
|
|
3252
3322
|
] }),
|
|
3253
|
-
/* @__PURE__ */
|
|
3323
|
+
/* @__PURE__ */ jsx16(Box11, { flexDirection: "column", width: "60%", paddingLeft: 2, children: current && /* @__PURE__ */ jsx16(Detail, { hit: current, book }) })
|
|
3254
3324
|
] }),
|
|
3255
|
-
/* @__PURE__ */
|
|
3325
|
+
/* @__PURE__ */ jsx16(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text10, { color: PALETTE.muted, children: t.word.footer }) })
|
|
3256
3326
|
] });
|
|
3257
3327
|
}
|
|
3258
3328
|
function HitRow({ hit, active }) {
|
|
3259
3329
|
const name = useDictName(hit.dictId);
|
|
3260
|
-
return /* @__PURE__ */ jsxs9(
|
|
3261
|
-
/* @__PURE__ */
|
|
3262
|
-
/* @__PURE__ */
|
|
3263
|
-
/* @__PURE__ */
|
|
3330
|
+
return /* @__PURE__ */ jsxs9(Box11, { children: [
|
|
3331
|
+
/* @__PURE__ */ jsx16(Text10, { color: active ? PALETTE.accent : PALETTE.muted, children: active ? "\u258C " : " " }),
|
|
3332
|
+
/* @__PURE__ */ jsx16(Text10, { bold: active, color: active ? PALETTE.text : PALETTE.muted, children: hit.word.name.padEnd(20) }),
|
|
3333
|
+
/* @__PURE__ */ jsx16(Text10, { color: PALETTE.muted, children: truncateName(name, 18) })
|
|
3264
3334
|
] });
|
|
3265
3335
|
}
|
|
3266
3336
|
function Detail({ hit, book }) {
|
|
3267
3337
|
const t = useStrings();
|
|
3268
3338
|
const name = useDictName(hit.dictId);
|
|
3269
3339
|
return /* @__PURE__ */ jsxs9(Fragment2, { children: [
|
|
3270
|
-
/* @__PURE__ */
|
|
3271
|
-
/* @__PURE__ */ jsxs9(
|
|
3272
|
-
hit.word.usphone && /* @__PURE__ */ jsxs9(
|
|
3340
|
+
/* @__PURE__ */ jsx16(Text10, { bold: true, color: PALETTE.text, children: hit.word.name }),
|
|
3341
|
+
/* @__PURE__ */ jsxs9(Box11, { marginTop: 1, children: [
|
|
3342
|
+
hit.word.usphone && /* @__PURE__ */ jsxs9(Text10, { color: PALETTE.muted, children: [
|
|
3273
3343
|
"US /",
|
|
3274
3344
|
hit.word.usphone,
|
|
3275
3345
|
"/ "
|
|
3276
3346
|
] }),
|
|
3277
|
-
hit.word.ukphone && /* @__PURE__ */ jsxs9(
|
|
3347
|
+
hit.word.ukphone && /* @__PURE__ */ jsxs9(Text10, { color: PALETTE.muted, children: [
|
|
3278
3348
|
"UK /",
|
|
3279
3349
|
hit.word.ukphone,
|
|
3280
3350
|
"/"
|
|
3281
3351
|
] })
|
|
3282
3352
|
] }),
|
|
3283
|
-
/* @__PURE__ */
|
|
3353
|
+
/* @__PURE__ */ jsx16(Box11, { marginTop: 1, flexDirection: "column", children: (hit.word.trans ?? []).map((tr, i) => /* @__PURE__ */ jsxs9(Text10, { color: PALETTE.primary, children: [
|
|
3284
3354
|
"\xB7 ",
|
|
3285
3355
|
tr
|
|
3286
3356
|
] }, i)) }),
|
|
3287
|
-
/* @__PURE__ */
|
|
3288
|
-
book[hit.word.name] && /* @__PURE__ */
|
|
3357
|
+
/* @__PURE__ */ jsx16(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text10, { color: PALETTE.muted, children: t.word.inDict(truncateName(name, 22)) }) }),
|
|
3358
|
+
book[hit.word.name] && /* @__PURE__ */ jsx16(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text10, { color: PALETTE.error, children: t.word.mistakes(book[hit.word.name].count, book[hit.word.name].lastSeen.slice(0, 10)) }) })
|
|
3289
3359
|
] });
|
|
3290
3360
|
}
|
|
3291
3361
|
|
|
3292
3362
|
// src/ui/screens/HelpScreen.tsx
|
|
3293
|
-
import { Box as
|
|
3294
|
-
import { jsx as
|
|
3363
|
+
import { Box as Box12, Text as Text11, useInput as useInput9 } from "ink";
|
|
3364
|
+
import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3295
3365
|
function HelpScreen() {
|
|
3296
3366
|
const nav = useNav();
|
|
3297
3367
|
const t = useStrings();
|
|
@@ -3314,31 +3384,35 @@ function HelpScreen() {
|
|
|
3314
3384
|
{ title: t.help.sections.stats, keys: [k.cycleWindow, k.backMenu] },
|
|
3315
3385
|
{ title: t.help.sections.word, keys: [k.filter, k.navigate, k.backMenu] }
|
|
3316
3386
|
];
|
|
3317
|
-
return /* @__PURE__ */ jsxs10(
|
|
3318
|
-
/* @__PURE__ */ jsxs10(
|
|
3319
|
-
/* @__PURE__ */
|
|
3320
|
-
/* @__PURE__ */
|
|
3321
|
-
/* @__PURE__ */
|
|
3387
|
+
return /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", paddingX: 2, paddingY: 1, width: "100%", height: "100%", children: [
|
|
3388
|
+
/* @__PURE__ */ jsxs10(Box12, { children: [
|
|
3389
|
+
/* @__PURE__ */ jsx17(Text11, { bold: true, color: PALETTE.accent, children: t.help.title }),
|
|
3390
|
+
/* @__PURE__ */ jsx17(Text11, { color: PALETTE.muted, children: " \xB7 " }),
|
|
3391
|
+
/* @__PURE__ */ jsx17(Text11, { color: PALETTE.muted, children: t.help.subtitle })
|
|
3322
3392
|
] }),
|
|
3323
|
-
/* @__PURE__ */
|
|
3324
|
-
/* @__PURE__ */
|
|
3325
|
-
sec.keys.map((line, i) => /* @__PURE__ */
|
|
3393
|
+
/* @__PURE__ */ jsx17(Box12, { marginTop: 1, flexDirection: "column", flexGrow: 1, children: sections.map((sec) => /* @__PURE__ */ jsxs10(Box12, { flexDirection: "column", marginTop: 1, children: [
|
|
3394
|
+
/* @__PURE__ */ jsx17(Text11, { bold: true, color: PALETTE.text, children: sec.title }),
|
|
3395
|
+
sec.keys.map((line, i) => /* @__PURE__ */ jsx17(Box12, { children: /* @__PURE__ */ jsxs10(Text11, { color: PALETTE.muted, children: [
|
|
3326
3396
|
" \xB7 ",
|
|
3327
3397
|
line
|
|
3328
3398
|
] }) }, i))
|
|
3329
3399
|
] }, sec.title)) }),
|
|
3330
|
-
/* @__PURE__ */
|
|
3400
|
+
/* @__PURE__ */ jsx17(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text11, { color: PALETTE.muted, children: t.help.footer }) })
|
|
3331
3401
|
] });
|
|
3332
3402
|
}
|
|
3333
3403
|
|
|
3334
3404
|
// src/ui/App.tsx
|
|
3335
|
-
import { jsx as
|
|
3336
|
-
function App({
|
|
3337
|
-
|
|
3405
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
3406
|
+
function App({
|
|
3407
|
+
initial,
|
|
3408
|
+
initialCfg,
|
|
3409
|
+
inline = false
|
|
3410
|
+
}) {
|
|
3411
|
+
return /* @__PURE__ */ jsx18(AppStateProvider, { initialCfg, children: /* @__PURE__ */ jsx18(LangBridge, { children: /* @__PURE__ */ jsx18(RegistryProvider, { children: /* @__PURE__ */ jsx18(AudioStatusProvider, { disabled: !initialCfg.sounds.master, children: /* @__PURE__ */ jsx18(NavProvider, { initial, children: inline ? /* @__PURE__ */ jsx18(Router, { inline: true }) : /* @__PURE__ */ jsx18(Fullscreen, { children: /* @__PURE__ */ jsx18(Router, {}) }) }) }) }) }) });
|
|
3338
3412
|
}
|
|
3339
3413
|
function LangBridge({ children }) {
|
|
3340
3414
|
const { cfg } = useAppState();
|
|
3341
|
-
return /* @__PURE__ */
|
|
3415
|
+
return /* @__PURE__ */ jsx18(StringsProvider, { pref: cfg.language, children });
|
|
3342
3416
|
}
|
|
3343
3417
|
function screenKey(frame) {
|
|
3344
3418
|
if (frame.name === "practice") {
|
|
@@ -3347,7 +3421,7 @@ function screenKey(frame) {
|
|
|
3347
3421
|
}
|
|
3348
3422
|
return frame.name;
|
|
3349
3423
|
}
|
|
3350
|
-
function Router() {
|
|
3424
|
+
function Router({ inline = false }) {
|
|
3351
3425
|
const nav = useNav();
|
|
3352
3426
|
const { cfg } = useAppState();
|
|
3353
3427
|
const { exit } = useApp4();
|
|
@@ -3358,24 +3432,24 @@ function Router() {
|
|
|
3358
3432
|
const frame = nav.current;
|
|
3359
3433
|
const key = screenKey(frame);
|
|
3360
3434
|
if (lastKeyRef.current !== key) {
|
|
3361
|
-
if (process.stdout.isTTY) process.stdout.write("\x1B[2J\x1B[H");
|
|
3435
|
+
if (!inline && process.stdout.isTTY) process.stdout.write("\x1B[2J\x1B[H");
|
|
3362
3436
|
lastKeyRef.current = key;
|
|
3363
3437
|
}
|
|
3364
3438
|
switch (frame.name) {
|
|
3365
3439
|
case "main":
|
|
3366
|
-
return /* @__PURE__ */
|
|
3440
|
+
return /* @__PURE__ */ jsx18(MainMenu, { cfg });
|
|
3367
3441
|
case "practice":
|
|
3368
|
-
return /* @__PURE__ */
|
|
3442
|
+
return /* @__PURE__ */ jsx18(PracticeScreen, { params: frame.params });
|
|
3369
3443
|
case "dict":
|
|
3370
|
-
return /* @__PURE__ */
|
|
3444
|
+
return /* @__PURE__ */ jsx18(DictBrowser, { params: frame.params });
|
|
3371
3445
|
case "config":
|
|
3372
|
-
return /* @__PURE__ */
|
|
3446
|
+
return /* @__PURE__ */ jsx18(ConfigEditor, {});
|
|
3373
3447
|
case "stats":
|
|
3374
|
-
return /* @__PURE__ */
|
|
3448
|
+
return /* @__PURE__ */ jsx18(StatsViewer, {});
|
|
3375
3449
|
case "word":
|
|
3376
|
-
return /* @__PURE__ */
|
|
3450
|
+
return /* @__PURE__ */ jsx18(WordLookup, {});
|
|
3377
3451
|
case "help":
|
|
3378
|
-
return /* @__PURE__ */
|
|
3452
|
+
return /* @__PURE__ */ jsx18(HelpScreen, {});
|
|
3379
3453
|
}
|
|
3380
3454
|
}
|
|
3381
3455
|
|
|
@@ -3468,12 +3542,17 @@ async function runPractice(dictIdArg, options) {
|
|
|
3468
3542
|
const { waitUntilExit } = render(
|
|
3469
3543
|
createElement(App, {
|
|
3470
3544
|
initial: { name: "practice", params: { dictId, chapterIndex, mode, stealth } },
|
|
3471
|
-
initialCfg: cfg
|
|
3545
|
+
initialCfg: cfg,
|
|
3546
|
+
inline: stealth
|
|
3472
3547
|
}),
|
|
3473
3548
|
{ patchConsole: false, exitOnCtrlC: false }
|
|
3474
3549
|
);
|
|
3475
3550
|
await waitUntilExit();
|
|
3476
|
-
|
|
3551
|
+
if (stealth && process.stdout.isTTY) {
|
|
3552
|
+
process.stdout.write("\x1B[3F\x1B[0J");
|
|
3553
|
+
} else {
|
|
3554
|
+
ensureMainScreen();
|
|
3555
|
+
}
|
|
3477
3556
|
const { lang, t } = pickStrings(cfg.language);
|
|
3478
3557
|
printSessionReport(report(), t, lang);
|
|
3479
3558
|
}
|