monkeytype/public/js/script.js
2020-05-31 20:30:56 +01:00

1535 lines
No EOL
42 KiB
JavaScript

let wordsList = [];
let currentWordIndex = 0;
let inputHistory = [];
let currentInput = "";
let time = 0;
let timers = [];
let testActive = false;
let testStart, testEnd;
let wpmHistory = [];
let rawHistory = [];
let restartCount = 0;
let currentTestLine = 0;
let pageTransition = false;
let keypressPerSecond = [];
let currentKeypressCount = 0;
let afkDetected = false;
let errorsPerSecond = [];
let currentErrorCount = 0;
let resultVisible = false;
let accuracyStats = {
correct: 0,
incorrect: 0
}
let customText = "The quick brown fox jumps over the lazy dog";
function showNotification(text, time) {
let noti = $(".notification");
noti.text(text);
noti.css('top', `-${noti.outerHeight()}px`);
noti.stop(true, true).animate({
top: "1rem"
}, 250, 'swing', () => {
noti.stop(true, true).animate({
opacity: 1
}, time, () => {
noti.stop(true, true).animate({
top: `-${noti.outerHeight()}px`
}, 250, 'swing');
})
});
}
function test() {
$("#resultScreenshot").removeClass("hidden");
html2canvas($("#resultScreenshot"), {
onclone: function(clonedDoc) {
clonedDoc.getElementById("resultScreenshot").style.display = "block";
}
}).then((canvas) => {
$("#resultScreenshot").removeClass("hidden");
document.body.appendChild(canvas);
});
}
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}</div>
<div class="date">${moment(release.published_at).format('DD MMM YYYY')}</div>
<div class="body">${release.body.replace(/\r\n/g, '<br>')}</div>
</div>
`);
}
})
})
}
function getLastChar(word) {
return word.charAt(word.length - 1);
}
function setFocus(foc) {
if (foc) {
// focus = true;
stopCaretAnimation();
$("#top").addClass("focus");
$("#bottom").addClass("focus");
$("body").css("cursor", "none");
} else {
startCaretAnimation();
$("#top").removeClass("focus");
$("#bottom").removeClass("focus");
$("body").css("cursor", "default");
}
}
function capitalizeFirstLetter(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function roundedToFixed(float, digits){
let rounded = Math.pow(10, digits);
return (Math.round(float * rounded) / rounded).toFixed(digits);
}
function initWords() {
testActive = false;
wordsList = [];
currentWordIndex = 0;
accuracyStats = {
correct: 0,
incorrect: 0
}
inputHistory = [];
currentInput = "";
let language = words[config.language];
if (language == undefined || language == []) {
config.language = "english";
language = words[config.language];
}
if (config.mode == "time" || config.mode == "words") {
let wordsBound = config.mode == "time" ? 50 : config.words;
for (let i = 0; i < wordsBound; i++) {
randomWord = language[Math.floor(Math.random() * language.length)];
previousWord = wordsList[i - 1];
while (randomWord == previousWord || (!config.punctuation && randomWord == "I") || randomWord.indexOf(' ') > -1) {
randomWord = language[Math.floor(Math.random() * language.length)];
}
if (config.punctuation && config.mode != "custom"){
randomWord = punctuateWord(previousWord, randomWord, i, wordsBound);
}
wordsList.push(randomWord);
}
} else if (config.mode == "custom") {
let w = customText.split(" ");
for (let i = 0; i < w.length; i++) {
wordsList.push(w[i]);
}
}
showWords();
}
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 += "!";
}
} 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;
}
function addWord() {
let language = words[config.language];
let randomWord = language[Math.floor(Math.random() * language.length)];
previousWord = wordsList[wordsList.length - 1];
previousWordStripped = previousWord.replace(/[.?!":\-,]/g,'').toLowerCase();
while (previousWordStripped == randomWord || randomWord.indexOf(' ') > -1 || (!config.punctuation && randomWord == "I")) {
randomWord = language[Math.floor(Math.random() * language.length)];
}
if (config.punctuation && config.mode != "custom"){
randomWord = punctuateWord(previousWord, randomWord, wordsList.length, 0)
}
wordsList.push(randomWord);
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();
if (config.mode == "words" || config.mode == "custom") {
$("#words").css("height", "auto");
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);
}
} else if (config.mode == "time") {
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);
}
$("#words").removeClass('hidden');
const wordHeight = $($(".word")[0]).outerHeight(true);
$("#words").css("height", wordHeight * 3 + 'px').css("overflow", "hidden");
}
updateActiveElement();
updateCaretPosition();
}
function updateActiveElement() {
$("#words .word").removeClass("active");
$($("#words .word")[currentWordIndex])
.addClass("active")
.removeClass("error");
}
function compareInput(wrdIndex,input,showError) {
$($('#words .word')[wrdIndex]).empty();
let ret = "";
let currentWord = wordsList[wrdIndex];
for (let i = 0; i < input.length; i++) {
if (currentWord[i] == input[i]) {
ret += '<letter class="correct">' + currentWord[i] + "</letter>";
// $(letterElems[i]).removeClass('incorrect').addClass('correct');
} else {
if(config.difficulty == "master"){
if(!resultVisible) showResult(true);
restartCount++;
}
if(!showError){
if (currentWord[i] == undefined) {
// ret += '<letter class="correct">' + input[i] + "</letter>";
} else {
ret += '<letter class="correct">' + currentWord[i] + "</letter>";
}
}else{
if (currentWord[i] == undefined) {
ret += '<letter class="incorrect extra">' + input[i] + "</letter>";
} else {
ret += '<letter class="incorrect">' + currentWord[i] + "</letter>";
}
}
}
}
if (input.length < currentWord.length) {
for (let i = input.length; i < currentWord.length; i++) {
ret += "<letter>" + currentWord[i] + "</letter>";
}
}
$($('#words .word')[wrdIndex]).html(ret);
if ((currentWord == input || (config.quickEnd && currentWord.length == input.length)) && wrdIndex == wordsList.length - 1) {
inputHistory.push(input);
currentInput = "";
if(!resultVisible) showResult();
}
// liveWPM()
}
function highlightBadWord(index,showError) {
if(!showError) return;
$($("#words .word")[index]).addClass("error");
}
function showTimer() {
if (!config.showTimerBar) return;
$("#timerWrapper").css("opacity", 1);
}
function hideTimer() {
$("#timerWrapper").css("opacity", 0);
}
function updateTimerBar() {
let percent = ((time + 1) / config.time) * 100;
$("#timer")
.stop(true, true)
.css("width", percent + "vw");
}
function hideCaret() {
$("#caret").addClass("hidden");
}
function showCaret() {
if($("#result").hasClass('hidden')){
updateCaretPosition();
$("#caret").removeClass("hidden");
startCaretAnimation();
}
}
function stopCaretAnimation() {
$("#caret").css("animation-name", "none");
$("#caret").css("opacity", "1");
}
function startCaretAnimation() {
$("#caret").css("animation-name", "caretFlash");
}
function updateCaretPosition() {
if ($("#words").hasClass('hidden')) return;
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]);
if(currentLetter.length == 0) return;
let currentLetterPos = currentLetter.position();
let letterHeight = currentLetter.height();
let newTop = 0;
let newLeft = 0;
newTop = currentLetterPos.top - letterHeight / 4;
if (inputLen == 0) {
// caret.css({
// top: currentLetterPos.top - letterHeight / 4,
// left: currentLetterPos.left - caret.width() / 2
// });
newLeft = currentLetterPos.left - caret.width() / 2;
} else {
// caret.css({
// top: currentLetterPos.top - letterHeight / 4,
// left: currentLetterPos.left + currentLetter.width() - caret.width() / 2
// });
newLeft = currentLetterPos.left + currentLetter.width() - caret.width() / 2;
}
let duration = 0;
// if (config.smoothCaret && Math.round(caret.position().top) == Math.round(newTop)) {
// duration = 100;
// }
if (config.smoothCaret) {
duration = 100;
if (Math.round(caret.position().top) != Math.round(newTop)) {
caret.css("top", newTop);
duration = 10;
}
}
caret.stop(true, true).animate({
top: newTop,
left: newLeft
}, duration)
let browserHeight = window.innerHeight;
let middlePos = (browserHeight / 2) - $("#caret").outerHeight()/2;
let contentHeight = document.body.scrollHeight;
if (newTop >= middlePos && contentHeight > browserHeight) {
window.scrollTo({
left: 0,
top: newTop - middlePos,
behavior: 'smooth'
})
}
}
function countChars() {
let correctWordChars = 0;
let correctChars = 0;
let incorrectChars = 0;
let extraChars = 0;
let missedChars = 0;
let spaces = 0;
for (let i = 0; i < inputHistory.length; i++) {
if (inputHistory[i] == wordsList[i]) {
//the word is correct
correctWordChars += wordsList[i].length;
correctChars += wordsList[i].length;
} else if (inputHistory[i].length >= wordsList[i].length) {
//too many chars
for (let c = 0; c < inputHistory[i].length; c++) {
if (c < wordsList[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
extraChars++;
}
}
} else {
//not enough chars
for (let c = 0; c < wordsList[i].length; c++) {
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++;
}
}
}
if(i < inputHistory.length-1){
spaces++;
}
}
return {
spaces: spaces,
correctWordChars: correctWordChars,
allCorrectChars: correctChars,
incorrectChars: incorrectChars,
extraChars: extraChars,
missedChars: missedChars
}
}
function roundTo2(num){
return Math.round((num + Number.EPSILON) * 100) / 100
}
function calculateStats() {
if (config.mode == "words" && config.difficulty == "normal") {
if (inputHistory.length != wordsList.length) return;
}
let chars = countChars();
let testNow = Date.now();
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);
return {
wpm: wpm,
wpmRaw: wpmraw,
acc: acc,
correctChars: chars.correctWordChars,
incorrectChars: chars.incorrectChars + chars.extraChars + chars.missedChars,
time: testSeconds,
spaces: chars.spaces
};
}
function hideCrown() {
$("#result .stats .wpm .crownWrapper").css('opacity', 0);
}
function showCrown() {
$("#result .stats .wpm .crownWrapper").delay(275).animate({
opacity: 1
}, 250,"easeOutCubic");
}
function showResult(difficultyFailed = false) {
testEnd = Date.now();
testActive = false;
setFocus(false);
hideCaret();
hideLiveWpm();
testInvalid = false;
let stats = calculateStats();
if(stats === undefined){
stats = {
wpm: 0,
wpmRaw: 0,
acc: 0,
correctChars: 0,
incorrectChars: 0,
time: 0,
spaces: 0
}
}
clearIntervals();
let testtime = roundedToFixed(stats.time,1);
$("#result .stats .wpm .bottom").text(Math.round(stats.wpm));
$("#result .stats .raw .bottom").text(Math.round(stats.wpmRaw));
$("#result .stats .acc .bottom").text(Math.round(stats.acc) + "%");
$("#result .stats .key .bottom").text(stats.correctChars + stats.spaces + "/" + stats.incorrectChars);
$("#result .stats .time .bottom").text(testtime+'s');
setTimeout(function() {
$("#showWordHistoryButton").removeClass('hidden').css('opacity',1);
}, 125);
let mode2 = "";
if (config.mode == "time") {
mode2 = config.time;
// $("#result .stats .time").addClass('hidden');
} else if (config.mode == "words") {
mode2 = config.words;
// $("#result .stats .time").removeClass('hidden');
// $("#result .stats .time .bottom").text(roundedToFixed(stats.time,1)+'s');
}
if(difficultyFailed){
showNotification("Test failed",3000);
}else if(afkDetected){
showNotification("Test invalid - AFK detected",3000);
}else{
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,
difficulty: config.difficulty,
testDuration: testtime,
blindMode: config.blindMode,
theme: config.theme
};
if(config.difficulty == "normal" || ((config.difficulty == "master" || config.difficulty == "expert") && !difficultyFailed)){
restartCount = 0;
}
if (stats.wpm > 0 && stats.wpm < 350 && stats.acc > 50 && stats.acc <= 100) {
if (firebase.auth().currentUser != null) {
db_getUserHighestWpm(config.mode, mode2, config.punctuation, config.language, config.difficulty).then(data => {
// console.log(`highest wpm for this mode is ${data}, current is ${stats.wpm}`);
if (data < stats.wpm) {
hideCrown();
showCrown();
}
completedEvent.uid = firebase.auth().currentUser.uid;
try{
firebase.analytics().logEvent('testCompleted', completedEvent);
}catch(e){
console.log("Analytics unavailable");
}
db_testCompleted(completedEvent);
dbSnapshot.unshift(completedEvent);
});
$("#result .loginTip").addClass('hidden');
} else {
try{
firebase.analytics().logEvent('testCompletedNoLogin', completedEvent);
}catch(e){
console.log("Analytics unavailable");
}
$("#result .loginTip").removeClass('hidden');
// 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");
}
}
}
let testType = "";
testType += config.mode;
if (config.mode == "time") {
testType += " " + config.time
} else if (config.mode == "words") {
testType += " " + config.words
}
if(config.mode != "custom"){
testType += "<br>" + config.language.replace('_', ' ');
}
if (config.punctuation) {
testType += "<br>punctuation"
}
if (config.blindMode) {
testType += "<br>blind"
}
if (config.difficulty == "expert") {
testType += "<br>expert";
}else if(config.difficulty == "master"){
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"
}
if(otherText == ""){
$("#result .stats .info").addClass('hidden');
}else{
$("#result .stats .info").removeClass('hidden');
otherText = otherText.substring(4);
$("#result .stats .info .bottom").html(otherText);
}
let labels = [];
for (let i = 1; i <= wpmHistory.length; i++) {
labels.push(i.toString());
}
let mainColor = getComputedStyle(document.body).getPropertyValue('--main-color').replace(' ', '');
let subColor = getComputedStyle(document.body).getPropertyValue('--sub-color').replace(' ', '');
wpmOverTimeChart.options.scales.xAxes[0].ticks.minor.fontColor = subColor;
wpmOverTimeChart.options.scales.xAxes[0].scaleLabel.fontColor = subColor;
wpmOverTimeChart.options.scales.yAxes[0].ticks.minor.fontColor = subColor;
wpmOverTimeChart.options.scales.yAxes[2].ticks.minor.fontColor = subColor;
wpmOverTimeChart.options.scales.yAxes[0].scaleLabel.fontColor = subColor;
wpmOverTimeChart.options.scales.yAxes[2].scaleLabel.fontColor = subColor;
wpmOverTimeChart.data.labels = labels;
wpmOverTimeChart.data.datasets[0].borderColor = mainColor;
wpmOverTimeChart.data.datasets[0].data = wpmHistory;
wpmOverTimeChart.data.datasets[1].borderColor = subColor;
wpmOverTimeChart.data.datasets[1].data = rawHistory;
let maxVal = 0;
rawHistory.forEach(raw =>{
if(raw >= maxVal){
maxVal = raw;
}
})
wpmOverTimeChart.options.scales.yAxes[0].ticks.max = maxVal;
wpmOverTimeChart.options.scales.yAxes[1].ticks.max = maxVal;
let errorsNoZero = [];
for(let i = 0; i < errorsPerSecond.length; i++){
// if(errorsPerSecond[i] != 0){
errorsNoZero.push({
x: i+1,
y: errorsPerSecond[i]
});
// }
}
wpmOverTimeChart.data.datasets[2].data = errorsNoZero;
wpmOverTimeChart.update({ duration: 0 });
swapElements($("#words"),$("#result"),250, () => {
resultVisible=true;
if(config.blindMode){
$.each($('#words .word'),(i,word)=>{
let input = inputHistory[i];
if(input == undefined) input = currentInput;
compareInput(i,input,true);
if(inputHistory[i] != wordsList[i]){
highlightBadWord(i,true);
}
})
}
let remove = false;
$.each($('#words .word'),(i,obj)=>{
if(remove){
$(obj).remove();
}else{
$(obj).removeClass('hidden');
if($(obj).hasClass('active')) remove = true;
}
});
});
}
function restartTest() {
clearIntervals();
time = 0;
afkDetected = false;
wpmHistory = [];
rawHistory = [];
setFocus(false);
hideCaret();
testActive = false;
hideLiveWpm();
keypressPerSecond = [];
currentKeypressCount = 0;
errorsPerSecond = [];
currentErrorCount = 0;
currentTestLine = 0;
let el = null;
if(resultVisible){
//results are being displayed
el = $("#result");
}else{
//words are being displayed
el = $("#words");
}
if(resultVisible){
$("#words").stop(true, true).animate({
opacity: 0
}, 125);
$("#wordsTitle").stop(true,true).animate({
opacity: 0
}, 125, ()=>{
$("#wordsTitle").slideUp(0);
});
$("#showWordHistoryButton").stop(true,true).animate({
opacity: 0
}, 125, ()=>{
$("#showWordHistoryButton").addClass('hidden');
});
}
resultVisible = false;
el.stop(true, true).animate({
opacity: 0
}, 125, () => {
initWords();
$("#result").addClass('hidden');
$("#words").css('opacity', 0).removeClass('hidden').stop(true, true).animate({
opacity: 1
}, 125, () => {
hideCrown();
clearIntervals();
$("#restartTestButton").css('opacity', 1);
if ($("#commandLineWrapper").hasClass('hidden')) focusWords();
hideTimer();
setTimeout(function() {
$("#timer")
.css("transition", "none")
.css("width", "0vw")
.stop(true, true)
.animate({ top: 0 }, 0, () => {
$("#timer").css("transition", "1s linear");
});
}, 250);
// 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();
// });
// }
});
})
}
function focusWords() {
if (!$("#words").hasClass('hidden')) $("#wordsInput").focus();
}
function changeCustomText() {
customText = prompt("Custom text");
customText = customText.replace(/[\n\r\t ]/gm, ' ');
customText = customText.replace(/ +/gm, ' ');
// initWords();
}
function changePage(page) {
if(pageTransition){
return;
}
restartTest();
let activePage = $(".page.active");
$(".page").removeClass('active');
$("#wordsInput").focusout();
if (page == "test" || page == "") {
pageTransition = true;
swapElements(activePage, $(".page.pageTest"), 250, () => {
pageTransition = false;
focusWords();
$(".page.pageTest").addClass('active');
history.pushState('/', null, '/');
});
showTestConfig();
hideSignOutButton();
restartCount = 0;
restartTest();
} else if (page == "about") {
pageTransition = true;
swapElements(activePage, $(".page.pageAbout"), 250, ()=>{
pageTransition = false;
history.pushState('about', null, 'about');
$(".page.pageAbout").addClass('active');
});
hideTestConfig();
hideSignOutButton();
} else if (page == "settings") {
pageTransition = true;
swapElements(activePage, $(".page.pageSettings"), 250, ()=>{
pageTransition = false;
history.pushState('settings', null, 'settings');
$(".page.pageSettings").addClass('active');
});
updateSettingsPage();
hideTestConfig();
hideSignOutButton();
} else if (page == "account") {
if (!firebase.auth().currentUser) {
changePage("login");
} else {
pageTransition = true;
swapElements(activePage, $(".page.pageAccount"), 250, () =>{
pageTransition = false;
history.pushState('account', null, 'account');
$(".page.pageAccount").addClass('active');
});
refreshAccountPage();
hideTestConfig();
showSignOutButton();
}
} else if (page == "login") {
if (firebase.auth().currentUser != null) {
changePage('account');
} else {
pageTransition = true;
swapElements(activePage, $(".page.pageLogin"), 250, ()=>{
pageTransition = false;
history.pushState('login', null, 'login');
$(".page.pageLogin").addClass('active');
});
hideTestConfig();
hideSignOutButton();
}
}
}
function changeMode(mode,nosave) {
config.mode = mode;
$("#top .config .mode .button").removeClass("active");
$("#top .config .mode .button[mode='" + mode + "']").addClass("active");
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");
}
if(!nosave) saveConfigToCookie();
}
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;
}
}
let testNow = Date.now();
let testSeconds = (testNow - testStart) / 1000;
wpm = (correctWordChars * (60 / testSeconds)) / 5;
return Math.round(wpm);
}
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);
}
function updateLiveWpm(wpm) {
if (!config.showLiveWpm) return;
if (wpm == 0 || !testActive) hideLiveWpm();
// let wpmstring = wpm < 100 ? `&nbsp;${wpm}` : `${wpm}`;
$("#liveWpm").html(wpm);
}
function showLiveWpm() {
if (!config.showLiveWpm) return;
if (!testActive) return;
$("#liveWpm").css('opacity', 0.25);
}
function hideLiveWpm() {
$("#liveWpm").css('opacity', 0);
}
function swapElements(el1, el2, totalDuration, callback = function() { return; }) {
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")) {
callback();
return false;
}
$(el1).removeClass('hidden').css('opacity', 1).animate({
opacity: 0
}, totalDuration / 2, () => {
$(el1).addClass('hidden');
$(el2).removeClass('hidden').css('opacity', 0).animate({
opacity: 1
}, totalDuration / 2, () => {
callback();
});
});
} else if (el1.hasClass('hidden') && el2.hasClass('hidden')) {
//both are hidden, only fade in the second
$(el2).removeClass('hidden').css('opacity', 0).animate({
opacity: 1
}, totalDuration, () => {
callback();
});
}else{
callback();
}
}
function clearIntervals() {
timers.forEach(timer => {
clearInterval(timer);
})
}
function updateAccountLoginButton() {
if (firebase.auth().currentUser != null) {
swapElements($("#menu .button.login"), $("#menu .button.account"), 250);
// $("#menu .button.account").removeClass('hidden');
// $("#menu .button.login").addClass('hidden');
} else {
swapElements($("#menu .button.account"), $("#menu .button.login"), 250);
// $("#menu .button.login").removeClass('hidden');
// $("#menu .button.account").addClass('hidden');
}
}
function toggleResultWordsDisplay(){
if(resultVisible){
if($("#words").hasClass('hidden')){
//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');
});
}
}
}
function flipTestColors(tf){
if(tf){
$("#words").addClass('flipped');
}else{
$("#words").removeClass('flipped');
}
}
$(document).on("click", "#top .logo", (e) => {
changePage('test');
});
$(document).on("click", "#top .config .wordCount .button", (e) => {
wrd = $(e.currentTarget).attr('wordCount');
if(wrd == "custom"){
let newWrd = prompt('Custom word amount');
if(newWrd !== null && !isNaN(newWrd) && newWrd > 0){
changeWordCount(newWrd);
}
}else{
changeWordCount(wrd);
}
restartTest();
});
$(document).on("click", "#top .config .time .button", (e) => {
time = $(e.currentTarget).attr('timeConfig');
if(time == "custom"){
let newTime = prompt('Custom time in seconds');
if(newTime !== null && !isNaN(newTime) && newTime > 0){
changeTimeConfig(newTime);
}
}else{
changeTimeConfig(time);
}
restartTest();
});
$(document).on("click", "#top .config .customText .button", (e) => {
changeCustomText();
restartTest();
});
$(document).on("click", "#top .config .punctuationMode .button", (e) => {
togglePunctuation();
restartTest();
});
$("#words").click((e) => {
focusWords();
});
$(document).on("click", "#top .config .mode .button", (e) => {
if ($(e.currentTarget).hasClass("active")) return;
mode = e.currentTarget.innerHTML;
changeMode(mode);
restartTest();
});
$(document).on("click", "#top #menu .button", (e) => {
if($(e.currentTarget).hasClass('discord')) return;
href = $(e.currentTarget).attr('href');
changePage(href.replace('/', ''));
})
$(window).on('popstate', (e) => {
let state = e.originalEvent.state;
if (state == "" || state == "/") {
// show test
changePage('test')
} else if (state == "about") {
// show about
changePage("about");
} else if (state == "account" || state == "login") {
if (firebase.auth().currentUser) {
changePage("account");
} else {
changePage('login');
}
}
});
$(document).on("keypress", "#restartTestButton", (event) => {
if (event.keyCode == 32 || event.keyCode == 13) {
if (testActive) {
restartCount++;
}
restartTest();
}
});
$(document.body).on("click", "#restartTestButton", (event) => {
restartTest();
});
$(document).on("keypress", "#showWordHistoryButton", (event) => {
if (event.keyCode == 32 || event.keyCode == 13) {
if (testActive) {
restartCount++;
}
toggleResultWordsDisplay();
}
});
$(document.body).on("click", "#showWordHistoryButton", (event) => {
toggleResultWordsDisplay();
});
$(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');
});
});
$("#wordsInput").keypress((event) => {
event.preventDefault();
});
$("#wordsInput").on("focus", (event) => {
showCaret();
});
$("#wordsInput").on("focusout", (event) => {
hideCaret();
});
$(window).resize(() => {
updateCaretPosition();
});
$(document).mousemove(function(event) {
if($("#top").hasClass("focus") && (event.originalEvent.movementX > 0 || event.originalEvent.movementY > 0)){
setFocus(false);
}
});
//keypresses for the test, using different method to be more responsive
$(document).keypress(function(event) {
if (!$("#wordsInput").is(":focus")) return;
if (event["keyCode"] == 13) return;
if (event["keyCode"] == 32) return;
if (event["keyCode"] == 27) return;
if (event["keyCode"] == 93) return;
//start the test
if (currentInput == "" && inputHistory.length == 0) {
try{
if (firebase.auth().currentUser != null) {
firebase.analytics().logEvent('testStarted');
} else {
firebase.analytics().logEvent('testStartedNoLogin');
}
}catch(e){
console.log("Analytics unavailable");
}
testActive = true;
testStart = Date.now();
if (config.mode == "time") {
showTimer();
}
updateTimerBar();
clearIntervals();
timers.push(setInterval(function() {
time++;
updateTimerBar();
let wpm = liveWPM();
updateLiveWpm(wpm);
showLiveWpm();
wpmHistory.push(wpm);
rawHistory.push(liveRaw());
keypressPerSecond.push(currentKeypressCount);
currentKeypressCount = 0;
errorsPerSecond.push(currentErrorCount);
currentErrorCount = 0;
if(keypressPerSecond[time-1] == 0 && keypressPerSecond[time-2] == 0 && keypressPerSecond[time-3] == 0 && keypressPerSecond[time-4] == 0 && !afkDetected){
showNotification("AFK detected",3000);
afkDetected = true;
}
if (config.mode == "time") {
if (time >= config.time) {
clearIntervals();
hideCaret();
testActive = false;
showResult();
}
}
}, 1000));
} else {
if (!testActive) return;
}
if (wordsList[currentWordIndex].substring(currentInput.length, currentInput.length + 1) != event["key"]) {
accuracyStats.incorrect++;
currentErrorCount++;
} else {
accuracyStats.correct++;
}
currentKeypressCount++;
currentInput += event["key"];
$("#words .word.active").attr('input',currentInput);
setFocus(true);
compareInput(currentWordIndex,currentInput,!config.blindMode);
updateCaretPosition();
});
//handle keyboard events
$(document).keydown((event) => {
//tab
if (event["keyCode"] == 9) {
if (config.quickTab && $(".pageTest").hasClass("active")) {
event.preventDefault();
if (testActive) {
restartCount++;
}
restartTest();
}
}
//only for the typing test
if ($("#wordsInput").is(":focus")) {
//backspace
if (event["keyCode"] == 8) {
event.preventDefault();
if (!testActive) return;
if (currentInput == "" && inputHistory.length > 0) {
if (
(inputHistory[currentWordIndex - 1] == wordsList[currentWordIndex - 1] && !config.freedomMode) || $($(".word")[currentWordIndex - 1]).hasClass("hidden")
) {
return;
} else {
if (event["ctrlKey"] || event["altKey"]) {
currentInput = "";
inputHistory.pop();
} else {
currentInput = inputHistory.pop();
}
currentWordIndex--;
updateActiveElement();
compareInput(currentWordIndex,currentInput,!config.blindMode);
}
} else {
// if ($($(".word")[currentWordIndex - 1]).hasClass("hidden")) {
// return;
// }
if (event["ctrlKey"]) {
currentInput = "";
} else {
currentInput = currentInput.substring(0, currentInput.length - 1);
}
compareInput(currentWordIndex,currentInput,!config.blindMode);
}
currentKeypressCount++;
updateCaretPosition();
}
//space
if (event["keyCode"] == 32) {
if (!testActive) return;
if (currentInput == "") return;
event.preventDefault();
let currentWord = wordsList[currentWordIndex];
if (config.mode == "time") {
let currentTop = $($("#words .word")[currentWordIndex]).position().top;
let nextTop = $($("#words .word")[currentWordIndex + 1]).position().top;
if (nextTop > currentTop) {
//last word of the line
if(currentTestLine > 0){
let toHide = [];
for (let i = 0; i < currentWordIndex + 1; i++) {
let forWordTop = $($("#words .word")[i]).position().top;
if(forWordTop < currentTop){
// $($("#words .word")[i]).addClass("hidden");
toHide.push($($("#words .word")[i]));
}
}
toHide.forEach(el => el.addClass('hidden'));
}
currentTestLine++;
}
}
if(config.blindMode) $("#words .word.active letter").addClass('correct');
if (currentWord == currentInput) {
inputHistory.push(currentInput);
currentInput = "";
currentWordIndex++;
updateActiveElement();
updateCaretPosition();
} else {
inputHistory.push(currentInput);
highlightBadWord(currentWordIndex,!config.blindMode)
currentInput = "";
currentWordIndex++;
if (currentWordIndex == wordsList.length) {
showResult();
return;
}else if(config.difficulty == "expert" || config.difficulty == "master"){
showResult(true);
restartCount++;
return;
}
updateActiveElement();
updateCaretPosition();
}
if (config.mode == "time") {
addWord();
}
}
}
});
loadConfigFromCookie();
getReleasesFromGitHub();
if (firebase.app().options.projectId === "monkey-type-dev-67af4") {
$("#top .logo .bottom").text("monkey-dev");
$("head title").text("Monkey Dev")
}
if (window.location.hostname === "localhost") {
window.onerror = function(error) {
this.showNotification(error,3000);
};
$("#top .logo .top").text("localhost");
$("head title").text($("head title").text() + " (localhost)");
}
$(document).on('mouseenter','#words .word',e =>{
if(resultVisible){
let input = $(e.currentTarget).attr('input');
if(input != undefined) $(e.currentTarget).append(`<div class="wordInputAfter">${input}</div>`);
}
})
$(document).on('mouseleave','#words .word',e =>{
$('.wordInputAfter').remove();
})
$(document).ready(() => {
updateFavicon(32,14);
$('body').css('transition', '.25s');
restartTest();
if (config.quickTab) {
$("#restartTestButton").addClass('hidden');
}
$("#centerContent").css("opacity", "0").removeClass("hidden").stop(true, true).animate({ opacity: 1 }, 250);
});
let ctx = $("#wpmChart");
let wpmOverTimeChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: "wpm",
data: [],
// backgroundColor: 'rgba(255, 255, 255, 0.25)',
borderColor: 'rgba(125, 125, 125, 1)',
borderWidth: 2,
yAxisID: "wpm",
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
},
{
label: "errors",
data: [],
// backgroundColor: 'rgba(255, 255, 255, 0.25)',
borderColor: 'rgba(255, 125, 125, 1)',
borderWidth: 2,
order: 1,
yAxisID: "error",
// barPercentage: 0.1,
maxBarThickness: 10,
type: "scatter",
pointStyle: "crossRot",
radius: function(context) {
var index = context.dataIndex;
var value = context.dataset.data[index];
return value.y <= 0 ? 0 : 3
}
}],
},
options: {
tooltips: {
titleFontFamily: "Roboto Mono",
bodyFontFamily: "Roboto Mono",
mode: 'x',
intersect: false
},
legend: {
display: false,
labels: {
defaultFontFamily: "Roboto Mono"
}
},
responsive: true,
maintainAspectRatio: false,
hover: {
mode: 'x',
intersect: false
},
scales: {
xAxes: [{
ticks: {
fontFamily: "Roboto Mono"
},
display: true,
scaleLabel: {
display: true,
labelString: 'Seconds',
fontFamily: "Roboto Mono"
}
}],
yAxes: [{
id: "wpm",
display: true,
scaleLabel: {
display: true,
labelString: 'Words per Minute',
fontFamily: 'Roboto Mono'
},
ticks: {
fontFamily: 'Roboto Mono',
beginAtZero: true
},
gridLines: {
display:false
}
},
{
id: "raw",
display: false,
scaleLabel: {
display: true,
labelString: 'Raw Words per Minute',
fontFamily: 'Roboto Mono'
},
ticks: {
fontFamily: 'Roboto Mono',
beginAtZero: true
},
gridLines: {
display:false
}
},
{
id: "error",
display: true,
position: 'right',
scaleLabel: {
display: true,
labelString: 'Errors',
fontFamily: 'Roboto Mono'
},
ticks: {
precision:0,
fontFamily: 'Roboto Mono',
beginAtZero: true
},
gridLines: {
display:true
}
}
]
}
}
});