From 3581940fb0cade9ef2782093998e4f0196336396 Mon Sep 17 00:00:00 2001 From: Nick Van Doorn Date: Thu, 12 Mar 2020 01:11:19 -0700 Subject: Init commit --- exercises.js | 14 ++++++++++++++ index.html | 25 +++++++++++++++++++++++++ lib.js | 31 +++++++++++++++++++++++++++++++ main.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netlify.toml | 6 ++++++ readme.md | 9 +++++++++ style.css | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ templates.js | 26 ++++++++++++++++++++++++++ 8 files changed, 223 insertions(+) create mode 100644 exercises.js create mode 100644 index.html create mode 100644 lib.js create mode 100644 main.js create mode 100644 netlify.toml create mode 100644 readme.md create mode 100644 style.css create mode 100644 templates.js diff --git a/exercises.js b/exercises.js new file mode 100644 index 0000000..ad5df88 --- /dev/null +++ b/exercises.js @@ -0,0 +1,14 @@ +// This object may become quite large, so there may be a tangible benefit to +// parsing a JSON string. +const EXERCISES = JSON.parse(` + { + "doggo-ipsum": { + "name": "Doggo Ipsum", + "content": "Doggo ipsum much ruin diet sub woofer big ol long doggo, doggo puggorino clouds, length boy much ruin diet. shibe shooberino. Doge you are doin me a concern tungg heckin good boys and girls many pats smol, ur givin me a spook porgo borking doggo shoob, noodle horse dat tungg tho sub woofer big ol pupper. Vvv shooberino borkdrive, such treat. Dat tungg tho heckin good boys extremely cuuuuuute shoob puggo, shibe most angery pupper I have ever seen. Noodle horse fluffer noodle horse stop it fren shoob puggorino, woofer length boy floofs heckin. Shibe most angery pupper I have ever seen doggorino pupperino, heck. you are doing me a frighten. Noodle horse very hand that feed shibe very jealous pupper puggorino, blop. Porgo heckin angery woofer doge fat boi long doggo borkdrive, bork borkf much ruin diet. Borking doggo doggorino shoober thicc borkdrive boof much ruin diet, tungg h*ck pupperino many pats floofs. Heckin good boys borkf smol borkf super chub, sub woofer adorable doggo. tungg blop pats. Adorable doggo long water shoob corgo dat tungg tho stop it fren lotsa pats shooberino, big ol heckin good boys and girls heck fat boi most angery pupper I have ever seen. Borkf boof what a nice floof blop wrinkler borking doggo dat tungg tho, borkf boof borkdrive super chub. Mlem aqua doggo very taste wow blep, lotsa pats. Pats heck wrinkler very jealous pupper you are doin me a concern most angery pupper I have ever seen vvv wrinkler noodle horse, lotsa pats boof shoober the neighborhood pupper wow very biscit boofers. Boofers sub woofer thicc I am bekom fat thicc length boy doggorino, wow very biscit wow such tempt super chub porgo. Adorable doggo super chub doing me a frighten super chub very good spot doggorino boof wrinkler, the neighborhood pupper very good spot pats doggo big ol pupper. Heckin angery woofer you are doing me the shock doge, long woofer." + }, + "samuel-l-ipsum": { + "name": "Samuel L Ipsum", + "content": "Now that we know who you are, I know who I am. I'm not a mistake! It all makes sense! In a comic, you know how you can tell who the arch-villain's going to be? He's the exact opposite of the hero. And most times they're friends, like you and me! I should've known way back when... You know why, David? Because of the kids. They called me Mr Glass. Well, the way they make shows is, they make one show. That show's called a pilot. Then they show that show to the people who make shows, and on the strength of that one show they decide if they're going to make more shows. Some pilots get picked and become television programs. Some don't, become nothing. She starred in one of the ones that became nothing. Now that we know who you are, I know who I am. I'm not a mistake! It all makes sense! In a comic, you know how you can tell who the arch-villain's going to be? He's the exact opposite of the hero. And most times they're friends, like you and me! I should've known way back when... You know why, David? Because of the kids. They called me Mr Glass. Do you see any Teletubbies in here? Do you see a slender plastic tag clipped to my shirt with my name printed on it? Do you see a little Asian child with a blank expression on his face sitting outside on a mechanical helicopter that shakes when you put quarters in it? No? Well, that's what you see at a toy store. And you must think you're in a toy store, because you're here shopping for an infant named Jeb." + } + } +`) diff --git a/index.html b/index.html new file mode 100644 index 0000000..01a18cb --- /dev/null +++ b/index.html @@ -0,0 +1,25 @@ + + + + + smoltype + + + + + +
+
+
+ +
+
+ +
+
+ + + + + + diff --git a/lib.js b/lib.js new file mode 100644 index 0000000..b529085 --- /dev/null +++ b/lib.js @@ -0,0 +1,31 @@ +const Lib = { + makeStateContainer(intialState, renderCallback) { + let currentState = intialState + return { + set(newState, afterRenderCallback = () => {}) { + currentState = { ...currentState, ...newState } + renderCallback() + afterRenderCallback() + }, + get() { return currentState } + } + }, + splitAt(str, index) { + return [str.slice(0, index), str.slice(index)] + }, + readFile(file) { + return new Promise(function(resolve, reject) { + const reader = new FileReader() + reader.onload = function(event) { + resolve(event.target.result) + } + reader.readAsText(file) + }) + }, + getQueryParam(paramName) { + const queryPairs = window.location.search.slice(1).split("&").map(k => k.split("=")) + const foundPair = queryPairs.find(([name]) => name === paramName) + if(!foundPair) return null; + return foundPair[1] + } +} diff --git a/main.js b/main.js new file mode 100644 index 0000000..b8ece73 --- /dev/null +++ b/main.js @@ -0,0 +1,60 @@ +(function () { + const DEFAULT_EXERCISE_SLUG = 'doggo-ipsum' + const mainInput = document.getElementById("main-input") + const root = document.getElementById("root") + const fileInput = document.getElementById("file-input") + mainInput.addEventListener("keyup", mainInputChangeHandler) + fileInput.addEventListener("change", fileChangeHandler) + + const exerciseSlug = Lib.getQueryParam("exercise") || DEFAULT_EXERCISE_SLUG + const wordList = EXERCISES[exerciseSlug].content.split(" ") + let state = Lib.makeStateContainer({ currentWordIndex: 0, currentMatchIndex: 0, wordList}, render) + + function render() { + const { currentWordIndex, currentMatchIndex, wordList } = state.get() + const currentWord = wordList[currentWordIndex] + + const [matchedSegment, rest] = Lib.splitAt(currentWord, currentMatchIndex) + const currentWordTemplate = Templates.currentWord({matchedSegment, rest}) + + const sidebarCollection = Object.entries(EXERCISES).map(([slug, exercise]) => [exercise.name, `/?exercise=${slug}`, slug]) + const sidebarTemplate = Templates.sidebar({collection: sidebarCollection, currentSlug: exerciseSlug }) + + const nextWord = wordList[currentWordIndex + 1] + const prevWord = wordList[currentWordIndex - 1] + const dashboardTemplate = Templates.dashboard({prevWord, nextWord, children: currentWordTemplate}) + root.innerHTML = sidebarTemplate + dashboardTemplate + } + + function currentMatchPosition(currentWord) { + // TODO this is not a very fast or clever way to do this, but its ok to + // read and works for now. At the very least this should be factored out. + let i + for(i = 0; i < currentWord.length; i++) { + if(currentWord[i] !== mainInput.value[i]) { + break + } + } + return i + } + + function fileChangeHandler(event) { + Lib.readFile(event.target.files[0]).then(function(text) { + state.set({ wordList: text.split(" "), currentWordIndex: 0, currentMatchIndex: 0 }) + }) + } + + function mainInputChangeHandler(event) { + const { currentWordIndex, wordList } = state.get() + const currentWord = wordList[currentWordIndex] + if(mainInput.value.includes(currentWord)) { + mainInput.value = "" + state.set({ currentWordIndex: currentWordIndex + 1, currentMatchIndex: 0 }) + } + else { + state.set({ currentMatchIndex: currentMatchPosition(currentWord) }) + } + } + + render() +})() diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..11f95d0 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,6 @@ +[Settings] + ID = "a902f49d-e3eb-4b57-a271-6588fc41da52" + +[Build] + Publish = "" + Functions = "" diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..de5533e --- /dev/null +++ b/readme.md @@ -0,0 +1,9 @@ +# smoltype + +[Try it!](https://thirsty-brown-af67d5.netlify.com/) + +I recently got an ortho linear split keyboard, so my typing is still 6.5/10 at best. Other typing tests exist, but this one is mine, and most importantly it's smol. + +## Contributing + +Don't like the CSS? Me neither, please help. diff --git a/style.css b/style.css new file mode 100644 index 0000000..9346162 --- /dev/null +++ b/style.css @@ -0,0 +1,52 @@ +body { + background: #262A32; + color: #ffffff; +} + +a { + color: #ffffff; +} + +.current-word_matched-segment { + color: green; +} + +.container { + width: 80%; + margin: 0 auto; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} + +.dashboard { + padding: 100px; + display: flex; +} + +.dashboard_current-word { + font-size: 3em; +} + +.dashboard_prev-word { + color: #575757; +} + +.dashboard_next-word { + color: #7a7a7a; +} + +.dashboard > * { + margin: 20px; + width: 300px; +} + +#root { + display: flex; +} + +.sidebar_active a { + color: teal; +} diff --git a/templates.js b/templates.js new file mode 100644 index 0000000..a0a5099 --- /dev/null +++ b/templates.js @@ -0,0 +1,26 @@ +const Templates = { + dashboard({prevWord, nextWord, children}) { + return ` +
+
${prevWord || ''}
+
${children}
+
${nextWord || ''}
+
+ ` + }, + currentWord({matchedSegment, rest}) { + return ` + ${matchedSegment} + ${rest} + ` + }, + sidebar({collection, currentSlug}) { + return ` + + ` + } +} -- cgit v1.2.3