sleeb

an experimental input method
git clone https://tongong.net/git/sleeb.git
Log | Files | Refs | README

commit 8f215d9f24129f4786e22004d19cf082e342fdd3
parent fcb438ef47ed5540024277756369aef69283d127
Author: tongong <tongong@gmx.net>
Date:   Sat, 15 Jan 2022 11:43:23 +0100

added trainer mode

Diffstat:
Msrc/components/key-boxes.js | 20++++++++++++--------
Msrc/components/page-input.js | 12+++++-------
Msrc/components/page-trainer.js | 50+++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/modules/input.js | 2++
Asrc/modules/training.js | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/styles.css | 43+++++++++++++++++++++++++++++++++++++++++++
6 files changed, 220 insertions(+), 18 deletions(-)

diff --git a/src/components/key-boxes.js b/src/components/key-boxes.js @@ -1,11 +1,12 @@ const m = require("mithril"); const input = require("../modules/input.js"); -/* inputCallback: - * expects one argument: char to input +/* inputCallback: expects one argument: char to input + * keyCallback: can be used for event handling of other pressed keys */ module.exports = (vnode) => { - let cb = vnode.attrs.inputCallback; + let icb = vnode.attrs.inputCallback; + let kcb = vnode.attrs.keyCallback; let active = [false, false, false]; // contains indizes of pressed key in the order in which they were pressed let pressed = []; @@ -15,10 +16,13 @@ module.exports = (vnode) => { oncreate: () => { document.onkeydown = (e) => { let index = input.keys.indexOf(e.key); - if (!e.repeat && index != -1) { - active[index] = true; - pressed.push(index); - m.redraw(); + if (!e.repeat) { + if (kcb) kcb(e); + if (index != -1) { + active[index] = true; + pressed.push(index); + m.redraw(); + } } } document.onkeyup = (e) => { @@ -30,7 +34,7 @@ module.exports = (vnode) => { pressed = []; let charMaybe = input.gestures2char(gestures); if (charMaybe) { - cb(charMaybe); + icb(charMaybe); gestures = []; } } diff --git a/src/components/page-input.js b/src/components/page-input.js @@ -2,16 +2,14 @@ const m = require("mithril"); const keyBoxes = require("./key-boxes.js"); module.exports = () => { - let written = ""; - let ongesture = (g) => { - written += g; + let written = "> "; + let oninput = (c) => { + written += c; } return { view: () => m("div", - m("div", {style: "height:40%;width:100%"}, written), - m("div", {style: "height:60%;width:100%"}, - m(keyBoxes, {inputCallback: ongesture}) - ) + m("div.inputTop", written), + m("div.inputBottom", m(keyBoxes, {inputCallback: oninput})) ) } }; diff --git a/src/components/page-trainer.js b/src/components/page-trainer.js @@ -1,9 +1,53 @@ const m = require("mithril"); +const keyBoxes = require("./key-boxes.js"); +const training = require("../modules/training.js"); +const input = require("../modules/input.js"); + +function fixSpace(c) { + if (c == " ") return "_"; + return c; +} module.exports = () => { - return { - view: () => { - return m("p", "will be added later"); + let test; + let showHint = false; + let hintWasShown = false; + let wrongSubmit = false; + let hinthandler = (e) => { + if (e.key == input.hintkey) showHint = !showHint; + if (showHint) hintWasShown = true; + m.redraw(); + } + let newTest = () => { + test = training.getTest(); + showHint = false; + hintWasShown = false; + wrongSubmit = false; + } + let oninput = (c) => { + if (test[0] == c) { + training.saveTest(test[0], !hintWasShown && !wrongSubmit); + newTest(); } + else wrongSubmit = true; + } + return { + oninit: () => { + newTest(); + }, + view: () => m("div", + m(".trainerTop", + m(".bigletter", { + class: wrongSubmit ? "red" : "" + }, m("span", fixSpace(test[0]))), + m(".hint", { + class: showHint ? "" : "invisible" + }, "Hint: " + test[1]) + ), + m(".trainerBottom", m(keyBoxes, { + inputCallback: oninput, + keyCallback: hinthandler + })) + ) } }; diff --git a/src/modules/input.js b/src/modules/input.js @@ -1,5 +1,6 @@ // maybe add a configuration option for this later const keys = ["j", "k", "l"]; +const hintkey = "h"; /* pressed is an array of pressed indizes from one gesture */ function pressed2gesture(pressed) { @@ -44,6 +45,7 @@ function gestures2char(gestures) { module.exports = { keys, + hintkey, pressed2gesture, gestures2char } diff --git a/src/modules/training.js b/src/modules/training.js @@ -0,0 +1,111 @@ +function unixtime() { + return (new Date).getTime(); +} + +function getData() { + let ls = localStorage.getItem("progress"); + if (ls) { + return JSON.parse(ls); + } + + /* sorted by frequency */ + let chars = " -.etaonihsrlducmwyfgpbvkjxqz0123456789"; + let hints = { + " ": "G7", + "-": "G8", + ".": "G9" + }; + for (let i = 0; i < 36; i++) { + let hint = "G" + Math.floor(i / 6 + 1) + " G" + (i % 6 + 1); + let ch = i < 26 ? + String.fromCharCode("a".charCodeAt() + i) : + (i - 26) + ""; + hints[ch] = hint; + } + let data = chars.split("").map(c => ({ + c: c, + hint: hints[c], + numt: 0, /* how often this character was tested */ + score: 0, + lastt: 0, /* timestamp of the last test */ + })); + return data; +} + +function setData(data) { + localStorage.setItem("progress", JSON.stringify(data)); +} + + +function getMaxIndex(valueFn, list) { + let max; + let maxValue = 0; + for (let i = 0; i < list.length; i++) { + let v = valueFn(list[i]); + if (v != 0 && (!max || v > maxValue)) { + max = i; + maxValue = v; + } + } + return max; +} + +/* basic idea: + * - remove previously tested character from the list of possible ones (so + * that no character is tested twice) + * - if there are chars older than a week, test the oldest + * - if there are chars with a score less than 20 test the one with minimal + * score + * - else test a new char + * - if all chars are known take the char with minimal score + * + * score: + * - starts at 0 + * - test correct? +5 + * - test incorrect? /2 + * - showing hints counts as incorrect (maybe also set a time limit?) + * + * return value consists of the character and a hint + */ +function getTest() { + let data = getData(); + + let prev = getMaxIndex(e => e.lastt, data); + if (prev) { + data.splice(prev, 1); + } + + let old = getMaxIndex(e => -e.lastt, data); + if (old && data[old].lastt < unixtime() - 1000 * 60 * 60 * 24 * 7) { + return [data[old].c, data[old].hint]; + } + + let known = data.filter(e => e.numt > 0).sort( + (a, b) => (a.score - b.score) + ); + let unknown = data.filter(e => e.numt == 0); + + // console.log(known); + // console.log(unknown); + + if ((known.length != 0 && known[0].score < 20) || unknown.length == 0) { + return [known[0].c, known[0].hint]; + } + return [unknown[0].c, unknown[0].hint]; +} + +function saveTest(c, success) { + // console.log(c, success); + let data = getData(); + let elem = data.find(e => e.c == c); + elem.numt++; + elem.lastt = unixtime(); + if (success) elem.score += 5; + else elem.score = Math.floor(elem.score / 2); + setData(data); +} + +module.exports = { + getTest, + saveTest +} diff --git a/src/styles.css b/src/styles.css @@ -27,6 +27,9 @@ h1 { text-align: center; line-height: 1.1em; } +.invisible { + display: none !important; +} /* LAYOUT */ @@ -85,3 +88,43 @@ h1 { top: 0; right: 0; } + +/* input page */ +.inputTop { + white-space: pre; + width: 100%; + height: 40%; +} +.inputBottom { + width: 100%; + height: 60%; +} + +/* trainer page */ +.trainerTop { + position: relative; + width: 100%; + height: 40%; +} +.trainerBottom { + width: 100%; + height: 60%; +} +.bigletter { + width: 100%; + height: 100%; + font-size: 200px; + display: flex; + justify-content: center; + align-items: center; +} +.hint { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + text-align: center; +} +.red { + color: red; +}