veto

Simple Sinatra voting website.
git clone git://git.knutsen.co/veto
Log | Files | Refs | README | LICENSE

commit 3eaa4f7595bf7cf97b01d00b68759b5986c32b85
parent 2be9226e157219924d645b5a231e4e919d1f937d
Author: knutsen <samuel@knutsen.co>
Date:   Fri, 18 Jun 2021 17:02:43 +0100

Working with Ruby 3.0

Diffstat:
M.gitignore | 1+
MGemfile.lock | 30++++++++++++++++--------------
Mpublic/poll.css | 9+++++----
Mpublic/poller.js | 30++++++++++++++++++++----------
Mpublic/stats.js | 12+++++++++---
Mpublic/styles.css | 53+++++++++++++++++++++++++++++++++++++++--------------
Mserver.rb | 7++++++-
7 files changed, 96 insertions(+), 46 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -4,6 +4,7 @@ # Shh, web stuff tmp/ log/ +.env* ## System and release files ## # OS files diff --git a/Gemfile.lock b/Gemfile.lock @@ -1,28 +1,30 @@ GEM remote: https://rubygems.org/ specs: - bson (4.3.0) - daemons (1.2.6) + bson (4.12.1) + daemons (1.4.0) eventmachine (1.2.7) - json (2.3.0) - mongo (2.6.2) - bson (>= 4.3.0, < 5.0.0) - mustermann (1.0.3) - rack (2.1.4) - rack-protection (2.0.4) + json (2.5.1) + mongo (2.14.0) + bson (>= 4.8.2, < 5.0.0) + mustermann (1.1.1) + ruby2_keywords (~> 0.0.1) + rack (2.2.3) + rack-protection (2.1.0) rack rack-ssl (1.4.1) rack - sinatra (2.0.4) + ruby2_keywords (0.0.4) + sinatra (2.1.0) mustermann (~> 1.0) - rack (~> 2.0) - rack-protection (= 2.0.4) + rack (~> 2.2) + rack-protection (= 2.1.0) tilt (~> 2.0) - thin (1.7.2) + thin (1.8.1) daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) - tilt (2.0.8) + tilt (2.0.10) PLATFORMS ruby @@ -36,4 +38,4 @@ DEPENDENCIES thin BUNDLED WITH - 2.0.1 + 2.2.20 diff --git a/public/poll.css b/public/poll.css @@ -25,6 +25,7 @@ td:first-child { padding: 0 0 0 10px !important; border-top-left-radius: 4px; border-bottom-left-radius: 4px; + border-left: 5px solid var(--fg); } td:last-child { @@ -34,19 +35,19 @@ td:last-child { tr:not(:first-child) { border: none; - background: #f5f5f5; + background: rgba(var(--fg-rgb), 0.05); margin: 0; padding: 0; } tr td:nth-child(2) { - background: #eeeeee; /* Gradient-ish */ + background: rgba(var(--fg-rgb), 0.08) } tr td:nth-child(3) { - background: #e1e1e1; + background: rgba(var(--fg-rgb), 0.10) } tr td:nth-child(4) { - background: #d7d7d7; + background: rgba(var(--fg-rgb), 0.13) } .row { display: block; diff --git a/public/poller.js b/public/poller.js @@ -1,9 +1,18 @@ +const getVarCSS = name => + getComputedStyle(document.documentElement) + .getPropertyValue(`--${name}`) + .trim(); + +const dp = (number, places = 2) => + Number.parseFloat(number).toFixed(2); + +const BASE = getVarCSS('fg-rgb'); +Chart.defaults.global.elements.arc.borderColor = `rgba(${BASE},0.1)`; +Chart.defaults.global.defaultColor = `rgba(${BASE},0.1)`; + const POLL_CODE = window.location.pathname.split('/').slice(-1); -let has_voted = false; -const dp = (number, places = 2) => { - return Number.parseFloat(number).toFixed(2); -} +let has_voted = false; const disable_vote = () => { has_voted = true; @@ -12,16 +21,16 @@ const disable_vote = () => { .val("You've already voted.") .prop('disabled', true) .css({ - background: "#eee", + opacity: 0.5, + color: getVarCSS('fg'), padding: "0 10px" }); $('#submit') .addClass('disabled') .prop('disabled', true) .css({ - background: '#eee', - color: "#888", - border: "2px solid #aaa" + background: getVarCSS('fg'), + opacity: 0.7 }); }; @@ -131,7 +140,7 @@ const cast_button = name => { $('document').ready(() => { update_votes(); - setInterval(update_votes, 1500); // Live view of votes. + //setInterval(update_votes, 6000); // Live view of votes. $.ajax({ url: POLL_CODE + '/has-voted', @@ -155,10 +164,11 @@ $('document').ready(() => { $('#submit').blur(); }, error: e => { + console.log(e); issue(ISSUE.FATAL, ` An error occurred while casting your vote.\n More Information:\n - ${e} + ${e.responseText} `); } }); diff --git a/public/stats.js b/public/stats.js @@ -1,4 +1,3 @@ -Chart.defaults.global.elements.arc.borderColor = '#ddd'; const ALPHA_RANGE = { min: 0.08, max: 1 @@ -9,7 +8,7 @@ const chart_colors = chart => { const N = chart.data.datasets[0].data.length; backgrounds = Array(N); for (let mono = range.min; mono <= range.max; mono += range.max / (N + 1)) - backgrounds[Math.round((mono - range.min) * N / range.max)] = `rgba(0,0,0,${mono})`; + backgrounds[Math.round((mono - range.min) * N / range.max)] = `rgba(${BASE},${mono})`; chart.data.datasets[0].backgroundColor = backgrounds; return backgrounds; }; @@ -66,16 +65,20 @@ $(document).ready(() => { datasets: [{ label: '№ of Votes', data: [], - backgroundColor: [], + backgroundColor: `rgba(${BASE}, 0.05)`, }] }, options: { title: { display: true, + color: '#fff', text: 'Distribution of alternative/other votes.' }, scales: { yAxes: [{ + gridLines: { + color: `rgba(${BASE}, 0.1)`, + }, categoryPercentage: 0.9, barPercentage: 1.0, ticks: { @@ -84,6 +87,9 @@ $(document).ready(() => { } }], xAxes: [{ + gridLines: { + color: `rgba(${BASE}, 0.1)`, + }, ticks: { stepSize: 1 } diff --git a/public/styles.css b/public/styles.css @@ -1,11 +1,33 @@ @import url('https://fonts.googleapis.com/css?family=Rubik:400,400i,500'); +:root { + --bg: #fff; + --fg: #000; + --bg-rgb: 255, 255, 255; + --fg-rgb: 000, 000, 000; +} + +@media (prefers-color-scheme: dark) { + :root { + --bg: #090909; + --fg: #fff; + --bg-rgb: 009, 009, 009; + --fg-rgb: 255, 255, 255; + } + + .github { + filter: invert(1); + } +} + html, body { margin: 0; padding: 0; font-size: 16px; min-height: 100vh; font-family: 'Rubik', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', sans-serif; + color: var(--fg); + background: var(--bg); overflow-x: hidden; } @@ -45,15 +67,17 @@ ul { padding: 5px 10px; margin-top: 20px; border-radius: 5px; - background: #fff; + background: var(--fg); + color: var(--bg); transition: all .1s ease; cursor: pointer; vertical-align: middle; + opacity: 0.8; } #options li input { - background: #111; - color: #eee; + background: var(--fg); + color: var(--bg); border-bottom: 2px solid #222; padding: 0 5px; margin: 0; @@ -65,17 +89,17 @@ ul { } #options li:nth-of-type(2n) { - background: #f2f2f2; + background: rgba(var(--fg-rgb), 0.8); } #options li:hover { - background: #222; - color: #fff; + opacity: 1; + background: var(--fg); } .dark-option { - background: #000 !important; - color: #fff; + background: var(--fg) !important; + color: var(--bg); } #options li span { @@ -107,8 +131,8 @@ label { input[type=submit] { font-weight: normal; border-color: #000; - color: #fff; - background: #000; + color: var(--bg); + background: var(--fg); transition: all .1s ease; } @@ -135,6 +159,7 @@ input[type=button], button { input[type=text] { background: transparent; + color: var(--fg); font-size: 1em; padding: 0; border: none; @@ -182,7 +207,7 @@ input[type=checkbox]:checked + label:before { h1, h2, h3, h4, h5, h6 { font-weight: 500; - color: #000; + color: var(--fg); } h3 { @@ -208,7 +233,7 @@ h3 { footer { margin-top: 2em; - border-top: 1px solid #eee; + border-top: 1px solid rgb(var(--fg-rgb), 0.1); display: flex; align-items: center; height: 4em; @@ -235,10 +260,10 @@ footer span { .home { padding: 10px 20px; cursor: pointer; - color: #fff; + color: var(--bg); text-transform: uppercase; font-size: 11px; - background: #000; + background: var(--fg); border-radius: 4px; float: right } diff --git a/server.rb b/server.rb @@ -114,7 +114,7 @@ get '/share/:code' do end post '/new' do - params[:code] = URI.decode params[:code] + params[:code] = URI.decode_www_form_component params[:code] return nil if poll_exist? params[:code] make_poll( @@ -148,6 +148,11 @@ end post '/poll/:poll/cast' do params[:vote].pseudo_dot! + if params[:vote].strip.empty? + status 406 + return "Cannot cast empty vote." + end + return nil if request.ip != '::1' && POLLS.find(:"$and" => [{:code => params[:poll]}, {:voters => request.ip}]).to_a.size > 0 POLLS.update_one({:code => params[:poll]}, {:"$push" => {:voters => request.ip}}) unless request.ip == '::1'