diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index a99367d..b7e64b2 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -9,6 +9,8 @@ "MAX_POLL_OPTIONS": "readonly", "MAX_CHARACTER_LENGTH": "readonly", "POLL_VOTE_DATA_STRING": "readonly", + "POLL_BACKEND_URL": "readonly", + "POLL_ID": "readonly", "textFit": "readonly", "google": "readonly" diff --git a/frontend/html/result.html b/frontend/html/result.html index 30e5ce7..2b8856d 100644 --- a/frontend/html/result.html +++ b/frontend/html/result.html @@ -13,6 +13,8 @@ diff --git a/frontend/static/css/result.css b/frontend/static/css/result.css index b17127e..d807143 100644 --- a/frontend/static/css/result.css +++ b/frontend/static/css/result.css @@ -41,7 +41,7 @@ main .notepad .poll-option .progress .poll-bar { vertical-align: top; display: inline-block; height: 100%; - width: calc(100% - 60px); + width: calc(100% - 50px); } main .notepad .poll-option .progress .poll-bar .poll-bar-fill { height: calc(100% - 5px); @@ -51,6 +51,10 @@ main .notepad .poll-option .progress .poll-bar .poll-bar-fill { } main .notepad .poll-option .progress .poll-bar-text { width: 50px; +} +main .notepad .poll-option .progress .poll-bar-text::after { + content: "%"; + margin-left: 5px; padding-right: 10px; } @@ -60,6 +64,7 @@ aside#chart { right: -200px; width: 200px; height: 200px; + visibility: hidden; } @media screen and (max-width: 750px) { diff --git a/frontend/static/js/result.js b/frontend/static/js/result.js index 7fae574..a3ce44d 100644 --- a/frontend/static/js/result.js +++ b/frontend/static/js/result.js @@ -1,5 +1,7 @@ "use strict"; +const POLL_REFRESH_INTERVAL = 5000; + const textFitOptions = { multiLine: true }; @@ -42,6 +44,33 @@ function domLoaded() { textFit(document.querySelector(".poll-title"), textFitOptions); document.querySelectorAll(".poll-option .poll-option-text").forEach(element => textFit(element, textFitOptions)); }); + + let prevResult = null; + async function fetchNewestResults() { + try { + const response = await fetch(POLL_BACKEND_URL + "/_backend/poll-result/" + POLL_ID); + const json = await response.json(); + if (json.error) throw new Error(json.error); + const votes = json.votes; + const totalVotes = Object.values(votes).reduce((a, b) => a + b, 0); + if (!prevResult || Object.entries(votes).some(([key, value]) => value !== prevResult[key])) { + drawChart(Object.entries(votes)); + Object.entries(votes).forEach(([key, value]) => { + const el = document.querySelector("main .notepad .poll-option[option='" + key + "']"); + if (!el) return; + + el.querySelector(".poll-option-votes").innerText = value; + el.querySelector(".poll-bar-fill").style.width = (value / totalVotes * 100) + "%"; + el.querySelector(".poll-bar-text").innerText = Math.round(value / totalVotes * 100); + }); + prevResult = votes; + } + } catch (error) { + console.warn(error); + } + setTimeout(fetchNewestResults, POLL_REFRESH_INTERVAL); + } + setTimeout(fetchNewestResults, POLL_REFRESH_INTERVAL); } if (document.readyState === "complete" || document.readyState === "loaded") domLoaded(); diff --git a/src/frontend.ts b/src/frontend.ts index 7f388e3..fb2d944 100644 --- a/src/frontend.ts +++ b/src/frontend.ts @@ -106,14 +106,14 @@ export default function init(router: Router): void { if (!poll || poll.error) return res.redirect("/"); const totalVotes = Object.values(poll.votes).reduce((acc, cur) => acc + cur, 0); const pollOptionsDivs = Object.entries(poll.votes).map(([option, votes]) => ` -
+
${ option }
${ votes }
-
${ Math.round((votes / totalVotes) * 100) }%
+
${ Math.round((votes / totalVotes) * 100) }
`).join(""); @@ -123,6 +123,7 @@ export default function init(router: Router): void { "POLL_TITLE": poll.title, "POLL_OPTION_DIVS": pollOptionsDivs, "POLL_VOTES_TOTAL": totalVotes, + "BACKEND_BASE_PATH": (program.opts().backendBaseUrl || ""), "POLL_OPTION_VOTES": JSON.stringify(Object.entries(poll.votes)) }); } catch (error) {