Added ReadMe and LICENSE FILES, Started Documentation
This commit is contained in:
parent
51cd9ac2b1
commit
1cb9d8f5a5
1
.env.example
Normal file
1
.env.example
Normal file
@ -0,0 +1 @@
|
||||
DATABASE_URL=sqlite:/Path/To/Your/Database.sqlite
|
9
LICENSE
Normal file
9
LICENSE
Normal file
@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Arlo Filley
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
51
ReadMe.md
Normal file
51
ReadMe.md
Normal file
@ -0,0 +1,51 @@
|
||||
# Touch Typing Website - My Computer Science Coursework
|
||||
|
||||
## Overview
|
||||
This project is a web server built using the Rocket framework in Rust. It provides APIs for interacting with a database and serves Javascript for the front-end of the website.
|
||||
|
||||
## Features
|
||||
- **User Authentication**: Users can sign up, login, and access their tests from anywhere they can access the server.
|
||||
- **Test Management**: Allows users to create tests and retrieve their test data.
|
||||
- **Leaderboard**: Provides a leaderboard of users based on their test performance.
|
||||
|
||||
## Setup
|
||||
|
||||
>[!NOTE]
|
||||
>You will need to have Rust and sqlite installed
|
||||
> - [Rustup - Rust Installer](https://rustup.rs/)
|
||||
> - [SQLITE - Installation Tutorial](https://www.sqlitetutorial.net/download-install-sqlite/)
|
||||
|
||||
1. Clone the repository: `git clone https://github.com/ArloFilley/cs_coursework`
|
||||
2. Navigate to the project directory: `cd cs_coursework`
|
||||
3. Install dependencies: `cargo build`
|
||||
|
||||
## Usage
|
||||
1. Start the server:
|
||||
`cargo run`
|
||||
2. By Default the server runs on `http://localhost:8000` This can be changed by creating a `Rocket.toml` file
|
||||
3. Front end will be acessible through `http://url/typing/`
|
||||
|
||||
>[!CAUTION]
|
||||
>The hashing algorithm used by this project is a basic one and should not be considered safe enough to use any password that is used on another site. Proceed at your own risk
|
||||
|
||||
4. Access the API endpoints:
|
||||
- User-related endpoints:
|
||||
- Sign up: `POST /api/create_user`
|
||||
- Login: `GET /api/login/<username>/<password>`
|
||||
- Get user tests: `GET /api/get_tests/<user_id>/<secret>`
|
||||
- Leaderboard: `GET /api/leaderboard`
|
||||
- Test-related endpoints:
|
||||
- Create test: `POST /api/post_test`
|
||||
- New test: `GET /api/new_test`
|
||||
|
||||
## Dependencies
|
||||
- **Rocket**: Web framework for Rust.
|
||||
- **Serde**: Serialization and deserialization library for Rust.
|
||||
- **Rusqlite**: SQLite database driver for Rust.
|
||||
- **Rand**: Random number generation library for Rust.
|
||||
|
||||
## Contributors
|
||||
- [Arlo Filley](https://github.com/ArloFilley)
|
||||
|
||||
## License
|
||||
This project is licensed under the [MIT License](LICENSE).
|
6
TODO.md
6
TODO.md
@ -1,12 +1,14 @@
|
||||
# Backend
|
||||
|
||||
-[x] Migrate to sqlx as backend database queryer
|
||||
-[ ] Allow Private Accounts
|
||||
-[ ] Secrets to authenicate that its the user
|
||||
-[x] Secrets to authenicate that its the user
|
||||
|
||||
# Frontend
|
||||
|
||||
-[ ] Make js api asynchronous
|
||||
-[ ] Hash passwords on front end so server plaintext password is never sent over the internet
|
||||
-[ ] Use secret to uniquely identify
|
||||
-[x] Use secret to uniquely identify
|
||||
-[x] Scrollbars
|
||||
-[ ] Change Color Scheme
|
||||
-[ ] Be able to view others accounts
|
||||
|
0
documentation/post_test.md
Normal file
0
documentation/post_test.md
Normal file
@ -83,9 +83,4 @@ async fn rocket() -> Rocket<Build> {
|
||||
// hosts the fileserver
|
||||
.mount("/typing", FileServer::from(relative!("websites/Typing")))
|
||||
.manage(Database::new().await.unwrap())
|
||||
// .mount("/servers", FileServer::from(relative!("websites/Servers")))
|
||||
//.mount(
|
||||
// "/BitBurner",
|
||||
// FileServer::from(relative!("websites/BitBurner")),
|
||||
//)
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ pub fn new_test() -> Json<Vec<String>> {
|
||||
let mut word_vec: Vec<&str> = vec![];
|
||||
let words: String = fs::read_to_string("wordlist.txt").unwrap();
|
||||
for word in words.split('\n') {
|
||||
word_vec.push(word.clone());
|
||||
word_vec.push(word);
|
||||
}
|
||||
|
||||
let mut return_list: Vec<String> = vec![];
|
||||
|
@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Panel</title>
|
||||
|
||||
<link rel="stylesheet" href="../index.css">
|
||||
<script src="index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<p>Arlo Filley</p>
|
||||
<ul>
|
||||
<li><a href="../">Home</a></li>
|
||||
<li><a href="./">Delete A User</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<h1>Password</h1>
|
||||
<input id="password" type="password">
|
||||
<table>
|
||||
<tbody id="table"></tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
@ -1,51 +0,0 @@
|
||||
get();
|
||||
|
||||
function get() {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', `https://arlofilley.com/api/typing/leaderboard`);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.responseType = 'json';
|
||||
xhr.send();
|
||||
xhr.onload = () => {
|
||||
json = xhr.response
|
||||
createTable(json);
|
||||
};
|
||||
}
|
||||
|
||||
function createTable(pJson) {
|
||||
|
||||
let table = document.getElementById("table");
|
||||
console.log(password)
|
||||
|
||||
pJson.forEach(element => {
|
||||
let tr = document.createElement('tr');
|
||||
let button = document.createElement('button');
|
||||
let username = element.username;
|
||||
button.textContent = "delete"
|
||||
button.addEventListener( "click", () => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
let password = document.getElementById("password").value;
|
||||
xhr.open('GET', `https://arlofilley.com/api/typing/delete_user/${password}/${username}/120932187`);
|
||||
xhr.send();
|
||||
})
|
||||
|
||||
create_table_element(tr, "", [element.username, element.wpm], button)
|
||||
table.appendChild(tr)
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function create_table_element(tr, string, elements, button) {
|
||||
if (elements.length > 0) {
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
let td = document.createElement('td');
|
||||
td.appendChild(document.createTextNode(elements[i]));
|
||||
tr.appendChild(td);
|
||||
}
|
||||
} else {
|
||||
let td = document.createElement('td');
|
||||
td.appendChild(document.createTextNode(string));
|
||||
tr.appendChild(td);
|
||||
}
|
||||
tr.appendChild(button);
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
:root {
|
||||
--navbar: #333333;
|
||||
|
||||
/* Navbar Button Properties */
|
||||
--navbar-button: #058ED9;
|
||||
--navbar-button-hover: #283F3B;
|
||||
--navbar-button-border: #058ED9;
|
||||
|
||||
/* */
|
||||
--background: #1f1f1f;
|
||||
|
||||
/* Table Properties*/
|
||||
--table: #FFD07B;
|
||||
--table-top:#FDB833;
|
||||
|
||||
/* Title Properties */
|
||||
--title-background: #333333;
|
||||
--title-text: #ffffff;
|
||||
|
||||
/* General Properties */
|
||||
--text: #000000;
|
||||
--border: #000000;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #111;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
nav {
|
||||
background-color: #333;
|
||||
height: 75px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
nav p {
|
||||
margin-right: 700px;
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
font-size: 38px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
font-family: "Montserrat", sans-serif;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
font-family: "Montserrat", sans-serif;
|
||||
padding: 10px 20px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-left: 5%;
|
||||
margin-bottom: 0%;
|
||||
color: #fff;
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-left: 5%;
|
||||
margin-top: 1.5%;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 90%;
|
||||
border-collapse: collapse;
|
||||
margin: 0px;
|
||||
margin-left: 5%;
|
||||
margin-top: 30px;
|
||||
padding: 0px;
|
||||
border-spacing: 0px;
|
||||
border: 2px var(--border) solid;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
table tr:nth-child(n) {
|
||||
background: var(--table);
|
||||
}
|
||||
|
||||
table tr:nth-child(1) {
|
||||
background: var(--table-top);
|
||||
}
|
||||
|
||||
tr {
|
||||
border: 1px black dotted;
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 40%;
|
||||
padding: 8px;
|
||||
padding-left: 12.5%;
|
||||
padding-right: 12.5%;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Admin Panel</title>
|
||||
|
||||
<link rel="stylesheet" href="index.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<p>Arlo Filley</p>
|
||||
<ul>
|
||||
<li><a href="./">Home</a></li>
|
||||
<li><a href="./Delete User/">Delete A User</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</body>
|
||||
</html>
|
@ -1,298 +0,0 @@
|
||||
/**
|
||||
* @file This file provides abstracted functions to interact with the api
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - use localstorage for storing test data
|
||||
* - allow for test storage without an account
|
||||
* - validate all inputs that are sent to the server
|
||||
* - give useful errors to the user if there is an api error
|
||||
* - split into multiple files to more easily read code
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class provides all the useful methods to interact with the api.
|
||||
*/
|
||||
class API {
|
||||
|
||||
constructor() { this.url = "../api"; }
|
||||
|
||||
/**
|
||||
* This takes the validated data and makes a post
|
||||
* request to the rocket server
|
||||
* @param {String} testType
|
||||
* @param {int} testLength
|
||||
* @param {int} testTime
|
||||
* @param {int} testSeed
|
||||
* @param {int} quoteId
|
||||
* @param {int} wpm
|
||||
* @param {int} accuracy
|
||||
* @param {int} userId
|
||||
*/
|
||||
postTest(pTestType, pTestLength, pTestTime, pTestSeed, pQuoteId, pWpm, pAccuracy, pUserId) {
|
||||
const data = {
|
||||
'test_type': pTestType,
|
||||
'test_length': pTestLength,
|
||||
'test_time': pTestTime,
|
||||
'test_seed': pTestSeed,
|
||||
'quote_id': pQuoteId,
|
||||
'wpm': pWpm,
|
||||
'accuracy': pAccuracy,
|
||||
'user_id': pUserId,
|
||||
'secret': user.secret
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(
|
||||
"POST",
|
||||
`${this.url}/post_test/`
|
||||
);
|
||||
|
||||
xhr.send(
|
||||
JSON.stringify(data)
|
||||
);
|
||||
|
||||
user.lastTest = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates all the parameters used for the postTest function which it then calls
|
||||
*/
|
||||
validateTest() {
|
||||
const test = screenManager.screen.textbox.getWords();
|
||||
const testType = "words";
|
||||
let testLength = test.length;
|
||||
let testTime = screenManager.screen.timer.getTime();
|
||||
const testSeed = 0;
|
||||
const quoteId = 0;
|
||||
let wpm;
|
||||
const userId = Number(user.userId);
|
||||
let test_content = screenManager.screen.textbox.getTestContent();
|
||||
|
||||
let string = "";
|
||||
let inaccurateLetters = 0;
|
||||
for (let letter = 0; letter < test.length; letter++) {
|
||||
if (test[letter] === test_content[letter]) {
|
||||
string += test[letter];
|
||||
} else {
|
||||
inaccurateLetters += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const accuracy = Math.round(((test.length - inaccurateLetters) / test.length) * 100);
|
||||
|
||||
// this is the wpm calculation factoring in the time of test
|
||||
// it assumes that all words are 5 characters long because on average
|
||||
// they are
|
||||
wpm = Math.round((string.length / 5) * (60 / testTime));
|
||||
|
||||
// the following code is a series of if statements that checks the
|
||||
// types of the variables is correct if not it errors it and returns
|
||||
// out of the function
|
||||
|
||||
if ( typeof testType !== "string" ) {
|
||||
console.error(`testType is value ${typeof testType}\nshould be a string`);
|
||||
return;
|
||||
}
|
||||
if ( typeof testLength !== "number") {
|
||||
console.error(`testLength is value ${typeof testLength}\n should be a number`);
|
||||
return;
|
||||
}
|
||||
if ( typeof testTime !== "number") {
|
||||
console.error(`testTime is value ${typeof testTime}\n should be a number`);
|
||||
return;
|
||||
}
|
||||
if ( typeof testSeed !== "number") {
|
||||
console.error(`testSeed is value ${typeof testSeed}\n should be a number`);
|
||||
return;
|
||||
}
|
||||
if ( typeof quoteId !== "number") {
|
||||
console.error(`quoteId is value ${typeof quoteId}\n should be a number`);
|
||||
return;
|
||||
}
|
||||
if ( typeof wpm !== "number") {
|
||||
console.error(`wpm is value ${typeof wpm}\n should be a number`);
|
||||
return;
|
||||
}
|
||||
if ( typeof accuracy !== "number") {
|
||||
console.error(`accuracy is value ${typeof accuracy}\n should be a number`);
|
||||
return;
|
||||
}
|
||||
if ( typeof userId !== "number") {
|
||||
console.error(`userId is value ${typeof userId}\n should be a number`);
|
||||
return;
|
||||
}
|
||||
|
||||
// after checking that all variables are of the correct type these if statements check
|
||||
// that they are acceptable values or are in acceptable bounds depending on variable types
|
||||
|
||||
if (testType !== "words") {
|
||||
// currently words is the only acceptable type but
|
||||
// this will change in later iterations
|
||||
|
||||
console.error(`testType is invalid\nacceptable options ['words']`);
|
||||
}
|
||||
// upper bounds for these numbers are less of a concern because the server will automatically
|
||||
// return an error if values are over the limit
|
||||
if (testLength < 0) {
|
||||
console.error(`testLength is too small, min value 0`)
|
||||
}
|
||||
if (testTime < 1) {
|
||||
console.error(`testTime is too small, min value 1`)
|
||||
}
|
||||
if (testSeed < 0) {
|
||||
console.error(`testSeed is too small, min value 0`)
|
||||
}
|
||||
if (quoteId < 0) {
|
||||
console.error(`quoteId is too small, min value 0`)
|
||||
}
|
||||
if (wpm < 0) {
|
||||
console.error(`wpm is too small, min value 0`)
|
||||
}
|
||||
// accuracy needs an upper bound check because users can't have more than 100%
|
||||
// accuracy when completing their tests
|
||||
if (accuracy < 0) {
|
||||
console.error(`accuracy is too small, min value 0`)
|
||||
} else if (accuracy > 100) {
|
||||
console.error(`accuracy is too big, max value 100`)
|
||||
}
|
||||
if (userId < 0) {
|
||||
console.error(`userId is too small, min value 0`)
|
||||
}
|
||||
|
||||
// there will be other tests here in later iterations but for now these tests should suffice
|
||||
|
||||
this.postTest(testType, testLength, testTime, testSeed, quoteId, wpm, accuracy, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* takes a validated name and password and sends
|
||||
* a post request to make a user with the given
|
||||
* username and password
|
||||
* @param {String} username
|
||||
* @param {String} password
|
||||
* @returns
|
||||
*/
|
||||
createUser( username, password ) {
|
||||
console.log( username, password );
|
||||
const user = {
|
||||
username: username,
|
||||
password: password
|
||||
};
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open( "POST", `${this.url}/create_user/` );
|
||||
|
||||
xhr.send( JSON.stringify(user) );
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 500) {
|
||||
alert("Sorry, looks like your username isn't unique");
|
||||
console.error("Sorry, looks like your username isn't unique")
|
||||
} else {
|
||||
this.login(username, password);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* takes a validated name and password and sends
|
||||
* a post request to make a user with the given
|
||||
* username and password
|
||||
* @param {String} username
|
||||
* @param {String} password
|
||||
* @param {boolean} initial
|
||||
* @returns
|
||||
*/
|
||||
login(pUsername, pPassword, initial = false) {
|
||||
// If Local Storage has the information we need there is no need to make a request to the server
|
||||
if (localStorage.getItem("username") === pUsername || (initial && localStorage.length === 3) ) {
|
||||
user.userId = localStorage.getItem("userId");
|
||||
user.secret = localStorage.getItem("secret");
|
||||
user.username = localStorage.getItem("username");
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Variable Validation
|
||||
if (pUsername == undefined || pPassword == undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', `${this.url}/login/${pUsername}/${pPassword}`);
|
||||
xhr.send();
|
||||
xhr.onload = () => {
|
||||
let response = JSON.parse(xhr.response);
|
||||
|
||||
// If there is an error with the login we need
|
||||
if (xhr.response === null) {
|
||||
alert("Error Logging in, maybe check your password");
|
||||
return
|
||||
}
|
||||
|
||||
user.userId = response.user_id;
|
||||
user.username = pUsername
|
||||
user.secret = response.secret;
|
||||
|
||||
localStorage.setItem("userId", user.userId);
|
||||
localStorage.setItem("username", pUsername);
|
||||
localStorage.setItem("secret", user.secret);
|
||||
};
|
||||
}
|
||||
|
||||
logout() {
|
||||
user = new User();
|
||||
user.username = "no one";
|
||||
user.password = "";
|
||||
user.userId = 0;
|
||||
user.tests = [];
|
||||
localStorage.clear();
|
||||
this.getTest();
|
||||
}
|
||||
|
||||
getUserTests() {
|
||||
if (user.userId === 0) {
|
||||
user.tests = undefined;
|
||||
return;
|
||||
}
|
||||
let xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open('GET', `${this.url}/get_tests/${user.userId}/${user.secret}`);
|
||||
xhr.send();
|
||||
xhr.onload = () => {
|
||||
user.tests = JSON.parse(xhr.response);
|
||||
};
|
||||
}
|
||||
|
||||
getLeaderBoard() {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', `${this.url}/leaderboard/`);
|
||||
xhr.send();
|
||||
xhr.onload = () => {
|
||||
user.leaderboard = JSON.parse(xhr.response);
|
||||
};
|
||||
}
|
||||
|
||||
getTest() {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', `${this.url}/new_test/`);
|
||||
xhr.send();
|
||||
xhr.onload = () =>{
|
||||
const effectiveWidth = (windowWidth - 200) / 13;
|
||||
let textArr = JSON.parse(xhr.response);
|
||||
let finalText = [];
|
||||
let text = "";
|
||||
for (let i = 0; i < textArr.length; i++) {
|
||||
if (text.length + textArr[i].length < effectiveWidth) {
|
||||
text += `${textArr[i]} `
|
||||
} else {
|
||||
finalText.push(text.substring(0,text.length-1));
|
||||
text = `${textArr[i]} `;
|
||||
}
|
||||
}
|
||||
user.nextTest = finalText;
|
||||
};
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/**
|
||||
* @file This file provides an abstraction of all the data about the user
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - save user preferences about colours
|
||||
* - make greater useage of localstorage to store tests before signup/login
|
||||
* and post them to the database if a login is made.
|
||||
*/
|
||||
|
||||
/**
|
||||
* this class displays a number of textboxes that allows the user to input a
|
||||
* username and password. Then find out the user_id of that account through the
|
||||
* necessary api routes.
|
||||
*/
|
||||
class User {
|
||||
constructor() {
|
||||
this.username = "not logged in";
|
||||
this.userId = 0;
|
||||
this.secret;
|
||||
|
||||
this.leaderboard;
|
||||
this.time = 15;
|
||||
|
||||
this.tests;
|
||||
this.lastTest;
|
||||
this.nextTest = `satisfy powerful pleasant bells disastrous mean kited is gusted romantic past taste immolate productive leak close show crabby awake handsails finicky betray long-term incompetent wander show manage toys convey hop constitute number send like off ice aboard well-made vast vacuous tramp seed force divergent flower porter fire untidy soggy fetch`;
|
||||
|
||||
this.colorScheme = {
|
||||
background: "#160C1C",
|
||||
text: "#FBB",
|
||||
|
||||
timerBar: "#50C5B7",
|
||||
timerText: "#000",
|
||||
|
||||
testGood: "#0A0",
|
||||
testBad: "#A00",
|
||||
|
||||
buttonBG: "#000",
|
||||
buttonText: "#fff",
|
||||
buttonBorder: "#000",
|
||||
|
||||
buttonHoverBG: "#FBB",
|
||||
buttonHoverText: "#000",
|
||||
buttonHoverBorder: "#000"
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
Before Width: | Height: | Size: 552 B |
Binary file not shown.
Before Width: | Height: | Size: 968 B |
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
@ -1 +0,0 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
Binary file not shown.
@ -1,25 +0,0 @@
|
||||
/*
|
||||
Index.css
|
||||
Description: This file is the stylesheet for the html that the
|
||||
user will see if they do not have javascript enabled.
|
||||
Author: Arlo Filley
|
||||
*/
|
||||
|
||||
:root {
|
||||
--background-color: #dde83d;
|
||||
--text-color: #000000;
|
||||
--font: Verdana, Geneva, Tahoma, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
noscript {
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
||||
font-family: var(--font);
|
||||
color: var(--text-color);
|
||||
font-size: large;
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TypeFast</title>
|
||||
|
||||
<!-- Css Files -->
|
||||
<link rel="stylesheet" href="index.css">
|
||||
|
||||
<!-- Favicon Files -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="./assets/favicon/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="./assets/favicon/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="./assets/favicon/favicon-16x16.png">
|
||||
<link rel="manifest" href="./assets/favicon/site.webmanifest">
|
||||
|
||||
<!-- Main Script Files -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.min.js"></script>
|
||||
<!-- <script src="./lib/p5.js"></script> -->
|
||||
<script src="./index.js" type="text/javascript"></script>
|
||||
|
||||
<!-- Element Script Files -->
|
||||
<script src="./ui_elements/button.js"></script>
|
||||
<script src="./ui_elements/canvas.js"></script>
|
||||
<script src="./ui_elements/textbox.js"></script>
|
||||
<script src="./ui_elements/timemenu.js"></script>
|
||||
<script src="./ui_elements/timer.js"></script>
|
||||
<script src="./ui_elements/menu.js"></script>
|
||||
|
||||
<!-- Screen Files-->
|
||||
<script src="./screens/screenmanager.js"></script>
|
||||
<script src="./screens/startscreen.js"></script>
|
||||
<script src="./screens/testscreen.js"></script>
|
||||
<script src="./screens/endscreen.js"></script>
|
||||
<script src="./screens/accountScreen.js"></script>
|
||||
<script src="./screens/signUpScreen.js"></script>
|
||||
<script src="./screens/loginscreen.js"></script>
|
||||
<script src="./screens/profilescreen.js"></script>
|
||||
<script src="./screens/leaderboardscreen.js"></script>
|
||||
<script src="./screens/settingsScreen.js"></script>
|
||||
|
||||
<!-- API Script Files -->
|
||||
<script src="./api/api.js"></script>
|
||||
<script src="./api/user.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>Please Enable Javascript</noscript>
|
||||
</body>
|
||||
</html>
|
@ -1,67 +0,0 @@
|
||||
/**
|
||||
* @file This files is the root of the website.
|
||||
* @author Arlo Filley
|
||||
*/
|
||||
|
||||
// these are all of the globally accessible variables that are
|
||||
// needed for the site to run correctly
|
||||
let canvas, api, screenManager, user;
|
||||
|
||||
/**
|
||||
* loads the any assets before the setup function
|
||||
* this allows p5.js to acess these assets including: sprites,
|
||||
* fonts, etc
|
||||
*/
|
||||
function preload() {
|
||||
roboto = loadFont('./assets/fonts/RobotoMono-Medium.ttf');
|
||||
}
|
||||
|
||||
/**
|
||||
* defines variables and sets up the p5.js canvas
|
||||
* ready to be drawn with using the draw() function
|
||||
*/
|
||||
function setup() {
|
||||
canvas = new Canvas();
|
||||
canvas.resize();
|
||||
canvas.center();
|
||||
|
||||
frameRate(60);
|
||||
|
||||
api = new API();
|
||||
screenManager = new ScreenManager();
|
||||
user = new User();
|
||||
screenManager.setScreen(new StartScreen());
|
||||
|
||||
api.login(null, null, true);
|
||||
api.getTest();
|
||||
textFont(roboto);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* called once per frame. draws all other elements onto the canvas
|
||||
* mostly will just call the screenManager.draw() method to make
|
||||
* sure that the correct screen is being drawn
|
||||
*/
|
||||
function draw() {
|
||||
background(user.colorScheme.background);
|
||||
screenManager.draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* called whenever a key is pressed, the variable key contains the
|
||||
* key that the user last pressed
|
||||
*/
|
||||
function keyPressed() {
|
||||
screenManager.letterTyped(key);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* called whenever the user resizes the window. Uses methods from the canvas wrapper class
|
||||
* to resize and center the canvas such that it displays correctly
|
||||
*/
|
||||
function windowResized() {
|
||||
canvas.resize();
|
||||
canvas.center();
|
||||
}
|
111165
websites/Typing/lib/p5.js
111165
websites/Typing/lib/p5.js
File diff suppressed because one or more lines are too long
6768
websites/Typing/lib/p5.min.js
vendored
6768
websites/Typing/lib/p5.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,115 +0,0 @@
|
||||
/**
|
||||
* @file This file provides a screen for the user to login through
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - move into an seperated account page with signup and logout
|
||||
* - make passwords not display plain text
|
||||
*/
|
||||
|
||||
/**
|
||||
* this class displays a number of textboxes that allows the user to input a
|
||||
* username and password. Then find out the user_id of that account through the
|
||||
* necessary api routes.
|
||||
*/
|
||||
class AccountScreen {
|
||||
constructor() {
|
||||
this.textboxes = [
|
||||
new Textbox(
|
||||
120, 350, 500, 100, 0, true, "#000", false,
|
||||
"#000", "#000", true
|
||||
),
|
||||
|
||||
new Textbox(
|
||||
120, 500, 500, 100, 0, true, "#000", false,
|
||||
"#000", "#000", false, false, true
|
||||
)
|
||||
]
|
||||
|
||||
this.buttons = [
|
||||
new Button(100, 300, 500, 100, "", false, true, "#000", "#000", "#fff", "#000", "#000", "#fff"),
|
||||
new Button(100, 450, 500, 100, "", false, true, "#000", "#000", "#fff", "#000", "#000", "#fff"),
|
||||
new Button(900, 300, 100, 50, "Login"),
|
||||
new Button(900, 400, 100, 50, "Sign up"),
|
||||
new Button(900, 500, 100, 50, "Logout"),
|
||||
]
|
||||
|
||||
this.menu = new Menu();
|
||||
|
||||
// keeps track of which textbox the user last clicked on
|
||||
this.activeTextBox = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the SignUpScreen class with all
|
||||
* appropriate elements
|
||||
*/
|
||||
draw() {
|
||||
textSize(100);
|
||||
fill(user.colorScheme.text);
|
||||
text("Account", 300, 100);
|
||||
for (let i = 0; i < this.buttons.length; i++) {
|
||||
this.buttons[i].draw();
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.textboxes.length; i++) {
|
||||
this.textboxes[i].draw();
|
||||
}
|
||||
|
||||
textSize(30);
|
||||
text("Username", 110, 275);
|
||||
text("Password", 110, 425);
|
||||
|
||||
if (this.buttons[0].isPressed()) {
|
||||
this.textboxes[this.activeTextBox].line = false;
|
||||
this.activeTextBox=0;
|
||||
this.textboxes[this.activeTextBox].line = true;
|
||||
} else if (this.buttons[1].isPressed()) {
|
||||
this.textboxes[this.activeTextBox].line = false;
|
||||
this.activeTextBox=1;
|
||||
this.textboxes[this.activeTextBox].line = true;
|
||||
} else if (this.buttons[2].isPressed()) {
|
||||
api.login(
|
||||
this.textboxes[0].getWords(),
|
||||
this.textboxes[1].getWords()
|
||||
)
|
||||
screenManager.setScreen(new StartScreen());
|
||||
} else if (this.buttons[3].isPressed()) {
|
||||
api.createUser(
|
||||
this.textboxes[0].getWords(),
|
||||
this.textboxes[1].getWords()
|
||||
)
|
||||
screenManager.setScreen(new StartScreen());
|
||||
} else if (this.buttons[4].isPressed()) {
|
||||
api.logout();
|
||||
screenManager.setScreen(new StartScreen());
|
||||
}
|
||||
this.menu.draw();
|
||||
fill(user.colorScheme.text);
|
||||
text(`Logged in as ${user.username}`, windowWidth-150, 15);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {key} key
|
||||
*/
|
||||
letterTyped(key) {
|
||||
if (key === "Tab" && this.activeTextBox === 0) {
|
||||
this.textboxes[this.activeTextBox].line = false;
|
||||
this.activeTextBox=1;
|
||||
this.textboxes[this.activeTextBox].line = true;
|
||||
} else if (key === "Tab" && this.activeTextBox === 1) {
|
||||
this.textboxes[this.activeTextBox].line = false;
|
||||
this.activeTextBox=0;
|
||||
this.textboxes[this.activeTextBox].line = true;
|
||||
} else if (key === "Enter") {
|
||||
api.login(
|
||||
this.textboxes[0].getWords(),
|
||||
this.textboxes[1].getWords()
|
||||
)
|
||||
screenManager.setScreen(new StartScreen());
|
||||
} else {
|
||||
this.textboxes[this.activeTextBox].letterTyped(key);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @file This file provides a screen class that can be displayed at the end of a test
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - provide the user with the data of the test that they have just
|
||||
* completed, such as their wpm, accuracy, etc.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class is for a screen that is displayed at the end of a test,
|
||||
* currently it just tells the user to press start to enter another test
|
||||
*/
|
||||
class EndScreen {
|
||||
constructor() {
|
||||
this.menu = new Menu();
|
||||
}
|
||||
|
||||
draw() {
|
||||
textSize(100);
|
||||
textAlign(CENTER, CENTER);
|
||||
fill(user.colorScheme.text);
|
||||
text("Test Complete", 0, 0, windowWidth - 100, windowHeight / 6);
|
||||
|
||||
textSize(30);
|
||||
text(`${user.lastTest.wpm} words per minute`, windowWidth / 2, 200);
|
||||
text(`${user.lastTest.accuracy}% accuracy`, windowWidth / 2, 240);
|
||||
text(`${user.lastTest.test_length} characters typed`, windowWidth / 2, 280);
|
||||
text(`${user.lastTest.test_time}s`, windowWidth / 2, 320);
|
||||
this.menu.draw();
|
||||
}
|
||||
|
||||
letterTyped(key) {
|
||||
if (key === "Enter") screenManager.setScreen(new TestScreen());
|
||||
}
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
/**
|
||||
* @file This file provides a leaderboard for the user to compare times.
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - implement a way for the user to scroll down the tests.
|
||||
* - display more tests on the screen at once, up to 15
|
||||
* - store the leaderboard in localstorage as a cache for the most recent results
|
||||
*/
|
||||
|
||||
/**
|
||||
* this class is a screen which shows the current leaderboard from the
|
||||
* results gotten via the api.
|
||||
*/
|
||||
class LeaderboardScreen {
|
||||
constructor() {
|
||||
this.menu = new Menu();
|
||||
api.getLeaderBoard();
|
||||
this.testButtons;
|
||||
// this.buttons = [
|
||||
// new Button(1150, 270, 240, 120, "up"),
|
||||
// new Button(1150, 390, 240, 120, "down"),
|
||||
// ]
|
||||
this.offset = 0;
|
||||
this.scroll_bar_button = new Button(1200, 200, 20, 20, "")
|
||||
}
|
||||
|
||||
draw() {
|
||||
textSize(100);
|
||||
textAlign(CENTER, CENTER);
|
||||
fill(user.colorScheme.text);
|
||||
text("Leaderboard", 0, 100, windowWidth, 120);
|
||||
this.menu.draw();
|
||||
|
||||
textSize(20);
|
||||
fill(user.colorScheme.text);
|
||||
if (user.leaderboard != undefined) {
|
||||
if (this.testButtons === undefined) {
|
||||
this.createTestButtons();
|
||||
}
|
||||
}
|
||||
|
||||
fill(user.colorScheme.testBad);
|
||||
rect(1200, 270, 20, 420);
|
||||
|
||||
fill(user.colorScheme.testGood);
|
||||
rect(1200, 270, 20, 420 / user.leaderboard.length * (this.offset + 1));
|
||||
this.scroll_bar_button.height = (user.leaderboard.length)
|
||||
|
||||
if (this.scroll_bar_button.isPressed()) {
|
||||
this.scroll_bar_button.y = mouseY - this.scroll_bar_button.height / 2;
|
||||
}
|
||||
|
||||
// the furthest up the scrollbar can go is the top
|
||||
if (this.scroll_bar_button.y < 270) {
|
||||
this.scroll_bar_button.y = 270;
|
||||
}
|
||||
|
||||
if (this.scroll_bar_button.y > 690 - this.scroll_bar_button.height) {
|
||||
this.scroll_bar_button.y = 690 - this.scroll_bar_button.height;
|
||||
}
|
||||
this.scroll_bar_button.draw();
|
||||
|
||||
if (this.testButtons !== undefined && this.testButtons.length > 1) {
|
||||
for (let i = 0; i < this.testButtons.length; i++) {
|
||||
this.testButtons[i][0].draw()
|
||||
this.testButtons[i][1].draw()
|
||||
this.testButtons[i][2].draw()
|
||||
}
|
||||
|
||||
this.offset = Number((
|
||||
// number of pixels from top of screen / total range of options, put to a whole integer to produce the correct offset
|
||||
(this.scroll_bar_button.y - 270) / ((420 - this.scroll_bar_button.height) / (user.leaderboard.length - 13))
|
||||
).toFixed(0));
|
||||
|
||||
this.createTestButtons(this.offset)
|
||||
} else {
|
||||
fill(user.colorScheme.text);
|
||||
text("Looks Like There Isn't A Leaderboard", windowWidth / 2, 300);
|
||||
}
|
||||
fill(user.colorScheme.text);
|
||||
text(`Logged in as ${user.username}`, windowWidth-150, 15);
|
||||
}
|
||||
|
||||
createTestButtons(offset = 0) {
|
||||
this.testButtons = [[
|
||||
new Button(400, 270, 100, 30, "ranking"), // test # button
|
||||
new Button(500, 270, 400, 30, "username"), // wpm button
|
||||
new Button(900, 270, 240, 30, "words per minute"), // accuracy button
|
||||
]];
|
||||
let j = 300;
|
||||
for (let i = 0 + offset; i < user.leaderboard.length && i <= 12+offset; i++) {
|
||||
this.testButtons.push([
|
||||
new Button(400, j, 100, 30, `${i+1}`, true, true, "#000", "#000", "#fff"), // test # button
|
||||
new Button(500, j, 400, 30, `${user.leaderboard[i].username}`, true, true, "#000", "#000", "#fff"), // accuracy button
|
||||
new Button(900, j, 240, 30, `${user.leaderboard[i].wpm}`, true, true, "#000", "#000", "#fff"), // wpm button
|
||||
])
|
||||
j+=30;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
/**
|
||||
* @file This file provides a screen for the user to login through
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - move into an seperated account page with signup and logout
|
||||
* - make passwords not display plain text
|
||||
*/
|
||||
|
||||
/**
|
||||
* this class displays a number of textboxes that allows the user to input a
|
||||
* username and password. Then find out the user_id of that account through the
|
||||
* necessary api routes.
|
||||
*/
|
||||
class LoginScreen {
|
||||
constructor() {
|
||||
this.textboxes = [
|
||||
new Textbox(
|
||||
120, 250, 500, 100, 0, true, "#000", false,
|
||||
"#000", "#000", true
|
||||
),
|
||||
|
||||
new Textbox(
|
||||
120, 400, 500, 100, 0, true, "#000", false,
|
||||
"000", "#000", false
|
||||
)
|
||||
]
|
||||
|
||||
this.buttons = [
|
||||
new Button(
|
||||
100, 200, 500, 100, 0, true, "#000", false,
|
||||
"#000", "#fff", ""
|
||||
),
|
||||
|
||||
new Button(
|
||||
100, 350, 500, 100, 0, true, "#000", false,
|
||||
"#000", "#fff", ""
|
||||
),
|
||||
|
||||
new Button(
|
||||
700, 300, 100, 50, 0, true, "#000", false,
|
||||
"#000", "#00ff00", "Login"
|
||||
),
|
||||
]
|
||||
|
||||
this.menu = new Menu();
|
||||
|
||||
// keeps track of which textbox the user last clicked on
|
||||
this.activeTextBox = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the SignUpScreen class with all
|
||||
* appropriate elements
|
||||
*/
|
||||
draw() {
|
||||
for (let i = 0; i < this.buttons.length; i++) {
|
||||
this.buttons[i].draw();
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.textboxes.length; i++) {
|
||||
this.textboxes[i].draw();
|
||||
}
|
||||
|
||||
text("Username", 110, 175);
|
||||
text("Password", 110, 325);
|
||||
|
||||
if (this.buttons[0].isPressed()) {
|
||||
this.textboxes[this.activeTextBox].line = false;
|
||||
this.activeTextBox=0;
|
||||
this.textboxes[this.activeTextBox].line = true;
|
||||
} else if (this.buttons[1].isPressed()) {
|
||||
this.textboxes[this.activeTextBox].line = false;
|
||||
this.activeTextBox=1;
|
||||
this.textboxes[this.activeTextBox].line = true;
|
||||
} else if (this.buttons[2].isPressed()) {
|
||||
api.login(
|
||||
this.textboxes[0].getWords(),
|
||||
this.textboxes[1].getWords()
|
||||
)
|
||||
screenManager.setScreen(new StartScreen());
|
||||
}
|
||||
this.menu.draw();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {key} key
|
||||
*/
|
||||
letterTyped(key) {
|
||||
if (key === "Tab" && this.activeTextBox === 0) {
|
||||
this.textboxes[this.activeTextBox].line = false;
|
||||
this.activeTextBox=1;
|
||||
this.textboxes[this.activeTextBox].line = true;
|
||||
} else if (key === "Tab" && this.activeTextBox === 1) {
|
||||
this.textboxes[this.activeTextBox].line = false;
|
||||
this.activeTextBox=0;
|
||||
this.textboxes[this.activeTextBox].line = true;
|
||||
} else if (key === "Enter") {
|
||||
api.login(
|
||||
this.textboxes[0].getWords(),
|
||||
this.textboxes[1].getWords()
|
||||
)
|
||||
screenManager.setScreen(new StartScreen());
|
||||
} else {
|
||||
this.textboxes[this.activeTextBox].letterTyped(key);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
/**
|
||||
* @file This file provides the user with their profilescreen, where they can see their own tests
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - change button name
|
||||
* - provide filters for tests
|
||||
* - implement a way to scroll through tests
|
||||
* - create a way to have personal bests and track them
|
||||
* - store tests in localstorage.
|
||||
* - show user tests even if they are not logged in
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class displays all of the test data for a given user
|
||||
*/
|
||||
class ProfileScreen {
|
||||
constructor() {
|
||||
this.menu = new Menu();
|
||||
api.getUserTests();
|
||||
this.testButtons;
|
||||
// this.buttons = [
|
||||
// new Button(950, 270, 240, 120, "up"),
|
||||
// new Button(950, 390, 240, 120, "down"),
|
||||
// ]
|
||||
this.offset = 0;
|
||||
this.scroll_bar_button = new Button(1200, 200, 20, 20, "")
|
||||
}
|
||||
|
||||
draw() {
|
||||
textSize(100);
|
||||
textAlign(CENTER, CENTER);
|
||||
fill(user.colorScheme.text);
|
||||
text("Profile", 0, 100, windowWidth, 120);
|
||||
|
||||
this.menu.draw();
|
||||
|
||||
textSize(20);
|
||||
fill(user.colorScheme.text);
|
||||
if (user.tests != undefined) {
|
||||
if (this.testButtons === undefined) {
|
||||
this.createTestButtons();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.testButtons !== undefined && this.testButtons.length > 1) {
|
||||
for (let i = 0; i < this.testButtons.length; i++) {
|
||||
this.testButtons[i][0].draw()
|
||||
this.testButtons[i][1].draw()
|
||||
this.testButtons[i][2].draw()
|
||||
this.testButtons[i][3].draw()
|
||||
}
|
||||
} else {
|
||||
fill(user.colorScheme.text);
|
||||
text("Looks Like You Don't have any tests :(", windowWidth / 2, 300);
|
||||
}
|
||||
|
||||
fill(user.colorScheme.text);
|
||||
text(`Logged in as ${user.username}`, windowWidth-150, 15);
|
||||
|
||||
if (user.tests === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
fill(user.colorScheme.testBad);
|
||||
rect(1200, 270, 20, 420);
|
||||
|
||||
fill(user.colorScheme.testGood);
|
||||
rect(1200, 270, 20, 420 / user.tests.length * (this.offset + 1));
|
||||
this.scroll_bar_button.height = (user.tests.length)
|
||||
|
||||
if (this.scroll_bar_button.isPressed()) {
|
||||
this.scroll_bar_button.y = mouseY - this.scroll_bar_button.height / 2;
|
||||
}
|
||||
|
||||
|
||||
// the furthest up the scrollbar can go is the top
|
||||
if (this.scroll_bar_button.y < 270) {
|
||||
this.scroll_bar_button.y = 270;
|
||||
}
|
||||
|
||||
if (this.scroll_bar_button.y > 690 - this.scroll_bar_button.height) {
|
||||
this.scroll_bar_button.y = 690 - this.scroll_bar_button.height;
|
||||
}
|
||||
this.scroll_bar_button.draw();
|
||||
|
||||
this.offset = Number((
|
||||
// number of pixels from top of screen / total range of options, put to a whole integer to produce the correct offset
|
||||
(this.scroll_bar_button.y - 270) / ((420 - this.scroll_bar_button.height) / (user.tests.length - 13))
|
||||
).toFixed(0));
|
||||
this.createTestButtons(this.offset);
|
||||
}
|
||||
|
||||
createTestButtons(offset = 0) {
|
||||
this.testButtons = [[
|
||||
new Button(600, 270, 100, 30, "test #"), // test # button
|
||||
new Button(700, 270, 100, 30, "wpm"), // wpm button
|
||||
new Button(800, 270, 100, 30, "accuracy"), // accuracy button
|
||||
new Button(900, 270, 240, 30, "characters typed")
|
||||
]];
|
||||
let j = 300;
|
||||
for (let i = user.tests.length-1-offset; i >= user.tests.length-13-offset && i >= 0; i--) {
|
||||
this.testButtons.push([
|
||||
new Button(600, j, 100, 30, `${i+1}`, true, true , "#000", "#000", "#fff"), // test # button
|
||||
new Button(700, j, 100, 30, `${user.tests[i].wpm}`, true, true , "#000", "#000", "#fff"), // accuracy button
|
||||
new Button(800, j, 100, 30, `${user.tests[i].accuracy}`, true, true , "#000", "#000", "#fff"), // wpm button
|
||||
new Button(900, j, 240, 30, `${user.tests[i].test_length}`, true, true , "#000", "#000", "#fff")
|
||||
])
|
||||
j+=30;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/**
|
||||
* @file This file provides the screen manager class, with the necassary code to switch between screen classes
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - implement transitions between screens in a more fluid way
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class provides the ScreenManager class stores the current screen
|
||||
* and provides the getters and setters necessary to switch between screen classes
|
||||
* easily
|
||||
*/
|
||||
class ScreenManager {
|
||||
constructor() {
|
||||
this.textbox;
|
||||
this.timer;
|
||||
this.screen;
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.screen.draw();
|
||||
}
|
||||
|
||||
setScreen(pScreen) {
|
||||
this.screen = pScreen;
|
||||
}
|
||||
|
||||
getScreen() {
|
||||
return this.screen;
|
||||
}
|
||||
|
||||
letterTyped(key) {
|
||||
let methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this.screen));
|
||||
for (let i = 0; i < methods.length; i++) {
|
||||
if (methods[i] === "letterTyped") {
|
||||
this.screen.letterTyped(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
/**
|
||||
* @file This file provides a screen where the user can edit the settings of their tests
|
||||
* @author Arlo Filley
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class provides all of the necessary settings for the user to be able to edit test settings
|
||||
*/
|
||||
class settingsScreen {
|
||||
constructor() {
|
||||
this.menu = new Menu();
|
||||
this.timeMenu = new TimeMenu();
|
||||
}
|
||||
|
||||
draw() {
|
||||
textAlign(CENTER, CENTER);
|
||||
|
||||
textSize(100);
|
||||
fill(user.colorScheme.text);
|
||||
text("Test Settings", windowWidth / 2, 100);
|
||||
|
||||
this.menu.draw();
|
||||
|
||||
fill(user.colorScheme.text);
|
||||
text("Test Duration", windowWidth / 2 - 250, 265)
|
||||
this.timeMenu.draw();
|
||||
fill("#000");
|
||||
text(`Logged in as ${user.username}`, windowWidth-150, 15);
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
/**
|
||||
* @file This file provides a way for the user to sign up for an account
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - move into an seperated account page with signup and logout
|
||||
* - make passwords not display plain text
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class provides the textboxes and methods necessary for a user
|
||||
* to sign up for a new account, which it should then log them into
|
||||
*/
|
||||
class SignUpScreen {
|
||||
constructor() {
|
||||
this.textboxes = [
|
||||
new Textbox(
|
||||
120, 250, 500, 100,0, true, "#000", false,
|
||||
"#000", "#000", true
|
||||
),
|
||||
|
||||
new Textbox(
|
||||
120, 400, 500, 100, 0, true, "#000", false,
|
||||
"000", "#000", false
|
||||
)
|
||||
]
|
||||
|
||||
this.buttons = [
|
||||
new Button(
|
||||
100, 200, 500, 100, 0, true, "#000", false,
|
||||
"#000", "#fff", ""
|
||||
),
|
||||
|
||||
new Button(
|
||||
100, 350, 500, 100, 0, true, "#000", false,
|
||||
"#000", "#fff", ""
|
||||
),
|
||||
|
||||
new Button(
|
||||
700, 300, 100, 50, 0, true, "#000", false,
|
||||
"#000", "#00ff00", "Sign Up"
|
||||
),
|
||||
]
|
||||
|
||||
this.menu = new Menu();
|
||||
|
||||
this.activeTextBox = 0
|
||||
// keeps track of which textbox the user last clicked on
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the SignUpScreen class with all
|
||||
* appropriate elements
|
||||
*/
|
||||
draw() {
|
||||
for (let i = 0; i < this.buttons.length; i++) {
|
||||
this.buttons[i].draw();
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.textboxes.length; i++) {
|
||||
this.textboxes[i].draw();
|
||||
}
|
||||
|
||||
fill(user.colorScheme.text);
|
||||
text("Username", 110, 175);
|
||||
text("Password", 110, 325);
|
||||
|
||||
if (this.buttons[0].isPressed()) {
|
||||
this.textboxes[this.activeTextBox].line = false;
|
||||
this.activeTextBox=0;
|
||||
this.textboxes[this.activeTextBox].line = true;
|
||||
} else if (this.buttons[1].isPressed()) {
|
||||
this.textboxes[this.activeTextBox].line = false;
|
||||
this.activeTextBox=1;
|
||||
this.textboxes[this.activeTextBox].line = true;
|
||||
} else if (this.buttons[2].isPressed()) {
|
||||
api.createUser(
|
||||
this.textboxes[0].getWords(),
|
||||
this.textboxes[1].getWords()
|
||||
)
|
||||
screenManager.setScreen(new StartScreen());
|
||||
}
|
||||
this.menu.draw();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {key} key
|
||||
*/
|
||||
letterTyped(key) {
|
||||
if (key === "Tab" && this.activeTextBox === 0) {
|
||||
this.textboxes[this.activeTextBox].line = false;
|
||||
this.activeTextBox=1;
|
||||
this.textboxes[this.activeTextBox].line = true;
|
||||
} else if (key === "Tab" && this.activeTextBox === 1) {
|
||||
this.textboxes[this.activeTextBox].line = false;
|
||||
this.activeTextBox=0;
|
||||
this.textboxes[this.activeTextBox].line = true;
|
||||
} else if (key === "Enter") {
|
||||
api.createUser(
|
||||
this.textboxes[0].getWords(),
|
||||
this.textboxes[1].getWords()
|
||||
)
|
||||
screenManager.setScreen(new StartScreen());
|
||||
} else {
|
||||
this.textboxes[this.activeTextBox].letterTyped(key);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
/**
|
||||
* @file This file is the base screen when the user visits the site
|
||||
* @author Arlo Filley
|
||||
*/
|
||||
|
||||
/**
|
||||
* This screen class is the base screen. It provides the user with basic instructions
|
||||
* and a set of menus to navigate the site
|
||||
*/
|
||||
class StartScreen {
|
||||
constructor() {
|
||||
this.menu = new Menu();
|
||||
}
|
||||
|
||||
draw() {
|
||||
textSize(100);
|
||||
textAlign(CENTER, CENTER);
|
||||
fill(user.colorScheme.text);
|
||||
text("Press enter to start test", 0, 0, windowWidth - 100, windowHeight - 100);
|
||||
|
||||
this.menu.draw();
|
||||
|
||||
fill(user.colorScheme.text);
|
||||
text(`Logged in as ${user.username}`, windowWidth-150, 15);
|
||||
}
|
||||
|
||||
letterTyped(key) {
|
||||
if (key === "Enter") {
|
||||
screenManager.setScreen(new TestScreen());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* @file This file provides the functionality for the test
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - provide a button that allows the user to exit the test
|
||||
* - provide a count down to the start of a test
|
||||
* - implement menus to allow the user to control the parameters of a test
|
||||
*/
|
||||
|
||||
/**
|
||||
* this class displays the text of the test to the the screen and then takes input from the user
|
||||
* displaying red if it is inaccurate, and green if it is
|
||||
*/
|
||||
class TestScreen {
|
||||
constructor() {
|
||||
this.textbox = new Textbox(
|
||||
100, windowHeight / 2 - 100,
|
||||
windowWidth - 500,windowHeight,
|
||||
0, true, "#000", false, "#000", "#000", true, true);
|
||||
this.timer = new Timer(0,0,windowWidth,50,0,true,"#fff", true, "#000", "#666", user.time, true);
|
||||
this.timerStarted = false;
|
||||
this.stopButton = new Button(0,50,200,50, "Stop Test");
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.textbox.draw();
|
||||
this.timer.draw();
|
||||
if (this.timerStarted) {
|
||||
this.timer.tick();
|
||||
}
|
||||
this.stopButton.draw();
|
||||
if (this.stopButton.isPressed()) {
|
||||
screenManager.setScreen(new StartScreen())
|
||||
}
|
||||
}
|
||||
|
||||
letterTyped(key) {
|
||||
this.textbox.letterTyped(key);
|
||||
if (!this.timerStarted) {
|
||||
this.timer.start();
|
||||
this.timerStarted = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,215 +0,0 @@
|
||||
/**
|
||||
* @file This file provides the button class, which can
|
||||
* be checked for clicks
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - implement visual changes (borders, etc)
|
||||
* - replace with methods with getters and setters
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Button class, a rectangle that can be checked for mouse clicks
|
||||
*/
|
||||
class Button {
|
||||
// this is the doc comment for the Timer class
|
||||
/**
|
||||
* @param {int} pX
|
||||
* @param {int} pY
|
||||
* @param {int} pWidth
|
||||
* @param {int} pHeight
|
||||
* @param {int} pLayer
|
||||
* @param {bool} pVisible
|
||||
* @param {hexcode} pTextColor
|
||||
* @param {bool} pBorder
|
||||
* @param {hexcode} pBorderColor
|
||||
* @param {hexcode} pBackgroundColor
|
||||
* @param {int} pTime
|
||||
* @param {bool} pBar
|
||||
* @param {string} Label
|
||||
*/
|
||||
constructor(pX = 100, pY = 100,
|
||||
pWidth = 200, pHeight = 30,
|
||||
pLabel = "Default Button",
|
||||
|
||||
pBorder = false,
|
||||
pHoverBorder = true,
|
||||
|
||||
pTextColor = user.colorScheme.buttonText,
|
||||
pBorderColor = user.colorScheme.buttonBorder,
|
||||
pBackgroundColor = user.colorScheme.buttonBG,
|
||||
|
||||
pHoverTextColor = user.colorScheme.buttonHoverText,
|
||||
pHoverBorderColor = user.colorScheme.buttonHoverBorder,
|
||||
pHoverBackgroundColor = user.colorScheme.buttonHoverBG,
|
||||
|
||||
pLayer = 0, pVisible = true,
|
||||
) {
|
||||
this.x = pX;
|
||||
this.y = pY;
|
||||
this.width = pWidth;
|
||||
this.height = pHeight;
|
||||
this.layer = pLayer;
|
||||
this.visible = pVisible;
|
||||
this.textColor = pTextColor;
|
||||
this.border = pBorder;
|
||||
this.borderColor = pBorderColor;
|
||||
this.backgroundColor = pBackgroundColor;
|
||||
this.label = pLabel;
|
||||
|
||||
// Attributes to control the look of the button
|
||||
// when the user is hovering over it
|
||||
this.hoverBorder = pHoverBorder;
|
||||
this.hoverBorderColor = pHoverBorderColor;
|
||||
this.hoverTextColor = pHoverTextColor;
|
||||
this.hoverBackgroundColor = pHoverBackgroundColor;
|
||||
}
|
||||
|
||||
getx() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
setX(pX) {
|
||||
this.x = pX;
|
||||
}
|
||||
|
||||
getY() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
setY(pY) {
|
||||
this.y = pY;
|
||||
}
|
||||
|
||||
getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
setWidth(pWidth) {
|
||||
this.width = pWidth;
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
setHeight(pHeight) {
|
||||
this.height = pHeight;
|
||||
}
|
||||
|
||||
getLayer() {
|
||||
return this.layer;
|
||||
}
|
||||
|
||||
setLayer(pLayer) {
|
||||
this.layer = pLayer;
|
||||
}
|
||||
|
||||
getVisible() {
|
||||
return this.visible;
|
||||
}
|
||||
|
||||
setVisible(pVisible) {
|
||||
this.visible = pVisible;
|
||||
}
|
||||
|
||||
getTextColor() {
|
||||
return this.textColor;
|
||||
}
|
||||
|
||||
setTextColor(pTextColor) {
|
||||
this.textColor = pTextColor;
|
||||
}
|
||||
|
||||
getBorder() {
|
||||
return this.border;
|
||||
}
|
||||
|
||||
setBorder(pBorder) {
|
||||
this.border = pBorder;
|
||||
}
|
||||
|
||||
getBorderColor() {
|
||||
return this.borderColor;
|
||||
}
|
||||
|
||||
setBorderColor(pBorderColor) {
|
||||
this.borderColor = pBorderColor;
|
||||
}
|
||||
|
||||
getBackgroundColor() {
|
||||
return this.backgroundColor;
|
||||
}
|
||||
|
||||
setBackgroundColor(pBackgroundColor) {
|
||||
this.backgroundColor = pBackgroundColor;
|
||||
}
|
||||
|
||||
getLabel() {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
setLabel(pLabel) {
|
||||
this.label = pLabel;
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions returns more
|
||||
*/
|
||||
isPressed() {
|
||||
if (!this.visible) {
|
||||
return;
|
||||
} else if (!mouseIsPressed) { // a unique p5.js value that checks if the mouse is clicked
|
||||
return false;
|
||||
}
|
||||
|
||||
// if the mouse is within the bounds of the return that the button has been pressed
|
||||
if (mouseX > this.x && mouseX < this.x + this.width && mouseY > this.y && mouseY < this.y + this.height) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function draws the button with the label
|
||||
*/
|
||||
draw() {
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
textSize(20);
|
||||
textAlign(CENTER, CENTER);
|
||||
|
||||
if (mouseX > this.x && mouseX < this.x + this.width && mouseY > this.y && mouseY < this.y + this.height) {
|
||||
|
||||
if (this.hoverBorder) {
|
||||
strokeWeight(2);
|
||||
stroke(this.hoverBorderColor)
|
||||
} else {
|
||||
noStroke();
|
||||
}
|
||||
fill(this.hoverBackgroundColor);
|
||||
rect(this.x, this.y, this.width, this.height);
|
||||
|
||||
noStroke();
|
||||
fill(this.hoverTextColor);
|
||||
text(this.label, this.x, this.y, this.width, this.height);
|
||||
} else {
|
||||
if (this.border) {
|
||||
strokeWeight(2);
|
||||
stroke(this.borderColor)
|
||||
} else {
|
||||
noStroke();
|
||||
}
|
||||
|
||||
fill(this.backgroundColor);
|
||||
rect(this.x, this.y, this.width, this.height);
|
||||
|
||||
noStroke();
|
||||
fill(this.textColor);
|
||||
text(this.label, this.x, this.y, this.width, this.height);
|
||||
}
|
||||
noStroke();
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
/**
|
||||
* @file This file provides a canvas class wrapper for the p5.js canvas
|
||||
* @author Arlo Filley
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* this class provides a wrapper around the
|
||||
* p5.js canvas, with easier methods to work with.
|
||||
*/
|
||||
class Canvas {
|
||||
constructor() {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
this.canvas = createCanvas(0, 0);
|
||||
}
|
||||
|
||||
|
||||
center() {
|
||||
this.canvas.position(this.x, this.y);
|
||||
}
|
||||
|
||||
|
||||
resize() {
|
||||
this.canvas.resize(windowWidth, windowHeight);
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.canvas.resize(0, 0);
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* @file This file provides a menu class to allow the user to easily navigate the site
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - more sensible button names for easier navigation
|
||||
*/
|
||||
|
||||
/**
|
||||
* this class provides a menu with all the relevent buttons the user will need,
|
||||
* it also handles when the user presses a button, by creating the correct screen
|
||||
*/
|
||||
class Menu {
|
||||
constructor() {
|
||||
this.buttons = [
|
||||
new Button(0, 0, 200, 50, "Account"),
|
||||
new Button(201, 0, 200, 50, "Test Data"),
|
||||
new Button(402, 0, 200, 50, "Start Test"),
|
||||
new Button(603, 0, 200, 50, "Leaderboard"),
|
||||
new Button(804, 0, 200, 50, "Test Settings")
|
||||
]
|
||||
}
|
||||
|
||||
draw() {
|
||||
textAlign(CENTER, CENTER);
|
||||
for (let i = 0; i < this.buttons.length; i++) {
|
||||
this.buttons[i].draw()
|
||||
}
|
||||
|
||||
if (this.buttons[0].isPressed()) {
|
||||
screenManager.setScreen(new AccountScreen());
|
||||
} else if (this.buttons[1].isPressed()) {
|
||||
screenManager.setScreen(new ProfileScreen());
|
||||
} else if (this.buttons[2].isPressed()) {
|
||||
screenManager.setScreen(new TestScreen())
|
||||
} else if (this.buttons[3].isPressed()) {
|
||||
screenManager.setScreen(new LeaderboardScreen())
|
||||
} else if (this.buttons[4].isPressed()) {
|
||||
screenManager.setScreen(new settingsScreen())
|
||||
}
|
||||
}
|
||||
}
|
@ -1,364 +0,0 @@
|
||||
/**
|
||||
* @file This file provides the textbox class for taking user input
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - add all characters a user could press
|
||||
* - refactor the code displaying the characters. It can become slow after lots of typing
|
||||
* - password mode, where the charcters are hidden from the user
|
||||
* - getters and setters
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class takes input from the user and displays it some form
|
||||
* it handles the test input from the user, and the login and sign
|
||||
* up pages.
|
||||
*/
|
||||
class Textbox {
|
||||
/**
|
||||
* Creates a new instance of the Textbox class
|
||||
* @param {int} pX
|
||||
* @param {int} pY
|
||||
* @param {int} pWidth
|
||||
* @param {int} pHeight
|
||||
* @param {int} pLayer
|
||||
* @param {bool} pVisible
|
||||
* @param {hexcode} pTextColor
|
||||
* @param {bool} pBorder
|
||||
* @param {hexcode} pBorderColor
|
||||
* @param {hexcode} pBackgroundColor
|
||||
* @param {bool} pLine
|
||||
* @param {bool} pIsTest
|
||||
* @param {bool} pIsPassword
|
||||
*/
|
||||
constructor(
|
||||
pX, pY,
|
||||
pWidth, pHeight,
|
||||
pLayer, pVisible,
|
||||
pTextColor = user.colorScheme.text,
|
||||
pBorder, pBorderColor,
|
||||
pBackgroundColor,
|
||||
pLine, pIsTest,
|
||||
pIsPassword = false
|
||||
) {
|
||||
this.x = pX;
|
||||
this.y = pY;
|
||||
this.width = pWidth;
|
||||
this.height = pHeight;
|
||||
this.layer = pLayer;
|
||||
this.visible = pVisible;
|
||||
this.textColor = pTextColor;
|
||||
this.border = pBorder;
|
||||
this.borderColor = pBorderColor;
|
||||
this.backgroundColor = pBackgroundColor;
|
||||
|
||||
this.letters = [];
|
||||
this.allowedLetters = [
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
|
||||
'l', 'm','n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
|
||||
'x', 'y', 'z',
|
||||
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
|
||||
'\'', '"', ',', '.', ' ', '!', '@', '$', '%', '^', '&', '*', '(', ')',
|
||||
]
|
||||
|
||||
this.line = pLine;
|
||||
this.isTest = pIsTest;
|
||||
|
||||
if (this.isTest) {
|
||||
this.testContent = user.nextTest;
|
||||
this.currentLine = 0;
|
||||
this.words = [""];
|
||||
} else {
|
||||
this.words = "";
|
||||
}
|
||||
|
||||
this.isPassword = pIsPassword;
|
||||
|
||||
this.goodColor = user.colorScheme.testGood;
|
||||
this.badColor = user.colorScheme.testBad;
|
||||
this.textColor = user.colorScheme.text;
|
||||
}
|
||||
|
||||
getX() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
setX(pX) {
|
||||
this.x = pX;
|
||||
}
|
||||
|
||||
getY() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
setY(pY) {
|
||||
this.y = pY;
|
||||
}
|
||||
|
||||
getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
setWidth(pWidth) {
|
||||
this.width = pWidth;
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
setHeight(pHeight) {
|
||||
this.height = pHeight;
|
||||
}
|
||||
|
||||
getLayer() {
|
||||
return this.layer;
|
||||
}
|
||||
|
||||
setLayer(pLayer) {
|
||||
this.layer = pLayer;
|
||||
}
|
||||
|
||||
getVisible() {
|
||||
return this.visible;
|
||||
}
|
||||
|
||||
setVisible(pVisible) {
|
||||
this.visible = pVisible;
|
||||
}
|
||||
|
||||
getTextColor() {
|
||||
return this.textColor;
|
||||
}
|
||||
|
||||
setTextColor(pTextColor) {
|
||||
this.textColor = pTextColor;
|
||||
}
|
||||
|
||||
getBorder() {
|
||||
return this.border;
|
||||
}
|
||||
|
||||
setBorder(pBorder) {
|
||||
this.border = pBorder;
|
||||
}
|
||||
|
||||
getBorderColor() {
|
||||
return this.borderColor;
|
||||
}
|
||||
|
||||
setBorderColor(pBorderColor) {
|
||||
this.borderColor = pBorderColor;
|
||||
}
|
||||
|
||||
getBackgroundColor() {
|
||||
return this.backgroundColor;
|
||||
}
|
||||
|
||||
setBackgroundColor(pBackgroundColor) {
|
||||
this.backgroundColor = pBackgroundColor;
|
||||
}
|
||||
|
||||
getLetters() {
|
||||
return this.letters;
|
||||
}
|
||||
|
||||
setLetters(pLetters) {
|
||||
this.letters = pLetters;
|
||||
}
|
||||
|
||||
/**
|
||||
* takes a key and handles it in the textbox
|
||||
* @param {String} pKey
|
||||
* @returns
|
||||
*/
|
||||
letterTyped(pKey) {
|
||||
if (pKey === "Backspace" && this.letters.length > 0) {
|
||||
this.letters.pop();
|
||||
|
||||
if (this.isTest) {
|
||||
this.words[this.currentLine] = this.words[this.currentLine].substring(0, this.words[this.currentLine].length-1);
|
||||
} else {
|
||||
this.words = this.words.substring(0, this.words.length-1)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.allowedLetters.length; i++) {
|
||||
if (pKey.toLowerCase() === this.allowedLetters[i]) {
|
||||
this.letters.push(pKey);
|
||||
if (this.isTest) {
|
||||
this.words[this.currentLine] += pKey;
|
||||
} else {
|
||||
this.words += pKey;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getWords() {
|
||||
let text = "";
|
||||
for (let i = 0; i < this.words.length; i++) {
|
||||
text += this.words[i];
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
setWords(pWords) {
|
||||
this.words = pWords;
|
||||
}
|
||||
|
||||
getAllowedLetters() {
|
||||
return this.allowedLetters;
|
||||
}
|
||||
|
||||
setAllowedLetters(pAllowedLetters) {
|
||||
this.allowedLetters = pAllowedLetters;
|
||||
}
|
||||
|
||||
getTestContent() {
|
||||
let text = "";
|
||||
for (let i = 0; i < this.testContent.length; i++) {
|
||||
text += this.testContent[i];
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* draws a Textbox
|
||||
* @returns
|
||||
*/
|
||||
draw() {
|
||||
// doesn't render the textbox if it should not be visible to the user.
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
noStroke();
|
||||
// sets a border if there should be one
|
||||
if (this.border) {
|
||||
stroke(this.borderColor);
|
||||
strokeWeight(1);
|
||||
}
|
||||
|
||||
// sets the parameters of what the text should look like;
|
||||
fill(this.textColor);
|
||||
textSize(23);
|
||||
textAlign(LEFT);
|
||||
if (this.words.length === 0 && this.line) {
|
||||
fill("#000")
|
||||
rect(this.x, this.y-15, 1, 30)
|
||||
}
|
||||
|
||||
// these variables allow me to use the values of x and y while updating them
|
||||
let i = this.x;
|
||||
let j = this.y;
|
||||
|
||||
if (this.isTest) {
|
||||
let i = this.x;
|
||||
let j = this.y;
|
||||
|
||||
if (this.words[this.currentLine].length >= this.testContent[this.currentLine].length) {
|
||||
this.currentLine++;
|
||||
this.words.push("");
|
||||
}
|
||||
|
||||
if (this.currentLine > 0) {
|
||||
for (let x = 0; x < this.testContent[this.currentLine-1].length; x++) {
|
||||
if (x < this.words[this.currentLine-1].length) {
|
||||
if (this.words[this.currentLine-1][x] === this.testContent[this.currentLine-1][x]) {
|
||||
fill("#00AA0044");
|
||||
} else {
|
||||
fill("#AA000044");
|
||||
}
|
||||
} else {
|
||||
fill("#00044");
|
||||
}
|
||||
|
||||
text(this.testContent[this.currentLine-1][x], i, j);
|
||||
|
||||
i += 13;
|
||||
}
|
||||
j+= 30;
|
||||
}
|
||||
|
||||
i = this.x;
|
||||
|
||||
for (let x = 0; x < this.testContent[this.currentLine].length; x++) {
|
||||
if (x < this.words[this.currentLine].length) {
|
||||
if (this.words[this.currentLine][x] === this.testContent[this.currentLine][x]) {
|
||||
fill(this.goodColor);
|
||||
} else {
|
||||
fill(this.badColor);
|
||||
}
|
||||
} else {
|
||||
fill(this.textColor);
|
||||
}
|
||||
|
||||
text(this.testContent[this.currentLine][x], i, j);
|
||||
|
||||
if (this.letters.length > 0 && x == this.letters.length && this.line) {
|
||||
fill(this.textColor)
|
||||
rect(i, j-15, 1, 30)
|
||||
}
|
||||
i += 13;
|
||||
}
|
||||
|
||||
|
||||
|
||||
i = this.x;
|
||||
j += 30;
|
||||
|
||||
fill(this.textColor);
|
||||
for (let x = this.currentLine + 1; x < this.testContent.length; x++) {
|
||||
text(this.testContent[x], i, j);
|
||||
j += 30;
|
||||
}
|
||||
|
||||
} else if (this.isPassword) {
|
||||
// these variables allow me to use the values of x and y while updating them
|
||||
let i = this.x;
|
||||
let j = this.y;
|
||||
|
||||
// currently this loop just prints out every letter in the array, including any enter characters
|
||||
for (let x = 0; x < this.letters.length; x++) {
|
||||
if (i > this.x + this.width) i = this.x, j += 30;
|
||||
if (this.letters[x] === "Enter") {
|
||||
i = this.x, j+= 30;
|
||||
} else {
|
||||
let char = "-";
|
||||
text(char, i, j);
|
||||
i += 13
|
||||
}
|
||||
if (this.letters.length > 0 && x == this.letters.length-1 && this.line) {
|
||||
fill(this.textColor)
|
||||
rect(i, j-15, 1, 30)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// these variables allow me to use the values of x and y while updating them
|
||||
let i = this.x;
|
||||
let j = this.y;
|
||||
|
||||
// currently this loop just prints out every letter in the array, including any enter characters
|
||||
for (let x = 0; x < this.letters.length; x++) {
|
||||
if (i > this.x + this.width) i = this.x, j += 30;
|
||||
if (this.letters[x] === "Enter") {
|
||||
i = this.x, j+= 30;
|
||||
} else {
|
||||
text(this.letters[x], i, j);
|
||||
i += 13
|
||||
}
|
||||
if (this.letters.length > 0 && x == this.letters.length-1 && this.line) {
|
||||
fill(this.textColor)
|
||||
rect(i, j-15, 1, 30)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw_line(line, y) {
|
||||
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
/**
|
||||
* @file This file provides a time menu class for editing the length of a test
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - implement visual changes (borders, etc)
|
||||
* - replace with methods with getters and setters
|
||||
* - highlight which option the user has chosen in some way
|
||||
*/
|
||||
|
||||
/**
|
||||
* this class displays a dropdown menu for the user where
|
||||
* they can edit the duration of a test
|
||||
*/
|
||||
class TimeMenu {
|
||||
constructor() {
|
||||
this.buttons = [
|
||||
new Button(900, 250, 100, 30, "15s"),
|
||||
new Button(900, 280, 100, 30, "30s"),
|
||||
new Button(900, 310, 100, 30, "45s"),
|
||||
new Button(900, 340, 100, 30, "60s"),
|
||||
];
|
||||
|
||||
this.topButton = this.buttons[0];
|
||||
this.dropDownButton = new Button(1000, 250, 30, 30, "v")
|
||||
this.dropdown = false;
|
||||
}
|
||||
|
||||
draw() {
|
||||
if (this.dropdown) {
|
||||
for (let i = 0; i < this.buttons.length; i++) {
|
||||
this.buttons[i].draw()
|
||||
}
|
||||
|
||||
if (this.buttons[0].isPressed() && user.time != 15) {
|
||||
user.time = 15;
|
||||
this.topButton = new Button(900, 250, 100, 30, "15s");
|
||||
this.dropdown = false;
|
||||
} else if (this.buttons[1].isPressed()) {
|
||||
user.time = 30;
|
||||
this.topButton = new Button(900, 250, 100, 30, "30s");
|
||||
this.dropdown = false;
|
||||
} else if (this.buttons[2].isPressed()) {
|
||||
user.time = 45;
|
||||
this.topButton = new Button(900, 250, 100, 30, "45s");
|
||||
this.dropdown = false;
|
||||
} else if (this.buttons[3].isPressed()) {
|
||||
user.time = 60;
|
||||
this.topButton = new Button(900, 250, 100, 30, "60s");
|
||||
this.dropdown = false;
|
||||
}
|
||||
} else {
|
||||
this.topButton.draw();
|
||||
}
|
||||
|
||||
this.dropDownButton.draw();
|
||||
if (this.dropDownButton.isPressed()) {
|
||||
this.dropdown = true;
|
||||
} else if (mouseIsPressed) { this.dropdown = false };
|
||||
}
|
||||
}
|
@ -1,222 +0,0 @@
|
||||
/**
|
||||
* @file This file provides a time menu class for editing the length of a test
|
||||
* @author Arlo Filley
|
||||
*
|
||||
* TODO:
|
||||
* - implement visual changes (borders, etc)
|
||||
* - fix the timer number becoming invisible after a
|
||||
* it drops below a certain amount of time
|
||||
* - use getters and setters
|
||||
* - use the millis() p5.js function for if the framerate becomes
|
||||
* slowed down by the amount being drawn to the screen
|
||||
*/
|
||||
|
||||
/**
|
||||
* This class provides the timer, which handles when a test starts and ends as well
|
||||
* as providing a visual element for the user to see
|
||||
*/
|
||||
class Timer {
|
||||
/**
|
||||
* @param {int} pX
|
||||
* @param {int} pY
|
||||
* @param {int} pWidth
|
||||
* @param {int} pHeight
|
||||
* @param {int} pLayer
|
||||
* @param {bool} pVisible
|
||||
* @param {hexcode} pTextColor
|
||||
* @param {bool} pBorder
|
||||
* @param {hexcode} pBorderColor
|
||||
* @param {hexcode} pBackgroundColor
|
||||
* @param {int} pTime
|
||||
* @param {bool} pBar
|
||||
*/
|
||||
constructor(pX, pY, pWidth, pHeight, pLayer, pVisible, pTextColor, pBorder, pBorderColor, pBackgroundColor, pTime, pBar) {
|
||||
this.x = pX;
|
||||
this.y = pY;
|
||||
this.width = pWidth;
|
||||
this.height = pHeight;
|
||||
this.layer = pLayer;
|
||||
this.visible = pVisible;
|
||||
this.textColor = pTextColor;
|
||||
this.border = pBorder;
|
||||
this.borderColor = pBorderColor;
|
||||
this.backgroundColor = pBackgroundColor;
|
||||
|
||||
this.bar = pBar;
|
||||
this.startTime;
|
||||
this.time = pTime;
|
||||
this.timeElapsed = 0;
|
||||
this.ended;
|
||||
this.hasStarted = false;
|
||||
}
|
||||
|
||||
getX() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
setX(pX) {
|
||||
this.x = pX;
|
||||
}
|
||||
|
||||
getY() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
setY(pY) {
|
||||
this.y = pY;
|
||||
}
|
||||
|
||||
getWidth() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
setWidth(pWidth) {
|
||||
this.width = pWidth;
|
||||
}
|
||||
|
||||
getHeight() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
setHeight(pHeight) {
|
||||
this.height = pHeight;
|
||||
}
|
||||
|
||||
getLayer() {
|
||||
return this.layer;
|
||||
}
|
||||
|
||||
setLayer(pLayer) {
|
||||
this.layer = pLayer;
|
||||
}
|
||||
|
||||
getVisible() {
|
||||
return this.visible;
|
||||
}
|
||||
|
||||
setVisible(pVisible) {
|
||||
this.visible = pVisible;
|
||||
}
|
||||
|
||||
getTextColor() {
|
||||
return this.textColor;
|
||||
}
|
||||
|
||||
setTextColor(pTextColor) {
|
||||
this.textColor = pTextColor;
|
||||
}
|
||||
|
||||
getBorder() {
|
||||
return this.border;
|
||||
}
|
||||
|
||||
setBorder(pBorder) {
|
||||
this.border = pBorder;
|
||||
}
|
||||
|
||||
getBorderColor() {
|
||||
return this.borderColor;
|
||||
}
|
||||
|
||||
setBorderColor(pBorderColor) {
|
||||
this.borderColor = pBorderColor;
|
||||
}
|
||||
|
||||
getBackgroundColor() {
|
||||
return this.backgroundColor;
|
||||
}
|
||||
|
||||
setBackgroundColor(pBackgroundColor) {
|
||||
this.backgroundColor = pBackgroundColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the amount of time the timer will run for
|
||||
*/
|
||||
getTime() {
|
||||
return this.time;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the amount of time the timer will run for
|
||||
* @param {int} pTime
|
||||
*/
|
||||
setTime(pTime) {
|
||||
this.time = pTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called to start the timer
|
||||
*/
|
||||
start() {
|
||||
this.startTime = millis();
|
||||
// framecount is a special p5 value that counts the number of frames that have passed
|
||||
// I am using the amount of frames passed to calculate the time, assuming that the website is running at 60q frames
|
||||
// per second
|
||||
this.timeElapsed = 0;
|
||||
this.hasStarted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be called once per frame
|
||||
* it keeps track of the amount of time passed
|
||||
*/
|
||||
tick() {
|
||||
this.timeElapsed = (millis() - this.startTime) / 1000;
|
||||
if (this.timeElapsed >= this.time) {
|
||||
this.end();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* this function is called at the end of the timer
|
||||
*/
|
||||
end() {
|
||||
this.visible = false;
|
||||
api.validateTest();
|
||||
this.timeElapsed = 0;
|
||||
this.time = 0;
|
||||
api.getTest();
|
||||
// Then this function will call all other functions necessary to complete the test
|
||||
// this will likely including changing the screen and interacting with the api
|
||||
screenManager.setScreen(new EndScreen());
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the timer, uses the attributes of the class as options
|
||||
*/
|
||||
draw() {
|
||||
// if the time shouldn't be rendered it quickly exits out of this method
|
||||
if (!this.visible) return;
|
||||
textAlign(LEFT);
|
||||
|
||||
// adds a border for the bar if one is needed
|
||||
if (this.border && this.bar) {
|
||||
strokeWeight(1);
|
||||
stroke(this.borderColor);
|
||||
// this doesn't use the fill function like other drawings
|
||||
// but this adds the necessary color to the border
|
||||
} else {
|
||||
noStroke();
|
||||
}
|
||||
|
||||
// draws a bar that move across the screen to show the time left
|
||||
if (this.bar) {
|
||||
fill(user.colorScheme.timerBar);
|
||||
if (this.hasStarted) {
|
||||
rect(this.y, this.x, windowWidth - windowWidth * (this.timeElapsed / this.time), this.height);
|
||||
} else {
|
||||
rect(this.y, this.x, windowWidth, this.height);
|
||||
}
|
||||
}
|
||||
|
||||
// draws the text in the corner of the screen
|
||||
noStroke();
|
||||
fill(user.colorScheme.timerText);
|
||||
if (this.hasStarted) {
|
||||
text(Math.ceil(this.time - this.timeElapsed), this.x + this.width / 6, this.y + this.height / 2);
|
||||
} else {
|
||||
text("Type A Letter To Start", this.x + this.width / 6, this.y + this.height / 2);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user