Create results page

The page displays the amount of votes, percentage and bars for quick
visual comparison, as well as a pie chart created with google charts.
This commit is contained in:
Wolvan 2022-01-06 19:52:53 +01:00
parent 2f8f36a7ca
commit dca2cc3eeb
6 changed files with 178 additions and 5 deletions

View file

@ -35,4 +35,5 @@ Execute the scripts with `npm run <script>`
## Components ## Components
This project makes use of the following components. Thanks a lot to the respective creators: This project makes use of the following components. Thanks a lot to the respective creators:
- [textFit](https://github.com/STRML/textFit) - [textFit](https://github.com/STRML/textFit)
- [Google Charts](https://developers.google.com/chart)

View file

@ -7,6 +7,10 @@
}, },
"globals": { "globals": {
"MAX_POLL_OPTIONS": "readonly", "MAX_POLL_OPTIONS": "readonly",
"MAX_CHARACTER_LENGTH": "readonly" "MAX_CHARACTER_LENGTH": "readonly",
"POLL_VOTE_DATA_STRING": "readonly",
"textFit": "readonly",
"google": "readonly"
} }
} }

View file

@ -4,10 +4,44 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ TITLE }}</title> <title>{{ TITLE }}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/css/main.css"> <link rel="stylesheet" href="/static/css/main.css">
<link rel="stylesheet" href="/static/css/result.css">
<script type="text/javascript" src="/static/js/textfit.js" defer="true"></script>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js" defer="true"></script>
<script type="text/javascript">
const POLL_VOTE_DATA_STRING = `{{ POLL_OPTION_VOTES }}`;
</script>
<script type="text/javascript" src="/static/js/result.js" defer="true"></script>
</head> </head>
<body> <body>
<h1>Welcome to poll.horse!</h1> <header>
<h2>{{ POLL_TITLE }} - Result</h2> <h1>Poll.horse</h1>
<h3>Make voting on things simpler</h3>
</header>
<main>
<section class="poll-id">ID: {{ POLL_ID }}</section>
<section class="notepad">
<div class="notepad-border"></div>
<section class="poll-title">
{{ POLL_TITLE }}
</section>
<section id="poll-options" class="poll-options">
{{ POLL_OPTION_DIVS }}
</section>
<section class="poll-footer">
<a href="/{{ POLL_ID }}"><button type="button" id="submit-button">Vote</button></a>
</section>
<aside id="chart"></aside>
</section>
</main>
<footer>
<ul>
{{ FOOTER_LINKS }}
</ul>
<div class="copyright">{{ FOOTER_COPYRIGHT }}</div>
</footer>
</body> </body>
</html> </html>

View file

@ -0,0 +1,69 @@
main .notepad .poll-option {
height: 80px;
width: 100%;
padding-left: 0;
padding-right: 0;
}
main .notepad .poll-option .poll-option-info,
main .notepad .poll-option .progress {
height: 40px;
}
main .notepad .poll-option .poll-option-info {
padding-left: 10%;
border-bottom: 1px solid #b1b874;
}
main .notepad .poll-option .poll-option-info .poll-option-text {
display: inline-block;
vertical-align: top;
height: 100%;
width: calc(100% - 105px);
}
main .notepad .poll-option .poll-option-info .poll-option-votes,
main .notepad .poll-option .progress .poll-bar-text {
display: inline-block;
vertical-align: top;
height: 100%;
width: 105px;
line-height: 40px;
font-size: 1.5em;
text-align: right;
}
main .notepad .poll-option .poll-option-info .poll-option-votes::after {
content: "Votes";
margin-left: 5px;
padding-right: 10px;
}
main .notepad .poll-option .progress {
width: 90%;
padding-left: 10%;
}
main .notepad .poll-option .progress .poll-bar {
vertical-align: top;
display: inline-block;
height: 100%;
width: calc(100% - 60px);
}
main .notepad .poll-option .progress .poll-bar .poll-bar-fill {
height: calc(100% - 5px);
margin-top: 2px;
background-color: #cf2828;
transition: width .3s;
}
main .notepad .poll-option .progress .poll-bar-text {
width: 50px;
padding-right: 10px;
}
aside#chart {
position: absolute;
top: 0px;
right: -200px;
width: 200px;
height: 200px;
}
@media screen and (max-width: 750px) {
aside#chart {
visibility: hidden !important;
}
}

View file

@ -0,0 +1,49 @@
"use strict";
const textFitOptions = {
multiLine: true
};
let drawChart = () => { /* STUB */ };
function domLoaded() {
textFit(document.querySelector(".poll-title"), textFitOptions);
document.querySelectorAll(".poll-option .poll-option-text").forEach(element => textFit(element, textFitOptions));
google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(() => {
const chartOptions = {
backgroundColor: "transparent",
legend: "none",
chartArea: {
width: "90%",
height: "90%",
}
};
const chartEl = document.getElementById('chart');
const chart = new google.visualization.PieChart(chartEl);
drawChart = data => {
chart.draw(
google.visualization.arrayToDataTable(
[["Options", "Votes"]].concat(data)
), chartOptions
);
chartEl.style.visibility = "visible";
};
try {
drawChart(JSON.parse(POLL_VOTE_DATA_STRING));
} catch (error) {
// eh
}
});
window.addEventListener("resize", function() {
textFit(document.querySelector(".poll-title"), textFitOptions);
document.querySelectorAll(".poll-option .poll-option-text").forEach(element => textFit(element, textFitOptions));
});
}
if (document.readyState === "complete" || document.readyState === "loaded") domLoaded();
else document.addEventListener('DOMContentLoaded', domLoaded);

View file

@ -104,10 +104,26 @@ export default function init(router: Router): void {
(program.opts().backendBaseUrl || "http://localhost:" + program.opts().port) + "/_backend/poll-result/" + id (program.opts().backendBaseUrl || "http://localhost:" + program.opts().port) + "/_backend/poll-result/" + id
).then(r => r.json()) as PollResult; ).then(r => r.json()) as PollResult;
if (!poll || poll.error) return res.redirect("/"); 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]) => `
<div class="poll-option">
<div class="poll-option-info">
<div class="poll-option-text">${ option }</div><div class="poll-option-votes">${ votes }</div>
</div>
<div class="progress">
<div class="poll-bar">
<div class="poll-bar-fill" style="width: ${ (votes / totalVotes) * 100 }%"></div>
</div><div class="poll-bar-text">${ Math.round((votes / totalVotes) * 100) }%</div>
</div>
</div>
`).join("");
await displayPage(req, res, "result.html", { await displayPage(req, res, "result.html", {
"POLL_ID": id, "POLL_ID": id,
"POLL_TITLE": poll.title, "POLL_TITLE": poll.title,
"POLL_OPTION_DIVS": pollOptionsDivs,
"POLL_VOTES_TOTAL": totalVotes,
"POLL_OPTION_VOTES": JSON.stringify(Object.entries(poll.votes))
}); });
} catch (error) { } catch (error) {
console.log(error); console.log(error);