numerous bug fixes

This commit is contained in:
Arlo Filley 2022-11-28 11:04:49 +00:00
parent e8bec58827
commit 5df9198be8
25 changed files with 598 additions and 154 deletions

2
TODO.md Normal file
View File

@ -0,0 +1,2 @@
### Backend
- [ ] hello there

View File

@ -1,10 +1,12 @@
//! src/main.rs //! src/main.rs
//! This file launches the web server which hosts the fileserver and the api //! This file launches the web server which hosts the fileserver and the api
//! Author: Arlo Filley
//! //!
//! TODO:
//! - move structures into a different file
//! - find a way to make logging in more secure (password hashes?)
pub mod sql; // Imports for rocket
// relevant macros and imports for rocket.rs
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
use rocket::{ use rocket::{
Rocket, Rocket,
@ -18,16 +20,31 @@ use rocket::{
json::Json json::Json
} }
}; };
use sql::LeaderBoardTest;
// Imports for sql, see sql.rs for more information
pub mod sql;
use crate::sql::*; use crate::sql::*;
/// Test api route that returns hello world.
/// Acessible from http://url/test
#[get("/")] #[get("/")]
fn test() -> String { fn test() -> String {
sql::create_database()
.expect("couldn't create database");
String::from("Hello World!") String::from("Hello World!")
} }
/// Api route that creates a database if one
/// does not already exist.
/// Acessible from http://url/api/create_database
#[get("/create_database")]
fn create_database() -> String {
sql::create_database()
.expect("couldn't create database");
String::from("Successfully created a database")
}
/// the datascructure that the webserver will recieve
/// when a post is made to the http://url/api/post_test route
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
struct PostTest<'r> { struct PostTest<'r> {
@ -41,6 +58,8 @@ struct PostTest<'r> {
user_id: u32 user_id: u32
} }
/// Api Route that accepts test data and posts it to the database
/// Acessible from http://url/api/post_test
#[post("/post_test", data = "<test>")] #[post("/post_test", data = "<test>")]
fn post_test( fn post_test(
test: Json<PostTest<'_>> test: Json<PostTest<'_>>
@ -57,6 +76,7 @@ fn post_test(
).expect("error in posting test to tests table"); ).expect("error in posting test to tests table");
} }
/// Struct representing the user
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
struct User<'r> { struct User<'r> {
@ -64,6 +84,9 @@ struct User<'r> {
password: &'r str password: &'r str
} }
/// Route takes data about the user as a struct
/// and then creates the user in the database
/// Acessible from http://url/api/create_user
#[post("/create_user", data = "<user>")] #[post("/create_user", data = "<user>")]
fn create_user( fn create_user(
user: Json<User<'_>> user: Json<User<'_>>
@ -74,28 +97,47 @@ fn create_user(
).expect("Error: Couldn't create new user"); ).expect("Error: Couldn't create new user");
} }
/// takes the users login information and returns the users user id
/// which can be used to identify their tests etc.
/// Accessible from http://url/api/login
#[get("/login/<username>/<password>")] #[get("/login/<username>/<password>")]
fn login(username: &str, password: &str) -> String { fn login(username: &str, password: &str) -> String {
let user_id = sql::find_user(username, password).expect("error finding user_id"); let user_id = sql::find_user(username, password).expect("error finding user_id");
user_id.to_string() user_id.to_string()
} }
/// Gets the users tests from the database and returns it as a
/// json array.
/// Accessible from http://url/api/get_user_tests
#[get("/get_user_tests/<user_id>")] #[get("/get_user_tests/<user_id>")]
fn get_user_tests(user_id: u32) -> Json<Vec<Test>> { fn get_user_tests(user_id: u32) -> Json<Vec<Test>> {
let tests = sql::get_user_tests(user_id).expect("error finding user_id"); let tests = sql::get_user_tests(user_id).expect("error finding user_id");
Json(tests) Json(tests)
} }
/// Returns the highest test data from each user as
/// a json array
/// Acessible from http://url/api/leaderboard
#[get("/leaderboard")] #[get("/leaderboard")]
fn leaderboard() -> Json<Vec<LeaderBoardTest>> { fn leaderboard() -> Json<Vec<LeaderBoardTest>> {
let leaderboard = sql::get_leaderboard(0).expect("error finding user_id"); let leaderboard = sql::get_leaderboard(0).expect("error finding user_id");
Json(leaderboard) Json(leaderboard)
} }
/// The main function which builds and launches the
/// webserver with all appropriate routes and fileservers
#[launch] #[launch]
fn rocket() -> Rocket<Build> { fn rocket() -> Rocket<Build> {
rocket::build() rocket::build()
.mount("/test", routes![test]) // testing only, should return "Hello world" // testing only, should return "Hello world"
.mount("/api", routes![post_test, create_user, login, get_user_tests, leaderboard]) .mount("/test", routes![test])
.mount("/typing", FileServer::from(relative!("website"))) // hosts the fileserver // hosts the api routes necessary for the website
// to interact with the database
.mount("/api", routes![
create_database, create_user,
post_test, login, get_user_tests,
leaderboard
])
// hosts the fileserver
.mount("/typing", FileServer::from(relative!("website")))
} }

View File

@ -1,11 +1,28 @@
//! src/sql.rs
//! This file contains all the necessary code to
//! interact with the sqlite database using functions
//! it abstracts away the rusqlite necessary to perform
//! these functions
//! Author: Arlo Filley
//!
//! TODO:
//! - put necessary structs into a different file
//! - create structure for the input of post test
// Imports for json handling and rusqlite
use rusqlite::{Connection, Result}; use rusqlite::{Connection, Result};
use rocket::serde::Serialize; use rocket::serde::Serialize;
/// gets a connection to the database and returns it as
/// a rusqlite::connection
fn get_connection() -> rusqlite::Connection { fn get_connection() -> rusqlite::Connection {
Connection::open("database/database.sqlite") Connection::open("database/database.sqlite")
.expect("Error creating database connection") .expect("Error creating database connection")
} }
/// Creates the necessary tables inside the database with
/// correct normalised links between data for later
/// querying
pub fn create_database() -> Result<()> { pub fn create_database() -> Result<()> {
let connection = get_connection(); let connection = get_connection();
@ -37,6 +54,8 @@ pub fn create_database() -> Result<()> {
Ok(()) Ok(())
} }
/// takes necessary data about a test and creates
/// a database record with the data
pub fn post_test( pub fn post_test(
test_type: &str, test_type: &str,
test_length: u32, test_length: u32,
@ -86,6 +105,8 @@ pub fn post_test(
Ok(()) Ok(())
} }
/// takes a username and password and creates a database
/// entry for a new user
pub fn create_user( pub fn create_user(
username: &str, username: &str,
password: &str password: &str
@ -113,11 +134,15 @@ pub fn create_user(
Ok(()) Ok(())
} }
/// struct which can be deserialised
/// from json to get the user_id
#[derive(Debug)] #[derive(Debug)]
pub struct User { pub struct User {
user_id: u32, user_id: u32,
} }
/// takes a username and password as inputs and returns the
/// user_id of the user if one exists
pub fn find_user( pub fn find_user(
username: &str, username: &str,
password: &str password: &str
@ -146,6 +171,8 @@ pub fn find_user(
Ok(user_id) Ok(user_id)
} }
/// struct representing data that needs to be sent
/// to the user
#[derive(Serialize)] #[derive(Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
pub struct Test { pub struct Test {
@ -158,6 +185,8 @@ pub struct Test {
accuracy: u8, accuracy: u8,
} }
/// returns all the tests that a given user_id has
/// completed from the database
pub fn get_user_tests( pub fn get_user_tests(
user_id: u32 user_id: u32
) -> Result<Vec<Test>> { ) -> Result<Vec<Test>> {
@ -189,6 +218,8 @@ pub fn get_user_tests(
Ok(tests) Ok(tests)
} }
/// struct that represents all the data that gets sent to the user
/// when they make a leaderboard request
#[derive(Serialize)] #[derive(Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
pub struct LeaderBoardTest { pub struct LeaderBoardTest {
@ -196,6 +227,8 @@ pub struct LeaderBoardTest {
wpm: u8, wpm: u8,
} }
/// returns a vector of leaderboard tests, where each one is the fastest words
/// per minute that a given user has achieved
pub fn get_leaderboard( pub fn get_leaderboard(
_user_id: u32 _user_id: u32
) -> Result<Vec<LeaderBoardTest>>{ ) -> Result<Vec<LeaderBoardTest>>{

View File

@ -1,3 +1,18 @@
/**
* @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 { class API {
constructor() { constructor() {
@ -48,6 +63,8 @@ class API {
xhr.send( xhr.send(
JSON.stringify(data) JSON.stringify(data)
); );
user.lastTest = data;
} }
/** /**
@ -57,21 +74,25 @@ class API {
const test = screenManager.screen.textbox.getLetters(); const test = screenManager.screen.textbox.getLetters();
const testType = "words"; const testType = "words";
let testLength = test.length; let testLength = test.length;
let testTime = screenManager.timer.getTime(); let testTime = screenManager.screen.timer.getTime();
const testSeed = 0; const testSeed = 0;
const quoteId = 0; const quoteId = 0;
let wpm; let wpm;
const accuracy = 0;
const userId = Number(user.userId); const userId = Number(user.userId);
let test_content = screenManager.screen.textbox.testContent; let test_content = screenManager.screen.textbox.testContent;
let string = ""; let string = "";
let inaccurateLetters = 0;
for (let letter = 0; letter < test.length; letter++) { for (let letter = 0; letter < test.length; letter++) {
if (test[letter] === test_content[letter]) { if (test[letter] === test_content[letter]) {
string += test[letter]; string += test[letter];
} else {
inaccurateLetters += 1;
} }
} }
const accuracy = (test.length - inaccurateLetters) / test.length * 100;
// this is the wpm calculation factoring in the time of test // this is the wpm calculation factoring in the time of test
// it assumes that all words are 5 characters long because on average // it assumes that all words are 5 characters long because on average
// they are // they are

View File

@ -1,11 +1,28 @@
/**
* @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 { class User {
constructor() { constructor() {
this.username = "not logged in"; this.username = "not logged in";
this.password = "there"; this.password = "there";
this.userId = 0; this.userId = 0;
this.tests;
this.leaderboard; this.leaderboard;
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.time = 15; 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`;
} }
} }

View File

@ -1,6 +0,0 @@
This favicon was generated using the following font:
- Font Title: Roboto
- Font Author: Copyright 2011 Google Inc. All Rights Reserved.
- Font Source: http://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5vAx05IsDqlA.ttf
- Font License: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html))

View File

@ -32,10 +32,12 @@
<script src="./screens/startscreen.js"></script> <script src="./screens/startscreen.js"></script>
<script src="./screens/testscreen.js"></script> <script src="./screens/testscreen.js"></script>
<script src="./screens/endscreen.js"></script> <script src="./screens/endscreen.js"></script>
<script src="./screens/accountScreen.js"></script>
<script src="./screens/signUpScreen.js"></script> <script src="./screens/signUpScreen.js"></script>
<script src="./screens/loginscreen.js"></script> <script src="./screens/loginscreen.js"></script>
<script src="./screens/profilescreen.js"></script> <script src="./screens/profilescreen.js"></script>
<script src="./screens/leaderboardscreen.js"></script> <script src="./screens/leaderboardscreen.js"></script>
<script src="./screens/settingsScreen.js"></script>
<!-- API Script Files --> <!-- API Script Files -->
<script src="./api/api.js"></script> <script src="./api/api.js"></script>

View File

@ -3,6 +3,8 @@
* @author Arlo Filley * @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; let canvas, api, screenManager, user;
/** /**

View File

@ -0,0 +1,133 @@
/**
* @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
)
]
this.buttons = [
new Button(
100, 300, 500, 100, 0, true, "#000", false,
"#000", "#fff", "", true, "#000", "#000", "#fff"
),
new Button(
100, 450, 500, 100, 0, true, "#000", false,
"#000", "#fff", "", true, "#000", "#000", "#fff"
),
new Button(
900, 300, 100, 50, 0, true, "#000", false,
"#000", "#00ff00", "Login"
),
new Button(
900, 400, 100, 50, 0, true, "#000", false,
"#000", "#00ff00", "Sign up"
),
new Button(
900, 500, 100, 50, 0, true, "#000", false,
"#000", "#00ff00", "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() {
background("#eeeee4");
textSize(100);
fill("#000");
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();
}
/**
*
* @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,3 +1,16 @@
/**
* @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 { class EndScreen {
constructor() { constructor() {
this.menu = new Menu(); this.menu = new Menu();
@ -8,7 +21,13 @@ class EndScreen {
textSize(100); textSize(100);
textAlign(CENTER, CENTER); textAlign(CENTER, CENTER);
fill(0); fill(0);
text("Test Complete\nPress enter to start another test", 0, 0, windowWidth - 100, windowHeight - 100); 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(); this.menu.draw();
} }

View File

@ -1,3 +1,17 @@
/**
* @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 { class LeaderboardScreen {
constructor() { constructor() {
this.menu = new Menu(); this.menu = new Menu();

View File

@ -1,65 +1,52 @@
/**
* @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 { class LoginScreen {
constructor() { constructor() {
this.textboxes = [ this.textboxes = [
new Textbox( new Textbox(
120,250, 120, 250, 500, 100, 0, true, "#000", false,
500,100, "#000", "#000", true
0,
true,
"#000",
false, "#000",
"#000",
true
), ),
new Textbox( new Textbox(
120,400, 120, 400, 500, 100, 0, true, "#000", false,
500,100, "000", "#000", false
0,
true,
"#000",
false,"000",
"#000",
false
) )
] ]
this.buttons = [ this.buttons = [
new Button( new Button(
100,200, 100, 200, 500, 100, 0, true, "#000", false,
500,100, "#000", "#fff", ""
0,
true,
"#000",
false,"#000",
"#fff",""
), ),
new Button( new Button(
100,350, 100, 350, 500, 100, 0, true, "#000", false,
500,100, "#000", "#fff", ""
0,
true,
"#000",
false,"#000",
"#fff",""
), ),
new Button( new Button(
700,300, 700, 300, 100, 50, 0, true, "#000", false,
100,50, "#000", "#00ff00", "Login"
0,
true,
"#000",
false,"#000",
"#00ff00","Login"
), ),
] ]
this.menu = new Menu(); this.menu = new Menu();
this.activeTextBox = 0
// keeps track of which textbox the user last clicked on // keeps track of which textbox the user last clicked on
this.activeTextBox = 0
} }
/** /**

View File

@ -1,3 +1,19 @@
/**
* @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 { class ProfileScreen {
constructor() { constructor() {
this.menu = new Menu(); this.menu = new Menu();

View File

@ -1,3 +1,16 @@
/**
* @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 { class ScreenManager {
constructor() { constructor() {
this.textbox; this.textbox;

View File

@ -0,0 +1,26 @@
/**
* @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);
background("#eeeee4");
textSize(100);
fill("#000");
text("Test Settings", 450, 100);
this.menu.draw();
this.timeMenu.draw();
}
}

View File

@ -1,58 +1,44 @@
/**
* @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 { class SignUpScreen {
constructor() { constructor() {
this.textboxes = [ this.textboxes = [
new Textbox( new Textbox(
120,250, 120, 250, 500, 100,0, true, "#000", false,
500,100, "#000", "#000", true
0,
true,
"#000",
false, "#000",
"#000",
true
), ),
new Textbox( new Textbox(
120,400, 120, 400, 500, 100, 0, true, "#000", false,
500,100, "000", "#000", false
0,
true,
"#000",
false,"000",
"#000",
false
) )
] ]
this.buttons = [ this.buttons = [
new Button( new Button(
100,200, 100, 200, 500, 100, 0, true, "#000", false,
500,100, "#000", "#fff", ""
0,
true,
"#000",
false,"#000",
"#fff",""
), ),
new Button( new Button(
100,350, 100, 350, 500, 100, 0, true, "#000", false,
500,100, "#000", "#fff", ""
0,
true,
"#000",
false,"#000",
"#fff",""
), ),
new Button( new Button(
700,300, 700, 300, 100, 50, 0, true, "#000", false,
100,50, "#000", "#00ff00", "Sign Up"
0,
true,
"#000",
false,"#000",
"#00ff00","Sign Up"
), ),
] ]

View File

@ -1,3 +1,12 @@
/**
* @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 { class StartScreen {
constructor() { constructor() {
this.menu = new Menu(); this.menu = new Menu();

View File

@ -1,15 +1,29 @@
/**
* @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 { class TestScreen {
constructor() { constructor() {
this.textbox = new Textbox(100,100,windowWidth - 200,windowHeight,0,true,"#000", false, "#000", "#000", true, true); this.textbox = new Textbox(100,100,windowWidth - 200,windowHeight,0,true,"#000", false, "#000", "#000", true, true);
screenManager.timer = new Timer(0,0,windowWidth,50,0,true,"#fff", true, "#000", "#666", user.time, true); this.timer = new Timer(0,0,windowWidth,50,0,true,"#fff", true, "#000", "#666", user.time, true);
screenManager.timer.start(); this.timer.start();
} }
draw() { draw() {
background("#eeeee4"); background("#eeeee4");
this.textbox.draw(); this.textbox.draw();
screenManager.timer.draw(); this.timer.draw();
screenManager.timer.tick(); this.timer.tick();
} }
letterTyped(key) { letterTyped(key) {

View File

@ -1,3 +1,17 @@
/**
* @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 { class Button {
// this is the doc comment for the Timer class // this is the doc comment for the Timer class
/** /**
@ -15,7 +29,16 @@ class Button {
* @param {bool} pBar * @param {bool} pBar
* @param {string} Label * @param {string} Label
*/ */
constructor(pX, pY, pWidth, pHeight, pLayer, pVisible, pTextColor, pBorder, pBorderColor, pBackgroundColor, pLabel) { constructor(pX = 100, pY = 100,
pWidth = 200, pHeight = 30,
pLayer = 0, pVisible = true,
pTextColor = "#fff",
pBorder = false, pBorderColor = "#000",
pBackgroundColor = "#000",
pLabel = "Default Button",
pHoverBorder = true, pHoverBorderColor = "#000",
pHoverTextColor = "#000", pHoverBackgroundColor = "#00ff00"
) {
this.x = pX; this.x = pX;
this.y = pY; this.y = pY;
this.width = pWidth; this.width = pWidth;
@ -27,9 +50,16 @@ class Button {
this.borderColor = pBorderColor; this.borderColor = pBorderColor;
this.backgroundColor = pBackgroundColor; this.backgroundColor = pBackgroundColor;
this.label = pLabel; 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() { getx() {
return this.x; return this.x;
} }
@ -119,11 +149,11 @@ class Button {
/** /**
* This functions returns more * This functions returns more
* @returns
*/ */
isPressed() { isPressed() {
if (!mouseIsPressed) { if (!this.visible) {
// a unique p5.js value that checks if the mouse is clicked return;
} else if (!mouseIsPressed) { // a unique p5.js value that checks if the mouse is clicked
return false; return false;
} }
@ -138,12 +168,31 @@ class Button {
* This function draws the button with the label * This function draws the button with the label
*/ */
draw() { draw() {
if (!this.visible) {
return;
}
textSize(20); textSize(20);
fill(this.backgroundColor);
rect(this.x, this.y, this.width, this.height);
fill(this.textColor); if (mouseX > this.x && mouseX < this.x + this.width && mouseY > this.y && mouseY < this.y + this.height) {
text(this.label, this.x, this.y, this.width, this.height);
// passing 4 arguments to this function means the text will wrap within this box 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 {
fill(this.backgroundColor);
rect(this.x, this.y, this.width, this.height);
fill(this.textColor);
text(this.label, this.x, this.y, this.width, this.height);
}
noStroke();
} }
} }

View File

@ -1,3 +1,13 @@
/**
* @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 { class Canvas {
constructor() { constructor() {
this.x = 0; this.x = 0;

View File

@ -1,35 +1,42 @@
/**
* @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 { class Menu {
constructor() { constructor() {
this.buttons = [ this.buttons = [
new Button(0,0,100,30,0,true,"#fff",false,"#000","#000","Sign Up"), new Button(0, 0, 100, 30, 0, true, "#fff", false, "#000", "#000", "Account"),
new Button(110,0,100,30,0,true,"#fff",false,"#000","#000","Login"), new Button(101, 0, 130, 30, 0, true, "#fff", false, "#000", "#000", "Test Data"),
new Button(220,0,100,30,0,true,"#fff",false,"#000","#000","Logout"), new Button(232, 0, 140, 30, 0, true, "#fff", false, "#000", "#000", "Start Test"),
new Button(330,0,100,30,0,true,"#fff",false,"#000","#000","Profile"), new Button(373, 0, 140, 30, 0, true, "#fff", false, "#000", "#000", "Leaderboard"),
new Button(440,0,100,30,0,true,"#fff",false,"#000","#000","Test"), new Button(514, 0, 180, 30, 0, true, "#fff", false, "#000", "#000", "Test Settings")
new Button(550,0,140,30,0,true,"#fff",false,"#000","#000","Leaderboard"),
] ]
this.timeMenu = new TimeMenu();
} }
draw() { draw() {
textAlign(CENTER, CENTER);
for (let i = 0; i < this.buttons.length; i++) { for (let i = 0; i < this.buttons.length; i++) {
this.buttons[i].draw() this.buttons[i].draw()
} }
if (this.buttons[0].isPressed()) { if (this.buttons[0].isPressed()) {
screenManager.setScreen(new SignUpScreen()); screenManager.setScreen(new AccountScreen());
} else if (this.buttons[1].isPressed()) { } else if (this.buttons[1].isPressed()) {
screenManager.setScreen(new LoginScreen());
} else if (this.buttons[2].isPressed()) {
api.logout();
} else if (this.buttons[3].isPressed()) {
screenManager.setScreen(new ProfileScreen()); screenManager.setScreen(new ProfileScreen());
} else if (this.buttons[4].isPressed()) { } else if (this.buttons[2].isPressed()) {
screenManager.setScreen(new TestScreen()) screenManager.setScreen(new TestScreen())
} else if (this.buttons[5].isPressed()) { } else if (this.buttons[3].isPressed()) {
screenManager.setScreen(new LeaderboardScreen()) screenManager.setScreen(new LeaderboardScreen())
} else if (this.buttons[4].isPressed()) {
screenManager.setScreen(new settingsScreen())
} }
this.timeMenu.draw();
} }
} }

View File

@ -1,3 +1,19 @@
/**
* @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 { class Textbox {
/** /**
* Creates a new instance of the Textbox class * Creates a new instance of the Textbox class
@ -142,10 +158,6 @@ class Textbox {
* @returns * @returns
*/ */
letterTyped(pKey) { letterTyped(pKey) {
if (screenManager.screen.constructor.name === "TestScreen" && screenManager.timer.time === 0) {
return;
}
if (pKey === "Backspace" && this.letters.length > 0) { if (pKey === "Backspace" && this.letters.length > 0) {
this.letters.pop(); this.letters.pop();
this.words = this.words.substring(0, this.words.length-1) this.words = this.words.substring(0, this.words.length-1)

View File

@ -1,41 +1,61 @@
/**
* @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 { class TimeMenu {
constructor() { constructor() {
this.buttons = [ this.buttons = [
new Button(700,0,100,30,0,true,"#fff",false,"#000","#000","15s"), new Button(100, 230, 100, 30, 0, true, "#fff", false, "#000", "#000", "15s"),
new Button(700,30,100,30,0,true,"#fff",false,"#000","#000","30s"), new Button(100, 260, 100, 30, 0, true, "#fff", false, "#000", "#000", "30s"),
new Button(700,60,100,30,0,true,"#fff",false,"#000","#000","45s"), new Button(100, 290, 100, 30, 0, true, "#fff", false, "#000", "#000", "45s"),
new Button(700,90,100,30,0,true,"#fff",false,"#000","#000","60s"), new Button(100, 320, 100, 30, 0, true, "#fff", false, "#000", "#000", "60s"),
]; ];
this.topButton = this.buttons[0];
this.dropdown = false; this.dropdown = false;
} }
draw() { draw() {
this.buttons[0].draw();
if (this.dropdown) { if (this.dropdown) {
for (let i = 0; i < this.buttons.length; i++) { for (let i = 0; i < this.buttons.length; i++) {
this.buttons[i].draw() this.buttons[i].draw()
if (this.buttons[0].isPressed() && user.time != 15) {
user.time = 15;
this.dropdown = false;
} else if (this.buttons[1].isPressed()) {
user.time = 30;
this.dropdown = false;
} else if (this.buttons[2].isPressed()) {
user.time = 45;
this.dropdown = false;
} else if (this.buttons[3].isPressed()) {
user.time = 60;
this.dropdown = false;
}
} }
}
if (this.buttons[0].isPressed()) { if (this.buttons[0].isPressed() && user.time != 15) {
user.time = 15;
this.topButton = new Button(100, 230, 100, 30, 0, true, "#fff", false, "#000", "#000", "15s");
this.dropdown = false;
} else if (this.buttons[1].isPressed()) {
user.time = 30;
this.topButton = new Button(100, 230, 100, 30, 0, true, "#fff", false, "#000", "#000", "30s");
this.dropdown = false;
} else if (this.buttons[2].isPressed()) {
user.time = 45;
this.topButton = new Button(100, 230, 100, 30, 0, true, "#fff", false, "#000", "#000", "45s");
this.dropdown = false;
} else if (this.buttons[3].isPressed()) {
user.time = 60;
this.topButton = new Button(100, 230, 100, 30, 0, true, "#fff", false, "#000", "#000", "60s");
this.dropdown = false;
}
} else {
this.topButton.draw();
}
if (this.topButton.isPressed()) {
this.dropdown = true; this.dropdown = true;
} else if (mouseIsPressed) { } else if (mouseIsPressed) {
this.dropdown = false; this.dropdown = false;
} }
} }
} }

View File

@ -1,5 +1,21 @@
/**
* @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 { class Timer {
// this is the doc comment for the Timer class
/** /**
* @param {int} pX * @param {int} pX
* @param {int} pY * @param {int} pY