Skip to content

Commit 7af3c93

Browse files
author
Bot
committed
live wpm and timer
1 parent 5fb21a9 commit 7af3c93

File tree

6 files changed

+125
-30
lines changed

6 files changed

+125
-30
lines changed

package-lock.json

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"cm-show-invisibles": "^3.1.0",
2828
"codemirror": "^5.58.1",
2929
"cors": "^2.8.5",
30+
"countup.js": "^2.0.7",
3031
"debug": "^4.2.0",
3132
"dotenv": "^8.2.0",
3233
"express": "^4.17.1",
@@ -38,6 +39,7 @@
3839
"vue": "^2.6.12",
3940
"vue-chartjs": "^3.5.1",
4041
"vue-codemirror": "^4.0.6",
42+
"vue-countup-v2": "^4.0.0",
4143
"vue-router": "^3.4.5",
4244
"vue-socket.io": "^3.0.10",
4345
"vue-socket.io-extended": "^4.0.4",

src/components/CodeEditor.vue

+33-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
class="codemirror"
1212
:class="{showInvisibles: codeInfo.language.name === 'Whitespace'}"
1313
:options="cmOptions"
14+
@focus="onFocus"
1415
@ready="onCmReady"
1516
@blur="onUnFocus"
1617
/>
@@ -76,6 +77,8 @@ export default {
7677
currentLine: 0,
7778
currentChar: 0,
7879
correctCharsInLine: 0,
80+
freshCorrect: 0,
81+
liveWpmInterval: null,
7982
currentChange: {},
8083
stats: {
8184
history: [],
@@ -122,6 +125,11 @@ export default {
122125
return {};
123126
},
124127
},
128+
beforeDestroy() {
129+
if (this.liveWpmInterval) {
130+
clearInterval(this.liveWpmInterval);
131+
}
132+
},
125133
methods: {
126134
popUp(action, text = this.popUpText) {
127135
this.popUpText = text;
@@ -391,7 +399,9 @@ export default {
391399
}
392400
if (this.started && !this.stats.firstCharTime) {
393401
this.stats.firstCharTime = Date.now();
402+
this.$emit('start');
394403
}
404+
395405
this.currentChange = {
396406
time: this.timeElapsed(),
397407
type: 'initialType',
@@ -478,7 +488,9 @@ export default {
478488
479489
if (this.currentChange.type !== 'initialType') {
480490
this.stats.history.push(this.currentChange);
481-
if (this.options.selectedMode === 2 && this.currentChange.type !== 'correct') {
491+
if (this.currentChange.type === 'correct') {
492+
this.freshCorrect += 1;
493+
} else if (this.options.selectedMode === 2) {
482494
if (this.stats.history.length < 30) {
483495
this.cm.setOption('readOnly', 'nocursor');
484496
this.popUp(true, 'Try again');
@@ -491,11 +503,21 @@ export default {
491503
console.log(JSON.parse(JSON.stringify(this.currentChange)));
492504
}
493505
506+
494507
this.currentChange = {};
495508
},
509+
onFocus() {
510+
console.log('cmFocus');
511+
console.log(this.liveWpmInterval);
512+
if (this.liveWpmInterval === null) {
513+
this.liveWpmInterval = setInterval(this.updateLiveWpm, this.options.liveWpmRefreshRate);
514+
}
515+
},
496516
onUnFocus(_, ev) {
497517
if (ev) {
498518
if (DEV) ev.preventDefault();
519+
clearInterval(this.liveWpmInterval);
520+
this.liveWpmInterval = null;
499521
if (!this.isCompleted && this.popUpText !== 'Try again' && ev) {
500522
if (DEV) this.cm.focus();
501523
if (ev.relatedTarget !== null) {
@@ -535,6 +557,14 @@ export default {
535557
timeElapsed() {
536558
return Date.now() - this.stats.firstCharTime;
537559
},
560+
updateLiveWpm() {
561+
console.log('correct: ', this.freshCorrect);
562+
const currentWpm = this.freshCorrect / (this.options.liveWpmRefreshRate / 1000 / 60) / 5;
563+
console.blue(`wpm: ${currentWpm}`);
564+
this.$emit('liveWpmUpdate', currentWpm || 0);
565+
566+
this.freshCorrect = 0;
567+
},
538568
start(interval) {
539569
clearInterval(interval);
540570
this.cm.markText(
@@ -553,9 +583,6 @@ export default {
553583
this.started = true;
554584
this.cm.focus();
555585
console.log('START');
556-
if (this.options.selectedMode === 1) {
557-
this.$emit('start');
558-
}
559586
this.stats.startTime = Date.now();
560587
},
561588
init() {
@@ -610,6 +637,8 @@ export default {
610637
this.$socket.client.emit('completed');
611638
}
612639
640+
clearInterval(this.liveWpmInterval);
641+
613642
const complete = this.currentLine + 1 >= this.codeInfo.lines;
614643
615644
const endMsgList = ['Too long, uh?', 'Time is over', 'Game over'];

src/store/modules/options.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const state = {
1212
selectedMode: 0,
1313
autoIndent: true,
1414
underScore: false,
15+
liveWpmRefreshRate: 2000,
1516
},
1617
};
1718

src/views/Run.vue

+79-25
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,24 @@
1515
<p>{{ codeSource }}</p>
1616
</div>
1717
</div>
18-
<h2 v-if="timer !== null && $route.path === '/run'" class="timer">
19-
{{ timer }}
20-
</h2>
18+
19+
<div v-show="!completed" class="counters">
20+
<span v-show="!pause && timer !== null">
21+
{{ timer > 120 ? Math.floor(timer / 60) + ' min ' + timer % 60 : timer }} s {{ options.selectedMode === 1 ? 'remaining' : '' }}
22+
</span>
23+
<span v-show="pause">paused</span>
24+
<span v-show="timer === null">waiting</span>
25+
26+
<ICountUp
27+
:key="resetEditorKey"
28+
class="live-wpm"
29+
:end-val="endWpm"
30+
:options="liveWpmOptions"
31+
@ready="onLiveWpmReady"
32+
/>
33+
</div>
34+
35+
2136
<div class="buttons">
2237
<button v-show="!room.connected" class="reset" @click="reset">
2338
Restart
@@ -41,9 +56,10 @@
4156
:key="resetEditorKey"
4257
class="code-editor"
4358
@reset="reset"
44-
@completed="completed"
59+
@completed="onCompleted"
4560
@start="startTimer"
4661
@pause="(action) => {pause = action}"
62+
@liveWpmUpdate="updateLiveWpm"
4763
/>
4864

4965
<div
@@ -62,13 +78,15 @@
6278
<script>
6379
import { mapGetters } from 'vuex';
6480
81+
import ICountUp from 'vue-countup-v2';
6582
import CodeEditor from '@/components/CodeEditor.vue';
6683
6784
const Results = () => import(/* webpackChunkName: "results" */ '@/views/Results.vue');
6885
6986
export default {
7087
name: 'Run',
7188
components: {
89+
ICountUp,
7290
CodeEditor,
7391
Results,
7492
},
@@ -77,13 +95,25 @@ export default {
7795
resetSelfKey: 1,
7896
resetEditorKey: 1,
7997
stats: false,
98+
completed: false,
8099
timer: null,
81100
pause: false,
82101
intervalId: null,
102+
endWpm: 0,
83103
};
84104
},
85105
computed: {
86-
...mapGetters(['codeInfo', 'room']),
106+
...mapGetters(['codeInfo', 'room', 'options']),
107+
liveWpmOptions() {
108+
return {
109+
duration: this.options.liveWpmRefreshRate / 1000,
110+
useEasing: false,
111+
useGrouping: false,
112+
decimalPlaces: 0,
113+
decimal: '.',
114+
suffix: ' WPM',
115+
};
116+
},
87117
codeSource() {
88118
if (this.codeInfo.name) {
89119
return this.codeInfo.source === 'own' ? 'Łukasz Wielgus archive' : this.codeInfo.source;
@@ -129,8 +159,10 @@ export default {
129159
reset() {
130160
this.stats = false;
131161
this.resetEditorKey += 1;
162+
this.liveWpmInstance = null;
132163
if (this.intervalId) {
133164
window.clearInterval(this.intervalId);
165+
this.timer = null;
134166
}
135167
136168
if (this.$route.path === '/results') {
@@ -144,20 +176,39 @@ export default {
144176
}
145177
},
146178
startTimer() {
147-
this.timer = 100;
148-
this.intervalId = window.setInterval(() => {
149-
if (this.timer === 0) {
150-
this.$refs.codeEditor.completed();
151-
} else if (!this.pause) {
152-
this.timer -= 1;
179+
console.log('timer start');
180+
this.completed = false; // after reset or try again
181+
this.timer = this.options.selectedMode === 1 ? 100 : 0;
182+
this.intervalId = setInterval(() => {
183+
if (!this.pause) {
184+
if (this.options.selectedMode === 1) {
185+
if (this.timer === 0) {
186+
this.$refs.codeEditor.completed();
187+
} else {
188+
this.timer -= 1;
189+
}
190+
} else {
191+
this.timer += 1;
192+
}
153193
}
154194
}, 1000);
155195
},
196+
onLiveWpmReady(instance) {
197+
if (!this.liveWpmInstance) {
198+
console.warn('LIVEWPM READY');
199+
this.liveWpmInstance = instance;
200+
}
201+
},
202+
updateLiveWpm(wpm) {
203+
console.log(`updating to ${wpm}`);
204+
this.liveWpmInstance.update(wpm);
205+
},
156206
finish() {
157207
this.$refs.codeEditor.completed();
158208
},
159-
completed(stats) {
209+
onCompleted(stats) {
160210
this.stats = stats;
211+
this.completed = true;
161212
if (this.intervalId) {
162213
window.clearInterval(this.intervalId);
163214
}
@@ -186,7 +237,7 @@ main
186237
max-width: none
187238
188239
.code-editor
189-
flex-grow: 1
240+
flex-grow: 1 //dev
190241
margin: 1rem 0
191242
192243
.top-bar
@@ -196,10 +247,10 @@ main
196247
animation: opacity-enter .5s ease-out forwards .7s
197248
animation-fill-mode: both
198249
position: relative
199-
200250
.info
201251
display: flex
202252
align-items: center
253+
flex-grow: 1
203254
flex-shrink: 2
204255
position: relative
205256
min-width: 0
@@ -213,25 +264,28 @@ main
213264
overflow: hidden
214265
text-overflow: ellipsis
215266
216-
.codeInfo
217-
display: flex
218-
justify-content: space-between
219-
flex-direction: column
220-
flex-shrink: 2
267+
.codeInfo, .counters
268+
display: flex
269+
justify-content: space-between
270+
flex-direction: column
271+
flex-shrink: 2
272+
min-width: 0
273+
274+
p, span
275+
margin-bottom: $thin-gap
276+
overflow: hidden
221277
min-width: 0
278+
text-overflow: ellipsis
222279
223-
p
224-
margin-bottom: $thin-gap
225-
overflow: hidden
226-
min-width: 0
227-
text-overflow: ellipsis
280+
.counters
281+
text-align: right
228282
229283
.buttons
230284
flex-shrink: 0
231285
button
232286
background: $navy-grey
233287
padding: 0 0.5em
234-
margin-left: max(10px, calc(20vw - 210px))
288+
margin-left: min(5em, max(1em, calc(20vw - 210px)))
235289
text-align: center
236290
height: 47px
237291
min-width: 150px

vue.config.js

-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ module.exports = {
5252
// config.plugins.delete('prefetch');
5353
},
5454
devServer: {
55-
progress: true,
5655
clientLogLevel: 'info',
5756
headers: {
5857
'Access-Control-Allow-Origin': '*',

0 commit comments

Comments
 (0)