From 90bb99f232baf7a5691a409a1dd7808cb1e40b1e Mon Sep 17 00:00:00 2001 From: ArloFilley Date: Tue, 17 Oct 2023 15:15:41 +0100 Subject: [PATCH] open_evening_2 --- Dockerfile | 17 + Rocket.toml | 23 ++ about/Coursework Specification.pdf | Bin 0 -> 65481 bytes about/TODO.md | 41 ++ database/dev/database.sqlite | Bin 16384 -> 16384 bytes public/Admin/Delete User/index.html | 26 ++ public/Admin/Delete User/index.js | 51 +++ public/Admin/index.css | 125 ++++++ public/Admin/index.html | 20 + public/api/api.js | 292 ++++++++++++++ public/api/user.js | 42 ++ .../assets/favicon/android-chrome-192x192.png | Bin 0 -> 3437 bytes .../assets/favicon/android-chrome-512x512.png | Bin 0 -> 11455 bytes public/assets/favicon/apple-touch-icon.png | Bin 0 -> 3081 bytes public/assets/favicon/favicon-16x16.png | Bin 0 -> 552 bytes public/assets/favicon/favicon-32x32.png | Bin 0 -> 968 bytes public/assets/favicon/favicon.ico | Bin 0 -> 15406 bytes public/assets/favicon/site.webmanifest | 1 + public/assets/fonts/RobotoMono-Medium.ttf | Bin 0 -> 86820 bytes public/assets/icons/account_circle.svg | 1 + public/components/button.js | 215 +++++++++++ public/components/canvas.js | 31 ++ public/components/menu.js | 44 +++ public/components/textbox.js | 364 ++++++++++++++++++ public/components/timemenu.js | 61 +++ public/components/timer.js | 222 +++++++++++ public/components/user.js | 35 ++ public/index.css | 27 ++ public/index.html | 51 +++ public/index.js | 68 ++++ public/screens/accountScreen.js | 115 ++++++ public/screens/endscreen.js | 36 ++ public/screens/leaderboardscreen.js | 101 +++++ public/screens/loginscreen.js | 108 ++++++ public/screens/profilescreen.js | 111 ++++++ public/screens/screenmanager.js | 41 ++ public/screens/settingsScreen.js | 29 ++ public/screens/signUpScreen.js | 109 ++++++ public/screens/startscreen.js | 31 ++ public/screens/testscreen.js | 45 +++ scripts/build.sh | 2 + scripts/run.sh | 2 + src/main.rs | 40 +- 43 files changed, 2507 insertions(+), 20 deletions(-) create mode 100644 Dockerfile create mode 100644 Rocket.toml create mode 100644 about/Coursework Specification.pdf create mode 100644 about/TODO.md create mode 100755 public/Admin/Delete User/index.html create mode 100755 public/Admin/Delete User/index.js create mode 100755 public/Admin/index.css create mode 100755 public/Admin/index.html create mode 100644 public/api/api.js create mode 100755 public/api/user.js create mode 100755 public/assets/favicon/android-chrome-192x192.png create mode 100755 public/assets/favicon/android-chrome-512x512.png create mode 100755 public/assets/favicon/apple-touch-icon.png create mode 100755 public/assets/favicon/favicon-16x16.png create mode 100755 public/assets/favicon/favicon-32x32.png create mode 100755 public/assets/favicon/favicon.ico create mode 100755 public/assets/favicon/site.webmanifest create mode 100755 public/assets/fonts/RobotoMono-Medium.ttf create mode 100644 public/assets/icons/account_circle.svg create mode 100755 public/components/button.js create mode 100755 public/components/canvas.js create mode 100755 public/components/menu.js create mode 100755 public/components/textbox.js create mode 100755 public/components/timemenu.js create mode 100755 public/components/timer.js create mode 100644 public/components/user.js create mode 100755 public/index.css create mode 100755 public/index.html create mode 100644 public/index.js create mode 100755 public/screens/accountScreen.js create mode 100755 public/screens/endscreen.js create mode 100755 public/screens/leaderboardscreen.js create mode 100755 public/screens/loginscreen.js create mode 100755 public/screens/profilescreen.js create mode 100755 public/screens/screenmanager.js create mode 100755 public/screens/settingsScreen.js create mode 100755 public/screens/signUpScreen.js create mode 100755 public/screens/startscreen.js create mode 100755 public/screens/testscreen.js create mode 100644 scripts/build.sh create mode 100755 scripts/run.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ab8ebce --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM rust:1 AS builder +WORKDIR /app +COPY . . + +RUN apt-get -y update && apt-get -y upgrade +RUN apt-get install -y sqlite3 libsqlite3-dev + +ENV DATABASE_URL="sqlite:/app/database/dev/database.sqlite" +RUN cargo build --release + +FROM ubuntu:latest as runner +WORKDIR /app +COPY ./wordlist.txt ./wordlist.txt +COPY ./Rocket.toml ./Rocket.toml +COPY --from=builder /app/target/release/cs_coursework /usr/local/bin/webapp +ENV DATABASE_URL="sqlite:/app/database/dev/database.sqlite" +CMD [ "webapp" ] \ No newline at end of file diff --git a/Rocket.toml b/Rocket.toml new file mode 100644 index 0000000..150fdd6 --- /dev/null +++ b/Rocket.toml @@ -0,0 +1,23 @@ +[default] +address = "::" +port = 8000 +workers = 4 +max_blocking = 512 +keep_alive = 5 +ident = "Rocket" +log_level = "normal" +temp_dir = "/tmp" +cli_colors = true +secret_key = "Change This" + +[default.limits] +form = "64 kB" +json = "1 MiB" +msgpack = "2 MiB" +"file/jpg" = "5 MiB" + +[default.shutdown] +ctrlc = true +signals = ["term", "hup"] +grace = 5 +mercy = 5 \ No newline at end of file diff --git a/about/Coursework Specification.pdf b/about/Coursework Specification.pdf new file mode 100644 index 0000000000000000000000000000000000000000..47f21dda350592cd3bb971086603c660dda48352 GIT binary patch literal 65481 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 ZgF!kRBM}pk6c;&0a_W?duIe$8{|^G|b=Uv^ literal 0 HcmV?d00001 diff --git a/about/TODO.md b/about/TODO.md new file mode 100644 index 0000000..b08cddd --- /dev/null +++ b/about/TODO.md @@ -0,0 +1,41 @@ +# Backend +-[x] Migrate to sqlx as backend database queryer +-[ ] Allow Private Accounts +-[ ] Secrets to authenicate that its the user +-[ ] Account Profile +-[ ] Better Password Security +-[ ] Login with google +-[ ] Give new test route a more descriptive name +-[x] Remove need for cors + +# Frontend +## API +-[ ] Make js api asynchronous +-[ ] Hash passwords on front end so server plaintext password is never sent over the internet +-[x] Use secret to uniquely identify users + +## Components +-[x] Scrollbars +-[ ] Scrollbars shouldn't appear if there's not enought content +-[ ] Scrollbar update +-[ ] Account Component +-[ ] Username gets cut off if too long +-[ ] Test caret doesn't display properley + +## Screens +-[x] Change Color Scheme +-[ ] Color Scheme Editor +-[ ] Be able to view others accounts + +## Users +-[ ] Validate username length +-[ ] Validate usernames +-[x] Login bug, text goes wrong color when removeing all text from a box + +## API +-[ ] Give useful errors to the user when something goes wrong + +## Admin +-[ ] Authentication +-[ ] Allow deleting users +-[ ] Allow deleting tests \ No newline at end of file diff --git a/database/dev/database.sqlite b/database/dev/database.sqlite index ee629d1c0835e296114f6baa4d4a37f6afefeebe..33b094ffa286edee183349d6091bfb09e4d10c31 100755 GIT binary patch delta 324 zcmZo@U~Fh$oFL68G*QNxQD|er5`Fma*f2RYG1bf<$<*A`+|t-QEhWjw%s9y`Da|s)G|?#4 z$RsVvG%+RB*d)a)HN`M7Ez#1%G%`FOvnaqX$k4Mas5HsKEyv8n-`LbFEypQ1*{mqZ zH`G7hD=9fO$c2QN0 delta 89 zcmZo@U~Fh$oFL7}J5k1&k#}Rl5`I2b-cknsa=xE@WxStwOE)VjB=c@AmF;I@W92`{ tz<+QvkHQ-Mi2>Y`59&J$u<;i#@ZaY@$-kL@4u2be0Z?@e|KtMu2>^>+8sh)} diff --git a/public/Admin/Delete User/index.html b/public/Admin/Delete User/index.html new file mode 100755 index 0000000..4d8c698 --- /dev/null +++ b/public/Admin/Delete User/index.html @@ -0,0 +1,26 @@ + + + + + + + Admin Panel + + + + + + +

Password

+ + + +
+ + \ No newline at end of file diff --git a/public/Admin/Delete User/index.js b/public/Admin/Delete User/index.js new file mode 100755 index 0000000..b15db80 --- /dev/null +++ b/public/Admin/Delete User/index.js @@ -0,0 +1,51 @@ +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); +} \ No newline at end of file diff --git a/public/Admin/index.css b/public/Admin/index.css new file mode 100755 index 0000000..cf59249 --- /dev/null +++ b/public/Admin/index.css @@ -0,0 +1,125 @@ +: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%; +} \ No newline at end of file diff --git a/public/Admin/index.html b/public/Admin/index.html new file mode 100755 index 0000000..6e4bb39 --- /dev/null +++ b/public/Admin/index.html @@ -0,0 +1,20 @@ + + + + + + + Admin Panel + + + + + + + \ No newline at end of file diff --git a/public/api/api.js b/public/api/api.js new file mode 100644 index 0000000..3368602 --- /dev/null +++ b/public/api/api.js @@ -0,0 +1,292 @@ +/** + * @file This file provides abstracted functions to interact with the api + * @author Arlo Filley + * + */ + +/** + * 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; + }; + } +} \ No newline at end of file diff --git a/public/api/user.js b/public/api/user.js new file mode 100755 index 0000000..b29d00f --- /dev/null +++ b/public/api/user.js @@ -0,0 +1,42 @@ +/** + * @file This file provides an abstraction of all the data about the user + * @author Arlo Filley + */ + +/** + * 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 = null; + this.colorScheme = { + background: "#121212", + text: "#AAA", + + timerBar: "#50C5B7", + timerText: "#000", + + testGood: "#0A0", + testBad: "#A00", + + buttonBG: "#12202f", + buttonText: "#fff", + buttonBorder: "#000", + + buttonHoverBG: "#FFF", + buttonHoverText: "#000", + buttonHoverBorder: "#000" + } + } +} \ No newline at end of file diff --git a/public/assets/favicon/android-chrome-192x192.png b/public/assets/favicon/android-chrome-192x192.png new file mode 100755 index 0000000000000000000000000000000000000000..5505cf627290f3ff06993dae75eedaa67af92a34 GIT binary patch literal 3437 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 literal 0 HcmV?d00001 diff --git a/public/assets/favicon/favicon.ico b/public/assets/favicon/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..95c11d6be0ffc760fd7064177903484c4c62c6a7 GIT binary patch literal 15406 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 P48ITh|KI-#G*#d)aB1S@ literal 0 HcmV?d00001 diff --git a/public/assets/favicon/site.webmanifest b/public/assets/favicon/site.webmanifest new file mode 100755 index 0000000..45dc8a2 --- /dev/null +++ b/public/assets/favicon/site.webmanifest @@ -0,0 +1 @@ +{"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"} \ No newline at end of file diff --git a/public/assets/fonts/RobotoMono-Medium.ttf b/public/assets/fonts/RobotoMono-Medium.ttf new file mode 100755 index 0000000000000000000000000000000000000000..752d0fab788b03c162f7aec80b8e39fa7711b900 GIT binary patch literal 86820 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 z zN4?sR5@rc;0Llaw8i-8rZ}Fs7A!RB`-iOxlj{f8AZ5J-Wm}rAli-2dq1#N=HOos#~ z4q)GgK>bKbN_IMYB!$KAMv2;G4!bv65xH;4>SMLBhI0mkeok%a6p%|&g>rHwV>^Mm z6PMN0TwdSv>u|WbrKaY-zM6Svqs64tO$*ps-R=dk=t7bOQ z{45gmg>k+)J`mWw+^_?PGlkLv#~yk17B!@~pZTtP@$5uh_2X++9;u4f_{#!&7ngQh z?X`;RcvbYs1_5&+GD`<88NGgHd7#^5vI~4VV+Zf4#q-uMCyvfr_jX6?%=Se5iG_Vf z!OJ8UO9JZNdm@n-BP($_msPR!TI}&$+0|l=%H=AI+b&p}25yhUSVj<_8%BiWm&s>F zc&@8ox}bple;v%U9;mZ~Xe9bzu0NGzEPMLt+*1zA%-Lq_Ch8a9v^7y#wZDI0f21<5 zQ-fI^>eB0t&9T}e^9LV_)wUEnwe4P8wZiWHoka&)Xt+ynFd6mwu5fUsK@SA1R=1S^ z8EP~dmAbXW*QQbHY2Uz}0A@~Qw+w{lIvsU#WuvEfX>>j;E+~LycMLUd^jX4EajDk3 zI{83(7x)kLTHUPj@GPU=WYQaFku&{MG+OXeYo_>1r)o6dG1g7Sbg*Icd)fooaS))& z4w+&5TEZbwDy)=ZA{kT>=G->oOjl0IK4!6qdc~6GP~Oy`$#u0=M^>#o5{=fB1 zF6*+GDrDK^(ej7aMZ3U?0tK$CymY3~U@t1s>sx>G>hk_|sQkfcn?IP`GGj`8!?8sJ zM-%bM5~0AY^z&3JV3LMnf4~Jr(r0msP~qd0c*(R1`L;Jwmy{ z<8ZD_L>D_8B~JL+{h$3+0ujFhp2k`Lvxufhs3Izc8=BC{o%BazQeaNJc(J9RX@8Bborq5J#QhB$UcC z_RlXk3GJDbHRdFw9xTjvS?x<=4Xd3_&mlpfC}M??7ZqJMX`{>S#gZ%PY1YCpsJzog zUr|B6SS+n^Zp*eQlo9fTXQm%T>KB+Hw4v8e4-`nv`9c9G+B#VV+SYVh{0r@EP0?dlFyMJ56Lvh9Tmd|vNmlP7QSczixj z@fLjL_1!0v$zmQ)Ob$^?skF}RtpfnPZe7zRkEgWMHBz(0_FR`H#qgHj4+A5_2d3Ls+g+znE*2wM#UK_e9inkgeFGy$Q9e)9xLa&}ex1EOYF1Tz+7ulQK5A^2+>3Tms2_*4K0-OQZp9-8c)8+!-S1|2Z zwP8zupW#gWpYQ~*~PpuwR)NjV4aCPO8Z=9 zt^xZGAgIt>qRtlQ73xXr1s*R;E-tjmb(nuj1bO+|!o#_e?3_G_u*fEpmTG35>)sV8 z4I_~-S0fN=a)kn5y=xk?wca9;sIWj7vTO|p-J9AP*E$?tsYvKkDVzd@@=38sP^`8k z)Jnat+-3Kfvjth%Iq1DwWrZhjCWSj; zn)!mUN*`pdLRHKuw33-ub4^3v9pUoo>WcC^`WmjPVZQNqs6hWvsoMPtzd})R2EG0~ zy8DIff>Ix>b^;E1jR*?OO-fG$0i(c)!)MnF@XP{bjih&aTU*8Rv= z70z%C{a+A?IMWva)R1y)rHo3OEv1)A$7#Xo^A=w@!#~|DwFv|^sl6{;+^Po&7!jDn zk`kRcB!&&c=b^`4PS>WX&NidODl9UJEnTJ4uk;p|d{|IaRARRHi}<-T4VnclD#~}N zOvOdQBC=g$19#$EG?gVT%+ryVy|m>1Xl1QTT40cfbOmw+NFTj$U%rT+oglY+wnfau3x?!<2AkVnd%zr9wbqwPz7~Ol7T^L z>G8JT*Y56Yb+lXl(KZrjmxXsuUj77Fgw2Wgv#Xk}tVk@eC^ajX`LQ9h*=jKv`=e`a zGN7U&VYyk~M1Q|@$%bHy>_nkeap~e6;axqxFhooB8)IM?C8q$NSU!>D#I(!Y29XY z)wy%uZF;dT7LUj3UflHEcTEei5^XdsZ2FE(7;(ocBX{@r-4m&(JLxo;X8B5HnoJJ- zCe~Y-Rls>GQxQK-(dDvRGDtKP>*LH=d#1ON?g#}#`xXSdZQ3%~FzdP;jLhE)z+tsc zHz)3DP^&!6fKikvF4;=4|gmVxh5iG7#Xao$Ra5Cj8ySLYw| zRw4rD(15SzrylnTaP0e;26PwmXXfE$!^04vt|gbD*O==zp)W3+K@gQd#JxZ-mt-l3 zDZnAP-47UqAIyRwt&OFVah(24+=n%l?7+WS){&{}NQLOey0E%Nr*!4zLw9@t7~nJCrt%8KT-e^gW#Zo-n zP+9rwskhI*sVrDtSzf+xZsS~?UaeB9J1a`3XmrNUD}uhOJ8z%4v$Ql^RZ)KD+ywa7 z)#wsWi%O$Y>U9g+*BuInBiScU=2Qhrub1M7ITQ`X z^Zxdi+?qhy_0#5DUlyEXl$UCC4VV^pjpor!JR8m!{h7ElpNmV6Pb4*yF$CDnGEB1M z6>M}`rPB_!lupy@Oz3?S^!cuya&qpCrT$X*xpD5v)~g{j0XM9MpNUXUx;9u=pa1Ur zqPdMtkIpsEd$h4}K=ko{3hGMzmo#vZYDBDtU3Wd``w(e~fm)QAPBYOFE(f+GXE_qz zEH#F?2yu|)qcY}9j$Wm1b~#(LTI0tN2*1vsdoWrV&)&PA&(Fy9!*}d~d^0nm2 z`g*cD8MtxhZs_~h;KA*M2y_X|J2)mwVc&$)qD2Z59*;bN>`5_cA$7-_YOS`n+EuSs zl=hS^JP1jVhYxGC;*d!`S6<|q>7Bi^)L#aH>8?PBTBAjR!e@o@P@wF#!LG+fdZOKG zAz1n^NCn{IxOEj)>CuRYRaw)*Cg4MykTv(j{^8)?cBH8U! zggMJY)y(re9lKDiT8-&ri0j-nf8M>(XdUmiJL&z0sEoBLlJOx4ltO>pzG?fxpIfhk za3ealVq&jCpRkkxPudJz_;FYGrvQ&jxiDZAb7qWA`&c?cP{;!zh`}kT zN8fqm#Ek5a$Fs46IZ@TV(d!B3%&-`YU1hFma&?|dB7s0>QL>}TT_qL^g&vxkVyj)I(nSXENb;xE7U%7CIcISW3T zT>_RA;CV=nv-dFnXO#{tE$w_cCqh`|1ICTg>ScPoEK{X!Ty))PaEaJAUbjBFV^YVC z=$dWo9d5g2`L_AhH%;xlIr(?L+qJf?ZmrAh^Nn5kr!3vEwraZWdD+ z(QxvOoUd}VEs1zrTRhQ%KBG>JR590Aw6#@4Iv^K~L?1ENP+1^9xTmQAC1#`Oz>Q#r zBro@?97B6uqNOEK*A5QX8tMevTsdiSq`Vzm-z4$S=vQHoLz%0#5q=165Ux=$uujYjc)O(J0ksjkez8D=#d} zF~W@6+wqJZvR5jVeyw{>FnDoO?=HVT;P?A>_4ylMO_WMSv(G)%W^Xh&>#Vju=IZkH z_VP-YI7$4L*+9GK>eR^1>~MhJ!cR(Aa%LTOgHwmnW*1B^bL8ZOe;|_2H|UCcEw;N0l4=xry(NAcpNdSKtC}c9g?EvvnlfaMBH%I`tx_|Ujo)CpFocR}g zhv$}K9@dY}!#9M!AQMzBw8!za%y)m$0 zL_K)RY3IoD=bwKb-x($Kw1s>h-vOQ~r7a_WBlRaw!ks#DIejDLPv5!m!Vz-$De$MV zcXnso*?r+CcL)828X@<9D;6@pL5@Iz2D#^jdwE+fzlPaLkfg0C;m)$rUiQv1;%WGoL>0_QY9H|-yagoqg!fp(W5kOGI`sQu*JekpLaspN=nsCl zjw$4-#61V_0N646Gp_MFQ&`W+A{Mh_NZ}d2!}I_rbC^~3Uru$>r4oW5b zq(YzY?i7A8isyK&)_LLZ>Y&dt*=nv)h&5WJy49_(HTZ(7L*aQgtB0H{-1+_DB}Wp8 zKGoIwo@nK5-K~GxEWPaSvj+}Txf6=(Reg!X(M3xx;4xIg7zR^g;CLc_IksWhpK-e$ z?_oR?;4Op2R_kCzcwMMOAJe%fX`~vZOdhvc+H57EwdLi5Hk+5M5$^mN#?X)$P+hHP z^wvDMwDYaa(u?1i*|)d4e1_&~RewX`=#s_X;W6CKJWJJQ?ZjFI&^q`{%tERja%}HE z&U^d0?>x_C!BT?n5n5HHT7k|3>YWhnK5+s)khSx(81<2{&9bn!^`U9jA)YD7E%tkzytBGw?OR61+?Engr zt^?nFcK{iv!^}!%6}nmsTxSWfo1Q{H$?{Rqq(IpPSAj7|zV&bBGIZI$(H2UNGdSL7 zrlAMlhxU8P<<#&vm!gJ89%i`|NkDLxcGF4d32O=&eNn&x$+v#b{2DFzJ(^`1d6>Eq zUCdl_26)m{XGS)ih68u_RMYS1dioMPj!ww(4=t24vToti76Y$o|%)LyRN)L|Yp|uUQHBC)5wGCwY zqUp^y(MYJNDHLvM8rh34Ld`8$6FGot=vwLs^vzMZ6k)DjJ2FV!F)~R0`=ytdnP<pv>}3S=2Ayxiz^P2B_As=yLY@?~-GHDSPtNBZrZ$gkC3~Y)$pZ(`+7oEaXKzVFf5e!6 zlu*!e`V6!Nxe*)dn?<)gd9u2itRA_Y>KZwMHvRc&iTJrcF^@c$YzO&C5>F6vx|DvK zd$M!vP58+t*thjWqsar*w2_C=Hq`Mj>iGQ&Lg9(zF;@0Ii`G*kl$n*V>>i*-MqVVh zp!MjXL(C6&79Wjnr3qFKChCzHbRV+vZ^_e}v%KHG4R=3=-D)4ZeK zzn$fUooFE5q3)pHz%n1s1^~xoV=nKcqANVY4qlc6Y(`ChXeSn6`SQQXNCbE27@4FU zF`Ogrmm_I7E@Zcu%!}%|c%I3;u%Uh-1ojR=&ZZOmONIIHUx7=fcNY}qq5Cant|`R+ zaMQ#>v)O{1y70-({QR7Pe3wy&1G&t_WMFghRr-PUiXofb>$Tg4D%vCS?KZF1W}kme zyQ)+olFLPsQdK+aqm_xp{$xQ%#eAF1(H5a}IE?5WHdl2kbKb%7wBDN5}#(A|ViHx@luyIxxa@P|yK0Yg{IUM~m_E1U$ zjPd*|VDp^L{=d1y#s`~?Mniv%IjRt9^Yhe2sv2u`zd;W`fLf|mMe6(lgGfDCH6In`AED ztt&tlwL?3QCRisKlWTN4bIm#CdbI8wwdO2Z&s=wQWCyhd+HZy|)^2DIe0zY^VaA@} z!+G#t?f^*2xSK#i z6OunOH?8#-Y<_#@#?CN(sX42Q3WY(d6^@yUL}GE+Y7Rmiezi_x9H^-pfINQ)FAP*w z&xP!iGZKKFqdEJbS`m^<&mn%b7; zSj~g;*1y))K4V6E+iUCBzuMN?HNBVxBZ09e`YC={67oT92 zFKa*^pjg}Aj6FR;@a3c&IWs$(8c(fQak8nYvlGu}#i_=o&ROhydtjD$zW4O7^F{3& z*RQ^@uHnk%Z!aHet`rLdZaJyl@LFqIS65r>Ya3wbtzBKMF!Z?(aua)S?z{(TYMYyD zYaVcoAicb!e4sCRCF(!s3QG<7dHEoDmQy#eCvWrNEF)+>K$aARM+h|lK>9%BMM!2kQ|bXqI} z;m};G#qEYHn~>Uv2}vaKsnsQrZ-c|9us`;q7wOAke-y%q5OCixvf6pz46^b`9drKR z+rPgTl1{IwVs@Ed(K9=u)RoM!HQ*TCc`-`#oZgEbVW!^&xuYbGtc?f=vHD=JXvY7;_qSB&N8NAXh^~3*M2L^cI#Iv0Z`pD8dqAB6d@HJ@9$> z$qe>|!-$1ZKiQ~!PL|B9DlflhuzE#6U!`qE(qpa7Z(5Da@C;8YxDtw8?u(jdT<#8aQmJRe%gqkKCBJSS%4mY--5J5zyJ$7X1Q|77k6IwKHxg zD~rgbg%;_Y;n^QvHFakc4Ky0+9ky9zvGrbWS!qehw&^9aJk}1U*xKRpuVlV4Xf*8} zNkFHo*A*~V70Gk+3#bRpRSLOAArS}Eg}_=o(n*>!r8)rX;~V{sz5(*S=HN(kZ2W8+ z$O|u08;#8tOP$IfFV`7rbqn_f0zMwyP+fWVVDC&m`tW*AK#!9lD*>*ghwHY^4Y^ZRckoKFI$G%it0((Ztpqy}~=W7sATWSJhUT zy2|V`eSs+kjaO*Vg7PsKj}4lP=5vj)x@Xs~hO@m*I&G0%uJwy8wpg`gPPlSMd0Cs< zX3(mQiyDH9U9Pg>p4QgmgR71~Tct|iQK`IQhb#HJ;zcXTjb z3Y>U|FrzE!Yp4>;4`cR>#p6hTQ%EOd^1$Xx??3vQdleE%P^a=M0Q>{X&14Z5_H3S4Aaj2x^@)n7y8qS*J=H;1X zW+-GJEcg4iV$UmtE9l4gdWaK2`U$~Dvx%$G%rv|{4euke&>d-ba~j?Q?R(O2XBs{L zKDBYUDGl!)-GXM1wa@xG4W9$=u7@#{GSk?)I%eDE2OV`vcI z*TI^u<|hCS{3Rf}Qfv z!nI2tuBom^ufTZjf%i`3t6)6f#bRmf8q}GF*QVipaBoipfnA0)C-R~jBq!+VHp zGT`Pkd;m7xczb^u-cJ;;Smwr~O~boKFGDlN`p-I-hGUwnh5dUcM0RnV7%iqqAz}}b zfdPqGGnQiGfm}Lxp6MXZpGS|&QbQ7d?z-?LE+a%2+{N7U!V8!)ZiOD}_?X&q*pc-R%UP;A25>REr@{DS zsd*;5r#U$0m>j%s^sg+(pg40t#V?;m}aW4{~aGSvP5?bmr6p*ly-qiWz=Zf#@Hyx zahW9IM1t`mN@AzX-1=jBQpubmwz?AFj~g%MiQKq)iMcFKgsMy66vR9f>~+9j4|)wv zhW0lyMqDom;NS}c_(I|+gSp53EcZYFFW~S!8bpyXIPZ}$II)cQe)QKE&Uo-n8iPYW zk28Nrw;zZ16I+wmn+M*1ps1s`cT6{YIR1c<5e}k<&tG#5bvW7lH8n)GCYL&%a3p_y z27TP}1oVcfBgRqz_`g{ySj33h)dC`G`u4oEn+ZSjIV1y4KUr_&?}%$YQluOXbR^a&vOj zB3+}yzbba|)3K)3y4u=PmtTG=5s#lo2BT?dd;0=7!eeyk2b-D~n`~xs!|{Q~CyAqW z*YcRHPNlHqnaib0)$GaLBioN}{bbhcMGLyS&cSK1u0z#TO^uP-`?^kcAB_cT>dLF{ zPtE-ret&w7^N4FR=AN5|&jB2Eg9RBGy@$Sx_Zifi{AhL7j=9BS;hLLx) zM#I7mL%Bj`En?cUvLS7j^c&r#`AwCx4CraL#?_V&yT9EoG3E<}3ubhmzv_7HrizM% zUgq;dUfl*2k(R>zJe?$2D6)(7)$gC|dAKfADm_vqDV z8qDKb;EZ?kKExGXep;m|)6U~^X<2^;@N~Kj*K9Hvx+73N1(L(a&!A*e%@MX_Q)1TT zPM71EJfX14kE=J;ud&YRa zSEQ(8Z3Z06c@Ew~tYhUl*B;Ax4n8pYSq2Au z_8h#2xPg~qdcQ~k z`cbBt2`<(euXkcKF11S0P#m5CNgofddZMPOHM1I5F7t`MQ7(~cwWigRxN2PaGM~!S zY@MK@Ys%!RV#$x|beSq7BB@TLsB<BY|jNz1A@-EozsWrs*8n?2&7ij^?o>k)g1EAo|XX)~GLJn}YW)@2ax)gjH zfhmE7XJtcs49`M?xoQjrOBO0@rJ?hyuqDg2#q`1T(F66IFfGJ@BkqArARD1S1Mei3 zk#|8~BxDSIO1}lhp_Q#j3)!}2Dd_rAR`~vmb`pjvSrejn6Y4I}Z#^;m{r6&r%sID$ zEsw=k%z`>hP@g34FUe}QSeCU~sVf!=oGNv~$(GEbUpf4%UmgBhVlIRd9Z)Un60U-l zLMC*pSJAH|8@TFOfIX<@)_k8anWwap*T`X#Y2sbldk+P`t|)u^8w2cs$*D&N&*m z`vI_kU9ii*TlC`^JwIaxPuOHocjdrvp;e}BD-N_O<@>z8ojv5=S04xl{3g9LWX$^& z(7{G#5nGL{8lMQ}JpS@h8Hky2-E@N03^E43b6Tn$lAkl8Yf|nQlsr|bY-3|x?Ndu= ziY)g2#1N`*G%hoys+hU`JVXwUmoY=hXt!Q#MyVoZ=-b6l*EdYdMy`1!V;h?B4oF`=W3+P8_uRNrQ!q*IZ@P*`^%n!qd4-bn=b=rD+ z5bApAxVm2DgC0|*f_~}H4L2ORP)-$TyhQ?$Myr`!l`ie2(?}~V$N}B7XHfspUFFuWkbtY_-x`nXg$hRy5fD3f@h&VS6vzkdPk~t57>od8veU< z>jC39^yM_P`yP~Tdq)~N$N4GXmPxcZn4Q}_SSQqD{@VvCZY7P-Aht(hnj;GAXD zg{I?jWjIH*rLL}pD^muCdd+RNF?wJGlhWcbGo{d@Hn%XA!vlGq|6AG$r{iP}iFP?+5A< z0(G$&HMrmkXyIT-Tf>whc??dyJ`Z_ZY_T$^$4UK%RGekvH#~7ea`em@a@m$A@oaC0 zUSg~yal$A;qgaaN2n%K92nQV;#$4@our#1*7>p1@vA1A9YiV$;vFzen_pZmXi>?2~ z_Uz(%z6kfsLfKu#LHoYPyNZM2UBy9rF2=ix>*3#N=)n1LDArYQTg*jQK^_-LEP`0% zee~V13bmM~GIe*%bADt}a}w*9({>h(6(5Tw9Cm9Z^!6mAd1yt|`v)i1Ap^bn7wV98 z-x-%gY>xR+drT!Y<%KGtJlQYRBumZqiYzR3xOvTkl^>rk*1?P{wq1 zK{@k#i;*m1r>rcIAxD9PpPLKy*KQeZe*LB*0at--(yl814C9--MWT!;=jSJH_g^)a zE5rtY#M#mkrKtteAUhY{-(Y8zbCfbkJhu8-n5sB@^ulRvpewG%7)w{6g=31HhUw}< zpkpxMp*05`{3B2-DgcUwdSodmQ0(aEEXA^w#n?W;QowyI!+o$&whs=Z`v{;p_eP z(>1>CKYa3Mi+nwH`2NQ*E?13yOy8CDByfw8PZ_Meq5Gg>>?l|LiwE2pTi|%c7h#^w z)&%2yo`&}QP@jf=k%snMoRx;Ym4*&{F%Eq-1;x8@HAFzz+HJh4^d46BrTkdnCSYxa zl*0=Aerd#jt{4jOran_7DsZaLs{#?%=rP=MBWdwXgZu&1=@r2Y5&a_ z{~JSx_U501%~USiIsZ@rrX$ytW*)XBEZ_ z2gPg4K?gn_hvK!xP`tK_SxLy&O=Ej5W8t%jovdEK!ExUlyocDu;+=!z-Z4B24P@iq zIVkR(gLYqwrR=pZ>Tb60V5;vs*uFXVm(Uu+!LrffJ~$|r1st^BR0P?ed+8^s9^P5t zl0=L$BC6*oa~>i3NhD;3Axwp?gZrIh_XCKGAA1@Jd1skngzMAYLBB_}@IJ*ckeC%J z^8~<>p<0;Fwln8n*mLU%`oj5hnr+*lAL2^-L#maR1dC{5j5%cu$yt$;8CRxSHx=dQ zTP3!*!C3Dsfn$aG&B&k@}$&1!25=02%b0UA4v~Sdi(8grORVFsHwrp@ay}bK00kH(K3?%_g%|ffy>4d-Xfid<0{TBHJ?@cz_FY_$W z2aUC{#zrWT%bOBvZVrW;n{f`Ih<=~?6Yn)>jpITY?Rgp3NR+6kNF*xYoYimR5jax? zC$4xiOXKlUePyR2nwQezL)$Ja4i$-IBM4cA5yIrF-E)^6M z+N4qooINZmEU-ub--QDDyt`4Uf+7;iMmMgosMD!b%`UmEu&}78pb#o^SPKgZ;mQiS z#{2X?sZrkNfPX&B5HkOvB8iwHzd4Eg$wR++YJ~SW^9{i$;|*?;eE)-(KuHC22U9kB2gY)J2BwkBj#h(~kVtW`Bp)Y~_r#neeAdyujaN za&cuu9x*y*dM2g^c}$UM&bbci-^Y{o3s{qHh3=B$_rzG0qk^`$idM)!^5`0YG0=rim34a zelAZd)lM(>G^^BFI`V@Jozd!`5QbR-)fq>=klO`vrQBdxiL)_2BDT>=>M*?hlF@mX z7ZH~bzk`2Mb%D8Rj^mY+({+KnF zUDI|FN-15^)OESr<;3|DyrUO(yP(LCYG!}eWiFQ!D#~q{OfLsUNwhN^^fcaah?3hO zE4Bu(0nyjAcK}>79A%^uvq2#N^nir`=Z7-rD|1(pNB(9r8oL99keyg2C{%c4+Ng1G zUpQ1TlKt<`$er)KQC?9ViNG&1QBhtIiNG&9a&TmR>kt%SXNxiKXNxgI{q8##(zOSZ zqJsyin~=P?y1KZyw$_{a7wF(&n1Bk-ZbKXZhn*2hJ6MD8w(%9~n`(NvoSQR$E%;l&M0R zMfX;eM}J&VnwLeAV-=+_7kV7hNxvL@9Jr8~-JR)Np-g`UVrKOBwm zk~AOnUicbQY_gL6khwMMQufr&gbEGj)Hy2_XZ+;kqnfZ(rV0k!8`}7W3W-!95DG(9 z`ojbb39dn0#$O`a$j`~vNJR?B+5>O!g(UsYQPVgtGy*R)i<4h}%Zwryx^Uv_ujzks ztdRKza?dO1uc-AflGMn_tt}hQWL?UvheKBcg>vb)X=B#MaF;t_1Y9-K?4LlXmwJD7 zWAfHSb!B5iv^qhG>#Mk{tdE&LAU89&w6P%&Y-lPiYp4$f8yZJP$znLs+CWXBvxq?m znIK{xP+$mlCl7E8Rx|t+kI=}%^Yo`sF^a}s3J2VOkKMhPb0cF88I~hRD$b{$W-Lq( zQc6T9Kea=})RL;|ky~Ko1K=lJ581`I(pi}8S<1^KlGLAk;`U_f@Aafq;>@rnRmJp> z@5JNr-(0@w`MQP#LW#P%7q?vgn^+?L1=5>Mi`&~4;_8cL!-BTfB?h~my!gZk^7z)z zySo?m&+IvW?eVLIyJrq9oH^@r)Npdr z5@K+!29m$X1*r80rsO}2|9g7i!ku`2Fqf^+4r1wW#mDk#18@tUA@FykDn}-rN58|J(w|r@ys{<|q=z&1z&b~NeY%knYbAPd1_mmED;+^W)O&(}sO z_Ya*LxD!e;o!2N84Wyj<^~i!ezumR2ooq=y`Tz0u765XUXTSKqXU^=bj=Q_NuguD< zyTzBt#uGxEI1(rcAqf)PgBB;aLkLo=K+8vIDbPaskOJL=3N6_=xxaH}cC$;N_ujvJ zVK>Z7=A84&^FIFko*mCNWqZn)L!`f=w<-JdcJ{O0O&(_i&m6`xw}2jNpeeBHnsrwaBen|v@)S)0!}#=ml7xt+Yld|SK3Zu9iCw7jrs(~;(uZnxdBBm*_)&GdAg zO6{=NmekkGx4M05g>Gnc^hN*K-97gW*CDNUXztvhq0cX<8@{)v=WPFrqmp_3jSsAE zSbk$Fl?Fuiof{g~J(%qS*eO-C5q_Ng@Oy_i#7Ub#zzylgA`BB5o33&Ha$;&`fJ83O zXY=OI7w`BDQJ&1F@h+}icx5aWkH=zHjMOal=t4lbQkVy$mKw9VR9z|2A{RWjT&>m3 z$k=POYPX00DofYut(&IRsp#tU0w87T4KuoX-Z?|D;rW)fUY$nb*BOFht-4(LFhv7; zFzk3u(Q`pOUI!>WyNWT16csW{rA(0s1h%oWyp7p}U3m%Ks$S4SJ`EQ8KQX28boqkc zigOb45O0}`uEEu8xK4^r_>=QnwQ0efbq$@Px{TQ~)4LcbE{(B>u_tIuD%?Jh((Nld zH=|XP5v;>Jzt-DnHb!MulW{N->jP@2RvASj6GqpBA~BA|#;t9En)3wlPdy;=0#xhvKrq?b)b#w0ZHJp0 zTX&yVzo*L^E-UAkmHUi_CVP63!`FLN|0xFmkO3GZ>#6PaOYL@-*#_*cj+Hi>h5jnk zZ?UH26_Wnyn#-rl*BxnV8yIYFe{qBSs*gKDy(WcMuJT9}rjimKO`9ujvIRVD_lB|G z$y?5hgwhqw;qdw@dHdPna2lZhXS1AQH|BmW>Oa9Trc_l-vFSLa2{VqtkCZ2d@nrJ? zolL2N;tat2dDz|6((>$v`46=>&m^Nx8$cafmRhV9wMH5>X{#+?e{D{xG#-%wR*+#q zYvL3Dk&X6bn+`49@I*tl#b(iD-TpS6!$l%JTRmRC*X!Cj%T%QR91!xOP1b}|DCN`i zJ;>G;mf@DVxLl@INTqRe_g0vh9+&&{UT=?C?GmBag=G#q-v!K>@U9=?P_V#<@>wg{@u)PYGxY>gLpT>l(MV(O zjO>Om`bDr2IHMW>q!OK8H>3LaV|3?+8>7*H-Kt2L%P(asuO9|ajDcHdpp!t>3BW28)alD_tFF!fo#poB?5nzZ_En+}9S_LOIQKowaoE4z(P$5Z zj-sQj?XBHp+wNbtw|927xBq%KvupQTZEYRTJ6x`<)2_N|#&(y(MqPIH>@(bGz-M!v zIh&*0M=;h5&O0(7SnFt#+u>v!Fd=z_q=Nz9&S}=HMVB`C^nC%2eput_v!#3eKBu6p zM5nx4VknV_vo+KwERA?i{003G$5;>V2;01Df?1}<`M(REZO{ZOEZ}{~9|9GM-NSDi zLc0{TZuiP&iAT&ASBf<#jL?-cH%j6z&+2wtkCv9W#Wp18+ABTGHtMu<&ta~vUFLB5 z1jWx%lwPf@HS;cZFYtI$l>%^h3qahh%Z~pUqdEj6nmLfKAmhdfo>QRQNyfA4|B#A* z;xyCc_Ub*;&w8b`wbSo0%}jP~_J$%Y*~VwL)H+h)%1X4~v8Xp&JB&t`&*5BBy}a>s zpC3?|@)~D&NB4yX>PGB9{?w_gPUqH{HB0EQo#A*mwkkDT4J_VP%y zK_@CzipqhU>{BSbnnfD;Oy+nR`?VTlrMl8(HqWk&4w`jdfP%h^a%7E2nX*`GVcN*C z7V}~6zE`lspvdJjfhQ=yHNH&=34Px2_jmn;py!ljX;XM4<&*ggSo#Vf@64y49y^D3 zX6}wzTkHUKMEB6x!G{95q(gc4&$^`_7pU|CEqK~{B=+FjFyJ%i|3%v zoKWQ!Y4m+r5v+g% zhAZ$f5KNwj;qL;T$rRR2E!h5SLqk)xDf9R??;Mj7v}{qSQ_5wDr5<;bP_bzAz=E!| z4zC+f=}SB6=0d7}(u!IDx;Nj|Z1scLx*>%Cj&-ZjR;5x|O7^L;KHtjvCWOqL!m_v% z%}Y);L<@{u6;GtF(x>1Q@4`;%*<4*%N0a9l%$ZO`|LKmeFJyX>MXsCfP+Ys?} znV4%8&0x*RWH7j`c~T=EQnVVQ)N!Na{GdO@ZDh1=Y=|rh1bk-=SZWQ%TEPExge_Ha zrH1g!Q85e;4i#D+zw42|}Jw&TL9)9!)Pl z;RM+>nyssQaP7H|Rj1=pvDl;fOSql_$n?DC7^@h;?|<1Dk5$)J%^T=@chlfyjSXu< zXE3>4ZTr&c7NgC2T0g7z3?rg^C*6q15+Zvo6IQ{bL;*#?bl{X7maPiLeRPh=>R^6s z6n%8d?eTaB>h5I=8kX7Z?sywCMS>dCl%jqq%xrI09cVDH>oL^dne7HV5LT4IWI5Z7Gqr%cyTgeXVMZ?r2RaedFTMGw)9( z!cps&7C%vGw9{&!Dee~6MZ&vgk*^um-gmk>XGxeac~f%QK>sgR9sJiRTfaZ2aYsDf z%UPGT(4(%$^Pxu-x@b?145ubUNZu;qmKZ}R>?&6Eq+SJkEkC~T4--i#x%e+ir?66F zb~v`pj?c6xfxm8-TW2PE*4p)U3eMF~xRS5{Ii1qibk z4%o6%DoIyIH@DR7?>+s$`upd(9I9FeT1Gfr?G4`3>Qb9B0|)A%EPzyY2WPt-S*;tT z!}7+kvCT_bnI|)+m`y=o;AfMm{aw2Wo1=FH=KVM{h7{L3n5B@(i6-L~UtUXaak^td zBA7zq0P>uAwDUAaKVGV-)g1JE`~r6Ly(^dBovCfI z)#si9Fl6tAn;Fx3k^)9R> zR*L!KvU@W1%?-7gdr#ZJ7m;rj?rNC=o#o^$Vdj^@nAOALDrR8WzR!Pwjledt4NQMT z_H(8Nx3?@?oefmR5>r-m}6ov(2;st1rXqzXPiC z70t%jSV=g3KA{EV+2@aw9&aj%aZ-T;+2W4)Nn*s5P#!|RbG%`t(Zw$=ahvR?^`z$5 zks4QNk=y5`XKt}ewK&iir&e?)e3=5e%&bDfOt~1E`9FCyUsQ^6DX9((h3JUM`nOhh1v=_$mnm_ zUA!2hnt?0wh~V%jTPB6#fT%GQ8`TCh!)4_8tFhGK`GJ(KjeY0Kf+f6g`zC zd1oIehhAvfnCo*;0)B}?q;3h;INqR^G1sU*WcI_(FP9((mbr%V(BI@9$eb4L*%9s^ z(dOP`k$34|kiEy>3vca=ZEEz7U|rw~r=P?u5w~fSS$O#2HkpS;3>G{R^&cg-A@Sy8 z=J=)24^me!$3HniD(2pU7S4~B!r{zwRr_-@kG+2+wtRuzw5IX~2E(3xyT!$8o}qSH7_iv@>^1*HzXzB?S9fwoC@lW7_faF9EI zRb*oirC3FRLlR>p6;S=csAPTn7>=Wr_Yn$LKOD2+OZYx=py&p?WFwv@xB7?7K?d#$ z(6qzIkLB?sm9nO=y>9TVfH@*AhkWAEWC3oSD+*6{0wO$>dLdowT;A+oR^{mPQnhf$ z6G*PF=vVL@IN&xn6&E7y);-kfnq9x8VvduK{^P1%(@zO=#tqdfTzgSyhQ1jaGfEuDa^B<(V^T*a;6I zx!Gh|)=OSys&^bpSHtN6{6& z&)iIZj_Cequ1-u;$lwyTK=JDj|_jz&BSFN;Uxk#OX~xzsO?<^IsSG+!wm6;WQs~?=jr@LS zGhRb`I)jy9h=kHQP>zhvZfDWj^JYJH#8lt{mV6v7hBN|h8F}7gOx2bbnp!&IF=Jnd zd6FQ%gYX#<{DG_5fAib6Q{frpAHq~_TsCcY!Wfh1R#RQ%A+(S~&v8BC55susR;<); zu?#+)KCBe?NWr+igGn#ybcebJ7iOrjysBg$-s;hr~O*4EE==lf#M9U(h*?{i23wb$2=Tp|f zO>R$ADEHf)8)jNVVzIJZwQ*5&TYKxRb9+wzMMwKkUu)}gYn7)y+LY}+67hOhXOv9< z%88@@>y{>EW;L3?v8VHLtT7S*!C}2@Z-H@4h`i}JIoxBj$Sjk?J(eFR5SNp&C5WHZ z7z~DiWSPBIX|dMV)ckxI(=kdaqa8RF5QH63=C7|L2T_VAmum*oE|+ryItp1#p-QUK zrZD#hde%AYj@+dP??+7LC@|OwHW8;jV$pj=a*pdA#E3%}@yR%OXl&gm{55Dly&3!G zWuBuEz(FQDD>d@k2g?}?a*Cv&332;*#VYwn)Z@RGUzU@V(eQWIl6ib0XD*cK$|}Uq z=NQt+JV#Nyt<27)_ct|7Cr1zEX1L}hS}_2^-Wy$dstbAt0hkrXdy#CwqFk}klXnXt zCyKtxT^W5peAU?3CzzX4?~{j^9x6j}%uI48_gj>Enf;7B#NKl|ILVBEkFreGI;J=o z|L?Sp?E~4CN9V14B%AGa+H|dcYqiqpZK(Hpit> zg<2+$>-0fMg|wKa@1<$<`jNz~rWDdov`R&l3A5dc6^vm8r|KNdOcLE#UK@pP%1r$c zU6}unv*1hk{sDZS_1&{M z>&LZGT#H?UzA|Ce3Qig`2~Jswj=ja|D#9{(OUfQwe@)2XDC6@988}K;GttO&N1#(5 z3zOIS`yxAggUeH-e2ml1x8wdzg|YEin^G2&;cp8y|1svB4<5bc(GQUY;^N6LA6DDk zl)E55r|~s_<}m^$%!+(m1v}v=lSUi(SMbyf0b!FznND^M#=kFMzt290{qESTqvTbt z`}*emXcl5L*Wv;A*0?=FUwix)hK-Ko`6_P8 zosRLGeGG|Xb%o-D@uY>k_!8tp#6zh>{Hl>-BWMGgP9+joFFeLMZog2d6itC(lR_n@ zW++su=3uZ{rBWb2f7wHAtqWw#a&o0~L0j9y%M?o=X=_^`BhN5R@&&E!4{ukbm-&57 zvfO~I$?sp@qo`Wp_czI?2idn!DA4%@r~EF=5i2*uu0%m+yO0-rUw+^ygB0rHFXQ+U z-nOybyshjN=VDdZyeu}4aC}lfrnk`@N7Z4Cy~|yPT5A1hQ(fHys~dN~(U<#IaVd)O zVWACFkxu5(71u^%QDG$mLyHu_Y@k=>$1_ED8~BQ*zq<9aTiNljzCzyCvEA(V=VScX z-XGQ7I=|vw_=;@F_+@!6RJfF4W0LeKfBU6+bP>=#v&tx_Y{9bZ7dcr5r96Nc# z(pz)?B&uV#Mp~2rU{xxcqqn9)p|d*Mx1vuXx%gRTonE0#==3S25&<(_(Iz}=A)fVr zqr1)hoOtH$3x$HgP-x#=>V>1Zud=}!vACi_EUpP=srzBUy^N=x-O;uK@+ddJE)6+h zQfq1rY`&KcJe-wEi@4{SK*Q|Gn5>`jrwStSvr$U3{FXGE37EcRIrAIhjdvkiU#V7i z$C=+yPm!~ZznlmKcXsuh9SX*t*C|xhCSzKmU~42cgXeZ2I+DP1^1c@=AxH29+L)ZF zTsRYj?2kPArCM4Sna$Q><^Wm4x0o!8+v79QhxTK5BJW&!&XETEsc$@T&fGhImie*M zVxGa<7j!xor*ErT>Gnjd219RP_;RR6Gdeq8T5DVLa%bnvnppgTel*ijgH)-aKIkKt zV}xwx0k;;AE}TtSP%|fs1VQ1Hj_a9|tWMJW_rEWt;VIp7%KUvn{0Rm3&!4?J7>x2i zs}zfqUT;c*$emc6^7xWsNhP(Oyn=mscF|qH#TRaZ517 zb`ROo*xR@@$TkuQ;*?Qr{sB!H8w~^oYI2A1_{h5Y;-2~_hmO%r8BD+G5`94 zm_~C!wMN|)BYNiDPzz#|bagx!I}NJm?;8*`(lamWl*)7wIq^GG!PSC? zu`4rP13b>a3Tq&n*i2-f7M6@R$#zGT|-=fY55LLZ0jA4)#gW9=;8DusYT=6n^rZRg7(jRvY5Gs zZ#E%5INrmlsU~y#q8(4w1FNpS?&%$KZmp~981LZ3A~C0LuUg@8am41Ib44;)n~%n= zeTgNucyw=HzH<}Bda!7)seD|&v5>sVx&2@-u{Hp!0JCCe@^Q2k`n-=Lhw&V_>nI*y z4rjYjOS~hyy#~90M;{=(c?V+AcsvrhbhhaMu~b^+)*Ia^sZ`7gS68d4aS^EEmjk`& z%ZD990amsqL*a9}yUz{hK9W`USbX}bsf0@RQ~~E&p#UF$#HCyv+~c2lhy|A0_if$?z!3!&u38Em^`! z8}^DBpt~zzMPbo^%Q1-~AhGY?-YTJ|7j}9wkD;&llmHLh#CAxMD?cZ4Df%1B%;i$q zpGeq`m~EinjOLByTCsIztaYQ$8-kzWo^T7YY$_||ok@Dy@p}@Ta+E9xVdcjmuXj^xpw}$JJFDE>A8gs+@dhB;L*X`+S|ydLdJ@#Ha-mo!5Em5+ zbw`waHxHCjf^qIoO@BF}~%Dc0ou&7cEp1pMDWq`@EfS*brTe;B?0 ze)7@so71Tp1Ql;yK02D{)L`LNn$E;19lwKZEPBWBH{zk-*{fKMHKf2T|ljcGfI%SNAC@fJgMj7wf5t zGI>wRy}UlVB^an89}sbch;2hGl4(omVnSZKOB>htx|Nk8txDD4ns;F+95(4ZXLP$~ zhsZCEzeHXu^9?w=&T^Y9;b36rfH5jpsU;#sgG-ap!GTAy7%=oFIH%>%wu%~oC5=c0 zXGuEP>>}R03%+M0Uoe&b+PC_5%-=3iDlQ`TF+e#3q9y)M?t7dd?89W)I}PAYH{uNa z+nrq8of=O48`Jd})AP3#E1&sdwNAI1XqnGAB@F*3+PjEt(IVzU+y@D;^fMTdj2#;{ zBF@MKpDoz9`TJcq{lA!JkG%Zi+izdB>Ye$iYwnp_QZhtdWNP6zeubCD0P#Bi`!IRJ z<7Qe5_kI>*{NLRB#?E({XO6t_?AveuY}Px2*;@|{iiDj+%KQaqc0aWNtQxQLzmE`M zBF-Gcz3VXU+c0hyYxiv!ujXLILqQneRo=m2W35O|K~NAjW?O$0X$Wao=BVG5mPutt zLxI4~=@bbbKSsLvGj;*1g8KdSJl-L4UA$SLkme3i{ZfUZInFF5*Nt2d@P~NR4Y|d< zfIqN*A+L|w0yOFK*&RU~fnJW;dRUC_4O|l@oWB^GV+|R=GF=7Rn#sx1qif2TY%bp5 zC~%>JW-m2Ln=A;di;lXT&Q+OGq8^hGWpT?Ir`to_c?r@R4}6yDP$^`5z+>N7EK{gD zQlEV`yek-p3JCd#u>nFOuxBpybLO-1asvIHVlz6p4DbM3Q#lfo<^6n&%PUIx&rlYNbtKLEY%KQ~bKuoi&!7bqO;hDdDQY=+ zlPxn*&m5Gu!dp3EzA(~KE2Givo>LTuyyw z2%Q{-uQNBl#;YFTQ6uDTky#@1s+cc{$`nVGfX57zn~N1Pl*h224Q_WEYy$E*Vva7z_sxUHWr;yv;?PRY9N$l!Ue@H&yDh#R)v`F1k3(+PY(#f27_dhMTcAQ zCd&4tmtNR&y36XQC>I6{#%_PI_v}C*LINJ=sr7w|gi38fx@p{zYTubi%`qCyp@3yL zRgGLLqh4n`r7FFvi}`P&YguA8=;57RBkcrdLyhm(}ten3+f{4>XS^(%e8dih29g@Y0$k(QRp z`>RczsIoO_y&|DQWgZNN`pm9csZjVBzZg*Dl@=M!OPLt$vw7k@ihm(;!z-Ez3AhT9 z-+_uq)<0b!nej3;P{(*S&4eO1kvVfxRDuW%6nBnU%H(V@PCcMk$z{g1{mET@)ApyE zM)dI6D)jRjsxO&2?c%D&5u;i&_F-9tFlaOOMq~XZcU)Lng8JWbQP^f0j3xSw_FyIQ zhc+K>-Cei%#;C&`^oB2nJt91o!a0Z z4wl{_i7yRS&$l4Q!eCue8(0_ymRhx*wEbEkj1Mf8ylO+KWc}~W~Tsd9psS?XYkJA6rlB@(_|iok81NP zot@M22oea@axY&UMf;N#H8m^H2qkCsxtz;tYnP!j1~d~XxYXbIM_8Oc);k#^+$dtP zlq(-W;0dS^=1Jx|a`dlcX}Lxsa>Ehk%Pw)d>olKh$vJcoHAttLqexMDm$*Idtphon z4#IKhRhT{4^~m^5G0%zIApy(aY~+n!6N5l06j`Jd23n<#8=})~?t1mbO68~j;u*Qx zVjk~Vk-%cHt?gkNmoKM&$*ye+GXM$CW^%_kYMK(Vnyg>{bUX-Nqyqnf51bu~3?x;c zD(Yf4qSYNOh-BlBZDmWxdbiX4JQ@u7%w{yc26$hE$7DdOp~}ZZ)rzV?z0Okl&E5U7 zimJP>Rr1+FXG3|U9H#6yfTC|~Z*R;#xt@`&f1)wl-kxoIf;vCo|F=^qtI7s286* z{2aAXCNr+@PoCE|ZC_Qxh!IAO8acJ;ebc62RNXiqUcPThd4iwqP$F| zs<5d|{jt<^vm;bdCZNw_qa)uRVULrY?JT;t{V=~zsChPoEXmRvN=kBDnUTv^CO!nn_ba^Q0cNBQafr=ES_{s)*Pd03t&Ilcl8Qvjzin-{e-FE#^<$7EU51nFuv@pP<~ zKGwtanSbs_dCOi!+c}X)b#)|iPTQokVvM&I9PpQdYRJo7Y*f1uy$~VZ*q^*>{v-p( zj!uYBjsX@zx9m(4#^;w>H4?A7ZClt`Cr49*NN88T zug8o;L6JjY>hkrU7Y-*CGF3+?ux6LR*KM_B&DN}~dX7=6KZjJ73*B0UPpxmXdlyxk z7)hTd3^(>A2KRCZ88(Fq(W#GR>})jBK0m2febDy7<^Rh0pquGX-0fwK*J#d=%z^FJz@0dxSkUkZDL9KLa$SU5dD zOrso+vFCSbO3vSpY?0&Nx8r2ZRbk6yQ})MW@JnQh%E}6KNp}g$#WcnH4UeM5<*0d5 zn9Idtxm+ADbehqaPBZc@tQNwT*KT2H(0w|gkW=}9sej}Jz?;Fse$-`CNhfV$b@UmXhN#C+#bw0_2zYOTc%s%=QXdB2LgZ!Dyp9(hcWDic^2&g2y&MSI0xjY_=)jZNrJI7!& z9z#{b1M65VptUhmd(SFnA7yQkid@gfz%Uk{Z z%SFsJ(5D___Hx=Js*@f z&THEi&75K|nqnLY@1-oY(ePQ>i5iwZ|1j%tHyf-=>+3G*u`PbMt$l%nxr+65u7y~- zgkJ|A2)rx&lmjH{5b(L*7~WH}-z4Tv=J%i7`4sbE8?nB@{Jw=)KECG>VrwOb$;!LV zxSL$TJbw4tcQaSqedgWFdeU&$nYb_W0DUXj0VE(nQBjj6;Zj)ys+%d}JtX%rxtDMG zJz4cN!ME<@tH>?I3N9zh1~+*!HA$Sq9g*TRgFG|ALNLDxP=WM9pSRa$t}$r5P|nUH znD?9-cZc24>2}ZJUvsWZSZNkjz=El80Y@k`pwsE3NCB@DLOmuYF5=6J&MBVFW^Od8 zQm&M1-7Wn!)zvACXp zKUU0zm~|gA<~AcHek0Zo!8j1nl-(mg@|o;MKI8q!XFqoBkA3!I*Z$Zu$i?ftsbpX+ zvMYKcVL&YzsJ`{SL^7}rOtUYHPjtqo*7@4F3AJBhw)zx}DEgFp;6~`^i5m#NtJ@Gd8$Ek1M zp7NG?Du1n3&0R|$n{uoFd@c6xC1z1Eja)ezSsy~q_h&$E7osRFHn}g%F?alM?7xf^S%{+X38FM@7;oUhx9L>z(+~5fF zehX>1VByP+m+{^ic`18&EpsCoq7RV;%zX@RZ`;Bhzh?iUS{A+1eD#=a(JL)i^JXn1 zz82=mT+<@v-8Rw!DgJYu%=6$uYe1`+*{wR zexj+Vv$Lt`iPene=y#bBWS=4LY$QYOqYMqXPoHk6slH{&iaTm*8VDR-qa?=`n#`|G z%t75cVXd<3k(tp8`-GRz)@#AjQ8n&7sibrLXC_IfAJi94I>&T|*}R&Ze>TqcjmuN> zU52Q#^n+1FMCY2DL{>mDHLBGcM_SztI-O4>_UW|^j-~}Bqk$&3%w*-|(021TP5D;$Xs#8BT;v4vBA3g>_T71jz6SeX!MzY)8F%1< z9pPtcrG`yHr^l9k`@qj`lxyVtVvET%FH6pQ9!14Ny}cjpeSk}0r?#JcmjCpo3`%Le zPtNM;erxmo_d7egflA0C9RG=44L{=#o>lNksq)Hheiv*BWZkU$%X3X@+-|?os2@yE zUvhCM=pAV9cxlsfTq+uQ#cs2&Y(YJ%H7>ndyRxzIf|^YaBZKyHmh)eUHToOog`%*= zd>+$yup*WKd>5f#V?JX3Wj;9j*s<9MU4TmsGnXNA_zFUp6Vysj&4#bP&h6)MHD#CJ zUPv#=-;2iqVPn~`R8V^mbSLy4=DjxqSN4vM_FftLJ&7<#n_>E2c_p75_2{EWmO>FX zVe?|>XE7!r#)KiTz&UI>3GPH)Nh%Q|xP_#cum0MWdxvC>Qmb;8aq06nQS;w8uBT9X zLD+LlKZlX9cff-u-{Ik3k!t2K;$hx>eqQd^q~@>Wc4iJ$MG-cw^_OFplG|}TqFLyR zJkf^*ku*FUL2?PH`}3bk{LiF4_a?8IT7~&<%SCVZVg&HVH*(Lxf)x%l6u?65-vo`}_)Hke zF6ztN!Y|+Wvci^@tBfUrVnP=Sv3n!&D1(# z(IF4tJKgP{;|_Lc)w&}Nt7}7N?cT1gi|V0hHT2-@lcEvk3;IgT#+LVWPYh}b8RkVZ zxPWEk%ZID)Hn>ahdpTsh73-PP6Wd!FH^j16Ft?RgmYL)#8+x*`mEU3@;)z7c=gUeR(}H#Tx=7oar(2qP zd$QSwmN8FsUyw;H_EHoQJB$nKeT$MLiWFFTjZ`Rp!)~!}XsO-X$-LdRt9HS`kP+x3 zMl*FA>FQi-Gg*<4x~84^d18^zlF=HP9RaxasiGd(7>|HQ_n_<0q*W`p4^Fb`lN#js z+Tkmls&#VJro?WsYX9z9Q7H1x+Wgu*5*Tu5JYuy*CR1eGjybW&Y>TT_fviilBy1{I zmNzW3Ib4Wj!3VO|?`^{UZ5H#~RLw;Lo#$1z-j4pt#R9b$g%r_aoD}ot1i1` zP9nN4(>rikecckf(_=(w02SS3uGZ*7fC2!nVZx%VG4Biv+6{4~uEA#Q1*L0Hb@5xA zb_74U6IjULgRQ0@V(NDvNiILi-1BKJNq>4=&)h_XaEwf};*B@hQ{qAP9E;+DYlqm= zJL%f*sAoTs?)^J++7#$-`LEuYCM(4>Am0=$ReHaSF%(+ z)nt;ysDn3BRp07s(TdbGsg}c3Z#LE%z9nwz!<>yiKQ~lb$>$eOt?S_P`NC3^c2Gq- z@zhp~msf~$gMKtyU-!Ui*&Q{HQzN+>A7@h$ z`6V>JSfkSnR!>bugdD@O=3#~!j)RPc`4!`lF;=0M*lzuei_)4Nu5 zoL==Eb&FUDFy)1h?8up?9g2ljX^W-PGq~PnGgFtMx1+K( z8aa0;vnHGwF>6f}k?7FqoU{>Ph0teI#*B_Cg~3vSAW8M~J<(WJIPaVf2L|UQ&97Fd z%gQQ9Ln5)ir|tQ{YnnT@0PV$Uc5Uy@TryBnVO42cyq{FoEcAx^)Do#urW*_y8ZF4P zrHb^3vh2g@_9M!|;hSKJ1(%r1l8@aL3?oe1R#*>CA(Q1DWQu%d7xj;w&(tmnojx}; z!)Uak4r)CiiA3V2l`}7^uId$q0=&Bj5W5#O&>fk~-Tk3vl~O^N^Gi%>byhy7^&)?j zA3=sHdgsa~z{?s@WiwB`q@!bNINBsCS@CqXxtn)|-Dn;RYGT7SEAT9VMR}M*P;=r` zL5dgDio9u?S5zm2T0x@mipYh`hs@(=9Xs~B&JAvVu(YDwV=@jT$*#k70B1?1Zd}NV zQ#*18sRfX{oBY1;RZD}z9)G(|qkn34Z}*#BWL@aO{1evaR9FbxwT!f>GSva{2mew2G619Ar-5T#!Vr)a$!?!rp+9a z-mO_yU$?7z?tyfY%Zt7N5k((po~^)Sb)Q1!8e2+!GV2dV&O$CiMcds@d%07k@Q8)h zTI=As=*@VHL3>wB?zG&@%n-y0;q%8kGxERAosF5XPjF&p|NAHGM+W*`E=Pq^sqo?n zH8wmU5FTY_a48m#GL?0M@Kq84{Vd!2F|>aUy_v~?=7u1LmqQN6i`p@Zyt6jHT6ymX zcUVw1q{IvS#FWWYUMHQw8#9WO%Vm&Y9N1(%|LXhUoEE{nFPZ-MYzMf4^tIc5pq+9wFv&D7{Fn9M4uX(Da zxf{Siw+>L`Tr#$i{+d~Pm&0jU-iGoPhZEP0q$T< z=Q51{??0yM_j&h?}&5owFHhpssAzR7VdfsN{v^%U0+lr3p zbd%X;H=Cw~)*M7}7CV|HY~E{q33=wa7l7^>19hQtnRb%xNELBWD^Ku{gIP?ixrjxc z9cJ3;Gw5pk>my*l?7#X>bQcMzH7Ayy$boXEgX6Pki20n`g8Mh`__KiM3!b*T|31bW z_y=&svTZ;ZJG_m2cJv=@%K>|1v09h6{eyQ|Q*G@Xi-OgkHeE##f2fZ7bG4A2?-1G$2Su`lyfnwt7#@(3tjy>UFPxp4aR&O@IKMO~(#Y9zAc~@k^YwOeNQ-Rt3;Jje+v}AnP zA!Veg)1-Eu{aI*m*W?sQCAV{Lxh$SA_E{s$e!xJ@w-$GUPp;+wR=h)q7DA`PAh? zLV2}+7OG1CGfuEK(sF6UB-NLel_@pqzAE&c3C1#2D5w3+Z(=SaD(gG}sEhtKL+ za&0`GdeVs-^G7Gt%6D^k*NBsFQJZj>f{nB&uMp`F|B5$7r>4p zDe@~y%e0kW!XyC^RaljBT`A;;TB~Y~Sknr#h5D|0tHmpPBV96Q~D z6I7UKLEyxu7@&!i%^Cl(Ew-p8=PKE3$G@!^HXGT}JB@00C6aRG=lNRUJR~m=jZQzO z!QZa;1zH=kPj6*D;1NsfQi}!n-PUz|uT!U3(a9iApHfo4jNvKYp5KB4k4`jY>%tGFsu0MWk76kD2*|N(o!yVC;Knt67W6CQ+;;*tw0ODa8j`}~^P)BKjW7*+)3(S0dY0%{r? z&zF>!8^mQIKq&yL2F|p2{1lh7K^u#bU)e0GdJj_I;+va@Aoql;Mel1=8Vyt|=2(&S zElQ0%H`hNq5M3IxXAQ1Kg-%b(O3E~nN`n~loP~L!lD&}oUQpft;}+-T?j*^T;8~7+ z_U*TyQRQVqht@i;x_Nt0>lR`L5CEl`imwc2LXZksA%OiOsGtR<*hzR6&!*01)nDc} z{vJc1UJYT=3JzKuTvZ!i9*r)HVB4(qdUuNssd55A3Bs@j^tLFP4V{(E)@Q4y@;vZ| zw0KfGm^?~WQNVw2Emrd65_yfw-0N>x?^D?-k#0?yWS`3cyG+wO0C;J{LIr?>Xqp~< z37Ykj`%<%Q-fD@c5-oMDgHenSYdY`nd~dhO5|^WPr@X90RwlB^c@<0(c?Mp;C)rbT zI<(J=p!IZfslY za(Sc@d6UoB?3#H|W2Wi;^(|*4`?fgsux(BAYBQHA(&_Zw8)iK?6F=4S%_yYJR##m! zla!>F2dif5WD1+*kUxN2sPm^cTt2{jKjVg`mUT`q)Kji|4ZRN5XcE4I{6J4eEGHYI zQ%ccUD;$fTH@m23r*Cn(>{h#dZQsEDWWd-HwoeQ0e5JX$y|cabt=*?wUsK&giOatb z78AKj*?nIo8rfd&&1{c`>x}xRrMi-0!K}{C7gu>#z1Z0`%NOwN8fG!qmPMRElviSm zZJ^zQpj{M|{{&b12}*!}S3$0%5Y&&`{F^!kFH0MMARvVjp!>7ILV?-@ib}vb$;?6_ zTvYAFdq`bV`p_GTUymMZ93FB+Q z__l#k-4o**XA+!DJ~_AoC2+&qOeX4IS9SirQwj*eG0&wHY+Y4DiSYMo!zzTV~XTFmzKUC%w& zy3}j|3bOUojwmV<9pqR~w(-7^&CdgB0*=n<$=IOD7a&(IJ(x^YB~pp&R)wdTOm@)i zjA-=?C=iE%JAi{WU@+QDdVOzb{*{S%RWhEqb}2CTJXs{Mn*&=}JLM*(frf2>9u-TA z$`}SH?n}!sYw`;3uhb~I2UK9~6Hp|SDgmcmlUIHj@oaQAqL~E$^qX&)dwEm0ZEh@f zD(*!L@W3>Z6z;X)1e7tvi53tI`y=I7*x=bjtAwsNlO)FnHaZ?G?!4*ehvqVL!)yd%>?;K4z0$CgwUTOM7p@4VX+lF%4S{MSii_-u?6im%aVr1 z#Q;2E|G5zbf;QXFD=I6TT8#^$0-k@TUV~0JD8i;-(i7SauZ~2aQvfol$5^FQ=;RWy zPnno+w|m1L&uMi%nbX{!Fz}V;)yv9H4(H~aBwk2@T8dsbi2V@#v?S&RBoV~;6~ z_7iO7pPu(Xb_^OaRrDlIu#|r%JcA7dzd!44$*YW$7Xd1k0v=wQ0=t@DDc+P|NM4=h z&CAZw=~atSIG+l5JnNbg!ww_7*^asm~qew4ZdV5W6BlQL8O82Y9Xqh3782z!uA#X4C zehbd$X8vKs-)%s5YlI~)vB}^j1O__!f3Tx6aBu31#}Sr%{Bcr5%Anm=*_}0P@tj;< zi-;4dCzkV2BPJ8e1p+=TC{~EYiedr(9R_U|SSSL(N}V}&4xjf9)M&NVfP@71JMI>( z#$eEBTfRLkwN^-^73GyK^9gbynbrgvUIyGN%%VjOrh1z2W%zbhJ_|mP|1CQF^fiWo(iF|tAMNXS_-uOP2 zTck?6NTu@X zB6{C?p{UHPl$xA@E3u*+eg8s%MRreF}u ztw?yyZU6s=0A4QeSALNR6z~}TVR(t>7M)(SzX;h4Ln@ZNDxrKA+%BL2=xcbNH(yU~ zdJ2N}e`1Tn=T_LwO1ISNL~jkZF(^4NW6I&T&X6b>6BVmvrtCjOe@`nQ#oZf zaVojptg2o-X*ZQBZZ~l%+1)G``Lx*0asXwsr&7rVxy#GS?KqWyR?pp;7k2IDQnYz1 zE)_(bYL8efwY#x#I)le*ogGe0H<-*)!JeOu4Ncmzl(O=t&uoRV>g1USW#rsel@o{|VlQxYVoU0_Kz;U2Y#mLaa8x(L$$T_*@pTjHuddFH!adhrR}ZlzuLPI?$(x#z0F&K!Fpls z)z!7LO(rLtuH_D`VOmx52!O{7X468N-<@rGY^8ENx`^}#ywU3x)LqbC^Bjqux@tpE z^N3bomrmcXprP@IRjaMl414?S;X#8dkcSzoTZhbX=)*UiP|*%phsX(Qfe2;;bhKk^ z$5C?Ym&I5AYZ?Fj@16LTkNGPXyX5SI$-S30=Dy{Ljz3EE6c(3TMFD4{Ohf?26M@IMPHGYd!qya0@FrFtcXIuEv>UCrz{=R4 z!sg*7g}~_ew&6nhQ8a>#n5;A6<{FJ9tq|+Vl_{%jdL%qxcBR3$D=Nh)hj}m(nP#)5 zl=8IAHiX_#R!>q~R(?0l7pSEohf3v@YMfA)*w90TT_X)>%s~-=H~I9~y9g$zq#~zU zA5qARxE;PzR%z3q8|c%A-1 zTd-4$HcLiRkH^zvGCI_1-C)ez+3VE!8ay5GN&D;|T!?+T8w2B4*R#e*tb_PO`D*70VR<)Yz+7}6j_Rc;oigc@_ zazQa7vXM~uB38HD4Vvl(#qznlGJ=9W0yuuQz)eOKGdsySFOajDGZ!j8`amxG-UDBnoIS)i?i`&!J4b3WPB$k3(Nv1%04zO1{yA8Kn)`B$drw$| z2OA%MD}WyWvly>o9@_}#?8T)#yisuduu)}RpD2L-^s7ZH!W3FZLL*R ztv7C4mZ}2`kG_gI?y=cC9=pv=38U44aBa+_RT;-FxocG&Z|7Z^)5gxKTXh$2=V_U{ zmZ_TKtFHR}#H$lAi`i^pjCJfE+LvMfP>#A}Hk)Ms`ig|yK83Zk z#HI52HrF|QJ}3K^=yr7!6RI z`g+PWgp($|?Sc#5+Ovn;xM$DX6R)ttqTIXm{Ur;*6+*CyQyfBODaE&qpH&>!{F)2$ zkQ0+t$~?yf4JTw+W*&a2g}HmFA*zSbzE)CRW;2xODk>yqhuTBEPJ9mgNV;LE&FWA| zr2!>e>BYsb&7*;LJUA8^i}2n$9*)XnZad5!RZJUi2&Y{x$E};_aXpHXFocQ}Jz+7JEt_ z1NF?Nhwdah?~}?9>Go7hWs-Y?KJS*U&MjVGEH%jwU-NNdY`>Xtfrmmne^y++uAoL`n~tDb4RWyeLKA$(iM_(iHbuG zNu@Zfj~Vm6Wx}5@p});-zD_K;4(%`ZAK&ozN6B?wAA>wbUeP1Cb{N-cflZM{#@J*4 zrKyoHSs~V$pDK$V&Pl+6*6|4W^wdYH6ETz~48+|{YMr68q3+JPOzTl5(ydi%3~GIU z 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 F{vUrGuebmJ literal 0 HcmV?d00001 diff --git a/public/assets/icons/account_circle.svg b/public/assets/icons/account_circle.svg new file mode 100644 index 0000000..ce59194 --- /dev/null +++ b/public/assets/icons/account_circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/components/button.js b/public/components/button.js new file mode 100755 index 0000000..2a1e752 --- /dev/null +++ b/public/components/button.js @@ -0,0 +1,215 @@ +/** + * @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(); + } +} \ No newline at end of file diff --git a/public/components/canvas.js b/public/components/canvas.js new file mode 100755 index 0000000..7aa8eac --- /dev/null +++ b/public/components/canvas.js @@ -0,0 +1,31 @@ +/** + * @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); + } +} \ No newline at end of file diff --git a/public/components/menu.js b/public/components/menu.js new file mode 100755 index 0000000..b9a74c9 --- /dev/null +++ b/public/components/menu.js @@ -0,0 +1,44 @@ +/** + * @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.numButtons = 5; + this.user = new UserShower((windowWidth / this.numButtons) * 0, 0, windowWidth / this.numButtons, 50); + this.buttons = [ + // new Button((windowWidth / this.numButtons) * 0, 0, windowWidth / 5, 50, "Account"), + new Button((windowWidth / this.numButtons) * 1, 0, windowWidth / this.numButtons, 50, "Test Data"), + new Button((windowWidth / this.numButtons) * 2, 0, windowWidth / this.numButtons, 50, "Start Test"), + new Button((windowWidth / this.numButtons) * 3, 0, windowWidth / this.numButtons, 50, "Leaderboard"), + new Button((windowWidth / this.numButtons) * 4, 0, windowWidth / this.numButtons, 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 ProfileScreen()); + } else if (this.buttons[1].isPressed()) { + screenManager.setScreen(new TestScreen()) + } else if (this.buttons[2].isPressed()) { + screenManager.setScreen(new LeaderboardScreen()) + } else if (this.buttons[3].isPressed()) { + screenManager.setScreen(new SettingsScreen()) + } + + this.user.draw() + } +} \ No newline at end of file diff --git a/public/components/textbox.js b/public/components/textbox.js new file mode 100755 index 0000000..cebccd7 --- /dev/null +++ b/public/components/textbox.js @@ -0,0 +1,364 @@ +/** + * @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) { + fill(this.textColor); + + // these variables allow me to use the values of x and y while updating them + let i = this.x; + let j = this.y; + + 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) { + 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) { + + } +} \ No newline at end of file diff --git a/public/components/timemenu.js b/public/components/timemenu.js new file mode 100755 index 0000000..047bee3 --- /dev/null +++ b/public/components/timemenu.js @@ -0,0 +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 { + 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 }; + } +} \ No newline at end of file diff --git a/public/components/timer.js b/public/components/timer.js new file mode 100755 index 0000000..950be0a --- /dev/null +++ b/public/components/timer.js @@ -0,0 +1,222 @@ +/** + * @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); + } + } +} \ No newline at end of file diff --git a/public/components/user.js b/public/components/user.js new file mode 100644 index 0000000..a816a4d --- /dev/null +++ b/public/components/user.js @@ -0,0 +1,35 @@ +/** + * @file This file provides a time menu class for editing the length of a test + * @author Arlo Filley + */ + +/** + * 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 UserShower { + constructor(x, y, height, width) { + this.button = new Button(x, y, height, width, ""); + + this.x = x; + this.y = y; + this.height = height; + this.width = width; + } + + draw() { + textAlign(CENTER, CENTER); + + this.button.draw(); + + imageMode(CENTER); + + + tint(255, 0, 255, 126); + image(accountIcon, this.x + this.height / 2, this.y + this.width / 2); + + if (this.button.isPressed()) { + screenManager.setScreen(new AccountScreen()); + } + } +} \ No newline at end of file diff --git a/public/index.css b/public/index.css new file mode 100755 index 0000000..b7e9549 --- /dev/null +++ b/public/index.css @@ -0,0 +1,27 @@ +/* +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); + position: absolute; + margin: 0; +} + +noscript { + display: block; + text-align: center; + + font-family: var(--font); + color: var(--text-color); + font-size: large; +} \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100755 index 0000000..5933f71 --- /dev/null +++ b/public/index.html @@ -0,0 +1,51 @@ + + + + + + + TypeFast + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/index.js b/public/index.js new file mode 100644 index 0000000..5dc26ee --- /dev/null +++ b/public/index.js @@ -0,0 +1,68 @@ +/** + * @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'); + accountIcon = loadImage('./assets/icons/account_circle.svg'); +} + +/** + * 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(); +} \ No newline at end of file diff --git a/public/screens/accountScreen.js b/public/screens/accountScreen.js new file mode 100755 index 0000000..bf793dc --- /dev/null +++ b/public/screens/accountScreen.js @@ -0,0 +1,115 @@ +/** + * @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(windowWidth / 2 - 400, 300, 500, 100, "", false, true, "#000", "#000", "#fff", "#000", "#000", "#fff"), + new Button(windowWidth / 2 - 400, 450, 500, 100, "", false, true, "#000", "#000", "#fff", "#000", "#000", "#fff"), + new Button(windowWidth / 2 + 200, 300, 100, 50, "Login"), + new Button(windowWidth / 2 + 200, 400, 100, 50, "Sign up"), + new Button(windowWidth / 2 + 200, 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", 0, 100, windowWidth, 110); + 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", windowWidth / 2 - 400, 275); + text("Password", windowWidth / 2 - 400, 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); + } + + /** + * + * @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); + } + } +} \ No newline at end of file diff --git a/public/screens/endscreen.js b/public/screens/endscreen.js new file mode 100755 index 0000000..d99b244 --- /dev/null +++ b/public/screens/endscreen.js @@ -0,0 +1,36 @@ +/** + * @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()); + } +} \ No newline at end of file diff --git a/public/screens/leaderboardscreen.js b/public/screens/leaderboardscreen.js new file mode 100755 index 0000000..ef8b57a --- /dev/null +++ b/public/screens/leaderboardscreen.js @@ -0,0 +1,101 @@ +/** + * @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); + } + + 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; + } + } +} \ No newline at end of file diff --git a/public/screens/loginscreen.js b/public/screens/loginscreen.js new file mode 100755 index 0000000..20802f0 --- /dev/null +++ b/public/screens/loginscreen.js @@ -0,0 +1,108 @@ +/** + * @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, user.colorScheme.text, false, + "#000", "#000", true, false + ), + + new Textbox( + 120, 400, 500, 100, 0, true, user.colorScheme.text, false, + "000", "#000", false, false + ) + ] + + this.buttons = [ + new Button( + 100, 200, 500, 100, 0, true, user.colorScheme.buttonBG, 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() { + fill(user.colorScheme.text); + text("Username", 110, 175); + text("Password", 110, 325); + + 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(); + } + + 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); + } + } +} \ No newline at end of file diff --git a/public/screens/profilescreen.js b/public/screens/profilescreen.js new file mode 100755 index 0000000..6837d02 --- /dev/null +++ b/public/screens/profilescreen.js @@ -0,0 +1,111 @@ +/** + * @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); + + 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; + } + } +} \ No newline at end of file diff --git a/public/screens/screenmanager.js b/public/screens/screenmanager.js new file mode 100755 index 0000000..824998e --- /dev/null +++ b/public/screens/screenmanager.js @@ -0,0 +1,41 @@ +/** + * @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) + } + } + } +} \ No newline at end of file diff --git a/public/screens/settingsScreen.js b/public/screens/settingsScreen.js new file mode 100755 index 0000000..1c44857 --- /dev/null +++ b/public/screens/settingsScreen.js @@ -0,0 +1,29 @@ +/** + * @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", 0, 100, windowWidth, 110); + + this.menu.draw(); + + fill(user.colorScheme.text); + text("Test Duration", windowWidth / 2 - 250, 265) + this.timeMenu.draw(); + fill("#000"); + } +} \ No newline at end of file diff --git a/public/screens/signUpScreen.js b/public/screens/signUpScreen.js new file mode 100755 index 0000000..95f2691 --- /dev/null +++ b/public/screens/signUpScreen.js @@ -0,0 +1,109 @@ +/** + * @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); + } + } +} \ No newline at end of file diff --git a/public/screens/startscreen.js b/public/screens/startscreen.js new file mode 100755 index 0000000..6d5aa41 --- /dev/null +++ b/public/screens/startscreen.js @@ -0,0 +1,31 @@ +/** + * @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, windowHeight); + + this.menu.draw(); + + fill(user.colorScheme.text); + } + + letterTyped(key) { + if (key === "Enter") { + screenManager.setScreen(new TestScreen()); + } + } +} \ No newline at end of file diff --git a/public/screens/testscreen.js b/public/screens/testscreen.js new file mode 100755 index 0000000..a96e7e7 --- /dev/null +++ b/public/screens/testscreen.js @@ -0,0 +1,45 @@ +/** + * @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; + } + } +} \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100644 index 0000000..9b94640 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,2 @@ +#/bin/sh +docker build -t "typing-website:Dockerfile" . \ No newline at end of file diff --git a/scripts/run.sh b/scripts/run.sh new file mode 100755 index 0000000..f1995f8 --- /dev/null +++ b/scripts/run.sh @@ -0,0 +1,2 @@ +#/bin/bash +docker run -p 8000:8000 -v /Users/arlo/Code/CS-Coursework/database/dev/database.sqlite:/app/database/dev/database.sqlite -v /Users/arlo/Code/CS-Coursework/public:/app/public "typing-website:Dockerfile" \ \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c7b86d9..4f8b451 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,27 +10,27 @@ use rocket::fairing::{Fairing, Info, Kind}; use rocket::http::Header; use rocket::{Request, Response}; -pub struct CORS; +// pub struct CORS; -#[rocket::async_trait] -impl Fairing for CORS { - fn info(&self) -> Info { - Info { - name: "Add CORS headers to responses", - kind: Kind::Response, - } - } +// #[rocket::async_trait] +// impl Fairing for CORS { +// fn info(&self) -> Info { +// Info { +// name: "Add CORS headers to responses", +// kind: Kind::Response, +// } +// } - async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) { - response.set_header(Header::new("Access-Control-Allow-Origin", "*")); - response.set_header(Header::new( - "Access-Control-Allow-Methods", - "POST, GET, PATCH, OPTIONS", - )); - response.set_header(Header::new("Access-Control-Allow-Headers", "*")); - response.set_header(Header::new("Access-Control-Allow-Credentials", "true")); - } -} +// async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) { +// response.set_header(Header::new("Access-Control-Allow-Origin", "*")); +// response.set_header(Header::new( +// "Access-Control-Allow-Methods", +// "POST, GET, PATCH, OPTIONS", +// )); +// response.set_header(Header::new("Access-Control-Allow-Headers", "*")); +// response.set_header(Header::new("Access-Control-Allow-Credentials", "true")); +// } +// } // Imports for rocket #[macro_use] @@ -63,7 +63,7 @@ fn test() -> &'static str { #[launch] async fn rocket() -> Rocket { rocket::build() - .attach(CORS) + // .attach(CORS) // testing only, should return "Hello world" .mount("/test", routes![test]) // hosts the api routes necessary for the website