I'm working on keyboard testing web application. As a mechanical keyboard enthusiast, I need to test that all the keys on my keyboard work whenever I build one, or change the firmware for one. Eventually, I'd also like to implement some typing tests that are similar to MonkeyType. This isn't a particularly new problem that's unsolved, but I thought it was a good chance to learn some Vue and TypeScript.
So far the codebase is only 3 files (that I've added on top of whatever Vue generates), but I have some questions. Full codebase is on GitHub if you'd like to run it
I'm brand new to Vue and TypeScript, so the things I'd like to know are:
- Am I doing things idiomatically?
- Am I sending events from
KeyboardtoKeyin a way that makes sense?- This is my biggest question. Coming from other OOP languages, it made sense to make
Keyit's own component, but I feel like in Vue, there are going to be communication difficulties betweenKeyboardandKey. It's hard to decide where the state information (such as key position, not implemented yet) should go.
- This is my biggest question. Coming from other OOP languages, it made sense to make
- Is there a better way to structure my components, given that I may also need to add more attributes to
Key(things such as location on a keyboard grid, size of the button, etc)? - When should I be using
setup()vsdata()orcreated()ormounted()etc.- It's quite confusing all the different things I've read only for using
const state = reactive({...})and...toRefs(state)vs individualrefs vs just usingdata()etc.
- It's quite confusing all the different things I've read only for using
- If I'm using TypeScript, should I still be using vanilla Javascript functions like
Object.fromEntries()?
The 3 files of interest are the following:
src/App.vue:
<template>
<h1>{{ state.appName }}</h1>
<Keyboard />
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
import Keyboard from "@/components/Keyboard.vue";
export default defineComponent({
name: 'App',
components: { Keyboard },
setup() {
const state = reactive({
appName: "Type Test"
})
return { state }
}
});
</script>
<style>
/* Used by Equal */
@import url("https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined");
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;900&display=swap");
</style>
src/components/Keyboard.vue:
<template>
<div v-on:keyup="keyPressed" class="keyboard" tabindex="0">
<!-- tabindex is set to make the div focus-able, which is required to respond to keypress events -->
<Key v-for="(data, name) in state.keys" :key="name" :name="name" :pressed="data.pressed"/>
</div>
<it-button @click="resetKeys" type="danger">Reset</it-button>
</template>
<script lang="ts">
import { defineComponent, reactive } from 'vue';
import Key from '@/components/Key.vue';
export default defineComponent({
name: "Keyboard",
components: { Key },
setup() {
const state = reactive({
keys: Object.fromEntries(
[
"q",
"w",
"e",
"r",
"t",
"y",
"u",
"i",
"o",
"p",
"[",
"]",
"\\",
"a",
"s",
"d",
"f",
"g",
"h",
"j",
"k",
"l",
";",
"'",
"Enter",
"Shift",
"z",
"x",
"c",
"v",
"b",
"n",
"m",
",",
".",
"/"
].map(name => [name, { pressed: false }])
)
})
return { state }
},
methods: {
keyPressed(event: KeyboardEvent) {
console.log("Pressed: " + event.key);
console.log("Code: " + event.key);
console.log("Location: " + event.location);
if (this.state.keys[event.key]) {
this.state.keys[event.key]['pressed'] = true;
}
event.preventDefault();
},
resetKeys() {
let keys = Object.keys(this.state.keys)
for (let key of keys) {
this.state.keys[key]['pressed'] = false;
}
}
}
});
</script>
src/components/Key.vue:
<template>
<it-button class="key" :type="pressed ? 'primary' : ''">
{{ name }}
</it-button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: "Key",
props: {
name: {
type: String,
required: true
},
pressed: {
type: Boolean,
default: false
}
}
});
</script>
<style scoped>
.key {
display: inline-block;
margin: 1px;
}
.key.pressed {
background-color: aquamarine;
}
</style>