Create voting page

This page displays all options that have been set on creation. Thanks to
`textFit` the texts in the title and options automatically get sized
correctly. The create poll button has also been renamed to submit-button
to make it more universal on other pages.
This commit is contained in:
Wolvan 2022-01-04 22:25:05 +01:00
parent 5b5dc9d922
commit edff19fb5b
6 changed files with 137 additions and 10 deletions

View file

@ -30,4 +30,9 @@ Execute the scripts with `npm run <script>`
- `build` - Compile the TypeScript Source to `dist/`
- `test` - Runs `lint`, `find-todo` and `mocha` in order
- `debug` - Start the `./src/main.ts` with node and start the debugger on port 9229
- `start` - Run `test` and `build`, then try and execute the `./dist/main.js` in the `./dist` directory to test
- `start` - Run `test` and `build`, then try and execute the `./dist/main.js` in the `./dist` directory to test
## Components
This project makes use of the following components. Thanks a lot to the respective creators:
- [textFit](https://github.com/STRML/textFit)

View file

@ -44,7 +44,7 @@
<span>Multiple Choice</span>
</label>
<br>
<input type="submit" name="submit" value="Create poll" id="create-poll">
<input type="submit" name="submit" value="Create poll" id="submit-button">
</form>
</section>
</section>

View file

@ -4,10 +4,56 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<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">
<script type="text/javascript" src="/static/js/textfit.js" defer="true"></script>
<script type="text/javascript">
const textFitOptions = {
multiLine: true
};
document.addEventListener('DOMContentLoaded', function() {
textFit(document.querySelector(".poll-title"), textFitOptions);
document.querySelectorAll(".poll-option .text").forEach(element => textFit(element, textFitOptions));
});
window.addEventListener("resize", function() {
textFit(document.querySelector(".poll-title"), textFitOptions);
document.querySelectorAll(".poll-option .text").forEach(element => textFit(element, textFitOptions));
});
</script>
</head>
<body>
<h1>Welcome to poll.horse!</h1>
<h2>{{ POLL_TITLE }}</h2>
<header>
<h1>Poll.horse</h1>
<h3>Make voting on things simpler</h3>
</header>
<main>
<section class="error {{ FORM_SUBMISSION_ERROR_SHOWN_CLASS }}">
{{ FORM_SUBMISSION_ERROR }}
</section>
<section class="poll-id">ID: {{ POLL_ID }}</section>
<section class="notepad">
<div class="notepad-border"></div>
<form action="{{ BACKEND_BASE_PATH }}/_backend/vote-form/{{ POLL_ID }}" method="POST">
<section class="poll-title" {{ TITLE_FONT_SIZE_STYLE }}>
{{ POLL_TITLE }}
</section>
<section id="poll-options" class="poll-options">
{{ POLL_OPTION_DIVS }}
</section>
<section class="poll-footer">
<input type="submit" name="submit" value="Vote" id="submit-button">
<a href="/{{ POLL_ID }}/r"><button type="button">Results</button></a>
</form>
</section>
</section>
</main>
<footer>
<ul>
{{ FOOTER_LINKS }}
</ul>
<div class="copyright">{{ FOOTER_COPYRIGHT }}</div>
</footer>
</body>
</html>

View file

@ -76,6 +76,16 @@ main .error.error-visible {
display: block;
}
/* #endregion Error */
/* #region ID */
main .poll-id {
position: absolute;
right: 30%;
top: 20px;
font-family: Arial, sans-serif;
color: #fff;
font-size: 1.75em;
}
/* #endregion ID */
/* #region notepad */
main .notepad {
position: relative;
@ -88,6 +98,7 @@ main .notepad {
}
main .notepad .poll-title {
height: 70px;
font-size: 0.9em;
}
main .notepad .poll-title input {
font-size: 2em;
@ -112,6 +123,58 @@ main .notepad .poll-footer {
padding-left: 10%;
padding-right: 5px;
width: calc(90% - 5px);
position: relative;
}
main .notepad .poll-options .poll-option .input-container {
position: relative;
width: 25px;
height: 25px;
display: inline-block;
vertical-align: top;
top: 13px;
}
main .notepad .poll-options .poll-option input[type="checkbox"],
main .notepad .poll-options .poll-option input[type="radio"] {
appearance: none;
position: absolute;
vertical-align: top;
top: 0;
left: 0;
width: 25px;
height: 25px;
border: 1px solid #b1b874;
border-radius: 5px;
margin-right: 5px;
}
main .notepad .poll-options .poll-option svg.checkmark {
width: 50px;
height: 50px;
border-radius: 50%;
display: block;
stroke-width: 2;
stroke: #cf2828;
stroke-miterlimit: 10;
stroke-dashoffset: 0;
margin: 10% auto;
}
main .notepad .poll-options .poll-option div.checkmark {
position: absolute;
top: -25px;
left: -20px;
display: none;
pointer-events: none;
}
main .notepad .poll-options .poll-option input[type="checkbox"]:checked ~ div.checkmark,
main .notepad .poll-options .poll-option input[type="radio"]:checked ~ div.checkmark {
display: block;
}
main .notepad .poll-options .poll-option .text {
display: inline-block;
vertical-align: top;
width: calc(100% - 25px - 5px);
height: 100%;
}
main .notepad-border {
@ -119,8 +182,8 @@ main .notepad-border {
left: 5%;
height: 100%;
width: 5px;
border-left: 2px solid red;
border-right: 2px solid red;
border-left: 2px solid #cf2828;
border-right: 2px solid #cf2828;
}
/* #endregion notepad */
/* #region notepad-footer */
@ -142,10 +205,10 @@ main .notepad .poll-footer button:hover {
background-color: #b6b6b6;
}
#create-poll {
#submit-button {
background-color: #cf2828;
}
#create-poll:hover {
#submit-button:hover {
background-color: #e94747;
}
main .notepad .poll-footer input[type="checkbox"] {

View file

@ -0,0 +1 @@
(function(root,factory){"use strict";if(typeof define==="function"&&define.amd){define([],factory)}else if(typeof exports==="object"){module.exports=factory()}else{root.textFit=factory()}})(typeof global==="object"?global:this,function(){"use strict";var defaultSettings={alignVert:false,alignHoriz:false,multiLine:false,detectMultiLine:true,minFontSize:6,maxFontSize:80,reProcess:true,widthOnly:false,alignVertWithFlexbox:false};return function textFit(els,options){if(!options)options={};var settings={};for(var key in defaultSettings){if(options.hasOwnProperty(key)){settings[key]=options[key]}else{settings[key]=defaultSettings[key]}}if(typeof els.toArray==="function"){els=els.toArray()}var elType=Object.prototype.toString.call(els);if(elType!=="[object Array]"&&elType!=="[object NodeList]"&&elType!=="[object HTMLCollection]"){els=[els]}for(var i=0;i<els.length;i++){processItem(els[i],settings)}};function processItem(el,settings){if(!isElement(el)||!settings.reProcess&&el.getAttribute("textFitted")){return false}if(!settings.reProcess){el.setAttribute("textFitted",1)}var innerSpan,originalHeight,originalHTML,originalWidth;var low,mid,high;originalHTML=el.innerHTML;originalWidth=innerWidth(el);originalHeight=innerHeight(el);if(!originalWidth||!settings.widthOnly&&!originalHeight){if(!settings.widthOnly)throw new Error("Set a static height and width on the target element "+el.outerHTML+" before using textFit!");else throw new Error("Set a static width on the target element "+el.outerHTML+" before using textFit!")}if(originalHTML.indexOf("textFitted")===-1){innerSpan=document.createElement("span");innerSpan.className="textFitted";innerSpan.style["display"]="inline-block";innerSpan.innerHTML=originalHTML;el.innerHTML="";el.appendChild(innerSpan)}else{innerSpan=el.querySelector("span.textFitted");if(hasClass(innerSpan,"textFitAlignVert")){innerSpan.className=innerSpan.className.replace("textFitAlignVert","");innerSpan.style["height"]="";el.className.replace("textFitAlignVertFlex","")}}if(settings.alignHoriz){el.style["text-align"]="center";innerSpan.style["text-align"]="center"}var multiLine=settings.multiLine;if(settings.detectMultiLine&&!multiLine&&innerSpan.scrollHeight>=parseInt(window.getComputedStyle(innerSpan)["font-size"],10)*2){multiLine=true}if(!multiLine){el.style["white-space"]="nowrap"}low=settings.minFontSize;high=settings.maxFontSize;var size=low;while(low<=high){mid=high+low>>1;innerSpan.style.fontSize=mid+"px";if(innerSpan.scrollWidth<=originalWidth&&(settings.widthOnly||innerSpan.scrollHeight<=originalHeight)){size=mid;low=mid+1}else{high=mid-1}}if(innerSpan.style.fontSize!=size+"px")innerSpan.style.fontSize=size+"px";if(settings.alignVert){addStyleSheet();var height=innerSpan.scrollHeight;if(window.getComputedStyle(el)["position"]==="static"){el.style["position"]="relative"}if(!hasClass(innerSpan,"textFitAlignVert")){innerSpan.className=innerSpan.className+" textFitAlignVert"}innerSpan.style["height"]=height+"px";if(settings.alignVertWithFlexbox&&!hasClass(el,"textFitAlignVertFlex")){el.className=el.className+" textFitAlignVertFlex"}}}function innerHeight(el){var style=window.getComputedStyle(el,null);return el.clientHeight-parseInt(style.getPropertyValue("padding-top"),10)-parseInt(style.getPropertyValue("padding-bottom"),10)}function innerWidth(el){var style=window.getComputedStyle(el,null);return el.clientWidth-parseInt(style.getPropertyValue("padding-left"),10)-parseInt(style.getPropertyValue("padding-right"),10)}function isElement(o){return typeof HTMLElement==="object"?o instanceof HTMLElement:o&&typeof o==="object"&&o!==null&&o.nodeType===1&&typeof o.nodeName==="string"}function hasClass(element,cls){return(" "+element.className+" ").indexOf(" "+cls+" ")>-1}function addStyleSheet(){if(document.getElementById("textFitStyleSheet"))return;var style=[".textFitAlignVert{","position: absolute;","top: 0; right: 0; bottom: 0; left: 0;","margin: auto;","display: flex;","justify-content: center;","flex-direction: column;","}",".textFitAlignVertFlex{","display: flex;","}",".textFitAlignVertFlex .textFitAlignVert{","position: static;","}"].join("");var css=document.createElement("style");css.type="text/css";css.id="textFitStyleSheet";css.innerHTML=style;document.body.appendChild(css)}});

View file

@ -121,10 +121,22 @@ export default function init(router: Router): void {
(program.opts().backendBaseUrl || "http://localhost:" + program.opts().port) + "/_backend/poll/" + id
).then(r => r.json()) as Poll;
if (!poll || poll.error) return res.redirect("/");
const pollOptions = poll.options.map(option =>
`<div class="poll-option">
<div class="input-container">
<input type="${poll.multiSelect ? "checkbox" : "radio"}" name="poll-option" value="${option}"" /><div class="checkmark"><svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><path class="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/></svg></div>
</div><div class="text">${option}</div>
</div>`
).join("");
await displayPage(req, res, "poll.html", {
"POLL_ID": id,
"POLL_ID": poll.id,
"POLL_TITLE": poll.title,
"POLL_OPTION_DIVS": pollOptions,
"BACKEND_BASE_PATH": (program.opts().backendBaseUrl || ""),
"FORM_SUBMISSION_ERROR": req.query.error,
"FORM_SUBMISSION_ERROR_SHOWN_CLASS": req.query.error ? "error-visible" : "",
});
} catch (error) {
console.log(error);