qwerty-cli 0.0.1-alpha.15 → 0.0.1-alpha.16
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/README.md +1 -1
- package/dist/ConfigEditor-KAL2MLQO.js +2 -0
- package/dist/ConfigEditor-KAL2MLQO.js.map +1 -0
- package/dist/{DictBrowser-EJ4N3W5P.js → DictBrowser-OF274IOX.js} +2 -2
- package/dist/{HelpScreen-QNZRWVXA.js → HelpScreen-O5RSN3C4.js} +2 -2
- package/dist/{PracticeScreen-S5BGC4VI.js → PracticeScreen-FOCGEFWL.js} +2 -2
- package/dist/{StatsViewer-RZ4NNMSI.js → StatsViewer-XOOMUPGL.js} +2 -2
- package/dist/{StatsViewer-RZ4NNMSI.js.map → StatsViewer-XOOMUPGL.js.map} +1 -1
- package/dist/{WordLookup-KEC2G7EI.js → WordLookup-MWWYQBCM.js} +2 -2
- package/dist/chunk-4EJYJITR.js +3 -0
- package/dist/chunk-4EJYJITR.js.map +1 -0
- package/dist/chunk-7LTZGB7F.js +2 -0
- package/dist/chunk-7LTZGB7F.js.map +1 -0
- package/dist/{chunk-4GNY4LY7.js → chunk-CQRKGMPU.js} +2 -2
- package/dist/{chunk-V3TAMY5K.js → chunk-DURXS5MX.js} +2 -2
- package/dist/chunk-UWTJMVJ6.js +2 -0
- package/dist/chunk-UWTJMVJ6.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/{config.impl-G25YTIQY.js → config.impl-3O5SL5QY.js} +2 -2
- package/dist/{dict.impl-7AE6RFW2.js → dict.impl-JAQ2GXCS.js} +2 -2
- package/dist/{doctor.impl-KTNLVHGF.js → doctor.impl-XVZCEC2O.js} +2 -2
- package/dist/menu.impl-4C7HT3VD.js +2 -0
- package/dist/{practice.impl-KRFKX4AS.js → practice.impl-OLNLJLIA.js} +2 -2
- package/dist/{word.impl-NOC72Y32.js → word.impl-CUBY4EZ4.js} +2 -2
- package/package.json +1 -1
- package/dist/ConfigEditor-U6VNM4X5.js +0 -2
- package/dist/ConfigEditor-U6VNM4X5.js.map +0 -1
- package/dist/chunk-3TQFRACL.js +0 -2
- package/dist/chunk-3TQFRACL.js.map +0 -1
- package/dist/chunk-K6THKMFC.js +0 -3
- package/dist/chunk-K6THKMFC.js.map +0 -1
- package/dist/chunk-QLTHO2HH.js +0 -2
- package/dist/chunk-QLTHO2HH.js.map +0 -1
- package/dist/menu.impl-SRQ6MJY3.js +0 -2
- /package/dist/{DictBrowser-EJ4N3W5P.js.map → DictBrowser-OF274IOX.js.map} +0 -0
- /package/dist/{HelpScreen-QNZRWVXA.js.map → HelpScreen-O5RSN3C4.js.map} +0 -0
- /package/dist/{PracticeScreen-S5BGC4VI.js.map → PracticeScreen-FOCGEFWL.js.map} +0 -0
- /package/dist/{WordLookup-KEC2G7EI.js.map → WordLookup-MWWYQBCM.js.map} +0 -0
- /package/dist/{chunk-4GNY4LY7.js.map → chunk-CQRKGMPU.js.map} +0 -0
- /package/dist/{chunk-V3TAMY5K.js.map → chunk-DURXS5MX.js.map} +0 -0
- /package/dist/{config.impl-G25YTIQY.js.map → config.impl-3O5SL5QY.js.map} +0 -0
- /package/dist/{dict.impl-7AE6RFW2.js.map → dict.impl-JAQ2GXCS.js.map} +0 -0
- /package/dist/{doctor.impl-KTNLVHGF.js.map → doctor.impl-XVZCEC2O.js.map} +0 -0
- /package/dist/{menu.impl-SRQ6MJY3.js.map → menu.impl-4C7HT3VD.js.map} +0 -0
- /package/dist/{practice.impl-KRFKX4AS.js.map → practice.impl-OLNLJLIA.js.map} +0 -0
- /package/dist/{word.impl-NOC72Y32.js.map → word.impl-CUBY4EZ4.js.map} +0 -0
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ Config lives at `~/.qwerty-cli/config.json`. Defaults:
|
|
|
51
51
|
"mirror": "jsdelivr",
|
|
52
52
|
"accent": "us",
|
|
53
53
|
"chapterSize": 20,
|
|
54
|
-
"sounds": { "master": true, "keystroke": true, "feedback": true
|
|
54
|
+
"sounds": { "master": true, "keystroke": true, "feedback": true },
|
|
55
55
|
"autoplayPronunciation": true,
|
|
56
56
|
"defaultMode": "order"
|
|
57
57
|
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{b as P}from"./chunk-CQRKGMPU.js";import{d as E}from"./chunk-7LTZGB7F.js";import{b as $,c as B,e as C}from"./chunk-QG7ZTS2G.js";import{b as F,d as M,f as l}from"./chunk-UWTJMVJ6.js";import"./chunk-NA5UNUVL.js";import"./chunk-E6BBQALJ.js";import{useState as k}from"react";import{Box as g,Text as d,useInput as V}from"ink";import{jsx as c,jsxs as S}from"react/jsx-runtime";var f=[{kind:"dictRef",path:"defaultDict",labelKey:"defaultDict"},{kind:"enum",path:"defaultMode",labelKey:"defaultMode",options:["order","dictation","review","random","loop"]},{kind:"enum",path:"accent",labelKey:"accent",options:["us","uk"]},{kind:"enum",path:"language",labelKey:"language",options:["auto","zh","en"]},{kind:"enum",path:"mirror",labelKey:"mirror",options:["jsdelivr","github"]},{kind:"enum",path:"stealth",labelKey:"stealth",options:["off","menu","default"]},{kind:"int",path:"chapterSize",labelKey:"chapterSize",min:1,max:200},{kind:"bool",path:"autoplayPronunciation",labelKey:"autoplayPronunciation"},{kind:"bool",path:"sounds.master",labelKey:"soundsMaster"},{kind:"bool",path:"sounds.keystroke",labelKey:"soundsKeystroke"},{kind:"bool",path:"sounds.feedback",labelKey:"soundsFeedback"},{kind:"enum",path:"sounds.pronunciationRate",labelKey:"soundsPronunciationRate",options:["0.5","0.75","1","1.25","1.5"]},{kind:"enum",path:"sounds.pronunciationSource",labelKey:"soundsPronunciationSource",options:["youdao","dictapi"]}];function N(a,e){return e.split(".").reduce((o,s)=>{if(o&&typeof o=="object")return o[s]},a)}function U(){let a=F(),{cfg:e,setCfg:o}=P(),s=M(),x=$(e.defaultDict),[y,K]=k(0),[m,h]=k(!1),[w,p]=k(""),[v,u]=k(null),r=f[y],T=N(e,r.path),b=async i=>{try{let n=E(e,r.path,i);await o(n),h(!1),u(null)}catch(n){u(n.message)}};V((i,n)=>{if(m&&r.kind==="string"){if(n.escape){h(!1),u(null);return}if(n.return){b(w);return}if(n.backspace||n.delete){p(t=>t.slice(0,-1));return}i&&!n.ctrl&&!n.meta&&p(t=>t+i);return}if(m&&r.kind==="int"){if(n.escape){h(!1),u(null);return}if(n.return){b(w);return}if(n.backspace||n.delete){p(t=>t.slice(0,-1));return}/^[0-9]$/.test(i)&&p(t=>t+i);return}if(n.escape){a.back();return}if(n.upArrow){K(t=>(t-1+f.length)%f.length),u(null);return}if(n.downArrow){K(t=>(t+1)%f.length),u(null);return}if(r.kind==="bool"&&(i===" "||n.return)){b(T?"false":"true");return}if(r.kind==="enum"&&(n.leftArrow||n.rightArrow)){let t=r.options.indexOf(String(T)),D=n.rightArrow?1:-1,R=r.options[(t+D+r.options.length)%r.options.length];b(R);return}if(r.kind==="dictRef"&&n.return){a.navigate({name:"dict",params:{pickerMode:"set-default"}});return}(r.kind==="string"||r.kind==="int")&&n.return&&(p(String(T??"")),h(!0),u(null))});let z=Math.max(...f.map(i=>B(s.config.fields[i.labelKey])))+4;return S(g,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[c(d,{bold:!0,color:l.accent,children:s.config.title}),c(g,{marginTop:1,flexDirection:"column",flexGrow:1,children:f.map((i,n)=>{let t=n===y,D=N(e,i.path),R=j(i,D,t&&m?w:null,s,i.path==="defaultDict"?x:""),A=s.config.fields[i.labelKey],I=" ".repeat(Math.max(0,z-B(A)));return S(g,{children:[c(d,{color:t?l.accent:l.muted,children:t?"\u258C ":" "}),S(d,{bold:t,color:t?l.text:l.muted,children:[A,I]}),c(d,{color:t?l.accent:l.muted,children:R})]},i.path)})}),v&&c(g,{marginTop:1,children:S(d,{color:l.error,children:["! ",v]})}),c(g,{marginTop:1,children:c(d,{color:l.muted,children:L(r,m,s)})})]})}function j(a,e,o,s,x){return o!==null?`${o}_`:a.kind==="bool"?e?`\u2713 ${s.common.on}`:`\u2717 ${s.common.off}`:a.kind==="dictRef"?e?C(x||String(e),24):"\u2014":a.kind==="enum"?`< ${s.config.enumValues[a.labelKey]?.[String(e)]??String(e)} >`:String(e??"")}function L(a,e,o){return e?o.config.hints.editing:a.kind==="bool"?o.config.hints.bool:a.kind==="enum"?o.config.hints.enum:a.kind==="dictRef"?o.config.hints.dictRef:o.config.hints.stringOrInt}export{U as ConfigEditor};
|
|
2
|
+
//# sourceMappingURL=ConfigEditor-KAL2MLQO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ui/screens/ConfigEditor.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Box, Text, useInput } from 'ink';\nimport { useNav } from '../nav.js';\nimport { useAppState } from '../app-state.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { useDictName } from '../registry-context.js';\nimport { setByPath, type Config } from '../../infra/config-store.js';\nimport { PALETTE } from '../components/BigWord.js';\nimport { visibleWidth } from '../../util/text.js';\nimport { truncateName } from '../../util/dict-name.js';\nimport type { Strings } from '../../i18n/strings.js';\n\ntype FieldSpec =\n | { kind: 'enum'; path: string; labelKey: keyof Strings['config']['fields']; options: string[] }\n | { kind: 'bool'; path: string; labelKey: keyof Strings['config']['fields'] }\n | { kind: 'int'; path: string; labelKey: keyof Strings['config']['fields']; min: number; max: number }\n | { kind: 'string'; path: string; labelKey: keyof Strings['config']['fields'] }\n | { kind: 'dictRef'; path: 'defaultDict'; labelKey: keyof Strings['config']['fields'] };\n\nconst FIELDS: FieldSpec[] = [\n { kind: 'dictRef', path: 'defaultDict', labelKey: 'defaultDict' },\n { kind: 'enum', path: 'defaultMode', labelKey: 'defaultMode', options: ['order', 'dictation', 'review', 'random', 'loop'] },\n { kind: 'enum', path: 'accent', labelKey: 'accent', options: ['us', 'uk'] },\n { kind: 'enum', path: 'language', labelKey: 'language', options: ['auto', 'zh', 'en'] },\n { kind: 'enum', path: 'mirror', labelKey: 'mirror', options: ['jsdelivr', 'github'] },\n { kind: 'enum', path: 'stealth', labelKey: 'stealth', options: ['off', 'menu', 'default'] },\n { kind: 'int', path: 'chapterSize', labelKey: 'chapterSize', min: 1, max: 200 },\n { kind: 'bool', path: 'autoplayPronunciation', labelKey: 'autoplayPronunciation' },\n { kind: 'bool', path: 'sounds.master', labelKey: 'soundsMaster' },\n { kind: 'bool', path: 'sounds.keystroke', labelKey: 'soundsKeystroke' },\n { kind: 'bool', path: 'sounds.feedback', labelKey: 'soundsFeedback' },\n {\n kind: 'enum',\n path: 'sounds.pronunciationRate',\n labelKey: 'soundsPronunciationRate',\n options: ['0.5', '0.75', '1', '1.25', '1.5'],\n },\n {\n kind: 'enum',\n path: 'sounds.pronunciationSource',\n labelKey: 'soundsPronunciationSource',\n options: ['youdao', 'dictapi'],\n },\n];\n\nfunction getByPath(cfg: Config, path: string): unknown {\n return path.split('.').reduce<unknown>((acc, k) => {\n if (acc && typeof acc === 'object') return (acc as Record<string, unknown>)[k];\n return undefined;\n }, cfg);\n}\n\nexport function ConfigEditor() {\n const nav = useNav();\n const { cfg, setCfg } = useAppState();\n const t = useStrings();\n const defaultDictName = useDictName(cfg.defaultDict);\n const [selected, setSelected] = useState(0);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const field = FIELDS[selected]!;\n const currentValue = getByPath(cfg, field.path);\n\n const commit = async (raw: string) => {\n try {\n const next = setByPath(cfg, field.path, raw);\n await setCfg(next);\n setEditing(false);\n setError(null);\n } catch (err) {\n setError((err as Error).message);\n }\n };\n\n useInput((input, key) => {\n if (editing && field.kind === 'string') {\n if (key.escape) {\n setEditing(false);\n setError(null);\n return;\n }\n if (key.return) {\n void commit(draft);\n return;\n }\n if (key.backspace || key.delete) {\n setDraft((d) => d.slice(0, -1));\n return;\n }\n if (input && !key.ctrl && !key.meta) setDraft((d) => d + input);\n return;\n }\n if (editing && field.kind === 'int') {\n if (key.escape) {\n setEditing(false);\n setError(null);\n return;\n }\n if (key.return) {\n void commit(draft);\n return;\n }\n if (key.backspace || key.delete) {\n setDraft((d) => d.slice(0, -1));\n return;\n }\n if (/^[0-9]$/.test(input)) setDraft((d) => d + input);\n return;\n }\n\n if (key.escape) {\n nav.back();\n return;\n }\n if (key.upArrow) {\n setSelected((i) => (i - 1 + FIELDS.length) % FIELDS.length);\n setError(null);\n return;\n }\n if (key.downArrow) {\n setSelected((i) => (i + 1) % FIELDS.length);\n setError(null);\n return;\n }\n if (field.kind === 'bool' && (input === ' ' || key.return)) {\n void commit(currentValue ? 'false' : 'true');\n return;\n }\n if (field.kind === 'enum' && (key.leftArrow || key.rightArrow)) {\n const idx = field.options.indexOf(String(currentValue));\n const delta = key.rightArrow ? 1 : -1;\n const next = field.options[(idx + delta + field.options.length) % field.options.length]!;\n void commit(next);\n return;\n }\n if (field.kind === 'dictRef' && key.return) {\n nav.navigate({ name: 'dict', params: { pickerMode: 'set-default' } });\n return;\n }\n if ((field.kind === 'string' || field.kind === 'int') && key.return) {\n setDraft(String(currentValue ?? ''));\n setEditing(true);\n setError(null);\n }\n });\n\n const labelW = Math.max(...FIELDS.map((f) => visibleWidth(t.config.fields[f.labelKey]))) + 4;\n\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1} width=\"100%\" height=\"100%\">\n <Text bold color={PALETTE.accent}>\n {t.config.title}\n </Text>\n\n <Box marginTop={1} flexDirection=\"column\" flexGrow={1}>\n {FIELDS.map((f, i) => {\n const active = i === selected;\n const value = getByPath(cfg, f.path);\n const display = renderValue(\n f,\n value,\n active && editing ? draft : null,\n t,\n f.path === 'defaultDict' ? defaultDictName : '',\n );\n const label = t.config.fields[f.labelKey];\n const pad = ' '.repeat(Math.max(0, labelW - visibleWidth(label)));\n return (\n <Box key={f.path}>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>{active ? '▌ ' : ' '}</Text>\n <Text bold={active} color={active ? PALETTE.text : PALETTE.muted}>\n {label}\n {pad}\n </Text>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>{display}</Text>\n </Box>\n );\n })}\n </Box>\n\n {error && (\n <Box marginTop={1}>\n <Text color={PALETTE.error}>! {error}</Text>\n </Box>\n )}\n\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{hintFor(field, editing, t)}</Text>\n </Box>\n </Box>\n );\n}\n\nfunction renderValue(\n field: FieldSpec,\n value: unknown,\n draft: string | null,\n t: Strings,\n dictDisplayName: string,\n): string {\n if (draft !== null) return `${draft}_`;\n if (field.kind === 'bool') return value ? `✓ ${t.common.on}` : `✗ ${t.common.off}`;\n if (field.kind === 'dictRef') {\n if (!value) return '—';\n return truncateName(dictDisplayName || String(value), 24);\n }\n if (field.kind === 'enum') {\n const map = (t.config.enumValues as Record<string, Record<string, string> | undefined>)[\n field.labelKey\n ];\n const label = map?.[String(value)] ?? String(value);\n return `< ${label} >`;\n }\n return String(value ?? '');\n}\n\nfunction hintFor(field: FieldSpec, editing: boolean, t: Strings): string {\n if (editing) return t.config.hints.editing;\n if (field.kind === 'bool') return t.config.hints.bool;\n if (field.kind === 'enum') return t.config.hints.enum;\n if (field.kind === 'dictRef') return t.config.hints.dictRef;\n return t.config.hints.stringOrInt;\n}\n"],"mappings":"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","names":["useState","Box","Text","useInput","jsx","jsxs","FIELDS","getByPath","cfg","path","acc","k","ConfigEditor","nav","useNav","setCfg","useAppState","t","useStrings","defaultDictName","useDictName","selected","setSelected","useState","editing","setEditing","draft","setDraft","error","setError","field","currentValue","commit","raw","next","setByPath","err","useInput","input","key","d","i","idx","delta","labelW","f","visibleWidth","Box","Text","PALETTE","active","value","display","renderValue","label","pad","hintFor","dictDisplayName","truncateName"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as F,d as H,f as N}from"./chunk-
|
|
2
|
-
//# sourceMappingURL=DictBrowser-
|
|
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-UWTJMVJ6.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-OF274IOX.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{b as p,d as a,f as i}from"./chunk-
|
|
2
|
-
//# sourceMappingURL=HelpScreen-
|
|
1
|
+
import{b as p,d as a,f as i}from"./chunk-UWTJMVJ6.js";import{Box as l,Text as n,useInput as x}from"ink";import{jsx as o,jsxs as c}from"react/jsx-runtime";function f(){let m=p(),t=a();x((s,r)=>{r.escape&&m.back()});let e=t.help.keys,u=[{title:t.help.sections.global,keys:[e.helpScreen,e.quit]},{title:t.help.sections.main,keys:[e.navigate,e.select,e.letterJump,e.helpScreen]},{title:t.help.sections.practice,keys:[e.pause,e.skip,e.replay,e.resume,e.nextChapter,e.reviewMistakes,e.stealthToggle,e.backMenu]},{title:t.help.sections.dict,keys:[e.navigate,e.filter,e.itemActions,e.moreActions,e.backScreen]},{title:t.help.sections.config,keys:[e.navigate,e.select,e.backMenu]},{title:t.help.sections.stats,keys:[e.cycleWindow,e.backMenu]},{title:t.help.sections.word,keys:[e.filter,e.navigate,e.backMenu]}];return c(l,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[c(l,{children:[o(n,{bold:!0,color:i.accent,children:t.help.title}),o(n,{color:i.muted,children:" \xB7 "}),o(n,{color:i.muted,children:t.help.subtitle})]}),o(l,{marginTop:1,flexDirection:"column",flexGrow:1,children:u.map(s=>c(l,{flexDirection:"column",marginTop:1,children:[o(n,{bold:!0,color:i.text,children:s.title}),s.keys.map((r,k)=>o(l,{children:c(n,{color:i.muted,children:[" \xB7 ",r]})},k))]},s.title))}),o(l,{marginTop:1,children:o(n,{color:i.muted,children:t.help.footer})})]})}export{f as HelpScreen};
|
|
2
|
+
//# sourceMappingURL=HelpScreen-O5RSN3C4.js.map
|
|
@@ -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-V3TAMY5K.js";import{b as Y}from"./chunk-4GNY4LY7.js";import"./chunk-QLTHO2HH.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-3TQFRACL.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 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-UWTJMVJ6.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-FOCGEFWL.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{b as O,c as V,d as Y,e as j,g as F}from"./chunk-HJIGZU3E.js";import{a as N,d as $}from"./chunk-G3DQB7FI.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-
|
|
2
|
-
//# sourceMappingURL=StatsViewer-
|
|
1
|
+
import{b as O,c as V,d as Y,e as j,g as F}from"./chunk-HJIGZU3E.js";import{a as N,d as $}from"./chunk-G3DQB7FI.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-UWTJMVJ6.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([O(),N()]);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=j(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=$(x,8),p=F(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:1,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," ",V(s),t.wpm," ",Math.round(Y(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-XOOMUPGL.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ui/screens/StatsViewer.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { Box, Text, useInput, useStdout } from 'ink';\nimport { useNav } from '../nav.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { useDictName } from '../registry-context.js';\nimport { PALETTE } from '../components/BigWord.js';\nimport {\n loadSessions,\n computeWPM,\n accuracy,\n windowAggregate,\n dailyStreak,\n type SessionRecord,\n} from '../../domain/stats.js';\nimport { loadMistakes, topN, type MistakeBook } from '../../domain/mistakes.js';\nimport { truncateName } from '../../util/dict-name.js';\nimport { pad } from '../../util/text.js';\n\nconst DAY_WINDOWS = [7, 14, 30, 90];\n\nexport function StatsViewer() {\n const nav = useNav();\n const t = useStrings();\n const [sessions, setSessions] = useState<SessionRecord[] | null>(null);\n const [book, setBook] = useState<MistakeBook | null>(null);\n const [windowIdx, setWindowIdx] = useState(1);\n\n useEffect(() => {\n void (async () => {\n const [s, b] = await Promise.all([loadSessions(), loadMistakes()]);\n setSessions(s);\n setBook(b);\n })();\n }, []);\n\n useInput((_input, key) => {\n if (key.escape) {\n nav.back();\n return;\n }\n if (key.rightArrow) setWindowIdx((i) => (i + 1) % DAY_WINDOWS.length);\n if (key.leftArrow) setWindowIdx((i) => (i - 1 + DAY_WINDOWS.length) % DAY_WINDOWS.length);\n });\n\n if (!sessions || !book) {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.muted}>{t.stats.loading}</Text>\n </Box>\n );\n }\n\n if (sessions.length === 0) {\n return (\n <Box flexDirection=\"column\" alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.muted}>{t.stats.none}</Text>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.stats.nonePractice}</Text>\n </Box>\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>Esc {t.common.back}</Text>\n </Box>\n </Box>\n );\n }\n\n const days = DAY_WINDOWS[windowIdx]!;\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 const recent = sessions.slice(-5).reverse();\n const top = topN(book, 8);\n\n const agg = windowAggregate(sessions, days);\n const speedPct = agg.peakWpmLifetime > 0 ? Math.min(1, agg.avgWpm / agg.peakWpmLifetime) : 0;\n const accPctFrac = Math.min(1, agg.avgAccPct / 100);\n const sessionsPctFrac = days > 0 ? Math.min(1, agg.sessionsInWindow / days) : 0;\n\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1} width=\"100%\" height=\"100%\">\n <Text bold color={PALETTE.accent}>\n {t.stats.title}\n </Text>\n\n <Box marginTop={1} flexDirection=\"column\">\n <Text color={PALETTE.muted}>{t.stats.lifetime}</Text>\n <Box marginTop={1}>\n <Stat label={t.stats.sessions} value={String(sessions.length)} />\n <Stat label={t.stats.words} value={String(totalWords)} />\n <Stat label={t.stats.errors} value={String(totalErrors)} />\n <Stat label={t.stats.wpm} value={String(overallWpm)} accent />\n <Stat label={t.stats.accuracy} value={`${Math.round(overallAcc * 1000) / 10}%`} accent />\n <Stat label={t.stats.streak} value={`${streak}d`} accent />\n </Box>\n </Box>\n\n <Box marginTop={2} flexDirection=\"column\">\n <Text color={PALETTE.muted}>{t.stats.last(days)}</Text>\n <Box marginTop={1} flexDirection=\"column\" borderStyle=\"round\" borderColor={PALETTE.muted} paddingX={2} paddingY={0}>\n <MetricBar\n label={t.stats.bars.speed}\n frac={speedPct}\n valueText={`${agg.avgWpm} wpm`}\n pct={Math.round(speedPct * 100)}\n />\n <MetricBar\n label={t.stats.bars.accuracy}\n frac={accPctFrac}\n valueText={`${agg.avgAccPct}%`}\n pct={Math.round(accPctFrac * 100)}\n />\n <MetricBar\n label={t.stats.bars.sessions}\n frac={sessionsPctFrac}\n valueText={`${agg.sessionsInWindow} / ${days}`}\n pct={Math.round(sessionsPctFrac * 100)}\n />\n </Box>\n </Box>\n\n <Box marginTop={2} flexDirection=\"column\">\n <Text color={PALETTE.muted}>{t.stats.recent}</Text>\n {recent.map((s, i) => (\n <RecentRow key={i} session={s} units={t.stats.recentUnits} />\n ))}\n </Box>\n\n {top.length > 0 && (\n <Box marginTop={2} flexDirection=\"column\">\n <Text color={PALETTE.muted}>{t.stats.topMistakes}</Text>\n {top.map(([word, entry]) => (\n <MistakeRow\n key={word}\n word={word}\n count={entry.count}\n dictIds={entry.dictIds}\n multiSuffix={t.stats.multiDictSuffix}\n />\n ))}\n </Box>\n )}\n\n <Box flexGrow={1} />\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.stats.footer}</Text>\n </Box>\n </Box>\n );\n}\n\nfunction RecentRow({\n session,\n units,\n}: {\n session: SessionRecord;\n units: { words: string; errors: string; wpm: string };\n}) {\n const name = useDictName(session.dictId);\n const display = truncateName(name, 14);\n return (\n <Box>\n <Text color={PALETTE.muted}> {session.ts.replace('T', ' ').slice(0, 16)} </Text>\n <Text color={PALETTE.text}>{display.padEnd(14)}</Text>\n <Text color={PALETTE.muted}>\n {' '}ch{String(session.chapter + 1).padStart(3)} {session.mode.padEnd(9)} {String(session.wordCount).padStart(3)}{units.words} {session.errors}{units.errors} {computeWPM(session)}{units.wpm} {Math.round(accuracy(session) * 1000) / 10}%\n </Text>\n </Box>\n );\n}\n\nfunction MistakeRow({\n word,\n count,\n dictIds,\n multiSuffix,\n}: {\n word: string;\n count: number;\n dictIds: string[];\n multiSuffix: (n: number) => string;\n}) {\n const firstId = dictIds[0] ?? '';\n const firstName = useDictName(firstId);\n const suffix = dictIds.length > 1 ? multiSuffix(dictIds.length - 1) : '';\n return (\n <Box>\n <Text color={PALETTE.error}> {String(count).padStart(3)} </Text>\n <Text color={PALETTE.text}>{word.padEnd(20)}</Text>\n <Text color={PALETTE.muted}>\n {truncateName(firstName, 20)}\n {suffix}\n </Text>\n </Box>\n );\n}\n\nfunction Stat({ label, value, accent = false }: { label: string; value: string; accent?: boolean }) {\n return (\n <Box marginRight={3}>\n <Text color={PALETTE.muted}>{label} </Text>\n <Text bold color={accent ? PALETTE.accent : PALETTE.text}>\n {value}\n </Text>\n </Box>\n );\n}\n\n// Pure layout calculation — extracted for testing without an Ink renderer.\n// OVERHEAD = outer paddingX(4) + inner border(2) + inner paddingX(4) + 1 col slack = 11.\n// The +1 slack guards against off-by-one in Ink/terminal cell rounding —\n// content was previously sized exactly to the inner width and any rounding\n// would wrap the bar onto a second line.\n// Fixed widths sum to 24; bar takes whatever cols remain, clamped 0..60.\nexport function calcMetricBarLayout(cols: number): {\n barWidth: number;\n labelWidth: number;\n valueWidth: number;\n pctWidth: number;\n marginLeft: number;\n} {\n const OVERHEAD = 11;\n const labelWidth = 9;\n const valueWidth = 8;\n const pctWidth = 5;\n const marginLeft = 2;\n const fixed = labelWidth + marginLeft + valueWidth + pctWidth;\n const barWidth = Math.max(0, Math.min(60, cols - OVERHEAD - fixed));\n return { barWidth, labelWidth, valueWidth, pctWidth, marginLeft };\n}\n\n// MetricBar uses pure inline <Text> siblings with CJK-aware pad() padding\n// rather than nested <Box width=N> wrappers. Mixed Box+Text flex children\n// can wrap unpredictably at terminal-width boundaries; inline text widths\n// are deterministic.\nfunction MetricBar({\n label,\n frac,\n valueText,\n pct,\n}: {\n label: string;\n frac: number;\n valueText: string;\n pct: number;\n}) {\n const { stdout } = useStdout();\n const cols = stdout?.columns ?? 80;\n const layout = calcMetricBarLayout(cols);\n const clamped = Math.max(0, Math.min(1, frac));\n const filled = Math.round(layout.barWidth * clamped);\n const empty = layout.barWidth - filled;\n const labelStr = pad(label, layout.labelWidth);\n const valueStr = pad(valueText, layout.valueWidth);\n const pctStr = String(pct).padStart(layout.pctWidth - 1, ' ') + '%';\n const margin = ' '.repeat(layout.marginLeft);\n return (\n <Box>\n <Text color={PALETTE.muted}>{labelStr}</Text>\n <Text color={PALETTE.accent}>{'━'.repeat(filled)}</Text>\n <Text color={PALETTE.muted}>{'─'.repeat(empty)}</Text>\n <Text color={PALETTE.text}>{margin}{valueStr}</Text>\n <Text color={PALETTE.muted}>{pctStr}</Text>\n </Box>\n );\n}\n"],"mappings":"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","names":["useEffect","useState","Box","Text","useInput","useStdout","jsx","jsxs","DAY_WINDOWS","StatsViewer","nav","useNav","useStrings","sessions","setSessions","useState","book","setBook","windowIdx","setWindowIdx","useEffect","s","b","loadSessions","loadMistakes","useInput","_input","key","i","Box","Text","PALETTE","days","streak","dailyStreak","totalWords","a","totalErrors","totalMs","firstTryWords","overallWpm","overallAcc","recent","top","topN","agg","windowAggregate","speedPct","accPctFrac","sessionsPctFrac","Stat","MetricBar","RecentRow","word","entry","MistakeRow","session","units","name","useDictName","display","truncateName","computeWPM","accuracy","count","dictIds","multiSuffix","firstId","firstName","suffix","label","value","accent","calcMetricBarLayout","cols","frac","valueText","pct","stdout","useStdout","layout","clamped","filled","empty","labelStr","pad","valueStr","pctStr","margin"]}
|
|
1
|
+
{"version":3,"sources":["../src/ui/screens/StatsViewer.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { Box, Text, useInput, useStdout } from 'ink';\nimport { useNav } from '../nav.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { useDictName } from '../registry-context.js';\nimport { PALETTE } from '../components/BigWord.js';\nimport {\n loadSessions,\n computeWPM,\n accuracy,\n windowAggregate,\n dailyStreak,\n type SessionRecord,\n} from '../../domain/stats.js';\nimport { loadMistakes, topN, type MistakeBook } from '../../domain/mistakes.js';\nimport { truncateName } from '../../util/dict-name.js';\nimport { pad } from '../../util/text.js';\n\nconst DAY_WINDOWS = [7, 14, 30, 90];\n\nexport function StatsViewer() {\n const nav = useNav();\n const t = useStrings();\n const [sessions, setSessions] = useState<SessionRecord[] | null>(null);\n const [book, setBook] = useState<MistakeBook | null>(null);\n const [windowIdx, setWindowIdx] = useState(1);\n\n useEffect(() => {\n void (async () => {\n const [s, b] = await Promise.all([loadSessions(), loadMistakes()]);\n setSessions(s);\n setBook(b);\n })();\n }, []);\n\n useInput((_input, key) => {\n if (key.escape) {\n nav.back();\n return;\n }\n if (key.rightArrow) setWindowIdx((i) => (i + 1) % DAY_WINDOWS.length);\n if (key.leftArrow) setWindowIdx((i) => (i - 1 + DAY_WINDOWS.length) % DAY_WINDOWS.length);\n });\n\n if (!sessions || !book) {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.muted}>{t.stats.loading}</Text>\n </Box>\n );\n }\n\n if (sessions.length === 0) {\n return (\n <Box flexDirection=\"column\" alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.muted}>{t.stats.none}</Text>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.stats.nonePractice}</Text>\n </Box>\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>Esc {t.common.back}</Text>\n </Box>\n </Box>\n );\n }\n\n const days = DAY_WINDOWS[windowIdx]!;\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 const recent = sessions.slice(-5).reverse();\n const top = topN(book, 8);\n\n const agg = windowAggregate(sessions, days);\n const speedPct = agg.peakWpmLifetime > 0 ? Math.min(1, agg.avgWpm / agg.peakWpmLifetime) : 0;\n const accPctFrac = Math.min(1, agg.avgAccPct / 100);\n const sessionsPctFrac = days > 0 ? Math.min(1, agg.sessionsInWindow / days) : 0;\n\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1} width=\"100%\" height=\"100%\">\n <Text bold color={PALETTE.accent}>\n {t.stats.title}\n </Text>\n\n <Box marginTop={1} flexDirection=\"column\">\n <Text color={PALETTE.muted}>{t.stats.lifetime}</Text>\n <Box marginTop={1}>\n <Stat label={t.stats.sessions} value={String(sessions.length)} />\n <Stat label={t.stats.words} value={String(totalWords)} />\n <Stat label={t.stats.errors} value={String(totalErrors)} />\n <Stat label={t.stats.wpm} value={String(overallWpm)} accent />\n <Stat label={t.stats.accuracy} value={`${Math.round(overallAcc * 1000) / 10}%`} accent />\n <Stat label={t.stats.streak} value={`${streak}d`} accent />\n </Box>\n </Box>\n\n <Box marginTop={2} flexDirection=\"column\">\n <Text color={PALETTE.muted}>{t.stats.last(days)}</Text>\n <Box marginTop={1} flexDirection=\"column\" borderStyle=\"round\" borderColor={PALETTE.muted} paddingX={2} paddingY={1}>\n <MetricBar\n label={t.stats.bars.speed}\n frac={speedPct}\n valueText={`${agg.avgWpm} wpm`}\n pct={Math.round(speedPct * 100)}\n />\n <MetricBar\n label={t.stats.bars.accuracy}\n frac={accPctFrac}\n valueText={`${agg.avgAccPct}%`}\n pct={Math.round(accPctFrac * 100)}\n />\n <MetricBar\n label={t.stats.bars.sessions}\n frac={sessionsPctFrac}\n valueText={`${agg.sessionsInWindow} / ${days}`}\n pct={Math.round(sessionsPctFrac * 100)}\n />\n </Box>\n </Box>\n\n <Box marginTop={2} flexDirection=\"column\">\n <Text color={PALETTE.muted}>{t.stats.recent}</Text>\n {recent.map((s, i) => (\n <RecentRow key={i} session={s} units={t.stats.recentUnits} />\n ))}\n </Box>\n\n {top.length > 0 && (\n <Box marginTop={2} flexDirection=\"column\">\n <Text color={PALETTE.muted}>{t.stats.topMistakes}</Text>\n {top.map(([word, entry]) => (\n <MistakeRow\n key={word}\n word={word}\n count={entry.count}\n dictIds={entry.dictIds}\n multiSuffix={t.stats.multiDictSuffix}\n />\n ))}\n </Box>\n )}\n\n <Box flexGrow={1} />\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.stats.footer}</Text>\n </Box>\n </Box>\n );\n}\n\nfunction RecentRow({\n session,\n units,\n}: {\n session: SessionRecord;\n units: { words: string; errors: string; wpm: string };\n}) {\n const name = useDictName(session.dictId);\n const display = truncateName(name, 14);\n return (\n <Box>\n <Text color={PALETTE.muted}> {session.ts.replace('T', ' ').slice(0, 16)} </Text>\n <Text color={PALETTE.text}>{display.padEnd(14)}</Text>\n <Text color={PALETTE.muted}>\n {' '}ch{String(session.chapter + 1).padStart(3)} {session.mode.padEnd(9)} {String(session.wordCount).padStart(3)}{units.words} {session.errors}{units.errors} {computeWPM(session)}{units.wpm} {Math.round(accuracy(session) * 1000) / 10}%\n </Text>\n </Box>\n );\n}\n\nfunction MistakeRow({\n word,\n count,\n dictIds,\n multiSuffix,\n}: {\n word: string;\n count: number;\n dictIds: string[];\n multiSuffix: (n: number) => string;\n}) {\n const firstId = dictIds[0] ?? '';\n const firstName = useDictName(firstId);\n const suffix = dictIds.length > 1 ? multiSuffix(dictIds.length - 1) : '';\n return (\n <Box>\n <Text color={PALETTE.error}> {String(count).padStart(3)} </Text>\n <Text color={PALETTE.text}>{word.padEnd(20)}</Text>\n <Text color={PALETTE.muted}>\n {truncateName(firstName, 20)}\n {suffix}\n </Text>\n </Box>\n );\n}\n\nfunction Stat({ label, value, accent = false }: { label: string; value: string; accent?: boolean }) {\n return (\n <Box marginRight={3}>\n <Text color={PALETTE.muted}>{label} </Text>\n <Text bold color={accent ? PALETTE.accent : PALETTE.text}>\n {value}\n </Text>\n </Box>\n );\n}\n\n// Pure layout calculation — extracted for testing without an Ink renderer.\n// OVERHEAD = outer paddingX(4) + inner border(2) + inner paddingX(4) + 1 col slack = 11.\n// The +1 slack guards against off-by-one in Ink/terminal cell rounding —\n// content was previously sized exactly to the inner width and any rounding\n// would wrap the bar onto a second line.\n// Fixed widths sum to 24; bar takes whatever cols remain, clamped 0..60.\nexport function calcMetricBarLayout(cols: number): {\n barWidth: number;\n labelWidth: number;\n valueWidth: number;\n pctWidth: number;\n marginLeft: number;\n} {\n const OVERHEAD = 11;\n const labelWidth = 9;\n const valueWidth = 8;\n const pctWidth = 5;\n const marginLeft = 2;\n const fixed = labelWidth + marginLeft + valueWidth + pctWidth;\n const barWidth = Math.max(0, Math.min(60, cols - OVERHEAD - fixed));\n return { barWidth, labelWidth, valueWidth, pctWidth, marginLeft };\n}\n\n// MetricBar uses pure inline <Text> siblings with CJK-aware pad() padding\n// rather than nested <Box width=N> wrappers. Mixed Box+Text flex children\n// can wrap unpredictably at terminal-width boundaries; inline text widths\n// are deterministic.\nfunction MetricBar({\n label,\n frac,\n valueText,\n pct,\n}: {\n label: string;\n frac: number;\n valueText: string;\n pct: number;\n}) {\n const { stdout } = useStdout();\n const cols = stdout?.columns ?? 80;\n const layout = calcMetricBarLayout(cols);\n const clamped = Math.max(0, Math.min(1, frac));\n const filled = Math.round(layout.barWidth * clamped);\n const empty = layout.barWidth - filled;\n const labelStr = pad(label, layout.labelWidth);\n const valueStr = pad(valueText, layout.valueWidth);\n const pctStr = String(pct).padStart(layout.pctWidth - 1, ' ') + '%';\n const margin = ' '.repeat(layout.marginLeft);\n return (\n <Box>\n <Text color={PALETTE.muted}>{labelStr}</Text>\n <Text color={PALETTE.accent}>{'━'.repeat(filled)}</Text>\n <Text color={PALETTE.muted}>{'─'.repeat(empty)}</Text>\n <Text color={PALETTE.text}>{margin}{valueStr}</Text>\n <Text color={PALETTE.muted}>{pctStr}</Text>\n </Box>\n );\n}\n"],"mappings":"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","names":["useEffect","useState","Box","Text","useInput","useStdout","jsx","jsxs","DAY_WINDOWS","StatsViewer","nav","useNav","useStrings","sessions","setSessions","useState","book","setBook","windowIdx","setWindowIdx","useEffect","s","b","loadSessions","loadMistakes","useInput","_input","key","i","Box","Text","PALETTE","days","streak","dailyStreak","totalWords","a","totalErrors","totalMs","firstTryWords","overallWpm","overallAcc","recent","top","topN","agg","windowAggregate","speedPct","accPctFrac","sessionsPctFrac","Stat","MetricBar","RecentRow","word","entry","MistakeRow","session","units","name","useDictName","display","truncateName","computeWPM","accuracy","count","dictIds","multiSuffix","firstId","firstName","suffix","label","value","accent","calcMetricBarLayout","cols","frac","valueText","pct","stdout","useStdout","layout","clamped","filled","empty","labelStr","pad","valueStr","pctStr","margin"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{c as b}from"./chunk-
|
|
2
|
-
//# sourceMappingURL=WordLookup-
|
|
1
|
+
import{c as b}from"./chunk-DURXS5MX.js";import"./chunk-7LTZGB7F.js";import{a as M}from"./chunk-G3DQB7FI.js";import{b as B,e as h}from"./chunk-QG7ZTS2G.js";import{b as L,d as g,f as t}from"./chunk-UWTJMVJ6.js";import"./chunk-NA5UNUVL.js";import{a as I}from"./chunk-E6BBQALJ.js";import{useEffect as E,useState as u}from"react";import{Box as n,Text as r,useInput as S}from"ink";import{readdir as $}from"fs/promises";import{Fragment as G,jsx as o,jsxs as d}from"react/jsx-runtime";async function N(){try{return(await $(I.dictsDir)).filter(e=>e.endsWith(".json")&&!e.endsWith(".meta.json")).map(e=>e.replace(/\.json$/,""))}catch{return[]}}function O(){let i=L(),e=g(),[m,x]=u(""),[l,w]=u([]),[W,C]=u({}),[H,j]=u(!0),[k,f]=u(0);E(()=>{(async()=>{let a=await N(),c=[];for(let s of a){let y=await b(s);if(y)for(let A of y)c.push({dictId:s,word:A})}w(c),C(await M()),j(!1)})()},[]);let T=m.toLowerCase().trim(),p=T?l.filter(a=>a.word.name.toLowerCase().includes(T)).slice(0,50):[];if(S((a,c)=>{if(c.escape){i.back();return}if(c.upArrow){f(s=>Math.max(0,s-1));return}if(c.downArrow){f(s=>Math.min(p.length-1,s+1));return}if(c.backspace||c.delete){x(s=>s.slice(0,-1)),f(0);return}a&&!c.ctrl&&!c.meta&&a.trim().length>0&&(x(s=>s+a),f(0))}),H)return o(n,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:o(r,{color:t.muted,children:e.word.indexing})});if(l.length===0)return d(n,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[o(r,{color:t.muted,children:e.word.none}),o(n,{marginTop:1,children:o(r,{color:t.muted,children:e.word.pullFirst})}),o(n,{marginTop:2,children:d(r,{color:t.muted,children:["[Esc] ",e.common.back]})})]});let D=p[k];return d(n,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[d(n,{children:[o(r,{bold:!0,color:t.accent,children:e.word.title}),o(n,{flexGrow:1}),o(r,{color:t.muted,children:e.word.countAcross(l.length)})]}),d(n,{marginTop:1,children:[o(r,{color:t.muted,children:"> "}),o(r,{color:t.text,children:m}),o(r,{color:t.accent,children:"_"})]}),d(n,{marginTop:1,flexGrow:1,children:[d(n,{flexDirection:"column",width:"40%",children:[p.map((a,c)=>o(q,{hit:a,active:c===k},`${a.dictId}-${a.word.name}-${c}`)),p.length===0&&T&&o(r,{color:t.muted,children:e.word.noMatches(m)})]}),o(n,{flexDirection:"column",width:"60%",paddingLeft:2,children:D&&o(v,{hit:D,book:W})})]}),o(n,{marginTop:1,children:o(r,{color:t.muted,children:e.word.footer})})]})}function q({hit:i,active:e}){let m=B(i.dictId);return d(n,{children:[o(r,{color:e?t.accent:t.muted,children:e?"\u258C ":" "}),o(r,{bold:e,color:e?t.text:t.muted,children:i.word.name.padEnd(20)}),o(r,{color:t.muted,children:h(m,18)})]})}function v({hit:i,book:e}){let m=g(),x=B(i.dictId);return d(G,{children:[o(r,{bold:!0,color:t.text,children:i.word.name}),d(n,{marginTop:1,children:[i.word.usphone&&d(r,{dimColor:!0,color:t.muted,children:["US ",i.word.usphone," "]}),i.word.ukphone&&d(r,{dimColor:!0,color:t.muted,children:["UK ",i.word.ukphone]})]}),o(n,{marginTop:1,flexDirection:"column",children:(i.word.trans??[]).map((l,w)=>d(r,{color:t.primary,children:["\xB7 ",l]},w))}),o(n,{marginTop:1,children:o(r,{color:t.muted,children:m.word.inDict(h(x,22))})}),e[i.word.name]&&o(n,{marginTop:1,children:o(r,{color:t.error,children:m.word.mistakes(e[i.word.name].count,e[i.word.name].lastSeen.slice(0,10))})})]})}export{O as WordLookup};
|
|
2
|
+
//# sourceMappingURL=WordLookup-MWWYQBCM.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{a as R,b as P,c as H}from"./chunk-6ROGUGNX.js";import{a as E,b as k}from"./chunk-CQRKGMPU.js";import{a as D,b as C,c as S,e as I}from"./chunk-QG7ZTS2G.js";import{a as L,b as M,c as N,d as B,f as a}from"./chunk-UWTJMVJ6.js";var J="\x1B[?1049h\x1B[?25l\x1B[2J\x1B[H",z="\x1B[?25h\x1B[?1049l",w=!1,$=!1;function K(){if($)return;$=!0;let e=()=>{if(w){try{process.stdout.write(z)}catch{}w=!1}};process.once("exit",e),process.once("SIGINT",()=>{e(),process.exit(130)}),process.once("SIGTERM",()=>{e(),process.exit(143)})}function W(){w||process.stdout.isTTY&&process.env.QWERTY_NO_ALTSCREEN!=="1"&&(K(),process.stdout.write(J),w=!0)}function A(){if(w){try{process.stdout.write(z)}catch{}w=!1}}import{Suspense as re,lazy as y,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 o=()=>{r({rows:process.stdout.rows??24,cols:process.stdout.columns??80})};return process.stdout.on("resize",o),()=>{process.stdout.off("resize",o),A()}},[]),U(Q,{width:s.cols,height:s.rows,flexDirection:"column",children:e})}import{useState as Z}from"react";import{Box as v,Text as f,useApp as ee,useInput as te}from"ink";import{jsx as g,jsxs as x}from"react/jsx-runtime";function Y({cfg:e}){let[t,s]=Z(0),{exit:r}=ee(),o=M(),h=P(),p=B(),l=C(e.defaultDict),n=p.mainMenu.items,d=c=>{if(c&&e.defaultDict){H({type:"stealth",dictId:e.defaultDict,chapterIndex:0,mode:e.defaultMode}),r();return}e.defaultDict?o.navigate({name:"practice",params:{dictId:e.defaultDict,chapterIndex:0,mode:e.defaultMode,stealth:c}}):o.navigate({name:"dict",params:{pickerMode:"choose-then-practice"}})},m=[{key:"p",label:n.practiceLabel,hint:e.defaultDict?n.practiceHintWith(I(l,24)):n.practiceHintNone,run:()=>d(e.stealth==="default")}];(e.stealth==="menu"||e.stealth==="default")&&m.push({key:"b",label:n.stealthLabel,hint:n.stealthHint,run:()=>d(!0)}),m.push({key:"d",label:n.dictLabel,hint:n.dictHint,run:()=>o.navigate({name:"dict"})},{key:"w",label:n.wordLabel,hint:n.wordHint,run:()=>o.navigate({name:"word"})},{key:"s",label:n.statsLabel,hint:n.statsHint,run:()=>o.navigate({name:"stats"})},{key:"c",label:n.configLabel,hint:n.configHint,run:()=>o.navigate({name:"config"})},{key:"q",label:n.quitLabel,hint:n.quitHint,run:()=>r()});let V=Math.max(...m.map(c=>S(c.label)))+4;return te((c,b)=>{if(b.escape){r();return}if(b.upArrow&&s(u=>(u-1+m.length)%m.length),b.downArrow&&s(u=>(u+1)%m.length),b.return){m[t].run();return}if(c==="?"){o.navigate({name:"help"});return}for(let u of m)if(c===u.key){u.run();return}}),x(v,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",children:[e.stealth==="off"&&x(v,{children:[g(f,{bold:!0,color:a.accent,children:p.app.title}),x(f,{color:a.muted,children:[" \xB7 ",p.app.subtitle]})]}),g(v,{marginTop:e.stealth==="off"?2:0,flexDirection:"column",children:m.map((c,b)=>{let u=b===t,G=" ".repeat(Math.max(0,V-S(c.label)));return x(v,{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,G]}),g(f,{color:a.muted,children:c.hint})]},c.key)})}),g(v,{marginTop:2,children:x(f,{color:a.muted,children:[p.mainMenu.hint," \xB7 ",p.mainMenu.helpHint]})}),h.warning&&g(v,{marginTop:1,children:g(f,{color:a.warning,children:p.audio.noPlayer})})]})}import{jsx as i}from"react/jsx-runtime";var ce=y(()=>import("./PracticeScreen-FOCGEFWL.js").then(e=>({default:e.PracticeScreen}))),ue=y(()=>import("./DictBrowser-OF274IOX.js").then(e=>({default:e.DictBrowser}))),pe=y(()=>import("./ConfigEditor-KAL2MLQO.js").then(e=>({default:e.ConfigEditor}))),le=y(()=>import("./StatsViewer-XOOMUPGL.js").then(e=>({default:e.StatsViewer}))),me=y(()=>import("./WordLookup-MWWYQBCM.js").then(e=>({default:e.WordLookup}))),de=y(()=>import("./HelpScreen-O5RSN3C4.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(E,{initialCfg:t,children:i(he,{children:i(D,{children:i(R,{disabled:!t.sounds.master,children:i(L,{initial:e,children:s?i(j,{inline:!0}):i(F,{children:i(j,{})})})})})})})}function he({children:e}){let{cfg:t}=k();return i(N,{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(),o=oe(null);ae((n,d)=>{d.ctrl&&n==="c"&&r()});let h=t.current,p=ge(h);o.current!==p&&(!e&&process.stdout.isTTY&&process.stdout.write("\x1B[2J\x1B[H"),o.current=p);let l=(()=>{switch(h.name){case"main":return i(Y,{cfg:s});case"practice":return i(ce,{params:h.params});case"dict":return i(ue,{params:h.params});case"config":return i(pe,{});case"stats":return i(le,{});case"word":return i(me,{});case"help":return i(de,{})}})();return i(re,{fallback:i(fe,{}),children:l})}import T from"chalk";import xe from"boxen";function tt(){A()}function q(e,t){let s=Math.floor(e/1e3),r=Math.floor(s/60),o=s%60;return t==="zh"?r===0?`${o} \u79D2`:`${r} \u5206 ${o} \u79D2`:r===0?`${o}s`:`${r}m ${o}s`}function be(e){return e>=90?T.green:e<75?T.dim:t=>t}function Se(e,t,s){let r=[],o=[t.report.duration,t.report.practiced,t.report.chapters,t.report.words,t.report.accuracy,t.report.wpm];e.newMistakeWords>0&&o.push(t.report.newMistakes);let h=Math.max(...o.map(S)),p=d=>d+" ".repeat(Math.max(0,h-S(d))),l=(d,m)=>`${T.dim(p(d))} ${m}`;r.push(l(t.report.duration,q(e.totalDurationMs,s))),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 n=Math.round(e.accuracy*1e3)/10;return r.push(l(t.report.accuracy,be(n)(`${n}%`))),r.push(l(t.report.wpm,String(e.wpm))),e.newMistakeWords>0&&r.push(l(t.report.newMistakes,T.red(String(e.newMistakeWords)))),r.push(""),r.push(T.dim.italic(t.report.farewell)),r}function rt(e,t,s){if(e.chaptersCompleted===0)return;let r=Se(e,t,s).join(`
|
|
2
|
+
`);console.log(xe(r,{title:T.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-4EJYJITR.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 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 === 'off' && (\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 === 'off' ? 2 : 0} 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[] = [\n t.report.duration,\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 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 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 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.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"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as i,e as g,f as m}from"./chunk-E6BBQALJ.js";import{z as n}from"zod";var d=n.object({mirror:n.enum(["jsdelivr","github"]).default("jsdelivr"),accent:n.enum(["us","uk"]).default("us"),chapterSize:n.number().int().positive().max(200).default(20),sounds:n.object({master:n.boolean().default(!0),keystroke:n.boolean().default(!0),feedback:n.boolean().default(!0),pronunciationRate:n.number().refine(e=>[.5,.75,1,1.25,1.5].includes(e),{message:"pronunciationRate must be 0.5, 0.75, 1, 1.25, or 1.5"}).default(1),pronunciationSource:n.enum(["youdao","dictapi"]).default("youdao")}).default({master:!0,keystroke:!0,feedback:!0,pronunciationRate:1,pronunciationSource:"youdao"}),autoplayPronunciation:n.boolean().default(!0),defaultMode:n.enum(["order","dictation","review","random","loop"]).default("order"),defaultDict:n.string().optional(),language:n.enum(["auto","zh","en"]).default("auto"),stealth:n.enum(["off","menu","default"]).default("off")}),a=null;function k(){return a||(a=d.parse({}),a)}async function j(){let e=await g(i.config);if(!e)return k();let o=d.safeParse(e);if(!o.success)throw new Error(`Invalid config at ${i.config}: ${o.error.message}`);return o.data}async function x(e){await m(i.config,e)}function $(e,o){let s=o.split("."),t=e;for(let r of s){if(t===null||typeof t!="object")return;t=t[r]}return t}function P(e,o,s){let t=o.split(".");if(t.length===0)throw new Error("Empty config key");let r=JSON.parse(JSON.stringify(e)),f=r;for(let u=0;u<t.length-1;u++){let w=t[u],l=f[w];if(typeof l!="object"||l===null)throw new Error(`Cannot set ${o}: ${t.slice(0,u+1).join(".")} is not an object`);f=l}let p=t[t.length-1];f[p]=b(s);let c=d.safeParse(r);if(!c.success)throw new Error(`Invalid value for ${o}: ${c.error.issues[0]?.message??"unknown"}`);return c.data}function b(e){return e==="true"?!0:e==="false"?!1:e==="null"?null:/^-?\d+$/.test(e)||/^-?\d+\.\d+$/.test(e)?Number(e):e}export{j as a,x as b,$ as c,P as d};
|
|
2
|
+
//# sourceMappingURL=chunk-7LTZGB7F.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/infra/config-store.ts"],"sourcesContent":["import { z } from 'zod';\nimport { paths } from './paths.js';\nimport { readJson, writeJsonAtomic } from './fs-store.js';\n\nexport const ConfigSchema = z.object({\n mirror: z.enum(['jsdelivr', 'github']).default('jsdelivr'),\n accent: z.enum(['us', 'uk']).default('us'),\n chapterSize: z.number().int().positive().max(200).default(20),\n sounds: z\n .object({\n master: z.boolean().default(true),\n keystroke: z.boolean().default(true),\n feedback: z.boolean().default(true),\n pronunciationRate: z\n .number()\n .refine((v) => [0.5, 0.75, 1, 1.25, 1.5].includes(v), {\n message: 'pronunciationRate must be 0.5, 0.75, 1, 1.25, or 1.5',\n })\n .default(1),\n pronunciationSource: z.enum(['youdao', 'dictapi']).default('youdao'),\n })\n .default({\n master: true,\n keystroke: true,\n feedback: true,\n pronunciationRate: 1,\n pronunciationSource: 'youdao',\n }),\n autoplayPronunciation: z.boolean().default(true),\n defaultMode: z.enum(['order', 'dictation', 'review', 'random', 'loop']).default('order'),\n defaultDict: z.string().optional(),\n language: z.enum(['auto', 'zh', 'en']).default('auto'),\n stealth: z.enum(['off', 'menu', 'default']).default('off'),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\n// Defaults are computed lazily at first loadConfig() call to keep zod default\n// synthesis (~5-10ms) out of the boot module-graph evaluation.\nlet cachedDefaults: Config | null = null;\nfunction getDefaults(): Config {\n if (cachedDefaults) return cachedDefaults;\n cachedDefaults = ConfigSchema.parse({});\n return cachedDefaults;\n}\n\nexport async function loadConfig(): Promise<Config> {\n const raw = await readJson<unknown>(paths.config);\n if (!raw) return getDefaults();\n const result = ConfigSchema.safeParse(raw);\n if (!result.success) {\n throw new Error(`Invalid config at ${paths.config}: ${result.error.message}`);\n }\n return result.data;\n}\n\nexport async function saveConfig(cfg: Config): Promise<void> {\n await writeJsonAtomic(paths.config, cfg);\n}\n\nexport function getByPath(cfg: Config, path: string): unknown {\n const parts = path.split('.');\n let cur: unknown = cfg;\n for (const p of parts) {\n if (cur === null || typeof cur !== 'object') return undefined;\n cur = (cur as Record<string, unknown>)[p];\n }\n return cur;\n}\n\nexport function setByPath(cfg: Config, path: string, rawValue: string): Config {\n const parts = path.split('.');\n if (parts.length === 0) throw new Error('Empty config key');\n const clone: Record<string, unknown> = JSON.parse(JSON.stringify(cfg));\n let cur: Record<string, unknown> = clone;\n for (let i = 0; i < parts.length - 1; i++) {\n const k = parts[i]!;\n const next = cur[k];\n if (typeof next !== 'object' || next === null) {\n throw new Error(`Cannot set ${path}: ${parts.slice(0, i + 1).join('.')} is not an object`);\n }\n cur = next as Record<string, unknown>;\n }\n const leaf = parts[parts.length - 1]!;\n cur[leaf] = coerce(rawValue);\n const validated = ConfigSchema.safeParse(clone);\n if (!validated.success) {\n throw new Error(`Invalid value for ${path}: ${validated.error.issues[0]?.message ?? 'unknown'}`);\n }\n return validated.data;\n}\n\nfunction coerce(v: string): unknown {\n if (v === 'true') return true;\n if (v === 'false') return false;\n if (v === 'null') return null;\n if (/^-?\\d+$/.test(v)) return Number(v);\n if (/^-?\\d+\\.\\d+$/.test(v)) return Number(v);\n return v;\n}\n"],"mappings":"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","names":["z","ConfigSchema","z","v","cachedDefaults","getDefaults","loadConfig","raw","readJson","paths","result","saveConfig","cfg","writeJsonAtomic","getByPath","path","parts","cur","p","setByPath","rawValue","clone","i","k","next","leaf","coerce","validated"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{b as o}from"./chunk-
|
|
2
|
-
//# sourceMappingURL=chunk-
|
|
1
|
+
import{b as o}from"./chunk-7LTZGB7F.js";import{createContext as s,useCallback as f,useContext as C,useState as c}from"react";import{jsx as g}from"react/jsx-runtime";var n=s(null);function d({initialCfg:t,children:i}){let[p,r]=c(t),a=f(async e=>{r(e),await o(e)},[]);return g(n.Provider,{value:{cfg:p,setCfg:a},children:i})}function l(){let t=C(n);if(!t)throw new Error("useAppState must be used inside AppStateProvider");return t}export{d as a,l as b};
|
|
2
|
+
//# sourceMappingURL=chunk-CQRKGMPU.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as y}from"./chunk-
|
|
2
|
-
//# sourceMappingURL=chunk-
|
|
1
|
+
import{a as y}from"./chunk-7LTZGB7F.js";import{a as w,d as p}from"./chunk-NA5UNUVL.js";import{a as s,b as m,d as f,e as h,f as u}from"./chunk-E6BBQALJ.js";import{createHash as g}from"crypto";import{stat as P,copyFile as S}from"fs/promises";var d=null;async function D(){return d||(d=(await import("undici")).request,d)}var F="https://cdn.jsdelivr.net/gh/RealKai42/qwerty-learner@master",x="https://raw.githubusercontent.com/RealKai42/qwerty-learner/master";async function q(t,r=2){let n=await D(),o;for(let e=0;e<=r;e++)try{let a=await n(t,{headersTimeout:1e4,bodyTimeout:3e4});if(a.statusCode>=400)throw new Error(`HTTP ${a.statusCode}`);return await a.body.text()}catch(a){o=a,e<r&&await new Promise(i=>setTimeout(i,500*(e+1)))}throw o instanceof Error?o:new Error(String(o))}function R(t,r){let o=`/public${r.startsWith("/")?r:`/${r}`}`,e=`${F}${o}`,a=`${x}${o}`;return t==="jsdelivr"?[e,a]:[a,e]}async function v(t){let r=await p(t);if(!r)throw new Error(`Unknown dictionary id: ${t}`);let n=await y();await m();let o=R(n.mirror,r.url),e=null,a;for(let l of o)try{e=await q(l);break}catch(b){a=b}if(!e)throw new Error(`Failed to download dictionary ${t} from any mirror: ${a instanceof Error?a.message:a}`);let i;try{i=JSON.parse(e)}catch(l){throw new Error(`Dictionary ${t} is not valid JSON: ${l.message}`)}let c=w.safeParse(i);if(!c.success)throw new Error(`Dictionary ${t} failed schema validation: ${c.error.issues[0]?.message}`);let E=g("sha256").update(e).digest("hex");await u(s.dictFile(t),c.data);let $={sha256:E,size:e.length,fetchedAt:new Date().toISOString(),id:t};return await u(s.dictMeta(t),$),{words:c.data,size:e.length}}async function I(t,r){if(!/^[a-z0-9-]+$/.test(r))throw new Error(`Invalid id "${r}". Must match /^[a-z0-9-]+$/`);if(await m(),!await f(t))throw new Error(`File not found: ${t}`);let n=await h(t),o=w.safeParse(n);if(!o.success)throw new Error(`Import rejected: ${o.error.issues[0]?.message}`);await S(t,s.dictFile(r));let e=(await P(s.dictFile(r))).size,i={sha256:g("sha256").update(JSON.stringify(o.data)).digest("hex"),size:e,fetchedAt:new Date().toISOString(),id:r};return await u(s.dictMeta(r),i),{words:o.data,size:e}}async function z(t){let r=await h(s.dictFile(t));if(!r)return null;let n=w.safeParse(r);if(!n.success)throw new Error(`Local dictionary ${t} corrupt: ${n.error.message}`);return n.data}async function N(t){return f(s.dictFile(t))}async function O(t){let r=await z(t);if(r)return r;let{words:n}=await v(t);return n}async function L(t){let{unlink:r}=await import("fs/promises"),n=!1;for(let o of[s.dictFile(t),s.dictMeta(t)])try{await r(o),n=!0}catch(e){if(e.code!=="ENOENT")throw e}return n}export{v as a,I as b,z as c,N as d,O as e,L as f};
|
|
2
|
+
//# sourceMappingURL=chunk-DURXS5MX.js.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{createContext as k,useContext as E,useState as v,useCallback as g}from"react";import{jsx as L}from"react/jsx-runtime";var h=k(null);function N({initial:t,children:e}){let[n,r]=v([t]),s=g(i=>{r(o=>[...o,i])},[]),a=g(i=>{r(o=>o.length===0?[i]:[...o.slice(0,-1),i])},[]),u=g(()=>{r(i=>i.length>1?i.slice(0,-1):i)},[]),c=g(i=>{r([i])},[]),l=n[n.length-1];return L(h.Provider,{value:{current:l,stack:n,navigate:s,replace:a,back:u,reset:c},children:e})}function D(){let t=E(h);if(!t)throw new Error("useNav must be used inside NavProvider");return t}import{createContext as x,useContext as S,useMemo as $}from"react";var d={app:{title:"qwerty",subtitle:"typing practice for the terminal"},common:{back:"back",quit:"quit",on:"on",off:"off",cancel:"cancel"},mainMenu:{items:{practiceLabel:"Practice",practiceHintWith:t=>`start ${t}`,practiceHintNone:"pick a dictionary",dictLabel:"Dictionaries",dictHint:"browse, pull, set default",wordLabel:"Word lookup",wordHint:"search local dicts",statsLabel:"Stats",statsHint:"history & trends",configLabel:"Config",configHint:"edit preferences",stealthLabel:"Stealth",stealthHint:"quiet practice mode",quitLabel:"Quit",quitHint:"Esc or Ctrl+C also exits"},hint:"\u2191/\u2193 navigate \xB7 Enter select \xB7 letters jump",helpHint:"? help"},dict:{title:"Dictionaries",loading:"loading dictionaries\u2026",entries:t=>`${t} entries`,filterPlaceholder:"type to filter",local:"local \u2713",notLocal:"not local",defaultMark:"default \u2605",tagsLabel:t=>`tags: ${t}`,wordsLabel:t=>`${t} words`,pulling:t=>`pulling ${t}\u2026`,removing:t=>`removing ${t}\u2026`,errorOn:(t,e)=>`error on ${t}: ${e}`,footer:"\u2191/\u2193 select \xB7 Enter actions \xB7 Ctrl+K more \xB7 Esc back",action:{title:"current dictionary",setDefault:"set as default",practice:"practice now",delete:"delete local"},command:{title:"more actions",pull:"pull selected",import:"import .json",refreshList:"update dictionary list"}},config:{title:"Config",fields:{defaultDict:"default dict",defaultMode:"default mode",accent:"accent",mirror:"dict mirror",chapterSize:"chapter size",autoplayPronunciation:"autoplay pronunciation",soundsMaster:"sounds master",soundsKeystroke:"sounds keystroke",soundsFeedback:"sounds feedback",soundsPronunciationRate:"pronunciation rate",soundsPronunciationSource:"pronunciation source",language:"language",stealth:"stealth mode"},enumValues:{stealth:{off:"off",menu:"show in menu",default:"default practice"},soundsPronunciationSource:{youdao:"Youdao",dictapi:"Wiktionary"},defaultMode:{order:"order",dictation:"dictation",review:"review",random:"random",loop:"loop"}},hints:{editing:"type to edit \xB7 Enter save \xB7 Esc cancel",bool:"space toggle \xB7 \u2191/\u2193 move \xB7 Esc back",enum:"\u2190/\u2192 cycle \xB7 \u2191/\u2193 move \xB7 Esc back",dictRef:"Enter pick dict \xB7 \u2191/\u2193 move \xB7 Esc back",stringOrInt:"Enter edit \xB7 \u2191/\u2193 move \xB7 Esc back"}},stats:{title:"Stats \xB7 overview",loading:"loading stats\u2026",none:"No practice history yet.",nonePractice:"Run a practice session first.",lifetime:"lifetime",sessions:"sessions",words:"words",errors:"errors",wpm:"wpm",accuracy:"accuracy",streak:"streak",last:t=>`last ${t} days (\u2190/\u2192 cycle window)`,cycleWindow:"\u2190/\u2192 cycle window \xB7 Esc back",recent:"recent sessions",topMistakes:"top mistakes",footer:"\u2190/\u2192 cycle window \xB7 Esc back",maxLabel:"max",recentUnits:{words:"w",errors:"err",wpm:"wpm"},multiDictSuffix:t=>` +${t} more`,bars:{speed:"speed",accuracy:"accuracy",sessions:"sessions"}},word:{title:"Word lookup",indexing:"indexing local dictionaries\u2026",none:"No local dictionaries.",pullFirst:"Pull one in Dictionaries first.",countAcross:t=>`${t} words across local dicts`,noMatches:t=>`no matches for "${t}"`,inDict:t=>`in: ${t}`,mistakes:(t,e)=>`mistakes: ${t} (last ${e})`,footer:"type to filter \xB7 \u2191/\u2193 select \xB7 Esc back"},practice:{loading:"loading\u2026",paused:"PAUSED",chapterComplete:"CHAPTER COMPLETE",chapterLabel:(t,e)=>`chapter ${t}/${e}`,reviewLabel:"review",statusBar:{mode:"mode",accent:"accent"},modes:{order:"order",dictation:"dictation",review:"review",random:"random",loop:"loop"},accents:{us:"us",uk:"uk"},statCards:{words:"words",errors:"errors",wpm:"wpm",accuracy:"accuracy",elapsed:t=>`elapsed ${t}`},pause:{title:"PAUSED",chapter:(t,e)=>`chapter ${t}/${e}`,progress:(t,e)=>`${t}/${e}`,hint:"Enter resume \xB7 Esc back to menu"},summary:{loopAgain:"again",nextChapter:"next chapter",reviewMistakes:"review mistakes",backMenu:"back to menu"},footers:{typing:"Ctrl+N skip \xB7 Esc pause \xB7 Tab replay"},errors:{noMistakes:"No mistakes to review yet. Practice some chapters first.",dictEmpty:t=>`Dictionary ${t} is empty.`,unknown:"Unknown error"},imeWarning:"! Non-English input detected \u2014 switch your IME to English.",imeWarningShort:"! IME",audioWarningShort:"! audio"},audio:{noPlayer:"! No audio player found on PATH (looked for afplay/ffplay/mpg123/paplay/aplay/powershell). Sounds disabled."},report:{title:"Session summary",duration:"duration",practiced:"practiced",chapters:"chapters",words:"words",accuracy:"accuracy",wpm:"wpm",newMistakes:"new mistakes",farewell:"see you next time."},help:{title:"Help",subtitle:"all shortcuts",sections:{main:"main menu",practice:"practice",dict:"dictionaries",config:"config",stats:"stats",word:"word lookup",global:"global"},keys:{navigate:"\u2191/\u2193 navigate items",select:"Enter confirm / continue",letterJump:"letter jump to menu item",pause:"Esc pause practice",skip:"Ctrl+N skip current word (neutral)",replay:"Tab replay pronunciation",resume:"Enter resume from pause",backMenu:"Esc back to previous screen",backScreen:"Esc close panel or back",nextChapter:"Enter next chapter",reviewMistakes:"m review mistakes",filter:"type to filter list",itemActions:"Enter open actions panel",moreActions:"Ctrl+K more actions panel",cycleWindow:"\u2190/\u2192 cycle day window",stealthToggle:"Ctrl+I toggle stealth info row",helpScreen:"? open this help screen",quit:"Ctrl+C quit immediately"},footer:"Esc back"},stealth:{paused:"paused",chapterDone:"chapter done",resumeHint:"Enter resume \xB7 Esc menu",nextHint:"Enter next \xB7 Esc menu",pausedHintRight:"Enter resume",nextHintRight:"Enter next",infoChipLabel:"info",infoFmt:(t,e,n,r,s,a)=>`${t} \xB7 ${e} \xB7 ${n}/${r} \xB7 ${s} wpm \xB7 ${a}%`}},p={app:{title:"qwerty",subtitle:"\u7EC8\u7AEF\u952E\u76D8\u7EC3\u4E60"},common:{back:"\u8FD4\u56DE",quit:"\u9000\u51FA",on:"\u5F00",off:"\u5173",cancel:"\u53D6\u6D88"},mainMenu:{items:{practiceLabel:"\u7EC3\u4E60",practiceHintWith:t=>`\u5F00\u59CB ${t}`,practiceHintNone:"\u8BF7\u5148\u9009\u8BCD\u5178",dictLabel:"\u8BCD\u5178",dictHint:"\u6D4F\u89C8\u3001\u4E0B\u8F7D\u3001\u8BBE\u4E3A\u9ED8\u8BA4",wordLabel:"\u67E5\u8BCD",wordHint:"\u5728\u672C\u5730\u8BCD\u5178\u4E2D\u641C\u7D22",statsLabel:"\u7EDF\u8BA1",statsHint:"\u5386\u53F2\u4E0E\u8D8B\u52BF",configLabel:"\u8BBE\u7F6E",configHint:"\u4FEE\u6539\u504F\u597D",stealthLabel:"\u795E\u9690",stealthHint:"\u795E\u9690\u7EC3\u4E60\u6A21\u5F0F",quitLabel:"\u9000\u51FA",quitHint:"Esc \u6216 Ctrl+C \u9000\u51FA"},hint:"\u2191/\u2193 \u79FB\u52A8 \xB7 Enter \u786E\u8BA4 \xB7 \u5B57\u6BCD\u76F4\u8FBE",helpHint:"? \u5E2E\u52A9"},dict:{title:"\u8BCD\u5178",loading:"\u52A0\u8F7D\u8BCD\u5178\u4E2D\u2026",entries:t=>`${t} \u90E8\u8BCD\u5178`,filterPlaceholder:"\u8F93\u5165\u8FC7\u6EE4",local:"\u5DF2\u4E0B\u8F7D \u2713",notLocal:"\u672A\u4E0B\u8F7D",defaultMark:"\u9ED8\u8BA4 \u2605",tagsLabel:t=>`\u6807\u7B7E:${t}`,wordsLabel:t=>`${t} \u8BCD`,pulling:t=>`\u62C9\u53D6 ${t} \u4E2D\u2026`,removing:t=>`\u5220\u9664 ${t} \u4E2D\u2026`,errorOn:(t,e)=>`${t} \u51FA\u9519:${e}`,footer:"\u2191/\u2193 \u9009\u62E9 \xB7 Enter \u64CD\u4F5C \xB7 Ctrl+K \u66F4\u591A \xB7 Esc \u8FD4\u56DE",action:{title:"\u5F53\u524D\u8BCD\u5178",setDefault:"\u8BBE\u4E3A\u9ED8\u8BA4",practice:"\u7ACB\u5373\u7EC3\u4E60",delete:"\u5220\u9664\u672C\u5730"},command:{title:"\u66F4\u591A\u529F\u80FD",pull:"\u62C9\u53D6\u9009\u4E2D",import:"\u5BFC\u5165 .json",refreshList:"\u66F4\u65B0\u8BCD\u5178\u5217\u8868"}},config:{title:"\u8BBE\u7F6E",fields:{defaultDict:"\u9ED8\u8BA4\u8BCD\u5178",defaultMode:"\u9ED8\u8BA4\u6A21\u5F0F",accent:"\u53D1\u97F3",mirror:"\u8BCD\u5178\u955C\u50CF\u6E90",chapterSize:"\u7AE0\u8282\u5355\u8BCD\u6570",autoplayPronunciation:"\u81EA\u52A8\u64AD\u653E\u53D1\u97F3",soundsMaster:"\u97F3\u6548\u603B\u5F00\u5173",soundsKeystroke:"\u6309\u952E\u97F3",soundsFeedback:"\u53CD\u9988\u97F3",soundsPronunciationRate:"\u53D1\u97F3\u901F\u7387",soundsPronunciationSource:"\u53D1\u97F3\u97F3\u6E90",language:"\u8BED\u8A00",stealth:"\u795E\u9690\u6A21\u5F0F"},enumValues:{stealth:{off:"\u5173\u95ED",menu:"\u4E3B\u83DC\u5355\u663E\u793A",default:"\u9ED8\u8BA4\u7EC3\u4E60\u6A21\u5F0F"},soundsPronunciationSource:{youdao:"\u6709\u9053\u8BCD\u5178",dictapi:"\u7EF4\u57FA\u8BCD\u5178"},defaultMode:{order:"\u987A\u5E8F",dictation:"\u9ED8\u5199",review:"\u590D\u4E60",random:"\u4E71\u5E8F",loop:"\u5FAA\u73AF"}},hints:{editing:"\u8F93\u5165\u4FEE\u6539 \xB7 Enter \u4FDD\u5B58 \xB7 Esc \u53D6\u6D88",bool:"\u7A7A\u683C\u5207\u6362 \xB7 \u2191/\u2193 \u79FB\u52A8 \xB7 Esc \u8FD4\u56DE",enum:"\u2190/\u2192 \u5207\u6362 \xB7 \u2191/\u2193 \u79FB\u52A8 \xB7 Esc \u8FD4\u56DE",dictRef:"Enter \u9009\u8BCD\u5178 \xB7 \u2191/\u2193 \u79FB\u52A8 \xB7 Esc \u8FD4\u56DE",stringOrInt:"Enter \u7F16\u8F91 \xB7 \u2191/\u2193 \u79FB\u52A8 \xB7 Esc \u8FD4\u56DE"}},stats:{title:"\u7EDF\u8BA1 \xB7 \u6982\u89C8",loading:"\u52A0\u8F7D\u7EDF\u8BA1\u4E2D\u2026",none:"\u8FD8\u6CA1\u6709\u7EC3\u4E60\u8BB0\u5F55\u3002",nonePractice:"\u5148\u6765\u4E00\u6B21\u7EC3\u4E60\u5427\u3002",lifetime:"\u7D2F\u8BA1",sessions:"\u4F1A\u8BDD",words:"\u8BCD\u6570",errors:"\u9519\u8BEF",wpm:"\u901F\u5EA6",accuracy:"\u51C6\u786E\u7387",streak:"\u8FDE\u7EED\u5929\u6570",last:t=>`\u6700\u8FD1 ${t} \u5929 (\u2190/\u2192 \u5207\u6362\u7A97\u53E3)`,cycleWindow:"\u2190/\u2192 \u5207\u6362\u7A97\u53E3 \xB7 Esc \u8FD4\u56DE",recent:"\u6700\u8FD1\u4F1A\u8BDD",topMistakes:"\u9AD8\u9891\u9519\u8BCD",footer:"\u2190/\u2192 \u5207\u6362\u7A97\u53E3 \xB7 Esc \u8FD4\u56DE",maxLabel:"\u6700\u5927",recentUnits:{words:"\u8BCD",errors:"\u9519",wpm:"\u901F"},multiDictSuffix:t=>` \u7B49 ${t} \u90E8`,bars:{speed:"\u901F\u5EA6",accuracy:"\u51C6\u786E\u7387",sessions:"\u4F1A\u8BDD"}},word:{title:"\u67E5\u8BCD",indexing:"\u7D22\u5F15\u672C\u5730\u8BCD\u5178\u4E2D\u2026",none:"\u6CA1\u6709\u672C\u5730\u8BCD\u5178\u3002",pullFirst:"\u5148\u5728\u300C\u8BCD\u5178\u300D\u4E2D\u62C9\u53D6\u4E00\u90E8\u3002",countAcross:t=>`\u672C\u5730\u8BCD\u5178\u5171 ${t} \u8BCD`,noMatches:t=>`\u6CA1\u6709\u5339\u914D\u300C${t}\u300D\u7684\u8BCD`,inDict:t=>`\u6765\u6E90:${t}`,mistakes:(t,e)=>`\u9519\u8FC7 ${t} \u6B21 (\u6700\u8FD1 ${e})`,footer:"\u8F93\u5165\u8FC7\u6EE4 \xB7 \u2191/\u2193 \u9009\u62E9 \xB7 Esc \u8FD4\u56DE"},practice:{loading:"\u52A0\u8F7D\u4E2D\u2026",paused:"\u5DF2\u6682\u505C",chapterComplete:"\u672C\u7AE0\u5B8C\u6210",chapterLabel:(t,e)=>`\u7B2C ${t}/${e} \u7AE0`,reviewLabel:"\u590D\u4E60",statusBar:{mode:"\u6A21\u5F0F",accent:"\u53D1\u97F3"},modes:{order:"\u987A\u5E8F",dictation:"\u9ED8\u5199",review:"\u590D\u4E60",random:"\u4E71\u5E8F",loop:"\u5FAA\u73AF"},accents:{us:"\u7F8E",uk:"\u82F1"},statCards:{words:"\u8BCD\u6570",errors:"\u9519\u8BEF",wpm:"\u901F\u5EA6",accuracy:"\u51C6\u786E\u7387",elapsed:t=>`\u8017\u65F6 ${t}`},pause:{title:"\u5DF2\u6682\u505C",chapter:(t,e)=>`\u7B2C ${t}/${e} \u7AE0`,progress:(t,e)=>`${t}/${e}`,hint:"Enter \u7EE7\u7EED \xB7 Esc \u8FD4\u56DE\u83DC\u5355"},summary:{loopAgain:"\u518D\u6765\u4E00\u904D",nextChapter:"\u4E0B\u4E00\u7AE0",reviewMistakes:"\u590D\u4E60\u9519\u8BCD",backMenu:"\u8FD4\u56DE\u83DC\u5355"},footers:{typing:"Ctrl+N \u8DF3\u8FC7 \xB7 Esc \u6682\u505C \xB7 Tab \u91CD\u64AD"},errors:{noMistakes:"\u9519\u8BCD\u672C\u662F\u7A7A\u7684\u3002\u5148\u7EC3\u4E60\u51E0\u7AE0\u5427\u3002",dictEmpty:t=>`\u8BCD\u5178 ${t} \u662F\u7A7A\u7684\u3002`,unknown:"\u672A\u77E5\u9519\u8BEF"},imeWarning:"! \u68C0\u6D4B\u5230\u4E2D\u6587/\u65E5\u6587/\u97E9\u6587\u8F93\u5165\uFF0C\u8BF7\u5207\u6362\u5230\u82F1\u6587\u8F93\u5165\u6CD5",imeWarningShort:"! \u4E2D\u6587\u8F93\u5165",audioWarningShort:"! \u65E0\u97F3\u6548"},audio:{noPlayer:"! \u672A\u5728 PATH \u4E2D\u627E\u5230\u97F3\u9891\u64AD\u653E\u5668(\u5C1D\u8BD5 afplay/ffplay/mpg123/paplay/aplay/powershell)\u3002\u97F3\u6548\u5DF2\u7981\u7528\u3002"},report:{title:"\u672C\u6B21\u4F1A\u8BDD",duration:"\u603B\u65F6\u957F",practiced:"\u7EC3\u4E60\u7528\u65F6",chapters:"\u5B8C\u6210\u7AE0\u8282",words:"\u8BCD\u6570",accuracy:"\u51C6\u786E\u7387",wpm:"\u901F\u5EA6",newMistakes:"\u65B0\u9519\u8BCD",farewell:"\u4E0B\u6B21\u89C1\u3002"},help:{title:"\u5E2E\u52A9",subtitle:"\u5168\u90E8\u5FEB\u6377\u952E",sections:{main:"\u4E3B\u83DC\u5355",practice:"\u7EC3\u4E60",dict:"\u8BCD\u5178",config:"\u8BBE\u7F6E",stats:"\u7EDF\u8BA1",word:"\u67E5\u8BCD",global:"\u5168\u5C40"},keys:{navigate:"\u2191/\u2193 \u79FB\u52A8\u9009\u9879",select:"Enter \u786E\u8BA4 / \u7EE7\u7EED",letterJump:"\u5B57\u6BCD\u952E \u76F4\u8FBE\u83DC\u5355\u9879",pause:"Esc \u6682\u505C\u7EC3\u4E60",skip:"Ctrl+N \u8DF3\u8FC7\u5F53\u524D\u8BCD(\u4E0D\u8BA1\u9519)",replay:"Tab \u91CD\u64AD\u53D1\u97F3",resume:"Enter \u7EE7\u7EED\u7EC3\u4E60",backMenu:"Esc \u8FD4\u56DE\u4E0A\u4E00\u5C4F",backScreen:"Esc \u5173\u95ED\u9762\u677F / \u8FD4\u56DE",nextChapter:"Enter \u4E0B\u4E00\u7AE0",reviewMistakes:"m \u590D\u4E60\u9519\u8BCD",filter:"\u8F93\u5165 \u8FC7\u6EE4\u5217\u8868",itemActions:"Enter \u5F39\u51FA\u52A8\u4F5C\u9762\u677F",moreActions:"Ctrl+K \u5F39\u51FA\u66F4\u591A\u529F\u80FD",cycleWindow:"\u2190/\u2192 \u5207\u6362\u65E5\u7A97\u53E3",stealthToggle:"Ctrl+I \u5207\u6362\u795E\u9690\u4FE1\u606F\u884C",helpScreen:"? \u6253\u5F00\u672C\u5E2E\u52A9\u9875",quit:"Ctrl+C \u7ACB\u5373\u9000\u51FA"},footer:"Esc \u8FD4\u56DE"},stealth:{paused:"paused",chapterDone:"chapter done",resumeHint:"Enter resume \xB7 Esc menu",nextHint:"Enter next \xB7 Esc menu",pausedHintRight:"Enter \u7EE7\u7EED",nextHintRight:"Enter \u4E0B\u4E00\u7AE0",infoChipLabel:"\u4FE1\u606F",infoFmt:(t,e,n,r,s,a)=>`${t} \xB7 ${e} \xB7 ${n}/${r} \xB7 ${s} wpm \xB7 ${a}%`}};function b(t){if(!t)return null;let e=t.toLowerCase();return e.startsWith("zh")?"zh":e.startsWith("en")?"en":null}function m(t){if(t==="zh"||t==="en")return t;let e=process.env.LC_ALL||process.env.LC_MESSAGES||process.env.LANG||process.env.LANGUAGE,n=b(e);if(n)return n;try{let r=Intl.DateTimeFormat().resolvedOptions().locale,s=b(r);if(s)return s}catch{}return"en"}import{jsx as C}from"react/jsx-runtime";var w=x(null);function I({pref:t,children:e}){let n=$(()=>{let r=m(t);return{lang:r,t:r==="zh"?p:d}},[t]);return C(w.Provider,{value:n,children:e})}function O(){let t=S(w);if(!t)throw new Error("useStrings must be used inside StringsProvider");return t.t}function K(t){let e=m(t);return{lang:e,t:e==="zh"?p:d}}import{Box as P,Text as M}from"ink";import{jsx as y}from"react/jsx-runtime";var f={accent:"#5eead4",muted:"#6b7280",text:"#e5e7eb",primary:"#7dcfff",success:"#86efac",warning:"#fbbf24",error:"#f87171"};function G({target:t,typed:e,error:n=!1,hideTarget:r=!1}){let s=[...t],a=[...e];return y(P,{paddingY:4,justifyContent:"center",children:s.map((u,c)=>{let l=c<a.length,i=r&&!l?"_":l?a[c]:u,o=n?f.error:l?f.accent:f.muted;return y(M,{bold:!0,color:o,children:i},c)})})}export{N as a,D as b,I as c,O as d,K as e,f,G as g};
|
|
2
|
+
//# sourceMappingURL=chunk-UWTJMVJ6.js.map
|