2020-05-09 08:29:47 +08:00
let wordsList = [ ] ;
let currentWordIndex = 0 ;
let inputHistory = [ ] ;
let currentInput = "" ;
2020-05-10 01:39:23 +08:00
let time = 0 ;
2020-05-15 06:19:28 +08:00
let timers = [ ] ;
2020-05-09 08:29:47 +08:00
let testActive = false ;
let testStart , testEnd ;
2020-05-10 01:39:23 +08:00
let wpmHistory = [ ] ;
2020-05-31 21:31:50 +08:00
let rawHistory = [ ] ;
2020-05-17 20:17:08 +08:00
let restartCount = 0 ;
2020-06-09 01:12:34 +08:00
let incompleteTestSeconds = 0 ;
2020-05-22 23:19:09 +08:00
let currentTestLine = 0 ;
2020-05-23 00:59:28 +08:00
let pageTransition = false ;
2020-05-23 23:29:36 +08:00
let keypressPerSecond = [ ] ;
let currentKeypressCount = 0 ;
let afkDetected = false ;
2020-05-24 06:52:49 +08:00
let errorsPerSecond = [ ] ;
let currentErrorCount = 0 ;
2020-05-27 08:00:28 +08:00
let resultVisible = false ;
2020-06-01 04:03:54 +08:00
let activeWordTopBeforeJump = 0 ;
let activeWordTop = 0 ;
let activeWordJumped = false ;
2020-06-06 09:05:21 +08:00
let sameWordset = false ;
2020-05-09 08:29:47 +08:00
2020-05-11 07:07:36 +08:00
let accuracyStats = {
correct : 0 ,
incorrect : 0
}
2020-07-01 05:23:50 +08:00
let customText = "The quick brown fox jumps over the lazy dog" . split ( ' ' ) ;
2020-05-09 08:29:47 +08:00
2020-06-08 06:43:01 +08:00
const testCompleted = firebase . functions ( ) . httpsCallable ( 'testCompleted' ) ;
2020-06-12 02:19:39 +08:00
const addTag = firebase . functions ( ) . httpsCallable ( 'addTag' ) ;
2020-06-12 02:38:24 +08:00
const editTag = firebase . functions ( ) . httpsCallable ( 'editTag' ) ;
const removeTag = firebase . functions ( ) . httpsCallable ( 'removeTag' ) ;
2020-06-24 07:55:15 +08:00
const updateResultTags = firebase . functions ( ) . httpsCallable ( 'updateResultTags' ) ;
2020-06-28 06:45:24 +08:00
const saveConfig = firebase . functions ( ) . httpsCallable ( 'saveConfig' ) ;
2020-06-08 06:43:01 +08:00
2020-06-25 08:30:58 +08:00
function smooth ( arr , windowSize , getter = ( value ) => value , setter ) {
const get = getter
const result = [ ]
for ( let i = 0 ; i < arr . length ; i += 1 ) {
const leftOffeset = i - windowSize
const from = leftOffeset >= 0 ? leftOffeset : 0
const to = i + windowSize + 1
let count = 0
let sum = 0
for ( let j = from ; j < to && j < arr . length ; j += 1 ) {
sum += get ( arr [ j ] )
count += 1
}
result [ i ] = setter ? setter ( arr [ i ] , sum / count ) : sum / count
}
return result
}
2020-05-12 07:59:12 +08:00
function showNotification ( text , time ) {
let noti = $ ( ".notification" ) ;
noti . text ( text ) ;
noti . css ( 'top' , ` - ${ noti . outerHeight ( ) } px ` ) ;
2020-05-15 11:09:00 +08:00
noti . stop ( true , true ) . animate ( {
2020-05-12 07:59:12 +08:00
top : "1rem"
} , 250 , 'swing' , ( ) => {
2020-05-15 11:09:00 +08:00
noti . stop ( true , true ) . animate ( {
opacity : 1
} , time , ( ) => {
noti . stop ( true , true ) . animate ( {
top : ` - ${ noti . outerHeight ( ) } px `
} , 250 , 'swing' ) ;
} )
2020-05-12 07:59:12 +08:00
} ) ;
}
2020-06-05 07:00:45 +08:00
function copyResultToClipboard ( ) {
2020-06-15 08:45:05 +08:00
if ( navigator . userAgent . toLowerCase ( ) . indexOf ( 'firefox' ) > - 1 ) {
showNotification ( 'Sorry, this feature is not supported in Firefox' , 4000 ) ;
} else {
let src = $ ( '#middle' ) ;
var sourceX = src . position ( ) . left ; /*X position from div#target*/
var sourceY = src . position ( ) . top ; /*Y position from div#target*/
var sourceWidth = src . width ( ) ; /*clientWidth/offsetWidth from div#target*/
var sourceHeight = src . height ( ) ; /*clientHeight/offsetHeight from div#target*/
2020-06-05 07:00:45 +08:00
2020-06-15 08:45:05 +08:00
let bgColor = getComputedStyle ( document . body ) . getPropertyValue ( '--bg-color' ) . replace ( ' ' , '' ) ;
2020-06-15 08:28:52 +08:00
try {
2020-06-15 08:45:05 +08:00
html2canvas ( document . body , {
backgroundColor : bgColor ,
height : sourceHeight + 50 ,
width : sourceWidth + 50 ,
x : sourceX - 25 ,
y : sourceY - 25
} ) . then ( function ( canvas ) {
// document.body.appendChild(canvas);
canvas . toBlob ( function ( blob ) {
navigator . clipboard . write ( [
new ClipboardItem (
Object . defineProperty ( { } , blob . type , {
value : blob ,
enumerable : true
} )
)
] ) . then ( f => {
showNotification ( 'Copied to clipboard' , 1000 ) ;
} ) . catch ( f => {
showNotification ( 'Error saving image to clipboard' , 2000 ) ;
} )
} ) ;
2020-06-15 08:28:52 +08:00
} ) ;
} catch ( e ) {
showNotification ( 'Error creating image' , 2000 ) ;
}
2020-06-15 08:45:05 +08:00
}
2020-05-09 08:29:47 +08:00
}
2020-06-15 08:45:05 +08:00
2020-05-17 19:40:41 +08:00
function getReleasesFromGitHub ( ) {
$ . getJSON ( "https://api.github.com/repos/Miodec/monkey-type/releases" , data => {
$ ( '#bottom .version' ) . text ( data [ 0 ] . name ) . css ( 'opacity' , 1 ) ;
$ ( "#versionHistory .releases" ) . empty ( ) ;
data . forEach ( release => {
if ( ! release . draft && ! release . prerelease ) {
$ ( "#versionHistory .releases" ) . append ( `
< div class = "release" >
< div class = "title" > $ { release . name } < / d i v >
< div class = "date" > $ { moment ( release . published _at ) . format ( 'DD MMM YYYY' ) } < / d i v >
< div class = "body" > $ { release . body . replace ( /\r\n/g , '<br>' ) } < / d i v >
< / d i v >
` );
}
} )
} )
}
2020-06-06 23:48:22 +08:00
function verifyUsername ( ) {
// test = firebase.functions().httpsCallable('moveResults')
// test2 = firebase.functions().httpsCallable('getNames')
// test3 = firebase.functions().httpsCallable('checkNameAvailability')
const check = firebase . functions ( ) . httpsCallable ( 'checkIfNeedsToChangeName' )
check ( { uid : firebase . auth ( ) . currentUser . uid } ) . then ( data => {
if ( data . data === 1 ) {
$ ( '.nameChangeMessage' ) . slideDown ( ) ;
} else if ( data . data === 2 ) {
$ ( '.nameChangeMessage' ) . slideDown ( ) ;
}
} )
$ ( '.nameChangeMessage' ) . click ( e => {
alert ( ` Im currently preparing the system to be ready for leaderboards and other awesome features - it looks like you need to change your display name.
It either contains special characters , or your display name is the same as someone elses and your account was made later .
Sorry for this inconvenience .
` );
let newName = prompt ( 'Please provide a new username - you can use lowercase and uppercase characters, numbers and one of these special characters ( . _ - ). The new name cannot be longer than 12 characters.' , firebase . auth ( ) . currentUser . displayName ) ;
2020-06-13 03:14:26 +08:00
if ( newName ) {
cn = firebase . functions ( ) . httpsCallable ( 'changeName' ) ;
cn ( { uid : firebase . auth ( ) . currentUser . uid , name : newName } ) . then ( d => {
if ( d . data === 1 ) {
//all good
alert ( 'Thanks! All good.' ) ;
location . reload ( ) ;
$ ( '.nameChangeMessage' ) . slideUp ( ) ;
} else if ( d . data === 0 ) {
//invalid or unavailable
alert ( 'Name invalid or taken. Try again.' ) ;
} else if ( d . data === - 1 ) {
//error
alert ( 'Unknown error. Contact Miodec on Discord.' ) ;
}
} ) ;
}
2020-06-06 23:48:22 +08:00
} )
}
2020-05-11 07:32:10 +08:00
function getLastChar ( word ) {
return word . charAt ( word . length - 1 ) ;
}
2020-05-09 08:29:47 +08:00
function setFocus ( foc ) {
if ( foc ) {
// focus = true;
2020-05-30 21:05:32 +08:00
stopCaretAnimation ( ) ;
2020-05-09 08:29:47 +08:00
$ ( "#top" ) . addClass ( "focus" ) ;
$ ( "#bottom" ) . addClass ( "focus" ) ;
$ ( "body" ) . css ( "cursor" , "none" ) ;
2020-06-15 01:19:56 +08:00
$ ( "#middle" ) . addClass ( "focus" ) ;
2020-05-09 08:29:47 +08:00
} else {
startCaretAnimation ( ) ;
$ ( "#top" ) . removeClass ( "focus" ) ;
$ ( "#bottom" ) . removeClass ( "focus" ) ;
$ ( "body" ) . css ( "cursor" , "default" ) ;
2020-06-15 01:19:56 +08:00
$ ( "#middle" ) . removeClass ( "focus" ) ;
2020-05-09 08:29:47 +08:00
}
}
function capitalizeFirstLetter ( str ) {
return str . charAt ( 0 ) . toUpperCase ( ) + str . slice ( 1 ) ;
}
2020-05-25 23:50:50 +08:00
function roundedToFixed ( float , digits ) {
let rounded = Math . pow ( 10 , digits ) ;
return ( Math . round ( float * rounded ) / rounded ) . toFixed ( digits ) ;
}
2020-05-09 08:29:47 +08:00
function initWords ( ) {
testActive = false ;
wordsList = [ ] ;
currentWordIndex = 0 ;
2020-05-11 07:07:36 +08:00
accuracyStats = {
correct : 0 ,
incorrect : 0
}
2020-05-09 08:29:47 +08:00
inputHistory = [ ] ;
currentInput = "" ;
2020-05-11 07:07:36 +08:00
2020-05-16 21:57:01 +08:00
let language = words [ config . language ] ;
2020-06-29 06:16:28 +08:00
if ( language === undefined && config . language === "english_10k" ) {
showBackgroundLoader ( ) ;
$ . ajax ( {
url : "js/english_10k.json" ,
2020-06-29 06:26:58 +08:00
async : false ,
success : function ( data ) {
hideBackgroundLoader ( ) ;
2020-06-29 06:49:10 +08:00
words [ 'english_10k' ] = data ;
language = words [ config . language ] ;
2020-06-29 06:26:58 +08:00
}
2020-06-29 06:16:28 +08:00
} )
}
2020-05-16 21:57:01 +08:00
if ( language == undefined || language == [ ] ) {
2020-05-17 03:14:47 +08:00
config . language = "english" ;
language = words [ config . language ] ;
2020-05-16 21:57:01 +08:00
}
2020-05-11 07:07:36 +08:00
if ( config . mode == "time" || config . mode == "words" ) {
2020-06-02 02:16:03 +08:00
let wordsBound = config . mode == "time" ? 60 : config . words ;
2020-05-22 10:19:54 +08:00
for ( let i = 0 ; i < wordsBound ; i ++ ) {
2020-05-16 21:57:01 +08:00
randomWord = language [ Math . floor ( Math . random ( ) * language . length ) ] ;
2020-05-09 08:29:47 +08:00
previousWord = wordsList [ i - 1 ] ;
2020-06-29 02:00:42 +08:00
previousWord2 = wordsList [ i - 2 ] ;
while ( randomWord == previousWord || randomWord == previousWord2 || ( ! config . punctuation && randomWord == "I" ) || randomWord . indexOf ( ' ' ) > - 1 ) {
2020-05-16 21:57:01 +08:00
randomWord = language [ Math . floor ( Math . random ( ) * language . length ) ] ;
2020-05-09 08:29:47 +08:00
}
2020-05-22 10:19:54 +08:00
if ( config . punctuation && config . mode != "custom" ) {
randomWord = punctuateWord ( previousWord , randomWord , i , wordsBound ) ;
}
2020-05-19 11:11:34 +08:00
wordsList . push ( randomWord ) ;
2020-05-09 08:29:47 +08:00
}
2020-05-11 07:07:36 +08:00
2020-05-10 06:33:48 +08:00
} else if ( config . mode == "custom" ) {
2020-07-01 05:23:50 +08:00
// let w = customText.split(" ");
for ( let i = 0 ; i < customText . length ; i ++ ) {
wordsList . push ( customText [ i ] ) ;
2020-05-09 08:29:47 +08:00
}
}
showWords ( ) ;
}
2020-06-04 00:10:14 +08:00
function emulateLayout ( event ) {
2020-06-28 07:09:02 +08:00
if ( config . layout == "default" || event . key === " " )
2020-06-04 00:10:14 +08:00
return event ;
2020-06-04 06:20:45 +08:00
const qwertyMasterLayout = { "Backquote" : "`~" , "Digit1" : "1!" , "Digit2" : "2@" , "Digit3" : "3#" , "Digit4" : "4$" , "Digit5" : "5%" , "Digit6" : "6^" , "Digit7" : "7&" , "Digit8" : "8*" , "Digit9" : "9(" , "Digit0" : "0)" , "Minus" : "-_" , "Equal" : "=+" , "KeyQ" : "qQ" , "KeyW" : "wW" , "KeyE" : "eE" , "KeyR" : "rR" , "KeyT" : "tT" , "KeyY" : "yY" , "KeyU" : "uU" , "KeyI" : "iI" , "KeyO" : "oO" , "KeyP" : "pP" , "BracketLeft" : "[{" , "BracketRight" : "]}" , "KeyA" : "aA" , "KeyS" : "sS" , "KeyD" : "dD" , "KeyF" : "fF" , "KeyG" : "gG" , "KeyH" : "hH" , "KeyJ" : "jJ" , "KeyK" : "kK" , "KeyL" : "lL" , "Semicolon" : ";:" , "Quote" : "'\"" , "Backslash" : "\\|" , "KeyZ" : "zZ" , "KeyX" : "xX" , "KeyC" : "cC" , "KeyV" : "vV" , "KeyB" : "bB" , "KeyN" : "nN" , "KeyM" : "mM" , "Comma" : ",<" , "Period" : ".>" , "Slash" : "/?" , "Space" : " " }
2020-06-04 00:10:14 +08:00
let layoutMap = layouts [ config . layout ] ;
let qwertyMap = layouts [ "qwerty" ] ;
2020-06-04 06:12:55 +08:00
let qwertyKey = qwertyMasterLayout [ event . code ] ;
2020-06-04 00:10:14 +08:00
let mapIndex ;
let newKey ;
2020-06-04 06:12:55 +08:00
let shift = event . shiftKey ? 1 : 0 ;
2020-06-04 00:10:14 +08:00
for ( let i = 0 ; i < qwertyMap . length ; i ++ ) {
const key = qwertyMap [ i ] ;
2020-06-04 06:12:55 +08:00
let keyIndex = key . indexOf ( qwertyKey ) ;
2020-06-04 00:10:14 +08:00
if ( keyIndex != - 1 ) {
mapIndex = i ;
}
}
2020-06-04 06:12:55 +08:00
if ( ! shift && /[A-Z]/gm . test ( event . key ) ) {
shift = 1 ;
}
2020-06-04 00:10:14 +08:00
newKey = layoutMap [ mapIndex ] [ shift ] ;
event . keyCode = newKey . charCodeAt ( 0 ) ;
event . charCode = newKey . charCodeAt ( 0 ) ;
event . which = newKey . charCodeAt ( 0 ) ;
event . key = newKey ;
return event ;
}
2020-05-22 10:19:54 +08:00
function punctuateWord ( previousWord , currentWord , index , maxindex ) {
let word = currentWord ;
if ( index == 0 || getLastChar ( previousWord ) == "." || getLastChar ( previousWord ) == "?" || getLastChar ( previousWord ) == "!" ) {
//always capitalise the first word or if there was a dot
word = capitalizeFirstLetter ( word ) ;
} else if (
//10% chance to end a sentence
( Math . random ( ) < 0.1 && getLastChar ( previousWord ) != "." && index != maxindex - 2 ) || index == maxindex - 1 ) {
let rand = Math . random ( ) ;
if ( rand <= 0.8 ) {
word += "." ;
} else if ( rand > . 8 && rand < . 9 ) {
word += "?" ;
} else {
word += "!" ;
2020-05-11 07:07:36 +08:00
}
2020-05-22 10:19:54 +08:00
} else if ( Math . random ( ) < 0.01 &&
getLastChar ( previousWord ) != "," &&
getLastChar ( previousWord ) != "." ) {
//1% chance to add quotes
word = ` " ${ word } " ` ;
} else if ( Math . random ( ) < 0.01 ) {
//1% chance to add a colon
word = word + ":" ;
} else if (
Math . random ( ) < 0.01 &&
getLastChar ( previousWord ) != "," &&
getLastChar ( previousWord ) != "." &&
previousWord != "-"
) {
//1% chance to add a dash
word = "-" ;
} else if (
Math . random ( ) < 0.2 &&
getLastChar ( previousWord ) != ","
) {
//2% chance to add a comma
word += "," ;
}
return word ;
2020-05-11 07:07:36 +08:00
}
2020-05-09 08:29:47 +08:00
function addWord ( ) {
2020-06-29 06:49:10 +08:00
let language = words [ config . language ] ;
2020-05-17 00:54:19 +08:00
let randomWord = language [ Math . floor ( Math . random ( ) * language . length ) ] ;
2020-05-22 10:19:54 +08:00
previousWord = wordsList [ wordsList . length - 1 ] ;
2020-05-28 05:11:48 +08:00
previousWordStripped = previousWord . replace ( /[.?!":\-,]/g , '' ) . toLowerCase ( ) ;
while ( previousWordStripped == randomWord || randomWord . indexOf ( ' ' ) > - 1 || ( ! config . punctuation && randomWord == "I" ) ) {
2020-05-17 00:57:42 +08:00
randomWord = language [ Math . floor ( Math . random ( ) * language . length ) ] ;
}
2020-05-27 21:06:59 +08:00
if ( config . punctuation && config . mode != "custom" ) {
2020-05-22 10:19:54 +08:00
randomWord = punctuateWord ( previousWord , randomWord , wordsList . length , 0 )
}
2020-05-09 08:29:47 +08:00
wordsList . push ( randomWord ) ;
2020-05-22 10:19:54 +08:00
2020-05-09 08:29:47 +08:00
let w = "<div class='word'>" ;
for ( let c = 0 ; c < randomWord . length ; c ++ ) {
w += "<letter>" + randomWord . charAt ( c ) + "</letter>" ;
}
w += "</div>" ;
$ ( "#words" ) . append ( w ) ;
}
function showWords ( ) {
$ ( "#words" ) . empty ( ) ;
2020-05-10 06:33:48 +08:00
if ( config . mode == "words" || config . mode == "custom" ) {
2020-05-10 02:47:11 +08:00
$ ( "#words" ) . css ( "height" , "auto" ) ;
2020-05-09 08:29:47 +08:00
for ( let i = 0 ; i < wordsList . length ; i ++ ) {
let w = "<div class='word'>" ;
for ( let c = 0 ; c < wordsList [ i ] . length ; c ++ ) {
w += "<letter>" + wordsList [ i ] . charAt ( c ) + "</letter>" ;
}
w += "</div>" ;
$ ( "#words" ) . append ( w ) ;
}
2020-05-10 06:33:48 +08:00
} else if ( config . mode == "time" ) {
2020-05-09 08:29:47 +08:00
for ( let i = 0 ; i < wordsList . length ; i ++ ) {
let w = "<div class='word'>" ;
for ( let c = 0 ; c < wordsList [ i ] . length ; c ++ ) {
w += "<letter>" + wordsList [ i ] . charAt ( c ) + "</letter>" ;
}
w += "</div>" ;
$ ( "#words" ) . append ( w ) ;
}
2020-05-20 23:22:19 +08:00
$ ( "#words" ) . removeClass ( 'hidden' ) ;
2020-05-22 23:19:09 +08:00
const wordHeight = $ ( $ ( ".word" ) [ 0 ] ) . outerHeight ( true ) ;
2020-05-19 17:45:35 +08:00
$ ( "#words" ) . css ( "height" , wordHeight * 3 + 'px' ) . css ( "overflow" , "hidden" ) ;
2020-05-09 08:29:47 +08:00
}
updateActiveElement ( ) ;
updateCaretPosition ( ) ;
}
function updateActiveElement ( ) {
$ ( "#words .word" ) . removeClass ( "active" ) ;
$ ( $ ( "#words .word" ) [ currentWordIndex ] )
. addClass ( "active" )
. removeClass ( "error" ) ;
2020-06-01 04:03:54 +08:00
activeWordTop = $ ( "#words .word.active" ) . position ( ) . top ;
2020-05-09 08:29:47 +08:00
}
2020-06-06 18:46:21 +08:00
function compareInput ( wrdIndex , input , showError ) {
2020-06-06 20:22:52 +08:00
$ ( $ ( '#words .word' ) [ wrdIndex ] ) . empty ( ) ;
2020-05-11 07:32:10 +08:00
let ret = "" ;
2020-05-31 04:30:50 +08:00
let currentWord = wordsList [ wrdIndex ] ;
for ( let i = 0 ; i < input . length ; i ++ ) {
if ( currentWord [ i ] == input [ i ] ) {
2020-05-11 07:32:10 +08:00
ret += '<letter class="correct">' + currentWord [ i ] + "</letter>" ;
// $(letterElems[i]).removeClass('incorrect').addClass('correct');
} else {
2020-05-29 01:41:01 +08:00
if ( config . difficulty == "master" ) {
2020-05-31 04:30:50 +08:00
if ( ! resultVisible ) showResult ( true ) ;
2020-06-15 01:19:56 +08:00
if ( ! afkDetected ) {
let testNow = Date . now ( ) ;
let testSeconds = roundTo2 ( ( testNow - testStart ) / 1000 ) ;
incompleteTestSeconds += testSeconds ;
restartCount ++ ;
}
2020-05-29 01:41:01 +08:00
}
2020-05-31 04:30:50 +08:00
if ( ! showError ) {
2020-05-30 20:15:00 +08:00
if ( currentWord [ i ] == undefined ) {
2020-05-31 04:30:50 +08:00
// ret += '<letter class="correct">' + input[i] + "</letter>";
2020-05-30 20:15:00 +08:00
} else {
ret += '<letter class="correct">' + currentWord [ i ] + "</letter>" ;
}
} else {
if ( currentWord [ i ] == undefined ) {
2020-05-31 04:30:50 +08:00
ret += '<letter class="incorrect extra">' + input [ i ] + "</letter>" ;
2020-05-30 20:15:00 +08:00
} else {
ret += '<letter class="incorrect">' + currentWord [ i ] + "</letter>" ;
}
2020-05-11 07:32:10 +08:00
}
2020-05-09 08:29:47 +08:00
}
}
2020-05-31 04:30:50 +08:00
if ( input . length < currentWord . length ) {
for ( let i = input . length ; i < currentWord . length ; i ++ ) {
2020-05-11 07:32:10 +08:00
ret += "<letter>" + currentWord [ i ] + "</letter>" ;
}
}
2020-05-31 04:30:50 +08:00
$ ( $ ( '#words .word' ) [ wrdIndex ] ) . html ( ret ) ;
if ( ( currentWord == input || ( config . quickEnd && currentWord . length == input . length ) ) && wrdIndex == wordsList . length - 1 ) {
inputHistory . push ( input ) ;
2020-05-11 07:32:10 +08:00
currentInput = "" ;
2020-05-31 04:30:50 +08:00
if ( ! resultVisible ) showResult ( ) ;
2020-05-11 07:32:10 +08:00
}
// liveWPM()
2020-05-09 08:29:47 +08:00
}
2020-05-31 04:30:50 +08:00
function highlightBadWord ( index , showError ) {
if ( ! showError ) return ;
$ ( $ ( "#words .word" ) [ index ] ) . addClass ( "error" ) ;
2020-05-09 08:29:47 +08:00
}
2020-05-11 07:32:10 +08:00
function showTimer ( ) {
2020-05-31 07:47:56 +08:00
if ( ! config . showTimerBar ) return ;
2020-06-16 06:13:40 +08:00
if ( config . timerStyle === "bar" ) {
$ ( "#timerWrapper" ) . animate ( {
"opacity" : 1
} , 250 ) ;
2020-06-22 07:54:10 +08:00
} else if ( config . timerStyle === "text" && config . mode === "time" ) {
2020-06-16 06:13:40 +08:00
$ ( "#timerNumber" ) . animate ( {
"opacity" : . 25
} , 250 ) ;
}
2020-05-11 07:32:10 +08:00
}
function hideTimer ( ) {
2020-06-16 06:13:40 +08:00
if ( config . timerStyle === "bar" ) {
$ ( "#timerWrapper" ) . animate ( {
"opacity" : 0
} , 125 ) ;
} else if ( config . timerStyle === "text" ) {
$ ( "#timerNumber" ) . animate ( {
"opacity" : 0
} , 125 ) ;
}
2020-06-16 05:52:47 +08:00
}
function restartTimer ( ) {
2020-06-16 06:13:40 +08:00
if ( config . timerStyle === "bar" ) {
2020-06-22 06:51:48 +08:00
if ( config . mode === "time" ) {
$ ( "#timer" ) . stop ( true , true ) . animate ( {
"width" : "100vw"
} , 0 ) ;
} else if ( config . mode === "words" || config . mode === "custom" ) {
$ ( "#timer" ) . stop ( true , true ) . animate ( {
"width" : "0vw"
} , 0 ) ;
}
2020-06-16 06:13:40 +08:00
}
2020-05-11 07:32:10 +08:00
}
2020-06-16 06:13:40 +08:00
function updateTimer ( ) {
2020-06-22 06:51:48 +08:00
if ( config . mode === "time" ) {
if ( config . timerStyle === "bar" ) {
let percent = 100 - ( ( ( time + 1 ) / config . time ) * 100 ) ;
$ ( "#timer" ) . stop ( true , true ) . animate ( {
"width" : percent + "vw"
} , 1000 , "linear" ) ;
} else if ( config . timerStyle === "text" ) {
$ ( "#timerNumber" ) . html ( config . time - time ) ;
}
} else if ( ( config . mode === "words" || config . mode === "custom" ) && config . timerStyle === "bar" ) {
let percent = Math . floor ( ( ( currentWordIndex + 1 ) / wordsList . length ) * 100 ) ;
$ ( "#timer" ) . stop ( true , true ) . animate ( {
"width" : percent + "vw"
} , 250 ) ;
2020-06-16 06:13:40 +08:00
}
2020-05-11 07:32:10 +08:00
}
2020-05-09 08:29:47 +08:00
function hideCaret ( ) {
$ ( "#caret" ) . addClass ( "hidden" ) ;
}
function showCaret ( ) {
2020-05-24 06:52:49 +08:00
if ( $ ( "#result" ) . hasClass ( 'hidden' ) ) {
updateCaretPosition ( ) ;
$ ( "#caret" ) . removeClass ( "hidden" ) ;
startCaretAnimation ( ) ;
}
2020-05-09 08:29:47 +08:00
}
function stopCaretAnimation ( ) {
$ ( "#caret" ) . css ( "animation-name" , "none" ) ;
2020-05-29 12:39:51 +08:00
$ ( "#caret" ) . css ( "opacity" , "1" ) ;
2020-05-09 08:29:47 +08:00
}
function startCaretAnimation ( ) {
$ ( "#caret" ) . css ( "animation-name" , "caretFlash" ) ;
}
function updateCaretPosition ( ) {
2020-05-15 06:40:29 +08:00
if ( $ ( "#words" ) . hasClass ( 'hidden' ) ) return ;
2020-05-09 08:29:47 +08:00
let caret = $ ( "#caret" ) ;
let activeWord = $ ( "#words .word.active" ) ;
let inputLen = currentInput . length ;
let currentLetterIndex = inputLen - 1 ;
if ( currentLetterIndex == - 1 ) {
currentLetterIndex = 0 ;
}
let currentLetter = $ ( $ ( "#words .word.active letter" ) [ currentLetterIndex ] ) ;
2020-05-31 22:10:02 +08:00
if ( currentLetter . length == 0 ) return ;
2020-05-09 08:29:47 +08:00
let currentLetterPos = currentLetter . position ( ) ;
let letterHeight = currentLetter . height ( ) ;
2020-05-10 02:47:11 +08:00
let newTop = 0 ;
let newLeft = 0 ;
newTop = currentLetterPos . top - letterHeight / 4 ;
2020-05-09 08:29:47 +08:00
if ( inputLen == 0 ) {
2020-05-10 02:47:11 +08:00
// caret.css({
// top: currentLetterPos.top - letterHeight / 4,
// left: currentLetterPos.left - caret.width() / 2
// });
2020-05-11 07:32:10 +08:00
2020-05-10 02:47:11 +08:00
newLeft = currentLetterPos . left - caret . width ( ) / 2 ;
2020-05-09 08:29:47 +08:00
} else {
2020-05-10 02:47:11 +08:00
// caret.css({
// top: currentLetterPos.top - letterHeight / 4,
// left: currentLetterPos.left + currentLetter.width() - caret.width() / 2
// });
newLeft = currentLetterPos . left + currentLetter . width ( ) - caret . width ( ) / 2 ;
2020-05-09 08:29:47 +08:00
}
2020-05-10 02:47:11 +08:00
let duration = 0 ;
2020-05-10 06:33:48 +08:00
// if (config.smoothCaret && Math.round(caret.position().top) == Math.round(newTop)) {
// duration = 100;
// }
if ( config . smoothCaret ) {
2020-05-10 02:47:11 +08:00
duration = 100 ;
2020-05-28 05:02:50 +08:00
if ( Math . round ( caret . position ( ) . top ) != Math . round ( newTop ) ) {
caret . css ( "top" , newTop ) ;
duration = 10 ;
}
2020-05-10 02:47:11 +08:00
}
2020-05-11 07:32:10 +08:00
caret . stop ( true , true ) . animate ( {
2020-05-10 02:47:11 +08:00
top : newTop ,
left : newLeft
2020-05-11 07:32:10 +08:00
} , duration )
2020-05-10 06:33:48 +08:00
2020-05-29 06:32:52 +08:00
let browserHeight = window . innerHeight ;
2020-05-28 09:37:56 +08:00
let middlePos = ( browserHeight / 2 ) - $ ( "#caret" ) . outerHeight ( ) / 2 ;
2020-05-29 06:32:52 +08:00
let contentHeight = document . body . scrollHeight ;
2020-05-28 09:04:48 +08:00
2020-05-29 06:32:52 +08:00
if ( newTop >= middlePos && contentHeight > browserHeight ) {
2020-05-28 09:04:48 +08:00
window . scrollTo ( {
left : 0 ,
top : newTop - middlePos ,
behavior : 'smooth'
} )
}
2020-05-11 07:07:36 +08:00
}
function countChars ( ) {
let correctWordChars = 0 ;
2020-05-09 08:29:47 +08:00
let correctChars = 0 ;
let incorrectChars = 0 ;
2020-05-11 07:07:36 +08:00
let extraChars = 0 ;
let missedChars = 0 ;
2020-05-28 20:10:20 +08:00
let spaces = 0 ;
2020-05-09 08:29:47 +08:00
for ( let i = 0 ; i < inputHistory . length ; i ++ ) {
2020-05-11 07:07:36 +08:00
if ( inputHistory [ i ] == wordsList [ i ] ) {
//the word is correct
2020-05-28 20:10:20 +08:00
correctWordChars += wordsList [ i ] . length ;
2020-05-11 07:07:36 +08:00
correctChars += wordsList [ i ] . length ;
} else if ( inputHistory [ i ] . length >= wordsList [ i ] . length ) {
//too many chars
2020-05-11 07:32:10 +08:00
for ( let c = 0 ; c < inputHistory [ i ] . length ; c ++ ) {
2020-05-11 07:07:36 +08:00
if ( c < wordsList [ i ] . length ) {
//on char that still has a word list pair
if ( inputHistory [ i ] [ c ] == wordsList [ i ] [ c ] ) {
correctChars ++ ;
} else {
incorrectChars ++ ;
}
2020-05-09 08:29:47 +08:00
} else {
2020-05-11 07:07:36 +08:00
//on char that is extra
extraChars ++ ;
}
}
} else {
//not enough chars
2020-05-11 07:32:10 +08:00
for ( let c = 0 ; c < wordsList [ i ] . length ; c ++ ) {
2020-05-11 07:07:36 +08:00
if ( c < inputHistory [ i ] . length ) {
//on char that still has a word list pair
if ( inputHistory [ i ] [ c ] == wordsList [ i ] [ c ] ) {
correctChars ++ ;
} else {
incorrectChars ++ ;
}
} else {
//on char that is extra
missedChars ++ ;
2020-05-09 08:29:47 +08:00
}
}
}
2020-05-28 20:10:20 +08:00
if ( i < inputHistory . length - 1 ) {
spaces ++ ;
}
2020-05-09 08:29:47 +08:00
}
2020-05-11 07:07:36 +08:00
return {
2020-05-28 20:10:20 +08:00
spaces : spaces ,
2020-05-11 07:07:36 +08:00
correctWordChars : correctWordChars ,
allCorrectChars : correctChars ,
incorrectChars : incorrectChars ,
extraChars : extraChars ,
missedChars : missedChars
}
2020-05-09 08:29:47 +08:00
}
2020-05-31 21:31:50 +08:00
function roundTo2 ( num ) {
return Math . round ( ( num + Number . EPSILON ) * 100 ) / 100
}
2020-05-11 07:32:10 +08:00
function calculateStats ( ) {
2020-05-29 01:51:52 +08:00
if ( config . mode == "words" && config . difficulty == "normal" ) {
2020-05-11 07:32:10 +08:00
if ( inputHistory . length != wordsList . length ) return ;
2020-05-09 08:29:47 +08:00
}
2020-05-11 07:32:10 +08:00
let chars = countChars ( ) ;
2020-05-11 07:07:36 +08:00
let testNow = Date . now ( ) ;
2020-05-31 21:31:50 +08:00
let testSeconds = roundTo2 ( ( testNow - testStart ) / 1000 ) ;
let wpm = roundTo2 ( ( ( chars . correctWordChars + chars . spaces ) * ( 60 / testSeconds ) ) / 5 ) ;
let wpmraw = roundTo2 ( ( ( chars . allCorrectChars + chars . spaces + chars . incorrectChars + chars . extraChars ) * ( 60 / testSeconds ) ) / 5 ) ;
let acc = roundTo2 ( ( accuracyStats . correct / ( accuracyStats . correct + accuracyStats . incorrect ) ) * 100 ) ;
2020-05-25 23:50:50 +08:00
return {
2020-06-23 23:27:33 +08:00
wpm : isNaN ( wpm ) ? 0 : wpm ,
wpmRaw : isNaN ( wpmraw ) ? 0 : wpmraw ,
2020-05-25 23:50:50 +08:00
acc : acc ,
2020-05-27 00:49:28 +08:00
correctChars : chars . correctWordChars ,
2020-05-25 23:50:50 +08:00
incorrectChars : chars . incorrectChars + chars . extraChars + chars . missedChars ,
2020-05-28 20:19:03 +08:00
time : testSeconds ,
spaces : chars . spaces
2020-05-25 23:50:50 +08:00
} ;
2020-05-10 01:39:23 +08:00
}
2020-05-12 07:59:12 +08:00
function hideCrown ( ) {
2020-05-22 22:41:34 +08:00
$ ( "#result .stats .wpm .crownWrapper" ) . css ( 'opacity' , 0 ) ;
2020-05-12 07:59:12 +08:00
}
function showCrown ( ) {
2020-06-08 09:25:23 +08:00
$ ( "#result .stats .wpm .crownWrapper" ) . animate ( {
2020-05-22 22:41:34 +08:00
opacity : 1
} , 250 , "easeOutCubic" ) ;
2020-05-12 07:59:12 +08:00
}
2020-05-29 02:01:44 +08:00
function showResult ( difficultyFailed = false ) {
2020-06-04 06:22:20 +08:00
resultVisible = true ;
2020-05-10 01:39:23 +08:00
testEnd = Date . now ( ) ;
2020-05-25 23:50:50 +08:00
testActive = false ;
setFocus ( false ) ;
hideCaret ( ) ;
hideLiveWpm ( ) ;
2020-06-16 06:13:40 +08:00
hideTimer ( ) ;
2020-05-29 21:28:15 +08:00
testInvalid = false ;
2020-05-10 01:39:23 +08:00
let stats = calculateStats ( ) ;
2020-05-22 22:41:34 +08:00
if ( stats === undefined ) {
stats = {
wpm : 0 ,
2020-05-27 00:49:28 +08:00
wpmRaw : 0 ,
2020-05-22 22:41:34 +08:00
acc : 0 ,
correctChars : 0 ,
2020-05-25 23:50:50 +08:00
incorrectChars : 0 ,
2020-05-28 20:19:03 +08:00
time : 0 ,
spaces : 0
2020-05-22 22:41:34 +08:00
}
}
2020-05-15 06:19:28 +08:00
clearIntervals ( ) ;
2020-05-29 23:00:22 +08:00
let testtime = roundedToFixed ( stats . time , 1 ) ;
2020-05-31 22:37:09 +08:00
$ ( "#result .stats .wpm .bottom" ) . text ( Math . round ( stats . wpm ) ) ;
$ ( "#result .stats .raw .bottom" ) . text ( Math . round ( stats . wpmRaw ) ) ;
2020-06-25 05:13:01 +08:00
$ ( "#result .stats .acc .bottom" ) . text ( Math . floor ( stats . acc ) + "%" ) ;
2020-05-28 20:19:03 +08:00
$ ( "#result .stats .key .bottom" ) . text ( stats . correctChars + stats . spaces + "/" + stats . incorrectChars ) ;
2020-05-29 23:00:22 +08:00
$ ( "#result .stats .time .bottom" ) . text ( testtime + 's' ) ;
2020-05-27 00:49:28 +08:00
2020-05-27 08:00:28 +08:00
setTimeout ( function ( ) {
2020-06-13 01:07:16 +08:00
$ ( "#resultExtraButtons" ) . removeClass ( 'hidden' ) . css ( 'opacity' , 0 ) . animate ( {
opacity : 1
} , 125 ) ;
2020-05-27 08:00:28 +08:00
} , 125 ) ;
2020-06-15 02:10:59 +08:00
$ ( "#testModesNotice" ) . css ( {
'opacity' : 0 ,
// 'height': 0,
// 'margin-bottom': 0
} ) ;
2020-05-27 08:00:28 +08:00
2020-05-10 06:33:48 +08:00
let mode2 = "" ;
2020-06-28 05:17:41 +08:00
if ( config . mode === "time" ) {
2020-05-10 06:33:48 +08:00
mode2 = config . time ;
2020-05-27 00:49:28 +08:00
// $("#result .stats .time").addClass('hidden');
2020-06-28 05:17:41 +08:00
} else if ( config . mode === "words" ) {
2020-05-10 06:33:48 +08:00
mode2 = config . words ;
2020-05-27 00:49:28 +08:00
// $("#result .stats .time").removeClass('hidden');
// $("#result .stats .time .bottom").text(roundedToFixed(stats.time,1)+'s');
2020-06-28 05:17:41 +08:00
} else if ( config . mode === "custom" ) {
mode2 = "custom" ;
2020-05-10 06:33:48 +08:00
}
2020-06-25 06:25:12 +08:00
2020-06-05 02:37:16 +08:00
2020-06-16 04:41:18 +08:00
if ( firebase . auth ( ) . currentUser != null ) {
$ ( "#result .loginTip" ) . addClass ( 'hidden' ) ;
} else {
$ ( "#result .loginTip" ) . removeClass ( 'hidden' ) ;
}
2020-05-10 01:39:23 +08:00
2020-05-29 21:28:15 +08:00
let testType = "" ;
2020-05-10 01:39:23 +08:00
2020-05-16 21:57:01 +08:00
2020-05-29 21:28:15 +08:00
testType += config . mode ;
2020-05-10 06:33:48 +08:00
if ( config . mode == "time" ) {
2020-05-29 21:28:15 +08:00
testType += " " + config . time
2020-05-10 06:33:48 +08:00
} else if ( config . mode == "words" ) {
2020-05-29 21:28:15 +08:00
testType += " " + config . words
2020-05-10 01:39:23 +08:00
}
2020-05-20 23:28:26 +08:00
if ( config . mode != "custom" ) {
2020-05-29 21:28:15 +08:00
testType += "<br>" + config . language . replace ( '_' , ' ' ) ;
2020-05-20 23:28:26 +08:00
}
2020-05-10 06:33:48 +08:00
if ( config . punctuation ) {
2020-05-29 21:28:15 +08:00
testType += "<br>punctuation"
2020-05-10 01:39:23 +08:00
}
2020-05-31 02:22:11 +08:00
if ( config . blindMode ) {
testType += "<br>blind"
}
2020-05-29 01:41:01 +08:00
if ( config . difficulty == "expert" ) {
2020-05-29 21:28:15 +08:00
testType += "<br>expert" ;
2020-05-29 01:41:01 +08:00
} else if ( config . difficulty == "master" ) {
2020-05-29 21:28:15 +08:00
testType += "<br>master" ;
}
$ ( "#result .stats .testType .bottom" ) . html ( testType ) ;
let otherText = "" ;
if ( difficultyFailed ) {
otherText += "<br>failed"
}
if ( afkDetected ) {
otherText += "<br>afk detected"
}
if ( testInvalid ) {
otherText += "<br>invalid"
}
2020-06-06 09:05:21 +08:00
if ( sameWordset ) {
otherText += "<br>repeated"
}
2020-05-29 21:28:15 +08:00
if ( otherText == "" ) {
$ ( "#result .stats .info" ) . addClass ( 'hidden' ) ;
} else {
$ ( "#result .stats .info" ) . removeClass ( 'hidden' ) ;
otherText = otherText . substring ( 4 ) ;
$ ( "#result .stats .info .bottom" ) . html ( otherText ) ;
2020-05-29 01:41:01 +08:00
}
2020-05-10 01:39:23 +08:00
2020-06-12 01:20:59 +08:00
let tagsText = "" ;
2020-06-24 02:30:54 +08:00
try {
dbSnapshot . tags . forEach ( tag => {
if ( tag . active === true ) {
tagsText += "<br>" + tag . name ;
}
} )
} catch ( e ) {
}
2020-06-12 01:20:59 +08:00
if ( tagsText == "" ) {
$ ( "#result .stats .tags" ) . addClass ( 'hidden' ) ;
} else {
$ ( "#result .stats .tags" ) . removeClass ( 'hidden' ) ;
tagsText = tagsText . substring ( 4 ) ;
$ ( "#result .stats .tags .bottom" ) . html ( tagsText ) ;
}
2020-05-10 01:39:23 +08:00
let labels = [ ] ;
2020-05-10 02:47:11 +08:00
for ( let i = 1 ; i <= wpmHistory . length ; i ++ ) {
labels . push ( i . toString ( ) ) ;
2020-05-10 01:39:23 +08:00
}
2020-05-11 23:29:18 +08:00
let mainColor = getComputedStyle ( document . body ) . getPropertyValue ( '--main-color' ) . replace ( ' ' , '' ) ;
2020-05-15 11:09:00 +08:00
let subColor = getComputedStyle ( document . body ) . getPropertyValue ( '--sub-color' ) . replace ( ' ' , '' ) ;
2020-06-05 02:37:16 +08:00
let bgColor = getComputedStyle ( document . body ) . getPropertyValue ( '--bg-color' ) . replace ( ' ' , '' ) ;
2020-05-15 11:09:00 +08:00
2020-05-11 23:56:10 +08:00
wpmOverTimeChart . options . scales . xAxes [ 0 ] . ticks . minor . fontColor = subColor ;
2020-05-24 06:52:49 +08:00
wpmOverTimeChart . options . scales . xAxes [ 0 ] . scaleLabel . fontColor = subColor ;
2020-05-11 23:56:10 +08:00
wpmOverTimeChart . options . scales . yAxes [ 0 ] . ticks . minor . fontColor = subColor ;
2020-05-31 21:54:54 +08:00
wpmOverTimeChart . options . scales . yAxes [ 2 ] . ticks . minor . fontColor = subColor ;
2020-05-24 06:52:49 +08:00
wpmOverTimeChart . options . scales . yAxes [ 0 ] . scaleLabel . fontColor = subColor ;
2020-05-31 21:54:54 +08:00
wpmOverTimeChart . options . scales . yAxes [ 2 ] . scaleLabel . fontColor = subColor ;
2020-05-24 06:52:49 +08:00
2020-05-31 21:54:54 +08:00
wpmOverTimeChart . data . labels = labels ;
2020-05-24 06:52:49 +08:00
2020-06-25 05:13:01 +08:00
let rawWpmPerSecond = keypressPerSecond . map ( f => Math . round ( ( f / 5 ) * 60 ) ) ;
2020-06-25 08:30:58 +08:00
rawWpmPerSecond = smooth ( rawWpmPerSecond , 1 ) ;
2020-05-11 23:29:18 +08:00
wpmOverTimeChart . data . datasets [ 0 ] . borderColor = mainColor ;
2020-05-10 09:04:05 +08:00
wpmOverTimeChart . data . datasets [ 0 ] . data = wpmHistory ;
2020-05-31 21:54:54 +08:00
wpmOverTimeChart . data . datasets [ 1 ] . borderColor = subColor ;
2020-06-25 05:13:01 +08:00
wpmOverTimeChart . data . datasets [ 1 ] . data = rawWpmPerSecond ;
2020-05-31 21:54:54 +08:00
2020-06-05 02:37:16 +08:00
wpmOverTimeChart . options . annotation . annotations [ 0 ] . borderColor = subColor ;
wpmOverTimeChart . options . annotation . annotations [ 0 ] . label . backgroundColor = subColor ;
wpmOverTimeChart . options . annotation . annotations [ 0 ] . label . fontColor = bgColor ;
2020-05-31 22:18:39 +08:00
2020-06-30 23:26:44 +08:00
let maxChartVal = Math . max ( ... [ Math . max ( ... rawWpmPerSecond ) , Math . max ( ... wpmHistory ) ] ) ;
2020-05-27 02:59:58 +08:00
let errorsNoZero = [ ] ;
for ( let i = 0 ; i < errorsPerSecond . length ; i ++ ) {
errorsNoZero . push ( {
x : i + 1 ,
y : errorsPerSecond [ i ]
} ) ;
}
2020-05-31 21:54:54 +08:00
wpmOverTimeChart . data . datasets [ 2 ] . data = errorsNoZero ;
2020-05-24 06:52:49 +08:00
2020-06-30 23:26:44 +08:00
if ( difficultyFailed ) {
showNotification ( "Test failed" , 2000 ) ;
} else if ( afkDetected ) {
showNotification ( "Test invalid - AFK detected" , 2000 ) ;
} else if ( sameWordset ) {
showNotification ( "Test invalid - repeated" , 2000 ) ;
} else {
let activeTags = [ ] ;
try {
dbSnapshot . tags . forEach ( tag => {
if ( tag . active === true ) {
activeTags . push ( tag . id ) ;
}
} )
} catch ( e ) {
}
let completedEvent = {
wpm : stats . wpm ,
rawWpm : stats . wpmRaw ,
correctChars : stats . correctChars + stats . spaces ,
incorrectChars : stats . incorrectChars ,
acc : stats . acc ,
mode : config . mode ,
mode2 : mode2 ,
punctuation : config . punctuation ,
timestamp : Date . now ( ) ,
language : config . language ,
restartCount : restartCount ,
incompleteTestSeconds : incompleteTestSeconds ,
difficulty : config . difficulty ,
testDuration : testtime ,
blindMode : config . blindMode ,
theme : config . theme ,
tags : activeTags
} ;
if ( config . difficulty == "normal" || ( ( config . difficulty == "master" || config . difficulty == "expert" ) && ! difficultyFailed ) ) {
// console.log(incompleteTestSeconds);
// console.log(restartCount);
restartCount = 0 ;
incompleteTestSeconds = 0 ;
}
if ( stats . wpm > 0 && stats . wpm < 350 && stats . acc > 50 && stats . acc <= 100 ) {
if ( firebase . auth ( ) . currentUser != null ) {
completedEvent . uid = firebase . auth ( ) . currentUser . uid ;
//check local pb
accountIconLoading ( true ) ;
let localPb = false ;
let dontShowCrown = false ;
db _getLocalPB ( config . mode , mode2 , config . punctuation , config . language , config . difficulty ) . then ( lpb => {
db _getUserHighestWpm ( config . mode , mode2 , config . punctuation , config . language , config . difficulty ) . then ( highestwpm => {
if ( lpb < stats . wpm && stats . wpm < highestwpm ) {
dontShowCrown = true ;
}
if ( lpb < stats . wpm ) {
//new pb based on local
if ( ! dontShowCrown ) {
hideCrown ( ) ;
showCrown ( ) ;
}
localPb = true ;
}
if ( highestwpm > 0 ) {
wpmOverTimeChart . options . annotation . annotations [ 0 ] . value = highestwpm ;
wpmOverTimeChart . options . annotation . annotations [ 0 ] . label . content = "PB: " + highestwpm ;
if ( maxChartVal >= highestwpm - 15 && maxChartVal <= highestwpm + 15 ) {
maxChartVal = highestwpm + 15 ;
}
wpmOverTimeChart . options . scales . yAxes [ 0 ] . ticks . max = Math . round ( maxChartVal ) ;
wpmOverTimeChart . options . scales . yAxes [ 1 ] . ticks . max = Math . round ( maxChartVal ) ;
wpmOverTimeChart . update ( { duration : 0 } ) ;
}
testCompleted ( { uid : firebase . auth ( ) . currentUser . uid , obj : completedEvent } ) . then ( e => {
accountIconLoading ( false ) ;
if ( e . data === - 1 ) {
showNotification ( 'Could not save result' , 3000 ) ;
} else if ( e . data === 1 || e . data === 2 ) {
dbSnapshot . results . unshift ( completedEvent ) ;
try {
firebase . analytics ( ) . logEvent ( 'testCompleted' , completedEvent ) ;
} catch ( e ) {
console . log ( "Analytics unavailable" ) ;
}
if ( e . data === 2 ) {
//new pb
if ( ! localPb ) {
showNotification ( 'Local PB data is out of sync! Resyncing.' , 5000 ) ;
}
db _saveLocalPB ( config . mode , mode2 , config . punctuation , config . language , config . difficulty , stats . wpm ) ;
} else {
if ( localPb ) {
showNotification ( 'Local PB data is out of sync! Refresh the page to resync it or contact Miodec on Discord.' , 15000 ) ;
}
}
}
} )
} )
} )
} else {
try {
firebase . analytics ( ) . logEvent ( 'testCompletedNoLogin' , completedEvent ) ;
} catch ( e ) {
console . log ( "Analytics unavailable" ) ;
}
// showNotification("Sign in to save your result",3000);
}
} else {
showNotification ( "Test invalid" , 3000 ) ;
testInvalid = true ;
try {
firebase . analytics ( ) . logEvent ( 'testCompletedInvalid' , completedEvent ) ;
} catch ( e ) {
console . log ( "Analytics unavailable" ) ;
}
}
}
2020-05-24 06:52:49 +08:00
2020-06-30 23:26:44 +08:00
wpmOverTimeChart . options . scales . yAxes [ 0 ] . ticks . max = maxChartVal ;
wpmOverTimeChart . options . scales . yAxes [ 1 ] . ticks . max = maxChartVal ;
2020-05-24 06:52:49 +08:00
2020-05-10 09:04:05 +08:00
wpmOverTimeChart . update ( { duration : 0 } ) ;
2020-05-27 08:00:28 +08:00
swapElements ( $ ( "#words" ) , $ ( "#result" ) , 250 , ( ) => {
2020-05-31 04:30:50 +08:00
if ( config . blindMode ) {
$ . each ( $ ( '#words .word' ) , ( i , word ) => {
2020-05-31 04:43:15 +08:00
let input = inputHistory [ i ] ;
if ( input == undefined ) input = currentInput ;
compareInput ( i , input , true ) ;
2020-05-31 04:30:50 +08:00
if ( inputHistory [ i ] != wordsList [ i ] ) {
highlightBadWord ( i , true ) ;
}
} )
}
2020-05-27 08:00:28 +08:00
let remove = false ;
$ . each ( $ ( '#words .word' ) , ( i , obj ) => {
if ( remove ) {
$ ( obj ) . remove ( ) ;
} else {
$ ( obj ) . removeClass ( 'hidden' ) ;
if ( $ ( obj ) . hasClass ( 'active' ) ) remove = true ;
}
} ) ;
} ) ;
2020-05-09 08:29:47 +08:00
}
2020-06-06 09:05:21 +08:00
function restartTest ( withSameWordset = false ) {
2020-05-15 06:19:28 +08:00
clearIntervals ( ) ;
2020-05-15 03:22:59 +08:00
time = 0 ;
2020-05-23 23:29:36 +08:00
afkDetected = false ;
wpmHistory = [ ] ;
2020-05-31 21:31:50 +08:00
rawHistory = [ ] ;
2020-05-09 08:29:47 +08:00
setFocus ( false ) ;
2020-05-10 02:47:11 +08:00
hideCaret ( ) ;
2020-05-11 07:07:36 +08:00
testActive = false ;
hideLiveWpm ( ) ;
2020-05-24 06:52:49 +08:00
keypressPerSecond = [ ] ;
currentKeypressCount = 0 ;
errorsPerSecond = [ ] ;
currentErrorCount = 0 ;
2020-05-22 23:19:09 +08:00
currentTestLine = 0 ;
2020-06-22 02:50:34 +08:00
activeWordJumped = false ;
2020-06-16 05:52:47 +08:00
hideTimer ( ) ;
// restartTimer();
2020-05-20 22:57:54 +08:00
let el = null ;
2020-05-27 08:00:28 +08:00
if ( resultVisible ) {
2020-05-20 22:57:54 +08:00
//results are being displayed
el = $ ( "#result" ) ;
2020-05-27 08:00:28 +08:00
} else {
2020-05-20 22:57:54 +08:00
//words are being displayed
el = $ ( "#words" ) ;
}
2020-05-27 08:00:28 +08:00
if ( resultVisible ) {
$ ( "#words" ) . stop ( true , true ) . animate ( {
opacity : 0
} , 125 ) ;
$ ( "#wordsTitle" ) . stop ( true , true ) . animate ( {
opacity : 0
} , 125 , ( ) => {
$ ( "#wordsTitle" ) . slideUp ( 0 ) ;
} ) ;
2020-06-06 09:05:21 +08:00
$ ( "#resultExtraButtons" ) . stop ( true , true ) . animate ( {
2020-05-27 08:00:28 +08:00
opacity : 0
} , 125 , ( ) => {
2020-06-06 09:05:21 +08:00
$ ( "#resultExtraButtons" ) . addClass ( 'hidden' ) ;
2020-06-05 07:00:45 +08:00
} ) ;
2020-05-27 08:00:28 +08:00
}
2020-05-27 23:52:48 +08:00
resultVisible = false ;
2020-06-16 05:52:47 +08:00
// .css("transition", "1s linear");
2020-05-20 22:57:54 +08:00
el . stop ( true , true ) . animate ( {
2020-05-10 02:47:11 +08:00
opacity : 0
} , 125 , ( ) => {
2020-06-06 09:05:21 +08:00
if ( ! withSameWordset ) {
sameWordset = false ;
initWords ( ) ;
} else {
sameWordset = true ;
testActive = false ;
currentWordIndex = 0 ;
accuracyStats = {
correct : 0 ,
incorrect : 0
}
inputHistory = [ ] ;
currentInput = "" ;
showWords ( ) ;
}
2020-05-10 02:47:11 +08:00
$ ( "#result" ) . addClass ( 'hidden' ) ;
2020-06-15 02:10:59 +08:00
$ ( "#testModesNotice" ) . css ( {
'opacity' : 1 ,
// 'height': 'auto',
// 'margin-bottom': '1.25rem'
} ) ;
2020-05-15 11:09:00 +08:00
$ ( "#words" ) . css ( 'opacity' , 0 ) . removeClass ( 'hidden' ) . stop ( true , true ) . animate ( {
2020-05-10 02:47:11 +08:00
opacity : 1
} , 125 , ( ) => {
2020-05-23 01:23:38 +08:00
hideCrown ( ) ;
2020-05-15 06:19:28 +08:00
clearIntervals ( ) ;
2020-05-10 02:47:11 +08:00
$ ( "#restartTestButton" ) . css ( 'opacity' , 1 ) ;
2020-05-11 23:29:18 +08:00
if ( $ ( "#commandLineWrapper" ) . hasClass ( 'hidden' ) ) focusWords ( ) ;
2020-06-25 06:25:12 +08:00
wpmOverTimeChart . options . annotation . annotations [ 0 ] . value = "-30" ;
2020-06-05 02:37:16 +08:00
wpmOverTimeChart . update ( ) ;
2020-06-06 20:22:52 +08:00
2020-05-10 02:47:11 +08:00
// let oldHeight = $("#words").height();
// let newHeight = $("#words")
// .css("height", "fit-content")
// .css("height", "-moz-fit-content")
// .height();
// if (testMode == "words" || testMode == "custom") {
// $("#words")
// .stop(true, true)
// .css("height", oldHeight)
// .animate({ height: newHeight }, 250, () => {
// $("#words")
// .css("height", "fit-content")
// .css("height", "-moz-fit-content");
// $("#wordsInput").focus();
// updateCaretPosition();
// });
// } else if (testMode == "time") {
// $("#words")
// .stop(true, true)
// .css("height", oldHeight)
// .animate({ height: 78 }, 250, () => {
// $("#wordsInput").focus();
// updateCaretPosition();
// });
// }
} ) ;
} )
2020-05-09 08:29:47 +08:00
}
2020-05-11 07:32:10 +08:00
function focusWords ( ) {
2020-05-15 11:09:00 +08:00
if ( ! $ ( "#words" ) . hasClass ( 'hidden' ) ) $ ( "#wordsInput" ) . focus ( ) ;
2020-05-11 07:32:10 +08:00
}
2020-05-09 08:29:47 +08:00
function changeCustomText ( ) {
2020-06-16 04:43:16 +08:00
customText = prompt ( "Custom text" ) . trim ( ) ;
2020-05-19 11:03:43 +08:00
customText = customText . replace ( /[\n\r\t ]/gm , ' ' ) ;
customText = customText . replace ( / +/gm , ' ' ) ;
2020-07-01 05:23:50 +08:00
customText = customText . split ( ' ' ) ;
if ( customText . length > 10000 ) {
showNotification ( 'Custom text cannot be longer than 10000 words.' , 4000 ) ;
changeMode ( 'time' ) ;
customText = "The quick brown fox jumped over the lazy dog" . split ( ' ' ) ;
}
2020-05-26 07:31:03 +08:00
// initWords();
2020-05-09 08:29:47 +08:00
}
2020-05-11 07:32:10 +08:00
function changePage ( page ) {
2020-05-23 00:59:28 +08:00
if ( pageTransition ) {
return ;
}
2020-05-22 06:02:19 +08:00
restartTest ( ) ;
2020-05-12 07:59:12 +08:00
let activePage = $ ( ".page.active" ) ;
$ ( ".page" ) . removeClass ( 'active' ) ;
2020-05-11 07:32:10 +08:00
$ ( "#wordsInput" ) . focusout ( ) ;
if ( page == "test" || page == "" ) {
2020-05-23 00:59:28 +08:00
pageTransition = true ;
swapElements ( activePage , $ ( ".page.pageTest" ) , 250 , ( ) => {
pageTransition = false ;
focusWords ( ) ;
$ ( ".page.pageTest" ) . addClass ( 'active' ) ;
history . pushState ( '/' , null , '/' ) ;
} ) ;
2020-05-15 11:09:00 +08:00
showTestConfig ( ) ;
hideSignOutButton ( ) ;
2020-05-17 20:17:08 +08:00
restartCount = 0 ;
2020-06-09 01:12:34 +08:00
incompleteTestSeconds = 0 ;
2020-05-22 06:02:19 +08:00
restartTest ( ) ;
2020-05-11 07:32:10 +08:00
} else if ( page == "about" ) {
2020-05-23 00:59:28 +08:00
pageTransition = true ;
swapElements ( activePage , $ ( ".page.pageAbout" ) , 250 , ( ) => {
pageTransition = false ;
history . pushState ( 'about' , null , 'about' ) ;
$ ( ".page.pageAbout" ) . addClass ( 'active' ) ;
} ) ;
2020-05-15 11:09:00 +08:00
hideTestConfig ( ) ;
hideSignOutButton ( ) ;
2020-06-29 06:16:28 +08:00
} else if ( page == "settings" ) {
2020-05-23 00:59:28 +08:00
pageTransition = true ;
swapElements ( activePage , $ ( ".page.pageSettings" ) , 250 , ( ) => {
pageTransition = false ;
history . pushState ( 'settings' , null , 'settings' ) ;
$ ( ".page.pageSettings" ) . addClass ( 'active' ) ;
} ) ;
updateSettingsPage ( ) ;
2020-05-15 11:09:00 +08:00
hideTestConfig ( ) ;
hideSignOutButton ( ) ;
2020-05-11 07:32:10 +08:00
} else if ( page == "account" ) {
if ( ! firebase . auth ( ) . currentUser ) {
changePage ( "login" ) ;
} else {
2020-05-23 00:59:28 +08:00
pageTransition = true ;
swapElements ( activePage , $ ( ".page.pageAccount" ) , 250 , ( ) => {
pageTransition = false ;
history . pushState ( 'account' , null , 'account' ) ;
$ ( ".page.pageAccount" ) . addClass ( 'active' ) ;
} ) ;
2020-05-11 07:32:10 +08:00
refreshAccountPage ( ) ;
2020-05-15 11:09:00 +08:00
hideTestConfig ( ) ;
showSignOutButton ( ) ;
2020-05-11 07:32:10 +08:00
}
} else if ( page == "login" ) {
2020-05-12 07:59:12 +08:00
if ( firebase . auth ( ) . currentUser != null ) {
changePage ( 'account' ) ;
} else {
2020-05-23 00:59:28 +08:00
pageTransition = true ;
swapElements ( activePage , $ ( ".page.pageLogin" ) , 250 , ( ) => {
pageTransition = false ;
history . pushState ( 'login' , null , 'login' ) ;
$ ( ".page.pageLogin" ) . addClass ( 'active' ) ;
} ) ;
2020-05-15 11:09:00 +08:00
hideTestConfig ( ) ;
hideSignOutButton ( ) ;
2020-05-12 07:59:12 +08:00
}
2020-05-11 07:32:10 +08:00
}
2020-05-10 01:39:23 +08:00
}
2020-05-28 21:03:29 +08:00
function changeMode ( mode , nosave ) {
2020-05-11 07:32:10 +08:00
config . mode = mode ;
2020-07-03 01:39:24 +08:00
$ ( "#top .config .mode .text-button" ) . removeClass ( "active" ) ;
$ ( "#top .config .mode .text-button[mode='" + mode + "']" ) . addClass ( "active" ) ;
2020-05-11 07:32:10 +08:00
if ( config . mode == "time" ) {
$ ( "#top .config .wordCount" ) . addClass ( "hidden" ) ;
$ ( "#top .config .time" ) . removeClass ( "hidden" ) ;
$ ( "#top .config .customText" ) . addClass ( "hidden" ) ;
$ ( "#top .config .punctuationMode" ) . removeClass ( "hidden" ) ;
} else if ( config . mode == "words" ) {
$ ( "#top .config .wordCount" ) . removeClass ( "hidden" ) ;
$ ( "#top .config .time" ) . addClass ( "hidden" ) ;
$ ( "#top .config .customText" ) . addClass ( "hidden" ) ;
$ ( "#top .config .punctuationMode" ) . removeClass ( "hidden" ) ;
} else if ( config . mode == "custom" ) {
$ ( "#top .config .wordCount" ) . addClass ( "hidden" ) ;
$ ( "#top .config .time" ) . addClass ( "hidden" ) ;
$ ( "#top .config .customText" ) . removeClass ( "hidden" ) ;
$ ( "#top .config .punctuationMode" ) . addClass ( "hidden" ) ;
2020-05-11 07:07:36 +08:00
}
2020-05-28 21:03:29 +08:00
if ( ! nosave ) saveConfigToCookie ( ) ;
2020-05-11 07:07:36 +08:00
}
2020-05-11 07:32:10 +08:00
function liveWPM ( ) {
let correctWordChars = 0 ;
for ( let i = 0 ; i < inputHistory . length ; i ++ ) {
if ( inputHistory [ i ] == wordsList [ i ] ) {
//the word is correct
//+1 for space
correctWordChars += wordsList [ i ] . length + 1 ;
}
2020-05-11 07:07:36 +08:00
}
2020-05-11 07:32:10 +08:00
let testNow = Date . now ( ) ;
let testSeconds = ( testNow - testStart ) / 1000 ;
wpm = ( correctWordChars * ( 60 / testSeconds ) ) / 5 ;
return Math . round ( wpm ) ;
2020-05-11 07:07:36 +08:00
}
2020-05-31 21:31:50 +08:00
function liveRaw ( ) {
let chars = 0 ;
for ( let i = 0 ; i < inputHistory . length ; i ++ ) {
chars += inputHistory [ i ] . length + 1 ;
}
let testNow = Date . now ( ) ;
let testSeconds = ( testNow - testStart ) / 1000 ;
raw = ( chars * ( 60 / testSeconds ) ) / 5 ;
return Math . round ( raw ) ;
}
2020-05-11 07:32:10 +08:00
function updateLiveWpm ( wpm ) {
if ( ! config . showLiveWpm ) return ;
if ( wpm == 0 || ! testActive ) hideLiveWpm ( ) ;
2020-05-15 10:19:39 +08:00
// let wpmstring = wpm < 100 ? ` ${wpm}` : `${wpm}`;
$ ( "#liveWpm" ) . html ( wpm ) ;
2020-05-10 06:33:48 +08:00
}
2020-05-11 07:32:10 +08:00
function showLiveWpm ( ) {
if ( ! config . showLiveWpm ) return ;
if ( ! testActive ) return ;
$ ( "#liveWpm" ) . css ( 'opacity' , 0.25 ) ;
2020-05-10 09:04:05 +08:00
}
2020-05-11 07:32:10 +08:00
function hideLiveWpm ( ) {
$ ( "#liveWpm" ) . css ( 'opacity' , 0 ) ;
2020-05-10 06:33:48 +08:00
}
2020-05-15 11:09:00 +08:00
function swapElements ( el1 , el2 , totalDuration , callback = function ( ) { return ; } ) {
2020-05-12 07:59:12 +08:00
if (
( el1 . hasClass ( 'hidden' ) && ! el2 . hasClass ( 'hidden' ) ) ||
( ! el1 . hasClass ( 'hidden' ) && el2 . hasClass ( 'hidden' ) )
) {
//one of them is hidden and the other is visible
if ( el1 . hasClass ( "hidden" ) ) {
2020-05-23 00:59:28 +08:00
callback ( ) ;
2020-05-12 07:59:12 +08:00
return false ;
}
2020-05-23 00:59:28 +08:00
$ ( el1 ) . removeClass ( 'hidden' ) . css ( 'opacity' , 1 ) . animate ( {
2020-05-12 07:59:12 +08:00
opacity : 0
} , totalDuration / 2 , ( ) => {
$ ( el1 ) . addClass ( 'hidden' ) ;
2020-05-23 00:59:28 +08:00
$ ( el2 ) . removeClass ( 'hidden' ) . css ( 'opacity' , 0 ) . animate ( {
2020-05-12 07:59:12 +08:00
opacity : 1
} , totalDuration / 2 , ( ) => {
2020-05-15 11:09:00 +08:00
callback ( ) ;
2020-05-12 07:59:12 +08:00
} ) ;
} ) ;
2020-05-15 11:09:00 +08:00
} else if ( el1 . hasClass ( 'hidden' ) && el2 . hasClass ( 'hidden' ) ) {
2020-05-12 07:59:12 +08:00
//both are hidden, only fade in the second
2020-05-23 00:59:28 +08:00
$ ( el2 ) . removeClass ( 'hidden' ) . css ( 'opacity' , 0 ) . animate ( {
2020-05-12 07:59:12 +08:00
opacity : 1
} , totalDuration , ( ) => {
2020-05-15 11:09:00 +08:00
callback ( ) ;
2020-05-12 07:59:12 +08:00
} ) ;
2020-05-23 00:59:28 +08:00
} else {
callback ( ) ;
2020-05-12 07:59:12 +08:00
}
}
2020-05-15 06:19:28 +08:00
function clearIntervals ( ) {
timers . forEach ( timer => {
clearInterval ( timer ) ;
} )
}
2020-05-13 23:34:20 +08:00
2020-05-12 07:59:12 +08:00
function updateAccountLoginButton ( ) {
if ( firebase . auth ( ) . currentUser != null ) {
2020-07-03 01:39:24 +08:00
swapElements ( $ ( "#menu .icon-button.login" ) , $ ( "#menu .icon-button.account" ) , 250 ) ;
// $("#menu .icon-button.account").removeClass('hidden');
// $("#menu .icon-button.login").addClass('hidden');
2020-05-12 07:59:12 +08:00
} else {
2020-07-03 01:39:24 +08:00
swapElements ( $ ( "#menu .icon-button.account" ) , $ ( "#menu .icon-button.login" ) , 250 ) ;
// $("#menu .icon-button.login").removeClass('hidden');
// $("#menu .icon-button.account").addClass('hidden');
2020-05-12 07:59:12 +08:00
}
}
2020-06-09 03:35:50 +08:00
function accountIconLoading ( truefalse ) {
if ( truefalse ) {
$ ( "#top #menu .account .icon" ) . html ( '<i class="fas fa-fw fa-spin fa-circle-notch"></i>' ) ;
} else {
$ ( "#top #menu .account .icon" ) . html ( '<i class="fas fa-fw fa-user"></i>' ) ;
}
}
2020-05-27 08:00:28 +08:00
function toggleResultWordsDisplay ( ) {
if ( resultVisible ) {
2020-06-09 05:11:50 +08:00
if ( $ ( "#words" ) . stop ( true , true ) . hasClass ( 'hidden' ) ) {
2020-05-27 08:00:28 +08:00
//show
$ ( "#wordsTitle" ) . css ( 'opacity' , 1 ) . removeClass ( 'hidden' ) . slideDown ( 250 ) ;
let newHeight = $ ( "#words" ) . removeClass ( 'hidden' ) . css ( 'height' , 'auto' ) . outerHeight ( ) ;
$ ( "#words" ) . css ( {
height : 0 ,
opacity : 0
} ) . animate ( {
height : newHeight ,
opacity : 1
} , 250 ) ;
} else {
//hide
$ ( "#wordsTitle" ) . slideUp ( 250 ) ;
let oldHeight = $ ( "#words" ) . outerHeight ( ) ;
$ ( "#words" ) . removeClass ( 'hidden' ) ;
$ ( "#words" ) . css ( {
opacity : 1 ,
height : oldHeight
} ) . animate ( {
height : 0 ,
opacity : 0
} , 250 , ( ) => {
$ ( "#words" ) . addClass ( 'hidden' ) ;
} ) ;
}
}
}
2020-06-01 03:30:56 +08:00
function flipTestColors ( tf ) {
if ( tf ) {
$ ( "#words" ) . addClass ( 'flipped' ) ;
} else {
$ ( "#words" ) . removeClass ( 'flipped' ) ;
}
}
2020-06-27 07:49:05 +08:00
function applyColorfulMode ( tc ) {
2020-06-09 06:00:16 +08:00
if ( tc ) {
2020-06-27 07:49:05 +08:00
$ ( "#words" ) . addClass ( 'colorfulMode' ) ;
2020-06-09 06:00:16 +08:00
} else {
2020-06-27 07:49:05 +08:00
$ ( "#words" ) . removeClass ( 'colorfulMode' ) ;
2020-06-09 06:00:16 +08:00
}
}
2020-06-12 02:38:24 +08:00
function showEditTags ( action , id , name ) {
2020-06-12 02:19:39 +08:00
if ( action === "add" ) {
$ ( "#tagsWrapper #tagsEdit" ) . attr ( 'action' , 'add' ) ;
$ ( "#tagsWrapper #tagsEdit .title" ) . html ( 'Add new tag' ) ;
$ ( "#tagsWrapper #tagsEdit .button" ) . html ( ` <i class="fas fa-plus"></i> ` ) ;
2020-06-12 02:38:24 +08:00
$ ( "#tagsWrapper #tagsEdit input" ) . val ( '' ) ;
$ ( "#tagsWrapper #tagsEdit input" ) . removeClass ( 'hidden' ) ;
} else if ( action === "edit" ) {
$ ( "#tagsWrapper #tagsEdit" ) . attr ( 'action' , 'edit' ) ;
$ ( "#tagsWrapper #tagsEdit" ) . attr ( 'tagid' , id ) ;
$ ( "#tagsWrapper #tagsEdit .title" ) . html ( 'Edit tag name' ) ;
$ ( "#tagsWrapper #tagsEdit .button" ) . html ( ` <i class="fas fa-pen"></i> ` ) ;
$ ( "#tagsWrapper #tagsEdit input" ) . val ( name ) ;
$ ( "#tagsWrapper #tagsEdit input" ) . removeClass ( 'hidden' ) ;
} else if ( action === "remove" ) {
$ ( "#tagsWrapper #tagsEdit" ) . attr ( 'action' , 'remove' ) ;
$ ( "#tagsWrapper #tagsEdit" ) . attr ( 'tagid' , id ) ;
$ ( "#tagsWrapper #tagsEdit .title" ) . html ( 'Remove tag ' + name ) ;
$ ( "#tagsWrapper #tagsEdit .button" ) . html ( ` <i class="fas fa-check"></i> ` ) ;
$ ( "#tagsWrapper #tagsEdit input" ) . addClass ( 'hidden' ) ;
2020-06-12 02:19:39 +08:00
}
2020-06-12 02:38:24 +08:00
2020-06-12 02:19:39 +08:00
if ( $ ( "#tagsWrapper" ) . hasClass ( "hidden" ) ) {
$ ( "#tagsWrapper" )
. stop ( true , true )
. css ( "opacity" , 0 )
. removeClass ( "hidden" )
2020-06-12 02:38:24 +08:00
. animate ( { opacity : 1 } , 100 , e => {
$ ( "#tagsWrapper #tagsEdit input" ) . focus ( ) ;
} ) ;
2020-06-12 02:19:39 +08:00
}
}
function hideEditTags ( ) {
if ( ! $ ( "#tagsWrapper" ) . hasClass ( "hidden" ) ) {
2020-06-12 02:38:24 +08:00
$ ( "#tagsWrapper #tagsEdit" ) . attr ( 'action' , '' ) ;
$ ( "#tagsWrapper #tagsEdit" ) . attr ( 'tagid' , '' ) ;
2020-06-12 02:19:39 +08:00
$ ( "#tagsWrapper" )
. stop ( true , true )
. css ( "opacity" , 1 )
. animate (
{
opacity : 0
} , 100 , e => {
$ ( "#tagsWrapper" ) . addClass ( 'hidden' ) ;
} ) ;
}
}
2020-06-13 00:30:20 +08:00
function showBackgroundLoader ( ) {
$ ( "#backgroundLoader" ) . stop ( true , true ) . fadeIn ( 125 ) ;
}
function hideBackgroundLoader ( ) {
$ ( "#backgroundLoader" ) . stop ( true , true ) . fadeOut ( 125 ) ;
}
2020-06-15 01:19:56 +08:00
function updateTestModesNotice ( ) {
2020-06-24 01:17:08 +08:00
let anim = false ;
if ( $ ( ".pageTest #testModesNotice" ) . text ( ) === "" ) anim = true ;
2020-06-15 01:19:56 +08:00
$ ( ".pageTest #testModesNotice" ) . empty ( ) ;
if ( config . difficulty === "expert" ) {
$ ( ".pageTest #testModesNotice" ) . append ( ` <div><i class="fas fa-star-half-alt"></i>expert</div> ` ) ;
} else if ( config . difficulty === "master" ) {
$ ( ".pageTest #testModesNotice" ) . append ( ` <div><i class="fas fa-star"></i>master</div> ` ) ;
}
if ( config . blindMode ) {
$ ( ".pageTest #testModesNotice" ) . append ( ` <div><i class="fas fa-eye-slash"></i>blind</div> ` ) ;
}
tagsString = "" ;
2020-06-24 00:15:21 +08:00
// $.each($('.pageSettings .section.tags .tagsList .tag'), (index, tag) => {
// if($(tag).children('.active').attr('active') === 'true'){
// tagsString += $(tag).children('.title').text() + ', ';
// }
// })
try {
dbSnapshot . tags . forEach ( tag => {
if ( tag . active === true ) {
tagsString += tag . name + ', ' ;
2020-06-15 01:19:56 +08:00
}
2020-06-24 00:15:21 +08:00
} )
if ( tagsString !== "" ) {
$ ( ".pageTest #testModesNotice" ) . append ( ` <div><i class="fas fa-tag"></i> ${ tagsString . substring ( 0 , tagsString . length - 2 ) } </div> ` ) ;
}
} catch ( e ) {
2020-06-15 01:19:56 +08:00
}
2020-06-24 01:17:08 +08:00
if ( anim ) {
$ ( ".pageTest #testModesNotice" ) . css ( 'transition' , 'none' ) . css ( 'opacity' , 0 ) . animate ( {
opacity : 1
} , 125 , ( e ) => {
$ ( ".pageTest #testModesNotice" ) . css ( 'transition' , '.125s' ) ;
} ) ;
}
2020-06-15 01:19:56 +08:00
}
2020-06-12 02:19:39 +08:00
$ ( "#tagsWrapper" ) . click ( e => {
if ( $ ( e . target ) . attr ( 'id' ) === "tagsWrapper" ) {
hideEditTags ( ) ;
}
} )
$ ( "#tagsWrapper #tagsEdit .button" ) . click ( e => {
2020-06-16 06:25:28 +08:00
tagsEdit ( ) ;
} )
$ ( "#tagsWrapper #tagsEdit input" ) . keypress ( e => {
if ( e . keyCode == 13 ) {
tagsEdit ( ) ;
}
} )
function tagsEdit ( ) {
2020-06-12 02:19:39 +08:00
let action = $ ( "#tagsWrapper #tagsEdit" ) . attr ( 'action' ) ;
let inputVal = $ ( "#tagsWrapper #tagsEdit input" ) . val ( ) ;
2020-06-12 02:38:24 +08:00
let tagid = $ ( "#tagsWrapper #tagsEdit" ) . attr ( 'tagid' ) ;
2020-06-12 02:19:39 +08:00
hideEditTags ( ) ;
2020-06-12 02:38:24 +08:00
if ( action === "add" ) {
2020-06-13 00:30:20 +08:00
showBackgroundLoader ( ) ;
2020-06-12 02:38:24 +08:00
addTag ( { uid : firebase . auth ( ) . currentUser . uid , name : inputVal } ) . then ( e => {
2020-06-13 00:30:20 +08:00
hideBackgroundLoader ( ) ;
2020-06-12 05:57:48 +08:00
let status = e . data . resultCode ;
2020-06-12 02:38:24 +08:00
if ( status === 1 ) {
showNotification ( 'Tag added' , 2000 ) ;
dbSnapshot . tags . push ( {
name : inputVal ,
id : e . data . id
} )
2020-06-25 04:20:35 +08:00
updateResultEditTagsPanelButtons ( ) ;
2020-06-12 02:38:24 +08:00
updateSettingsPage ( ) ;
2020-06-12 05:31:06 +08:00
updateFilterTags ( ) ;
2020-06-12 02:38:24 +08:00
} else if ( status === - 1 ) {
showNotification ( 'Invalid tag name' , 3000 ) ;
} else if ( status < - 1 ) {
showNotification ( 'Unknown error' , 3000 ) ;
}
} )
} else if ( action === "edit" ) {
2020-06-13 00:30:20 +08:00
showBackgroundLoader ( ) ;
2020-06-12 02:38:24 +08:00
editTag ( { uid : firebase . auth ( ) . currentUser . uid , name : inputVal , tagid : tagid } ) . then ( e => {
2020-06-13 00:30:20 +08:00
hideBackgroundLoader ( ) ;
2020-06-12 05:57:48 +08:00
let status = e . data . resultCode ;
2020-06-12 02:38:24 +08:00
if ( status === 1 ) {
showNotification ( 'Tag updated' , 2000 ) ;
dbSnapshot . tags . forEach ( tag => {
if ( tag . id === tagid ) {
tag . name = inputVal ;
}
} )
2020-06-25 04:20:35 +08:00
updateResultEditTagsPanelButtons ( ) ;
2020-06-12 02:38:24 +08:00
updateSettingsPage ( ) ;
2020-06-12 05:31:06 +08:00
updateFilterTags ( ) ;
2020-06-12 02:38:24 +08:00
} else if ( status === - 1 ) {
showNotification ( 'Invalid tag name' , 3000 ) ;
} else if ( status < - 1 ) {
showNotification ( 'Unknown error' , 3000 ) ;
}
} )
} else if ( action === "remove" ) {
2020-06-13 00:30:20 +08:00
showBackgroundLoader ( ) ;
2020-06-12 02:38:24 +08:00
removeTag ( { uid : firebase . auth ( ) . currentUser . uid , tagid : tagid } ) . then ( e => {
2020-06-13 00:30:20 +08:00
hideBackgroundLoader ( ) ;
2020-06-12 05:57:48 +08:00
let status = e . data . resultCode ;
2020-06-12 02:38:24 +08:00
if ( status === 1 ) {
showNotification ( 'Tag removed' , 2000 ) ;
dbSnapshot . tags . forEach ( ( tag , index ) => {
if ( tag . id === tagid ) {
dbSnapshot . tags . splice ( index , 1 ) ;
}
} )
2020-06-25 04:20:35 +08:00
updateResultEditTagsPanelButtons ( ) ;
2020-06-12 02:38:24 +08:00
updateSettingsPage ( ) ;
2020-06-12 05:31:06 +08:00
updateFilterTags ( ) ;
2020-06-16 05:20:34 +08:00
updateActiveTags ( ) ;
2020-06-12 02:38:24 +08:00
} else if ( status < - 1 ) {
showNotification ( 'Unknown error' , 3000 ) ;
}
} )
}
2020-06-16 06:25:28 +08:00
}
2020-05-27 08:00:28 +08:00
2020-05-15 10:28:13 +08:00
$ ( document ) . on ( "click" , "#top .logo" , ( e ) => {
changePage ( 'test' ) ;
} ) ;
2020-07-03 01:39:24 +08:00
$ ( document ) . on ( "click" , "#top .config .wordCount .text-button" , ( e ) => {
2020-05-24 21:22:41 +08:00
wrd = $ ( e . currentTarget ) . attr ( 'wordCount' ) ;
if ( wrd == "custom" ) {
let newWrd = prompt ( 'Custom word amount' ) ;
2020-07-03 03:19:32 +08:00
if ( newWrd !== null && ! isNaN ( newWrd ) && newWrd > 0 && newWrd < 10000 ) {
2020-05-24 21:22:41 +08:00
changeWordCount ( newWrd ) ;
2020-07-03 03:19:32 +08:00
if ( newWrd > 2000 ) {
showNotification ( "Very long tests can cause performance issues or crash the website on some machines!" , 5000 ) ;
}
2020-05-24 21:22:41 +08:00
}
} else {
changeWordCount ( wrd ) ;
}
2020-05-26 07:31:03 +08:00
restartTest ( ) ;
2020-05-11 07:32:10 +08:00
} ) ;
2020-05-10 06:33:48 +08:00
2020-07-03 01:39:24 +08:00
$ ( document ) . on ( "click" , "#top .config .time .text-button" , ( e ) => {
2020-05-23 02:23:15 +08:00
time = $ ( e . currentTarget ) . attr ( 'timeConfig' ) ;
if ( time == "custom" ) {
let newTime = prompt ( 'Custom time in seconds' ) ;
2020-07-03 04:51:23 +08:00
if ( newTime !== null && ! isNaN ( newTime ) && newTime > 0 && newTime <= 3600 ) {
2020-05-23 02:23:15 +08:00
changeTimeConfig ( newTime ) ;
2020-07-03 03:19:32 +08:00
if ( newTime >= 1800 ) {
showNotification ( "Very long tests can cause performance issues or crash the website on some machines!" , 5000 ) ;
}
2020-05-23 02:23:15 +08:00
}
} else {
changeTimeConfig ( time ) ;
}
2020-05-26 07:31:03 +08:00
restartTest ( ) ;
2020-05-11 07:32:10 +08:00
} ) ;
2020-07-03 01:39:24 +08:00
$ ( document ) . on ( "click" , "#top .config .customText .text-button" , ( e ) => {
2020-05-11 07:32:10 +08:00
changeCustomText ( ) ;
2020-05-26 07:31:03 +08:00
restartTest ( ) ;
2020-05-11 07:32:10 +08:00
} ) ;
2020-07-03 01:39:24 +08:00
$ ( document ) . on ( "click" , "#top .config .punctuationMode .text-button" , ( e ) => {
2020-05-11 07:32:10 +08:00
togglePunctuation ( ) ;
restartTest ( ) ;
} ) ;
$ ( "#words" ) . click ( ( e ) => {
focusWords ( ) ;
} ) ;
2020-05-09 08:29:47 +08:00
2020-07-03 01:39:24 +08:00
$ ( document ) . on ( "click" , "#top .config .mode .text-button" , ( e ) => {
2020-05-09 08:29:47 +08:00
if ( $ ( e . currentTarget ) . hasClass ( "active" ) ) return ;
mode = e . currentTarget . innerHTML ;
changeMode ( mode ) ;
2020-05-10 01:39:23 +08:00
restartTest ( ) ;
2020-05-09 08:29:47 +08:00
} ) ;
2020-07-03 01:39:24 +08:00
$ ( document ) . on ( "click" , "#top #menu .icon-button" , ( e ) => {
2020-05-25 23:55:31 +08:00
if ( $ ( e . currentTarget ) . hasClass ( 'discord' ) ) return ;
2020-05-10 01:39:23 +08:00
href = $ ( e . currentTarget ) . attr ( 'href' ) ;
changePage ( href . replace ( '/' , '' ) ) ;
} )
$ ( window ) . on ( 'popstate' , ( e ) => {
2020-05-15 03:22:59 +08:00
let state = e . originalEvent . state ;
if ( state == "" || state == "/" ) {
2020-05-10 01:39:23 +08:00
// show test
changePage ( 'test' )
2020-05-15 03:22:59 +08:00
} else if ( state == "about" ) {
2020-05-10 01:39:23 +08:00
// show about
changePage ( "about" ) ;
2020-05-15 03:22:59 +08:00
} else if ( state == "account" || state == "login" ) {
2020-05-10 06:33:48 +08:00
if ( firebase . auth ( ) . currentUser ) {
changePage ( "account" ) ;
} else {
changePage ( 'login' ) ;
}
2020-05-10 01:39:23 +08:00
}
2020-05-11 07:32:10 +08:00
} ) ;
2020-05-09 08:29:47 +08:00
2020-05-15 11:09:00 +08:00
$ ( document ) . on ( "keypress" , "#restartTestButton" , ( event ) => {
2020-05-09 08:29:47 +08:00
if ( event . keyCode == 32 || event . keyCode == 13 ) {
2020-06-15 01:19:56 +08:00
if ( testActive && ! afkDetected ) {
2020-06-09 01:58:18 +08:00
let testNow = Date . now ( ) ;
let testSeconds = roundTo2 ( ( testNow - testStart ) / 1000 ) ;
incompleteTestSeconds += testSeconds ;
2020-05-17 20:17:08 +08:00
restartCount ++ ;
}
2020-05-09 08:29:47 +08:00
restartTest ( ) ;
}
} ) ;
2020-05-15 11:09:00 +08:00
$ ( document . body ) . on ( "click" , "#restartTestButton" , ( event ) => {
2020-05-09 08:29:47 +08:00
restartTest ( ) ;
} ) ;
2020-05-27 08:00:28 +08:00
$ ( document ) . on ( "keypress" , "#showWordHistoryButton" , ( event ) => {
if ( event . keyCode == 32 || event . keyCode == 13 ) {
toggleResultWordsDisplay ( ) ;
}
} ) ;
$ ( document . body ) . on ( "click" , "#showWordHistoryButton" , ( event ) => {
toggleResultWordsDisplay ( ) ;
} ) ;
2020-06-06 09:05:21 +08:00
$ ( document . body ) . on ( "click" , "#restartTestButtonWithSameWordset" , ( event ) => {
restartTest ( true ) ;
} ) ;
$ ( document ) . on ( "keypress" , "#restartTestButtonWithSameWordset" , ( event ) => {
if ( event . keyCode == 32 || event . keyCode == 13 ) {
restartTest ( true ) ;
}
} ) ;
2020-06-05 07:00:45 +08:00
$ ( document . body ) . on ( "click" , "#copyResultToClipboardButton" , ( event ) => {
copyResultToClipboard ( ) ;
} ) ;
2020-05-17 19:40:41 +08:00
$ ( document . body ) . on ( "click" , ".version" , ( event ) => {
$ ( "#versionHistoryWrapper" ) . css ( 'opacity' , 0 ) . removeClass ( 'hidden' ) . animate ( { opacity : 1 } , 125 ) ;
} ) ;
$ ( document . body ) . on ( "click" , "#versionHistoryWrapper" , ( event ) => {
$ ( "#versionHistoryWrapper" ) . css ( 'opacity' , 1 ) . animate ( { opacity : 0 } , 125 , ( ) => {
$ ( "#versionHistoryWrapper" ) . addClass ( 'hidden' ) ;
} ) ;
} ) ;
2020-05-09 08:29:47 +08:00
$ ( "#wordsInput" ) . keypress ( ( event ) => {
event . preventDefault ( ) ;
} ) ;
$ ( "#wordsInput" ) . on ( "focus" , ( event ) => {
2020-06-06 20:22:52 +08:00
showCaret ( ) ;
2020-05-09 08:29:47 +08:00
} ) ;
$ ( "#wordsInput" ) . on ( "focusout" , ( event ) => {
hideCaret ( ) ;
} ) ;
2020-05-11 07:32:10 +08:00
$ ( window ) . resize ( ( ) => {
updateCaretPosition ( ) ;
} ) ;
$ ( document ) . mousemove ( function ( event ) {
2020-05-25 05:42:43 +08:00
if ( $ ( "#top" ) . hasClass ( "focus" ) && ( event . originalEvent . movementX > 0 || event . originalEvent . movementY > 0 ) ) {
2020-05-25 05:29:16 +08:00
setFocus ( false ) ;
}
2020-05-11 07:32:10 +08:00
} ) ;
2020-05-10 01:39:23 +08:00
2020-05-11 07:32:10 +08:00
//keypresses for the test, using different method to be more responsive
2020-05-10 01:39:23 +08:00
$ ( document ) . keypress ( function ( event ) {
2020-06-04 00:10:14 +08:00
event = emulateLayout ( event ) ;
2020-05-09 08:29:47 +08:00
if ( ! $ ( "#wordsInput" ) . is ( ":focus" ) ) return ;
if ( event [ "keyCode" ] == 13 ) return ;
if ( event [ "keyCode" ] == 32 ) return ;
2020-05-16 05:25:38 +08:00
if ( event [ "keyCode" ] == 27 ) return ;
2020-05-28 20:25:45 +08:00
if ( event [ "keyCode" ] == 93 ) return ;
2020-05-11 07:07:36 +08:00
//start the test
2020-06-30 23:26:44 +08:00
if ( currentInput == "" && inputHistory . length == 0 && ! testActive ) {
2020-05-19 08:20:57 +08:00
try {
if ( firebase . auth ( ) . currentUser != null ) {
firebase . analytics ( ) . logEvent ( 'testStarted' ) ;
} else {
firebase . analytics ( ) . logEvent ( 'testStartedNoLogin' ) ;
}
} catch ( e ) {
console . log ( "Analytics unavailable" ) ;
2020-05-15 09:58:32 +08:00
}
2020-05-09 08:29:47 +08:00
testActive = true ;
testStart = Date . now ( ) ;
2020-06-22 06:51:48 +08:00
// if (config.mode == "time") {
2020-06-16 05:52:47 +08:00
restartTimer ( ) ;
2020-05-09 08:29:47 +08:00
showTimer ( ) ;
2020-06-22 06:51:48 +08:00
// }
2020-06-22 02:50:34 +08:00
updateActiveElement ( ) ;
2020-06-16 06:13:40 +08:00
updateTimer ( ) ;
2020-05-15 06:19:28 +08:00
clearIntervals ( ) ;
timers . push ( setInterval ( function ( ) {
2020-05-10 01:39:23 +08:00
time ++ ;
2020-06-22 06:51:48 +08:00
if ( config . mode === "time" ) {
updateTimer ( ) ;
}
2020-05-11 07:07:36 +08:00
let wpm = liveWPM ( ) ;
updateLiveWpm ( wpm ) ;
showLiveWpm ( ) ;
2020-05-10 01:39:23 +08:00
wpmHistory . push ( wpm ) ;
2020-05-31 21:31:50 +08:00
rawHistory . push ( liveRaw ( ) ) ;
2020-05-23 23:29:36 +08:00
keypressPerSecond . push ( currentKeypressCount ) ;
currentKeypressCount = 0 ;
2020-05-24 06:52:49 +08:00
errorsPerSecond . push ( currentErrorCount ) ;
currentErrorCount = 0 ;
2020-06-15 01:19:56 +08:00
if ( keypressPerSecond [ time - 1 ] == 0 &&
keypressPerSecond [ time - 2 ] == 0 &&
keypressPerSecond [ time - 3 ] == 0 &&
keypressPerSecond [ time - 4 ] == 0 &&
keypressPerSecond [ time - 5 ] == 0 &&
keypressPerSecond [ time - 6 ] == 0 && ! afkDetected ) {
2020-05-23 23:29:36 +08:00
showNotification ( "AFK detected" , 3000 ) ;
afkDetected = true ;
}
2020-05-10 06:33:48 +08:00
if ( config . mode == "time" ) {
2020-05-15 06:19:28 +08:00
if ( time >= config . time ) {
clearIntervals ( ) ;
hideCaret ( ) ;
testActive = false ;
2020-05-27 08:00:28 +08:00
showResult ( ) ;
2020-05-09 08:29:47 +08:00
}
2020-05-10 01:39:23 +08:00
}
2020-05-15 06:19:28 +08:00
} , 1000 ) ) ;
2020-05-09 08:29:47 +08:00
} else {
if ( ! testActive ) return ;
}
2020-05-11 07:32:10 +08:00
if ( wordsList [ currentWordIndex ] . substring ( currentInput . length , currentInput . length + 1 ) != event [ "key" ] ) {
2020-05-11 07:07:36 +08:00
accuracyStats . incorrect ++ ;
2020-05-24 06:52:49 +08:00
currentErrorCount ++ ;
2020-05-11 07:07:36 +08:00
} else {
accuracyStats . correct ++ ;
2020-05-09 08:29:47 +08:00
}
2020-05-23 23:29:36 +08:00
currentKeypressCount ++ ;
2020-05-09 08:29:47 +08:00
currentInput += event [ "key" ] ;
2020-05-31 04:30:50 +08:00
$ ( "#words .word.active" ) . attr ( 'input' , currentInput ) ;
2020-05-23 23:29:36 +08:00
setFocus ( true ) ;
2020-06-01 04:03:54 +08:00
activeWordTopBeforeJump = activeWordTop ;
2020-05-31 04:30:50 +08:00
compareInput ( currentWordIndex , currentInput , ! config . blindMode ) ;
2020-06-01 04:03:54 +08:00
let newActiveTop = $ ( "#words .word.active" ) . position ( ) . top ;
if ( activeWordTopBeforeJump != newActiveTop ) {
activeWordJumped = true ;
}
2020-05-23 23:29:36 +08:00
updateCaretPosition ( ) ;
2020-05-09 08:29:47 +08:00
} ) ;
2020-05-11 07:32:10 +08:00
//handle keyboard events
2020-05-09 08:29:47 +08:00
$ ( document ) . keydown ( ( event ) => {
2020-05-11 07:32:10 +08:00
2020-05-09 08:29:47 +08:00
2020-05-11 07:32:10 +08:00
//tab
2020-05-15 11:09:00 +08:00
2020-05-12 07:59:12 +08:00
if ( event [ "keyCode" ] == 9 ) {
2020-06-30 04:16:01 +08:00
if ( config . quickTab ) {
2020-05-10 02:47:11 +08:00
event . preventDefault ( ) ;
2020-06-30 04:16:01 +08:00
if ( $ ( ".pageTest" ) . hasClass ( "active" ) ) {
if ( testActive && ! afkDetected ) {
let testNow = Date . now ( ) ;
let testSeconds = roundTo2 ( ( testNow - testStart ) / 1000 ) ;
incompleteTestSeconds += testSeconds ;
restartCount ++ ;
}
restartTest ( ) ;
} else {
changePage ( 'test' ) ;
2020-05-17 20:17:08 +08:00
}
2020-05-10 02:47:11 +08:00
}
}
2020-05-11 07:32:10 +08:00
//only for the typing test
2020-05-09 08:29:47 +08:00
if ( $ ( "#wordsInput" ) . is ( ":focus" ) ) {
2020-05-11 07:32:10 +08:00
//backspace
2020-05-09 08:29:47 +08:00
if ( event [ "keyCode" ] == 8 ) {
event . preventDefault ( ) ;
if ( ! testActive ) return ;
if ( currentInput == "" && inputHistory . length > 0 ) {
if (
2020-05-23 02:39:35 +08:00
( inputHistory [ currentWordIndex - 1 ] == wordsList [ currentWordIndex - 1 ] && ! config . freedomMode ) || $ ( $ ( ".word" ) [ currentWordIndex - 1 ] ) . hasClass ( "hidden" )
2020-05-09 08:29:47 +08:00
) {
return ;
} else {
2020-06-16 05:15:10 +08:00
if ( config . maxConfidence ) return ;
2020-05-09 08:29:47 +08:00
if ( event [ "ctrlKey" ] || event [ "altKey" ] ) {
currentInput = "" ;
inputHistory . pop ( ) ;
} else {
currentInput = inputHistory . pop ( ) ;
}
currentWordIndex -- ;
updateActiveElement ( ) ;
2020-05-31 04:30:50 +08:00
compareInput ( currentWordIndex , currentInput , ! config . blindMode ) ;
2020-05-09 08:29:47 +08:00
}
} else {
// if ($($(".word")[currentWordIndex - 1]).hasClass("hidden")) {
// return;
// }
if ( event [ "ctrlKey" ] ) {
currentInput = "" ;
} else {
currentInput = currentInput . substring ( 0 , currentInput . length - 1 ) ;
}
2020-05-31 04:30:50 +08:00
compareInput ( currentWordIndex , currentInput , ! config . blindMode ) ;
2020-05-09 08:29:47 +08:00
}
2020-06-25 05:23:59 +08:00
// currentKeypressCount++;
2020-05-09 08:29:47 +08:00
updateCaretPosition ( ) ;
}
//space
if ( event [ "keyCode" ] == 32 ) {
if ( ! testActive ) return ;
if ( currentInput == "" ) return ;
2020-05-25 23:50:50 +08:00
event . preventDefault ( ) ;
2020-05-09 08:29:47 +08:00
let currentWord = wordsList [ currentWordIndex ] ;
2020-05-10 06:33:48 +08:00
if ( config . mode == "time" ) {
2020-06-21 09:33:44 +08:00
let currentTop = Math . floor ( $ ( $ ( "#words .word" ) [ currentWordIndex ] ) . position ( ) . top ) ;
let nextTop = Math . floor ( $ ( $ ( "#words .word" ) [ currentWordIndex + 1 ] ) . position ( ) . top ) ;
2020-06-01 04:03:54 +08:00
if ( nextTop > currentTop || activeWordJumped ) {
2020-05-09 08:29:47 +08:00
//last word of the line
2020-05-22 23:19:09 +08:00
if ( currentTestLine > 0 ) {
2020-06-01 04:03:54 +08:00
let hideBound = currentTop ;
if ( activeWordJumped ) {
hideBound = activeWordTopBeforeJump ;
}
activeWordJumped = false ;
2020-05-22 23:19:09 +08:00
let toHide = [ ] ;
2020-06-21 09:33:44 +08:00
let wordElements = $ ( "#words .word" ) ;
2020-05-22 23:19:09 +08:00
for ( let i = 0 ; i < currentWordIndex + 1 ; i ++ ) {
2020-06-21 09:33:44 +08:00
if ( $ ( wordElements [ i ] ) . hasClass ( 'hidden' ) ) continue ;
let forWordTop = Math . floor ( $ ( wordElements [ i ] ) . position ( ) . top ) ;
2020-06-01 04:03:54 +08:00
if ( forWordTop < hideBound ) {
2020-05-22 23:19:09 +08:00
// $($("#words .word")[i]).addClass("hidden");
toHide . push ( $ ( $ ( "#words .word" ) [ i ] ) ) ;
}
}
toHide . forEach ( el => el . addClass ( 'hidden' ) ) ;
2020-05-09 08:29:47 +08:00
}
2020-05-22 23:19:09 +08:00
currentTestLine ++ ;
2020-05-09 08:29:47 +08:00
}
}
2020-05-31 22:01:38 +08:00
if ( config . blindMode ) $ ( "#words .word.active letter" ) . addClass ( 'correct' ) ;
2020-05-09 08:29:47 +08:00
if ( currentWord == currentInput ) {
inputHistory . push ( currentInput ) ;
currentInput = "" ;
currentWordIndex ++ ;
updateActiveElement ( ) ;
updateCaretPosition ( ) ;
2020-06-25 05:21:30 +08:00
currentKeypressCount ++ ;
2020-05-09 08:29:47 +08:00
} else {
inputHistory . push ( currentInput ) ;
2020-05-31 04:30:50 +08:00
highlightBadWord ( currentWordIndex , ! config . blindMode )
2020-05-09 08:29:47 +08:00
currentInput = "" ;
currentWordIndex ++ ;
2020-05-29 02:01:44 +08:00
if ( currentWordIndex == wordsList . length ) {
2020-05-11 07:32:10 +08:00
showResult ( ) ;
2020-05-09 08:29:47 +08:00
return ;
2020-05-29 07:26:03 +08:00
} else if ( config . difficulty == "expert" || config . difficulty == "master" ) {
2020-05-29 02:01:44 +08:00
showResult ( true ) ;
2020-06-15 01:19:56 +08:00
if ( ! afkDetected ) {
let testNow = Date . now ( ) ;
let testSeconds = roundTo2 ( ( testNow - testStart ) / 1000 ) ;
incompleteTestSeconds += testSeconds ;
restartCount ++ ;
}
2020-05-29 02:01:44 +08:00
return ;
2020-05-09 08:29:47 +08:00
}
updateActiveElement ( ) ;
updateCaretPosition ( ) ;
2020-06-25 05:21:30 +08:00
currentKeypressCount ++ ;
2020-05-09 08:29:47 +08:00
}
2020-06-22 06:51:48 +08:00
if ( config . mode === "words" || config . mode === "custom" ) {
updateTimer ( ) ;
}
2020-05-10 06:33:48 +08:00
if ( config . mode == "time" ) {
2020-05-09 08:29:47 +08:00
addWord ( ) ;
}
}
}
} ) ;
2020-05-11 07:32:10 +08:00
2020-05-11 23:29:18 +08:00
loadConfigFromCookie ( ) ;
2020-05-17 19:40:41 +08:00
getReleasesFromGitHub ( ) ;
2020-05-11 23:29:18 +08:00
2020-05-19 19:21:27 +08:00
if ( firebase . app ( ) . options . projectId === "monkey-type-dev-67af4" ) {
$ ( "#top .logo .bottom" ) . text ( "monkey-dev" ) ;
2020-06-27 08:13:42 +08:00
$ ( "head title" ) . text ( "Monkey Dev" ) ;
$ ( 'body' ) . append ( `
< div class = "devIndicator tr" >
DEV
< / d i v >
< div class = "devIndicator bl" >
DEV
< / d i v >
` );
2020-05-19 19:21:27 +08:00
}
2020-05-31 04:43:15 +08:00
2020-05-19 19:21:27 +08:00
if ( window . location . hostname === "localhost" ) {
2020-05-31 21:31:50 +08:00
window . onerror = function ( error ) {
this . showNotification ( error , 3000 ) ;
} ;
2020-05-19 19:21:27 +08:00
$ ( "#top .logo .top" ) . text ( "localhost" ) ;
$ ( "head title" ) . text ( $ ( "head title" ) . text ( ) + " (localhost)" ) ;
2020-06-06 01:17:59 +08:00
firebase . functions ( ) . useFunctionsEmulator ( "http://localhost:5001" ) ;
2020-06-27 08:13:42 +08:00
$ ( 'body' ) . append ( ` <div class="devIndicator tl">
local
< / d i v >
< div class = "devIndicator br" >
local
< / d i v > ` ) ;
2020-05-19 19:21:27 +08:00
}
2020-05-31 03:59:42 +08:00
$ ( document ) . on ( 'mouseenter' , '#words .word' , e => {
2020-05-31 04:43:15 +08:00
if ( resultVisible ) {
let input = $ ( e . currentTarget ) . attr ( 'input' ) ;
if ( input != undefined ) $ ( e . currentTarget ) . append ( ` <div class="wordInputAfter"> ${ input } </div> ` ) ;
}
2020-05-31 03:59:42 +08:00
} )
$ ( document ) . on ( 'mouseleave' , '#words .word' , e => {
$ ( '.wordInputAfter' ) . remove ( ) ;
} )
2020-05-11 07:32:10 +08:00
$ ( document ) . ready ( ( ) => {
2020-05-21 06:46:25 +08:00
updateFavicon ( 32 , 14 ) ;
2020-05-15 11:23:48 +08:00
$ ( 'body' ) . css ( 'transition' , '.25s' ) ;
2020-05-26 07:31:03 +08:00
restartTest ( ) ;
2020-05-11 07:32:10 +08:00
if ( config . quickTab ) {
2020-05-12 07:59:12 +08:00
$ ( "#restartTestButton" ) . addClass ( 'hidden' ) ;
2020-05-11 07:32:10 +08:00
}
$ ( "#centerContent" ) . css ( "opacity" , "0" ) . removeClass ( "hidden" ) . stop ( true , true ) . animate ( { opacity : 1 } , 250 ) ;
2020-06-24 01:58:53 +08:00
if ( window . location . pathname === '/account' ) {
history . replaceState ( '/' , null , '/' ) ;
} else if ( window . location . pathname !== '/' ) {
let page = window . location . pathname . replace ( '/' , '' ) ;
changePage ( page ) ;
}
2020-05-11 07:32:10 +08:00
} ) ;
2020-05-11 23:29:18 +08:00
let ctx = $ ( "#wpmChart" ) ;
let wpmOverTimeChart = new Chart ( ctx , {
2020-05-11 07:32:10 +08:00
type : 'line' ,
data : {
labels : [ ] ,
datasets : [ {
label : "wpm" ,
data : [ ] ,
2020-05-11 23:29:18 +08:00
// backgroundColor: 'rgba(255, 255, 255, 0.25)',
borderColor : 'rgba(125, 125, 125, 1)' ,
2020-05-24 06:52:49 +08:00
borderWidth : 2 ,
2020-05-27 02:59:58 +08:00
yAxisID : "wpm" ,
2020-05-31 21:54:54 +08:00
order : 2 ,
radius : 2
} ,
{
label : "raw" ,
data : [ ] ,
// backgroundColor: 'rgba(255, 255, 255, 0.25)',
borderColor : 'rgba(125, 125, 125, 1)' ,
borderWidth : 2 ,
yAxisID : "raw" ,
order : 3 ,
radius : 2
2020-05-24 06:52:49 +08:00
} ,
{
label : "errors" ,
data : [ ] ,
// backgroundColor: 'rgba(255, 255, 255, 0.25)',
borderColor : 'rgba(255, 125, 125, 1)' ,
borderWidth : 2 ,
2020-05-31 21:54:54 +08:00
order : 1 ,
2020-05-27 02:59:58 +08:00
yAxisID : "error" ,
// barPercentage: 0.1,
maxBarThickness : 10 ,
type : "scatter" ,
pointStyle : "crossRot" ,
2020-05-27 05:23:57 +08:00
radius : function ( context ) {
var index = context . dataIndex ;
var value = context . dataset . data [ index ] ;
2020-05-31 21:54:54 +08:00
return value . y <= 0 ? 0 : 3
2020-06-07 09:30:49 +08:00
} ,
pointHoverRadius : function ( context ) {
var index = context . dataIndex ;
var value = context . dataset . data [ index ] ;
return value . y <= 0 ? 0 : 5
} ,
2020-05-11 07:32:10 +08:00
} ] ,
} ,
options : {
2020-05-12 07:59:12 +08:00
tooltips : {
titleFontFamily : "Roboto Mono" ,
2020-05-24 21:05:40 +08:00
bodyFontFamily : "Roboto Mono" ,
2020-06-07 09:30:49 +08:00
mode : 'index' ,
2020-05-24 21:05:40 +08:00
intersect : false
2020-05-12 07:59:12 +08:00
} ,
2020-05-11 07:32:10 +08:00
legend : {
display : false ,
labels : {
defaultFontFamily : "Roboto Mono"
}
} ,
responsive : true ,
maintainAspectRatio : false ,
2020-06-07 09:30:49 +08:00
// hover: {
// mode: 'x',
// intersect: false
// },
2020-05-11 07:32:10 +08:00
scales : {
2020-05-15 11:09:00 +08:00
2020-05-11 07:32:10 +08:00
xAxes : [ {
ticks : {
2020-06-07 09:30:49 +08:00
fontFamily : "Roboto Mono" ,
autoSkip : true ,
2020-06-09 01:58:18 +08:00
autoSkipPadding : 40
2020-05-11 07:32:10 +08:00
} ,
display : true ,
scaleLabel : {
2020-05-24 06:52:49 +08:00
display : true ,
labelString : 'Seconds' ,
fontFamily : "Roboto Mono"
2020-05-11 07:32:10 +08:00
}
} ] ,
yAxes : [ {
2020-05-24 06:52:49 +08:00
id : "wpm" ,
2020-05-11 07:32:10 +08:00
display : true ,
scaleLabel : {
2020-05-24 06:52:49 +08:00
display : true ,
labelString : 'Words per Minute' ,
fontFamily : 'Roboto Mono'
2020-05-11 23:29:18 +08:00
} ,
ticks : {
2020-05-16 00:45:45 +08:00
fontFamily : 'Roboto Mono' ,
2020-06-05 02:37:16 +08:00
beginAtZero : true ,
min : 0 ,
2020-06-07 09:30:49 +08:00
autoSkip : true ,
2020-06-09 01:58:18 +08:00
autoSkipPadding : 40
2020-05-27 02:59:58 +08:00
} ,
gridLines : {
display : false
2020-05-11 07:32:10 +08:00
}
2020-05-24 06:52:49 +08:00
} ,
2020-05-31 21:54:54 +08:00
{
id : "raw" ,
display : false ,
scaleLabel : {
display : true ,
labelString : 'Raw Words per Minute' ,
fontFamily : 'Roboto Mono'
} ,
ticks : {
fontFamily : 'Roboto Mono' ,
2020-06-05 02:37:16 +08:00
beginAtZero : true ,
2020-06-07 09:30:49 +08:00
min : 0 ,
autoSkip : true ,
2020-06-09 01:58:18 +08:00
autoSkipPadding : 40
2020-05-31 21:54:54 +08:00
} ,
gridLines : {
display : false
}
} ,
2020-05-24 06:52:49 +08:00
{
id : "error" ,
display : true ,
2020-05-27 02:59:58 +08:00
position : 'right' ,
2020-05-24 06:52:49 +08:00
scaleLabel : {
display : true ,
labelString : 'Errors' ,
fontFamily : 'Roboto Mono'
} ,
ticks : {
precision : 0 ,
fontFamily : 'Roboto Mono' ,
2020-06-07 09:30:49 +08:00
beginAtZero : true ,
autoSkip : true ,
2020-06-09 01:58:18 +08:00
autoSkipPadding : 40
2020-05-24 09:02:31 +08:00
} ,
gridLines : {
2020-05-27 02:59:58 +08:00
display : true
2020-05-24 06:52:49 +08:00
}
}
]
2020-06-05 02:37:16 +08:00
} ,
annotation : {
annotations : [ {
enabled : false ,
type : 'line' ,
mode : 'horizontal' ,
scaleID : 'wpm' ,
2020-06-25 06:25:12 +08:00
value : '-30' ,
2020-06-05 02:37:16 +08:00
borderColor : 'red' ,
borderWidth : 1 ,
borderDash : [ 2 , 2 ] ,
label : {
// Background color of label, default below
backgroundColor : 'blue' ,
fontFamily : "Roboto Mono" ,
// Font size of text, inherits from global
fontSize : 11 ,
// Font style of text, default below
fontStyle : "normal" ,
// Font color of text, default below
fontColor : "#fff" ,
// Padding of label to add left/right, default below
xPadding : 6 ,
// Padding of label to add top/bottom, default below
yPadding : 6 ,
// Radius of label rectangle, default below
cornerRadius : 3 ,
// Anchor position of label on line, can be one of: top, bottom, left, right, center. Default below.
position : "center" ,
// Whether the label is enabled and should be displayed
enabled : true ,
// Text to display in label - default is null. Provide an array to display values on a new line
content : "PB" ,
2020-06-25 06:25:12 +08:00
}
2020-06-05 02:37:16 +08:00
} ]
2020-05-11 07:32:10 +08:00
}
}
} ) ;