diff options
| author | altaf-creator <dev@altafcreator.com> | 2025-11-29 12:48:14 +0700 |
|---|---|---|
| committer | altaf-creator <dev@altafcreator.com> | 2025-11-29 12:48:14 +0700 |
| commit | b107add5afb23a710e3297249e314eeee6bf0563 (patch) | |
| tree | aa37a07ce05ef24ebbb73728871d9ae33b94079c /frontend | |
| parent | 3e0e77713e73b03e98bdfeb9bf8bd9cfa1b6c88e (diff) | |
a lot of progress, frontend, a lot of new backend methods
Diffstat (limited to 'frontend')
| -rw-r--r-- | frontend/dryer_clothes.png | bin | 0 -> 36448 bytes | |||
| -rw-r--r-- | frontend/dryer_off.png | bin | 0 -> 31935 bytes | |||
| -rw-r--r-- | frontend/dryer_on.png | bin | 0 -> 41171 bytes | |||
| -rw-r--r-- | frontend/index.html | 28 | ||||
| -rw-r--r-- | frontend/main.js | 235 | ||||
| -rw-r--r-- | frontend/start/index.html | 84 | ||||
| -rw-r--r-- | frontend/status/index.html | 60 | ||||
| -rw-r--r-- | frontend/style.css | 233 | ||||
| -rw-r--r-- | frontend/timer/index.html | 36 | ||||
| -rw-r--r-- | frontend/washer_clothes.png | bin | 0 -> 37298 bytes | |||
| -rw-r--r-- | frontend/washer_off.png | bin | 0 -> 32719 bytes | |||
| -rw-r--r-- | frontend/washer_on.png | bin | 0 -> 40206 bytes |
12 files changed, 655 insertions, 21 deletions
diff --git a/frontend/dryer_clothes.png b/frontend/dryer_clothes.png Binary files differnew file mode 100644 index 0000000..babfc36 --- /dev/null +++ b/frontend/dryer_clothes.png diff --git a/frontend/dryer_off.png b/frontend/dryer_off.png Binary files differnew file mode 100644 index 0000000..9919a01 --- /dev/null +++ b/frontend/dryer_off.png diff --git a/frontend/dryer_on.png b/frontend/dryer_on.png Binary files differnew file mode 100644 index 0000000..7851dd5 --- /dev/null +++ b/frontend/dryer_on.png diff --git a/frontend/index.html b/frontend/index.html index deceae5..0eeb0b0 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,9 @@ <html lang="en"> <head> <meta charset="UTF-8" /> - <link rel="icon" type="image/svg+xml" href="/vite.svg" /> + <link rel="stylesheet" href="./style.css"> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>frontend</title> + <title>Victoria Hall LaundryWeb</title> <script src="https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js" defer></script> <script> window.OneSignalDeferred = window.OneSignalDeferred || []; @@ -19,7 +19,27 @@ </script> </head> <body> - <button id="notbtn"></button> - <script src="main.js"></script> + <div class="section-container row bg-1" style="height: 164px;"> + <span id="logo">Victoria Hall<br>LaundryWeb</span> + <span id="logo-id">H1</span> + </div> + <div class="section-container row bg-2" style="padding: 8px; gap: 8px;"> + <button class="button button-tab bg-3" disabled>Timer</button> + <button class="button button-tab bg-3" onclick="window.location.href = '/status/'">Status</button> + </div> + <div class="section-container no-pad"> + <span>Loading...</span> + </div> + <script src="/main.js"></script> + <script> + (async () => { + const result = await checkUserStatus(); + if (result != "you got laundry") { + window.location.href = './start/'; + } else { + window.location.href = './timer/'; + } + })(); + </script> </body> </html> diff --git a/frontend/main.js b/frontend/main.js index 51041fe..4f47473 100644 --- a/frontend/main.js +++ b/frontend/main.js @@ -1,23 +1,224 @@ +// --- begin, important constants +const data = { + duration: 1, // will be multiplied by 30 + block: 1, + machine: 2, +} + +const API_URL = "http://localhost:8000" + +// --- permission request const btn = document.getElementById("notbtn") -btn.addEventListener("click", () => requestPermission()) -function requestPermission() { - console.log("Requesting permission..."); - OneSignal.Notifications.requestPermission(); +if (btn) { + btn.addEventListener("click", () => requestPermission()) + function requestPermission() { + console.log("Requesting permission..."); + OneSignal.Notifications.requestPermission(); + } } -document.cookie = "session_key=0" +// --- check user status +// returns true if there exists a timer, and returns false otherwise. +// cookie need to be included. +async function checkUserStatus() { + const response = await fetch(`${API_URL}/check`, { + credentials: "include", + method: "POST", + }); + return await response.text(); +} -const data = { - duration: 2, - block: 1, - machine: 2, +// --- check machine status +// returns a 2d array representing machines +// []: root array +// []: block +// enum: machine status +async function checkMachineStatus() { + const response = await fetch(`${API_URL}/status`, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({block: 1}), + }); + return await response.json(); } -//fetch("http://localhost:8000/start", { -// credentials: "include", -// method: "POST", -// headers: { -// "Content-Type": "application/json" -// }, -// body: JSON.stringify(data) -//}); +// --- timer duration +const minField = document.getElementById("min-field"); +const decBtn = document.getElementById("decTime"); +function increaseTime() { + data.duration += 1; + minField.innerText = (30 * data.duration).toString() + " minutes"; + decBtn.disabled = false; +} + +function decreaseTime() { + data.duration -= 1; + data.duration = Math.max(data.duration, 1); + if (data.duration == 1) { + decBtn.disabled = true; + } + minField.innerText = (30 * data.duration).toString() + " minutes"; +} + +// --- starting a timer +function start() { + fetch(`${API_URL}/start`, { + credentials: "include", + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(data) + }).then(response => response.text()) + .then(data => { + console.log(data); + if (data == "all good bro timer started") { + window.location.href = "/timer/"; + } + }); +} + + +// ------ page specific ----- + +// ---- machine status page + +// --- machines visual +async function startUpdateMachines() { + updateMachines(); + + while (true) { + await delay(60 * 1000); + + await updateMachines(); + } +} + +async function updateMachines() { + const dryer1 = document.getElementById("dryer1-img"); + const washer1 = document.getElementById("washer1-img"); + const dryer2 = document.getElementById("dryer2-img"); + const washer2 = document.getElementById("washer2-img"); + + const machine_imgs = [dryer1, washer1, dryer2, washer2]; + + const dryer1txt = document.getElementById("dryer1-span"); + const washer1txt = document.getElementById("washer1-span"); + const dryer2txt = document.getElementById("dryer2-span"); + const washer2txt = document.getElementById("washer2-span"); + + const machine_txts = [dryer1txt, washer1txt, dryer2txt, washer2txt]; + + const status = await checkMachineStatus(); + console.log(status); + + for (let i = 0; i < status[0].length; i++) { + if (status[0][i] == "RUNNING") { + if ((i + 1) % 2 == 0) { + machine_imgs[i].src = "/washer_on.png"; + } else { + machine_imgs[i].src = "/dryer_on.png"; + } + const now = Date.now(); + const start = Date.parse(status[1][i]); + machine_txts[i].innerHTML = Math.ceil(((start + (status[2][i] * 60000)) - now) / 60000).toString() + " min(s) left"; + } else if (status[0][i] == "FINISHED") { + if ((i + 1) % 2 == 0) { + machine_imgs[i].src = "/washer_clothes.png"; + } else { + machine_imgs[i].src = "/dryer_clothes.png"; + } + machine_txts[i].innerHTML = "Idle" + } else { + if ((i + 1) % 2 == 0) { + machine_imgs[i].src = "/washer_off.png"; + } else { + machine_imgs[i].src = "/dryer_off.png"; + } + machine_txts[i].innerHTML = "Idle" + } + } +} + +// --- current timers +async function startLoadTimers() { + const timers = await fetchTimers(); + + const container = document.getElementById("timer-container") + + const textList = [] + const progList = [] + const endTimestamps = [] + + for (let i = 0; i < timers.length; i++) { + container.innerHTML += ` + <div class="section-container no-pad"> + <h1>Timer #${(i + 1).toString()}</h1> + <div class="machine-container"> + <div class="txtcol-dryer ${timers[i]["machine"] == 1 ? "machine-selected" : ""}"> + <span>Dryer 1</span> + <img src="/dryer_${timers[i]["machine"] == 1 ? "on" : "off"}.png" alt=""> + </div> + <div class="txtcol-washer ${timers[i]["machine"] == 2 ? "machine-selected" : ""}"> + <span>Washer 1</span> + <img src="/washer_${timers[i]["machine"] == 2 ? "on" : "off"}.png" alt=""> + </div> + <div class="txtcol-dryer ${timers[i]["machine"] == 3 ? "machine-selected" : ""}"> + <span>Dryer 2</span> + <img src="/dryer_${timers[i]["machine"] == 3 ? "on" : "off"}.png" alt=""> + </div> + <div class="txtcol-washer ${timers[i]["machine"] == 4 ? "machine-selected" : ""}"> + <span>Washer 2</span> + <img src="/washer_${timers[i]["machine"] == 4 ? "on" : "off"}.png" alt=""> + </div> + </div> + <div class="timer-container"> + <span id="timer-txt-${i}">15:00</span> + <div class="prog-container"> + <div class="prog" id="timer-prog-${i}"></div> + </div> + </div> + <button class="button bg-1" disabled>I have collected my laundry!</button> + </div> + ` + + textList.push(`timer-txt-${i}`); + progList.push(`timer-prog-${i}`); + endTimestamps.push(Date.parse(timers[i]["start_time"]) + timers[i]["duration"] * 60000); + } + + console.log(textList); + console.log(endTimestamps); + + while (true) { + for (let i = 0; i < textList.length; i++) { + const text = document.getElementById(textList[i]); + text.innerText = ""; + const timeRemaining = Math.max(Math.round((endTimestamps[i] - Date.now()) / 1000), 0); + const hours = Math.floor(timeRemaining / 3600); + const minutes = Math.floor(timeRemaining / 60); + const seconds = timeRemaining % 60; + if (hours > 0) text.innerText += hours.toString().padStart(2, '0') + ':'; + text.innerText += minutes.toString().padStart(2, '0') + ':'; + text.innerText += seconds.toString().padStart(2, '0'); + + const prog = document.getElementById(progList[i]); + prog.style.width = ((timeRemaining / (timers[i]["duration"] * 60)) * 100).toString() + "%"; + } + await delay(1000); + } +} + +async function fetchTimers() { + const response = await fetch(`${API_URL}/laundry`, { + method: "POST", + credentials: "include", + }); + return await response.json(); +} + +const delay = (durationMs) => { + return new Promise(resolve => setTimeout(resolve, durationMs)); +} diff --git a/frontend/start/index.html b/frontend/start/index.html new file mode 100644 index 0000000..7567ad5 --- /dev/null +++ b/frontend/start/index.html @@ -0,0 +1,84 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <link rel="stylesheet" href="/style.css"> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Victoria Hall LaundryWeb</title> + <script src="https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js" defer></script> + <script> + window.OneSignalDeferred = window.OneSignalDeferred || []; + OneSignalDeferred.push(async function(OneSignal) { + await OneSignal.init({ + appId: "83901cc7-d964-475a-90ec-9f854df4ba52", + welcomeNotification: { + disable: true, + }, + }); + }); + </script> + </head> + <body> + <div class="section-container row bg-1" style="height: 164px;"> + <span id="logo">Victoria Hall<br>LaundryWeb</span> + <span id="logo-id">H1</span> + </div> + <div class="section-container row bg-2" style="padding: 8px; gap: 8px;"> + <button class="button button-tab bg-3" disabled>Timer</button> + <button class="button button-tab bg-3" onclick="window.location.href = '/status/'">Status</button> + </div> + <div class="section-container no-pad" style="opacity: .5;"> + <h1>Selected Machine</h1> + <div class="machine-container"> + <div class="txtcol-dryer" id="dryer1"> + <span>Dryer 1</span> + <img src="/dryer_off.png" alt="" id="dryer1-img"> + <span id="dryer1-span"></span> + </div> + <div class="txtcol-washer machine-selected" id="washer1"> + <span>Washer 1</span> + <img src="/washer_off.png" alt="" id="washer1-img"> + <span id="washer1-span"></span> + </div> + <div class="txtcol-dryer" id="dryer2"> + <span>Dryer 2</span> + <img src="/dryer_off.png" alt="" id="dryer2-img"> + <span id="dryer2-span"></span> + </div> + <div class="txtcol-washer" id="washer2"> + <span>Washer 2</span> + <img src="/washer_off.png" alt="" id="washer2-img"> + <span id="washer2-span"></span> + </div> + </div> + </div> + <div class="section-container no-pad"> + <h1>Duration</h1> + <div class="input-field"> + <button class="button bg-3" onclick="decreaseTime()" id="decTime" disabled>-</button> + <span id="min-field">30 minutes</span> + <button class="button bg-3" onclick="increaseTime()" id="incTime">+</button> + </div> + </div> + <div class="section-container no-pad"> + <h1>Start</h1> + <div class="section-container row bg-red"> + <div class="flex-center-container"> + <span class="icon">🔔</span> + </div> + <div> + <span>Please allow this website to send you notifications to remind you about your laundry. That's like, the whole point of this website.</span> + <button id="notbtn" class="button bg-redder">Enable Required Permissions</button> + </div> + </div> + <button class="button bg-1" id="startbtn">Start</button> + </div> + <script src="/main.js"></script> + <script> + document.getElementById("startbtn").addEventListener("click", () => { + start(); + }); + startUpdateMachines(); + </script> + </body> +</html> diff --git a/frontend/status/index.html b/frontend/status/index.html new file mode 100644 index 0000000..61a2e06 --- /dev/null +++ b/frontend/status/index.html @@ -0,0 +1,60 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <link rel="stylesheet" href="/style.css"> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Victoria Hall LaundryWeb</title> + <script src="https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js" defer></script> + <script> + window.OneSignalDeferred = window.OneSignalDeferred || []; + OneSignalDeferred.push(async function(OneSignal) { + await OneSignal.init({ + appId: "83901cc7-d964-475a-90ec-9f854df4ba52", + welcomeNotification: { + disable: true, + }, + }); + }); + </script> + </head> + <body> + <style> + .machine-container > div > img { + padding: 6px; + } + </style> + <div class="section-container row bg-1" style="height: 164px;"> + <span id="logo">Victoria Hall<br>LaundryWeb</span> + <span id="logo-id">H1</span> + </div> + <div class="section-container no-pad"> + <div class="machine-container"> + <div class="txtcol-dryer" id="dryer1"> + <span>Dryer 1</span> + <img src="/dryer_off.png" alt="" id="dryer1-img"> + <span id="dryer1-span"></span> + </div> + <div class="txtcol-washer" id="washer1"> + <span>Washer 1</span> + <img src="/washer_off.png" alt="" id="washer1-img"> + <span id="washer1-span"></span> + </div> + <div class="txtcol-dryer" id="dryer2"> + <span>Dryer 2</span> + <img src="/dryer_off.png" alt="" id="dryer2-img"> + <span id="dryer2-span"></span> + </div> + <div class="txtcol-washer" id="washer2"> + <span>Washer 2</span> + <img src="/washer_off.png" alt="" id="washer2-img"> + <span id="washer2-span"></span> + </div> + </div> + </div> + <script src="/main.js"></script> + <script> + startUpdateMachines(); + </script> + </body> +</html> diff --git a/frontend/style.css b/frontend/style.css new file mode 100644 index 0000000..5713576 --- /dev/null +++ b/frontend/style.css @@ -0,0 +1,233 @@ +:root { + --col-1: #93B6B1; + --col-2: #E8DEB6; + --col-3: #B3C9AA; + --col-red-bg: #FFD0D0; + --col-red-fg: #A33939; + --col-washer: #666a83; + --col-dryer: #7c89a0; +} + +body { + margin: 0; + display: flex; + align-items: center; + flex-direction: column; + gap: 16px; + padding: 16px; + font-family: sans-serif; +} + +.section-container { + border-radius: 32px; + padding: 24px; + max-width: 512px; + width: 100%; + box-sizing: border-box; + display: flex; + flex-direction: column; + justify-content: space-between; + gap: 16px; +} + +.section-container > span { + align-self: center; +} + +.section-container > div { + display: flex; + flex-direction: column; + gap: 16px; +} + +.section-container > h1 { + margin: 0; +} + +.no-pad { + padding: 0; +} + +.row { + flex-direction: row !important; +} + +.flex-center-container { + display: flex; + align-items: center; + justify-content: center; +} + +.button { + width: 100%; + height: 48px; + border-radius: 48px; + border: none; + cursor: pointer; +} + +.button:hover:not(:disabled) { + box-shadow: inset 0 0 0 3px #00000033; +} + +.button:active:not(:disabled) { + opacity: .5; +} + +.button:disabled { + cursor: default !important; + color: black !important; + opacity: .5; +} + +.button-tab:not(:disabled) { + background-color: transparent; +} + +.button-tab:disabled { + opacity: 1 !important; +} + +.button-tab:hover { + background-color: var(--col-3); +} + +.bg-1 { + background-color: var(--col-1); +} + +.bg-2 { + background-color: var(--col-2); +} + +.bg-3 { + background-color: var(--col-3); +} + +.bg-red { + background-color: var(--col-red-bg); +} + +.bg-redder { + background-color: var(--col-red-fg); + color: white; +} + +.txtcol-washer { + color: var(--col-washer); +} + +.txtcol-dryer { + color: var(--col-dryer); +} + +.icon { + margin: 16px; + margin-left: 0; + font-size: 3rem; +} + +.machine-container { + display: flex; + flex-direction: row !important; + width: 100%; +} + +.machine-container > div { + flex: 1; +} + +.machine-container > div > span { + text-align: center; + width: 100%; + display: block; + height: 1rem; +} + +.machine-container > div > img { + width: 100%; + display: block; + padding: 11px; + box-sizing: border-box; + margin-top: 6px; + margin-bottom: 6px; + border-radius: 16px; + overflow: visible; +} + +.machine-selected { + font-weight: bold; +} + +.machine-selected > img { + background-color: var(--col-2); +} + +.input-field { + display: flex; + flex-direction: row !important; + flex-wrap: nowrap; + background-color: var(--col-2); + width: 272px; + margin-left: auto; + margin-right: auto; + padding: 8px; + box-sizing: border-box; + align-items: center; + border-radius: 36px; +} + +.input-field > span { + flex: 1; + text-align: center; +} + +.input-field > button { + height: 48px; + width: 56px; + border-radius: 64px; +} + +.timer-container { + height: 124px; + background-color: var(--col-2); + position: relative; + border-radius: 32px; + overflow: hidden; +} + +.timer-container > span { + font-weight: bolder; + font-size: 4.1rem; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, calc(-50% - 8px)); +} + +.timer-container > .prog-container { + position: absolute; + bottom: 0; +} + +.prog-container { + width: 100%; + height: 16px; + background: #00000011; +} + +.prog { + height: 100%; + width: 50%; + background-color: var(--col-1); +} + +#logo-id { + font-size: 4rem; + margin: 0; + font-weight: 900; +} + +#logo { + font-size: 2rem; +} diff --git a/frontend/timer/index.html b/frontend/timer/index.html new file mode 100644 index 0000000..6405843 --- /dev/null +++ b/frontend/timer/index.html @@ -0,0 +1,36 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <link rel="stylesheet" href="/style.css"> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Victoria Hall LaundryWeb</title> + <script src="https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js" defer></script> + <script> + window.OneSignalDeferred = window.OneSignalDeferred || []; + OneSignalDeferred.push(async function(OneSignal) { + await OneSignal.init({ + appId: "83901cc7-d964-475a-90ec-9f854df4ba52", + welcomeNotification: { + disable: true, + }, + }); + }); + </script> + </head> + <body> + <div class="section-container row bg-1" style="height: 164px;"> + <span id="logo">Victoria Hall<br>LaundryWeb</span> + <span id="logo-id">H1</span> + </div> + <div class="section-container row bg-2" style="padding: 8px; gap: 8px;"> + <button class="button button-tab bg-3" disabled>Timer</button> + <button class="button button-tab bg-3" onclick="window.location.href = '/status/'">Status</button> + </div> + <div id="timer-container" class="section-container no-pad"></div> + <script src="/main.js"></script> + <script> + startLoadTimers(); + </script> + </body> +</html> diff --git a/frontend/washer_clothes.png b/frontend/washer_clothes.png Binary files differnew file mode 100644 index 0000000..b16688a --- /dev/null +++ b/frontend/washer_clothes.png diff --git a/frontend/washer_off.png b/frontend/washer_off.png Binary files differnew file mode 100644 index 0000000..0be7233 --- /dev/null +++ b/frontend/washer_off.png diff --git a/frontend/washer_on.png b/frontend/washer_on.png Binary files differnew file mode 100644 index 0000000..7b811a4 --- /dev/null +++ b/frontend/washer_on.png |
