qwerty-cli 0.0.1-alpha.11 → 0.0.1-alpha.12
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/assets/sounds/key-default.wav +0 -0
- package/dist/{ConfigEditor-U4J6FVDN.js → ConfigEditor-HAP5C2Y7.js} +2 -2
- package/dist/{DictBrowser-DZZWZXEA.js → DictBrowser-7CIISVDN.js} +2 -2
- package/dist/{HelpScreen-7U5XF3IU.js → HelpScreen-P4U5O4OP.js} +2 -2
- package/dist/PracticeScreen-LM6M2OYD.js +2 -0
- package/dist/PracticeScreen-LM6M2OYD.js.map +1 -0
- package/dist/{StatsViewer-PDXZ7PJR.js → StatsViewer-3CUMIAV4.js} +2 -2
- package/dist/{WordLookup-EAXT7PGW.js → WordLookup-YIU6LP4H.js} +2 -2
- package/dist/chunk-7RMRK5MO.js +2 -0
- package/dist/chunk-7RMRK5MO.js.map +1 -0
- package/dist/chunk-KBRGNL2D.js +3 -0
- package/dist/chunk-KBRGNL2D.js.map +1 -0
- package/dist/chunk-R6HQWKXU.js +2 -0
- package/dist/chunk-R6HQWKXU.js.map +1 -0
- package/dist/chunk-ZXMHFRCR.js +3 -0
- package/dist/chunk-ZXMHFRCR.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/doctor.impl-5UHLQ4SZ.js +4 -0
- package/dist/doctor.impl-5UHLQ4SZ.js.map +1 -0
- package/dist/menu.impl-PUAAZGHA.js +2 -0
- package/dist/{menu.impl-EQABPGB2.js.map → menu.impl-PUAAZGHA.js.map} +1 -1
- package/dist/practice.impl-CJLWRT5Z.js +2 -0
- package/dist/{practice.impl-NCY3HVPA.js.map → practice.impl-CJLWRT5Z.js.map} +1 -1
- package/package.json +1 -1
- package/dist/PracticeScreen-VLXPLVNW.js +0 -2
- package/dist/PracticeScreen-VLXPLVNW.js.map +0 -1
- package/dist/chunk-LN2WQT5M.js +0 -3
- package/dist/chunk-LN2WQT5M.js.map +0 -1
- package/dist/chunk-SSDQJ6MT.js +0 -2
- package/dist/chunk-SSDQJ6MT.js.map +0 -1
- package/dist/chunk-WE6IV5XB.js +0 -2
- package/dist/chunk-WE6IV5XB.js.map +0 -1
- package/dist/menu.impl-EQABPGB2.js +0 -2
- package/dist/practice.impl-NCY3HVPA.js +0 -2
- /package/dist/{ConfigEditor-U4J6FVDN.js.map → ConfigEditor-HAP5C2Y7.js.map} +0 -0
- /package/dist/{DictBrowser-DZZWZXEA.js.map → DictBrowser-7CIISVDN.js.map} +0 -0
- /package/dist/{HelpScreen-7U5XF3IU.js.map → HelpScreen-P4U5O4OP.js.map} +0 -0
- /package/dist/{StatsViewer-PDXZ7PJR.js.map → StatsViewer-3CUMIAV4.js.map} +0 -0
- /package/dist/{WordLookup-EAXT7PGW.js.map → WordLookup-YIU6LP4H.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui/nav.tsx","../src/i18n/context.tsx","../src/i18n/strings.ts","../src/i18n/locale.ts","../src/ui/components/BigWord.tsx"],"sourcesContent":["import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';\n\nexport type ScreenName =\n | 'main'\n | 'practice'\n | 'dict'\n | 'config'\n | 'stats'\n | 'word'\n | 'help';\n\nexport type PracticeParams = {\n dictId: string;\n chapterIndex: number;\n mode: 'order' | 'dictation' | 'review' | 'random' | 'loop';\n stealth?: boolean;\n};\n\nexport type DictParams = {\n pickerMode?: 'set-default' | 'choose-then-practice';\n};\n\nexport type ScreenFrame =\n | { name: 'main' }\n | { name: 'practice'; params: PracticeParams }\n | { name: 'dict'; params?: DictParams }\n | { name: 'config' }\n | { name: 'stats' }\n | { name: 'word' }\n | { name: 'help' };\n\ntype NavContextValue = {\n current: ScreenFrame;\n stack: ScreenFrame[];\n navigate: (frame: ScreenFrame) => void;\n replace: (frame: ScreenFrame) => void;\n back: () => void;\n reset: (frame: ScreenFrame) => void;\n};\n\nconst NavContext = createContext<NavContextValue | null>(null);\n\nexport function NavProvider({ initial, children }: { initial: ScreenFrame; children: ReactNode }) {\n const [stack, setStack] = useState<ScreenFrame[]>([initial]);\n\n const navigate = useCallback((frame: ScreenFrame) => {\n setStack((s) => [...s, frame]);\n }, []);\n const replace = useCallback((frame: ScreenFrame) => {\n setStack((s) => (s.length === 0 ? [frame] : [...s.slice(0, -1), frame]));\n }, []);\n const back = useCallback(() => {\n setStack((s) => (s.length > 1 ? s.slice(0, -1) : s));\n }, []);\n const reset = useCallback((frame: ScreenFrame) => {\n setStack([frame]);\n }, []);\n\n const current = stack[stack.length - 1]!;\n return (\n <NavContext.Provider value={{ current, stack, navigate, replace, back, reset }}>\n {children}\n </NavContext.Provider>\n );\n}\n\nexport function useNav(): NavContextValue {\n const ctx = useContext(NavContext);\n if (!ctx) throw new Error('useNav must be used inside NavProvider');\n return ctx;\n}\n","import { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { en, zh, type Strings } from './strings.js';\nimport { detectLocale, type Lang, type LangPref } from './locale.js';\n\nconst StringsContext = createContext<{ lang: Lang; t: Strings } | null>(null);\n\nexport function StringsProvider({\n pref,\n children,\n}: {\n pref: LangPref;\n children: ReactNode;\n}) {\n const value = useMemo(() => {\n const lang = detectLocale(pref);\n return { lang, t: lang === 'zh' ? zh : en };\n }, [pref]);\n return <StringsContext.Provider value={value}>{children}</StringsContext.Provider>;\n}\n\nexport function useStrings(): Strings {\n const ctx = useContext(StringsContext);\n if (!ctx) throw new Error('useStrings must be used inside StringsProvider');\n return ctx.t;\n}\n\nexport function useLang(): Lang {\n const ctx = useContext(StringsContext);\n if (!ctx) throw new Error('useLang must be used inside StringsProvider');\n return ctx.lang;\n}\n\nexport function pickStrings(pref: LangPref): { lang: Lang; t: Strings } {\n const lang = detectLocale(pref);\n return { lang, t: lang === 'zh' ? zh : en };\n}\n","// Centralized string table for the qwerty-cli TUI.\n//\n// Print-only subcommands (qwerty config list, qwerty dict list, etc.) keep\n// English output regardless of locale so shell scripts stay predictable.\n// Only the interactive TUI is localized via this table.\n\nexport type Strings = {\n app: {\n title: string;\n subtitle: string;\n };\n common: {\n back: string;\n quit: string;\n on: string;\n off: string;\n cancel: string;\n };\n mainMenu: {\n items: {\n practiceLabel: string;\n practiceHintWith: (name: string) => string;\n practiceHintNone: string;\n dictLabel: string;\n dictHint: string;\n wordLabel: string;\n wordHint: string;\n statsLabel: string;\n statsHint: string;\n configLabel: string;\n configHint: string;\n stealthLabel: string;\n stealthHint: string;\n quitLabel: string;\n quitHint: string;\n };\n hint: string;\n helpHint: string;\n };\n dict: {\n title: string;\n loading: string;\n entries: (n: number) => string;\n filterPlaceholder: string;\n local: string;\n notLocal: string;\n defaultMark: string;\n tagsLabel: (tags: string) => string;\n wordsLabel: (n: number) => string;\n pulling: (id: string) => string;\n removing: (id: string) => string;\n errorOn: (id: string, msg: string) => string;\n footer: string;\n action: {\n title: string;\n setDefault: string;\n practice: string;\n delete: string;\n };\n command: {\n title: string;\n pull: string;\n import: string;\n refreshList: string;\n };\n };\n config: {\n title: string;\n fields: {\n defaultDict: string;\n defaultMode: string;\n accent: string;\n mirror: string;\n chapterSize: string;\n autoplayPronunciation: string;\n soundsMaster: string;\n soundsKeystroke: string;\n soundsFeedback: string;\n soundsKeySound: string;\n language: string;\n stealth: string;\n };\n enumValues: {\n stealth: { off: string; menu: string; default: string };\n };\n hints: {\n editing: string;\n bool: string;\n enum: string;\n dictRef: string;\n stringOrInt: string;\n };\n };\n stats: {\n title: string;\n loading: string;\n none: string;\n nonePractice: string;\n lifetime: string;\n sessions: string;\n words: string;\n errors: string;\n wpm: string;\n accuracy: string;\n streak: string;\n last: (n: number) => string;\n cycleWindow: string;\n recent: string;\n topMistakes: string;\n footer: string;\n maxLabel: string;\n recentUnits: { words: string; errors: string; wpm: string };\n multiDictSuffix: (n: number) => string;\n bars: { speed: string; accuracy: string; sessions: string };\n };\n word: {\n title: string;\n indexing: string;\n none: string;\n pullFirst: string;\n countAcross: (n: number) => string;\n noMatches: (q: string) => string;\n inDict: (name: string) => string;\n mistakes: (n: number, date: string) => string;\n footer: string;\n };\n practice: {\n loading: string;\n paused: string;\n chapterComplete: string;\n chapterLabel: (c: number, t: number) => string;\n reviewLabel: string;\n statusBar: {\n mode: string;\n accent: string;\n };\n modes: {\n order: string;\n dictation: string;\n review: string;\n random: string;\n loop: string;\n };\n accents: {\n us: string;\n uk: string;\n };\n statCards: {\n words: string;\n errors: string;\n wpm: string;\n accuracy: string;\n elapsed: (t: string) => string;\n };\n pause: {\n title: string;\n chapter: (c: number, t: number) => string;\n progress: (completed: number, total: number) => string;\n hint: string;\n };\n summary: {\n loopAgain: string;\n nextChapter: string;\n reviewMistakes: string;\n backMenu: string;\n };\n footers: {\n typing: string;\n };\n errors: {\n noMistakes: string;\n dictEmpty: (id: string) => string;\n unknown: string;\n };\n imeWarning: string;\n imeWarningShort: string;\n audioWarningShort: string;\n };\n audio: {\n noPlayer: string;\n };\n report: {\n title: string;\n duration: string;\n practiced: string;\n chapters: string;\n words: string;\n accuracy: string;\n wpm: string;\n newMistakes: string;\n farewell: string;\n notPracticed: string;\n };\n help: {\n title: string;\n subtitle: string;\n sections: {\n main: string;\n practice: string;\n dict: string;\n config: string;\n stats: string;\n word: string;\n global: string;\n };\n keys: {\n navigate: string;\n select: string;\n letterJump: string;\n pause: string;\n skip: string;\n replay: string;\n resume: string;\n backMenu: string;\n backScreen: string;\n nextChapter: string;\n reviewMistakes: string;\n filter: string;\n itemActions: string;\n moreActions: string;\n cycleWindow: string;\n stealthToggle: string;\n helpScreen: string;\n quit: string;\n };\n footer: string;\n };\n stealth: {\n paused: string;\n chapterDone: string;\n resumeHint: string;\n nextHint: string;\n pausedHintRight: string;\n nextHintRight: string;\n infoChipLabel: string;\n infoFmt: (\n dict: string,\n chapter: string,\n completed: number,\n total: number,\n wpm: number,\n accPct: number,\n ) => string;\n };\n};\n\nexport const en: Strings = {\n app: {\n title: 'qwerty',\n subtitle: 'typing practice for the terminal',\n },\n common: {\n back: 'back',\n quit: 'quit',\n on: 'on',\n off: 'off',\n cancel: 'cancel',\n },\n mainMenu: {\n items: {\n practiceLabel: 'Practice',\n practiceHintWith: (name) => `start ${name}`,\n practiceHintNone: 'pick a dictionary',\n dictLabel: 'Dictionaries',\n dictHint: 'browse, pull, set default',\n wordLabel: 'Word lookup',\n wordHint: 'search local dicts',\n statsLabel: 'Stats',\n statsHint: 'history & trends',\n configLabel: 'Config',\n configHint: 'edit preferences',\n stealthLabel: 'Stealth',\n stealthHint: 'quiet practice mode',\n quitLabel: 'Quit',\n quitHint: 'Esc or Ctrl+C also exits',\n },\n hint: '↑/↓ navigate · Enter select · letters jump',\n helpHint: '? help',\n },\n dict: {\n title: 'Dictionaries',\n loading: 'loading dictionaries…',\n entries: (n) => `${n} entries`,\n filterPlaceholder: 'type to filter',\n local: 'local ✓',\n notLocal: 'not local',\n defaultMark: 'default ★',\n tagsLabel: (tags) => `tags: ${tags}`,\n wordsLabel: (n) => `${n} words`,\n pulling: (id) => `pulling ${id}…`,\n removing: (id) => `removing ${id}…`,\n errorOn: (id, msg) => `error on ${id}: ${msg}`,\n footer: '↑/↓ select · Enter actions · Ctrl+K more · Esc back',\n action: {\n title: 'current dictionary',\n setDefault: 'set as default',\n practice: 'practice now',\n delete: 'delete local',\n },\n command: {\n title: 'more actions',\n pull: 'pull selected',\n import: 'import .json',\n refreshList: 'update dictionary list',\n },\n },\n config: {\n title: 'Config',\n fields: {\n defaultDict: 'default dict',\n defaultMode: 'default mode',\n accent: 'accent',\n mirror: 'dict mirror',\n chapterSize: 'chapter size',\n autoplayPronunciation: 'autoplay pronunciation',\n soundsMaster: 'sounds master',\n soundsKeystroke: 'sounds keystroke',\n soundsFeedback: 'sounds feedback',\n soundsKeySound: 'sounds key sound',\n language: 'language',\n stealth: 'stealth mode',\n },\n enumValues: {\n stealth: { off: 'off', menu: 'show in menu', default: 'default practice' },\n },\n hints: {\n editing: 'type to edit · Enter save · Esc cancel',\n bool: 'space toggle · ↑/↓ move · Esc back',\n enum: '←/→ cycle · ↑/↓ move · Esc back',\n dictRef: 'Enter pick dict · ↑/↓ move · Esc back',\n stringOrInt: 'Enter edit · ↑/↓ move · Esc back',\n },\n },\n stats: {\n title: 'Stats · overview',\n loading: 'loading stats…',\n none: 'No practice history yet.',\n nonePractice: 'Run a practice session first.',\n lifetime: 'lifetime',\n sessions: 'sessions',\n words: 'words',\n errors: 'errors',\n wpm: 'wpm',\n accuracy: 'accuracy',\n streak: 'streak',\n last: (n) => `last ${n} days (←/→ cycle window)`,\n cycleWindow: '←/→ cycle window · Esc back',\n recent: 'recent sessions',\n topMistakes: 'top mistakes',\n footer: '←/→ cycle window · Esc back',\n maxLabel: 'max',\n recentUnits: { words: 'w', errors: 'err', wpm: 'wpm' },\n multiDictSuffix: (n) => ` +${n} more`,\n bars: { speed: 'speed', accuracy: 'accuracy', sessions: 'sessions' },\n },\n word: {\n title: 'Word lookup',\n indexing: 'indexing local dictionaries…',\n none: 'No local dictionaries.',\n pullFirst: 'Pull one in Dictionaries first.',\n countAcross: (n) => `${n} words across local dicts`,\n noMatches: (q) => `no matches for \"${q}\"`,\n inDict: (name) => `in: ${name}`,\n mistakes: (n, date) => `mistakes: ${n} (last ${date})`,\n footer: 'type to filter · ↑/↓ select · Esc back',\n },\n practice: {\n loading: 'loading…',\n paused: 'PAUSED',\n chapterComplete: 'CHAPTER COMPLETE',\n chapterLabel: (c, t) => `chapter ${c}/${t}`,\n reviewLabel: 'review',\n statusBar: {\n mode: 'mode',\n accent: 'accent',\n },\n modes: {\n order: 'order',\n dictation: 'dictation',\n review: 'review',\n random: 'random',\n loop: 'loop',\n },\n accents: {\n us: 'us',\n uk: 'uk',\n },\n statCards: {\n words: 'words',\n errors: 'errors',\n wpm: 'wpm',\n accuracy: 'accuracy',\n elapsed: (t) => `elapsed ${t}`,\n },\n pause: {\n title: 'PAUSED',\n chapter: (c, t) => `chapter ${c}/${t}`,\n progress: (completed, total) => `${completed}/${total}`,\n hint: 'Enter resume · Esc back to menu',\n },\n summary: {\n loopAgain: 'again',\n nextChapter: 'next chapter',\n reviewMistakes: 'review mistakes',\n backMenu: 'back to menu',\n },\n footers: {\n typing: 'Ctrl+N skip · Esc pause · Tab replay',\n },\n errors: {\n noMistakes: 'No mistakes to review yet. Practice some chapters first.',\n dictEmpty: (id) => `Dictionary ${id} is empty.`,\n unknown: 'Unknown error',\n },\n imeWarning: '! Non-English input detected — switch your IME to English.',\n imeWarningShort: '! IME',\n audioWarningShort: '! audio',\n },\n audio: {\n noPlayer: '! No audio player found on PATH (looked for afplay/ffplay/mpg123/paplay/aplay/powershell). Sounds disabled.',\n },\n report: {\n title: 'Session summary',\n duration: 'duration',\n practiced: 'practiced',\n chapters: 'chapters',\n words: 'words',\n accuracy: 'accuracy',\n wpm: 'wpm',\n newMistakes: 'new mistakes',\n farewell: 'see you next time.',\n notPracticed: 'no practice this run',\n },\n help: {\n title: 'Help',\n subtitle: 'all shortcuts',\n sections: {\n main: 'main menu',\n practice: 'practice',\n dict: 'dictionaries',\n config: 'config',\n stats: 'stats',\n word: 'word lookup',\n global: 'global',\n },\n keys: {\n navigate: '↑/↓ navigate items',\n select: 'Enter confirm / continue',\n letterJump: 'letter jump to menu item',\n pause: 'Esc pause practice',\n skip: 'Ctrl+N skip current word (neutral)',\n replay: 'Tab replay pronunciation',\n resume: 'Enter resume from pause',\n backMenu: 'Esc back to previous screen',\n backScreen: 'Esc close panel or back',\n nextChapter: 'Enter next chapter',\n reviewMistakes: 'm review mistakes',\n filter: 'type to filter list',\n itemActions: 'Enter open actions panel',\n moreActions: 'Ctrl+K more actions panel',\n cycleWindow: '←/→ cycle day window',\n stealthToggle: 'Ctrl+I toggle stealth info row',\n helpScreen: '? open this help screen',\n quit: 'Ctrl+C quit immediately',\n },\n footer: 'Esc back',\n },\n stealth: {\n paused: 'paused',\n chapterDone: 'chapter done',\n resumeHint: 'Enter resume · Esc menu',\n nextHint: 'Enter next · Esc menu',\n pausedHintRight: 'Enter resume',\n nextHintRight: 'Enter next',\n infoChipLabel: 'info',\n infoFmt: (dict, chapter, completed, total, wpm, accPct) =>\n `${dict} · ${chapter} · ${completed}/${total} · ${wpm} wpm · ${accPct}%`,\n },\n};\n\nexport const zh: Strings = {\n app: {\n title: 'qwerty',\n subtitle: '终端键盘练习',\n },\n common: {\n back: '返回',\n quit: '退出',\n on: '开',\n off: '关',\n cancel: '取消',\n },\n mainMenu: {\n items: {\n practiceLabel: '练习',\n practiceHintWith: (name) => `开始 ${name}`,\n practiceHintNone: '请先选词典',\n dictLabel: '词典',\n dictHint: '浏览、下载、设为默认',\n wordLabel: '查词',\n wordHint: '在本地词典中搜索',\n statsLabel: '统计',\n statsHint: '历史与趋势',\n configLabel: '设置',\n configHint: '修改偏好',\n stealthLabel: '神隐',\n stealthHint: '神隐练习模式',\n quitLabel: '退出',\n quitHint: 'Esc 或 Ctrl+C 退出',\n },\n hint: '↑/↓ 移动 · Enter 确认 · 字母直达',\n helpHint: '? 帮助',\n },\n dict: {\n title: '词典',\n loading: '加载词典中…',\n entries: (n) => `${n} 部词典`,\n filterPlaceholder: '输入过滤',\n local: '已下载 ✓',\n notLocal: '未下载',\n defaultMark: '默认 ★',\n tagsLabel: (tags) => `标签:${tags}`,\n wordsLabel: (n) => `${n} 词`,\n pulling: (id) => `拉取 ${id} 中…`,\n removing: (id) => `删除 ${id} 中…`,\n errorOn: (id, msg) => `${id} 出错:${msg}`,\n footer: '↑/↓ 选择 · Enter 操作 · Ctrl+K 更多 · Esc 返回',\n action: {\n title: '当前词典',\n setDefault: '设为默认',\n practice: '立即练习',\n delete: '删除本地',\n },\n command: {\n title: '更多功能',\n pull: '拉取选中',\n import: '导入 .json',\n refreshList: '更新词典列表',\n },\n },\n config: {\n title: '设置',\n fields: {\n defaultDict: '默认词典',\n defaultMode: '默认模式',\n accent: '发音',\n mirror: '词典镜像源',\n chapterSize: '章节单词数',\n autoplayPronunciation: '自动播放发音',\n soundsMaster: '音效总开关',\n soundsKeystroke: '按键音',\n soundsFeedback: '反馈音',\n soundsKeySound: '按键音色',\n language: '语言',\n stealth: '神隐模式',\n },\n enumValues: {\n stealth: { off: '关闭', menu: '主菜单显示', default: '默认练习模式' },\n },\n hints: {\n editing: '输入修改 · Enter 保存 · Esc 取消',\n bool: '空格切换 · ↑/↓ 移动 · Esc 返回',\n enum: '←/→ 切换 · ↑/↓ 移动 · Esc 返回',\n dictRef: 'Enter 选词典 · ↑/↓ 移动 · Esc 返回',\n stringOrInt: 'Enter 编辑 · ↑/↓ 移动 · Esc 返回',\n },\n },\n stats: {\n title: '统计 · 概览',\n loading: '加载统计中…',\n none: '还没有练习记录。',\n nonePractice: '先来一次练习吧。',\n lifetime: '累计',\n sessions: '会话',\n words: '词数',\n errors: '错误',\n wpm: '速度',\n accuracy: '准确率',\n streak: '连续天数',\n last: (n) => `最近 ${n} 天 (←/→ 切换窗口)`,\n cycleWindow: '←/→ 切换窗口 · Esc 返回',\n recent: '最近会话',\n topMistakes: '高频错词',\n footer: '←/→ 切换窗口 · Esc 返回',\n maxLabel: '最大',\n recentUnits: { words: '词', errors: '错', wpm: '速' },\n multiDictSuffix: (n) => ` 等 ${n} 部`,\n bars: { speed: '速度', accuracy: '准确率', sessions: '会话' },\n },\n word: {\n title: '查词',\n indexing: '索引本地词典中…',\n none: '没有本地词典。',\n pullFirst: '先在「词典」中拉取一部。',\n countAcross: (n) => `本地词典共 ${n} 词`,\n noMatches: (q) => `没有匹配「${q}」的词`,\n inDict: (name) => `来源:${name}`,\n mistakes: (n, date) => `错过 ${n} 次 (最近 ${date})`,\n footer: '输入过滤 · ↑/↓ 选择 · Esc 返回',\n },\n practice: {\n loading: '加载中…',\n paused: '已暂停',\n chapterComplete: '本章完成',\n chapterLabel: (c, t) => `第 ${c}/${t} 章`,\n reviewLabel: '复习',\n statusBar: {\n mode: '模式',\n accent: '发音',\n },\n modes: {\n order: '顺序',\n dictation: '默写',\n review: '复习',\n random: '乱序',\n loop: '循环',\n },\n accents: {\n us: '美',\n uk: '英',\n },\n statCards: {\n words: '词数',\n errors: '错误',\n wpm: '速度',\n accuracy: '准确率',\n elapsed: (t) => `耗时 ${t}`,\n },\n pause: {\n title: '已暂停',\n chapter: (c, t) => `第 ${c}/${t} 章`,\n progress: (completed, total) => `${completed}/${total}`,\n hint: 'Enter 继续 · Esc 返回菜单',\n },\n summary: {\n loopAgain: '再来一遍',\n nextChapter: '下一章',\n reviewMistakes: '复习错词',\n backMenu: '返回菜单',\n },\n footers: {\n typing: 'Ctrl+N 跳过 · Esc 暂停 · Tab 重播',\n },\n errors: {\n noMistakes: '错词本是空的。先练习几章吧。',\n dictEmpty: (id) => `词典 ${id} 是空的。`,\n unknown: '未知错误',\n },\n imeWarning: '! 检测到中文/日文/韩文输入,请切换到英文输入法',\n imeWarningShort: '! 中文输入',\n audioWarningShort: '! 无音效',\n },\n audio: {\n noPlayer: '! 未在 PATH 中找到音频播放器(尝试 afplay/ffplay/mpg123/paplay/aplay/powershell)。音效已禁用。',\n },\n report: {\n title: '本次会话',\n duration: '总时长',\n practiced: '练习用时',\n chapters: '完成章节',\n words: '词数',\n accuracy: '准确率',\n wpm: '速度',\n newMistakes: '新错词',\n farewell: '下次见。',\n notPracticed: '本次未练习',\n },\n help: {\n title: '帮助',\n subtitle: '全部快捷键',\n sections: {\n main: '主菜单',\n practice: '练习',\n dict: '词典',\n config: '设置',\n stats: '统计',\n word: '查词',\n global: '全局',\n },\n keys: {\n navigate: '↑/↓ 移动选项',\n select: 'Enter 确认 / 继续',\n letterJump: '字母键 直达菜单项',\n pause: 'Esc 暂停练习',\n skip: 'Ctrl+N 跳过当前词(不计错)',\n replay: 'Tab 重播发音',\n resume: 'Enter 继续练习',\n backMenu: 'Esc 返回上一屏',\n backScreen: 'Esc 关闭面板 / 返回',\n nextChapter: 'Enter 下一章',\n reviewMistakes: 'm 复习错词',\n filter: '输入 过滤列表',\n itemActions: 'Enter 弹出动作面板',\n moreActions: 'Ctrl+K 弹出更多功能',\n cycleWindow: '←/→ 切换日窗口',\n stealthToggle: 'Ctrl+I 切换神隐信息行',\n helpScreen: '? 打开本帮助页',\n quit: 'Ctrl+C 立即退出',\n },\n footer: 'Esc 返回',\n },\n stealth: {\n paused: 'paused',\n chapterDone: 'chapter done',\n resumeHint: 'Enter resume · Esc menu',\n nextHint: 'Enter next · Esc menu',\n pausedHintRight: 'Enter 继续',\n nextHintRight: 'Enter 下一章',\n infoChipLabel: '信息',\n infoFmt: (dict, chapter, completed, total, wpm, accPct) =>\n `${dict} · ${chapter} · ${completed}/${total} · ${wpm} wpm · ${accPct}%`,\n },\n};\n","export type Lang = 'zh' | 'en';\nexport type LangPref = 'auto' | Lang;\n\nfunction pickFromString(s: string | undefined): Lang | null {\n if (!s) return null;\n const lower = s.toLowerCase();\n if (lower.startsWith('zh')) return 'zh';\n if (lower.startsWith('en')) return 'en';\n return null;\n}\n\nexport function detectLocale(pref: LangPref): Lang {\n if (pref === 'zh' || pref === 'en') return pref;\n const env =\n process.env.LC_ALL ||\n process.env.LC_MESSAGES ||\n process.env.LANG ||\n process.env.LANGUAGE;\n const fromEnv = pickFromString(env);\n if (fromEnv) return fromEnv;\n try {\n const intlLocale = Intl.DateTimeFormat().resolvedOptions().locale;\n const fromIntl = pickFromString(intlLocale);\n if (fromIntl) return fromIntl;\n } catch {\n // ignore\n }\n return 'en';\n}\n","import { Box, Text } from 'ink';\n\nexport const PALETTE = {\n accent: '#5eead4',\n muted: '#6b7280',\n text: '#e5e7eb',\n primary: '#7dcfff',\n success: '#86efac',\n warning: '#fbbf24',\n error: '#f87171',\n} as const;\n\ntype Props = {\n target: string;\n typed: string;\n error?: boolean;\n hideTarget?: boolean;\n};\n\nexport function BigWord({ target, typed, error = false, hideTarget = false }: Props) {\n const chars = [...target];\n const typedChars = [...typed];\n\n return (\n <Box paddingY={4} justifyContent=\"center\">\n {chars.map((ch, i) => {\n const isTyped = i < typedChars.length;\n const display = hideTarget && !isTyped ? '_' : isTyped ? typedChars[i]! : ch;\n const color = error\n ? PALETTE.error\n : isTyped\n ? PALETTE.accent\n : PALETTE.muted;\n return (\n <Text key={i} bold color={color}>\n {display}\n </Text>\n );\n })}\n </Box>\n );\n}\n"],"mappings":"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","names":["createContext","useContext","useState","useCallback","jsx","NavContext","NavProvider","initial","children","stack","setStack","navigate","frame","s","replace","back","reset","current","useNav","ctx","createContext","useContext","useMemo","en","name","n","tags","id","msg","q","date","c","t","completed","total","dict","chapter","wpm","accPct","zh","pickFromString","s","lower","detectLocale","pref","env","fromEnv","intlLocale","fromIntl","jsx","StringsContext","createContext","StringsProvider","pref","children","value","useMemo","lang","detectLocale","zh","en","useStrings","ctx","useContext","pickStrings","pref","lang","detectLocale","zh","en","Box","Text","jsx","PALETTE","BigWord","target","typed","error","hideTarget","chars","typedChars","ch","i","isTyped","display","color"]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{a as L,b as R}from"./chunk-7RMRK5MO.js";import{a as C,b as P}from"./chunk-2GTGXODM.js";import{a as B,b as D,c as w,e as H}from"./chunk-IUFBN3RD.js";import{a as E,b as M,c as I,d as N,f as a}from"./chunk-R6HQWKXU.js";var K="\x1B[?1049h\x1B[?25l\x1B[2J\x1B[H",z="\x1B[?25h\x1B[?1049l",v=!1,$=!1;function _(){if($)return;$=!0;let t=()=>{if(v){try{process.stdout.write(z)}catch{}v=!1}};process.once("exit",t),process.once("SIGINT",()=>{t(),process.exit(130)}),process.once("SIGTERM",()=>{t(),process.exit(143)})}function W(){v||process.stdout.isTTY&&process.env.QWERTY_NO_ALTSCREEN!=="1"&&(_(),process.stdout.write(K),v=!0)}function T(){if(v){try{process.stdout.write(z)}catch{}v=!1}}var k=null;function F(t){k=t}function yt(){let t=k;return k=null,t}import{Suspense as ot,lazy as A,useRef as nt}from"react";import{Box as it,Text as st,useApp as at,useInput as ct}from"ink";import{useEffect as O,useState as Q}from"react";import{Box as X,useStdout as U}from"ink";import{jsx as Z}from"react/jsx-runtime";function j({children:t}){let{stdout:e}=U(),[s,r]=Q(()=>({rows:e?.rows??24,cols:e?.columns??80}));return O(()=>{W();let n=()=>{r({rows:process.stdout.rows??24,cols:process.stdout.columns??80})};return process.stdout.on("resize",n),()=>{process.stdout.off("resize",n),T()}},[]),Z(X,{width:s.cols,height:s.rows,flexDirection:"column",children:t})}import{useState as tt}from"react";import{Box as y,Text as m,useApp as et,useInput as rt}from"ink";import{jsx as h,jsxs as g}from"react/jsx-runtime";function Y({cfg:t}){let[e,s]=tt(0),{exit:r}=et(),n=M(),f=R(),u=N(),l=D(t.defaultDict),o=u.mainMenu.items,b=c=>{if(c&&t.defaultDict){F({type:"stealth",dictId:t.defaultDict,chapterIndex:0,mode:t.defaultMode}),r();return}t.defaultDict?n.navigate({name:"practice",params:{dictId:t.defaultDict,chapterIndex:0,mode:t.defaultMode,stealth:c}}):n.navigate({name:"dict",params:{pickerMode:"choose-then-practice"}})},d=[{key:"p",label:o.practiceLabel,hint:t.defaultDict?o.practiceHintWith(H(l,24)):o.practiceHintNone,run:()=>b(t.stealth==="default")}];(t.stealth==="menu"||t.stealth==="default")&&d.push({key:"b",label:o.stealthLabel,hint:o.stealthHint,run:()=>b(!0)}),d.push({key:"d",label:o.dictLabel,hint:o.dictHint,run:()=>n.navigate({name:"dict"})},{key:"w",label:o.wordLabel,hint:o.wordHint,run:()=>n.navigate({name:"word"})},{key:"s",label:o.statsLabel,hint:o.statsHint,run:()=>n.navigate({name:"stats"})},{key:"c",label:o.configLabel,hint:o.configHint,run:()=>n.navigate({name:"config"})},{key:"q",label:o.quitLabel,hint:o.quitHint,run:()=>r()});let G=Math.max(...d.map(c=>w(c.label)))+4;return rt((c,S)=>{if(S.escape){r();return}if(S.upArrow&&s(p=>(p-1+d.length)%d.length),S.downArrow&&s(p=>(p+1)%d.length),S.return){d[e].run();return}if(c==="?"){n.navigate({name:"help"});return}for(let p of d)if(c===p.key){p.run();return}}),g(y,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",children:[g(y,{children:[h(m,{bold:!0,color:a.accent,children:u.app.title}),g(m,{color:a.muted,children:[" \xB7 ",u.app.subtitle]})]}),h(y,{marginTop:2,flexDirection:"column",children:d.map((c,S)=>{let p=S===e,J=" ".repeat(Math.max(0,G-w(c.label)));return g(y,{children:[h(m,{color:p?a.accent:a.muted,children:p?"\u258C ":" "}),g(m,{color:p?a.accent:a.muted,children:["[",c.key,"]"]}),h(m,{children:" "}),g(m,{bold:p,color:p?a.text:a.muted,children:[c.label,J]}),h(m,{color:a.muted,children:c.hint})]},c.key)})}),h(y,{marginTop:2,children:g(m,{color:a.muted,children:[u.mainMenu.hint," \xB7 ",u.mainMenu.helpHint]})}),f.warning&&h(y,{marginTop:1,children:h(m,{color:a.warning,children:u.audio.noPlayer})})]})}import{jsx as i}from"react/jsx-runtime";var pt=A(()=>import("./PracticeScreen-LM6M2OYD.js").then(t=>({default:t.PracticeScreen}))),ut=A(()=>import("./DictBrowser-7CIISVDN.js").then(t=>({default:t.DictBrowser}))),lt=A(()=>import("./ConfigEditor-HAP5C2Y7.js").then(t=>({default:t.ConfigEditor}))),dt=A(()=>import("./StatsViewer-3CUMIAV4.js").then(t=>({default:t.StatsViewer}))),mt=A(()=>import("./WordLookup-YIU6LP4H.js").then(t=>({default:t.WordLookup}))),ft=A(()=>import("./HelpScreen-P4U5O4OP.js").then(t=>({default:t.HelpScreen})));function ht(){return i(it,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:i(st,{color:a.muted,children:"\u2026"})})}function Xt({initial:t,initialCfg:e,inline:s=!1}){return i(C,{initialCfg:e,children:i(gt,{children:i(B,{children:i(L,{disabled:!e.sounds.master,children:i(E,{initial:t,children:s?i(q,{inline:!0}):i(j,{children:i(q,{})})})})})})})}function gt({children:t}){let{cfg:e}=P();return i(I,{pref:e.language,children:t})}function xt(t){if(t.name==="practice"){let e=t.params;return`practice:${e.dictId}:${e.chapterIndex}:${e.mode}:${e.stealth?"s":"n"}`}return t.name}function q({inline:t=!1}){let e=M(),{cfg:s}=P(),{exit:r}=at(),n=nt(null);ct((o,b)=>{b.ctrl&&o==="c"&&r()});let f=e.current,u=xt(f);n.current!==u&&(!t&&process.stdout.isTTY&&process.stdout.write("\x1B[2J\x1B[H"),n.current=u);let l=(()=>{switch(f.name){case"main":return i(Y,{cfg:s});case"practice":return i(pt,{params:f.params});case"dict":return i(ut,{params:f.params});case"config":return i(lt,{});case"stats":return i(dt,{});case"word":return i(mt,{});case"help":return i(ft,{})}})();return i(ot,{fallback:i(ht,{}),children:l})}import x from"chalk";import bt from"boxen";function ne(){T()}function V(t,e){let s=Math.floor(t/1e3),r=Math.floor(s/60),n=s%60;return e==="zh"?r===0?`${n} \u79D2`:`${r} \u5206 ${n} \u79D2`:r===0?`${n}s`:`${r}m ${n}s`}function St(t){return t>=90?x.green:t<75?x.dim:e=>e}function wt(t,e,s){let r=[],n=[e.report.duration];t.chaptersCompleted===0?n.push(e.report.notPracticed):(n.push(e.report.practiced,e.report.chapters,e.report.words,e.report.accuracy,e.report.wpm),t.newMistakeWords>0&&n.push(e.report.newMistakes));let f=Math.max(...n.map(w)),u=o=>o+" ".repeat(Math.max(0,f-w(o))),l=(o,b)=>`${x.dim(u(o))} ${b}`;if(r.push(l(e.report.duration,V(t.totalDurationMs,s))),t.chaptersCompleted===0)r.push(x.dim(e.report.notPracticed));else{r.push(l(e.report.practiced,V(t.practiceMs,s))),r.push(l(e.report.chapters,String(t.chaptersCompleted))),r.push(l(e.report.words,String(t.wordCount)));let o=Math.round(t.accuracy*1e3)/10;r.push(l(e.report.accuracy,St(o)(`${o}%`))),r.push(l(e.report.wpm,String(t.wpm))),t.newMistakeWords>0&&r.push(l(e.report.newMistakes,x.red(String(t.newMistakeWords))))}return r.push(""),r.push(x.dim.italic(e.report.farewell)),r}function ie(t,e,s){if(t.startedAt===null&&t.chaptersCompleted===0)return;let r=wt(t,e,s).join(`
|
|
2
|
+
`);console.log(bt(r,{title:x.bold.cyan(e.report.title),titleAlignment:"left",borderStyle:"round",borderColor:"gray",padding:{top:1,bottom:1,left:3,right:3},margin:{top:1,bottom:1,left:2,right:0}}))}export{W as a,yt as b,Xt as c,ne as d,ie as e};
|
|
3
|
+
//# sourceMappingURL=chunk-ZXMHFRCR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/util/altscreen.ts","../src/util/post-exit-action.ts","../src/ui/App.tsx","../src/ui/Fullscreen.tsx","../src/ui/screens/MainMenu.tsx","../src/util/report.ts"],"sourcesContent":["// Shared alt-screen state. Both the command entry point (which writes\n// ENTER before render() to avoid Ink's first frame leaking onto the main\n// screen) and Fullscreen.tsx (which used to write ENTER inside useEffect)\n// route through this module so enter/leave are idempotent.\n\nconst ENTER = '\\x1b[?1049h\\x1b[?25l\\x1b[2J\\x1b[H';\nconst LEAVE = '\\x1b[?25h\\x1b[?1049l';\n\nlet active = false;\nlet signalsRegistered = false;\n\nfunction ensureSignals(): void {\n if (signalsRegistered) return;\n signalsRegistered = true;\n const cleanup = () => {\n if (!active) return;\n try {\n process.stdout.write(LEAVE);\n } catch {\n // stream may be closed\n }\n active = false;\n };\n process.once('exit', cleanup);\n process.once('SIGINT', () => {\n cleanup();\n process.exit(130);\n });\n process.once('SIGTERM', () => {\n cleanup();\n process.exit(143);\n });\n}\n\nexport function enterAltScreen(): void {\n if (active) return;\n if (!process.stdout.isTTY) return;\n if (process.env.QWERTY_NO_ALTSCREEN === '1') return;\n ensureSignals();\n process.stdout.write(ENTER);\n active = true;\n}\n\nexport function leaveAltScreen(): void {\n if (!active) return;\n try {\n process.stdout.write(LEAVE);\n } catch {\n // stream may be closed\n }\n active = false;\n}\n\nexport function isAltScreenActive(): boolean {\n return active;\n}\n","// Single-shot handoff store: lets a UI screen request that the CLI re-enter a\n// different mode after Ink unmounts. Used by MainMenu → stealth, where the\n// menu runs inside an alt-screen render() and we cannot switch to inline mode\n// mid-flight. The menu screen calls setPostExitAction({ type: 'stealth', ... })\n// then exit(); the command entry point consumes the action after\n// waitUntilExit() and routes accordingly.\n//\n// Lifecycle: consumePostExitAction() reads-and-clears so a leftover action\n// from a previous run can never leak into the next one.\n\nimport type { Mode } from '../domain/chapters.js';\n\nexport type PostExitAction =\n | { type: 'stealth'; dictId: string; chapterIndex: number; mode: Mode };\n\nlet pending: PostExitAction | null = null;\n\nexport function setPostExitAction(action: PostExitAction): void {\n pending = action;\n}\n\nexport function consumePostExitAction(): PostExitAction | null {\n const a = pending;\n pending = null;\n return a;\n}\n","import { Suspense, lazy, useRef, type ReactNode } from 'react';\nimport { Box, Text, useApp, useInput } from 'ink';\nimport { NavProvider, useNav, type ScreenFrame } from './nav.js';\nimport { Fullscreen } from './Fullscreen.js';\nimport { AudioStatusProvider } from './audio-context.js';\nimport { AppStateProvider, useAppState } from './app-state.js';\nimport { StringsProvider } from '../i18n/context.js';\nimport { RegistryProvider } from './registry-context.js';\nimport { MainMenu } from './screens/MainMenu.js';\nimport { PALETTE } from './components/BigWord.js';\nimport type { Config } from '../infra/config-store.js';\n\n// MainMenu stays eager — it's the initial screen for `qwerty` (no args) and\n// must render instantly. All other screens are lazy so their module graphs\n// (DictBrowser pulls registry helpers, StatsViewer pulls stats domain, etc.)\n// only load when the user navigates to them.\nconst PracticeScreen = lazy(() =>\n import('./screens/PracticeScreen.js').then((m) => ({ default: m.PracticeScreen })),\n);\nconst DictBrowser = lazy(() =>\n import('./screens/DictBrowser.js').then((m) => ({ default: m.DictBrowser })),\n);\nconst ConfigEditor = lazy(() =>\n import('./screens/ConfigEditor.js').then((m) => ({ default: m.ConfigEditor })),\n);\nconst StatsViewer = lazy(() =>\n import('./screens/StatsViewer.js').then((m) => ({ default: m.StatsViewer })),\n);\nconst WordLookup = lazy(() =>\n import('./screens/WordLookup.js').then((m) => ({ default: m.WordLookup })),\n);\nconst HelpScreen = lazy(() =>\n import('./screens/HelpScreen.js').then((m) => ({ default: m.HelpScreen })),\n);\n\nfunction LazyFallback() {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.muted}>…</Text>\n </Box>\n );\n}\n\nexport function App({\n initial,\n initialCfg,\n inline = false,\n}: {\n initial: ScreenFrame;\n initialCfg: Config;\n inline?: boolean;\n}) {\n return (\n <AppStateProvider initialCfg={initialCfg}>\n <LangBridge>\n <RegistryProvider>\n <AudioStatusProvider disabled={!initialCfg.sounds.master}>\n <NavProvider initial={initial}>\n {inline ? <Router inline /> : <Fullscreen><Router /></Fullscreen>}\n </NavProvider>\n </AudioStatusProvider>\n </RegistryProvider>\n </LangBridge>\n </AppStateProvider>\n );\n}\n\nfunction LangBridge({ children }: { children: ReactNode }) {\n const { cfg } = useAppState();\n return <StringsProvider pref={cfg.language}>{children}</StringsProvider>;\n}\n\nfunction screenKey(frame: ScreenFrame): string {\n if (frame.name === 'practice') {\n const p = frame.params;\n return `practice:${p.dictId}:${p.chapterIndex}:${p.mode}:${p.stealth ? 's' : 'n'}`;\n }\n return frame.name;\n}\n\nfunction Router({ inline = false }: { inline?: boolean }) {\n const nav = useNav();\n const { cfg } = useAppState();\n const { exit } = useApp();\n const lastKeyRef = useRef<string | null>(null);\n\n useInput((input, key) => {\n if (key.ctrl && input === 'c') exit();\n });\n\n const frame = nav.current;\n const key = screenKey(frame);\n if (lastKeyRef.current !== key) {\n if (!inline && process.stdout.isTTY) process.stdout.write('\\x1b[2J\\x1b[H');\n lastKeyRef.current = key;\n }\n\n const screen = (() => {\n switch (frame.name) {\n case 'main':\n return <MainMenu cfg={cfg} />;\n case 'practice':\n return <PracticeScreen params={frame.params} />;\n case 'dict':\n return <DictBrowser params={frame.params} />;\n case 'config':\n return <ConfigEditor />;\n case 'stats':\n return <StatsViewer />;\n case 'word':\n return <WordLookup />;\n case 'help':\n return <HelpScreen />;\n }\n })();\n\n return <Suspense fallback={<LazyFallback />}>{screen}</Suspense>;\n}\n","import { useEffect, useState, type ReactNode } from 'react';\nimport { Box, useStdout } from 'ink';\nimport { enterAltScreen, leaveAltScreen } from '../util/altscreen.js';\n\nexport function Fullscreen({ children }: { children: ReactNode }) {\n const { stdout } = useStdout();\n const [size, setSize] = useState(() => ({\n rows: stdout?.rows ?? 24,\n cols: stdout?.columns ?? 80,\n }));\n\n useEffect(() => {\n // enterAltScreen is idempotent — if the command entry already wrote the\n // ENTER sequence pre-render (the normal path for menu / non-stealth\n // practice), this is a no-op. Acts as a safety net for any code path\n // that mounts the App without pre-render alt-screen setup.\n enterAltScreen();\n\n const onResize = () => {\n setSize({ rows: process.stdout.rows ?? 24, cols: process.stdout.columns ?? 80 });\n };\n process.stdout.on('resize', onResize);\n\n return () => {\n process.stdout.off('resize', onResize);\n leaveAltScreen();\n };\n }, []);\n\n return (\n <Box width={size.cols} height={size.rows} flexDirection=\"column\">\n {children}\n </Box>\n );\n}\n","import { useState } from 'react';\nimport { Box, Text, useApp, useInput } from 'ink';\nimport { useNav } from '../nav.js';\nimport { useAudioStatus } from '../audio-context.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { useDictName } from '../registry-context.js';\nimport { PALETTE } from '../components/BigWord.js';\nimport { visibleWidth } from '../../util/text.js';\nimport { truncateName } from '../../util/dict-name.js';\nimport { setPostExitAction } from '../../util/post-exit-action.js';\nimport type { Config } from '../../infra/config-store.js';\n\ntype Item = { key: string; label: string; hint: string; run: () => void };\n\nexport function MainMenu({ cfg }: { cfg: Config }) {\n const [selected, setSelected] = useState(0);\n const { exit } = useApp();\n const nav = useNav();\n const audio = useAudioStatus();\n const t = useStrings();\n const defaultDictName = useDictName(cfg.defaultDict);\n\n const m = t.mainMenu.items;\n const startPractice = (stealth: boolean) => {\n // Stealth needs Ink rendered in inline mode (no alt-screen), but the menu\n // is currently running inside an alt-screen render(). Hand the dict +\n // mode off via a single-shot store and exit Ink; the CLI entry point\n // consumes the action and re-renders inline.\n if (stealth && cfg.defaultDict) {\n setPostExitAction({\n type: 'stealth',\n dictId: cfg.defaultDict,\n chapterIndex: 0,\n mode: cfg.defaultMode,\n });\n exit();\n return;\n }\n if (cfg.defaultDict) {\n nav.navigate({\n name: 'practice',\n params: {\n dictId: cfg.defaultDict,\n chapterIndex: 0,\n mode: cfg.defaultMode,\n stealth,\n },\n });\n } else {\n nav.navigate({ name: 'dict', params: { pickerMode: 'choose-then-practice' } });\n }\n };\n\n const items: Item[] = [\n {\n key: 'p',\n label: m.practiceLabel,\n hint: cfg.defaultDict\n ? m.practiceHintWith(truncateName(defaultDictName, 24))\n : m.practiceHintNone,\n run: () => startPractice(cfg.stealth === 'default'),\n },\n ];\n\n if (cfg.stealth === 'menu' || cfg.stealth === 'default') {\n items.push({\n key: 'b',\n label: m.stealthLabel,\n hint: m.stealthHint,\n run: () => startPractice(true),\n });\n }\n\n items.push(\n { key: 'd', label: m.dictLabel, hint: m.dictHint, run: () => nav.navigate({ name: 'dict' }) },\n { key: 'w', label: m.wordLabel, hint: m.wordHint, run: () => nav.navigate({ name: 'word' }) },\n { key: 's', label: m.statsLabel, hint: m.statsHint, run: () => nav.navigate({ name: 'stats' }) },\n { key: 'c', label: m.configLabel, hint: m.configHint, run: () => nav.navigate({ name: 'config' }) },\n { key: 'q', label: m.quitLabel, hint: m.quitHint, run: () => exit() },\n );\n\n const labelW = Math.max(...items.map((it) => visibleWidth(it.label))) + 4;\n\n useInput((input, key) => {\n if (key.escape) {\n exit();\n return;\n }\n if (key.upArrow) setSelected((i) => (i - 1 + items.length) % items.length);\n if (key.downArrow) setSelected((i) => (i + 1) % items.length);\n if (key.return) {\n items[selected]!.run();\n return;\n }\n if (input === '?') {\n nav.navigate({ name: 'help' });\n return;\n }\n for (const it of items) {\n if (input === it.key) {\n it.run();\n return;\n }\n }\n });\n\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1} width=\"100%\">\n <Box>\n <Text bold color={PALETTE.accent}>\n {t.app.title}\n </Text>\n <Text color={PALETTE.muted}> · {t.app.subtitle}</Text>\n </Box>\n\n <Box marginTop={2} flexDirection=\"column\">\n {items.map((it, i) => {\n const active = i === selected;\n const pad = ' '.repeat(Math.max(0, labelW - visibleWidth(it.label)));\n return (\n <Box key={it.key}>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>{active ? '▌ ' : ' '}</Text>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>[{it.key}]</Text>\n <Text>{' '}</Text>\n <Text bold={active} color={active ? PALETTE.text : PALETTE.muted}>\n {it.label}\n {pad}\n </Text>\n <Text color={PALETTE.muted}>{it.hint}</Text>\n </Box>\n );\n })}\n </Box>\n\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>\n {t.mainMenu.hint}\n {' · '}\n {t.mainMenu.helpHint}\n </Text>\n </Box>\n\n {audio.warning && (\n <Box marginTop={1}>\n <Text color={PALETTE.warning}>{t.audio.noPlayer}</Text>\n </Box>\n )}\n </Box>\n );\n}\n","import chalk from 'chalk';\nimport boxen from 'boxen';\nimport type { SessionReport } from '../infra/session-tracker.js';\nimport type { Strings } from '../i18n/strings.js';\nimport { leaveAltScreen } from './altscreen.js';\nimport { visibleWidth } from './text.js';\n\nexport function ensureMainScreen(): void {\n leaveAltScreen();\n}\n\nfunction fmtDuration(ms: number, lang: 'zh' | 'en'): string {\n const total = Math.floor(ms / 1000);\n const m = Math.floor(total / 60);\n const s = total % 60;\n if (lang === 'zh') {\n if (m === 0) return `${s} 秒`;\n return `${m} 分 ${s} 秒`;\n }\n if (m === 0) return `${s}s`;\n return `${m}m ${s}s`;\n}\n\nfunction accColor(pct: number): (s: string) => string {\n if (pct >= 90) return chalk.green;\n if (pct < 75) return chalk.dim;\n return (s) => s;\n}\n\nfunction buildLines(r: SessionReport, t: Strings, lang: 'zh' | 'en'): string[] {\n const lines: string[] = [];\n\n // Align labels in a single visual column. Use the longest label's\n // visible width as the target (CJK counted at 2 cols by visibleWidth).\n const allLabels: string[] = [t.report.duration];\n if (r.chaptersCompleted === 0) {\n allLabels.push(t.report.notPracticed);\n } else {\n allLabels.push(\n t.report.practiced,\n t.report.chapters,\n t.report.words,\n t.report.accuracy,\n t.report.wpm,\n );\n if (r.newMistakeWords > 0) allLabels.push(t.report.newMistakes);\n }\n const labelW = Math.max(...allLabels.map(visibleWidth));\n const padLabel = (s: string) =>\n s + ' '.repeat(Math.max(0, labelW - visibleWidth(s)));\n const row = (label: string, value: string) =>\n `${chalk.dim(padLabel(label))} ${value}`;\n\n lines.push(row(t.report.duration, fmtDuration(r.totalDurationMs, lang)));\n\n if (r.chaptersCompleted === 0) {\n lines.push(chalk.dim(t.report.notPracticed));\n } else {\n lines.push(row(t.report.practiced, fmtDuration(r.practiceMs, lang)));\n lines.push(row(t.report.chapters, String(r.chaptersCompleted)));\n lines.push(row(t.report.words, String(r.wordCount)));\n const accPct = Math.round(r.accuracy * 1000) / 10;\n lines.push(row(t.report.accuracy, accColor(accPct)(`${accPct}%`)));\n lines.push(row(t.report.wpm, String(r.wpm)));\n if (r.newMistakeWords > 0) {\n lines.push(row(t.report.newMistakes, chalk.red(String(r.newMistakeWords))));\n }\n }\n\n lines.push('');\n lines.push(chalk.dim.italic(t.report.farewell));\n return lines;\n}\n\nexport function printSessionReport(r: SessionReport, t: Strings, lang: 'zh' | 'en'): void {\n if (r.startedAt === null && r.chaptersCompleted === 0) return;\n const content = buildLines(r, t, lang).join('\\n');\n console.log(\n boxen(content, {\n title: chalk.bold.cyan(t.report.title),\n titleAlignment: 'left',\n borderStyle: 'round',\n borderColor: 'gray',\n padding: { top: 1, bottom: 1, left: 3, right: 3 },\n margin: { top: 1, bottom: 1, left: 2, right: 0 },\n }),\n );\n}\n"],"mappings":"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","names":["ENTER","LEAVE","active","signalsRegistered","ensureSignals","cleanup","enterAltScreen","leaveAltScreen","pending","setPostExitAction","action","consumePostExitAction","a","Suspense","lazy","useRef","Box","Text","useApp","useInput","useEffect","useState","Box","useStdout","jsx","Fullscreen","children","stdout","useStdout","size","setSize","useState","useEffect","enterAltScreen","onResize","leaveAltScreen","Box","useState","Box","Text","useApp","useInput","jsx","jsxs","MainMenu","cfg","selected","setSelected","useState","exit","useApp","nav","useNav","audio","useAudioStatus","t","useStrings","defaultDictName","useDictName","m","startPractice","stealth","setPostExitAction","items","truncateName","labelW","it","visibleWidth","useInput","input","key","i","Box","Text","PALETTE","active","pad","jsx","PracticeScreen","lazy","m","DictBrowser","ConfigEditor","StatsViewer","WordLookup","HelpScreen","LazyFallback","Box","Text","PALETTE","App","initial","initialCfg","inline","AppStateProvider","LangBridge","RegistryProvider","AudioStatusProvider","NavProvider","Router","Fullscreen","children","cfg","useAppState","StringsProvider","screenKey","frame","p","nav","useNav","exit","useApp","lastKeyRef","useRef","useInput","input","key","screen","MainMenu","Suspense","chalk","boxen","ensureMainScreen","leaveAltScreen","fmtDuration","ms","lang","total","m","s","accColor","pct","chalk","buildLines","r","t","lines","allLabels","labelW","visibleWidth","padLabel","row","label","value","accPct","printSessionReport","content","boxen"]}
|
package/dist/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Command as
|
|
1
|
+
import{Command as b}from"commander";import{Command as u}from"commander";function r(){let o=new u("config").description("Manage CLI configuration");return o.command("list").description("Show the effective merged config").action(async()=>{let{configList:t}=await import("./config.impl-IYJ4ZUPE.js");await t()}),o.command("get <key>").description("Get a config value by dotted path (e.g. sounds.keystroke)").action(async t=>{let{configGet:a}=await import("./config.impl-IYJ4ZUPE.js");await a(t)}),o.command("set <key> <value>").description("Set a config value by dotted path").action(async(t,a)=>{let{configSet:n}=await import("./config.impl-IYJ4ZUPE.js");await n(t,a)}),o}import{Command as g}from"commander";function e(){let o=new g("dict").description("Manage dictionaries");return o.command("list").description("List dictionaries (\u2713 = locally available)").option("-c, --category <category>","filter by category").option("--local-only","show only locally downloaded dictionaries").action(async t=>{let{dictList:a}=await import("./dict.impl-Y66SRRZL.js");await a(t)}),o.command("search <keyword>").description("Search the upstream registry by name/description/category/tags").option("-c, --category <category>","restrict to category").option("-l, --language <language>","restrict to language").action(async(t,a)=>{let{dictSearch:n}=await import("./dict.impl-Y66SRRZL.js");await n(t,a)}),o.command("pull <id>").description("Download an upstream dictionary into the local cache").action(async t=>{let{dictPull:a}=await import("./dict.impl-Y66SRRZL.js");await a(t)}),o.command("import <file>").description("Import a local qwerty-native JSON dictionary").requiredOption("--id <id>","dictionary id (lowercase, digits, dashes)").action(async(t,a)=>{let{dictImport:n}=await import("./dict.impl-Y66SRRZL.js");await n(t,a)}),o.command("rm <id>").description("Remove a local dictionary").action(async t=>{let{dictRemove:a}=await import("./dict.impl-Y66SRRZL.js");await a(t)}),o}import{Command as y}from"commander";function c(){return new y("doctor").description("Diagnose audio playback and runtime environment").action(async()=>{let{runDoctor:o}=await import("./doctor.impl-5UHLQ4SZ.js");await o()})}import{Command as w}from"commander";function m(){return new w("practice").argument("[dictId]","dictionary id; falls back to config.defaultDict").description("Start a typing practice session").option("-c, --chapter <n>","chapter number (1-based)","1").option("-m, --mode <mode>","order | dictation | review | random | loop").option("--stealth","enter stealth mode (minimal UI, no sound)").action(async(o,t)=>{let{runPractice:a}=await import("./practice.impl-CJLWRT5Z.js");await a(o,t)})}import{Command as f}from"commander";function d(){return new f("stats").description("Show practice history and trends").option("-d, --days <n>","window size for trend (default 14)","14").option("--top <n>","how many top mistakes to show (default 10)","10").action(async o=>{let{runStats:t}=await import("./stats.impl-IXVF3Q5Y.js");await t(o)})}import{Command as C}from"commander";function s(){return new C("word").argument("<keyword>").description("Look up a word across local dictionaries").option("--exact","require exact (case-insensitive) match").action(async(o,t)=>{let{runWordLookup:a}=await import("./word.impl-C4AYZ3NC.js");await a(o,t)})}import{Command as h}from"commander";function p(){return new h("boss").alias("stealth").description("Start practice in stealth mode (minimal UI, looks like plain terminal output)").argument("[dictId]","dictionary id; falls back to config.defaultDict").option("-c, --chapter <n>","chapter number (1-based)","1").option("-m, --mode <mode>","order | dictation | review | random | loop").action(async(o,t)=>{let{runPractice:a}=await import("./practice.impl-CJLWRT5Z.js");await a(o,{...t,stealth:!0})})}async function l(){if(!process.stdout.isTTY){console.log("qwerty-cli \u2014 run `qwerty --help` for available commands.");return}let{runMainMenuImpl:o}=await import("./menu.impl-PUAAZGHA.js");await o()}var i=new b;i.name("qwerty").description("Terminal clone of qwerty-learner \u2014 typing practice for English vocabulary").version("0.0.1-alpha.12");i.addCommand(m());i.addCommand(p());i.addCommand(e());i.addCommand(s());i.addCommand(d());i.addCommand(r());i.addCommand(c());i.action(async()=>{await l()});i.parseAsync(process.argv).catch(o=>{console.error(o instanceof Error?o.message:o),process.exit(1)});
|
|
2
2
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/commands/config.ts","../src/commands/dict.ts","../src/commands/practice.ts","../src/commands/stats.ts","../src/commands/word.ts","../src/commands/stealth.ts","../src/commands/menu.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { buildConfigCommand } from './commands/config.js';\nimport { buildDictCommand } from './commands/dict.js';\nimport { buildPracticeCommand } from './commands/practice.js';\nimport { buildStatsCommand } from './commands/stats.js';\nimport { buildWordCommand } from './commands/word.js';\nimport { buildStealthCommand } from './commands/stealth.js';\nimport { runMainMenu } from './commands/menu.js';\n\nconst program = new Command();\n\nprogram\n .name('qwerty')\n .description('Terminal clone of qwerty-learner — typing practice for English vocabulary')\n .version(__APP_VERSION__);\n\nprogram.addCommand(buildPracticeCommand());\nprogram.addCommand(buildStealthCommand());\nprogram.addCommand(buildDictCommand());\nprogram.addCommand(buildWordCommand());\nprogram.addCommand(buildStatsCommand());\nprogram.addCommand(buildConfigCommand());\n\nprogram.action(async () => {\n await runMainMenu();\n});\n\nprogram.parseAsync(process.argv).catch((err) => {\n console.error(err instanceof Error ? err.message : err);\n process.exit(1);\n});\n","import { Command } from 'commander';\n\nexport function buildConfigCommand(): Command {\n const cmd = new Command('config').description('Manage CLI configuration');\n\n cmd\n .command('list')\n .description('Show the effective merged config')\n .action(async () => {\n const { configList } = await import('./config.impl.js');\n await configList();\n });\n\n cmd\n .command('get <key>')\n .description('Get a config value by dotted path (e.g. sounds.keystroke)')\n .action(async (key: string) => {\n const { configGet } = await import('./config.impl.js');\n await configGet(key);\n });\n\n cmd\n .command('set <key> <value>')\n .description('Set a config value by dotted path')\n .action(async (key: string, value: string) => {\n const { configSet } = await import('./config.impl.js');\n await configSet(key, value);\n });\n\n return cmd;\n}\n","import { Command } from 'commander';\n\nexport function buildDictCommand(): Command {\n const cmd = new Command('dict').description('Manage dictionaries');\n\n cmd\n .command('list')\n .description('List dictionaries (✓ = locally available)')\n .option('-c, --category <category>', 'filter by category')\n .option('--local-only', 'show only locally downloaded dictionaries')\n .action(async (opts: { category?: string; localOnly?: boolean }) => {\n const { dictList } = await import('./dict.impl.js');\n await dictList(opts);\n });\n\n cmd\n .command('search <keyword>')\n .description('Search the upstream registry by name/description/category/tags')\n .option('-c, --category <category>', 'restrict to category')\n .option('-l, --language <language>', 'restrict to language')\n .action(async (keyword: string, opts: { category?: string; language?: string }) => {\n const { dictSearch } = await import('./dict.impl.js');\n await dictSearch(keyword, opts);\n });\n\n cmd\n .command('pull <id>')\n .description('Download an upstream dictionary into the local cache')\n .action(async (id: string) => {\n const { dictPull } = await import('./dict.impl.js');\n await dictPull(id);\n });\n\n cmd\n .command('import <file>')\n .description('Import a local qwerty-native JSON dictionary')\n .requiredOption('--id <id>', 'dictionary id (lowercase, digits, dashes)')\n .action(async (file: string, opts: { id: string }) => {\n const { dictImport } = await import('./dict.impl.js');\n await dictImport(file, opts);\n });\n\n cmd\n .command('rm <id>')\n .description('Remove a local dictionary')\n .action(async (id: string) => {\n const { dictRemove } = await import('./dict.impl.js');\n await dictRemove(id);\n });\n\n return cmd;\n}\n","import { Command } from 'commander';\n\nexport function buildPracticeCommand(): Command {\n return new Command('practice')\n .argument('[dictId]', 'dictionary id; falls back to config.defaultDict')\n .description('Start a typing practice session')\n .option('-c, --chapter <n>', 'chapter number (1-based)', '1')\n .option('-m, --mode <mode>', 'order | dictation | review | random | loop')\n .option('--stealth', 'enter stealth mode (minimal UI, no sound)')\n .action(\n async (\n dictIdArg: string | undefined,\n options: { chapter: string; mode?: string; stealth?: boolean },\n ) => {\n const { runPractice } = await import('./practice.impl.js');\n await runPractice(dictIdArg, options);\n },\n );\n}\n","import { Command } from 'commander';\n\nexport function buildStatsCommand(): Command {\n return new Command('stats')\n .description('Show practice history and trends')\n .option('-d, --days <n>', 'window size for trend (default 14)', '14')\n .option('--top <n>', 'how many top mistakes to show (default 10)', '10')\n .action(async (opts: { days: string; top: string }) => {\n const { runStats } = await import('./stats.impl.js');\n await runStats(opts);\n });\n}\n","import { Command } from 'commander';\n\nexport function buildWordCommand(): Command {\n return new Command('word')\n .argument('<keyword>')\n .description('Look up a word across local dictionaries')\n .option('--exact', 'require exact (case-insensitive) match')\n .action(async (keyword: string, opts: { exact?: boolean }) => {\n const { runWordLookup } = await import('./word.impl.js');\n await runWordLookup(keyword, opts);\n });\n}\n","import { Command } from 'commander';\n\nexport function buildStealthCommand(): Command {\n return new Command('boss')\n .alias('stealth')\n .description('Start practice in stealth mode (minimal UI, looks like plain terminal output)')\n .argument('[dictId]', 'dictionary id; falls back to config.defaultDict')\n .option('-c, --chapter <n>', 'chapter number (1-based)', '1')\n .option('-m, --mode <mode>', 'order | dictation | review | random | loop')\n .action(\n async (\n dictIdArg: string | undefined,\n options: { chapter: string; mode?: string },\n ) => {\n const { runPractice } = await import('./practice.impl.js');\n await runPractice(dictIdArg, { ...options, stealth: true });\n },\n );\n}\n","export async function runMainMenu(): Promise<void> {\n if (!process.stdout.isTTY) {\n console.log('qwerty-cli — run `qwerty --help` for available commands.');\n return;\n }\n const { runMainMenuImpl } = await import('./menu.impl.js');\n await runMainMenuImpl();\n}\n"],"mappings":"AAAA,OAAS,WAAAA,MAAe,YCAxB,OAAS,WAAAC,MAAe,YAEjB,SAASC,GAA8B,CAC5C,IAAMC,EAAM,IAAIF,EAAQ,QAAQ,EAAE,YAAY,0BAA0B,EAExE,OAAAE,EACG,QAAQ,MAAM,EACd,YAAY,kCAAkC,EAC9C,OAAO,SAAY,CAClB,GAAM,CAAE,WAAAC,CAAW,EAAI,KAAM,QAAO,2BAAkB,EACtD,MAAMA,EAAW,CACnB,CAAC,EAEHD,EACG,QAAQ,WAAW,EACnB,YAAY,2DAA2D,EACvE,OAAO,MAAOE,GAAgB,CAC7B,GAAM,CAAE,UAAAC,CAAU,EAAI,KAAM,QAAO,2BAAkB,EACrD,MAAMA,EAAUD,CAAG,CACrB,CAAC,EAEHF,EACG,QAAQ,mBAAmB,EAC3B,YAAY,mCAAmC,EAC/C,OAAO,MAAOE,EAAaE,IAAkB,CAC5C,GAAM,CAAE,UAAAC,CAAU,EAAI,KAAM,QAAO,2BAAkB,EACrD,MAAMA,EAAUH,EAAKE,CAAK,CAC5B,CAAC,EAEIJ,CACT,CC9BA,OAAS,WAAAM,MAAe,YAEjB,SAASC,GAA4B,CAC1C,IAAMC,EAAM,IAAIF,EAAQ,MAAM,EAAE,YAAY,qBAAqB,EAEjE,OAAAE,EACG,QAAQ,MAAM,EACd,YAAY,gDAA2C,EACvD,OAAO,4BAA6B,oBAAoB,EACxD,OAAO,eAAgB,2CAA2C,EAClE,OAAO,MAAOC,GAAqD,CAClE,GAAM,CAAE,SAAAC,CAAS,EAAI,KAAM,QAAO,yBAAgB,EAClD,MAAMA,EAASD,CAAI,CACrB,CAAC,EAEHD,EACG,QAAQ,kBAAkB,EAC1B,YAAY,gEAAgE,EAC5E,OAAO,4BAA6B,sBAAsB,EAC1D,OAAO,4BAA6B,sBAAsB,EAC1D,OAAO,MAAOG,EAAiBF,IAAmD,CACjF,GAAM,CAAE,WAAAG,CAAW,EAAI,KAAM,QAAO,yBAAgB,EACpD,MAAMA,EAAWD,EAASF,CAAI,CAChC,CAAC,EAEHD,EACG,QAAQ,WAAW,EACnB,YAAY,sDAAsD,EAClE,OAAO,MAAOK,GAAe,CAC5B,GAAM,CAAE,SAAAC,CAAS,EAAI,KAAM,QAAO,yBAAgB,EAClD,MAAMA,EAASD,CAAE,CACnB,CAAC,EAEHL,EACG,QAAQ,eAAe,EACvB,YAAY,8CAA8C,EAC1D,eAAe,YAAa,2CAA2C,EACvE,OAAO,MAAOO,EAAcN,IAAyB,CACpD,GAAM,CAAE,WAAAO,CAAW,EAAI,KAAM,QAAO,yBAAgB,EACpD,MAAMA,EAAWD,EAAMN,CAAI,CAC7B,CAAC,EAEHD,EACG,QAAQ,SAAS,EACjB,YAAY,2BAA2B,EACvC,OAAO,MAAOK,GAAe,CAC5B,GAAM,CAAE,WAAAI,CAAW,EAAI,KAAM,QAAO,yBAAgB,EACpD,MAAMA,EAAWJ,CAAE,CACrB,CAAC,EAEIL,CACT,CCnDA,OAAS,WAAAU,MAAe,YAEjB,SAASC,GAAgC,CAC9C,OAAO,IAAID,EAAQ,UAAU,EAC1B,SAAS,WAAY,iDAAiD,EACtE,YAAY,iCAAiC,EAC7C,OAAO,oBAAqB,2BAA4B,GAAG,EAC3D,OAAO,oBAAqB,4CAA4C,EACxE,OAAO,YAAa,2CAA2C,EAC/D,OACC,MACEE,EACAC,IACG,CACH,GAAM,CAAE,YAAAC,CAAY,EAAI,KAAM,QAAO,6BAAoB,EACzD,MAAMA,EAAYF,EAAWC,CAAO,CACtC,CACF,CACJ,CClBA,OAAS,WAAAE,MAAe,YAEjB,SAASC,GAA6B,CAC3C,OAAO,IAAID,EAAQ,OAAO,EACvB,YAAY,kCAAkC,EAC9C,OAAO,iBAAkB,qCAAsC,IAAI,EACnE,OAAO,YAAa,6CAA8C,IAAI,EACtE,OAAO,MAAOE,GAAwC,CACrD,GAAM,CAAE,SAAAC,CAAS,EAAI,KAAM,QAAO,0BAAiB,EACnD,MAAMA,EAASD,CAAI,CACrB,CAAC,CACL,CCXA,OAAS,WAAAE,MAAe,YAEjB,SAASC,GAA4B,CAC1C,OAAO,IAAID,EAAQ,MAAM,EACtB,SAAS,WAAW,EACpB,YAAY,0CAA0C,EACtD,OAAO,UAAW,wCAAwC,EAC1D,OAAO,MAAOE,EAAiBC,IAA8B,CAC5D,GAAM,CAAE,cAAAC,CAAc,EAAI,KAAM,QAAO,yBAAgB,EACvD,MAAMA,EAAcF,EAASC,CAAI,CACnC,CAAC,CACL,CCXA,OAAS,WAAAE,MAAe,YAEjB,SAASC,GAA+B,CAC7C,OAAO,IAAID,EAAQ,MAAM,EACtB,MAAM,SAAS,EACf,YAAY,+EAA+E,EAC3F,SAAS,WAAY,iDAAiD,EACtE,OAAO,oBAAqB,2BAA4B,GAAG,EAC3D,OAAO,oBAAqB,4CAA4C,EACxE,OACC,MACEE,EACAC,IACG,CACH,GAAM,CAAE,YAAAC,CAAY,EAAI,KAAM,QAAO,6BAAoB,EACzD,MAAMA,EAAYF,EAAW,CAAE,GAAGC,EAAS,QAAS,EAAK,CAAC,CAC5D,CACF,CACJ,CClBA,eAAsBE,GAA6B,CACjD,GAAI,CAAC,QAAQ,OAAO,MAAO,CACzB,QAAQ,IAAI,+DAA0D,EACtE,MACF,CACA,GAAM,CAAE,gBAAAC,CAAgB,EAAI,KAAM,QAAO,yBAAgB,EACzD,MAAMA,EAAgB,CACxB,CPEA,IAAMC,EAAU,IAAIC,EAEpBD,EACG,KAAK,QAAQ,EACb,YAAY,gFAA2E,EACvF,QAAQ,gBAAe,EAE1BA,EAAQ,WAAWE,EAAqB,CAAC,EACzCF,EAAQ,WAAWG,EAAoB,CAAC,EACxCH,EAAQ,WAAWI,EAAiB,CAAC,EACrCJ,EAAQ,WAAWK,EAAiB,CAAC,EACrCL,EAAQ,WAAWM,EAAkB,CAAC,EACtCN,EAAQ,WAAWO,EAAmB,CAAC,EAEvCP,EAAQ,OAAO,SAAY,CACzB,MAAMQ,EAAY,CACpB,CAAC,EAEDR,EAAQ,WAAW,QAAQ,IAAI,EAAE,MAAOS,GAAQ,CAC9C,QAAQ,MAAMA,aAAe,MAAQA,EAAI,QAAUA,CAAG,EACtD,QAAQ,KAAK,CAAC,CAChB,CAAC","names":["Command","Command","buildConfigCommand","cmd","configList","key","configGet","value","configSet","Command","buildDictCommand","cmd","opts","dictList","keyword","dictSearch","id","dictPull","file","dictImport","dictRemove","Command","buildPracticeCommand","dictIdArg","options","runPractice","Command","buildStatsCommand","opts","runStats","Command","buildWordCommand","keyword","opts","runWordLookup","Command","buildStealthCommand","dictIdArg","options","runPractice","runMainMenu","runMainMenuImpl","program","Command","buildPracticeCommand","buildStealthCommand","buildDictCommand","buildWordCommand","buildStatsCommand","buildConfigCommand","runMainMenu","err"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/config.ts","../src/commands/dict.ts","../src/commands/doctor.ts","../src/commands/practice.ts","../src/commands/stats.ts","../src/commands/word.ts","../src/commands/stealth.ts","../src/commands/menu.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { buildConfigCommand } from './commands/config.js';\nimport { buildDictCommand } from './commands/dict.js';\nimport { buildDoctorCommand } from './commands/doctor.js';\nimport { buildPracticeCommand } from './commands/practice.js';\nimport { buildStatsCommand } from './commands/stats.js';\nimport { buildWordCommand } from './commands/word.js';\nimport { buildStealthCommand } from './commands/stealth.js';\nimport { runMainMenu } from './commands/menu.js';\n\nconst program = new Command();\n\nprogram\n .name('qwerty')\n .description('Terminal clone of qwerty-learner — typing practice for English vocabulary')\n .version(__APP_VERSION__);\n\nprogram.addCommand(buildPracticeCommand());\nprogram.addCommand(buildStealthCommand());\nprogram.addCommand(buildDictCommand());\nprogram.addCommand(buildWordCommand());\nprogram.addCommand(buildStatsCommand());\nprogram.addCommand(buildConfigCommand());\nprogram.addCommand(buildDoctorCommand());\n\nprogram.action(async () => {\n await runMainMenu();\n});\n\nprogram.parseAsync(process.argv).catch((err) => {\n console.error(err instanceof Error ? err.message : err);\n process.exit(1);\n});\n","import { Command } from 'commander';\n\nexport function buildConfigCommand(): Command {\n const cmd = new Command('config').description('Manage CLI configuration');\n\n cmd\n .command('list')\n .description('Show the effective merged config')\n .action(async () => {\n const { configList } = await import('./config.impl.js');\n await configList();\n });\n\n cmd\n .command('get <key>')\n .description('Get a config value by dotted path (e.g. sounds.keystroke)')\n .action(async (key: string) => {\n const { configGet } = await import('./config.impl.js');\n await configGet(key);\n });\n\n cmd\n .command('set <key> <value>')\n .description('Set a config value by dotted path')\n .action(async (key: string, value: string) => {\n const { configSet } = await import('./config.impl.js');\n await configSet(key, value);\n });\n\n return cmd;\n}\n","import { Command } from 'commander';\n\nexport function buildDictCommand(): Command {\n const cmd = new Command('dict').description('Manage dictionaries');\n\n cmd\n .command('list')\n .description('List dictionaries (✓ = locally available)')\n .option('-c, --category <category>', 'filter by category')\n .option('--local-only', 'show only locally downloaded dictionaries')\n .action(async (opts: { category?: string; localOnly?: boolean }) => {\n const { dictList } = await import('./dict.impl.js');\n await dictList(opts);\n });\n\n cmd\n .command('search <keyword>')\n .description('Search the upstream registry by name/description/category/tags')\n .option('-c, --category <category>', 'restrict to category')\n .option('-l, --language <language>', 'restrict to language')\n .action(async (keyword: string, opts: { category?: string; language?: string }) => {\n const { dictSearch } = await import('./dict.impl.js');\n await dictSearch(keyword, opts);\n });\n\n cmd\n .command('pull <id>')\n .description('Download an upstream dictionary into the local cache')\n .action(async (id: string) => {\n const { dictPull } = await import('./dict.impl.js');\n await dictPull(id);\n });\n\n cmd\n .command('import <file>')\n .description('Import a local qwerty-native JSON dictionary')\n .requiredOption('--id <id>', 'dictionary id (lowercase, digits, dashes)')\n .action(async (file: string, opts: { id: string }) => {\n const { dictImport } = await import('./dict.impl.js');\n await dictImport(file, opts);\n });\n\n cmd\n .command('rm <id>')\n .description('Remove a local dictionary')\n .action(async (id: string) => {\n const { dictRemove } = await import('./dict.impl.js');\n await dictRemove(id);\n });\n\n return cmd;\n}\n","import { Command } from 'commander';\n\nexport function buildDoctorCommand(): Command {\n return new Command('doctor')\n .description('Diagnose audio playback and runtime environment')\n .action(async () => {\n const { runDoctor } = await import('./doctor.impl.js');\n await runDoctor();\n });\n}\n","import { Command } from 'commander';\n\nexport function buildPracticeCommand(): Command {\n return new Command('practice')\n .argument('[dictId]', 'dictionary id; falls back to config.defaultDict')\n .description('Start a typing practice session')\n .option('-c, --chapter <n>', 'chapter number (1-based)', '1')\n .option('-m, --mode <mode>', 'order | dictation | review | random | loop')\n .option('--stealth', 'enter stealth mode (minimal UI, no sound)')\n .action(\n async (\n dictIdArg: string | undefined,\n options: { chapter: string; mode?: string; stealth?: boolean },\n ) => {\n const { runPractice } = await import('./practice.impl.js');\n await runPractice(dictIdArg, options);\n },\n );\n}\n","import { Command } from 'commander';\n\nexport function buildStatsCommand(): Command {\n return new Command('stats')\n .description('Show practice history and trends')\n .option('-d, --days <n>', 'window size for trend (default 14)', '14')\n .option('--top <n>', 'how many top mistakes to show (default 10)', '10')\n .action(async (opts: { days: string; top: string }) => {\n const { runStats } = await import('./stats.impl.js');\n await runStats(opts);\n });\n}\n","import { Command } from 'commander';\n\nexport function buildWordCommand(): Command {\n return new Command('word')\n .argument('<keyword>')\n .description('Look up a word across local dictionaries')\n .option('--exact', 'require exact (case-insensitive) match')\n .action(async (keyword: string, opts: { exact?: boolean }) => {\n const { runWordLookup } = await import('./word.impl.js');\n await runWordLookup(keyword, opts);\n });\n}\n","import { Command } from 'commander';\n\nexport function buildStealthCommand(): Command {\n return new Command('boss')\n .alias('stealth')\n .description('Start practice in stealth mode (minimal UI, looks like plain terminal output)')\n .argument('[dictId]', 'dictionary id; falls back to config.defaultDict')\n .option('-c, --chapter <n>', 'chapter number (1-based)', '1')\n .option('-m, --mode <mode>', 'order | dictation | review | random | loop')\n .action(\n async (\n dictIdArg: string | undefined,\n options: { chapter: string; mode?: string },\n ) => {\n const { runPractice } = await import('./practice.impl.js');\n await runPractice(dictIdArg, { ...options, stealth: true });\n },\n );\n}\n","export async function runMainMenu(): Promise<void> {\n if (!process.stdout.isTTY) {\n console.log('qwerty-cli — run `qwerty --help` for available commands.');\n return;\n }\n const { runMainMenuImpl } = await import('./menu.impl.js');\n await runMainMenuImpl();\n}\n"],"mappings":"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","names":["Command","Command","buildConfigCommand","cmd","configList","key","configGet","value","configSet","Command","buildDictCommand","cmd","opts","dictList","keyword","dictSearch","id","dictPull","file","dictImport","dictRemove","Command","buildDoctorCommand","runDoctor","Command","buildPracticeCommand","dictIdArg","options","runPractice","Command","buildStatsCommand","opts","runStats","Command","buildWordCommand","keyword","opts","runWordLookup","Command","buildStealthCommand","dictIdArg","options","runPractice","runMainMenu","runMainMenuImpl","program","Command","buildPracticeCommand","buildStealthCommand","buildDictCommand","buildWordCommand","buildStatsCommand","buildConfigCommand","buildDoctorCommand","runMainMenu","err"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import{a as w,g as u,h as d,i as f}from"./chunk-KBRGNL2D.js";import{c as m}from"./chunk-6KRVNT2S.js";import{spawn as y}from"child_process";import{join as $}from"path";function p(e,o,s=5e3){return new Promise(r=>{let l=Date.now(),i=[],c=!1,a=(n,t)=>{c||(c=!0,r({cmd:e,args:o,exitCode:n,durationMs:Date.now()-l,stderr:Buffer.concat(i).toString("utf8").trim(),spawnError:t}))};try{let n=y(e,o,{stdio:["ignore","pipe","pipe"],windowsHide:!0});n.on("error",t=>a(null,t.message)),n.stdout?.resume(),n.stderr?.on("data",t=>i.push(t)),n.on("exit",t=>a(t)),setTimeout(()=>{try{n.kill()}catch{}a(null,`timeout after ${s}ms`)},s)}catch(n){a(null,n.message)}})}function g(e,o=""){let s=[];if(s.push(`${o}cmd: ${e.cmd} ${e.args.join(" ")}`),s.push(`${o}exit: ${e.exitCode??"null"} duration: ${e.durationMs}ms`),e.spawnError&&s.push(`${o}spawnError: ${e.spawnError}`),e.stderr){let r=e.stderr.length>500?e.stderr.slice(0,500)+"\u2026":e.stderr;s.push(`${o}stderr: ${r.replace(/\n/g,`
|
|
2
|
+
`+o+" ")}`)}return s.join(`
|
|
3
|
+
`)}async function S(){console.log("qwerty doctor \u2014 environment + audio diagnostics"),console.log(""),console.log("Runtime"),console.log(` platform : ${process.platform}`),console.log(` arch : ${process.arch}`),console.log(` node : ${process.version}`);let e=(process.env.PATH??"").slice(0,240);console.log(` PATH(240): ${e}${(process.env.PATH??"").length>240?"\u2026":""}`),console.log(""),console.log("Audio detection"),await w(!1);let o=f();if(console.log(` wav player : ${o.wav?`${o.wav.kind} (${o.wav.cmd})`:"none"}`),console.log(` mp3 player : ${o.mp3?`${o.mp3.kind} (${o.mp3.cmd})`:"none"}`),console.log(` disabled : ${d()}`),console.log(` warning : ${u()??"(none)"}`),console.log(""),process.platform==="win32"){console.log("Windows PowerShell type resolution");let s="Try { [System.Media.SoundPlayer] | Out-Null; 'ok' } Catch { Write-Error $_.Exception.Message; 'fail' }",r=await p("powershell",["-NoProfile","-Command",s]),l=await p("pwsh",["-NoProfile","-Command",s]);console.log(" powershell.exe (5.1):"),console.log(g(r," ")),console.log(" pwsh (7+):"),console.log(g(l," ")),console.log("")}if(console.log("Built-in WAV playback test"),!o.wav)console.log(" no wav player available \u2014 skipping");else{let s=m(),r=["correct.wav","wrong.wav","key-default.wav"];for(let l of r){let i=$(s,"sounds",l),c=o.wav.cmd,a=h(o.wav.kind,i,"wav"),n=await p(c,a,4e3);console.log(` ${l}:`),console.log(g(n," "))}}console.log(""),u()||d()?console.log("Hint: sounds are disabled or degraded. See diagnosis above."):console.log("Audio appears healthy. If you still hear nothing, check OS volume / mute.")}function h(e,o,s){switch(e){case"afplay":return[o];case"ffplay":return["-nodisp","-autoexit","-loglevel","quiet",o];case"mpg123":return["-q",o];case"paplay":return[o];case"aplay":return["-q",o];case"powershell":case"pwsh":{let r=o.replace(/'/g,"''");return s==="wav"?["-NoProfile","-Command",`(New-Object System.Media.SoundPlayer '${r}').PlaySync();`]:["-NoProfile","-ExecutionPolicy","Bypass","-Command",`Add-Type -AssemblyName presentationCore; $p = New-Object System.Windows.Media.MediaPlayer; $p.Open([Uri]::new('${r}')); $p.Play(); Start-Sleep -Milliseconds 3000`]}default:return[o]}}export{S as runDoctor};
|
|
4
|
+
//# sourceMappingURL=doctor.impl-5UHLQ4SZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/doctor.impl.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { join } from 'node:path';\nimport { initAudio, audioWarning, audioDisabled, audioPlayers } from '../infra/audio.js';\nimport { packageAssetsDir } from '../infra/paths.js';\n\ntype ProbeResult = {\n cmd: string;\n args: string[];\n exitCode: number | null;\n durationMs: number;\n stderr: string;\n spawnError?: string;\n};\n\nfunction probe(cmd: string, args: string[], timeoutMs = 5000): Promise<ProbeResult> {\n return new Promise((resolve) => {\n const t0 = Date.now();\n const stderrChunks: Buffer[] = [];\n let done = false;\n const finish = (exitCode: number | null, spawnError?: string) => {\n if (done) return;\n done = true;\n resolve({\n cmd,\n args,\n exitCode,\n durationMs: Date.now() - t0,\n stderr: Buffer.concat(stderrChunks).toString('utf8').trim(),\n spawnError,\n });\n };\n try {\n const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });\n child.on('error', (err) => finish(null, err.message));\n child.stdout?.resume();\n child.stderr?.on('data', (b: Buffer) => stderrChunks.push(b));\n child.on('exit', (code) => finish(code));\n setTimeout(() => {\n try {\n child.kill();\n } catch {\n /* ignore */\n }\n finish(null, `timeout after ${timeoutMs}ms`);\n }, timeoutMs);\n } catch (err) {\n finish(null, (err as Error).message);\n }\n });\n}\n\nfunction fmtResult(r: ProbeResult, prefix = ''): string {\n const lines: string[] = [];\n lines.push(`${prefix}cmd: ${r.cmd} ${r.args.join(' ')}`);\n lines.push(`${prefix}exit: ${r.exitCode ?? 'null'} duration: ${r.durationMs}ms`);\n if (r.spawnError) lines.push(`${prefix}spawnError: ${r.spawnError}`);\n if (r.stderr) {\n const truncated = r.stderr.length > 500 ? r.stderr.slice(0, 500) + '…' : r.stderr;\n lines.push(`${prefix}stderr: ${truncated.replace(/\\n/g, '\\n' + prefix + ' ')}`);\n }\n return lines.join('\\n');\n}\n\nexport async function runDoctor(): Promise<void> {\n console.log('qwerty doctor — environment + audio diagnostics');\n console.log('');\n console.log('Runtime');\n console.log(` platform : ${process.platform}`);\n console.log(` arch : ${process.arch}`);\n console.log(` node : ${process.version}`);\n const PATH = (process.env.PATH ?? '').slice(0, 240);\n console.log(` PATH(240): ${PATH}${(process.env.PATH ?? '').length > 240 ? '…' : ''}`);\n console.log('');\n\n console.log('Audio detection');\n await initAudio(false);\n const players = audioPlayers();\n console.log(` wav player : ${players.wav ? `${players.wav.kind} (${players.wav.cmd})` : 'none'}`);\n console.log(` mp3 player : ${players.mp3 ? `${players.mp3.kind} (${players.mp3.cmd})` : 'none'}`);\n console.log(` disabled : ${audioDisabled()}`);\n console.log(` warning : ${audioWarning() ?? '(none)'}`);\n console.log('');\n\n if (process.platform === 'win32') {\n console.log('Windows PowerShell type resolution');\n const typeProbe = `Try { [System.Media.SoundPlayer] | Out-Null; 'ok' } Catch { Write-Error $_.Exception.Message; 'fail' }`;\n const psResult = await probe('powershell', ['-NoProfile', '-Command', typeProbe]);\n const pwshResult = await probe('pwsh', ['-NoProfile', '-Command', typeProbe]);\n console.log(' powershell.exe (5.1):');\n console.log(fmtResult(psResult, ' '));\n console.log(' pwsh (7+):');\n console.log(fmtResult(pwshResult, ' '));\n console.log('');\n }\n\n console.log('Built-in WAV playback test');\n if (!players.wav) {\n console.log(' no wav player available — skipping');\n } else {\n const assetsDir = packageAssetsDir();\n const samples = ['correct.wav', 'wrong.wav', 'key-default.wav'];\n for (const name of samples) {\n const file = join(assetsDir, 'sounds', name);\n const wavCmd = players.wav.cmd;\n const wavArgs = buildArgsFor(players.wav.kind, file, 'wav');\n const r = await probe(wavCmd, wavArgs, 4000);\n console.log(` ${name}:`);\n console.log(fmtResult(r, ' '));\n }\n }\n console.log('');\n\n if (audioWarning() || audioDisabled()) {\n console.log('Hint: sounds are disabled or degraded. See diagnosis above.');\n } else {\n console.log('Audio appears healthy. If you still hear nothing, check OS volume / mute.');\n }\n}\n\n// Replicates the args builders from audio.ts at the doctor layer so we can\n// invoke a player without going through the runtime. Mirrors CANDIDATES.\nfunction buildArgsFor(kind: string, file: string, fileKind: 'wav' | 'mp3'): string[] {\n switch (kind) {\n case 'afplay':\n return [file];\n case 'ffplay':\n return ['-nodisp', '-autoexit', '-loglevel', 'quiet', file];\n case 'mpg123':\n return ['-q', file];\n case 'paplay':\n return [file];\n case 'aplay':\n return ['-q', file];\n case 'powershell':\n case 'pwsh': {\n const escaped = file.replace(/'/g, \"''\");\n if (fileKind === 'wav') {\n return [\n '-NoProfile',\n '-Command',\n `(New-Object System.Media.SoundPlayer '${escaped}').PlaySync();`,\n ];\n }\n return [\n '-NoProfile',\n '-ExecutionPolicy',\n 'Bypass',\n '-Command',\n `Add-Type -AssemblyName presentationCore; $p = New-Object System.Windows.Media.MediaPlayer; $p.Open([Uri]::new('${escaped}')); $p.Play(); Start-Sleep -Milliseconds 3000`,\n ];\n }\n default:\n return [file];\n }\n}\n"],"mappings":"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","names":["spawn","join","probe","cmd","args","timeoutMs","resolve","t0","stderrChunks","done","finish","exitCode","spawnError","child","spawn","err","b","code","fmtResult","r","prefix","lines","truncated","runDoctor","PATH","initAudio","players","audioPlayers","audioDisabled","audioWarning","typeProbe","psResult","pwshResult","assetsDir","packageAssetsDir","samples","name","file","join","wavCmd","wavArgs","buildArgsFor","kind","fileKind","escaped"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as o,b as n,c as s,d as p,e as c}from"./chunk-ZXMHFRCR.js";import{c as a,e as m}from"./chunk-7RMRK5MO.js";import"./chunk-KBRGNL2D.js";import"./chunk-2GTGXODM.js";import{a as i}from"./chunk-ELWVQGDK.js";import"./chunk-IUFBN3RD.js";import{e}from"./chunk-R6HQWKXU.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{render as g}from"ink";import{createElement as h}from"react";async function y(){o();let r=await i();a();let{waitUntilExit:f}=g(h(s,{initial:{name:"main"},initialCfg:r}),{patchConsole:!1,exitOnCtrlC:!1});await f(),p();let t=n();if(t?.type==="stealth"){let{runPractice:u}=await import("./practice.impl-CJLWRT5Z.js");await u(t.dictId,{chapter:t.chapterIndex+1,mode:t.mode,stealth:!0});return}let{lang:l,t:d}=e(r.language);c(m(),d,l)}export{y as runMainMenuImpl};
|
|
2
|
+
//# sourceMappingURL=menu.impl-PUAAZGHA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/menu.impl.ts"],"sourcesContent":["import { render } from 'ink';\nimport { createElement } from 'react';\nimport { loadConfig } from '../infra/config-store.js';\nimport { App } from '../ui/App.js';\nimport { start as startSession, report as sessionReport } from '../infra/session-tracker.js';\nimport { printSessionReport, ensureMainScreen } from '../util/report.js';\nimport { pickStrings } from '../i18n/context.js';\nimport { enterAltScreen } from '../util/altscreen.js';\nimport { consumePostExitAction } from '../util/post-exit-action.js';\n\nexport async function runMainMenuImpl(): Promise<void> {\n // Enter alt-screen synchronously BEFORE render() so Ink's first frame\n // never lands on the main screen. Without this, the menu leaks under\n // the exit report when the user quits.\n enterAltScreen();\n\n const cfg = await loadConfig();\n startSession();\n const { waitUntilExit } = render(\n createElement(App, { initial: { name: 'main' }, initialCfg: cfg }),\n { patchConsole: false, exitOnCtrlC: false },\n );\n await waitUntilExit();\n ensureMainScreen();\n\n // Handoff: if the menu requested stealth practice, leave alt-screen (done)\n // and re-enter via runPractice() in inline mode so the 3 rows render under\n // the real shell scrollback. runPractice prints its own report at the end.\n const pending = consumePostExitAction();\n if (pending?.type === 'stealth') {\n const { runPractice } = await import('./practice.impl.js');\n await runPractice(pending.dictId, {\n chapter: pending.chapterIndex + 1,\n mode: pending.mode,\n stealth: true,\n });\n return;\n }\n\n const { lang, t } = pickStrings(cfg.language);\n printSessionReport(sessionReport(), t, lang);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/commands/menu.impl.ts"],"sourcesContent":["import { render } from 'ink';\nimport { createElement } from 'react';\nimport { loadConfig } from '../infra/config-store.js';\nimport { App } from '../ui/App.js';\nimport { start as startSession, report as sessionReport } from '../infra/session-tracker.js';\nimport { printSessionReport, ensureMainScreen } from '../util/report.js';\nimport { pickStrings } from '../i18n/context.js';\nimport { enterAltScreen } from '../util/altscreen.js';\nimport { consumePostExitAction } from '../util/post-exit-action.js';\n\nexport async function runMainMenuImpl(): Promise<void> {\n // Enter alt-screen synchronously BEFORE render() so Ink's first frame\n // never lands on the main screen. Without this, the menu leaks under\n // the exit report when the user quits.\n enterAltScreen();\n\n const cfg = await loadConfig();\n startSession();\n const { waitUntilExit } = render(\n createElement(App, { initial: { name: 'main' }, initialCfg: cfg }),\n { patchConsole: false, exitOnCtrlC: false },\n );\n await waitUntilExit();\n ensureMainScreen();\n\n // Handoff: if the menu requested stealth practice, leave alt-screen (done)\n // and re-enter via runPractice() in inline mode so the 3 rows render under\n // the real shell scrollback. runPractice prints its own report at the end.\n const pending = consumePostExitAction();\n if (pending?.type === 'stealth') {\n const { runPractice } = await import('./practice.impl.js');\n await runPractice(pending.dictId, {\n chapter: pending.chapterIndex + 1,\n mode: pending.mode,\n stealth: true,\n });\n return;\n }\n\n const { lang, t } = pickStrings(cfg.language);\n printSessionReport(sessionReport(), t, lang);\n}\n"],"mappings":"0UAAA,OAAS,UAAAA,MAAc,MACvB,OAAS,iBAAAC,MAAqB,QAS9B,eAAsBC,GAAiC,CAIrDC,EAAe,EAEf,IAAMC,EAAM,MAAMC,EAAW,EAC7BC,EAAa,EACb,GAAM,CAAE,cAAAC,CAAc,EAAIC,EACxBC,EAAcC,EAAK,CAAE,QAAS,CAAE,KAAM,MAAO,EAAG,WAAYN,CAAI,CAAC,EACjE,CAAE,aAAc,GAAO,YAAa,EAAM,CAC5C,EACA,MAAMG,EAAc,EACpBI,EAAiB,EAKjB,IAAMC,EAAUC,EAAsB,EACtC,GAAID,GAAS,OAAS,UAAW,CAC/B,GAAM,CAAE,YAAAE,CAAY,EAAI,KAAM,QAAO,6BAAoB,EACzD,MAAMA,EAAYF,EAAQ,OAAQ,CAChC,QAASA,EAAQ,aAAe,EAChC,KAAMA,EAAQ,KACd,QAAS,EACX,CAAC,EACD,MACF,CAEA,GAAM,CAAE,KAAAG,EAAM,EAAAC,CAAE,EAAIC,EAAYb,EAAI,QAAQ,EAC5Cc,EAAmBC,EAAc,EAAGH,EAAGD,CAAI,CAC7C","names":["render","createElement","runMainMenuImpl","enterAltScreen","cfg","loadConfig","start","waitUntilExit","render","createElement","App","ensureMainScreen","pending","consumePostExitAction","runPractice","lang","t","pickStrings","printSessionReport","report"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as c,c as p,d as f,e as u}from"./chunk-ZXMHFRCR.js";import{c as m,e as l}from"./chunk-7RMRK5MO.js";import"./chunk-KBRGNL2D.js";import"./chunk-2GTGXODM.js";import{a}from"./chunk-ELWVQGDK.js";import"./chunk-IUFBN3RD.js";import{e as d}from"./chunk-R6HQWKXU.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import n from"chalk";import{render as S}from"ink";import{createElement as v}from"react";var g=["order","dictation","review","random","loop"];function w(t){return g.includes(t)}async function A(t,o){if(!process.stdout.isTTY){console.error(n.red("Practice requires an interactive TTY.")),process.exitCode=1;return}let e=await a(),s=t??e.defaultDict;if(!s){console.error(n.red("No dictionary specified. Pass an id or set config.defaultDict.")),process.exitCode=1;return}let i=o.mode??e.defaultMode;if(!w(i)){console.error(n.red(`Invalid mode "${i}". Valid: ${g.join(", ")}`)),process.exitCode=1;return}let h=Math.max(0,Number(o.chapter??1)-1),r=o.stealth===!0||e.stealth==="default";r||c(),m();let{waitUntilExit:x}=S(v(p,{initial:{name:"practice",params:{dictId:s,chapterIndex:h,mode:i,stealth:r}},initialCfg:e,inline:r}),{patchConsole:!1,exitOnCtrlC:!1});await x(),r||f();let{lang:C,t:M}=d(e.language);u(l(),M,C)}export{A as runPractice};
|
|
2
|
+
//# sourceMappingURL=practice.impl-CJLWRT5Z.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/practice.impl.ts"],"sourcesContent":["import chalk from 'chalk';\nimport { render } from 'ink';\nimport { createElement } from 'react';\nimport { loadConfig } from '../infra/config-store.js';\nimport { App } from '../ui/App.js';\nimport { start as startSession, report as sessionReport } from '../infra/session-tracker.js';\nimport { printSessionReport, ensureMainScreen } from '../util/report.js';\nimport { pickStrings } from '../i18n/context.js';\nimport { enterAltScreen } from '../util/altscreen.js';\nimport type { Mode } from '../domain/chapters.js';\n\nconst MODES: Mode[] = ['order', 'dictation', 'review', 'random', 'loop'];\n\nfunction isMode(v: string): v is Mode {\n return (MODES as string[]).includes(v);\n}\n\nexport async function runPractice(\n dictIdArg: string | undefined,\n options: { chapter?: string | number; mode?: string; stealth?: boolean },\n): Promise<void> {\n if (!process.stdout.isTTY) {\n console.error(chalk.red('Practice requires an interactive TTY.'));\n process.exitCode = 1;\n return;\n }\n const cfg = await loadConfig();\n const dictId = dictIdArg ?? cfg.defaultDict;\n if (!dictId) {\n console.error(chalk.red('No dictionary specified. Pass an id or set config.defaultDict.'));\n process.exitCode = 1;\n return;\n }\n const mode = options.mode ?? cfg.defaultMode;\n if (!isMode(mode)) {\n console.error(chalk.red(`Invalid mode \"${mode}\". Valid: ${MODES.join(', ')}`));\n process.exitCode = 1;\n return;\n }\n const chapterIndex = Math.max(0, Number(options.chapter ?? 1) - 1);\n const stealth = options.stealth === true || cfg.stealth === 'default';\n\n // Enter alt-screen synchronously BEFORE render() so Ink's first frame\n // never lands on the main screen. Without this, when the user exits,\n // the main screen still has the menu/practice content from frame 0\n // showing under the exit report. Stealth uses inline mode, skip.\n if (!stealth) enterAltScreen();\n\n startSession();\n const { waitUntilExit } = render(\n createElement(App, {\n initial: { name: 'practice', params: { dictId, chapterIndex, mode, stealth } },\n initialCfg: cfg,\n inline: stealth,\n }),\n { patchConsole: false, exitOnCtrlC: false },\n );\n await waitUntilExit();\n if (!stealth) {\n ensureMainScreen();\n }\n // Stealth: leave the 3 inline rows in scrollback so the practice \"stacks\"\n // under the previous shell session — that's the whole point of the mode.\n const { lang, t } = pickStrings(cfg.language);\n printSessionReport(sessionReport(), t, lang);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/commands/practice.impl.ts"],"sourcesContent":["import chalk from 'chalk';\nimport { render } from 'ink';\nimport { createElement } from 'react';\nimport { loadConfig } from '../infra/config-store.js';\nimport { App } from '../ui/App.js';\nimport { start as startSession, report as sessionReport } from '../infra/session-tracker.js';\nimport { printSessionReport, ensureMainScreen } from '../util/report.js';\nimport { pickStrings } from '../i18n/context.js';\nimport { enterAltScreen } from '../util/altscreen.js';\nimport type { Mode } from '../domain/chapters.js';\n\nconst MODES: Mode[] = ['order', 'dictation', 'review', 'random', 'loop'];\n\nfunction isMode(v: string): v is Mode {\n return (MODES as string[]).includes(v);\n}\n\nexport async function runPractice(\n dictIdArg: string | undefined,\n options: { chapter?: string | number; mode?: string; stealth?: boolean },\n): Promise<void> {\n if (!process.stdout.isTTY) {\n console.error(chalk.red('Practice requires an interactive TTY.'));\n process.exitCode = 1;\n return;\n }\n const cfg = await loadConfig();\n const dictId = dictIdArg ?? cfg.defaultDict;\n if (!dictId) {\n console.error(chalk.red('No dictionary specified. Pass an id or set config.defaultDict.'));\n process.exitCode = 1;\n return;\n }\n const mode = options.mode ?? cfg.defaultMode;\n if (!isMode(mode)) {\n console.error(chalk.red(`Invalid mode \"${mode}\". Valid: ${MODES.join(', ')}`));\n process.exitCode = 1;\n return;\n }\n const chapterIndex = Math.max(0, Number(options.chapter ?? 1) - 1);\n const stealth = options.stealth === true || cfg.stealth === 'default';\n\n // Enter alt-screen synchronously BEFORE render() so Ink's first frame\n // never lands on the main screen. Without this, when the user exits,\n // the main screen still has the menu/practice content from frame 0\n // showing under the exit report. Stealth uses inline mode, skip.\n if (!stealth) enterAltScreen();\n\n startSession();\n const { waitUntilExit } = render(\n createElement(App, {\n initial: { name: 'practice', params: { dictId, chapterIndex, mode, stealth } },\n initialCfg: cfg,\n inline: stealth,\n }),\n { patchConsole: false, exitOnCtrlC: false },\n );\n await waitUntilExit();\n if (!stealth) {\n ensureMainScreen();\n }\n // Stealth: leave the 3 inline rows in scrollback so the practice \"stacks\"\n // under the previous shell session — that's the whole point of the mode.\n const { lang, t } = pickStrings(cfg.language);\n printSessionReport(sessionReport(), t, lang);\n}\n"],"mappings":"mUAAA,OAAOA,MAAW,QAClB,OAAS,UAAAC,MAAc,MACvB,OAAS,iBAAAC,MAAqB,QAS9B,IAAMC,EAAgB,CAAC,QAAS,YAAa,SAAU,SAAU,MAAM,EAEvE,SAASC,EAAOC,EAAsB,CACpC,OAAQF,EAAmB,SAASE,CAAC,CACvC,CAEA,eAAsBC,EACpBC,EACAC,EACe,CACf,GAAI,CAAC,QAAQ,OAAO,MAAO,CACzB,QAAQ,MAAMC,EAAM,IAAI,uCAAuC,CAAC,EAChE,QAAQ,SAAW,EACnB,MACF,CACA,IAAMC,EAAM,MAAMC,EAAW,EACvBC,EAASL,GAAaG,EAAI,YAChC,GAAI,CAACE,EAAQ,CACX,QAAQ,MAAMH,EAAM,IAAI,gEAAgE,CAAC,EACzF,QAAQ,SAAW,EACnB,MACF,CACA,IAAMI,EAAOL,EAAQ,MAAQE,EAAI,YACjC,GAAI,CAACN,EAAOS,CAAI,EAAG,CACjB,QAAQ,MAAMJ,EAAM,IAAI,iBAAiBI,CAAI,aAAaV,EAAM,KAAK,IAAI,CAAC,EAAE,CAAC,EAC7E,QAAQ,SAAW,EACnB,MACF,CACA,IAAMW,EAAe,KAAK,IAAI,EAAG,OAAON,EAAQ,SAAW,CAAC,EAAI,CAAC,EAC3DO,EAAUP,EAAQ,UAAY,IAAQE,EAAI,UAAY,UAMvDK,GAASC,EAAe,EAE7BC,EAAa,EACb,GAAM,CAAE,cAAAC,CAAc,EAAIC,EACxBC,EAAcC,EAAK,CACjB,QAAS,CAAE,KAAM,WAAY,OAAQ,CAAE,OAAAT,EAAQ,aAAAE,EAAc,KAAAD,EAAM,QAAAE,CAAQ,CAAE,EAC7E,WAAYL,EACZ,OAAQK,CACV,CAAC,EACD,CAAE,aAAc,GAAO,YAAa,EAAM,CAC5C,EACA,MAAMG,EAAc,EACfH,GACHO,EAAiB,EAInB,GAAM,CAAE,KAAAC,EAAM,EAAAC,CAAE,EAAIC,EAAYf,EAAI,QAAQ,EAC5CgB,EAAmBC,EAAc,EAAGH,EAAGD,CAAI,CAC7C","names":["chalk","render","createElement","MODES","isMode","v","runPractice","dictIdArg","options","chalk","cfg","loadConfig","dictId","mode","chapterIndex","stealth","enterAltScreen","start","waitUntilExit","render","createElement","App","ensureMainScreen","lang","t","pickStrings","printSessionReport","report"]}
|
package/package.json
CHANGED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a as st,b as at,c as ut,d as lt,e as dt,f as mt,i as bt}from"./chunk-WE6IV5XB.js";import{b as Y}from"./chunk-2GTGXODM.js";import{a as xt}from"./chunk-MPE25TTQ.js";import{e as it}from"./chunk-TP77EGJ2.js";import"./chunk-ELWVQGDK.js";import{a as V,b as ht,c as gt}from"./chunk-UPA4JFCH.js";import{b as ft,e as L}from"./chunk-IUFBN3RD.js";import{b as X,d as M,f as i,g as pt}from"./chunk-SSDQJ6MT.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useState as j,useEffect as H,useRef as Z}from"react";import{Box as f,Text as g,useApp as ie,useInput as K}from"ink";function yt(t,e=Math.random){let r=[...t];for(let n=r.length-1;n>0;n--){let c=Math.floor(e()*(n+1)),l=r[n];r[n]=r[c],r[c]=l}return r}function wt(t){let e=t>>>0;return()=>{e=e+1831565813>>>0;let r=Math.imul(e^e>>>15,1|e);return r=r+Math.imul(r^r>>>7,61|r)^r,((r^r>>>14)>>>0)/4294967296}}function Tt(t,e){if(e<=0)throw new Error("chapterSize must be positive");let r=[];for(let n=0;n<t.length;n+=e)r.push(t.slice(n,n+e));return r}function It(t,e,r){if(e==="random"){let n=r===void 0?Math.random:wt(r);return yt(t,n)}return t}function _(t){return{target:t,typed:"",errorsThisWord:0}}function St(t,e){switch(e.type){case"reset":return{state:{...t,typed:""},effect:"none"};case"backspace":return t.typed.length===0?{state:t,effect:"none"}:{state:{...t,typed:t.typed.slice(0,-1)},effect:"none"};case"char":{let r=t.typed+e.ch,n=[...t.target].slice(0,[...r].length).join("");return r===n?r.length===t.target.length?{state:{...t,typed:r},effect:"correct"}:{state:{...t,typed:r},effect:"progress"}:{state:{...t,typed:"",errorsThisWord:t.errorsThisWord+1},effect:"wrong"}}}}function q(t,e=Date.now()){return t.length===0?{startedAt:e,results:[],current:null,finishedAt:e,playlist:t}:{startedAt:e,results:[],current:{wordIndex:0,wordStartedAt:e,input:_(t[0].name)},finishedAt:null,playlist:t}}function J(t,e,r=Date.now()){if(!t.current)return{session:t,effect:"none"};let{state:n,effect:c}=St(t.current.input,e);if(c==="correct"){let l={word:n.target,errors:n.errorsThisWord,durationMs:r-t.current.wordStartedAt},s=t.current.wordIndex+1,a=[...t.results,l];return s>=t.playlist.length?{session:{...t,results:a,current:null,finishedAt:r},effect:c}:{session:{...t,results:a,current:{wordIndex:s,wordStartedAt:r,input:_(t.playlist[s].name)}},effect:c}}return{session:{...t,current:{...t.current,input:n}},effect:c}}function kt(t,e=Date.now()){if(!t.current)return{session:t,effect:"none"};let r={word:t.current.input.target,errors:0,durationMs:e-t.current.wordStartedAt,skipped:!0},n=t.current.wordIndex+1,c=[...t.results,r];return n>=t.playlist.length?{session:{...t,results:c,current:null,finishedAt:e},effect:"skipped"}:{session:{...t,results:c,current:{wordIndex:n,wordStartedAt:e,input:_(t.playlist[n].name)}},effect:"skipped"}}function Q(t){let e=t.results.reduce((c,l)=>c+l.errors,0),r=(t.finishedAt??Date.now())-t.startedAt,n={};for(let c of t.results)c.errors>0&&(n[c.word]=(n[c.word]??0)+c.errors);return{wordCount:t.results.length,errors:e,durationMs:r,perWordErrors:n}}import{useEffect as Ct,useReducer as Ut,useRef as Xt,useState as Yt}from"react";import{useInput as qt,useApp as Jt}from"ink";function Qt(t,e){if(e.type==="start")return{session:q(e.playlist,e.now),lastEffect:null};if(e.type==="skip"){let r=kt(t.session,e.now);return{session:r.session,lastEffect:r.effect}}if(e.type==="event"){if(e.key.backspace||e.key.delete){let c=J(t.session,{type:"backspace"},e.now);return{session:c.session,lastEffect:c.effect}}if(e.input.length===0)return t;let r=t.session,n=t.lastEffect;for(let c of e.input){let l=J(r,{type:"char",ch:c},e.now);if(r=l.session,n=l.effect,r.finishedAt!==null)break}return{session:r,lastEffect:n}}return t}function Zt(t){let e=[...t];if(e.some(n=>n.codePointAt(0)>=128))return{kind:"ime",cleaned:""};let r=e.filter(n=>{let c=n.codePointAt(0);return c>=32&&c<=126}).join("");return{kind:r.length>0?"valid":"noise",cleaned:r}}function Mt({playlist:t,onComplete:e,onTab:r,onEscape:n,onSkip:c,onImeBlock:l,onValidInput:s,enabled:a=!0}){let[d,b]=Ut(Qt,void 0,()=>({session:q(t,Date.now()),lastEffect:null})),v=Xt(!1),[S,I]=Yt(0),{exit:w}=Jt();return qt((p,x)=>{if(x.ctrl&&p==="c"){w();return}if(x.ctrl&&p==="n"){c?.(),b({type:"skip",now:Date.now()});return}if(x.escape){n?.();return}if(x.tab){r?.();return}if(x.upArrow||x.downArrow||x.leftArrow||x.rightArrow||x.return||x.ctrl||x.meta)return;let{kind:A,cleaned:R}=Zt(p);if(A==="ime"){l?.();return}A!=="noise"&&(s?.(),b({type:"event",input:R,key:x,now:Date.now()}))},{isActive:a}),Ct(()=>{d.session.finishedAt!==null&&!v.current&&(v.current=!0,e(d.session))},[d.session,e]),Ct(()=>{if(d.session.finishedAt!==null)return;let p=setInterval(()=>I(x=>x+1),1e3);return()=>clearInterval(p)},[d.session.finishedAt]),{session:d.session,lastEffect:d.lastEffect,tick:S}}import{useEffect as te,useRef as ee}from"react";function vt(t){let e=ee(!1);return te(()=>{e.current||(e.current=!0,st(!t.enabled).catch(()=>{}))},[t.enabled]),{keystroke:()=>t.enabled&&at(),correct:()=>t.enabled&&ut(),wrong:()=>t.enabled&<(),pronounce:r=>{t.enabled&&t.autoplayPronunciation&&dt(r,t.accent)},prefetch:r=>{t.enabled&&mt(r,t.accent)}}}import{useCallback as re}from"react";function Et(t){return re(async e=>{let r={ts:new Date().toISOString(),dictId:t.dictId,chapter:t.chapterIndex,mode:t.mode,wordCount:e.wordCount,errors:e.errors,durationMs:e.durationMs,perWordErrors:e.perWordErrors};await xt(r),bt({dictId:t.dictId,chapterIndex:t.chapterIndex,mode:t.mode,wordCount:e.wordCount,errors:e.errors,durationMs:e.durationMs,perWordErrors:e.perWordErrors});let n=Object.entries(e.perWordErrors).filter(([,l])=>l>0);if(n.length===0)return;let c=await V();for(let[l,s]of n)c=gt(c,l,t.dictId,s);await ht(c)},[t.dictId,t.chapterIndex,t.mode])}import{Box as N,Text as m,useStdout as ne}from"ink";import{Fragment as ce,jsx as u,jsxs as B}from"react/jsx-runtime";var Wt=28;function Bt(t){let e=Math.floor(t/1e3),r=Math.floor(e/60),n=e%60;return`${r}:${String(n).padStart(2,"0")}`}function oe(){let{stdout:t}=ne(),e=t?.columns??80;return Math.max(20,e-Wt)}function W({left:t,right:e}){let r=oe();return B(N,{children:[u(N,{width:r,children:t}),u(N,{width:Wt,justifyContent:"flex-end",children:e})]})}function At(t){let e=M(),r=[...t.target],n=[...t.typed],c=B(N,{children:[r.map((S,I)=>{let w=I<n.length,p=t.hideTarget&&!w?"_":w?n[I]:S,x=t.error?i.error:w?i.accent:i.muted;return u(m,{bold:!0,color:x,children:p},I)}),t.phonetic&&B(ce,{children:[u(m,{children:" "}),u(m,{italic:!0,dimColor:!0,color:i.muted,children:t.phonetic})]})]}),l=t.translation.length>0?u(m,{color:i.primary,children:t.translation[0]}):u(m,{children:" "}),s=t.info,a=Number.isInteger(s.accPct)?`${s.accPct}`:s.accPct.toFixed(1),d=t.imeBlocked?u(m,{color:i.warning,children:e.practice.imeWarningShort}):s.visible?u(m,{color:i.muted,children:`${s.dictName} \xB7 ${s.chapterLabel}`}):u(m,{children:" "}),b=t.imeBlocked?u(m,{children:" "}):s.visible?u(m,{color:i.muted,children:`${s.completed}/${s.total} \xB7 ${s.wpm}wpm \xB7 ${a}%`}):u(m,{children:" "}),v=t.imeBlocked?u(m,{children:" "}):s.visible?u(m,{color:i.muted,children:Bt(s.elapsedMs)}):u(m,{children:" "});return B(N,{flexDirection:"column",children:[u(W,{left:c,right:d}),u(W,{left:u(m,{children:" "}),right:b}),u(W,{left:l,right:v})]})}function Pt(){let t=M();return B(N,{flexDirection:"column",children:[u(W,{left:u(m,{color:i.warning,children:t.stealth.paused}),right:u(m,{color:i.muted,children:t.stealth.pausedHintRight})}),u(W,{left:u(m,{children:" "}),right:B(m,{color:i.muted,children:["Esc ",t.common.back]})}),u(W,{left:u(m,{children:" "}),right:u(m,{children:" "})})]})}function $t(t){let e=M(),r=Number.isInteger(t.accPct)?`${t.accPct}`:t.accPct.toFixed(1),n=`${e.stealth.chapterDone} \xB7 ${t.wordCount}w \xB7 ${t.wpm}wpm \xB7 ${r}% \xB7 ${Bt(t.durationMs)}`;return B(N,{flexDirection:"column",children:[u(W,{left:u(m,{color:i.success,children:n}),right:u(m,{color:i.muted,children:e.stealth.nextHintRight})}),u(W,{left:u(m,{children:" "}),right:B(m,{color:i.muted,children:["Esc ",e.common.back]})}),u(W,{left:u(m,{children:" "}),right:u(m,{children:" "})})]})}import{jsx as o,jsxs as C}from"react/jsx-runtime";function tr({params:t}){let{dictId:e,chapterIndex:r,mode:n}=t,{cfg:c}=Y(),l=M(),[s,a]=j("loading"),[d,b]=j(null),[v,S]=j(null);return H(()=>{let I=!1;return a("loading"),b(null),S(null),(async()=>{try{let w=await it(e);if(I)return;if(n==="review"){let R=await V();if(I)return;let O=w.filter(F=>R[F.name]?.count).slice(0,c.chapterSize);if(O.length===0){S(l.practice.errors.noMistakes),a("error");return}b({playlist:O,totalChapters:1}),a("typing");return}let p=Tt(w,c.chapterSize);if(p.length===0){S(l.practice.errors.dictEmpty(e)),a("error");return}let x=Math.max(0,Math.min(p.length-1,r)),A=It(p[x],n);b({playlist:A,totalChapters:p.length}),a("typing")}catch(w){if(I)return;S(w.message),a("error")}})(),()=>{I=!0}},[e,r,n,c.chapterSize,l]),s==="loading"?o(fe,{text:l.practice.loading,color:i.muted}):s==="error"?o(de,{msg:v??l.practice.errors.unknown}):d?o(se,{params:t,loaded:d,phase:s,setPhase:a},`${e}-${r}-${n}-${t.stealth?"s":"n"}`):null}function se({params:t,loaded:e,phase:r,setPhase:n}){let{dictId:c,chapterIndex:l,mode:s}=t,a=t.stealth===!0,{cfg:d}=Y(),b=X(),{exit:v}=ie(),S=()=>b.stack.length>1?b.back():v(),I=Et({dictId:c,chapterIndex:l,mode:s}),w=ft(c),p=vt({enabled:!a&&d.sounds.master,accent:d.accent,autoplayPronunciation:!a&&d.autoplayPronunciation}),x=Z(!1),A=Z(null),R=Z(-1),[O,F]=j(!1),[et,Dt]=j(null),[rt,nt]=j(!1);H(()=>{if(et===null)return;let h=setTimeout(()=>F(!1),2e3);return()=>clearTimeout(h)},[et]);let{session:T,lastEffect:E,tick:Lt}=Mt({playlist:e.playlist,enabled:r==="typing",onComplete:h=>{x.current||(x.current=!0,n("summary"),Promise.resolve(I(Q(h))).catch(y=>{console.error("Failed to persist session:",y)}))},onEscape:()=>n(r==="paused"?"typing":"paused"),onTab:a?void 0:()=>{let h=T.current?e.playlist[T.current.wordIndex]:void 0;h&&p.pronounce(h.name)},onImeBlock:()=>nt(!0),onValidInput:()=>nt(!1)});H(()=>{a||E!==null&&E!==A.current&&(A.current=E,E==="wrong"&&d.sounds.feedback&&p.wrong(),E==="progress"&&d.sounds.keystroke&&p.keystroke(),E==="correct"&&(d.sounds.feedback&&p.correct(),d.sounds.keystroke&&p.keystroke()))},[a,E,p,d.sounds.feedback,d.sounds.keystroke]),H(()=>{if(a)return;let h=T.current?.wordIndex??-1;if(h===-1||h===R.current)return;R.current=h;let y=e.playlist[h],$=e.playlist[h+1];y&&d.autoplayPronunciation&&p.pronounce(y.name),$&&p.prefetch($.name)},[a,T.current?.wordIndex,p,d.autoplayPronunciation,e.playlist]),K((h,y)=>{if(y.tab){F(!0),Dt(Date.now());return}},{isActive:a&&r==="typing"}),K((h,y)=>{if(y.return){n("typing");return}if(y.escape){S();return}},{isActive:r==="paused"}),K((h,y)=>{if(y.escape){S();return}if(y.return){let $=l+1;s==="loop"?b.replace({name:"practice",params:{dictId:c,chapterIndex:l,mode:s,stealth:t.stealth}}):s==="review"||$>=e.totalChapters?S():b.replace({name:"practice",params:{dictId:c,chapterIndex:$,mode:s,stealth:t.stealth}});return}if(h==="m"){b.replace({name:"practice",params:{dictId:c,chapterIndex:0,mode:"review",stealth:t.stealth}});return}},{isActive:r==="summary"});let P=T.results.length,jt=T.results.reduce((h,y)=>h+y.errors,0),z=Date.now()-T.startedAt,ot=z/6e4,ct=ot>0?Math.round(P/ot*10)/10:0,k=r==="summary"?Q(T):null;if(a){if(r==="paused")return o(Pt,{});if(r==="summary"&&k){let D=k.durationMs/6e4,Gt=D>0?Math.round(k.wordCount/D*10)/10:0,Ht=Object.keys(k.perWordErrors).length,Kt=k.wordCount===0?1:Math.max(0,(k.wordCount-Ht)/k.wordCount),zt=Math.round(Kt*1e3)/10;return o($t,{wordCount:k.wordCount,errors:k.errors,durationMs:k.durationMs,wpm:Gt,accPct:zt})}let h=T.current?e.playlist[T.current.wordIndex]:e.playlist[e.playlist.length-1],y=T.current?.input??{target:"",typed:"",errorsThisWord:0},$=new Set(T.results.filter(D=>D.errors>0).map(D=>D.word)).size,Ft=P===0?1:Math.max(0,(P-$)/P),Vt=Math.round(Ft*1e3)/10,_t=s==="review"?"review":`ch ${l+1}/${e.totalChapters}`;return o(At,{target:h?.name??"",typed:y.typed,hideTarget:s==="dictation",phonetic:Nt(h,d.accent),translation:h?.trans??[],error:E==="wrong",imeBlocked:rt,info:{visible:O,dictName:L(w,24),chapterLabel:_t,completed:P,total:e.playlist.length,wpm:ct,accPct:Vt,elapsedMs:z}})}if(r==="paused")return o(le,{dictName:w,chapterIndex:l,totalChapters:e.totalChapters,mode:s,completed:P,total:e.playlist.length});if(r==="summary"&&k)return o(pe,{dictName:w,chapterIndex:l,totalChapters:e.totalChapters,mode:s,summary:k});let U=T.current?e.playlist[T.current.wordIndex]:e.playlist[e.playlist.length-1],Ot=T.current?.input??{target:"",typed:"",errorsThisWord:0};return o(ae,{dictName:w,chapterIndex:l,totalChapters:e.totalChapters,mode:s,accent:d.accent,completed:P,total:e.playlist.length,errors:jt,wpm:ct,elapsedMs:z,target:U?.name??"",typed:Ot.typed,flashError:E==="wrong",hideTarget:s==="dictation",phonetic:Nt(U,d.accent),translation:U?.trans??[],imeBlocked:rt})}function Nt(t,e){return t?(e==="us"?t.usphone:t.ukphone)??null:null}function tt(t){let e=Math.floor(t/1e3),r=Math.floor(e/60),n=e%60;return`${r}:${String(n).padStart(2,"0")}`}function ae(t){let e=M(),r=t.total===0?0:t.completed/t.total;return C(f,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[o(ue,{dictName:t.dictName,chapterIndex:t.chapterIndex,totalChapters:t.totalChapters,mode:t.mode,accent:t.accent,completed:t.completed,total:t.total,elapsedMs:t.elapsedMs}),C(f,{flexGrow:1,flexDirection:"column",alignItems:"center",justifyContent:"center",children:[o(pt,{target:t.target,typed:t.typed,error:t.flashError,hideTarget:t.hideTarget}),t.phonetic&&o(f,{marginTop:1,children:o(g,{italic:!0,dimColor:!0,color:i.muted,children:t.phonetic})}),t.translation.length>0&&o(f,{marginTop:1,flexDirection:"column",alignItems:"center",children:t.translation.slice(0,2).map((n,c)=>o(g,{color:i.primary,children:n},c))}),t.imeBlocked&&o(f,{marginTop:1,children:o(g,{color:i.warning,children:e.practice.imeWarning})})]}),C(f,{flexDirection:"column",children:[o(Rt,{frac:r}),o(f,{justifyContent:"center",marginTop:1,children:C(g,{color:i.muted,children:[t.completed,"/",t.total," \xB7 ",tt(t.elapsedMs)," \xB7 ",t.wpm," ",e.practice.statCards.wpm," \xB7 ",t.errors," ",e.practice.statCards.errors]})}),o(f,{justifyContent:"center",marginTop:1,children:o(g,{color:i.muted,children:e.practice.footers.typing})})]})]})}function ue(t){let e=M(),r=e.practice.modes[t.mode],n=e.practice.accents[t.accent],c=L(t.dictName,20),l=t.mode==="review"?`${c} \xB7 ${e.practice.reviewLabel} \xB7 ${n}`:`${c} \xB7 ${e.practice.chapterLabel(t.chapterIndex+1,t.totalChapters)} \xB7 ${r} \xB7 ${n}`,s=`${t.completed}/${t.total} \xB7 ${tt(t.elapsedMs)}`;return C(f,{children:[o(g,{color:i.muted,children:l}),o(f,{flexGrow:1}),o(g,{color:i.muted,children:s})]})}function Rt({frac:t}){let e=process.stdout.columns??80,r=Math.max(20,Math.min(72,e-16)),n=Math.round(r*Math.max(0,Math.min(1,t))),c=r-n;return C(f,{justifyContent:"center",children:[o(g,{color:i.accent,children:"\u2501".repeat(n)}),o(g,{color:i.muted,children:"\u2500".repeat(c)})]})}function le(t){let e=M(),r=t.total===0?0:t.completed/t.total,n=t.mode==="review"?`${L(t.dictName,20)} \xB7 ${e.practice.reviewLabel}`:`${L(t.dictName,20)} \xB7 ${e.practice.pause.chapter(t.chapterIndex+1,t.totalChapters)}`;return C(f,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[o(g,{bold:!0,color:i.warning,children:e.practice.pause.title}),o(f,{marginTop:1,children:o(g,{color:i.muted,children:n})}),o(f,{marginTop:2,children:o(Rt,{frac:r})}),o(f,{marginTop:1,children:o(g,{color:i.muted,children:e.practice.pause.progress(t.completed,t.total)})}),o(f,{marginTop:2,children:o(g,{color:i.muted,children:e.practice.pause.hint})})]})}function de({msg:t}){let e=M();return C(f,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[o(g,{color:i.error,children:t}),o(f,{marginTop:2,children:C(g,{color:i.muted,children:["Esc ",e.common.back]})}),o(me,{})]})}function me(){let t=X();return K((e,r)=>{r.escape&&t.back()}),null}function fe({text:t,color:e}){return o(f,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:o(g,{color:e,children:t})})}function pe(t){let{summary:e}=t,r=e.durationMs/6e4,n=r>0?Math.round(e.wordCount/r*10)/10:0,c=Object.keys(e.perWordErrors).length,l=e.wordCount===0?1:Math.max(0,(e.wordCount-c)/e.wordCount),s=Math.round(l*1e3)/10,a=M(),d=a.practice.modes[t.mode],b=L(t.dictName,20),v=t.mode==="review"?`${b} \xB7 ${a.practice.reviewLabel}`:`${b} \xB7 ${a.practice.chapterLabel(t.chapterIndex+1,t.totalChapters)} \xB7 ${d}`,I=`Enter ${t.mode==="loop"?a.practice.summary.loopAgain:t.mode==="review"||t.chapterIndex+1>=t.totalChapters?a.practice.summary.backMenu:a.practice.summary.nextChapter} \xB7 m ${a.practice.summary.reviewMistakes} \xB7 Esc ${a.practice.summary.backMenu}`;return C(f,{flexDirection:"column",alignItems:"center",justifyContent:"center",paddingY:1,width:"100%",height:"100%",children:[o(g,{bold:!0,color:i.success,children:a.practice.chapterComplete}),o(f,{marginTop:1,children:o(g,{color:i.muted,children:v})}),C(f,{marginTop:3,flexDirection:"row",justifyContent:"center",children:[o(G,{label:a.practice.statCards.words,value:String(e.wordCount),color:i.text}),o(G,{label:a.practice.statCards.errors,value:String(e.errors),color:e.errors>0?i.error:i.muted}),o(G,{label:a.practice.statCards.wpm,value:String(n),color:i.accent}),o(G,{label:a.practice.statCards.accuracy,value:`${s}%`,color:i.accent})]}),o(f,{marginTop:2,children:o(g,{color:i.muted,children:a.practice.statCards.elapsed(tt(e.durationMs))})}),o(f,{flexGrow:1}),o(f,{marginTop:2,children:o(g,{color:i.muted,children:I})})]})}function G({label:t,value:e,color:r}){return C(f,{flexDirection:"column",alignItems:"center",marginX:3,children:[o(g,{bold:!0,color:r,children:e}),o(g,{color:i.muted,children:t})]})}export{tr as PracticeScreen};
|
|
2
|
-
//# sourceMappingURL=PracticeScreen-VLXPLVNW.js.map
|