-
Notifications
You must be signed in to change notification settings - Fork 0
/
ui.js
120 lines (109 loc) · 2.93 KB
/
ui.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
function h(tag, childOrAttrs, ...children) {
const element = document.createElement(tag);
if (childOrAttrs instanceof Node || typeof childOrAttrs === "string") {
element.append(childOrAttrs, ...children);
} else {
Object.assign(element, childOrAttrs);
element.append(...children);
}
return element;
}
const charSpans = {};
function charClassName(state, i) {
return state.finish && i == state.cursor
? ""
: i === state.cursor
? "cursor"
: i < state.cursor
? state.text[i].miss
? "miss"
: "hit"
: "";
}
function renderChar(state, i) {
const charSpan = h(
"span",
{ className: charClassName(state, i) },
i < state.text.length ? state.text[i].target : " ",
);
charSpans[i] = charSpan;
return charSpan;
}
function renderText(state) {
return h(
"span",
...state.text.map((_, i) => renderChar(state, i)),
renderChar(state, state.text.length),
"\n\n",
);
}
let resultsSpan;
function renderResults(state) {
const wpm = Math.floor(
(12000 * state.cursor) / ((state.finish || Date.now()) - state.start),
);
const accuracy = Math.floor((100 * state.hits) / (state.hits + state.misses));
let results;
if (state.finish) {
results = `${wpm} words per minute\n${accuracy}% accuracy\nhit esc to reset`;
} else if (state.cursor > 5) {
const miss = state.text.findIndex((c) => c.miss);
if (
miss >= 0 &&
(state.cursor > miss + 5 || state.cursor == state.text.length)
) {
results = wpm + "\nerrors must be corrected\n ";
} else {
results = wpm + "\n \n ";
}
} else {
results = "type the above text\nerrors must be corrected\nhit esc to reset";
}
resultsSpan = h("span", { className: "results" }, results);
return resultsSpan;
}
function renderWordsLinks(state) {
return h(
"span",
"words: ",
...[40, 50, 70, 100, 140].flatMap((wc) => [
h(
"a",
{
href: `?wordCount=${wc}`,
className: wc === state.wordCount ? "selected" : "",
},
wc,
),
" ",
]),
);
}
function renderSourceLink() {
return h(
"a",
{ href: "https://github.com/callum-oakley/nonsense" },
"source",
);
}
let cursorAtLastRender;
export function render(state, app) {
app.firstChild.replaceWith(
h(
"div",
h("p", renderText(state), renderResults(state)),
h("footer", renderWordsLinks(state), renderSourceLink()),
),
);
cursorAtLastRender = state.cursor;
}
// Optimisation for the common case when we know we're only changing classes
// around the cursor and updating the results.
export function renderIncremental(state) {
const [iMin, iMax] = [state.cursor, cursorAtLastRender].sort((a, b) => a - b);
for (let i = iMin; i <= iMax && i <= state.text.length; i++) {
charSpans[i].className = charClassName(state, i);
}
resultsSpan.replaceWith(renderResults(state));
cursorAtLastRender = state.cursor;
}