summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Van Doorn <vandoorn.nick@gmail.com>2020-03-12 01:11:19 -0700
committerNick Van Doorn <vandoorn.nick@gmail.com>2020-03-25 10:58:49 -0700
commit3581940fb0cade9ef2782093998e4f0196336396 (patch)
tree8672ef72d9e645f1ddea3a73415ce24801c7a5c1
Init commit
-rw-r--r--exercises.js14
-rw-r--r--index.html25
-rw-r--r--lib.js31
-rw-r--r--main.js60
-rw-r--r--netlify.toml6
-rw-r--r--readme.md9
-rw-r--r--style.css52
-rw-r--r--templates.js26
8 files changed, 223 insertions, 0 deletions
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 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>smoltype</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+
+ <link href="style.css" rel="stylesheet">
+</head>
+<body>
+ <main class="container">
+ <div id="root"></div>
+ <form>
+ <input type="text" id="main-input" />
+ </form>
+ <form style="display: none">
+ <input type="file" id="file-input" />
+ </form>
+ </main>
+ <script src="exercises.js"></script>
+ <script src="lib.js"></script>
+ <script src="templates.js"></script>
+ <script src="main.js"></script>
+</body>
+</html>
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 `
+ <div class="dashboard">
+ <div class="dashboard_prev-word">${prevWord || ''}</div>
+ <div class="dashboard_current-word">${children}</div>
+ <div class="dashboard_next-word">${nextWord || ''}</div>
+ </div>
+ `
+ },
+ currentWord({matchedSegment, rest}) {
+ return `
+ <span class="current-word_matched-segment">${matchedSegment}</span>
+ <span class="current-word_rest">${rest}</span>
+ `
+ },
+ sidebar({collection, currentSlug}) {
+ return `
+ <ul class="sidebar">
+ ${collection.map(([label, value, slug]) =>
+ `<li class="sidebar ${slug === currentSlug ? "sidebar_active": ""}"><a href="${value}">${label}</a></li>`
+ ).join("")}
+ </ul>
+ `
+ }
+}