Added ReadMe and LICENSE FILES, Started Documentation

This commit is contained in:
Arlo Filley 2024-03-20 17:16:15 +00:00
parent 51cd9ac2b1
commit 1cb9d8f5a5
43 changed files with 66 additions and 120317 deletions

1
.env.example Normal file
View File

@ -0,0 +1 @@
DATABASE_URL=sqlite:/Path/To/Your/Database.sqlite

9
LICENSE Normal file
View 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
View 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).

View File

@ -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

View File

View 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")),
//)
}

View File

@ -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![];

View File

@ -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>

View File

@ -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);
}

View File

@ -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%;
}

View File

@ -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>

View File

@ -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;
};
}
}

View File

@ -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

View File

@ -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"}

View File

@ -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;
}

View File

@ -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>

View File

@ -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();
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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);
}
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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)
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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());
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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())
}
}
}

View File

@ -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) {
}
}

View File

@ -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 };
}
}

View File

@ -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);
}
}
}