diff --git a/config/analysis-pipeline.json b/config/analysis-pipeline.json index db735fd7..12562da6 100644 --- a/config/analysis-pipeline.json +++ b/config/analysis-pipeline.json @@ -144,34 +144,34 @@ "outputPath":"scores.auto", "options":{ "weightedPaths":{ - "countsAuto.placement31":6+1.666, - "countsAuto.placement32":6+1.666, - "countsAuto.placement33":6+1.666, - "countsAuto.placement34":6+1.666, - "countsAuto.placement35":6+1.666, - "countsAuto.placement36":6+1.666, - "countsAuto.placement37":6+1.666, - "countsAuto.placement38":6+1.666, - "countsAuto.placement39":6+1.666, - "countsAuto.placement21":4+1.666, - "countsAuto.placement22":4+1.666, - "countsAuto.placement23":4+1.666, - "countsAuto.placement24":4+1.666, - "countsAuto.placement25":4+1.666, - "countsAuto.placement26":4+1.666, - "countsAuto.placement27":4+1.666, - "countsAuto.placement28":4+1.666, - "countsAuto.placement29":4+1.666, - "autoCounts.hybrid.conePickupGrid":3+1.666, - "autoCounts.hybrid.conePickupCommunity":3+1.666, - "autoCounts.hybrid.conePickupFloor":3+1.666, - "autoCounts.hybrid.conePickupChute":3+1.666, - "autoCounts.hybrid.conePickupPlatform":3+1.666, - "autoCounts.hybrid.cubePickupGrid":3+1.666, - "autoCounts.hybrid.cubePickupCommunity":3+1.666, - "autoCounts.hybrid.cubePickupFloor":3+1.666, - "autoCounts.hybrid.cubePickupChute":3+1.666, - "autoCounts.hybrid.cubePickupPlatform":3+1.666 + "countsAuto.placement31":7.666, + "countsAuto.placement32":7.666, + "countsAuto.placement33":7.666, + "countsAuto.placement34":7.666, + "countsAuto.placement35":7.666, + "countsAuto.placement36":7.666, + "countsAuto.placement37":7.666, + "countsAuto.placement38":7.666, + "countsAuto.placement39":7.666, + "countsAuto.placement21":5.666, + "countsAuto.placement22":5.666, + "countsAuto.placement23":5.666, + "countsAuto.placement24":5.666, + "countsAuto.placement25":5.666, + "countsAuto.placement26":5.666, + "countsAuto.placement27":5.666, + "countsAuto.placement28":5.666, + "countsAuto.placement29":5.666, + "autoCounts.hybrid.conePickupGrid":4.666, + "autoCounts.hybrid.conePickupCommunity":4.666, + "autoCounts.hybrid.conePickupFloor":4.666, + "autoCounts.hybrid.conePickupChute":4.666, + "autoCounts.hybrid.conePickupPlatform":4.666, + "autoCounts.hybrid.cubePickupGrid":4.666, + "autoCounts.hybrid.cubePickupCommunity":4.666, + "autoCounts.hybrid.cubePickupFloor":4.666, + "autoCounts.hybrid.cubePickupChute":4.666, + "autoCounts.hybrid.cubePickupPlatform":4.666 } } }, @@ -181,34 +181,34 @@ "outputPath":"scores.teleop", "options":{ "weightedPaths":{ - "countsTeleop.placement31":5+1.666, - "countsTeleop.placement32":5+1.666, - "countsTeleop.placement33":5+1.666, - "countsTeleop.placement34":5+1.666, - "countsTeleop.placement35":5+1.666, - "countsTeleop.placement36":5+1.666, - "countsTeleop.placement37":5+1.666, - "countsTeleop.placement38":5+1.666, - "countsTeleop.placement39":5+1.666, - "countsTeleop.placement21":3+1.666, - "countsTeleop.placement22":3+1.666, - "countsTeleop.placement23":3+1.666, - "countsTeleop.placement24":3+1.666, - "countsTeleop.placement25":3+1.666, - "countsTeleop.placement26":3+1.666, - "countsTeleop.placement27":3+1.666, - "countsTeleop.placement28":3+1.666, - "countsTeleop.placement29":3+1.666, - "teleopCounts.hybrid.conePickupGrid":2+1.666, - "teleopCounts.hybrid.conePickupCommunity":2+1.666, - "teleopCounts.hybrid.conePickupFloor":2+1.666, - "teleopCounts.hybrid.conePickupChute":2+1.666, - "teleopCounts.hybrid.conePickupPlatform":2+1.666, - "teleopCounts.hybrid.cubePickupGrid":2+1.666, - "teleopCounts.hybrid.cubePickupCommunity":2+1.666, - "teleopCounts.hybrid.cubePickupFloor":2+1.666, - "teleopCounts.hybrid.cubePickupChute":2+1.666, - "teleopCounts.hybrid.cubePickupPlatform":2+1.666 + "countsTeleop.placement31":6.666, + "countsTeleop.placement32":6.666, + "countsTeleop.placement33":6.666, + "countsTeleop.placement34":6.666, + "countsTeleop.placement35":6.666, + "countsTeleop.placement36":6.666, + "countsTeleop.placement37":6.666, + "countsTeleop.placement38":6.666, + "countsTeleop.placement39":6.666, + "countsTeleop.placement21":4.666, + "countsTeleop.placement22":4.666, + "countsTeleop.placement23":4.666, + "countsTeleop.placement24":4.666, + "countsTeleop.placement25":4.666, + "countsTeleop.placement26":4.666, + "countsTeleop.placement27":4.666, + "countsTeleop.placement28":4.666, + "countsTeleop.placement29":4.666, + "teleopCounts.hybrid.conePickupGrid":3.666, + "teleopCounts.hybrid.conePickupCommunity":3.666, + "teleopCounts.hybrid.conePickupFloor":3.666, + "teleopCounts.hybrid.conePickupChute":3.666, + "teleopCounts.hybrid.conePickupPlatform":3.666, + "teleopCounts.hybrid.cubePickupGrid":3.666, + "teleopCounts.hybrid.cubePickupCommunity":3.666, + "teleopCounts.hybrid.cubePickupFloor":3.666, + "teleopCounts.hybrid.cubePickupChute":3.666, + "teleopCounts.hybrid.cubePickupPlatform":3.666 } } }, diff --git a/src/admin/public/js/script.js b/src/admin/public/js/script.js index 0a3dc3c7..8bbb9f93 100644 --- a/src/admin/public/js/script.js +++ b/src/admin/public/js/script.js @@ -125,6 +125,8 @@ async function updateMatches(accessCode) { document.querySelector("#match-list").innerHTML = ""; //rebuild matches view + console.log("all matches") + console.log(allMatches) for (let match of allMatches) { let matchElement = document.createElement("div"); matchElement.classList.add("match"); @@ -160,12 +162,14 @@ async function updateMatches(accessCode) { checkbox.checked = false; //set it to unchecked while processing the request //send a post request with the new match + console.log(match) fetch("/admin/api/setMatch", { method: "POST", headers: { "Content-Type": "application/json", Authorization: accessCode }, + body: JSON.stringify(match), }).then((res) => res.json()).then((success) => { if (success === true) { //if the match is successfully updated on the server-side diff --git a/src/admin/routes/api.js b/src/admin/routes/api.js index b5526c54..ddf84970 100644 --- a/src/admin/routes/api.js +++ b/src/admin/routes/api.js @@ -3,6 +3,8 @@ const ScoutingSync = require("../../scouting/scouting-sync")(); let router = Router(); const config = require("../../../config/config.json"); const { TeamMatchPerformance } = require("../../lib/db"); +const axios = require("axios") +//const {processedManualMatches} = require("../../schedule/public/js/script") const DEMO = false; @@ -87,9 +89,24 @@ router.post("/setMatch", (req,res) => { }); router.get("/matches", async (req,res) => { - res.json({ - "allMatches": await ScoutingSync.getMatches(), - "currentMatch": ScoutingSync.match - }) +//1 check if there is a manual schedu,e +//2 if there is a manual schedule, send it instead of the TBA one +//3 if there isnt send the TBA one + let manualSchedule = await axios.get('http://localhost:8080/schedule/matches').then(res=>res.data) // temp fix + console.log(manualSchedule) + if(Object.keys(manualSchedule).length != 0){ // find a better way to check if its empty + console.log("using manual schedule") + res.json({ + "allMatches": manualSchedule, + "currentMatch": ScoutingSync.match + }) + } else { + console.log("using tba schedule") + res.json({ + "allMatches": await ScoutingSync.getMatches(), + "currentMatch": ScoutingSync.match + }) + } + }) -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/src/app.js b/src/app.js index f297e563..87f24524 100644 --- a/src/app.js +++ b/src/app.js @@ -5,7 +5,7 @@ const chalk = require("chalk"); const fs = require("fs"); app.set("view engine", "ejs"); -let bodyParser = require("body-parser"); +var bodyParser = require("body-parser"); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({extended: true})); @@ -19,6 +19,7 @@ if (fs.existsSync("config/config.json")) { app.use("/admin", require("./admin/admin.js")); app.use("/setup", require("./setup/setup.js")); app.use("/checklist", require("./checklist/checklist.js")); + app.use("/schedule", require("./schedule/schedule.js")); } else { console.log(chalk.cyan.bold.underline("config.json not detected! First time setup flow enabled on server.")) app.use("/",require("./setup/setup.js")); diff --git a/src/schedule/public/css/form.css b/src/schedule/public/css/form.css new file mode 100644 index 00000000..d0506867 --- /dev/null +++ b/src/schedule/public/css/form.css @@ -0,0 +1,111 @@ +#form { + background-color: var(--bg); +} + +#form .input { + background-color: var(--bg-alt); + border: black 2px solid; + color: black; + cursor: pointer; + text-align: left; + border-radius: 8px; + font-size: 1.5em; + padding: 8px 12px; + transition: 0.3s color, 0.3s border; + outline: none; +} + + +#form .input:focus { + border: var(--accent) 2px solid; +} + +#form .label { + position: relative +} + +#form .label > span { + position: absolute; + padding: 0 5px; + left: 25px; + /* background-color: var(--bg-alt); */ + top: 38px; + border-radius: 5px; + font-size: 1.5em; + transition: 0.2s transform; + pointer-events: none; + color: var(--placeholder); +} + +.input:focus + span, .input:valid + span { + transform: scale(0.75) translate(0px, -54px); + color: black !important; +} + +.input { + width: 100%; +} + + +#form { + display: flex; + align-items: center; + justify-content: center; +} + + + +#form .form { + background-color: var(--bg-alt); + border-radius: var(--border-radius); + display: grid; + grid-template-columns: 50% 50%; + grid-template-rows: 20% 30% 30% 20%; + width: 750px; + justify-items: center; + align-items: center; + height: 90vh; + gap: 2.5px; + padding: 20px; +} + +#form .form .title { + grid-area: 1/1/2/3; + margin: 0px 0; + font-size: 2em; +} + +#form .form .save { + grid-area: 4/1/5/3; + font-size: 1.75em; + width: 90%; + background-color: var(--accent); + border-radius: var(--border-radius); + color: var(--bg-alt); + border: none; + margin: 6px 0; + height: 2em; +} + +@media screen and (min-height: 600px) { + #form .form { + width: 350px; + height: auto; + display: flex; + flex-direction: column; + justify-content: space-evenly; + align-items: center; + padding: 20px; + } + #form .form .title { + font-size: 2em + } + #form .form .save { + width: calc(100% - 24px); + margin: 12px 0; + } + #form .input { + width: 100%; + margin: 30px 0px; + } +} \ No newline at end of file diff --git a/src/schedule/public/css/global.css b/src/schedule/public/css/global.css new file mode 100644 index 00000000..6ab477d6 --- /dev/null +++ b/src/schedule/public/css/global.css @@ -0,0 +1,28 @@ +:root { + --bg: #EFEFEF; + --text: #232323; + --bg-alt: #FEFEFE; + --accent: #30a2ff; + --accent-alt: #ff6030; + --error: #ff5166; + --light-gray: #bebebe; + --gray: #757575; + --green: #4caf50; + --placeholder: #a3a3a3; + --border-radius: 16px; + --font: Cairo, sans-serif +} +html { + overflow: hidden; + height: 100%; + position: fixed; +} + +body { + height: 100%; + position: relative; +} + +* { + font-family: var(--font, sans-serif) +} \ No newline at end of file diff --git a/src/schedule/public/css/internal.css b/src/schedule/public/css/internal.css new file mode 100644 index 00000000..bd38a33b --- /dev/null +++ b/src/schedule/public/css/internal.css @@ -0,0 +1,59 @@ +/***************************************************************** + !!!WARNING!!! + EVERYTHING HERE IS VERY IMPORTANT +DO NOT REMOVE OR MODIFY ANYTHING UNLESS YOU KNOW WHAT YOU'RE DOING +*****************************************************************/ +* { + box-sizing: border-box; +} + +body { + margin: 0; +} + +html { + height: 100%; + width: 100%; +} + +#spinner { + height: 300px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: -1; +} + +.main-svg { + border-radius: var(--border-radius); +} + +/* width */ +::-webkit-scrollbar { + width: 10px; +} + +/* Track */ +::-webkit-scrollbar-track { + background: transparent; +} + +/* Handle */ +::-webkit-scrollbar-thumb { + background: var(--gray); + filter: saturate(0.8); + transition: 0.2s background; + border-radius: 999px; + width: 4px; + border: 3px solid rgba(0,0,0,0); + background-clip: padding-box; +} + +/* Handle on hover */ +::-webkit-scrollbar-thumb:hover { + background: var(--text); + width: 4px; + border: 3px solid rgba(0,0,0,0); + background-clip: padding-box; +} \ No newline at end of file diff --git a/src/schedule/public/css/style.css b/src/schedule/public/css/style.css new file mode 100644 index 00000000..db952914 --- /dev/null +++ b/src/schedule/public/css/style.css @@ -0,0 +1,200 @@ +body { + background-color: var(--bg); + overflow-y: auto; + overflow-x: hidden; + } + + #app { + height: 100vh; + width: 100%; + display: grid; + grid-template-columns: 275px auto; + grid-template-rows: 75px 30px auto; + transition: 0.3s visibility, opacity; + opacity: 0; + visibility: hidden; + background-color: var(--bg); + overflow: hidden; + } + + #app.visible { + visibility: unset; + opacity: 1; + transition: visibility 0s 0s, opacity 0.3s 0s; + } + + .access-input { + border-radius: 8px; + border: 2px solid var(--text); + background-color: var(--bg-alt); + color: var(--text); + padding: 8px 12px; + font-size: 1.5em; +} + /* styling for page header */ + #header { + background: var(--text); + height: 100px; + width: 100px; + border-bottom: 0.25vh solid white; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 0 20px; + box-shadow: 0 0 12px -2px #ff6030; + grid-area: 1 / 2 / 2 / 4; + } + /* styling for logo */ + #logo { + height: 95px; + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + grid-area: 1 / 1 / 3 / 2; + gap: 25px; + + background-color: var(--accent-alt); + } + + #logo-img { + height: 60px; + } + + #logo-text { + color: white; + font-size: 2.5rem; + } + + #lock-img { + height: 40px; + } + + #matches { + padding: 20px; + padding-top: 24px; + border: 2px solid var(--text); + border-radius: 10px; + position: relative; + margin-top: 10px; + min-height: 0; + + +} + +.title { + position: absolute; + background-color: var(--bg); + border-radius: 10px; + top: 0px; + left: 40px; + font: 2em/1em "Cairo"; + padding: 0 6px; + transform: translateY(-50%); +} + +#match-list { + height: 100%; + width: 100%; + overflow-y: auto; + overflow-x: hidden; + gap: 12px; + display: flex; + flex-direction: column; + +} + +#matches .match { + display: grid; + width: 100%; + grid-template-columns: calc(.75 * 80px + 14px) repeat(2,1fr); + grid-template-rows: 30% 70%; + border: 2px solid var(--text); + border-radius: 10px; + padding: 10px 15px; + background: var(--bg-alt); +} + +.match .match-select { + width: calc(.75 * 80px); + height: calc(.75 * 80px); + margin-right: 14px; + appearance: none; + background-color: var(--text); + display: grid; + place-content: center; + border-radius: 10px; + align-self: center; +} + + +.match .match-select::before { + content: ""; + width: calc(.4 * 100px); + height: calc(.4 * 100px); + transform: scale(0); + /* border-radius: 16px; */ + transition: 120ms transform cubic-bezier(.37,-0.02,.47,1.77); + background: center url("../img/lock_closed.png"); + /* box-shadow: inset calc(.75 * 80px) calc(.75 * 80px) var(--accent); */ + +} + +.match .match-select:checked::before { + transform: scale(1); + +} + + +.match .match-header { + grid-area: 1 / 1 / 2 / 4; + font-size: 1.2em; + line-height: 1em; +} + +.match .match-teams { + display: flex; + flex-direction: row; + justify-content: space-evenly; + align-items: center; + text-align: center; + font: 1.8em/calc(80px * .75) "Cairo"; + color: var(--bg-alt); +} + +.match .match-teams.red { + background: #ff6666; + border-radius: 10px 0 0 10px; +} + +.match .match-teams.blue { + background: #6666ff; + border-radius: 0 10px 10px 0; +} + + + @media screen and (max-width: 1100px) { + html { + font-size: 75%; + } + + #app { + grid-template-columns: 200px auto; + grid-template-rows: 40px 20px auto; + } + + #dashboard > div.visible { + height: calc(100vh - 40px); + } + + #main-list { + padding: 25px 0; + } + + #side-list { + padding: 25px 0; + } + } + \ No newline at end of file diff --git a/src/schedule/public/css/ui-elements.css b/src/schedule/public/css/ui-elements.css new file mode 100644 index 00000000..d1782ce7 --- /dev/null +++ b/src/schedule/public/css/ui-elements.css @@ -0,0 +1,124 @@ +.modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 99; + background-color: var(--bg-alt); + border-radius: 10px; + padding: 40px; + transition: 0.3s all; + opacity: 0; + visibility: hidden; + display: flex; + flex-direction: column; +} + +.modal.visible { + visibility: unset; + opacity: 1; + transition: visibility 0s 0s, opacity 0.3s 0s; +} + +.modal-blind { + position: fixed; + top: 0; + left: 0; + height: 100%;; + width: 100%; + background-color: black; + z-index: 98; + cursor: pointer; + transition: 0.3s all; + opacity: 0; + visibility: hidden; +} + +.modal-blind.visible { + visibility: unset; + opacity: 0.7; + transition: visibility 0s 0s, opacity 0.3s 0s; +} + +.modal-close { + font-size: 1.5em; + color: var(--text); + cursor: pointer; + position: absolute; + transform: translate(50%, -50%); + top: 25px; + right: 25px; +} + +.modal.large { + min-width: 800px; + min-height: 500px; +} + +.modal.medium { + min-width: 600px; + min-height: 375px; +} + +.modal.small { + min-width: 400px; + min-height: 250px; +} + +.modal.main-center { + justify-content: center; +} + +.modal.alt-center { + align-items: center; +} + +.modal img { + width: 100%; + height: 100%; + object-fit: contain; + border-radius: 10px; +} + +.modal.image > img { + height: 100%; +} + +.modal > div.header { + font-size: 2.5em; + font-weight: bold; + line-height: 1.5em; + margin-bottom: 8px; +} + +.modal > div.text { + font-size: 1.25em; +} + +.modal > button { + border: none; + background-color: var(--accent); + border-radius: 10px; + color: white; + font-size: 1.5em; + padding: 4px 20px; + margin-top: 20px; +} + +.popup { + position: fixed; + top: 0px; + left: 50%; + border-radius: 8px; + color: white; + margin: 0; + font-size: 1.4rem; + line-height: 1.75rem; + padding: 6px 18px; + text-align: center; + transition: 0.6s cubic-bezier(.76,-0.47,.24,1.47); + z-index: 100; + transform: translate(-50%, -100%); + box-shadow: 0 0 2px var(--text); + max-width: 100%; +} diff --git a/src/schedule/public/icons/android-chrome-192x192.png b/src/schedule/public/icons/android-chrome-192x192.png new file mode 100644 index 00000000..9cce4775 Binary files /dev/null and b/src/schedule/public/icons/android-chrome-192x192.png differ diff --git a/src/schedule/public/icons/android-chrome-512x512.png b/src/schedule/public/icons/android-chrome-512x512.png new file mode 100644 index 00000000..27f826c1 Binary files /dev/null and b/src/schedule/public/icons/android-chrome-512x512.png differ diff --git a/src/schedule/public/icons/apple-touch-icon.png b/src/schedule/public/icons/apple-touch-icon.png new file mode 100644 index 00000000..dfa8a225 Binary files /dev/null and b/src/schedule/public/icons/apple-touch-icon.png differ diff --git a/src/schedule/public/icons/browserconfig.xml b/src/schedule/public/icons/browserconfig.xml new file mode 100644 index 00000000..0cfc3dc1 --- /dev/null +++ b/src/schedule/public/icons/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #fefefe + + + diff --git a/src/schedule/public/icons/favicon-16x16.png b/src/schedule/public/icons/favicon-16x16.png new file mode 100644 index 00000000..a6155191 Binary files /dev/null and b/src/schedule/public/icons/favicon-16x16.png differ diff --git a/src/schedule/public/icons/favicon-32x32.png b/src/schedule/public/icons/favicon-32x32.png new file mode 100644 index 00000000..774418b4 Binary files /dev/null and b/src/schedule/public/icons/favicon-32x32.png differ diff --git a/src/schedule/public/icons/favicon.ico b/src/schedule/public/icons/favicon.ico new file mode 100644 index 00000000..e426fc53 Binary files /dev/null and b/src/schedule/public/icons/favicon.ico differ diff --git a/src/schedule/public/icons/mstile-150x150.png b/src/schedule/public/icons/mstile-150x150.png new file mode 100644 index 00000000..cc7b02ac Binary files /dev/null and b/src/schedule/public/icons/mstile-150x150.png differ diff --git a/src/schedule/public/icons/safari-pinned-tab.svg b/src/schedule/public/icons/safari-pinned-tab.svg new file mode 100644 index 00000000..c421bfae --- /dev/null +++ b/src/schedule/public/icons/safari-pinned-tab.svg @@ -0,0 +1,591 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + + + + + + + + diff --git a/src/schedule/public/icons/site.webmanifest b/src/schedule/public/icons/site.webmanifest new file mode 100644 index 00000000..b9319c60 --- /dev/null +++ b/src/schedule/public/icons/site.webmanifest @@ -0,0 +1,19 @@ +{ + "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": "#fefefe", + "background_color": "#fefefe", + "display": "standalone" +} diff --git a/src/schedule/public/img/lock.png b/src/schedule/public/img/lock.png new file mode 100644 index 00000000..f616a204 Binary files /dev/null and b/src/schedule/public/img/lock.png differ diff --git a/src/schedule/public/img/lock_closed.png b/src/schedule/public/img/lock_closed.png new file mode 100644 index 00000000..d5562dc2 Binary files /dev/null and b/src/schedule/public/img/lock_closed.png differ diff --git a/src/schedule/public/img/spinner.svg b/src/schedule/public/img/spinner.svg new file mode 100644 index 00000000..c0e7e57e --- /dev/null +++ b/src/schedule/public/img/spinner.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/schedule/public/js/elements.js b/src/schedule/public/js/elements.js new file mode 100644 index 00000000..7ee2c94a --- /dev/null +++ b/src/schedule/public/js/elements.js @@ -0,0 +1 @@ +const matchElement = document.getElementById("match-list") diff --git a/src/schedule/public/js/form.js b/src/schedule/public/js/form.js new file mode 100644 index 00000000..972302a4 --- /dev/null +++ b/src/schedule/public/js/form.js @@ -0,0 +1,38 @@ +document.querySelector("#form .save").addEventListener("click", async () => { + localStorage.setItem("firstName",document.querySelector("#form .first-name").value); //store form values in localstorage + localStorage.setItem("lastName",document.querySelector("#form .last-name").value); + + if (ScoutingSync.state.offlineMode) { + ScoutingSync.updateState({ //dont await, the network is going to fail + matchNumber: document.querySelector("#form .match-number").value, + robotNumber: document.querySelector("#form .robot-number").value, + scouterId: `${document.querySelector("#form .first-name").value}${document.querySelector("#form .last-name").value}`, + status: ScoutingSync.SCOUTER_STATUS.WAITING + }) + switchPage("match-scouting"); + document.querySelector(".scouting-info").style.display = "block" + } else { + await ScoutingSync.updateState({ + scouterId: `${document.querySelector("#form .first-name").value}${document.querySelector("#form .last-name").value}`, + status: ScoutingSync.SCOUTER_STATUS.WAITING + }) + switchPage("waiting"); + } +}) + +function updateForm() { + try { + document.querySelector("#form .first-name").value = localStorage.getItem("firstName") || ""; + document.querySelector("#form .last-name").value = localStorage.getItem("lastName") || ""; + + if (ScoutingSync.state.offlineMode || !ScoutingSync.state.connected) { //only show manual entry for robot and match number when permenantly offline or temporarily disconnected + document.querySelector("#form .match-number").parentElement.style.display = "inline"; + document.querySelector("#form .robot-number").parentElement.style.display = "inline"; + } else { + document.querySelector("#form .match-number").parentElement.style.display = "none"; + document.querySelector("#form .robot-number").parentElement.style.display = "none"; + } + } catch (e) { + //keep going even if this errors, we need them to be able to input data + } +} \ No newline at end of file diff --git a/src/schedule/public/js/script.js b/src/schedule/public/js/script.js new file mode 100644 index 00000000..48d35134 --- /dev/null +++ b/src/schedule/public/js/script.js @@ -0,0 +1,203 @@ +// matches array +let processedMatches = []; +// authentication +;(async () => { + const authRequest = await fetch("./api/auth").then(res => res.json()) + + if (authRequest.status !== 2) { + const authModal = new Modal("small", false).header("Sign In") + const accessCodeInput = createDOMElement("input", "access-input") + accessCodeInput.placeholder = "Access Code" + accessCodeInput.type = "password" + accessCodeInput.addEventListener("keydown", (e) => { + if (e.keyCode == 13) { + validate(accessCodeInput.value, authModal) + } + }) + authModal.element.appendChild(accessCodeInput) + authModal.action("Submit", async () => { + validate(accessCodeInput.value, authModal) + }) + } else { + await constructApp() + } + + async function validate(accessCode, authModal) { + const auth = await fetch("./api/auth", { + headers: { + Authorization: accessCode + } + }).then(res => res.json()) + + if (auth.status === 1) { + await constructApp(accessCode) + authModal.modalExit() + } else { + new Popup("error", "Wrong Access Code") + } + } + + + // number of matches input; after enter is clicked make match schedule + var matches = 0; + + + let numMatchesInput = document.querySelector("#numMatches"); + numMatchesInput.addEventListener("keydown", function (e){ + if(e.keyCode == 13) { + matches = numMatchesInput.value + console.log(matches); + makeMatchSchedule(matches); + updateMatches(matches); + + } + }) + +})() + +// construct app +async function constructApp() { + + + updateMatches(matches); + + +} + + + +async function updateMatches(matchNumber) { + + //makes a place to store data + for(let i=0; i${i} - ${"MANUAL"}-${"QM" + i} + + +
+
+
+
+
+
+
+
+
+
+ ` + + let checkbox = matchElement.querySelector(".match-select") + checkbox.addEventListener("input", () => { + console.log(checkbox.checked) + if (checkbox.checked) { //if its already selected, do nothing + //disable editable inputs + + let element = document.getElementsByClassName(`m${i}`); + for(team of element) + { + team.setAttribute("contentEditable", false); + } + let totalTeams = getTeams("m"+checkbox.id) + processTeams(checkbox.id, totalTeams); + + + + } else { + //enable editable inputs + let allianceElement = document.getElementsByClassName(`match-teams qm${i}`); + for(element of allianceElement) + { + let element = document.getElementsByClassName('match-team') + for(team of element) + { + team.setAttribute("contentEditable", true); + } + } + } + + }) + + } + +} + +function getTeams(num) { + + let teamArray = document.getElementsByClassName(num); + let processedArray = []; + for(value of teamArray) + { + processedArray.push(value.innerHTML); + } + + // returns team data for match selected in an array of 6 strings + return processedArray; +} + +var processedManualMatches = []; +// make an array which will get filled in tba format + +function makeMatchSchedule(matchTotalNum){ + // creates a blank form with the correct number of matches + processedManualMatches = []; + + // tba formatted data + for (let i=1; i<=matchTotalNum; i++) { + processedManualMatches.push({ + number: i, + match_string: `2023temp_q${i}`, // use temp bc we don't need event keys and don't have one + robots: { + red: [null, null, null], // empty arrays to get filled + blue: [null, null, null], + } + }); + } +} + + +async function processTeams(matchNum, teams) { + // inserts data into the correct spot + let data = await teams; + console.log(data); + let redTeams = []; + let blueTeams = []; + for(let i = 0; i < 3; i++) + { + redTeams.push(data[i]); + blueTeams.push(data[i+3]); + } + + // acccess the correct match object and then adding to properties + processedManualMatches[matchNum-1].robots.red = redTeams; + processedManualMatches[matchNum-1].robots.blue = blueTeams; + + // post request to send schedule to admin + console.log(processedManualMatches); + console.log(JSON.stringify(processedManualMatches)) + fetch('/schedule/matches',{ + method:"POST", + headers: { + "Content-Type": "application/json", + }, + body:JSON.stringify(processedManualMatches) + }).catch(e=>console.log(e)) + +} + +async function getManualMatches() { + return processedManualMatches; + +} + + + + diff --git a/src/schedule/public/js/ui-elements.js b/src/schedule/public/js/ui-elements.js new file mode 100644 index 00000000..231ac169 --- /dev/null +++ b/src/schedule/public/js/ui-elements.js @@ -0,0 +1,162 @@ +class Modal { + blind; // dimmed background element (closes modal on click) + close; // close button element + element; // main modal container element + modalExit; // function to close and destroy the modal + dismissButton; // optional dismiss button element + cancel; // cancel function passed in + + constructor(size, closable=true) { + this.blind = document.createElement("div") + this.blind.classList = "modal-blind" + this.element = document.createElement("div") + this.element.classList = `modal ${size}` + + this.modalExit = () => { + hideFade(this.blind) + hideFade(this.element) + setTimeout(() => { + document.body.removeChild(this.blind) + document.body.removeChild(this.element) + }, 300) + } + + if (closable) { + this.close = document.createElement("i") + this.close.classList = "fa fa-times modal-close" + + this.blind.addEventListener("click", this.modalExit) + this.close.addEventListener("click", this.modalExit) + this.element.appendChild(this.close) + } + document.body.appendChild(this.blind) + document.body.appendChild(this.element) + this.blind.offsetHeight + this.element.offsetHeight + showFade(this.blind) + showFade(this.element) + return this + } + + assignCancel(cancelFunction) { + this.blind.addEventListener("click", cancelFunction) + this.close.addEventListener("click", cancelFunction) + this.dismissButton ? this.dismissButton.addEventListener("click", cancelFunction) : null + this.cancel = cancelFunction + } + + header(text) { + const headerElement = document.createElement("div") + headerElement.innerHTML = text + headerElement.classList.add("header") + this.element.appendChild(headerElement) + return this + } + + text(text) { + const textElement = document.createElement("div") + textElement.innerHTML = text + textElement.classList.add("text") + this.element.appendChild(textElement) + return this + } + + image(src) { + const imgElement = document.createElement("img") + imgElement.src = src + this.element.appendChild(imgElement) + return this + } + + center(horizontal = true, vertical = false) { + if (vertical) { + this.element.classList.add("main-center") + } + + if (horizontal) { + this.element.classList.add("alt-center") + } + + return this + } + + dismiss(buttonText = "Dismiss") { + const buttonElement = document.createElement("button") + buttonElement.innerText = buttonText + buttonElement.addEventListener("click", this.modalExit) + this.cancel ? buttonElement.addEventListener("click", this.cancel) : null + this.element.appendChild(buttonElement) + + return this + } + + action(buttonText, func) { + const buttonElement = document.createElement("button") + buttonElement.innerText = buttonText + buttonElement.addEventListener("click", func); + this.element.appendChild(buttonElement); + + return this + } +} + +function showFade(element) { + element.classList.add("visible") +} + +function hideFade(element) { + element.classList.remove("visible") +} + +class Popup { + static types = { + "error": { + "prefix": "Error: ", + "color": "var(--error)" + }, + + "notice": { + "prefix": "", + "color": "var(--accent)" + }, + + "success": { + "prefix": "", + "color": "var(--green)" + }, + } + popupElement; + + constructor(type, text, duration = 5000) { + this.popupElement = document.createElement("p") + this.popupElement.classList = "popup" + this.popupElement.style.backgroundColor = Popup.types[type].color + this.popupElement.innerText = Popup.types[type].prefix + text + document.body.appendChild(this.popupElement) + this.popupElement.offsetHeight + this.popupElement.style.top = "55px" + setTimeout(() => { + this.popupElement.style.top = "0" + setTimeout(() => { + document.body.removeChild(this.popupElement) + }, 600) + }, duration) + } + setText(text) { + this.popupElement.innerText = text; + return this; + } + setType(type) { + this.popupElement.style.backgroundColor = Popup.types[type].color; + return this; + } +} + +function createDOMElement(tag, classes, id) { + const element = document.createElement(tag) + element.classList = classes + if (id) { + element.id = id + } + return element +} \ No newline at end of file diff --git a/src/schedule/routes/api.js b/src/schedule/routes/api.js new file mode 100644 index 00000000..a58b0ac5 --- /dev/null +++ b/src/schedule/routes/api.js @@ -0,0 +1,28 @@ +const { Router } = require("express"); +let router = Router(); +const config = require("../../../config/config.json"); +const { TeamMatchPerformance } = require("../../lib/db"); + + +router.get("/auth", (req, res) => { + if (config.secrets.ACCESS_CODE === "") { + res.json({status: 2}) + } else if (config.secrets.ACCESS_CODE == req.headers.authorization) { + res.json({status: 1}) + } else { + res.json({status: 0}) + } +}) + + +router.get("/data", async (req,res) => { + res.json(await TeamMatchPerformance.find()); +}) + +router.get("/matches", async (req,res) => { + res.json({ + "allMatches": await ScoutingSync.getMatches(), + "currentMatch": ScoutingSync.match + }) +}) +module.exports = router; \ No newline at end of file diff --git a/src/schedule/schedule.js b/src/schedule/schedule.js new file mode 100644 index 00000000..388ab219 --- /dev/null +++ b/src/schedule/schedule.js @@ -0,0 +1,25 @@ +express = require("express"); + +var schedule = {}; +let router = express.Router(); + +router.use(express.static(__dirname + "/public")); + +router.get("/", (req,res) => { + res.render(__dirname + "/views/index.ejs"); +}) + +router.post('/matches',(req,res)=>{ + + console.log("req body on /schedule/matches") + console.log(req.body) + schedule = req.body; + res.send(200) +}) +router.get('/matches',(req,res)=>{ + res.send(schedule) +}) + +router.use("/api", require("./routes/api.js")); + +module.exports = router; \ No newline at end of file diff --git a/src/schedule/views/index.ejs b/src/schedule/views/index.ejs new file mode 100644 index 00000000..5d5a1206 --- /dev/null +++ b/src/schedule/views/index.ejs @@ -0,0 +1,63 @@ + + + + + + + + SPOT - Manual Schedule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+
Matches
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/src/scouting/scouting-sync.js b/src/scouting/scouting-sync.js index 0cb7b995..7e714482 100644 --- a/src/scouting/scouting-sync.js +++ b/src/scouting/scouting-sync.js @@ -41,6 +41,8 @@ class ScoutingSync { if (!config.secrets.TBA_API_KEY) { console.error(chalk.whiteBright.bgRed.bold("TBA_API_KEY not found in config.json file! SPOT will not properly function without this.")) } + + ScoutingSync.match = (await ScoutingSync.getMatches())[0] || {number: 0, match_string: "", robots: {red: [], blue: []}}; io = require("socket.io")(server);