qwerty-cli 0.0.1-alpha.17 → 0.0.1-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DictBrowser-ZQ773KQV.js +2 -0
- package/dist/DictBrowser-ZQ773KQV.js.map +1 -0
- package/dist/{PracticeScreen-7UGWQ4HP.js → PracticeScreen-PWXXG33U.js} +2 -2
- package/dist/{StatsViewer-SRBXYBNA.js → StatsViewer-XARAMVZW.js} +2 -2
- package/dist/chunk-GULN5HRV.js +2 -0
- package/dist/chunk-GULN5HRV.js.map +1 -0
- package/dist/chunk-TCYEMBFW.js +3 -0
- package/dist/chunk-TCYEMBFW.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/menu.impl-HBP6CFSW.js +2 -0
- package/dist/{menu.impl-BV26PXOO.js.map → menu.impl-HBP6CFSW.js.map} +1 -1
- package/dist/practice.impl-AVTRDL7X.js +2 -0
- package/dist/practice.impl-AVTRDL7X.js.map +1 -0
- package/dist/{stats.impl-3I7BB7X3.js → stats.impl-WX3BFWI3.js} +3 -3
- package/dist/{stats.impl-3I7BB7X3.js.map → stats.impl-WX3BFWI3.js.map} +1 -1
- package/package.json +1 -1
- package/dist/DictBrowser-W5GYDO3B.js +0 -2
- package/dist/DictBrowser-W5GYDO3B.js.map +0 -1
- package/dist/chunk-HJIGZU3E.js +0 -2
- package/dist/chunk-HJIGZU3E.js.map +0 -1
- package/dist/chunk-QL7QPZ6S.js +0 -3
- package/dist/chunk-QL7QPZ6S.js.map +0 -1
- package/dist/menu.impl-BV26PXOO.js +0 -2
- package/dist/practice.impl-HO5G57AA.js +0 -2
- package/dist/practice.impl-HO5G57AA.js.map +0 -1
- /package/dist/{PracticeScreen-7UGWQ4HP.js.map → PracticeScreen-PWXXG33U.js.map} +0 -0
- /package/dist/{StatsViewer-SRBXYBNA.js.map → StatsViewer-XARAMVZW.js.map} +0 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as _,d as H,f as N}from"./chunk-DURXS5MX.js";import{b as z}from"./chunk-CQRKGMPU.js";import"./chunk-7LTZGB7F.js";import{b as K,e as Q}from"./chunk-GULN5HRV.js";import{b as q,d as J,f as e}from"./chunk-FQ2MEK7M.js";import{b as X,c as Y}from"./chunk-NA5UNUVL.js";import"./chunk-E6BBQALJ.js";import{useEffect as st,useMemo as mt,useState as B}from"react";import{Box as l,Text as c,useInput as ut,useStdout as ft}from"ink";import{useState as at}from"react";import{Box as M,Text as I,useInput as dt}from"ink";import{jsx as A,jsxs as U}from"react/jsx-runtime";function G({title:D,items:f,onClose:T}){let g=f.map((s,u)=>s.disabled?-1:u).filter(s=>s>=0),o=g[0]??0,[P,v]=at(o);dt((s,u)=>{if(u.escape){T();return}if(u.upArrow){let a=g.indexOf(P),d=g[(a-1+g.length)%g.length];d!==void 0&&v(d);return}if(u.downArrow){let a=g.indexOf(P),d=g[(a+1)%g.length];d!==void 0&&v(d);return}if(u.return){let a=f[P];a&&!a.disabled&&a.run();return}for(let a=0;a<f.length;a++){let d=f[a];if(!d.disabled&&d.key&&s===d.key){d.run();return}}});let S=Math.max(...f.map(s=>s.label.length)),L=Math.max(S+8,D.length+4,24);return U(M,{flexDirection:"column",borderStyle:"round",borderColor:e.accent,paddingX:2,paddingY:1,width:L,children:[A(M,{marginBottom:1,children:A(I,{bold:!0,color:e.accent,children:D})}),f.map((s,u)=>{let a=u===P,d=s.disabled?e.muted:a?e.text:e.muted;return U(M,{children:[A(I,{color:a?e.accent:e.muted,children:a?"\u258C ":" "}),A(I,{bold:a,color:d,children:s.label})]},u)}),A(M,{marginTop:1,children:A(I,{color:e.muted,children:"\u2191/\u2193 \xB7 Enter \xB7 Esc"})})]})}import{Fragment as gt,jsx as t,jsxs as h}from"react/jsx-runtime";function Lt({params:D}){let f=q(),{cfg:T,setCfg:g}=z(),o=J(),{stdout:P}=ft(),[v,S]=B([]),[L,s]=B(!0),[u,a]=B(0),[d,O]=B(""),[x,y]=B(null),[V,E]=B(0),[C,p]=B(null),W=async()=>{let n=await Y(),r=await Promise.all(n.map(async m=>({entry:m,local:await H(m.id)})));S(r),s(!1)};st(()=>{W()},[V]);let b=mt(()=>d?v.filter(n=>X([n.entry],d).length>0):v,[d,v]),R=Math.max(0,Math.min(b.length-1,u)),i=b[R],Z=P?.rows??24,$=Math.max(6,Z-8),tt=Math.floor($/2),j=Math.max(0,Math.min(b.length-$,R-tt)),et=Math.min(b.length,j+$),F=async n=>{let r=Q(await K(),n);f.replace({name:"practice",params:{dictId:n,chapterIndex:r,mode:T.defaultMode,stealth:T.stealth==="default"}})},nt=async(n,r=!0)=>{await g({...T,defaultDict:n}),p(null),r&&(D?.pickerMode==="choose-then-practice"?await F(n):f.back())},ot=n=>{p(null),y({kind:"removing",id:n}),(async()=>{try{await N(n),y(null),E(r=>r+1)}catch(r){y({kind:"error",id:n,msg:r.message})}})()},rt=n=>{p(null),y({kind:"pulling",id:n}),(async()=>{try{await _(n),y(null),E(r=>r+1)}catch(r){y({kind:"error",id:n,msg:r.message})}})()},it=()=>{p(null),y({kind:"refreshing"}),E(n=>n+1),y(null)};if(ut((n,r)=>{if(C===null){if(r.escape){f.back();return}if(r.upArrow){a(m=>Math.max(0,m-1));return}if(r.downArrow){a(m=>Math.min(b.length-1,m+1));return}if(r.ctrl&&n==="k"){p("more");return}if(r.return){i&&p("item");return}if(r.backspace||r.delete){O(m=>m.slice(0,-1)),a(0);return}if(n&&!r.ctrl&&!r.meta){let m=[...n].filter(w=>{let k=w.codePointAt(0);return k>=32&&k!==127}).join("");if(m.length===0)return;O(w=>w+m),a(0)}}}),L)return t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(c,{color:e.muted,children:o.dict.loading})});let lt=i?[{label:o.dict.action.setDefault,run:()=>{nt(i.entry.id,D?.pickerMode!==void 0)}},{label:o.dict.action.practice,run:()=>{F(i.entry.id)}},{label:o.dict.action.delete,disabled:!i.local,run:()=>ot(i.entry.id)},{label:o.common.cancel,run:()=>p(null)}]:[],ct=[{label:o.dict.command.pull,disabled:!i,run:()=>i&&rt(i.entry.id)},{label:o.dict.command.import,disabled:!0,run:()=>{}},{label:o.dict.command.refreshList,run:()=>it()},{label:o.common.cancel,run:()=>p(null)}];return C==="item"&&i?t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(G,{title:`${o.dict.action.title} \xB7 ${i.entry.name}`,items:lt,onClose:()=>p(null)})}):C==="more"?t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(G,{title:o.dict.command.title,items:ct,onClose:()=>p(null)})}):h(l,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[h(l,{children:[t(c,{bold:!0,color:e.accent,children:o.dict.title}),t(l,{flexGrow:1}),t(c,{color:e.muted,children:d?`${o.dict.filterPlaceholder}: ${d}_`:`${o.dict.filterPlaceholder}_`}),h(c,{color:e.muted,children:[" ",o.dict.entries(b.length)]})]}),h(l,{marginTop:1,flexGrow:1,children:[t(l,{flexDirection:"column",width:"75%",paddingRight:1,children:b.slice(j,et).map((n,r)=>{let w=j+r===R,k=T.defaultDict===n.entry.id;return h(l,{children:[t(l,{width:2,children:t(c,{color:w?e.accent:e.muted,children:w?"\u258C ":" "})}),t(l,{width:2,children:t(c,{color:n.local?e.accent:e.muted,children:n.local?"\u25CF":"\u25CB"})}),t(l,{width:2,children:t(c,{color:k?e.success:e.muted,children:k?"\u2605":" "})}),t(l,{flexGrow:1,children:t(c,{bold:w,color:w?e.text:e.muted,wrap:"truncate",children:n.entry.name})}),t(l,{width:6,children:t(c,{color:e.muted,children:String(n.entry.length).padStart(5)})})]},n.entry.id)})}),t(l,{flexDirection:"column",width:"25%",paddingLeft:1,children:i&&h(gt,{children:[t(c,{bold:!0,color:e.text,wrap:"wrap",children:i.entry.name}),t(c,{color:e.muted,children:i.entry.id}),t(l,{marginTop:1,children:h(c,{color:e.muted,wrap:"wrap",children:[i.entry.language," \xB7 ",i.entry.category]})}),t(l,{marginTop:1,children:t(c,{color:e.muted,children:o.dict.wordsLabel(i.entry.length)})}),i.entry.description&&t(l,{marginTop:1,children:t(c,{color:e.primary,wrap:"wrap",children:i.entry.description})}),i.entry.tags.length>0&&t(l,{marginTop:1,children:t(c,{color:e.muted,wrap:"wrap",children:o.dict.tagsLabel(i.entry.tags.join(", "))})}),t(l,{marginTop:1,children:t(c,{color:i.local?e.accent:e.muted,children:i.local?o.dict.local:o.dict.notLocal})}),T.defaultDict===i.entry.id&&t(l,{children:t(c,{color:e.success,children:o.dict.defaultMark})})]})})]}),x&&h(l,{marginTop:1,children:[x.kind==="pulling"&&t(c,{color:e.warning,children:o.dict.pulling(x.id)}),x.kind==="removing"&&t(c,{color:e.warning,children:o.dict.removing(x.id)}),x.kind==="refreshing"&&h(c,{color:e.warning,children:[o.dict.command.refreshList,"\u2026"]}),x.kind==="error"&&t(c,{color:e.error,children:o.dict.errorOn(x.id,x.msg)})]}),t(l,{marginTop:1,children:t(c,{color:e.muted,children:o.dict.footer})})]})}export{Lt as DictBrowser};
|
|
2
|
+
//# sourceMappingURL=DictBrowser-ZQ773KQV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui/screens/DictBrowser.tsx","../src/ui/components/ActionPanel.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Box, Text, useInput, useStdout } from 'ink';\nimport { useNav, type DictParams } from '../nav.js';\nimport { useAppState } from '../app-state.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { PALETTE } from '../components/BigWord.js';\nimport { ActionPanel, type ActionItem } from '../components/ActionPanel.js';\nimport { loadRegistry } from '../../infra/registry-store.js';\nimport {\n isLocallyAvailable,\n pullDictionary,\n removeDictionary,\n} from '../../infra/dict-downloader.js';\nimport type { DictionaryEntry } from '../../domain/dictionary.js';\nimport { filterRegistry } from '../../domain/dictionary.js';\nimport { loadSessions, resumeChapterFor } from '../../domain/stats.js';\n\ntype Row = { entry: DictionaryEntry; local: boolean };\ntype PendingAction =\n | { kind: 'pulling'; id: string }\n | { kind: 'removing'; id: string }\n | { kind: 'refreshing' }\n | { kind: 'error'; id: string; msg: string }\n | null;\n\ntype Panel = null | 'item' | 'more';\n\nexport function DictBrowser({ params }: { params?: DictParams }) {\n const nav = useNav();\n const { cfg, setCfg } = useAppState();\n const t = useStrings();\n const { stdout } = useStdout();\n const [rows, setRows] = useState<Row[]>([]);\n const [loading, setLoading] = useState(true);\n const [selected, setSelected] = useState(0);\n const [filter, setFilter] = useState('');\n const [pending, setPending] = useState<PendingAction>(null);\n const [tick, setTick] = useState(0);\n const [panel, setPanel] = useState<Panel>(null);\n\n const refresh = async () => {\n const reg = await loadRegistry();\n const flagged = await Promise.all(\n reg.map(async (e) => ({ entry: e, local: await isLocallyAvailable(e.id) })),\n );\n setRows(flagged);\n setLoading(false);\n };\n\n useEffect(() => {\n void refresh();\n }, [tick]);\n\n const filtered = useMemo(\n () => (filter ? rows.filter((r) => filterRegistry([r.entry], filter).length > 0) : rows),\n [filter, rows],\n );\n const safeSelected = Math.max(0, Math.min(filtered.length - 1, selected));\n const current = filtered[safeSelected];\n\n const rowsTotal = stdout?.rows ?? 24;\n const visibleH = Math.max(6, rowsTotal - 8);\n const half = Math.floor(visibleH / 2);\n const start = Math.max(0, Math.min(filtered.length - visibleH, safeSelected - half));\n const end = Math.min(filtered.length, start + visibleH);\n\n const goPractice = async (id: string) => {\n const chapterIndex = resumeChapterFor(await loadSessions(), id);\n nav.replace({\n name: 'practice',\n params: {\n dictId: id,\n chapterIndex,\n mode: cfg.defaultMode,\n stealth: cfg.stealth === 'default',\n },\n });\n };\n\n const doSetDefault = async (id: string, navigate = true) => {\n await setCfg({ ...cfg, defaultDict: id });\n setPanel(null);\n if (navigate) {\n if (params?.pickerMode === 'choose-then-practice') {\n await goPractice(id);\n } else {\n nav.back();\n }\n }\n };\n\n const doDelete = (id: string) => {\n setPanel(null);\n setPending({ kind: 'removing', id });\n void (async () => {\n try {\n await removeDictionary(id);\n setPending(null);\n setTick((n) => n + 1);\n } catch (err) {\n setPending({ kind: 'error', id, msg: (err as Error).message });\n }\n })();\n };\n\n const doPull = (id: string) => {\n setPanel(null);\n setPending({ kind: 'pulling', id });\n void (async () => {\n try {\n await pullDictionary(id);\n setPending(null);\n setTick((n) => n + 1);\n } catch (err) {\n setPending({ kind: 'error', id, msg: (err as Error).message });\n }\n })();\n };\n\n const doRefreshList = () => {\n setPanel(null);\n setPending({ kind: 'refreshing' });\n setTick((n) => n + 1);\n setPending(null);\n };\n\n useInput((input, key) => {\n if (panel !== null) return;\n\n if (key.escape) {\n nav.back();\n return;\n }\n if (key.upArrow) {\n setSelected((i) => Math.max(0, i - 1));\n return;\n }\n if (key.downArrow) {\n setSelected((i) => Math.min(filtered.length - 1, i + 1));\n return;\n }\n if (key.ctrl && input === 'k') {\n setPanel('more');\n return;\n }\n if (key.return) {\n if (current) setPanel('item');\n return;\n }\n if (key.backspace || key.delete) {\n setFilter((f) => f.slice(0, -1));\n setSelected(0);\n return;\n }\n if (input && !key.ctrl && !key.meta) {\n // Keep printable codepoints (>= 0x20, != 0x7F DEL). Removing the old\n // `input.length === 1` cap so IME multi-char commits like '考研' (length 2)\n // and pasted runs reach the filter. filterRegistry already matches the\n // dictionary `name` field, which is Chinese for ~75% of the registry —\n // the bug was that the keystroke never made it to filter state.\n const cleaned = [...input]\n .filter((c) => {\n const cp = c.codePointAt(0)!;\n return cp >= 0x20 && cp !== 0x7f;\n })\n .join('');\n if (cleaned.length === 0) return;\n setFilter((f) => f + cleaned);\n setSelected(0);\n }\n });\n\n if (loading) {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.muted}>{t.dict.loading}</Text>\n </Box>\n );\n }\n\n const itemPanelItems: ActionItem[] = current\n ? [\n {\n label: t.dict.action.setDefault,\n run: () => void doSetDefault(current.entry.id, params?.pickerMode !== undefined),\n },\n {\n label: t.dict.action.practice,\n run: () => void goPractice(current.entry.id),\n },\n {\n label: t.dict.action.delete,\n disabled: !current.local,\n run: () => doDelete(current.entry.id),\n },\n { label: t.common.cancel, run: () => setPanel(null) },\n ]\n : [];\n\n const morePanelItems: ActionItem[] = [\n {\n label: t.dict.command.pull,\n disabled: !current,\n run: () => current && doPull(current.entry.id),\n },\n {\n label: t.dict.command.import,\n disabled: true,\n run: () => undefined,\n },\n { label: t.dict.command.refreshList, run: () => doRefreshList() },\n { label: t.common.cancel, run: () => setPanel(null) },\n ];\n\n if (panel === 'item' && current) {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <ActionPanel\n title={`${t.dict.action.title} · ${current.entry.name}`}\n items={itemPanelItems}\n onClose={() => setPanel(null)}\n />\n </Box>\n );\n }\n if (panel === 'more') {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <ActionPanel\n title={t.dict.command.title}\n items={morePanelItems}\n onClose={() => setPanel(null)}\n />\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1} width=\"100%\" height=\"100%\">\n <Box>\n <Text bold color={PALETTE.accent}>\n {t.dict.title}\n </Text>\n <Box flexGrow={1} />\n <Text color={PALETTE.muted}>\n {filter ? `${t.dict.filterPlaceholder}: ${filter}_` : `${t.dict.filterPlaceholder}_`}\n </Text>\n <Text color={PALETTE.muted}>\n {' '}\n {t.dict.entries(filtered.length)}\n </Text>\n </Box>\n\n <Box marginTop={1} flexGrow={1}>\n <Box flexDirection=\"column\" width=\"75%\" paddingRight={1}>\n {filtered.slice(start, end).map((row, vi) => {\n const i = start + vi;\n const active = i === safeSelected;\n const isDefault = cfg.defaultDict === row.entry.id;\n return (\n <Box key={row.entry.id}>\n <Box width={2}>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>{active ? '▌ ' : ' '}</Text>\n </Box>\n <Box width={2}>\n <Text color={row.local ? PALETTE.accent : PALETTE.muted}>\n {row.local ? '●' : '○'}\n </Text>\n </Box>\n <Box width={2}>\n <Text color={isDefault ? PALETTE.success : PALETTE.muted}>\n {isDefault ? '★' : ' '}\n </Text>\n </Box>\n <Box flexGrow={1}>\n <Text bold={active} color={active ? PALETTE.text : PALETTE.muted} wrap=\"truncate\">\n {row.entry.name}\n </Text>\n </Box>\n <Box width={6}>\n <Text color={PALETTE.muted}>{String(row.entry.length).padStart(5)}</Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n\n <Box flexDirection=\"column\" width=\"25%\" paddingLeft={1}>\n {current && (\n <>\n <Text bold color={PALETTE.text} wrap=\"wrap\">\n {current.entry.name}\n </Text>\n <Text color={PALETTE.muted}>{current.entry.id}</Text>\n <Box marginTop={1}>\n <Text color={PALETTE.muted} wrap=\"wrap\">\n {current.entry.language} · {current.entry.category}\n </Text>\n </Box>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.dict.wordsLabel(current.entry.length)}</Text>\n </Box>\n {current.entry.description && (\n <Box marginTop={1}>\n <Text color={PALETTE.primary} wrap=\"wrap\">{current.entry.description}</Text>\n </Box>\n )}\n {current.entry.tags.length > 0 && (\n <Box marginTop={1}>\n <Text color={PALETTE.muted} wrap=\"wrap\">{t.dict.tagsLabel(current.entry.tags.join(', '))}</Text>\n </Box>\n )}\n <Box marginTop={1}>\n <Text color={current.local ? PALETTE.accent : PALETTE.muted}>\n {current.local ? t.dict.local : t.dict.notLocal}\n </Text>\n </Box>\n {cfg.defaultDict === current.entry.id && (\n <Box>\n <Text color={PALETTE.success}>{t.dict.defaultMark}</Text>\n </Box>\n )}\n </>\n )}\n </Box>\n </Box>\n\n {pending && (\n <Box marginTop={1}>\n {pending.kind === 'pulling' && (\n <Text color={PALETTE.warning}>{t.dict.pulling(pending.id)}</Text>\n )}\n {pending.kind === 'removing' && (\n <Text color={PALETTE.warning}>{t.dict.removing(pending.id)}</Text>\n )}\n {pending.kind === 'refreshing' && (\n <Text color={PALETTE.warning}>{t.dict.command.refreshList}…</Text>\n )}\n {pending.kind === 'error' && (\n <Text color={PALETTE.error}>{t.dict.errorOn(pending.id, pending.msg)}</Text>\n )}\n </Box>\n )}\n\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.dict.footer}</Text>\n </Box>\n </Box>\n );\n}\n","import { useState } from 'react';\nimport { Box, Text, useInput } from 'ink';\nimport { PALETTE } from './BigWord.js';\n\nexport type ActionItem = {\n key?: string;\n label: string;\n disabled?: boolean;\n run: () => void | Promise<void>;\n};\n\ntype Props = {\n title: string;\n items: ActionItem[];\n onClose: () => void;\n};\n\nexport function ActionPanel({ title, items, onClose }: Props) {\n const enabledIndices = items\n .map((it, i) => (it.disabled ? -1 : i))\n .filter((i) => i >= 0);\n const initial = enabledIndices[0] ?? 0;\n const [selected, setSelected] = useState(initial);\n\n useInput((input, key) => {\n if (key.escape) {\n onClose();\n return;\n }\n if (key.upArrow) {\n const cur = enabledIndices.indexOf(selected);\n const next = enabledIndices[(cur - 1 + enabledIndices.length) % enabledIndices.length];\n if (next !== undefined) setSelected(next);\n return;\n }\n if (key.downArrow) {\n const cur = enabledIndices.indexOf(selected);\n const next = enabledIndices[(cur + 1) % enabledIndices.length];\n if (next !== undefined) setSelected(next);\n return;\n }\n if (key.return) {\n const item = items[selected];\n if (item && !item.disabled) {\n void item.run();\n }\n return;\n }\n for (let i = 0; i < items.length; i++) {\n const it = items[i]!;\n if (it.disabled) continue;\n if (it.key && input === it.key) {\n void it.run();\n return;\n }\n }\n });\n\n const maxLabel = Math.max(...items.map((it) => it.label.length));\n const width = Math.max(maxLabel + 8, title.length + 4, 24);\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n borderColor={PALETTE.accent}\n paddingX={2}\n paddingY={1}\n width={width}\n >\n <Box marginBottom={1}>\n <Text bold color={PALETTE.accent}>\n {title}\n </Text>\n </Box>\n {items.map((it, i) => {\n const active = i === selected;\n const color = it.disabled\n ? PALETTE.muted\n : active\n ? PALETTE.text\n : PALETTE.muted;\n return (\n <Box key={i}>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>\n {active ? '▌ ' : ' '}\n </Text>\n <Text bold={active} color={color}>\n {it.label}\n </Text>\n </Box>\n );\n })}\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>↑/↓ · Enter · Esc</Text>\n </Box>\n </Box>\n );\n}\n"],"mappings":"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","names":["useEffect","useMemo","useState","Box","Text","useInput","useStdout","useState","Box","Text","useInput","jsx","jsxs","ActionPanel","title","items","onClose","enabledIndices","it","i","initial","selected","setSelected","useState","useInput","input","key","cur","next","item","maxLabel","width","Box","PALETTE","Text","active","color","Fragment","jsx","jsxs","DictBrowser","params","nav","useNav","cfg","setCfg","useAppState","t","useStrings","stdout","useStdout","rows","setRows","useState","loading","setLoading","selected","setSelected","filter","setFilter","pending","setPending","tick","setTick","panel","setPanel","refresh","reg","loadRegistry","flagged","e","isLocallyAvailable","useEffect","filtered","useMemo","r","filterRegistry","safeSelected","current","rowsTotal","visibleH","half","start","end","goPractice","id","chapterIndex","resumeChapterFor","loadSessions","doSetDefault","navigate","doDelete","removeDictionary","n","err","doPull","pullDictionary","doRefreshList","useInput","input","key","i","f","cleaned","c","cp","Box","Text","PALETTE","itemPanelItems","morePanelItems","ActionPanel","row","vi","active","isDefault"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{b as ht,e as bt,h as Tt}from"./chunk-6ROGUGNX.js";import{a as ut,b as lt,c as dt,d as mt,e as ft,f as pt}from"./chunk-BIBS2Q3E.js";import{e as st}from"./chunk-DURXS5MX.js";import{b as Y}from"./chunk-CQRKGMPU.js";import"./chunk-7LTZGB7F.js";import{a as St}from"./chunk-HJIGZU3E.js";import{a as V,b as yt,c as wt}from"./chunk-G3DQB7FI.js";import{b as gt,e as L}from"./chunk-QG7ZTS2G.js";import{b as X,d as W,f as i,g as xt}from"./chunk-FQ2MEK7M.js";import"./chunk-NA5UNUVL.js";import"./chunk-E6BBQALJ.js";import{useState as j,useEffect as H,useRef as tt}from"react";import{Box as f,Text as g,useApp as de,useInput as O}from"ink";function It(t,e=Math.random){let r=[...t];for(let n=r.length-1;n>0;n--){let c=Math.floor(e()*(n+1)),d=r[n];r[n]=r[c],r[c]=d}return r}function kt(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 Ct(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 Mt(t,e,r){if(e==="random"){let n=r===void 0?Math.random:kt(r);return It(t,n)}return t}function _(t){return{target:t,typed:"",errorsThisWord:0}}function Wt(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 J(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 Q(t,e,r=Date.now()){if(!t.current)return{session:t,effect:"none"};let{state:n,effect:c}=Wt(t.current.input,e);if(c==="correct"){let d={word:n.target,errors:n.errorsThisWord,durationMs:r-t.current.wordStartedAt},a=t.current.wordIndex+1,s=[...t.results,d];return a>=t.playlist.length?{session:{...t,results:s,current:null,finishedAt:r},effect:c}:{session:{...t,results:s,current:{wordIndex:a,wordStartedAt:r,input:_(t.playlist[a].name)}},effect:c}}return{session:{...t,current:{...t.current,input:n}},effect:c}}function vt(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 Z(t){let e=t.results.reduce((c,d)=>c+d.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 Et,useReducer as Qt,useRef as Zt,useState as te}from"react";import{useInput as ee,useApp as re}from"ink";function ne(t,e){if(e.type==="start")return{session:J(e.playlist,e.now),lastEffect:null,effectSeq:0};if(e.type==="skip"){let r=vt(t.session,e.now);return{session:r.session,lastEffect:r.effect,effectSeq:t.effectSeq+1}}if(e.type==="event"){if(e.key.backspace||e.key.delete){let c=Q(t.session,{type:"backspace"},e.now);return{session:c.session,lastEffect:c.effect,effectSeq:t.effectSeq+1}}if(e.input.length===0)return t;let r=t.session,n=t.lastEffect;for(let c of e.input){let d=Q(r,{type:"char",ch:c},e.now);if(r=d.session,n=d.effect,r.finishedAt!==null)break}return{session:r,lastEffect:n,effectSeq:t.effectSeq+1}}return t}function oe(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 Bt({playlist:t,onComplete:e,onTab:r,onEscape:n,onSkip:c,onImeBlock:d,onValidInput:a,enabled:s=!0}){let[l,y]=Qt(ne,void 0,()=>({session:J(t,Date.now()),lastEffect:null,effectSeq:0})),M=Zt(!1),[T,I]=te(0),{exit:w}=re();return ee((p,x)=>{if(x.ctrl&&p==="c"){w();return}if(x.ctrl&&p==="n"){c?.(),y({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:v,cleaned:N}=oe(p);if(v==="ime"){d?.();return}v!=="noise"&&(a?.(),y({type:"event",input:N,key:x,now:Date.now()}))},{isActive:s}),Et(()=>{l.session.finishedAt!==null&&!M.current&&(M.current=!0,e(l.session))},[l.session,e]),Et(()=>{if(l.session.finishedAt!==null)return;let p=setInterval(()=>I(x=>x+1),1e3);return()=>clearInterval(p)},[l.session.finishedAt]),{session:l.session,lastEffect:l.lastEffect,effectSeq:l.effectSeq,tick:T}}import{useEffect as ce,useRef as ie}from"react";function At(t){let e=ie(!1);return ce(()=>{e.current||(e.current=!0,ut(!t.enabled).catch(()=>{}))},[t.enabled]),{keystroke:()=>t.enabled&<(),correct:()=>t.enabled&&dt(),wrong:()=>t.enabled&&mt(),pronounce:r=>{t.enabled&&t.autoplayPronunciation&&ft(r,t.accent,t.pronunciationRate,t.pronunciationSource)},prefetch:r=>{t.enabled&&pt(r,t.accent,t.pronunciationSource)}}}import{useCallback as ae}from"react";function Pt(t){return ae(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 St(r),Tt({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(([,d])=>d>0);if(n.length===0)return;let c=await V();for(let[d,a]of n)c=wt(c,d,t.dictId,a);await yt(c)},[t.dictId,t.chapterIndex,t.mode])}import{Box as R,Text as m,useStdout as se}from"ink";import{Fragment as le,jsx as u,jsxs as B}from"react/jsx-runtime";var $t=28;function Rt(t){let e=Math.floor(t/1e3),r=Math.floor(e/60),n=e%60;return`${r}:${String(n).padStart(2,"0")}`}function ue(){let{stdout:t}=se(),e=t?.columns??80;return Math.max(20,e-$t)}function E({left:t,right:e}){let r=ue();return B(R,{children:[u(R,{width:r,children:t}),u(R,{width:$t,justifyContent:"flex-end",children:e})]})}function Nt(t){let e=W(),r=[...t.target],n=[...t.typed],c=B(R,{children:[r.map((I,w)=>{let p=w<n.length,x=t.hideTarget&&!p?"_":p?n[w]:I,v=t.error?i.error:p?i.accent:i.muted;return u(m,{bold:!0,color:v,children:x},w)}),t.phonetic&&B(le,{children:[u(m,{children:" "}),u(m,{italic:!0,dimColor:!0,color:i.muted,children:t.phonetic})]})]}),d=t.translation.length>0?u(m,{color:i.primary,children:t.translation[0]}):u(m,{children:" "}),a=t.info,s=Number.isInteger(a.accPct)?`${a.accPct}`:a.accPct.toFixed(1),l=!t.imeBlocked&&t.audioWarning!==null,y=t.imeBlocked?u(m,{color:i.warning,children:e.practice.imeWarningShort}):l?u(m,{color:i.warning,children:e.practice.audioWarningShort}):a.visible?u(m,{color:i.muted,children:`${a.dictName} \xB7 ${a.chapterLabel}`}):u(m,{children:" "}),M=t.imeBlocked||l?u(m,{children:" "}):a.visible?u(m,{color:i.muted,children:`${a.completed}/${a.total} \xB7 ${a.wpm}wpm \xB7 ${s}%`}):u(m,{children:" "}),T=t.imeBlocked||l?u(m,{children:" "}):a.visible?u(m,{color:i.muted,children:Rt(a.elapsedMs)}):u(m,{children:" "});return B(R,{flexDirection:"column",children:[u(E,{left:c,right:y}),u(E,{left:u(m,{children:" "}),right:M}),u(E,{left:d,right:T})]})}function Dt(){let t=W();return B(R,{flexDirection:"column",children:[u(E,{left:u(m,{color:i.warning,children:t.stealth.paused}),right:u(m,{color:i.muted,children:t.stealth.pausedHintRight})}),u(E,{left:u(m,{children:" "}),right:B(m,{color:i.muted,children:["Esc ",t.common.back]})}),u(E,{left:u(m,{children:" "}),right:u(m,{children:" "})})]})}function Lt(t){let e=W(),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 ${Rt(t.durationMs)}`;return B(R,{flexDirection:"column",children:[u(E,{left:u(m,{color:i.success,children:n}),right:u(m,{color:i.muted,children:e.stealth.nextHintRight})}),u(E,{left:u(m,{children:" "}),right:B(m,{color:i.muted,children:["Esc ",e.common.back]})}),u(E,{left:u(m,{children:" "}),right:u(m,{children:" "})})]})}import{jsx as o,jsxs as C}from"react/jsx-runtime";function ar({params:t}){let{dictId:e,chapterIndex:r,mode:n}=t,{cfg:c}=Y(),d=W(),[a,s]=j("loading"),[l,y]=j(null),[M,T]=j(null);return H(()=>{let I=!1;return s("loading"),y(null),T(null),(async()=>{try{let w=await st(e);if(I)return;if(n==="review"){let N=await V();if(I)return;let q=w.filter(F=>N[F.name]?.count).slice(0,c.chapterSize);if(q.length===0){T(d.practice.errors.noMistakes),s("error");return}y({playlist:q,totalChapters:1}),s("typing");return}let p=Ct(w,c.chapterSize);if(p.length===0){T(d.practice.errors.dictEmpty(e)),s("error");return}let x=Math.max(0,Math.min(p.length-1,r)),v=Mt(p[x],n);y({playlist:v,totalChapters:p.length}),s("typing")}catch(w){if(I)return;T(w.message),s("error")}})(),()=>{I=!0}},[e,r,n,c.chapterSize,d]),a==="loading"?o(be,{text:d.practice.loading,color:i.muted}):a==="error"?o(ge,{msg:M??d.practice.errors.unknown}):l?o(me,{params:t,loaded:l,phase:a,setPhase:s},`${e}-${r}-${n}-${t.stealth?"s":"n"}`):null}function me({params:t,loaded:e,phase:r,setPhase:n}){let{dictId:c,chapterIndex:d,mode:a}=t,s=t.stealth===!0,{cfg:l}=Y(),y=X(),{exit:M}=de(),T=()=>y.stack.length>1?y.back():M(),I=Pt({dictId:c,chapterIndex:d,mode:a}),w=gt(c),p=At({enabled:!s&&l.sounds.master,accent:l.accent,autoplayPronunciation:!s&&l.autoplayPronunciation,pronunciationRate:l.sounds.pronunciationRate,pronunciationSource:l.sounds.pronunciationSource}),x=ht(),v=l.sounds.master?x.warning:null,N=tt(!1),q=tt(0),F=tt(-1),[Ot,rt]=j(!1),[nt,Ft]=j(null),[ot,ct]=j(!1);H(()=>{if(nt===null)return;let h=setTimeout(()=>rt(!1),2e3);return()=>clearTimeout(h)},[nt]);let{session:S,lastEffect:A,effectSeq:K,tick:Vt}=Bt({playlist:e.playlist,enabled:r==="typing",onComplete:h=>{N.current||(N.current=!0,n("summary"),Promise.resolve(I(Z(h))).catch(b=>{console.error("Failed to persist session:",b)}))},onEscape:()=>n(r==="paused"?"typing":"paused"),onTab:s?void 0:()=>{let h=S.current?e.playlist[S.current.wordIndex]:void 0;h&&p.pronounce(h.name)},onImeBlock:()=>ct(!0),onValidInput:()=>ct(!1)});H(()=>{s||K!==q.current&&(q.current=K,A!==null&&(A==="wrong"&&l.sounds.feedback&&p.wrong(),A==="progress"&&l.sounds.keystroke&&p.keystroke(),A==="correct"&&(l.sounds.feedback&&p.correct(),l.sounds.keystroke&&p.keystroke())))},[s,K,A,p,l.sounds.feedback,l.sounds.keystroke]),H(()=>{if(s)return;let h=S.current?.wordIndex??-1;if(h===-1||h===F.current)return;F.current=h;let b=e.playlist[h],$=e.playlist[h+1];b&&l.autoplayPronunciation&&p.pronounce(b.name),$&&p.prefetch($.name)},[s,S.current?.wordIndex,p,l.autoplayPronunciation,e.playlist]),O((h,b)=>{if(b.tab){rt(!0),Ft(Date.now());return}},{isActive:s&&r==="typing"}),O((h,b)=>{if(b.return){n("typing");return}if(b.escape){T();return}},{isActive:r==="paused"}),O((h,b)=>{b.ctrl&&h==="c"&&(bt(!0),M())},{isActive:s&&r==="paused"}),O((h,b)=>{if(b.escape){T();return}if(b.return){let $=d+1;a==="loop"?y.replace({name:"practice",params:{dictId:c,chapterIndex:d,mode:a,stealth:t.stealth}}):a==="review"||$>=e.totalChapters?T():y.replace({name:"practice",params:{dictId:c,chapterIndex:$,mode:a,stealth:t.stealth}});return}if(h==="m"){y.replace({name:"practice",params:{dictId:c,chapterIndex:0,mode:"review",stealth:t.stealth}});return}},{isActive:r==="summary"});let P=S.results.length,_t=S.results.reduce((h,b)=>h+b.errors,0),z=Date.now()-S.startedAt,it=z/6e4,at=it>0?Math.round(P/it*10)/10:0,k=r==="summary"?Z(S):null;if(s){if(r==="paused")return o(Dt,{});if(r==="summary"&&k){let D=k.durationMs/6e4,Ut=D>0?Math.round(k.wordCount/D*10)/10:0,Xt=Object.keys(k.perWordErrors).length,Yt=k.wordCount===0?1:Math.max(0,(k.wordCount-Xt)/k.wordCount),Jt=Math.round(Yt*1e3)/10;return o(Lt,{wordCount:k.wordCount,errors:k.errors,durationMs:k.durationMs,wpm:Ut,accPct:Jt})}let h=S.current?e.playlist[S.current.wordIndex]:e.playlist[e.playlist.length-1],b=S.current?.input??{target:"",typed:"",errorsThisWord:0},$=new Set(S.results.filter(D=>D.errors>0).map(D=>D.word)).size,Ht=P===0?1:Math.max(0,(P-$)/P),Kt=Math.round(Ht*1e3)/10,zt=a==="review"?"review":`ch ${d+1}/${e.totalChapters}`;return o(Nt,{target:h?.name??"",typed:b.typed,hideTarget:a==="dictation",phonetic:jt(h,l.accent),translation:h?.trans??[],error:A==="wrong",imeBlocked:ot,audioWarning:v,info:{visible:Ot,dictName:L(w,24),chapterLabel:zt,completed:P,total:e.playlist.length,wpm:at,accPct:Kt,elapsedMs:z}})}if(r==="paused")return o(he,{dictName:w,chapterIndex:d,totalChapters:e.totalChapters,mode:a,completed:P,total:e.playlist.length});if(r==="summary"&&k)return o(ye,{dictName:w,chapterIndex:d,totalChapters:e.totalChapters,mode:a,summary:k});let U=S.current?e.playlist[S.current.wordIndex]:e.playlist[e.playlist.length-1],Gt=S.current?.input??{target:"",typed:"",errorsThisWord:0};return o(fe,{dictName:w,chapterIndex:d,totalChapters:e.totalChapters,mode:a,accent:l.accent,completed:P,total:e.playlist.length,errors:_t,wpm:at,elapsedMs:z,target:U?.name??"",typed:Gt.typed,flashError:A==="wrong",hideTarget:a==="dictation",phonetic:jt(U,l.accent),translation:U?.trans??[],imeBlocked:ot,audioWarning:v})}function jt(t,e){return t?(e==="us"?t.usphone:t.ukphone)??null:null}function et(t){let e=Math.floor(t/1e3),r=Math.floor(e/60),n=e%60;return`${r}:${String(n).padStart(2,"0")}`}function fe(t){let e=W(),r=t.total===0?0:t.completed/t.total;return C(f,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[o(pe,{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(xt,{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})}),!t.imeBlocked&&t.audioWarning&&o(f,{marginTop:1,children:o(g,{color:i.warning,children:t.audioWarning})})]}),C(f,{flexDirection:"column",children:[o(qt,{frac:r}),o(f,{justifyContent:"center",marginTop:1,children:C(g,{color:i.muted,children:[t.completed,"/",t.total," \xB7 ",et(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 pe(t){let e=W(),r=e.practice.modes[t.mode],n=e.practice.accents[t.accent],c=L(t.dictName,20),d=t.mode==="review"?`${c} \xB7 ${e.practice.reviewLabel} \xB7 ${n}`:`${c} \xB7 ${e.practice.chapterLabel(t.chapterIndex+1,t.totalChapters)} \xB7 ${r} \xB7 ${n}`,a=`${t.completed}/${t.total} \xB7 ${et(t.elapsedMs)}`;return C(f,{children:[o(g,{color:i.muted,children:d}),o(f,{flexGrow:1}),o(g,{color:i.muted,children:a})]})}function qt({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 he(t){let e=W(),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(qt,{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 ge({msg:t}){let e=W();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(xe,{})]})}function xe(){let t=X();return O((e,r)=>{r.escape&&t.back()}),null}function be({text:t,color:e}){return o(f,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:o(g,{color:e,children:t})})}function ye(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,d=e.wordCount===0?1:Math.max(0,(e.wordCount-c)/e.wordCount),a=Math.round(d*1e3)/10,s=W(),l=s.practice.modes[t.mode],y=L(t.dictName,20),M=t.mode==="review"?`${y} \xB7 ${s.practice.reviewLabel}`:`${y} \xB7 ${s.practice.chapterLabel(t.chapterIndex+1,t.totalChapters)} \xB7 ${l}`,I=`Enter ${t.mode==="loop"?s.practice.summary.loopAgain:t.mode==="review"||t.chapterIndex+1>=t.totalChapters?s.practice.summary.backMenu:s.practice.summary.nextChapter} \xB7 m ${s.practice.summary.reviewMistakes} \xB7 Esc ${s.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:s.practice.chapterComplete}),o(f,{marginTop:1,children:o(g,{color:i.muted,children:M})}),C(f,{marginTop:3,flexDirection:"row",justifyContent:"center",children:[o(G,{label:s.practice.statCards.words,value:String(e.wordCount),color:i.text}),o(G,{label:s.practice.statCards.errors,value:String(e.errors),color:e.errors>0?i.error:i.muted}),o(G,{label:s.practice.statCards.wpm,value:String(n),color:i.accent}),o(G,{label:s.practice.statCards.accuracy,value:`${a}%`,color:i.accent})]}),o(f,{marginTop:2,children:o(g,{color:i.muted,children:s.practice.statCards.elapsed(et(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{ar as PracticeScreen};
|
|
2
|
-
//# sourceMappingURL=PracticeScreen-
|
|
1
|
+
import{b as ht,e as bt,h as Tt}from"./chunk-6ROGUGNX.js";import{a as ut,b as lt,c as dt,d as mt,e as ft,f as pt}from"./chunk-BIBS2Q3E.js";import{e as st}from"./chunk-DURXS5MX.js";import{b as Y}from"./chunk-CQRKGMPU.js";import"./chunk-7LTZGB7F.js";import{a as V,b as wt,c as St}from"./chunk-G3DQB7FI.js";import{a as yt}from"./chunk-GULN5HRV.js";import{b as gt,e as L}from"./chunk-QG7ZTS2G.js";import{b as X,d as W,f as i,g as xt}from"./chunk-FQ2MEK7M.js";import"./chunk-NA5UNUVL.js";import"./chunk-E6BBQALJ.js";import{useState as j,useEffect as H,useRef as tt}from"react";import{Box as f,Text as g,useApp as de,useInput as O}from"ink";function It(t,e=Math.random){let r=[...t];for(let n=r.length-1;n>0;n--){let c=Math.floor(e()*(n+1)),d=r[n];r[n]=r[c],r[c]=d}return r}function kt(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 Ct(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 Mt(t,e,r){if(e==="random"){let n=r===void 0?Math.random:kt(r);return It(t,n)}return t}function _(t){return{target:t,typed:"",errorsThisWord:0}}function Wt(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 J(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 Q(t,e,r=Date.now()){if(!t.current)return{session:t,effect:"none"};let{state:n,effect:c}=Wt(t.current.input,e);if(c==="correct"){let d={word:n.target,errors:n.errorsThisWord,durationMs:r-t.current.wordStartedAt},a=t.current.wordIndex+1,s=[...t.results,d];return a>=t.playlist.length?{session:{...t,results:s,current:null,finishedAt:r},effect:c}:{session:{...t,results:s,current:{wordIndex:a,wordStartedAt:r,input:_(t.playlist[a].name)}},effect:c}}return{session:{...t,current:{...t.current,input:n}},effect:c}}function vt(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 Z(t){let e=t.results.reduce((c,d)=>c+d.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 Et,useReducer as Qt,useRef as Zt,useState as te}from"react";import{useInput as ee,useApp as re}from"ink";function ne(t,e){if(e.type==="start")return{session:J(e.playlist,e.now),lastEffect:null,effectSeq:0};if(e.type==="skip"){let r=vt(t.session,e.now);return{session:r.session,lastEffect:r.effect,effectSeq:t.effectSeq+1}}if(e.type==="event"){if(e.key.backspace||e.key.delete){let c=Q(t.session,{type:"backspace"},e.now);return{session:c.session,lastEffect:c.effect,effectSeq:t.effectSeq+1}}if(e.input.length===0)return t;let r=t.session,n=t.lastEffect;for(let c of e.input){let d=Q(r,{type:"char",ch:c},e.now);if(r=d.session,n=d.effect,r.finishedAt!==null)break}return{session:r,lastEffect:n,effectSeq:t.effectSeq+1}}return t}function oe(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 Bt({playlist:t,onComplete:e,onTab:r,onEscape:n,onSkip:c,onImeBlock:d,onValidInput:a,enabled:s=!0}){let[l,y]=Qt(ne,void 0,()=>({session:J(t,Date.now()),lastEffect:null,effectSeq:0})),M=Zt(!1),[T,I]=te(0),{exit:w}=re();return ee((p,x)=>{if(x.ctrl&&p==="c"){w();return}if(x.ctrl&&p==="n"){c?.(),y({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:v,cleaned:N}=oe(p);if(v==="ime"){d?.();return}v!=="noise"&&(a?.(),y({type:"event",input:N,key:x,now:Date.now()}))},{isActive:s}),Et(()=>{l.session.finishedAt!==null&&!M.current&&(M.current=!0,e(l.session))},[l.session,e]),Et(()=>{if(l.session.finishedAt!==null)return;let p=setInterval(()=>I(x=>x+1),1e3);return()=>clearInterval(p)},[l.session.finishedAt]),{session:l.session,lastEffect:l.lastEffect,effectSeq:l.effectSeq,tick:T}}import{useEffect as ce,useRef as ie}from"react";function At(t){let e=ie(!1);return ce(()=>{e.current||(e.current=!0,ut(!t.enabled).catch(()=>{}))},[t.enabled]),{keystroke:()=>t.enabled&<(),correct:()=>t.enabled&&dt(),wrong:()=>t.enabled&&mt(),pronounce:r=>{t.enabled&&t.autoplayPronunciation&&ft(r,t.accent,t.pronunciationRate,t.pronunciationSource)},prefetch:r=>{t.enabled&&pt(r,t.accent,t.pronunciationSource)}}}import{useCallback as ae}from"react";function Pt(t){return ae(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 yt(r),Tt({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(([,d])=>d>0);if(n.length===0)return;let c=await V();for(let[d,a]of n)c=St(c,d,t.dictId,a);await wt(c)},[t.dictId,t.chapterIndex,t.mode])}import{Box as R,Text as m,useStdout as se}from"ink";import{Fragment as le,jsx as u,jsxs as B}from"react/jsx-runtime";var $t=28;function Rt(t){let e=Math.floor(t/1e3),r=Math.floor(e/60),n=e%60;return`${r}:${String(n).padStart(2,"0")}`}function ue(){let{stdout:t}=se(),e=t?.columns??80;return Math.max(20,e-$t)}function E({left:t,right:e}){let r=ue();return B(R,{children:[u(R,{width:r,children:t}),u(R,{width:$t,justifyContent:"flex-end",children:e})]})}function Nt(t){let e=W(),r=[...t.target],n=[...t.typed],c=B(R,{children:[r.map((I,w)=>{let p=w<n.length,x=t.hideTarget&&!p?"_":p?n[w]:I,v=t.error?i.error:p?i.accent:i.muted;return u(m,{bold:!0,color:v,children:x},w)}),t.phonetic&&B(le,{children:[u(m,{children:" "}),u(m,{italic:!0,dimColor:!0,color:i.muted,children:t.phonetic})]})]}),d=t.translation.length>0?u(m,{color:i.primary,children:t.translation[0]}):u(m,{children:" "}),a=t.info,s=Number.isInteger(a.accPct)?`${a.accPct}`:a.accPct.toFixed(1),l=!t.imeBlocked&&t.audioWarning!==null,y=t.imeBlocked?u(m,{color:i.warning,children:e.practice.imeWarningShort}):l?u(m,{color:i.warning,children:e.practice.audioWarningShort}):a.visible?u(m,{color:i.muted,children:`${a.dictName} \xB7 ${a.chapterLabel}`}):u(m,{children:" "}),M=t.imeBlocked||l?u(m,{children:" "}):a.visible?u(m,{color:i.muted,children:`${a.completed}/${a.total} \xB7 ${a.wpm}wpm \xB7 ${s}%`}):u(m,{children:" "}),T=t.imeBlocked||l?u(m,{children:" "}):a.visible?u(m,{color:i.muted,children:Rt(a.elapsedMs)}):u(m,{children:" "});return B(R,{flexDirection:"column",children:[u(E,{left:c,right:y}),u(E,{left:u(m,{children:" "}),right:M}),u(E,{left:d,right:T})]})}function Dt(){let t=W();return B(R,{flexDirection:"column",children:[u(E,{left:u(m,{color:i.warning,children:t.stealth.paused}),right:u(m,{color:i.muted,children:t.stealth.pausedHintRight})}),u(E,{left:u(m,{children:" "}),right:B(m,{color:i.muted,children:["Esc ",t.common.back]})}),u(E,{left:u(m,{children:" "}),right:u(m,{children:" "})})]})}function Lt(t){let e=W(),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 ${Rt(t.durationMs)}`;return B(R,{flexDirection:"column",children:[u(E,{left:u(m,{color:i.success,children:n}),right:u(m,{color:i.muted,children:e.stealth.nextHintRight})}),u(E,{left:u(m,{children:" "}),right:B(m,{color:i.muted,children:["Esc ",e.common.back]})}),u(E,{left:u(m,{children:" "}),right:u(m,{children:" "})})]})}import{jsx as o,jsxs as C}from"react/jsx-runtime";function ar({params:t}){let{dictId:e,chapterIndex:r,mode:n}=t,{cfg:c}=Y(),d=W(),[a,s]=j("loading"),[l,y]=j(null),[M,T]=j(null);return H(()=>{let I=!1;return s("loading"),y(null),T(null),(async()=>{try{let w=await st(e);if(I)return;if(n==="review"){let N=await V();if(I)return;let q=w.filter(F=>N[F.name]?.count).slice(0,c.chapterSize);if(q.length===0){T(d.practice.errors.noMistakes),s("error");return}y({playlist:q,totalChapters:1}),s("typing");return}let p=Ct(w,c.chapterSize);if(p.length===0){T(d.practice.errors.dictEmpty(e)),s("error");return}let x=Math.max(0,Math.min(p.length-1,r)),v=Mt(p[x],n);y({playlist:v,totalChapters:p.length}),s("typing")}catch(w){if(I)return;T(w.message),s("error")}})(),()=>{I=!0}},[e,r,n,c.chapterSize,d]),a==="loading"?o(be,{text:d.practice.loading,color:i.muted}):a==="error"?o(ge,{msg:M??d.practice.errors.unknown}):l?o(me,{params:t,loaded:l,phase:a,setPhase:s},`${e}-${r}-${n}-${t.stealth?"s":"n"}`):null}function me({params:t,loaded:e,phase:r,setPhase:n}){let{dictId:c,chapterIndex:d,mode:a}=t,s=t.stealth===!0,{cfg:l}=Y(),y=X(),{exit:M}=de(),T=()=>y.stack.length>1?y.back():M(),I=Pt({dictId:c,chapterIndex:d,mode:a}),w=gt(c),p=At({enabled:!s&&l.sounds.master,accent:l.accent,autoplayPronunciation:!s&&l.autoplayPronunciation,pronunciationRate:l.sounds.pronunciationRate,pronunciationSource:l.sounds.pronunciationSource}),x=ht(),v=l.sounds.master?x.warning:null,N=tt(!1),q=tt(0),F=tt(-1),[Ot,rt]=j(!1),[nt,Ft]=j(null),[ot,ct]=j(!1);H(()=>{if(nt===null)return;let h=setTimeout(()=>rt(!1),2e3);return()=>clearTimeout(h)},[nt]);let{session:S,lastEffect:A,effectSeq:K,tick:Vt}=Bt({playlist:e.playlist,enabled:r==="typing",onComplete:h=>{N.current||(N.current=!0,n("summary"),Promise.resolve(I(Z(h))).catch(b=>{console.error("Failed to persist session:",b)}))},onEscape:()=>n(r==="paused"?"typing":"paused"),onTab:s?void 0:()=>{let h=S.current?e.playlist[S.current.wordIndex]:void 0;h&&p.pronounce(h.name)},onImeBlock:()=>ct(!0),onValidInput:()=>ct(!1)});H(()=>{s||K!==q.current&&(q.current=K,A!==null&&(A==="wrong"&&l.sounds.feedback&&p.wrong(),A==="progress"&&l.sounds.keystroke&&p.keystroke(),A==="correct"&&(l.sounds.feedback&&p.correct(),l.sounds.keystroke&&p.keystroke())))},[s,K,A,p,l.sounds.feedback,l.sounds.keystroke]),H(()=>{if(s)return;let h=S.current?.wordIndex??-1;if(h===-1||h===F.current)return;F.current=h;let b=e.playlist[h],$=e.playlist[h+1];b&&l.autoplayPronunciation&&p.pronounce(b.name),$&&p.prefetch($.name)},[s,S.current?.wordIndex,p,l.autoplayPronunciation,e.playlist]),O((h,b)=>{if(b.tab){rt(!0),Ft(Date.now());return}},{isActive:s&&r==="typing"}),O((h,b)=>{if(b.return){n("typing");return}if(b.escape){T();return}},{isActive:r==="paused"}),O((h,b)=>{b.ctrl&&h==="c"&&(bt(!0),M())},{isActive:s&&r==="paused"}),O((h,b)=>{if(b.escape){T();return}if(b.return){let $=d+1;a==="loop"?y.replace({name:"practice",params:{dictId:c,chapterIndex:d,mode:a,stealth:t.stealth}}):a==="review"||$>=e.totalChapters?T():y.replace({name:"practice",params:{dictId:c,chapterIndex:$,mode:a,stealth:t.stealth}});return}if(h==="m"){y.replace({name:"practice",params:{dictId:c,chapterIndex:0,mode:"review",stealth:t.stealth}});return}},{isActive:r==="summary"});let P=S.results.length,_t=S.results.reduce((h,b)=>h+b.errors,0),z=Date.now()-S.startedAt,it=z/6e4,at=it>0?Math.round(P/it*10)/10:0,k=r==="summary"?Z(S):null;if(s){if(r==="paused")return o(Dt,{});if(r==="summary"&&k){let D=k.durationMs/6e4,Ut=D>0?Math.round(k.wordCount/D*10)/10:0,Xt=Object.keys(k.perWordErrors).length,Yt=k.wordCount===0?1:Math.max(0,(k.wordCount-Xt)/k.wordCount),Jt=Math.round(Yt*1e3)/10;return o(Lt,{wordCount:k.wordCount,errors:k.errors,durationMs:k.durationMs,wpm:Ut,accPct:Jt})}let h=S.current?e.playlist[S.current.wordIndex]:e.playlist[e.playlist.length-1],b=S.current?.input??{target:"",typed:"",errorsThisWord:0},$=new Set(S.results.filter(D=>D.errors>0).map(D=>D.word)).size,Ht=P===0?1:Math.max(0,(P-$)/P),Kt=Math.round(Ht*1e3)/10,zt=a==="review"?"review":`ch ${d+1}/${e.totalChapters}`;return o(Nt,{target:h?.name??"",typed:b.typed,hideTarget:a==="dictation",phonetic:jt(h,l.accent),translation:h?.trans??[],error:A==="wrong",imeBlocked:ot,audioWarning:v,info:{visible:Ot,dictName:L(w,24),chapterLabel:zt,completed:P,total:e.playlist.length,wpm:at,accPct:Kt,elapsedMs:z}})}if(r==="paused")return o(he,{dictName:w,chapterIndex:d,totalChapters:e.totalChapters,mode:a,completed:P,total:e.playlist.length});if(r==="summary"&&k)return o(ye,{dictName:w,chapterIndex:d,totalChapters:e.totalChapters,mode:a,summary:k});let U=S.current?e.playlist[S.current.wordIndex]:e.playlist[e.playlist.length-1],Gt=S.current?.input??{target:"",typed:"",errorsThisWord:0};return o(fe,{dictName:w,chapterIndex:d,totalChapters:e.totalChapters,mode:a,accent:l.accent,completed:P,total:e.playlist.length,errors:_t,wpm:at,elapsedMs:z,target:U?.name??"",typed:Gt.typed,flashError:A==="wrong",hideTarget:a==="dictation",phonetic:jt(U,l.accent),translation:U?.trans??[],imeBlocked:ot,audioWarning:v})}function jt(t,e){return t?(e==="us"?t.usphone:t.ukphone)??null:null}function et(t){let e=Math.floor(t/1e3),r=Math.floor(e/60),n=e%60;return`${r}:${String(n).padStart(2,"0")}`}function fe(t){let e=W(),r=t.total===0?0:t.completed/t.total;return C(f,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[o(pe,{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(xt,{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})}),!t.imeBlocked&&t.audioWarning&&o(f,{marginTop:1,children:o(g,{color:i.warning,children:t.audioWarning})})]}),C(f,{flexDirection:"column",children:[o(qt,{frac:r}),o(f,{justifyContent:"center",marginTop:1,children:C(g,{color:i.muted,children:[t.completed,"/",t.total," \xB7 ",et(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 pe(t){let e=W(),r=e.practice.modes[t.mode],n=e.practice.accents[t.accent],c=L(t.dictName,20),d=t.mode==="review"?`${c} \xB7 ${e.practice.reviewLabel} \xB7 ${n}`:`${c} \xB7 ${e.practice.chapterLabel(t.chapterIndex+1,t.totalChapters)} \xB7 ${r} \xB7 ${n}`,a=`${t.completed}/${t.total} \xB7 ${et(t.elapsedMs)}`;return C(f,{children:[o(g,{color:i.muted,children:d}),o(f,{flexGrow:1}),o(g,{color:i.muted,children:a})]})}function qt({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 he(t){let e=W(),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(qt,{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 ge({msg:t}){let e=W();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(xe,{})]})}function xe(){let t=X();return O((e,r)=>{r.escape&&t.back()}),null}function be({text:t,color:e}){return o(f,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:o(g,{color:e,children:t})})}function ye(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,d=e.wordCount===0?1:Math.max(0,(e.wordCount-c)/e.wordCount),a=Math.round(d*1e3)/10,s=W(),l=s.practice.modes[t.mode],y=L(t.dictName,20),M=t.mode==="review"?`${y} \xB7 ${s.practice.reviewLabel}`:`${y} \xB7 ${s.practice.chapterLabel(t.chapterIndex+1,t.totalChapters)} \xB7 ${l}`,I=`Enter ${t.mode==="loop"?s.practice.summary.loopAgain:t.mode==="review"||t.chapterIndex+1>=t.totalChapters?s.practice.summary.backMenu:s.practice.summary.nextChapter} \xB7 m ${s.practice.summary.reviewMistakes} \xB7 Esc ${s.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:s.practice.chapterComplete}),o(f,{marginTop:1,children:o(g,{color:i.muted,children:M})}),C(f,{marginTop:3,flexDirection:"row",justifyContent:"center",children:[o(G,{label:s.practice.statCards.words,value:String(e.wordCount),color:i.text}),o(G,{label:s.practice.statCards.errors,value:String(e.errors),color:e.errors>0?i.error:i.muted}),o(G,{label:s.practice.statCards.wpm,value:String(n),color:i.accent}),o(G,{label:s.practice.statCards.accuracy,value:`${a}%`,color:i.accent})]}),o(f,{marginTop:2,children:o(g,{color:i.muted,children:s.practice.statCards.elapsed(et(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{ar as PracticeScreen};
|
|
2
|
+
//# sourceMappingURL=PracticeScreen-PWXXG33U.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
2
|
-
//# sourceMappingURL=StatsViewer-
|
|
1
|
+
import{a as j,d as F}from"./chunk-G3DQB7FI.js";import{b as N,c as $,d as O,f as V,h as Y}from"./chunk-GULN5HRV.js";import{b as B,d as k,e as y}from"./chunk-QG7ZTS2G.js";import{b as R,d as C,f as o}from"./chunk-FQ2MEK7M.js";import"./chunk-NA5UNUVL.js";import"./chunk-E6BBQALJ.js";import{useEffect as G,useState as D}from"react";import{Box as i,Text as r,useInput as U,useStdout as q}from"ink";import{jsx as e,jsxs as c}from"react/jsx-runtime";var W=[7,14,30,90];function it(){let s=R(),t=C(),[n,u]=D(null),[x,g]=D(null),[d,b]=D(1);if(G(()=>{(async()=>{let[l,a]=await Promise.all([N(),j()]);u(l),g(a)})()},[]),U((l,a)=>{if(a.escape){s.back();return}a.rightArrow&&b(M=>(M+1)%W.length),a.leftArrow&&b(M=>(M-1+W.length)%W.length)}),!n||!x)return e(i,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:e(r,{color:o.muted,children:t.stats.loading})});if(n.length===0)return c(i,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[e(r,{color:o.muted,children:t.stats.none}),e(i,{marginTop:1,children:e(r,{color:o.muted,children:t.stats.nonePractice})}),e(i,{marginTop:2,children:c(r,{color:o.muted,children:["Esc ",t.common.back]})})]});let m=W[d],S=V(n),f=n.reduce((l,a)=>l+a.wordCount,0),w=n.reduce((l,a)=>l+a.errors,0),T=n.reduce((l,a)=>l+a.durationMs,0),v=n.reduce((l,a)=>l+(a.wordCount-Object.keys(a.perWordErrors).length),0),H=T>0?Math.round(f/(T/6e4)*10)/10:0,X=f===0?1:v/f,_=n.slice(-5).reverse(),I=F(x,8),p=Y(n,m),A=p.peakWpmLifetime>0?Math.min(1,p.avgWpm/p.peakWpmLifetime):0,L=Math.min(1,p.avgAccPct/100),P=m>0?Math.min(1,p.sessionsInWindow/m):0;return c(i,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[e(r,{bold:!0,color:o.accent,children:t.stats.title}),c(i,{marginTop:1,flexDirection:"column",children:[e(r,{color:o.muted,children:t.stats.lifetime}),c(i,{marginTop:1,children:[e(h,{label:t.stats.sessions,value:String(n.length)}),e(h,{label:t.stats.words,value:String(f)}),e(h,{label:t.stats.errors,value:String(w)}),e(h,{label:t.stats.wpm,value:String(H),accent:!0}),e(h,{label:t.stats.accuracy,value:`${Math.round(X*1e3)/10}%`,accent:!0}),e(h,{label:t.stats.streak,value:`${S}d`,accent:!0})]})]}),c(i,{marginTop:2,flexDirection:"column",children:[e(r,{color:o.muted,children:t.stats.last(m)}),c(i,{marginTop:1,flexDirection:"column",borderStyle:"round",borderColor:o.muted,paddingX:2,paddingY:0,children:[e(E,{label:t.stats.bars.speed,frac:A,valueText:`${p.avgWpm} wpm`,pct:Math.round(A*100)}),e(E,{label:t.stats.bars.accuracy,frac:L,valueText:`${p.avgAccPct}%`,pct:Math.round(L*100)}),e(E,{label:t.stats.bars.sessions,frac:P,valueText:`${p.sessionsInWindow} / ${m}`,pct:Math.round(P*100)})]})]}),c(i,{marginTop:2,flexDirection:"column",children:[e(r,{color:o.muted,children:t.stats.recent}),_.map((l,a)=>e(z,{session:l,units:t.stats.recentUnits},a))]}),I.length>0&&c(i,{marginTop:2,flexDirection:"column",children:[e(r,{color:o.muted,children:t.stats.topMistakes}),I.map(([l,a])=>e(J,{word:l,count:a.count,dictIds:a.dictIds,multiSuffix:t.stats.multiDictSuffix},l))]}),e(i,{flexGrow:1}),e(i,{marginTop:1,children:e(r,{color:o.muted,children:t.stats.footer})})]})}function z({session:s,units:t}){let n=B(s.dictId),u=y(n,14);return c(i,{children:[c(r,{color:o.muted,children:[" ",s.ts.replace("T"," ").slice(0,16)," "]}),e(r,{color:o.text,children:u.padEnd(14)}),c(r,{color:o.muted,children:[" ","ch",String(s.chapter+1).padStart(3)," ",s.mode.padEnd(9)," ",String(s.wordCount).padStart(3),t.words," ",s.errors,t.errors," ",$(s),t.wpm," ",Math.round(O(s)*1e3)/10,"%"]})]})}function J({word:s,count:t,dictIds:n,multiSuffix:u}){let x=n[0]??"",g=B(x),d=n.length>1?u(n.length-1):"";return c(i,{children:[c(r,{color:o.error,children:[" ",String(t).padStart(3)," "]}),e(r,{color:o.text,children:s.padEnd(20)}),c(r,{color:o.muted,children:[y(g,20),d]})]})}function h({label:s,value:t,accent:n=!1}){return c(i,{marginRight:3,children:[c(r,{color:o.muted,children:[s," "]}),e(r,{bold:!0,color:n?o.accent:o.text,children:t})]})}function K(s){return{barWidth:Math.max(0,Math.min(60,s-11-24)),labelWidth:9,valueWidth:8,pctWidth:5,marginLeft:2}}function E({label:s,frac:t,valueText:n,pct:u}){let{stdout:x}=q(),g=x?.columns??80,d=K(g),b=Math.max(0,Math.min(1,t)),m=Math.round(d.barWidth*b),S=d.barWidth-m,f=k(s,d.labelWidth),w=k(n,d.valueWidth),T=String(u).padStart(d.pctWidth-1," ")+"%",v=" ".repeat(d.marginLeft);return c(i,{children:[e(r,{color:o.muted,children:f}),e(r,{color:o.accent,children:"\u2501".repeat(m)}),e(r,{color:o.muted,children:"\u2500".repeat(S)}),c(r,{color:o.text,children:[v,w]}),e(r,{color:o.muted,children:T})]})}export{it as StatsViewer,K as calcMetricBarLayout};
|
|
2
|
+
//# sourceMappingURL=StatsViewer-XARAMVZW.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as l,g as h,h as S}from"./chunk-E6BBQALJ.js";import{z as c}from"zod";var x=c.object({ts:c.string(),dictId:c.string(),chapter:c.number().int().nonnegative(),mode:c.string(),wordCount:c.number().int().nonnegative(),errors:c.number().int().nonnegative(),durationMs:c.number().int().nonnegative(),perWordErrors:c.record(c.string(),c.number().int().nonnegative()).default({})});async function k(e){await h(l.stats,e)}async function C(){return(await S(l.stats)).map(t=>x.safeParse(t)).filter(t=>t.success).map(t=>t.data)}function b(e){if(e.durationMs===0)return 0;let t=e.durationMs/6e4;return Math.round(e.wordCount/t*10)/10}function y(e){if(e.wordCount===0)return 1;let t=Object.keys(e.perWordErrors).length;return Math.max(0,Math.min(1,(e.wordCount-t)/e.wordCount))}function R(e,t){let o=null;for(let r of e)r.dictId===t&&(o=r.chapter);return o===null?0:o+1}function v(e,t=new Date){if(e.length===0)return 0;let o=new Set;for(let n of e)o.add(n.ts.slice(0,10));let r=0,i=new Date(t);for(;;){let n=i.toISOString().slice(0,10);if(!o.has(n))break;r++,i.setUTCDate(i.getUTCDate()-1)}return r}var w=["\u2581","\u2582","\u2583","\u2584","\u2585","\u2586","\u2587","\u2588"];function I(e){if(e.length===0)return"";let t=Math.max(...e,1),o=Math.min(...e,0),r=Math.max(1,t-o);return e.map(i=>{let n=Math.floor((i-o)/r*(w.length-1));return w[Math.max(0,Math.min(w.length-1,n))]}).join("")}function P(e,t,o=new Date){if(e.length===0||t<=0)return{avgWpm:0,peakWpmLifetime:0,avgAccPct:0,sessionsInWindow:0,days:t};let r=new Date(o);r.setUTCDate(r.getUTCDate()-(t-1));let i=r.toISOString().slice(0,10),n=0,m=0,u=0,s=0,p=0;for(let a of e){let f=b(a);f>p&&(p=f),!(a.ts.slice(0,10)<i)&&(s++,n+=a.wordCount,m+=f*a.wordCount,u+=y(a)*a.wordCount)}if(n===0)return{avgWpm:0,peakWpmLifetime:p,avgAccPct:0,sessionsInWindow:s,days:t};let g=Math.round(m/n*10)/10,d=Math.round(u/n*1e3)/10;return{avgWpm:g,peakWpmLifetime:p,avgAccPct:d,sessionsInWindow:s,days:t}}function A(e,t,o=new Date){let r=[],i=new Map;for(let m of e){let u=m.ts.slice(0,10),s=i.get(u)??[];s.push(m),i.set(u,s)}let n=new Date(o);n.setUTCDate(n.getUTCDate()-(t-1));for(let m=0;m<t;m++){let u=n.toISOString().slice(0,10),s=i.get(u)??[];if(s.length===0)r.push({date:u,wpm:0,accuracy:0,sessions:0});else{let p=s.reduce((d,a)=>d+b(a),0)/s.length,g=s.reduce((d,a)=>d+y(a),0)/s.length;r.push({date:u,wpm:p,accuracy:g,sessions:s.length})}n.setUTCDate(n.getUTCDate()+1)}return r}export{k as a,C as b,b as c,y as d,R as e,v as f,I as g,P as h,A as i};
|
|
2
|
+
//# sourceMappingURL=chunk-GULN5HRV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/domain/stats.ts"],"sourcesContent":["import { z } from 'zod';\nimport { paths } from '../infra/paths.js';\nimport { appendJsonl, readJsonl } from '../infra/fs-store.js';\n\nexport const SessionRecordSchema = z.object({\n ts: z.string(),\n dictId: z.string(),\n chapter: z.number().int().nonnegative(),\n mode: z.string(),\n wordCount: z.number().int().nonnegative(),\n errors: z.number().int().nonnegative(),\n durationMs: z.number().int().nonnegative(),\n perWordErrors: z.record(z.string(), z.number().int().nonnegative()).default({}),\n});\n\nexport type SessionRecord = z.infer<typeof SessionRecordSchema>;\n\nexport async function appendSession(record: SessionRecord): Promise<void> {\n await appendJsonl(paths.stats, record);\n}\n\nexport async function loadSessions(): Promise<SessionRecord[]> {\n const rows = await readJsonl<unknown>(paths.stats);\n return rows\n .map((r) => SessionRecordSchema.safeParse(r))\n .filter((r): r is { success: true; data: SessionRecord } => r.success)\n .map((r) => r.data);\n}\n\nexport function computeWPM(record: SessionRecord): number {\n if (record.durationMs === 0) return 0;\n const minutes = record.durationMs / 60000;\n return Math.round((record.wordCount / minutes) * 10) / 10;\n}\n\nexport function accuracy(record: SessionRecord): number {\n // Word-level: fraction of words finished without any mistake (first-try rate).\n if (record.wordCount === 0) return 1;\n const wordsWithErrors = Object.keys(record.perWordErrors).length;\n return Math.max(0, Math.min(1, (record.wordCount - wordsWithErrors) / record.wordCount));\n}\n\n// Resume target for a fresh entry into practice for dictId.\n// stats.jsonl is append-only in time order, so the last matching row is the\n// most recently practiced chapter; advance one to land on the next.\n// Returns 0 if the user has never practiced this dict.\n// PracticeScreen clamps to chapters.length - 1, so overshoot at the end of a\n// dict is harmless.\nexport function resumeChapterFor(sessions: SessionRecord[], dictId: string): number {\n let last: number | null = null;\n for (const s of sessions) if (s.dictId === dictId) last = s.chapter;\n return last === null ? 0 : last + 1;\n}\n\nexport function dailyStreak(sessions: SessionRecord[], now = new Date()): number {\n if (sessions.length === 0) return 0;\n const days = new Set<string>();\n for (const s of sessions) days.add(s.ts.slice(0, 10));\n let streak = 0;\n const cur = new Date(now);\n while (true) {\n const key = cur.toISOString().slice(0, 10);\n if (!days.has(key)) break;\n streak++;\n cur.setUTCDate(cur.getUTCDate() - 1);\n }\n return streak;\n}\n\nconst SPARK = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];\n\nexport function sparkline(values: number[]): string {\n if (values.length === 0) return '';\n const max = Math.max(...values, 1);\n const min = Math.min(...values, 0);\n const range = Math.max(1, max - min);\n return values\n .map((v) => {\n const idx = Math.floor(((v - min) / range) * (SPARK.length - 1));\n return SPARK[Math.max(0, Math.min(SPARK.length - 1, idx))]!;\n })\n .join('');\n}\n\nexport type DailyMetric = 'wpm' | 'accuracy' | 'sessions';\n\nexport function dailyValues(\n sessions: SessionRecord[],\n days: number,\n metric: DailyMetric,\n now = new Date(),\n): number[] {\n const buckets = dailyBuckets(sessions, days, now);\n if (metric === 'wpm') return buckets.map((b) => b.wpm);\n if (metric === 'accuracy') return buckets.map((b) => b.accuracy * 100);\n return buckets.map((b) => b.sessions);\n}\n\nexport type WindowAggregate = {\n avgWpm: number;\n peakWpmLifetime: number;\n avgAccPct: number;\n sessionsInWindow: number;\n days: number;\n};\n\nexport function windowAggregate(\n sessions: SessionRecord[],\n days: number,\n now = new Date(),\n): WindowAggregate {\n if (sessions.length === 0 || days <= 0) {\n return { avgWpm: 0, peakWpmLifetime: 0, avgAccPct: 0, sessionsInWindow: 0, days };\n }\n const cutoff = new Date(now);\n cutoff.setUTCDate(cutoff.getUTCDate() - (days - 1));\n const cutoffKey = cutoff.toISOString().slice(0, 10);\n\n let wordsInWindow = 0;\n let wpmWeightedSum = 0;\n let accWeightedSum = 0;\n let sessionsInWindow = 0;\n let peakWpmLifetime = 0;\n for (const s of sessions) {\n const wpm = computeWPM(s);\n if (wpm > peakWpmLifetime) peakWpmLifetime = wpm;\n if (s.ts.slice(0, 10) < cutoffKey) continue;\n sessionsInWindow++;\n wordsInWindow += s.wordCount;\n wpmWeightedSum += wpm * s.wordCount;\n accWeightedSum += accuracy(s) * s.wordCount;\n }\n if (wordsInWindow === 0) {\n return { avgWpm: 0, peakWpmLifetime, avgAccPct: 0, sessionsInWindow, days };\n }\n const avgWpm = Math.round((wpmWeightedSum / wordsInWindow) * 10) / 10;\n const avgAccPct = Math.round((accWeightedSum / wordsInWindow) * 1000) / 10;\n return { avgWpm, peakWpmLifetime, avgAccPct, sessionsInWindow, days };\n}\n\nexport type DailyBucket = { date: string; wpm: number; accuracy: number; sessions: number };\n\nexport function dailyBuckets(sessions: SessionRecord[], days: number, now = new Date()): DailyBucket[] {\n const out: DailyBucket[] = [];\n const byDay = new Map<string, SessionRecord[]>();\n for (const s of sessions) {\n const key = s.ts.slice(0, 10);\n const arr = byDay.get(key) ?? [];\n arr.push(s);\n byDay.set(key, arr);\n }\n const cur = new Date(now);\n cur.setUTCDate(cur.getUTCDate() - (days - 1));\n for (let i = 0; i < days; i++) {\n const key = cur.toISOString().slice(0, 10);\n const todays = byDay.get(key) ?? [];\n if (todays.length === 0) {\n out.push({ date: key, wpm: 0, accuracy: 0, sessions: 0 });\n } else {\n const wpm = todays.reduce((a, s) => a + computeWPM(s), 0) / todays.length;\n const acc = todays.reduce((a, s) => a + accuracy(s), 0) / todays.length;\n out.push({ date: key, wpm, accuracy: acc, sessions: todays.length });\n }\n cur.setUTCDate(cur.getUTCDate() + 1);\n }\n return out;\n}\n"],"mappings":"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","names":["z","SessionRecordSchema","z","appendSession","record","appendJsonl","paths","loadSessions","readJsonl","r","computeWPM","minutes","accuracy","wordsWithErrors","resumeChapterFor","sessions","dictId","last","s","dailyStreak","now","days","streak","cur","key","SPARK","sparkline","values","max","min","range","v","idx","windowAggregate","sessions","days","now","cutoff","cutoffKey","wordsInWindow","wpmWeightedSum","accWeightedSum","sessionsInWindow","peakWpmLifetime","s","wpm","computeWPM","accuracy","avgWpm","avgAccPct","dailyBuckets","out","byDay","key","arr","cur","i","todays","a","acc"]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{a as L,b as R,c as I}from"./chunk-6ROGUGNX.js";import{a as C,b as k}from"./chunk-CQRKGMPU.js";import{b as $,e as z}from"./chunk-GULN5HRV.js";import{a as B,b as D,c as w,e as H}from"./chunk-QG7ZTS2G.js";import{a as P,b as M,c as E,d as N,f as a}from"./chunk-FQ2MEK7M.js";var _="\x1B[?1049h\x1B[?25l\x1B[2J\x1B[H",F="\x1B[?25h\x1B[?1049l",v=!1,W=!1;function O(){if(W)return;W=!0;let e=()=>{if(v){try{process.stdout.write(F)}catch{}v=!1}};process.once("exit",e),process.once("SIGINT",()=>{e(),process.exit(130)}),process.once("SIGTERM",()=>{e(),process.exit(143)})}function Y(){v||process.stdout.isTTY&&process.env.QWERTY_NO_ALTSCREEN!=="1"&&(O(),process.stdout.write(_),v=!0)}function A(){if(v){try{process.stdout.write(F)}catch{}v=!1}}import{Suspense as ne,lazy as T,useRef as ie}from"react";import{Box as se,Text as ae,useApp as ce,useInput as ue}from"ink";import{useEffect as Q,useState as X}from"react";import{Box as U,useStdout as Z}from"ink";import{jsx as ee}from"react/jsx-runtime";function j({children:e}){let{stdout:t}=Z(),[s,r]=X(()=>({rows:t?.rows??24,cols:t?.columns??80}));return Q(()=>{Y();let n=()=>{r({rows:process.stdout.rows??24,cols:process.stdout.columns??80})};return process.stdout.on("resize",n),()=>{process.stdout.off("resize",n),A()}},[]),ee(U,{width:s.cols,height:s.rows,flexDirection:"column",children:e})}import{useState as te}from"react";import{Box as y,Text as f,useApp as re,useInput as oe}from"ink";import{jsx as g,jsxs as x}from"react/jsx-runtime";function q({cfg:e}){let[t,s]=te(0),{exit:r}=re(),n=M(),h=R(),p=N(),l=D(e.defaultDict),o=p.mainMenu.items,S=async c=>{if(!e.defaultDict){n.navigate({name:"dict",params:{pickerMode:"choose-then-practice"}});return}let m=z(await $(),e.defaultDict);if(c){I({type:"stealth",dictId:e.defaultDict,chapterIndex:m,mode:e.defaultMode}),r();return}n.navigate({name:"practice",params:{dictId:e.defaultDict,chapterIndex:m,mode:e.defaultMode,stealth:c}})},d=[{key:"p",label:o.practiceLabel,hint:e.defaultDict?o.practiceHintWith(H(l,24)):o.practiceHintNone,run:()=>{S(e.stealth==="default")}}];(e.stealth==="menu"||e.stealth==="default")&&d.push({key:"b",label:o.stealthLabel,hint:o.stealthHint,run:()=>{S(!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 J=Math.max(...d.map(c=>w(c.label)))+4;return oe((c,m)=>{if(m.escape){r();return}if(m.upArrow&&s(u=>(u-1+d.length)%d.length),m.downArrow&&s(u=>(u+1)%d.length),m.return){d[t].run();return}if(c==="?"){n.navigate({name:"help"});return}for(let u of d)if(c===u.key){u.run();return}}),x(y,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",children:[e.stealth!=="default"&&x(y,{children:[g(f,{bold:!0,color:a.accent,children:p.app.title}),x(f,{color:a.muted,children:[" \xB7 ",p.app.subtitle]})]}),g(y,{marginTop:e.stealth==="default"?0:2,flexDirection:"column",children:d.map((c,m)=>{let u=m===t,K=" ".repeat(Math.max(0,J-w(c.label)));return x(y,{children:[g(f,{color:u?a.accent:a.muted,children:u?"\u258C ":" "}),x(f,{color:u?a.accent:a.muted,children:["[",c.key,"]"]}),g(f,{children:" "}),x(f,{bold:u,color:u?a.text:a.muted,children:[c.label,K]}),g(f,{color:a.muted,children:c.hint})]},c.key)})}),g(y,{marginTop:2,children:x(f,{color:a.muted,children:[p.mainMenu.hint," \xB7 ",p.mainMenu.helpHint]})}),h.warning&&g(y,{marginTop:1,children:g(f,{color:a.warning,children:p.audio.noPlayer})})]})}import{jsx as i}from"react/jsx-runtime";var pe=T(()=>import("./PracticeScreen-PWXXG33U.js").then(e=>({default:e.PracticeScreen}))),le=T(()=>import("./DictBrowser-ZQ773KQV.js").then(e=>({default:e.DictBrowser}))),de=T(()=>import("./ConfigEditor-ZJ52VG3I.js").then(e=>({default:e.ConfigEditor}))),me=T(()=>import("./StatsViewer-XARAMVZW.js").then(e=>({default:e.StatsViewer}))),fe=T(()=>import("./WordLookup-LKBEPDTB.js").then(e=>({default:e.WordLookup}))),he=T(()=>import("./HelpScreen-Y3TWEEWZ.js").then(e=>({default:e.HelpScreen})));function ge(){return i(se,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:i(ae,{color:a.muted,children:"\u2026"})})}function Xe({initial:e,initialCfg:t,inline:s=!1}){return i(C,{initialCfg:t,children:i(xe,{children:i(B,{children:i(L,{disabled:!t.sounds.master,children:i(P,{initial:e,children:s?i(V,{inline:!0}):i(j,{children:i(V,{})})})})})})})}function xe({children:e}){let{cfg:t}=k();return i(E,{pref:t.language,children:e})}function be(e){if(e.name==="practice"){let t=e.params;return`practice:${t.dictId}:${t.chapterIndex}:${t.mode}:${t.stealth?"s":"n"}`}return e.name}function V({inline:e=!1}){let t=M(),{cfg:s}=k(),{exit:r}=ce(),n=ie(null);ue((o,S)=>{S.ctrl&&o==="c"&&r()});let h=t.current,p=be(h);n.current!==p&&(!e&&process.stdout.isTTY&&process.stdout.write("\x1B[2J\x1B[H"),n.current=p);let l=(()=>{switch(h.name){case"main":return i(q,{cfg:s});case"practice":return i(pe,{params:h.params});case"dict":return i(le,{params:h.params});case"config":return i(de,{});case"stats":return i(me,{});case"word":return i(fe,{});case"help":return i(he,{})}})();return i(ne,{fallback:i(ge,{}),children:l})}import b from"chalk";import Se from"boxen";function nt(){A()}function G(e,t){let s=Math.floor(e/1e3),r=Math.floor(s/60),n=s%60;return t==="zh"?r===0?`${n} \u79D2`:`${r} \u5206 ${n} \u79D2`:r===0?`${n}s`:`${r}m ${n}s`}function we(e){return e>=90?b.green:e<75?b.dim:t=>t}function ve(e,t,s){let r=[],n=[t.report.duration];e.chaptersCompleted===0?n.push(t.report.notPracticed):(n.push(t.report.practiced,t.report.chapters,t.report.words,t.report.accuracy,t.report.wpm),e.newMistakeWords>0&&n.push(t.report.newMistakes));let h=Math.max(...n.map(w)),p=o=>o+" ".repeat(Math.max(0,h-w(o))),l=(o,S)=>`${b.dim(p(o))} ${S}`;if(r.push(l(t.report.duration,G(e.totalDurationMs,s))),e.chaptersCompleted===0)r.push(b.dim(t.report.notPracticed));else{r.push(l(t.report.practiced,G(e.practiceMs,s))),r.push(l(t.report.chapters,String(e.chaptersCompleted))),r.push(l(t.report.words,String(e.wordCount)));let o=Math.round(e.accuracy*1e3)/10;r.push(l(t.report.accuracy,we(o)(`${o}%`))),r.push(l(t.report.wpm,String(e.wpm))),e.newMistakeWords>0&&r.push(l(t.report.newMistakes,b.red(String(e.newMistakeWords))))}return r.push(""),r.push(b.dim.italic(t.report.farewell)),r}function it(e,t,s){if(e.startedAt===null&&e.chaptersCompleted===0)return;let r=ve(e,t,s).join(`
|
|
2
|
+
`);console.log(Se(r,{title:b.bold.cyan(t.report.title),titleAlignment:"left",borderStyle:"round",borderColor:"gray",padding:{top:0,bottom:0,left:3,right:3},margin:{top:1,bottom:1,left:2,right:0}}))}export{Y as a,Xe as b,nt as c,it as d};
|
|
3
|
+
//# sourceMappingURL=chunk-TCYEMBFW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/util/altscreen.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","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 { loadSessions, resumeChapterFor } from '../../domain/stats.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 = async (stealth: boolean) => {\n if (!cfg.defaultDict) {\n nav.navigate({ name: 'dict', params: { pickerMode: 'choose-then-practice' } });\n return;\n }\n const chapterIndex = resumeChapterFor(await loadSessions(), cfg.defaultDict);\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) {\n setPostExitAction({\n type: 'stealth',\n dictId: cfg.defaultDict,\n chapterIndex,\n mode: cfg.defaultMode,\n });\n exit();\n return;\n }\n nav.navigate({\n name: 'practice',\n params: {\n dictId: cfg.defaultDict,\n chapterIndex,\n mode: cfg.defaultMode,\n stealth,\n },\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: () => void 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: () => void 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 {cfg.stealth !== 'default' && (\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\n <Box marginTop={cfg.stealth === 'default' ? 0 : 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: 0, bottom: 0, left: 3, right: 3 },\n margin: { top: 1, bottom: 1, left: 2, right: 0 },\n }),\n );\n}\n"],"mappings":"qRAKA,IAAMA,EAAQ,oCACRC,EAAQ,uBAEVC,EAAS,GACTC,EAAoB,GAExB,SAASC,GAAsB,CAC7B,GAAID,EAAmB,OACvBA,EAAoB,GACpB,IAAME,EAAU,IAAM,CACpB,GAAKH,EACL,IAAI,CACF,QAAQ,OAAO,MAAMD,CAAK,CAC5B,MAAQ,CAER,CACAC,EAAS,GACX,EACA,QAAQ,KAAK,OAAQG,CAAO,EAC5B,QAAQ,KAAK,SAAU,IAAM,CAC3BA,EAAQ,EACR,QAAQ,KAAK,GAAG,CAClB,CAAC,EACD,QAAQ,KAAK,UAAW,IAAM,CAC5BA,EAAQ,EACR,QAAQ,KAAK,GAAG,CAClB,CAAC,CACH,CAEO,SAASC,GAAuB,CACjCJ,GACC,QAAQ,OAAO,OAChB,QAAQ,IAAI,sBAAwB,MACxCE,EAAc,EACd,QAAQ,OAAO,MAAMJ,CAAK,EAC1BE,EAAS,GACX,CAEO,SAASK,GAAuB,CACrC,GAAKL,EACL,IAAI,CACF,QAAQ,OAAO,MAAMD,CAAK,CAC5B,MAAQ,CAER,CACAC,EAAS,GACX,CCnDA,OAAS,YAAAM,GAAU,QAAAC,EAAM,UAAAC,OAA8B,QACvD,OAAS,OAAAC,GAAK,QAAAC,GAAM,UAAAC,GAAQ,YAAAC,OAAgB,MCD5C,OAAS,aAAAC,EAAW,YAAAC,MAAgC,QACpD,OAAS,OAAAC,EAAK,aAAAC,MAAiB,MA6B3B,cAAAC,OAAA,oBA1BG,SAASC,EAAW,CAAE,SAAAC,CAAS,EAA4B,CAChE,GAAM,CAAE,OAAAC,CAAO,EAAIC,EAAU,EACvB,CAACC,EAAMC,CAAO,EAAIC,EAAS,KAAO,CACtC,KAAMJ,GAAQ,MAAQ,GACtB,KAAMA,GAAQ,SAAW,EAC3B,EAAE,EAEF,OAAAK,EAAU,IAAM,CAKdC,EAAe,EAEf,IAAMC,EAAW,IAAM,CACrBJ,EAAQ,CAAE,KAAM,QAAQ,OAAO,MAAQ,GAAI,KAAM,QAAQ,OAAO,SAAW,EAAG,CAAC,CACjF,EACA,eAAQ,OAAO,GAAG,SAAUI,CAAQ,EAE7B,IAAM,CACX,QAAQ,OAAO,IAAI,SAAUA,CAAQ,EACrCC,EAAe,CACjB,CACF,EAAG,CAAC,CAAC,EAGHX,GAACY,EAAA,CAAI,MAAOP,EAAK,KAAM,OAAQA,EAAK,KAAM,cAAc,SACrD,SAAAH,EACH,CAEJ,CClCA,OAAS,YAAAW,OAAgB,QACzB,OAAS,OAAAC,EAAK,QAAAC,EAAM,UAAAC,GAAQ,YAAAC,OAAgB,MA+GlC,cAAAC,EAGA,QAAAC,MAHA,oBAjGH,SAASC,EAAS,CAAE,IAAAC,CAAI,EAAoB,CACjD,GAAM,CAACC,EAAUC,CAAW,EAAIC,GAAS,CAAC,EACpC,CAAE,KAAAC,CAAK,EAAIC,GAAO,EAClBC,EAAMC,EAAO,EACbC,EAAQC,EAAe,EACvBC,EAAIC,EAAW,EACfC,EAAkBC,EAAYb,EAAI,WAAW,EAE7Cc,EAAIJ,EAAE,SAAS,MACfK,EAAgB,MAAOC,GAAqB,CAChD,GAAI,CAAChB,EAAI,YAAa,CACpBM,EAAI,SAAS,CAAE,KAAM,OAAQ,OAAQ,CAAE,WAAY,sBAAuB,CAAE,CAAC,EAC7E,MACF,CACA,IAAMW,EAAeC,EAAiB,MAAMC,EAAa,EAAGnB,EAAI,WAAW,EAK3E,GAAIgB,EAAS,CACXI,EAAkB,CAChB,KAAM,UACN,OAAQpB,EAAI,YACZ,aAAAiB,EACA,KAAMjB,EAAI,WACZ,CAAC,EACDI,EAAK,EACL,MACF,CACAE,EAAI,SAAS,CACX,KAAM,WACN,OAAQ,CACN,OAAQN,EAAI,YACZ,aAAAiB,EACA,KAAMjB,EAAI,YACV,QAAAgB,CACF,CACF,CAAC,CACH,EAEMK,EAAgB,CACpB,CACE,IAAK,IACL,MAAOP,EAAE,cACT,KAAMd,EAAI,YACNc,EAAE,iBAAiBQ,EAAaV,EAAiB,EAAE,CAAC,EACpDE,EAAE,iBACN,IAAK,IAAG,CAAQC,EAAcf,EAAI,UAAY,SAAS,EACzD,CACF,GAEIA,EAAI,UAAY,QAAUA,EAAI,UAAY,YAC5CqB,EAAM,KAAK,CACT,IAAK,IACL,MAAOP,EAAE,aACT,KAAMA,EAAE,YACR,IAAK,IAAG,CAAQC,EAAc,EAAI,EACpC,CAAC,EAGHM,EAAM,KACJ,CAAE,IAAK,IAAK,MAAOP,EAAE,UAAW,KAAMA,EAAE,SAAU,IAAK,IAAMR,EAAI,SAAS,CAAE,KAAM,MAAO,CAAC,CAAE,EAC5F,CAAE,IAAK,IAAK,MAAOQ,EAAE,UAAW,KAAMA,EAAE,SAAU,IAAK,IAAMR,EAAI,SAAS,CAAE,KAAM,MAAO,CAAC,CAAE,EAC5F,CAAE,IAAK,IAAK,MAAOQ,EAAE,WAAY,KAAMA,EAAE,UAAW,IAAK,IAAMR,EAAI,SAAS,CAAE,KAAM,OAAQ,CAAC,CAAE,EAC/F,CAAE,IAAK,IAAK,MAAOQ,EAAE,YAAa,KAAMA,EAAE,WAAY,IAAK,IAAMR,EAAI,SAAS,CAAE,KAAM,QAAS,CAAC,CAAE,EAClG,CAAE,IAAK,IAAK,MAAOQ,EAAE,UAAW,KAAMA,EAAE,SAAU,IAAK,IAAMV,EAAK,CAAE,CACtE,EAEA,IAAMmB,EAAS,KAAK,IAAI,GAAGF,EAAM,IAAKG,GAAOC,EAAaD,EAAG,KAAK,CAAC,CAAC,EAAI,EAExE,OAAAE,GAAS,CAACC,EAAOC,IAAQ,CACvB,GAAIA,EAAI,OAAQ,CACdxB,EAAK,EACL,MACF,CAGA,GAFIwB,EAAI,SAAS1B,EAAa2B,IAAOA,EAAI,EAAIR,EAAM,QAAUA,EAAM,MAAM,EACrEO,EAAI,WAAW1B,EAAa2B,IAAOA,EAAI,GAAKR,EAAM,MAAM,EACxDO,EAAI,OAAQ,CACdP,EAAMpB,CAAQ,EAAG,IAAI,EACrB,MACF,CACA,GAAI0B,IAAU,IAAK,CACjBrB,EAAI,SAAS,CAAE,KAAM,MAAO,CAAC,EAC7B,MACF,CACA,QAAWkB,KAAMH,EACf,GAAIM,IAAUH,EAAG,IAAK,CACpBA,EAAG,IAAI,EACP,MACF,CAEJ,CAAC,EAGC1B,EAACgC,EAAA,CAAI,cAAc,SAAS,SAAU,EAAG,SAAU,EAAG,MAAM,OACzD,UAAA9B,EAAI,UAAY,WACfF,EAACgC,EAAA,CACC,UAAAjC,EAACkC,EAAA,CAAK,KAAI,GAAC,MAAOC,EAAQ,OACvB,SAAAtB,EAAE,IAAI,MACT,EACAZ,EAACiC,EAAA,CAAK,MAAOC,EAAQ,MAAO,qBAAMtB,EAAE,IAAI,UAAS,GACnD,EAGFb,EAACiC,EAAA,CAAI,UAAW9B,EAAI,UAAY,UAAY,EAAI,EAAG,cAAc,SAC9D,SAAAqB,EAAM,IAAI,CAACG,EAAIK,IAAM,CACpB,IAAMI,EAASJ,IAAM5B,EACfiC,EAAM,IAAI,OAAO,KAAK,IAAI,EAAGX,EAASE,EAAaD,EAAG,KAAK,CAAC,CAAC,EACnE,OACE1B,EAACgC,EAAA,CACC,UAAAjC,EAACkC,EAAA,CAAK,MAAOE,EAASD,EAAQ,OAASA,EAAQ,MAAQ,SAAAC,EAAS,UAAO,KAAK,EAC5EnC,EAACiC,EAAA,CAAK,MAAOE,EAASD,EAAQ,OAASA,EAAQ,MAAO,cAAER,EAAG,IAAI,KAAC,EAChE3B,EAACkC,EAAA,CAAM,aAAI,EACXjC,EAACiC,EAAA,CAAK,KAAME,EAAQ,MAAOA,EAASD,EAAQ,KAAOA,EAAQ,MACxD,UAAAR,EAAG,MACHU,GACH,EACArC,EAACkC,EAAA,CAAK,MAAOC,EAAQ,MAAQ,SAAAR,EAAG,KAAK,IAR7BA,EAAG,GASb,CAEJ,CAAC,EACH,EAEA3B,EAACiC,EAAA,CAAI,UAAW,EACd,SAAAhC,EAACiC,EAAA,CAAK,MAAOC,EAAQ,MAClB,UAAAtB,EAAE,SAAS,KACX,WACAA,EAAE,SAAS,UACd,EACF,EAECF,EAAM,SACLX,EAACiC,EAAA,CAAI,UAAW,EACd,SAAAjC,EAACkC,EAAA,CAAK,MAAOC,EAAQ,QAAU,SAAAtB,EAAE,MAAM,SAAS,EAClD,GAEJ,CAEJ,CFnHM,cAAAyB,MAAA,oBAtBN,IAAMC,GAAiBC,EAAK,IAC1B,OAAO,8BAA6B,EAAE,KAAMC,IAAO,CAAE,QAASA,EAAE,cAAe,EAAE,CACnF,EACMC,GAAcF,EAAK,IACvB,OAAO,2BAA0B,EAAE,KAAMC,IAAO,CAAE,QAASA,EAAE,WAAY,EAAE,CAC7E,EACME,GAAeH,EAAK,IACxB,OAAO,4BAA2B,EAAE,KAAMC,IAAO,CAAE,QAASA,EAAE,YAAa,EAAE,CAC/E,EACMG,GAAcJ,EAAK,IACvB,OAAO,2BAA0B,EAAE,KAAMC,IAAO,CAAE,QAASA,EAAE,WAAY,EAAE,CAC7E,EACMI,GAAaL,EAAK,IACtB,OAAO,0BAAyB,EAAE,KAAMC,IAAO,CAAE,QAASA,EAAE,UAAW,EAAE,CAC3E,EACMK,GAAaN,EAAK,IACtB,OAAO,0BAAyB,EAAE,KAAMC,IAAO,CAAE,QAASA,EAAE,UAAW,EAAE,CAC3E,EAEA,SAASM,IAAe,CACtB,OACET,EAACU,GAAA,CAAI,WAAW,SAAS,eAAe,SAAS,MAAM,OAAO,OAAO,OACnE,SAAAV,EAACW,GAAA,CAAK,MAAOC,EAAQ,MAAO,kBAAC,EAC/B,CAEJ,CAEO,SAASC,GAAI,CAClB,QAAAC,EACA,WAAAC,EACA,OAAAC,EAAS,EACX,EAIG,CACD,OACEhB,EAACiB,EAAA,CAAiB,WAAYF,EAC5B,SAAAf,EAACkB,GAAA,CACC,SAAAlB,EAACmB,EAAA,CACC,SAAAnB,EAACoB,EAAA,CAAoB,SAAU,CAACL,EAAW,OAAO,OAChD,SAAAf,EAACqB,EAAA,CAAY,QAASP,EACnB,SAAAE,EAAShB,EAACsB,EAAA,CAAO,OAAM,GAAC,EAAKtB,EAACuB,EAAA,CAAW,SAAAvB,EAACsB,EAAA,EAAO,EAAE,EACtD,EACF,EACF,EACF,EACF,CAEJ,CAEA,SAASJ,GAAW,CAAE,SAAAM,CAAS,EAA4B,CACzD,GAAM,CAAE,IAAAC,CAAI,EAAIC,EAAY,EAC5B,OAAO1B,EAAC2B,EAAA,CAAgB,KAAMF,EAAI,SAAW,SAAAD,EAAS,CACxD,CAEA,SAASI,GAAUC,EAA4B,CAC7C,GAAIA,EAAM,OAAS,WAAY,CAC7B,IAAMC,EAAID,EAAM,OAChB,MAAO,YAAYC,EAAE,MAAM,IAAIA,EAAE,YAAY,IAAIA,EAAE,IAAI,IAAIA,EAAE,QAAU,IAAM,GAAG,EAClF,CACA,OAAOD,EAAM,IACf,CAEA,SAASP,EAAO,CAAE,OAAAN,EAAS,EAAM,EAAyB,CACxD,IAAMe,EAAMC,EAAO,EACb,CAAE,IAAAP,CAAI,EAAIC,EAAY,EACtB,CAAE,KAAAO,CAAK,EAAIC,GAAO,EAClBC,EAAaC,GAAsB,IAAI,EAE7CC,GAAS,CAACC,EAAOC,IAAQ,CACnBA,EAAI,MAAQD,IAAU,KAAKL,EAAK,CACtC,CAAC,EAED,IAAMJ,EAAQE,EAAI,QACZQ,EAAMX,GAAUC,CAAK,EACvBM,EAAW,UAAYI,IACrB,CAACvB,GAAU,QAAQ,OAAO,OAAO,QAAQ,OAAO,MAAM,eAAe,EACzEmB,EAAW,QAAUI,GAGvB,IAAMC,GAAU,IAAM,CACpB,OAAQX,EAAM,KAAM,CAClB,IAAK,OACH,OAAO7B,EAACyC,EAAA,CAAS,IAAKhB,EAAK,EAC7B,IAAK,WACH,OAAOzB,EAACC,GAAA,CAAe,OAAQ4B,EAAM,OAAQ,EAC/C,IAAK,OACH,OAAO7B,EAACI,GAAA,CAAY,OAAQyB,EAAM,OAAQ,EAC5C,IAAK,SACH,OAAO7B,EAACK,GAAA,EAAa,EACvB,IAAK,QACH,OAAOL,EAACM,GAAA,EAAY,EACtB,IAAK,OACH,OAAON,EAACO,GAAA,EAAW,EACrB,IAAK,OACH,OAAOP,EAACQ,GAAA,EAAW,CACvB,CACF,GAAG,EAEH,OAAOR,EAAC0C,GAAA,CAAS,SAAU1C,EAACS,GAAA,EAAa,EAAK,SAAA+B,EAAO,CACvD,CGrHA,OAAOG,MAAW,QAClB,OAAOC,OAAW,QAMX,SAASC,IAAyB,CACvCC,EAAe,CACjB,CAEA,SAASC,EAAYC,EAAYC,EAA2B,CAC1D,IAAMC,EAAQ,KAAK,MAAMF,EAAK,GAAI,EAC5BG,EAAI,KAAK,MAAMD,EAAQ,EAAE,EACzBE,EAAIF,EAAQ,GAClB,OAAID,IAAS,KACPE,IAAM,EAAU,GAAGC,CAAC,UACjB,GAAGD,CAAC,WAAMC,CAAC,UAEhBD,IAAM,EAAU,GAAGC,CAAC,IACjB,GAAGD,CAAC,KAAKC,CAAC,GACnB,CAEA,SAASC,GAASC,EAAoC,CACpD,OAAIA,GAAO,GAAWC,EAAM,MACxBD,EAAM,GAAWC,EAAM,IACnBH,GAAMA,CAChB,CAEA,SAASI,GAAWC,EAAkB,EAAYR,EAA6B,CAC7E,IAAMS,EAAkB,CAAC,EAInBC,EAAsB,CAAC,EAAE,OAAO,QAAQ,EAC1CF,EAAE,oBAAsB,EAC1BE,EAAU,KAAK,EAAE,OAAO,YAAY,GAEpCA,EAAU,KACR,EAAE,OAAO,UACT,EAAE,OAAO,SACT,EAAE,OAAO,MACT,EAAE,OAAO,SACT,EAAE,OAAO,GACX,EACIF,EAAE,gBAAkB,GAAGE,EAAU,KAAK,EAAE,OAAO,WAAW,GAEhE,IAAMC,EAAS,KAAK,IAAI,GAAGD,EAAU,IAAIE,CAAY,CAAC,EAChDC,EAAYV,GAChBA,EAAI,IAAI,OAAO,KAAK,IAAI,EAAGQ,EAASC,EAAaT,CAAC,CAAC,CAAC,EAChDW,EAAM,CAACC,EAAeC,IAC1B,GAAGV,EAAM,IAAIO,EAASE,CAAK,CAAC,CAAC,MAAMC,CAAK,GAI1C,GAFAP,EAAM,KAAKK,EAAI,EAAE,OAAO,SAAUhB,EAAYU,EAAE,gBAAiBR,CAAI,CAAC,CAAC,EAEnEQ,EAAE,oBAAsB,EAC1BC,EAAM,KAAKH,EAAM,IAAI,EAAE,OAAO,YAAY,CAAC,MACtC,CACLG,EAAM,KAAKK,EAAI,EAAE,OAAO,UAAWhB,EAAYU,EAAE,WAAYR,CAAI,CAAC,CAAC,EACnES,EAAM,KAAKK,EAAI,EAAE,OAAO,SAAU,OAAON,EAAE,iBAAiB,CAAC,CAAC,EAC9DC,EAAM,KAAKK,EAAI,EAAE,OAAO,MAAO,OAAON,EAAE,SAAS,CAAC,CAAC,EACnD,IAAMS,EAAS,KAAK,MAAMT,EAAE,SAAW,GAAI,EAAI,GAC/CC,EAAM,KAAKK,EAAI,EAAE,OAAO,SAAUV,GAASa,CAAM,EAAE,GAAGA,CAAM,GAAG,CAAC,CAAC,EACjER,EAAM,KAAKK,EAAI,EAAE,OAAO,IAAK,OAAON,EAAE,GAAG,CAAC,CAAC,EACvCA,EAAE,gBAAkB,GACtBC,EAAM,KAAKK,EAAI,EAAE,OAAO,YAAaR,EAAM,IAAI,OAAOE,EAAE,eAAe,CAAC,CAAC,CAAC,CAE9E,CAEA,OAAAC,EAAM,KAAK,EAAE,EACbA,EAAM,KAAKH,EAAM,IAAI,OAAO,EAAE,OAAO,QAAQ,CAAC,EACvCG,CACT,CAEO,SAASS,GAAmBV,EAAkB,EAAYR,EAAyB,CACxF,GAAIQ,EAAE,YAAc,MAAQA,EAAE,oBAAsB,EAAG,OACvD,IAAMW,EAAUZ,GAAWC,EAAG,EAAGR,CAAI,EAAE,KAAK;AAAA,CAAI,EAChD,QAAQ,IACNoB,GAAMD,EAAS,CACb,MAAOb,EAAM,KAAK,KAAK,EAAE,OAAO,KAAK,EACrC,eAAgB,OAChB,YAAa,QACb,YAAa,OACb,QAAS,CAAE,IAAK,EAAG,OAAQ,EAAG,KAAM,EAAG,MAAO,CAAE,EAChD,OAAQ,CAAE,IAAK,EAAG,OAAQ,EAAG,KAAM,EAAG,MAAO,CAAE,CACjD,CAAC,CACH,CACF","names":["ENTER","LEAVE","active","signalsRegistered","ensureSignals","cleanup","enterAltScreen","leaveAltScreen","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","chapterIndex","resumeChapterFor","loadSessions","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","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 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-3O5SL5QY.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-3O5SL5QY.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-3O5SL5QY.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-JAQ2GXCS.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-JAQ2GXCS.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-JAQ2GXCS.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-JAQ2GXCS.js");await n(t,a)}),o.command("rm <id>").description("Remove a local dictionary").action(async t=>{let{dictRemove:a}=await import("./dict.impl-JAQ2GXCS.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-XVZCEC2O.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-
|
|
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-3O5SL5QY.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-3O5SL5QY.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-3O5SL5QY.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-JAQ2GXCS.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-JAQ2GXCS.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-JAQ2GXCS.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-JAQ2GXCS.js");await n(t,a)}),o.command("rm <id>").description("Remove a local dictionary").action(async t=>{let{dictRemove:a}=await import("./dict.impl-JAQ2GXCS.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-XVZCEC2O.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-AVTRDL7X.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-WX3BFWI3.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-CUBY4EZ4.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-AVTRDL7X.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-HBP6CFSW.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.18");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
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as o,b as s,c as p,d as c}from"./chunk-TCYEMBFW.js";import{d as n,g as a,i as m}from"./chunk-6ROGUGNX.js";import"./chunk-BIBS2Q3E.js";import"./chunk-CQRKGMPU.js";import{a as i}from"./chunk-7LTZGB7F.js";import"./chunk-GULN5HRV.js";import"./chunk-QG7ZTS2G.js";import{e}from"./chunk-FQ2MEK7M.js";import"./chunk-NA5UNUVL.js";import"./chunk-E6BBQALJ.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-AVTRDL7X.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-HBP6CFSW.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":"sWAAA,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,b as g,c as h,d as x}from"./chunk-TCYEMBFW.js";import{f as m,g as l,i as u}from"./chunk-6ROGUGNX.js";import"./chunk-BIBS2Q3E.js";import"./chunk-CQRKGMPU.js";import{a}from"./chunk-7LTZGB7F.js";import{b as f,e as p}from"./chunk-GULN5HRV.js";import"./chunk-QG7ZTS2G.js";import{e as d}from"./chunk-FQ2MEK7M.js";import"./chunk-NA5UNUVL.js";import"./chunk-E6BBQALJ.js";import s from"chalk";import{render as v}from"ink";import{createElement as E}from"react";var C=["order","dictation","review","random","loop"];function P(o){return C.includes(o)}async function Y(o,t){if(!process.stdout.isTTY){console.error(s.red("Practice requires an interactive TTY.")),process.exitCode=1;return}let e=await a(),i=o??e.defaultDict;if(!i){console.error(s.red("No dictionary specified. Pass an id or set config.defaultDict.")),process.exitCode=1;return}let n=t.mode??e.defaultMode;if(!P(n)){console.error(s.red(`Invalid mode "${n}". Valid: ${C.join(", ")}`)),process.exitCode=1;return}let M=t.chapter!==void 0?Math.max(0,Number(t.chapter)-1):p(await f(),i),r=t.stealth===!0||e.stealth==="default";r||c(),l();let{waitUntilExit:S}=v(E(g,{initial:{name:"practice",params:{dictId:i,chapterIndex:M,mode:n,stealth:r}},initialCfg:e,inline:r}),{patchConsole:!1,exitOnCtrlC:!1});if(await S(),r||h(),m()){process.stdout.write("\x1B[3F\x1B[0J");return}if(r)return;let{lang:w,t:b}=d(e.language);x(u(),b,w)}export{Y as runPractice};
|
|
2
|
+
//# sourceMappingURL=practice.impl-AVTRDL7X.js.map
|
|
@@ -0,0 +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 { consumeSilentExit } from '../util/post-exit-action.js';\nimport { loadSessions, resumeChapterFor } from '../domain/stats.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 =\n options.chapter !== undefined\n ? Math.max(0, Number(options.chapter) - 1)\n : resumeChapterFor(await loadSessions(), dictId);\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\n if (consumeSilentExit()) {\n // User hit Ctrl+C on the stealth paused screen — erase the 3 inline rows\n // (paused / Esc back / blank) and skip the session report for a fully\n // clean exit. Cursor goes back to where the paused UI started; the shell\n // prompt then renders there, leaving no trace.\n process.stdout.write('\\x1b[3F\\x1b[0J');\n return;\n }\n if (stealth) {\n // Stealth: StealthSummary already rendered an inline summary row that\n // stays in scrollback. A second ~10-row boxen report would defeat the\n // point of the mode, so skip it.\n return;\n }\n\n const { lang, t } = pickStrings(cfg.language);\n printSessionReport(sessionReport(), t, lang);\n}\n"],"mappings":"yXAAA,OAAOA,MAAW,QAClB,OAAS,UAAAC,MAAc,MACvB,OAAS,iBAAAC,MAAqB,QAW9B,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,EACJN,EAAQ,UAAY,OAChB,KAAK,IAAI,EAAG,OAAOA,EAAQ,OAAO,EAAI,CAAC,EACvCO,EAAiB,MAAMC,EAAa,EAAGJ,CAAM,EAC7CK,EAAUT,EAAQ,UAAY,IAAQE,EAAI,UAAY,UAMvDO,GAASC,EAAe,EAE7BC,EAAa,EACb,GAAM,CAAE,cAAAC,CAAc,EAAIC,EACxBC,EAAcC,EAAK,CACjB,QAAS,CAAE,KAAM,WAAY,OAAQ,CAAE,OAAAX,EAAQ,aAAAE,EAAc,KAAAD,EAAM,QAAAI,CAAQ,CAAE,EAC7E,WAAYP,EACZ,OAAQO,CACV,CAAC,EACD,CAAE,aAAc,GAAO,YAAa,EAAM,CAC5C,EAMA,GALA,MAAMG,EAAc,EACfH,GACHO,EAAiB,EAGfC,EAAkB,EAAG,CAKvB,QAAQ,OAAO,MAAM,gBAAgB,EACrC,MACF,CACA,GAAIR,EAIF,OAGF,GAAM,CAAE,KAAAS,EAAM,EAAAC,CAAE,EAAIC,EAAYlB,EAAI,QAAQ,EAC5CmB,EAAmBC,EAAc,EAAGH,EAAGD,CAAI,CAC7C","names":["chalk","render","createElement","MODES","isMode","v","runPractice","dictIdArg","options","chalk","cfg","loadConfig","dictId","mode","chapterIndex","resumeChapterFor","loadSessions","stealth","enterAltScreen","start","waitUntilExit","render","createElement","App","ensureMainScreen","consumeSilentExit","lang","t","pickStrings","printSessionReport","report"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{a as y,d as h}from"./chunk-G3DQB7FI.js";import{b as m,c as p,d as $,f as g,g as a,i as u}from"./chunk-GULN5HRV.js";import"./chunk-E6BBQALJ.js";import t from"chalk";async function T(d){let l=Math.max(1,Number(d.days)||14),w=Math.max(1,Number(d.top)||10),r=await m(),M=await y();if(r.length===0){console.log(t.yellow("No practice history yet. Run `qwerty practice <dict>` to get started."));return}let e=u(r,l),k=g(r),n=r.reduce((o,s)=>o+s.wordCount,0),b=r.reduce((o,s)=>o+s.errors,0),i=r.reduce((o,s)=>o+s.durationMs,0),f=r.reduce((o,s)=>o+(s.wordCount-Object.keys(s.perWordErrors).length),0),S=i>0?Math.round(n/(i/6e4)*10)/10:0,v=n===0?1:f/n;console.log(t.bold(`
|
|
2
2
|
Lifetime`)),console.log(` ${t.dim("sessions")} ${r.length} ${t.dim("words")} ${n} ${t.dim("errors")} ${b}`),console.log(` ${t.dim("avg wpm")} ${S} ${t.dim("avg accuracy")} ${Math.round(v*1e3)/10}% ${t.dim("streak")} ${t.bold(k)}d`),console.log(t.bold(`
|
|
3
3
|
Last ${l} days`)),console.log(` ${t.dim("wpm ")} ${a(e.map(o=>o.wpm))} ${t.dim("max")} ${Math.round(Math.max(...e.map(o=>o.wpm)))}`),console.log(` ${t.dim("accuracy")} ${a(e.map(o=>o.accuracy*100))} ${t.dim("range")} ${Math.round(Math.min(...e.map(o=>o.accuracy*100)))}-${Math.round(Math.max(...e.map(o=>o.accuracy*100)))}%`),console.log(` ${t.dim("sessions")} ${a(e.map(o=>o.sessions))}`);let x=r.slice(-5).reverse();console.log(t.bold(`
|
|
4
|
-
Last 5 sessions`));for(let o of x){let s=
|
|
4
|
+
Last 5 sessions`));for(let o of x){let s=p(o),E=Math.round($(o)*1e3)/10;console.log(` ${t.dim(o.ts.replace("T"," ").slice(0,16))} ${t.cyan(o.dictId.padEnd(14))} ch${String(o.chapter+1).padStart(3)} ${o.mode.padEnd(9)} ${String(o.wordCount).padStart(3)}w ${o.errors}err ${s}wpm ${E}%`)}let c=h(M,w);if(c.length>0){console.log(t.bold(`
|
|
5
5
|
Top ${c.length} mistakes`));for(let[o,s]of c)console.log(` ${t.red(String(s.count).padStart(3))} ${t.bold(o.padEnd(20))} ${t.dim(s.dictIds.join(", "))}`)}else console.log(t.bold(`
|
|
6
6
|
Top mistakes`)),console.log(t.dim(" none \u2014 keep going"));console.log()}export{T as runStats};
|
|
7
|
-
//# sourceMappingURL=stats.impl-
|
|
7
|
+
//# sourceMappingURL=stats.impl-WX3BFWI3.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/stats.impl.ts"],"sourcesContent":["import chalk from 'chalk';\nimport {\n loadSessions,\n computeWPM,\n accuracy,\n dailyBuckets,\n sparkline,\n dailyStreak,\n} from '../domain/stats.js';\nimport { loadMistakes, topN } from '../domain/mistakes.js';\n\nexport async function runStats(opts: { days: string; top: string }): Promise<void> {\n const days = Math.max(1, Number(opts.days) || 14);\n const topCount = Math.max(1, Number(opts.top) || 10);\n const sessions = await loadSessions();\n const book = await loadMistakes();\n\n if (sessions.length === 0) {\n console.log(\n chalk.yellow('No practice history yet. Run `qwerty practice <dict>` to get started.'),\n );\n return;\n }\n\n const buckets = dailyBuckets(sessions, days);\n const streak = dailyStreak(sessions);\n const totalWords = sessions.reduce((a, s) => a + s.wordCount, 0);\n const totalErrors = sessions.reduce((a, s) => a + s.errors, 0);\n const totalMs = sessions.reduce((a, s) => a + s.durationMs, 0);\n const firstTryWords = sessions.reduce(\n (a, s) => a + (s.wordCount - Object.keys(s.perWordErrors).length),\n 0,\n );\n const overallWpm = totalMs > 0 ? Math.round((totalWords / (totalMs / 60000)) * 10) / 10 : 0;\n const overallAcc = totalWords === 0 ? 1 : firstTryWords / totalWords;\n\n console.log(chalk.bold('\\nLifetime'));\n console.log(\n ` ${chalk.dim('sessions')} ${sessions.length} ${chalk.dim('words')} ${totalWords} ${chalk.dim('errors')} ${totalErrors}`,\n );\n console.log(\n ` ${chalk.dim('avg wpm')} ${overallWpm} ${chalk.dim('avg accuracy')} ${Math.round(overallAcc * 1000) / 10}% ${chalk.dim('streak')} ${chalk.bold(streak)}d`,\n );\n\n console.log(chalk.bold(`\\nLast ${days} days`));\n console.log(\n ` ${chalk.dim('wpm ')} ${sparkline(buckets.map((b) => b.wpm))} ${chalk.dim('max')} ${Math.round(Math.max(...buckets.map((b) => b.wpm)))}`,\n );\n console.log(\n ` ${chalk.dim('accuracy')} ${sparkline(buckets.map((b) => b.accuracy * 100))} ${chalk.dim('range')} ${Math.round(Math.min(...buckets.map((b) => b.accuracy * 100)))}-${Math.round(Math.max(...buckets.map((b) => b.accuracy * 100)))}%`,\n );\n console.log(` ${chalk.dim('sessions')} ${sparkline(buckets.map((b) => b.sessions))}`);\n\n const recent = sessions.slice(-5).reverse();\n console.log(chalk.bold('\\nLast 5 sessions'));\n for (const s of recent) {\n const wpm = computeWPM(s);\n const acc = Math.round(accuracy(s) * 1000) / 10;\n console.log(\n ` ${chalk.dim(s.ts.replace('T', ' ').slice(0, 16))} ${chalk.cyan(s.dictId.padEnd(14))} ch${String(s.chapter + 1).padStart(3)} ${s.mode.padEnd(9)} ${String(s.wordCount).padStart(3)}w ${s.errors}err ${wpm}wpm ${acc}%`,\n );\n }\n\n const top = topN(book, topCount);\n if (top.length > 0) {\n console.log(chalk.bold(`\\nTop ${top.length} mistakes`));\n for (const [word, entry] of top) {\n console.log(\n ` ${chalk.red(String(entry.count).padStart(3))} ${chalk.bold(word.padEnd(20))} ${chalk.dim(entry.dictIds.join(', '))}`,\n );\n }\n } else {\n console.log(chalk.bold('\\nTop mistakes'));\n console.log(chalk.dim(' none — keep going'));\n }\n console.log();\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/commands/stats.impl.ts"],"sourcesContent":["import chalk from 'chalk';\nimport {\n loadSessions,\n computeWPM,\n accuracy,\n dailyBuckets,\n sparkline,\n dailyStreak,\n} from '../domain/stats.js';\nimport { loadMistakes, topN } from '../domain/mistakes.js';\n\nexport async function runStats(opts: { days: string; top: string }): Promise<void> {\n const days = Math.max(1, Number(opts.days) || 14);\n const topCount = Math.max(1, Number(opts.top) || 10);\n const sessions = await loadSessions();\n const book = await loadMistakes();\n\n if (sessions.length === 0) {\n console.log(\n chalk.yellow('No practice history yet. Run `qwerty practice <dict>` to get started.'),\n );\n return;\n }\n\n const buckets = dailyBuckets(sessions, days);\n const streak = dailyStreak(sessions);\n const totalWords = sessions.reduce((a, s) => a + s.wordCount, 0);\n const totalErrors = sessions.reduce((a, s) => a + s.errors, 0);\n const totalMs = sessions.reduce((a, s) => a + s.durationMs, 0);\n const firstTryWords = sessions.reduce(\n (a, s) => a + (s.wordCount - Object.keys(s.perWordErrors).length),\n 0,\n );\n const overallWpm = totalMs > 0 ? Math.round((totalWords / (totalMs / 60000)) * 10) / 10 : 0;\n const overallAcc = totalWords === 0 ? 1 : firstTryWords / totalWords;\n\n console.log(chalk.bold('\\nLifetime'));\n console.log(\n ` ${chalk.dim('sessions')} ${sessions.length} ${chalk.dim('words')} ${totalWords} ${chalk.dim('errors')} ${totalErrors}`,\n );\n console.log(\n ` ${chalk.dim('avg wpm')} ${overallWpm} ${chalk.dim('avg accuracy')} ${Math.round(overallAcc * 1000) / 10}% ${chalk.dim('streak')} ${chalk.bold(streak)}d`,\n );\n\n console.log(chalk.bold(`\\nLast ${days} days`));\n console.log(\n ` ${chalk.dim('wpm ')} ${sparkline(buckets.map((b) => b.wpm))} ${chalk.dim('max')} ${Math.round(Math.max(...buckets.map((b) => b.wpm)))}`,\n );\n console.log(\n ` ${chalk.dim('accuracy')} ${sparkline(buckets.map((b) => b.accuracy * 100))} ${chalk.dim('range')} ${Math.round(Math.min(...buckets.map((b) => b.accuracy * 100)))}-${Math.round(Math.max(...buckets.map((b) => b.accuracy * 100)))}%`,\n );\n console.log(` ${chalk.dim('sessions')} ${sparkline(buckets.map((b) => b.sessions))}`);\n\n const recent = sessions.slice(-5).reverse();\n console.log(chalk.bold('\\nLast 5 sessions'));\n for (const s of recent) {\n const wpm = computeWPM(s);\n const acc = Math.round(accuracy(s) * 1000) / 10;\n console.log(\n ` ${chalk.dim(s.ts.replace('T', ' ').slice(0, 16))} ${chalk.cyan(s.dictId.padEnd(14))} ch${String(s.chapter + 1).padStart(3)} ${s.mode.padEnd(9)} ${String(s.wordCount).padStart(3)}w ${s.errors}err ${wpm}wpm ${acc}%`,\n );\n }\n\n const top = topN(book, topCount);\n if (top.length > 0) {\n console.log(chalk.bold(`\\nTop ${top.length} mistakes`));\n for (const [word, entry] of top) {\n console.log(\n ` ${chalk.red(String(entry.count).padStart(3))} ${chalk.bold(word.padEnd(20))} ${chalk.dim(entry.dictIds.join(', '))}`,\n );\n }\n } else {\n console.log(chalk.bold('\\nTop mistakes'));\n console.log(chalk.dim(' none — keep going'));\n }\n console.log();\n}\n"],"mappings":"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","names":["chalk","runStats","opts","days","topCount","sessions","loadSessions","book","loadMistakes","chalk","buckets","dailyBuckets","streak","dailyStreak","totalWords","a","totalErrors","totalMs","firstTryWords","overallWpm","overallAcc","sparkline","b","recent","s","wpm","computeWPM","acc","accuracy","top","topN","word","entry"]}
|
package/package.json
CHANGED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a as F,d as H,f as N}from"./chunk-DURXS5MX.js";import{b as z}from"./chunk-CQRKGMPU.js";import"./chunk-7LTZGB7F.js";import{b as q,d as J,f as e}from"./chunk-FQ2MEK7M.js";import{b as Y,c as _}from"./chunk-NA5UNUVL.js";import"./chunk-E6BBQALJ.js";import{useEffect as at,useMemo as dt,useState as B}from"react";import{Box as l,Text as c,useInput as st,useStdout as mt}from"ink";import{useState as lt}from"react";import{Box as M,Text as I,useInput as ct}from"ink";import{jsx as A,jsxs as K}from"react/jsx-runtime";function G({title:D,items:g,onClose:w}){let f=g.map((s,u)=>s.disabled?-1:u).filter(s=>s>=0),o=f[0]??0,[P,v]=lt(o);ct((s,u)=>{if(u.escape){w();return}if(u.upArrow){let a=f.indexOf(P),d=f[(a-1+f.length)%f.length];d!==void 0&&v(d);return}if(u.downArrow){let a=f.indexOf(P),d=f[(a+1)%f.length];d!==void 0&&v(d);return}if(u.return){let a=g[P];a&&!a.disabled&&a.run();return}for(let a=0;a<g.length;a++){let d=g[a];if(!d.disabled&&d.key&&s===d.key){d.run();return}}});let L=Math.max(...g.map(s=>s.label.length)),S=Math.max(L+8,D.length+4,24);return K(M,{flexDirection:"column",borderStyle:"round",borderColor:e.accent,paddingX:2,paddingY:1,width:S,children:[A(M,{marginBottom:1,children:A(I,{bold:!0,color:e.accent,children:D})}),g.map((s,u)=>{let a=u===P,d=s.disabled?e.muted:a?e.text:e.muted;return K(M,{children:[A(I,{color:a?e.accent:e.muted,children:a?"\u258C ":" "}),A(I,{bold:a,color:d,children:s.label})]},u)}),A(M,{marginTop:1,children:A(I,{color:e.muted,children:"\u2191/\u2193 \xB7 Enter \xB7 Esc"})})]})}import{Fragment as ut,jsx as t,jsxs as h}from"react/jsx-runtime";function Mt({params:D}){let g=q(),{cfg:w,setCfg:f}=z(),o=J(),{stdout:P}=mt(),[v,L]=B([]),[S,s]=B(!0),[u,a]=B(0),[d,O]=B(""),[x,y]=B(null),[Q,E]=B(0),[C,p]=B(null),U=async()=>{let n=await _(),i=await Promise.all(n.map(async m=>({entry:m,local:await H(m.id)})));L(i),s(!1)};at(()=>{U()},[Q]);let b=dt(()=>d?v.filter(n=>Y([n.entry],d).length>0):v,[d,v]),R=Math.max(0,Math.min(b.length-1,u)),r=b[R],V=P?.rows??24,$=Math.max(6,V-8),W=Math.floor($/2),j=Math.max(0,Math.min(b.length-$,R-W)),Z=Math.min(b.length,j+$),X=n=>{g.replace({name:"practice",params:{dictId:n,chapterIndex:0,mode:w.defaultMode,stealth:w.stealth==="default"}})},tt=async(n,i=!0)=>{await f({...w,defaultDict:n}),p(null),i&&(D?.pickerMode==="choose-then-practice"?X(n):g.back())},et=n=>{p(null),y({kind:"removing",id:n}),(async()=>{try{await N(n),y(null),E(i=>i+1)}catch(i){y({kind:"error",id:n,msg:i.message})}})()},nt=n=>{p(null),y({kind:"pulling",id:n}),(async()=>{try{await F(n),y(null),E(i=>i+1)}catch(i){y({kind:"error",id:n,msg:i.message})}})()},ot=()=>{p(null),y({kind:"refreshing"}),E(n=>n+1),y(null)};if(st((n,i)=>{if(C===null){if(i.escape){g.back();return}if(i.upArrow){a(m=>Math.max(0,m-1));return}if(i.downArrow){a(m=>Math.min(b.length-1,m+1));return}if(i.ctrl&&n==="k"){p("more");return}if(i.return){r&&p("item");return}if(i.backspace||i.delete){O(m=>m.slice(0,-1)),a(0);return}if(n&&!i.ctrl&&!i.meta){let m=[...n].filter(T=>{let k=T.codePointAt(0);return k>=32&&k!==127}).join("");if(m.length===0)return;O(T=>T+m),a(0)}}}),S)return t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(c,{color:e.muted,children:o.dict.loading})});let rt=r?[{label:o.dict.action.setDefault,run:()=>{tt(r.entry.id,D?.pickerMode!==void 0)}},{label:o.dict.action.practice,run:()=>X(r.entry.id)},{label:o.dict.action.delete,disabled:!r.local,run:()=>et(r.entry.id)},{label:o.common.cancel,run:()=>p(null)}]:[],it=[{label:o.dict.command.pull,disabled:!r,run:()=>r&&nt(r.entry.id)},{label:o.dict.command.import,disabled:!0,run:()=>{}},{label:o.dict.command.refreshList,run:()=>ot()},{label:o.common.cancel,run:()=>p(null)}];return C==="item"&&r?t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(G,{title:`${o.dict.action.title} \xB7 ${r.entry.name}`,items:rt,onClose:()=>p(null)})}):C==="more"?t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(G,{title:o.dict.command.title,items:it,onClose:()=>p(null)})}):h(l,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[h(l,{children:[t(c,{bold:!0,color:e.accent,children:o.dict.title}),t(l,{flexGrow:1}),t(c,{color:e.muted,children:d?`${o.dict.filterPlaceholder}: ${d}_`:`${o.dict.filterPlaceholder}_`}),h(c,{color:e.muted,children:[" ",o.dict.entries(b.length)]})]}),h(l,{marginTop:1,flexGrow:1,children:[t(l,{flexDirection:"column",width:"75%",paddingRight:1,children:b.slice(j,Z).map((n,i)=>{let T=j+i===R,k=w.defaultDict===n.entry.id;return h(l,{children:[t(l,{width:2,children:t(c,{color:T?e.accent:e.muted,children:T?"\u258C ":" "})}),t(l,{width:2,children:t(c,{color:n.local?e.accent:e.muted,children:n.local?"\u25CF":"\u25CB"})}),t(l,{width:2,children:t(c,{color:k?e.success:e.muted,children:k?"\u2605":" "})}),t(l,{flexGrow:1,children:t(c,{bold:T,color:T?e.text:e.muted,wrap:"truncate",children:n.entry.name})}),t(l,{width:6,children:t(c,{color:e.muted,children:String(n.entry.length).padStart(5)})})]},n.entry.id)})}),t(l,{flexDirection:"column",width:"25%",paddingLeft:1,children:r&&h(ut,{children:[t(c,{bold:!0,color:e.text,wrap:"wrap",children:r.entry.name}),t(c,{color:e.muted,children:r.entry.id}),t(l,{marginTop:1,children:h(c,{color:e.muted,wrap:"wrap",children:[r.entry.language," \xB7 ",r.entry.category]})}),t(l,{marginTop:1,children:t(c,{color:e.muted,children:o.dict.wordsLabel(r.entry.length)})}),r.entry.description&&t(l,{marginTop:1,children:t(c,{color:e.primary,wrap:"wrap",children:r.entry.description})}),r.entry.tags.length>0&&t(l,{marginTop:1,children:t(c,{color:e.muted,wrap:"wrap",children:o.dict.tagsLabel(r.entry.tags.join(", "))})}),t(l,{marginTop:1,children:t(c,{color:r.local?e.accent:e.muted,children:r.local?o.dict.local:o.dict.notLocal})}),w.defaultDict===r.entry.id&&t(l,{children:t(c,{color:e.success,children:o.dict.defaultMark})})]})})]}),x&&h(l,{marginTop:1,children:[x.kind==="pulling"&&t(c,{color:e.warning,children:o.dict.pulling(x.id)}),x.kind==="removing"&&t(c,{color:e.warning,children:o.dict.removing(x.id)}),x.kind==="refreshing"&&h(c,{color:e.warning,children:[o.dict.command.refreshList,"\u2026"]}),x.kind==="error"&&t(c,{color:e.error,children:o.dict.errorOn(x.id,x.msg)})]}),t(l,{marginTop:1,children:t(c,{color:e.muted,children:o.dict.footer})})]})}export{Mt as DictBrowser};
|
|
2
|
-
//# sourceMappingURL=DictBrowser-W5GYDO3B.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ui/screens/DictBrowser.tsx","../src/ui/components/ActionPanel.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Box, Text, useInput, useStdout } from 'ink';\nimport { useNav, type DictParams } from '../nav.js';\nimport { useAppState } from '../app-state.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { PALETTE } from '../components/BigWord.js';\nimport { ActionPanel, type ActionItem } from '../components/ActionPanel.js';\nimport { loadRegistry } from '../../infra/registry-store.js';\nimport {\n isLocallyAvailable,\n pullDictionary,\n removeDictionary,\n} from '../../infra/dict-downloader.js';\nimport type { DictionaryEntry } from '../../domain/dictionary.js';\nimport { filterRegistry } from '../../domain/dictionary.js';\n\ntype Row = { entry: DictionaryEntry; local: boolean };\ntype PendingAction =\n | { kind: 'pulling'; id: string }\n | { kind: 'removing'; id: string }\n | { kind: 'refreshing' }\n | { kind: 'error'; id: string; msg: string }\n | null;\n\ntype Panel = null | 'item' | 'more';\n\nexport function DictBrowser({ params }: { params?: DictParams }) {\n const nav = useNav();\n const { cfg, setCfg } = useAppState();\n const t = useStrings();\n const { stdout } = useStdout();\n const [rows, setRows] = useState<Row[]>([]);\n const [loading, setLoading] = useState(true);\n const [selected, setSelected] = useState(0);\n const [filter, setFilter] = useState('');\n const [pending, setPending] = useState<PendingAction>(null);\n const [tick, setTick] = useState(0);\n const [panel, setPanel] = useState<Panel>(null);\n\n const refresh = async () => {\n const reg = await loadRegistry();\n const flagged = await Promise.all(\n reg.map(async (e) => ({ entry: e, local: await isLocallyAvailable(e.id) })),\n );\n setRows(flagged);\n setLoading(false);\n };\n\n useEffect(() => {\n void refresh();\n }, [tick]);\n\n const filtered = useMemo(\n () => (filter ? rows.filter((r) => filterRegistry([r.entry], filter).length > 0) : rows),\n [filter, rows],\n );\n const safeSelected = Math.max(0, Math.min(filtered.length - 1, selected));\n const current = filtered[safeSelected];\n\n const rowsTotal = stdout?.rows ?? 24;\n const visibleH = Math.max(6, rowsTotal - 8);\n const half = Math.floor(visibleH / 2);\n const start = Math.max(0, Math.min(filtered.length - visibleH, safeSelected - half));\n const end = Math.min(filtered.length, start + visibleH);\n\n const goPractice = (id: string) => {\n nav.replace({\n name: 'practice',\n params: {\n dictId: id,\n chapterIndex: 0,\n mode: cfg.defaultMode,\n stealth: cfg.stealth === 'default',\n },\n });\n };\n\n const doSetDefault = async (id: string, navigate = true) => {\n await setCfg({ ...cfg, defaultDict: id });\n setPanel(null);\n if (navigate) {\n if (params?.pickerMode === 'choose-then-practice') {\n goPractice(id);\n } else {\n nav.back();\n }\n }\n };\n\n const doDelete = (id: string) => {\n setPanel(null);\n setPending({ kind: 'removing', id });\n void (async () => {\n try {\n await removeDictionary(id);\n setPending(null);\n setTick((n) => n + 1);\n } catch (err) {\n setPending({ kind: 'error', id, msg: (err as Error).message });\n }\n })();\n };\n\n const doPull = (id: string) => {\n setPanel(null);\n setPending({ kind: 'pulling', id });\n void (async () => {\n try {\n await pullDictionary(id);\n setPending(null);\n setTick((n) => n + 1);\n } catch (err) {\n setPending({ kind: 'error', id, msg: (err as Error).message });\n }\n })();\n };\n\n const doRefreshList = () => {\n setPanel(null);\n setPending({ kind: 'refreshing' });\n setTick((n) => n + 1);\n setPending(null);\n };\n\n useInput((input, key) => {\n if (panel !== null) return;\n\n if (key.escape) {\n nav.back();\n return;\n }\n if (key.upArrow) {\n setSelected((i) => Math.max(0, i - 1));\n return;\n }\n if (key.downArrow) {\n setSelected((i) => Math.min(filtered.length - 1, i + 1));\n return;\n }\n if (key.ctrl && input === 'k') {\n setPanel('more');\n return;\n }\n if (key.return) {\n if (current) setPanel('item');\n return;\n }\n if (key.backspace || key.delete) {\n setFilter((f) => f.slice(0, -1));\n setSelected(0);\n return;\n }\n if (input && !key.ctrl && !key.meta) {\n // Keep printable codepoints (>= 0x20, != 0x7F DEL). Removing the old\n // `input.length === 1` cap so IME multi-char commits like '考研' (length 2)\n // and pasted runs reach the filter. filterRegistry already matches the\n // dictionary `name` field, which is Chinese for ~75% of the registry —\n // the bug was that the keystroke never made it to filter state.\n const cleaned = [...input]\n .filter((c) => {\n const cp = c.codePointAt(0)!;\n return cp >= 0x20 && cp !== 0x7f;\n })\n .join('');\n if (cleaned.length === 0) return;\n setFilter((f) => f + cleaned);\n setSelected(0);\n }\n });\n\n if (loading) {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.muted}>{t.dict.loading}</Text>\n </Box>\n );\n }\n\n const itemPanelItems: ActionItem[] = current\n ? [\n {\n label: t.dict.action.setDefault,\n run: () => void doSetDefault(current.entry.id, params?.pickerMode !== undefined),\n },\n {\n label: t.dict.action.practice,\n run: () => goPractice(current.entry.id),\n },\n {\n label: t.dict.action.delete,\n disabled: !current.local,\n run: () => doDelete(current.entry.id),\n },\n { label: t.common.cancel, run: () => setPanel(null) },\n ]\n : [];\n\n const morePanelItems: ActionItem[] = [\n {\n label: t.dict.command.pull,\n disabled: !current,\n run: () => current && doPull(current.entry.id),\n },\n {\n label: t.dict.command.import,\n disabled: true,\n run: () => undefined,\n },\n { label: t.dict.command.refreshList, run: () => doRefreshList() },\n { label: t.common.cancel, run: () => setPanel(null) },\n ];\n\n if (panel === 'item' && current) {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <ActionPanel\n title={`${t.dict.action.title} · ${current.entry.name}`}\n items={itemPanelItems}\n onClose={() => setPanel(null)}\n />\n </Box>\n );\n }\n if (panel === 'more') {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <ActionPanel\n title={t.dict.command.title}\n items={morePanelItems}\n onClose={() => setPanel(null)}\n />\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1} width=\"100%\" height=\"100%\">\n <Box>\n <Text bold color={PALETTE.accent}>\n {t.dict.title}\n </Text>\n <Box flexGrow={1} />\n <Text color={PALETTE.muted}>\n {filter ? `${t.dict.filterPlaceholder}: ${filter}_` : `${t.dict.filterPlaceholder}_`}\n </Text>\n <Text color={PALETTE.muted}>\n {' '}\n {t.dict.entries(filtered.length)}\n </Text>\n </Box>\n\n <Box marginTop={1} flexGrow={1}>\n <Box flexDirection=\"column\" width=\"75%\" paddingRight={1}>\n {filtered.slice(start, end).map((row, vi) => {\n const i = start + vi;\n const active = i === safeSelected;\n const isDefault = cfg.defaultDict === row.entry.id;\n return (\n <Box key={row.entry.id}>\n <Box width={2}>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>{active ? '▌ ' : ' '}</Text>\n </Box>\n <Box width={2}>\n <Text color={row.local ? PALETTE.accent : PALETTE.muted}>\n {row.local ? '●' : '○'}\n </Text>\n </Box>\n <Box width={2}>\n <Text color={isDefault ? PALETTE.success : PALETTE.muted}>\n {isDefault ? '★' : ' '}\n </Text>\n </Box>\n <Box flexGrow={1}>\n <Text bold={active} color={active ? PALETTE.text : PALETTE.muted} wrap=\"truncate\">\n {row.entry.name}\n </Text>\n </Box>\n <Box width={6}>\n <Text color={PALETTE.muted}>{String(row.entry.length).padStart(5)}</Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n\n <Box flexDirection=\"column\" width=\"25%\" paddingLeft={1}>\n {current && (\n <>\n <Text bold color={PALETTE.text} wrap=\"wrap\">\n {current.entry.name}\n </Text>\n <Text color={PALETTE.muted}>{current.entry.id}</Text>\n <Box marginTop={1}>\n <Text color={PALETTE.muted} wrap=\"wrap\">\n {current.entry.language} · {current.entry.category}\n </Text>\n </Box>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.dict.wordsLabel(current.entry.length)}</Text>\n </Box>\n {current.entry.description && (\n <Box marginTop={1}>\n <Text color={PALETTE.primary} wrap=\"wrap\">{current.entry.description}</Text>\n </Box>\n )}\n {current.entry.tags.length > 0 && (\n <Box marginTop={1}>\n <Text color={PALETTE.muted} wrap=\"wrap\">{t.dict.tagsLabel(current.entry.tags.join(', '))}</Text>\n </Box>\n )}\n <Box marginTop={1}>\n <Text color={current.local ? PALETTE.accent : PALETTE.muted}>\n {current.local ? t.dict.local : t.dict.notLocal}\n </Text>\n </Box>\n {cfg.defaultDict === current.entry.id && (\n <Box>\n <Text color={PALETTE.success}>{t.dict.defaultMark}</Text>\n </Box>\n )}\n </>\n )}\n </Box>\n </Box>\n\n {pending && (\n <Box marginTop={1}>\n {pending.kind === 'pulling' && (\n <Text color={PALETTE.warning}>{t.dict.pulling(pending.id)}</Text>\n )}\n {pending.kind === 'removing' && (\n <Text color={PALETTE.warning}>{t.dict.removing(pending.id)}</Text>\n )}\n {pending.kind === 'refreshing' && (\n <Text color={PALETTE.warning}>{t.dict.command.refreshList}…</Text>\n )}\n {pending.kind === 'error' && (\n <Text color={PALETTE.error}>{t.dict.errorOn(pending.id, pending.msg)}</Text>\n )}\n </Box>\n )}\n\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.dict.footer}</Text>\n </Box>\n </Box>\n );\n}\n","import { useState } from 'react';\nimport { Box, Text, useInput } from 'ink';\nimport { PALETTE } from './BigWord.js';\n\nexport type ActionItem = {\n key?: string;\n label: string;\n disabled?: boolean;\n run: () => void | Promise<void>;\n};\n\ntype Props = {\n title: string;\n items: ActionItem[];\n onClose: () => void;\n};\n\nexport function ActionPanel({ title, items, onClose }: Props) {\n const enabledIndices = items\n .map((it, i) => (it.disabled ? -1 : i))\n .filter((i) => i >= 0);\n const initial = enabledIndices[0] ?? 0;\n const [selected, setSelected] = useState(initial);\n\n useInput((input, key) => {\n if (key.escape) {\n onClose();\n return;\n }\n if (key.upArrow) {\n const cur = enabledIndices.indexOf(selected);\n const next = enabledIndices[(cur - 1 + enabledIndices.length) % enabledIndices.length];\n if (next !== undefined) setSelected(next);\n return;\n }\n if (key.downArrow) {\n const cur = enabledIndices.indexOf(selected);\n const next = enabledIndices[(cur + 1) % enabledIndices.length];\n if (next !== undefined) setSelected(next);\n return;\n }\n if (key.return) {\n const item = items[selected];\n if (item && !item.disabled) {\n void item.run();\n }\n return;\n }\n for (let i = 0; i < items.length; i++) {\n const it = items[i]!;\n if (it.disabled) continue;\n if (it.key && input === it.key) {\n void it.run();\n return;\n }\n }\n });\n\n const maxLabel = Math.max(...items.map((it) => it.label.length));\n const width = Math.max(maxLabel + 8, title.length + 4, 24);\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n borderColor={PALETTE.accent}\n paddingX={2}\n paddingY={1}\n width={width}\n >\n <Box marginBottom={1}>\n <Text bold color={PALETTE.accent}>\n {title}\n </Text>\n </Box>\n {items.map((it, i) => {\n const active = i === selected;\n const color = it.disabled\n ? PALETTE.muted\n : active\n ? PALETTE.text\n : PALETTE.muted;\n return (\n <Box key={i}>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>\n {active ? '▌ ' : ' '}\n </Text>\n <Text bold={active} color={color}>\n {it.label}\n </Text>\n </Box>\n );\n })}\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>↑/↓ · Enter · Esc</Text>\n </Box>\n </Box>\n );\n}\n"],"mappings":"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","names":["useEffect","useMemo","useState","Box","Text","useInput","useStdout","useState","Box","Text","useInput","jsx","jsxs","ActionPanel","title","items","onClose","enabledIndices","it","i","initial","selected","setSelected","useState","useInput","input","key","cur","next","item","maxLabel","width","Box","PALETTE","Text","active","color","Fragment","jsx","jsxs","DictBrowser","params","nav","useNav","cfg","setCfg","useAppState","t","useStrings","stdout","useStdout","rows","setRows","useState","loading","setLoading","selected","setSelected","filter","setFilter","pending","setPending","tick","setTick","panel","setPanel","refresh","reg","loadRegistry","flagged","e","isLocallyAvailable","useEffect","filtered","useMemo","r","filterRegistry","safeSelected","current","rowsTotal","visibleH","half","start","end","goPractice","id","doSetDefault","navigate","doDelete","removeDictionary","n","err","doPull","pullDictionary","doRefreshList","useInput","input","key","i","f","cleaned","c","cp","Box","Text","PALETTE","itemPanelItems","morePanelItems","ActionPanel","row","vi","active","isDefault"]}
|
package/dist/chunk-HJIGZU3E.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a as w,g as h,h as S}from"./chunk-E6BBQALJ.js";import{z as i}from"zod";var D=i.object({ts:i.string(),dictId:i.string(),chapter:i.number().int().nonnegative(),mode:i.string(),wordCount:i.number().int().nonnegative(),errors:i.number().int().nonnegative(),durationMs:i.number().int().nonnegative(),perWordErrors:i.record(i.string(),i.number().int().nonnegative()).default({})});async function k(e){await h(w.stats,e)}async function C(){return(await S(w.stats)).map(t=>D.safeParse(t)).filter(t=>t.success).map(t=>t.data)}function y(e){if(e.durationMs===0)return 0;let t=e.durationMs/6e4;return Math.round(e.wordCount/t*10)/10}function b(e){if(e.wordCount===0)return 1;let t=Object.keys(e.perWordErrors).length;return Math.max(0,Math.min(1,(e.wordCount-t)/e.wordCount))}function R(e,t=new Date){if(e.length===0)return 0;let a=new Set;for(let n of e)a.add(n.ts.slice(0,10));let r=0,s=new Date(t);for(;;){let n=s.toISOString().slice(0,10);if(!a.has(n))break;r++,s.setUTCDate(s.getUTCDate()-1)}return r}var l=["\u2581","\u2582","\u2583","\u2584","\u2585","\u2586","\u2587","\u2588"];function v(e){if(e.length===0)return"";let t=Math.max(...e,1),a=Math.min(...e,0),r=Math.max(1,t-a);return e.map(s=>{let n=Math.floor((s-a)/r*(l.length-1));return l[Math.max(0,Math.min(l.length-1,n))]}).join("")}function P(e,t,a=new Date){if(e.length===0||t<=0)return{avgWpm:0,peakWpmLifetime:0,avgAccPct:0,sessionsInWindow:0,days:t};let r=new Date(a);r.setUTCDate(r.getUTCDate()-(t-1));let s=r.toISOString().slice(0,10),n=0,m=0,u=0,o=0,p=0;for(let c of e){let f=y(c);f>p&&(p=f),!(c.ts.slice(0,10)<s)&&(o++,n+=c.wordCount,m+=f*c.wordCount,u+=b(c)*c.wordCount)}if(n===0)return{avgWpm:0,peakWpmLifetime:p,avgAccPct:0,sessionsInWindow:o,days:t};let g=Math.round(m/n*10)/10,d=Math.round(u/n*1e3)/10;return{avgWpm:g,peakWpmLifetime:p,avgAccPct:d,sessionsInWindow:o,days:t}}function A(e,t,a=new Date){let r=[],s=new Map;for(let m of e){let u=m.ts.slice(0,10),o=s.get(u)??[];o.push(m),s.set(u,o)}let n=new Date(a);n.setUTCDate(n.getUTCDate()-(t-1));for(let m=0;m<t;m++){let u=n.toISOString().slice(0,10),o=s.get(u)??[];if(o.length===0)r.push({date:u,wpm:0,accuracy:0,sessions:0});else{let p=o.reduce((d,c)=>d+y(c),0)/o.length,g=o.reduce((d,c)=>d+b(c),0)/o.length;r.push({date:u,wpm:p,accuracy:g,sessions:o.length})}n.setUTCDate(n.getUTCDate()+1)}return r}export{k as a,C as b,y as c,b as d,R as e,v as f,P as g,A as h};
|
|
2
|
-
//# sourceMappingURL=chunk-HJIGZU3E.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/domain/stats.ts"],"sourcesContent":["import { z } from 'zod';\nimport { paths } from '../infra/paths.js';\nimport { appendJsonl, readJsonl } from '../infra/fs-store.js';\n\nexport const SessionRecordSchema = z.object({\n ts: z.string(),\n dictId: z.string(),\n chapter: z.number().int().nonnegative(),\n mode: z.string(),\n wordCount: z.number().int().nonnegative(),\n errors: z.number().int().nonnegative(),\n durationMs: z.number().int().nonnegative(),\n perWordErrors: z.record(z.string(), z.number().int().nonnegative()).default({}),\n});\n\nexport type SessionRecord = z.infer<typeof SessionRecordSchema>;\n\nexport async function appendSession(record: SessionRecord): Promise<void> {\n await appendJsonl(paths.stats, record);\n}\n\nexport async function loadSessions(): Promise<SessionRecord[]> {\n const rows = await readJsonl<unknown>(paths.stats);\n return rows\n .map((r) => SessionRecordSchema.safeParse(r))\n .filter((r): r is { success: true; data: SessionRecord } => r.success)\n .map((r) => r.data);\n}\n\nexport function computeWPM(record: SessionRecord): number {\n if (record.durationMs === 0) return 0;\n const minutes = record.durationMs / 60000;\n return Math.round((record.wordCount / minutes) * 10) / 10;\n}\n\nexport function accuracy(record: SessionRecord): number {\n // Word-level: fraction of words finished without any mistake (first-try rate).\n if (record.wordCount === 0) return 1;\n const wordsWithErrors = Object.keys(record.perWordErrors).length;\n return Math.max(0, Math.min(1, (record.wordCount - wordsWithErrors) / record.wordCount));\n}\n\nexport function dailyStreak(sessions: SessionRecord[], now = new Date()): number {\n if (sessions.length === 0) return 0;\n const days = new Set<string>();\n for (const s of sessions) days.add(s.ts.slice(0, 10));\n let streak = 0;\n const cur = new Date(now);\n while (true) {\n const key = cur.toISOString().slice(0, 10);\n if (!days.has(key)) break;\n streak++;\n cur.setUTCDate(cur.getUTCDate() - 1);\n }\n return streak;\n}\n\nconst SPARK = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];\n\nexport function sparkline(values: number[]): string {\n if (values.length === 0) return '';\n const max = Math.max(...values, 1);\n const min = Math.min(...values, 0);\n const range = Math.max(1, max - min);\n return values\n .map((v) => {\n const idx = Math.floor(((v - min) / range) * (SPARK.length - 1));\n return SPARK[Math.max(0, Math.min(SPARK.length - 1, idx))]!;\n })\n .join('');\n}\n\nexport type DailyMetric = 'wpm' | 'accuracy' | 'sessions';\n\nexport function dailyValues(\n sessions: SessionRecord[],\n days: number,\n metric: DailyMetric,\n now = new Date(),\n): number[] {\n const buckets = dailyBuckets(sessions, days, now);\n if (metric === 'wpm') return buckets.map((b) => b.wpm);\n if (metric === 'accuracy') return buckets.map((b) => b.accuracy * 100);\n return buckets.map((b) => b.sessions);\n}\n\nexport type WindowAggregate = {\n avgWpm: number;\n peakWpmLifetime: number;\n avgAccPct: number;\n sessionsInWindow: number;\n days: number;\n};\n\nexport function windowAggregate(\n sessions: SessionRecord[],\n days: number,\n now = new Date(),\n): WindowAggregate {\n if (sessions.length === 0 || days <= 0) {\n return { avgWpm: 0, peakWpmLifetime: 0, avgAccPct: 0, sessionsInWindow: 0, days };\n }\n const cutoff = new Date(now);\n cutoff.setUTCDate(cutoff.getUTCDate() - (days - 1));\n const cutoffKey = cutoff.toISOString().slice(0, 10);\n\n let wordsInWindow = 0;\n let wpmWeightedSum = 0;\n let accWeightedSum = 0;\n let sessionsInWindow = 0;\n let peakWpmLifetime = 0;\n for (const s of sessions) {\n const wpm = computeWPM(s);\n if (wpm > peakWpmLifetime) peakWpmLifetime = wpm;\n if (s.ts.slice(0, 10) < cutoffKey) continue;\n sessionsInWindow++;\n wordsInWindow += s.wordCount;\n wpmWeightedSum += wpm * s.wordCount;\n accWeightedSum += accuracy(s) * s.wordCount;\n }\n if (wordsInWindow === 0) {\n return { avgWpm: 0, peakWpmLifetime, avgAccPct: 0, sessionsInWindow, days };\n }\n const avgWpm = Math.round((wpmWeightedSum / wordsInWindow) * 10) / 10;\n const avgAccPct = Math.round((accWeightedSum / wordsInWindow) * 1000) / 10;\n return { avgWpm, peakWpmLifetime, avgAccPct, sessionsInWindow, days };\n}\n\nexport type DailyBucket = { date: string; wpm: number; accuracy: number; sessions: number };\n\nexport function dailyBuckets(sessions: SessionRecord[], days: number, now = new Date()): DailyBucket[] {\n const out: DailyBucket[] = [];\n const byDay = new Map<string, SessionRecord[]>();\n for (const s of sessions) {\n const key = s.ts.slice(0, 10);\n const arr = byDay.get(key) ?? [];\n arr.push(s);\n byDay.set(key, arr);\n }\n const cur = new Date(now);\n cur.setUTCDate(cur.getUTCDate() - (days - 1));\n for (let i = 0; i < days; i++) {\n const key = cur.toISOString().slice(0, 10);\n const todays = byDay.get(key) ?? [];\n if (todays.length === 0) {\n out.push({ date: key, wpm: 0, accuracy: 0, sessions: 0 });\n } else {\n const wpm = todays.reduce((a, s) => a + computeWPM(s), 0) / todays.length;\n const acc = todays.reduce((a, s) => a + accuracy(s), 0) / todays.length;\n out.push({ date: key, wpm, accuracy: acc, sessions: todays.length });\n }\n cur.setUTCDate(cur.getUTCDate() + 1);\n }\n return out;\n}\n"],"mappings":"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","names":["z","SessionRecordSchema","z","appendSession","record","appendJsonl","paths","loadSessions","readJsonl","r","computeWPM","minutes","accuracy","wordsWithErrors","dailyStreak","sessions","now","days","s","streak","cur","key","SPARK","sparkline","values","max","min","range","v","idx","windowAggregate","sessions","days","now","cutoff","cutoffKey","wordsInWindow","wpmWeightedSum","accWeightedSum","sessionsInWindow","peakWpmLifetime","s","wpm","computeWPM","accuracy","avgWpm","avgAccPct","dailyBuckets","out","byDay","key","arr","cur","i","todays","a","acc"]}
|
package/dist/chunk-QL7QPZ6S.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{a as L,b as R,c as H}from"./chunk-6ROGUGNX.js";import{a as C,b as k}from"./chunk-CQRKGMPU.js";import{a as B,b as D,c as w,e as I}from"./chunk-QG7ZTS2G.js";import{a as P,b as M,c as E,d as N,f as a}from"./chunk-FQ2MEK7M.js";var J="\x1B[?1049h\x1B[?25l\x1B[2J\x1B[H",z="\x1B[?25h\x1B[?1049l",v=!1,$=!1;function K(){if($)return;$=!0;let e=()=>{if(v){try{process.stdout.write(z)}catch{}v=!1}};process.once("exit",e),process.once("SIGINT",()=>{e(),process.exit(130)}),process.once("SIGTERM",()=>{e(),process.exit(143)})}function W(){v||process.stdout.isTTY&&process.env.QWERTY_NO_ALTSCREEN!=="1"&&(K(),process.stdout.write(J),v=!0)}function A(){if(v){try{process.stdout.write(z)}catch{}v=!1}}import{Suspense as re,lazy as T,useRef as oe}from"react";import{Box as ne,Text as ie,useApp as se,useInput as ae}from"ink";import{useEffect as _,useState as O}from"react";import{Box as Q,useStdout as X}from"ink";import{jsx as U}from"react/jsx-runtime";function F({children:e}){let{stdout:t}=X(),[s,r]=O(()=>({rows:t?.rows??24,cols:t?.columns??80}));return _(()=>{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),A()}},[]),U(Q,{width:s.cols,height:s.rows,flexDirection:"column",children:e})}import{useState as Z}from"react";import{Box as y,Text as m,useApp as ee,useInput as te}from"ink";import{jsx as h,jsxs as g}from"react/jsx-runtime";function Y({cfg:e}){let[t,s]=Z(0),{exit:r}=ee(),n=M(),f=R(),p=N(),l=D(e.defaultDict),o=p.mainMenu.items,b=c=>{if(c&&e.defaultDict){H({type:"stealth",dictId:e.defaultDict,chapterIndex:0,mode:e.defaultMode}),r();return}e.defaultDict?n.navigate({name:"practice",params:{dictId:e.defaultDict,chapterIndex:0,mode:e.defaultMode,stealth:c}}):n.navigate({name:"dict",params:{pickerMode:"choose-then-practice"}})},d=[{key:"p",label:o.practiceLabel,hint:e.defaultDict?o.practiceHintWith(I(l,24)):o.practiceHintNone,run:()=>b(e.stealth==="default")}];(e.stealth==="menu"||e.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 V=Math.max(...d.map(c=>w(c.label)))+4;return te((c,S)=>{if(S.escape){r();return}if(S.upArrow&&s(u=>(u-1+d.length)%d.length),S.downArrow&&s(u=>(u+1)%d.length),S.return){d[t].run();return}if(c==="?"){n.navigate({name:"help"});return}for(let u of d)if(c===u.key){u.run();return}}),g(y,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",children:[e.stealth!=="default"&&g(y,{children:[h(m,{bold:!0,color:a.accent,children:p.app.title}),g(m,{color:a.muted,children:[" \xB7 ",p.app.subtitle]})]}),h(y,{marginTop:e.stealth==="default"?0:2,flexDirection:"column",children:d.map((c,S)=>{let u=S===t,G=" ".repeat(Math.max(0,V-w(c.label)));return g(y,{children:[h(m,{color:u?a.accent:a.muted,children:u?"\u258C ":" "}),g(m,{color:u?a.accent:a.muted,children:["[",c.key,"]"]}),h(m,{children:" "}),g(m,{bold:u,color:u?a.text:a.muted,children:[c.label,G]}),h(m,{color:a.muted,children:c.hint})]},c.key)})}),h(y,{marginTop:2,children:g(m,{color:a.muted,children:[p.mainMenu.hint," \xB7 ",p.mainMenu.helpHint]})}),f.warning&&h(y,{marginTop:1,children:h(m,{color:a.warning,children:p.audio.noPlayer})})]})}import{jsx as i}from"react/jsx-runtime";var ce=T(()=>import("./PracticeScreen-7UGWQ4HP.js").then(e=>({default:e.PracticeScreen}))),ue=T(()=>import("./DictBrowser-W5GYDO3B.js").then(e=>({default:e.DictBrowser}))),pe=T(()=>import("./ConfigEditor-ZJ52VG3I.js").then(e=>({default:e.ConfigEditor}))),le=T(()=>import("./StatsViewer-SRBXYBNA.js").then(e=>({default:e.StatsViewer}))),de=T(()=>import("./WordLookup-LKBEPDTB.js").then(e=>({default:e.WordLookup}))),me=T(()=>import("./HelpScreen-Y3TWEEWZ.js").then(e=>({default:e.HelpScreen})));function fe(){return i(ne,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:i(ie,{color:a.muted,children:"\u2026"})})}function _e({initial:e,initialCfg:t,inline:s=!1}){return i(C,{initialCfg:t,children:i(he,{children:i(B,{children:i(L,{disabled:!t.sounds.master,children:i(P,{initial:e,children:s?i(j,{inline:!0}):i(F,{children:i(j,{})})})})})})})}function he({children:e}){let{cfg:t}=k();return i(E,{pref:t.language,children:e})}function ge(e){if(e.name==="practice"){let t=e.params;return`practice:${t.dictId}:${t.chapterIndex}:${t.mode}:${t.stealth?"s":"n"}`}return e.name}function j({inline:e=!1}){let t=M(),{cfg:s}=k(),{exit:r}=se(),n=oe(null);ae((o,b)=>{b.ctrl&&o==="c"&&r()});let f=t.current,p=ge(f);n.current!==p&&(!e&&process.stdout.isTTY&&process.stdout.write("\x1B[2J\x1B[H"),n.current=p);let l=(()=>{switch(f.name){case"main":return i(Y,{cfg:s});case"practice":return i(ce,{params:f.params});case"dict":return i(ue,{params:f.params});case"config":return i(pe,{});case"stats":return i(le,{});case"word":return i(de,{});case"help":return i(me,{})}})();return i(re,{fallback:i(fe,{}),children:l})}import x from"chalk";import xe from"boxen";function tt(){A()}function q(e,t){let s=Math.floor(e/1e3),r=Math.floor(s/60),n=s%60;return t==="zh"?r===0?`${n} \u79D2`:`${r} \u5206 ${n} \u79D2`:r===0?`${n}s`:`${r}m ${n}s`}function be(e){return e>=90?x.green:e<75?x.dim:t=>t}function Se(e,t,s){let r=[],n=[t.report.duration];e.chaptersCompleted===0?n.push(t.report.notPracticed):(n.push(t.report.practiced,t.report.chapters,t.report.words,t.report.accuracy,t.report.wpm),e.newMistakeWords>0&&n.push(t.report.newMistakes));let f=Math.max(...n.map(w)),p=o=>o+" ".repeat(Math.max(0,f-w(o))),l=(o,b)=>`${x.dim(p(o))} ${b}`;if(r.push(l(t.report.duration,q(e.totalDurationMs,s))),e.chaptersCompleted===0)r.push(x.dim(t.report.notPracticed));else{r.push(l(t.report.practiced,q(e.practiceMs,s))),r.push(l(t.report.chapters,String(e.chaptersCompleted))),r.push(l(t.report.words,String(e.wordCount)));let o=Math.round(e.accuracy*1e3)/10;r.push(l(t.report.accuracy,be(o)(`${o}%`))),r.push(l(t.report.wpm,String(e.wpm))),e.newMistakeWords>0&&r.push(l(t.report.newMistakes,x.red(String(e.newMistakeWords))))}return r.push(""),r.push(x.dim.italic(t.report.farewell)),r}function rt(e,t,s){if(e.startedAt===null&&e.chaptersCompleted===0)return;let r=Se(e,t,s).join(`
|
|
2
|
-
`);console.log(xe(r,{title:x.bold.cyan(t.report.title),titleAlignment:"left",borderStyle:"round",borderColor:"gray",padding:{top:0,bottom:0,left:3,right:3},margin:{top:1,bottom:1,left:2,right:0}}))}export{W as a,_e as b,tt as c,rt as d};
|
|
3
|
-
//# sourceMappingURL=chunk-QL7QPZ6S.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/util/altscreen.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","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 {cfg.stealth !== 'default' && (\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\n <Box marginTop={cfg.stealth === 'default' ? 0 : 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: 0, bottom: 0, 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","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","lines","allLabels","labelW","visibleWidth","padLabel","row","label","value","accPct","printSessionReport","content","boxen"]}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a as o,b as s,c as p,d as c}from"./chunk-QL7QPZ6S.js";import{d as n,g as a,i as m}from"./chunk-6ROGUGNX.js";import"./chunk-BIBS2Q3E.js";import"./chunk-CQRKGMPU.js";import{a as i}from"./chunk-7LTZGB7F.js";import"./chunk-QG7ZTS2G.js";import{e}from"./chunk-FQ2MEK7M.js";import"./chunk-NA5UNUVL.js";import"./chunk-E6BBQALJ.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-HO5G57AA.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-BV26PXOO.js.map
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a as c,b as l,c as u,d as g}from"./chunk-QL7QPZ6S.js";import{f as m,g as f,i as p}from"./chunk-6ROGUGNX.js";import"./chunk-BIBS2Q3E.js";import"./chunk-CQRKGMPU.js";import{a}from"./chunk-7LTZGB7F.js";import"./chunk-QG7ZTS2G.js";import{e as d}from"./chunk-FQ2MEK7M.js";import"./chunk-NA5UNUVL.js";import"./chunk-E6BBQALJ.js";import n from"chalk";import{render as b}from"ink";import{createElement as v}from"react";var h=["order","dictation","review","random","loop"];function w(t){return h.includes(t)}async function O(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: ${h.join(", ")}`)),process.exitCode=1;return}let x=Math.max(0,Number(o.chapter??1)-1),r=o.stealth===!0||e.stealth==="default";r||c(),f();let{waitUntilExit:C}=b(v(l,{initial:{name:"practice",params:{dictId:s,chapterIndex:x,mode:i,stealth:r}},initialCfg:e,inline:r}),{patchConsole:!1,exitOnCtrlC:!1});if(await C(),r||u(),m()){process.stdout.write("\x1B[3F\x1B[0J");return}if(r)return;let{lang:M,t:S}=d(e.language);g(p(),S,M)}export{O as runPractice};
|
|
2
|
-
//# sourceMappingURL=practice.impl-HO5G57AA.js.map
|
|
@@ -1 +0,0 @@
|
|
|
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 { consumeSilentExit } from '../util/post-exit-action.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\n if (consumeSilentExit()) {\n // User hit Ctrl+C on the stealth paused screen — erase the 3 inline rows\n // (paused / Esc back / blank) and skip the session report for a fully\n // clean exit. Cursor goes back to where the paused UI started; the shell\n // prompt then renders there, leaving no trace.\n process.stdout.write('\\x1b[3F\\x1b[0J');\n return;\n }\n if (stealth) {\n // Stealth: StealthSummary already rendered an inline summary row that\n // stays in scrollback. A second ~10-row boxen report would defeat the\n // point of the mode, so skip it.\n return;\n }\n\n const { lang, t } = pickStrings(cfg.language);\n printSessionReport(sessionReport(), t, lang);\n}\n"],"mappings":"0UAAA,OAAOA,MAAW,QAClB,OAAS,UAAAC,MAAc,MACvB,OAAS,iBAAAC,MAAqB,QAU9B,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,EAMA,GALA,MAAMG,EAAc,EACfH,GACHO,EAAiB,EAGfC,EAAkB,EAAG,CAKvB,QAAQ,OAAO,MAAM,gBAAgB,EACrC,MACF,CACA,GAAIR,EAIF,OAGF,GAAM,CAAE,KAAAS,EAAM,EAAAC,CAAE,EAAIC,EAAYhB,EAAI,QAAQ,EAC5CiB,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","consumeSilentExit","lang","t","pickStrings","printSessionReport","report"]}
|
|
File without changes
|
|
File without changes
|