diff --git a/.gitignore b/.gitignore index 54954c0..4e6d578 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /target -/pkg/opentally.js -/pkg/opentally_bg.wasm +/html/opentally.js +/html/opentally_bg.wasm diff --git a/build_wasm.sh b/build_wasm.sh index 50be928..d4d9df6 100755 --- a/build_wasm.sh +++ b/build_wasm.sh @@ -1,2 +1,2 @@ #!/bin/sh -cargo build --lib --target wasm32-unknown-unknown && /home/runassudo/.cargo/bin/wasm-bindgen --target no-modules target/wasm32-unknown-unknown/debug/opentally.wasm --out-dir pkg --no-typescript +cargo build --lib --target wasm32-unknown-unknown && /home/runassudo/.cargo/bin/wasm-bindgen --target no-modules target/wasm32-unknown-unknown/debug/opentally.wasm --out-dir html --no-typescript diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..f3b6f40 --- /dev/null +++ b/html/index.html @@ -0,0 +1,250 @@ + + + + + + OpenTally + + + + + + + + +
+ +
+ +
+ + + +
Printing directly from this page is not supported. Use the ‘Print result’ button to generate a printer-friendly report.
+ + + + + + + diff --git a/html/index.js b/html/index.js new file mode 100644 index 0000000..da2c09b --- /dev/null +++ b/html/index.js @@ -0,0 +1,77 @@ +/* OpenTally: Open-source election vote counting + * Copyright © 2021 Lee Yingtong Li (RunasSudo) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +function clickAdvancedOptions() { + if (document.getElementById('divAdvancedOptions').style.display === 'none') { + document.getElementById('divAdvancedOptions').style.display = 'grid'; + document.getElementById('btnAdvancedOptions').innerHTML = 'Hide advanced options'; + } else { + document.getElementById('divAdvancedOptions').style.display = 'none'; + document.getElementById('btnAdvancedOptions').innerHTML = 'Show advanced options'; + } +} + +console.log = function(v) { + document.getElementById('resultLogs1').append(v); + document.getElementById('resultLogs1').append("\n"); +}; + +var wasm = wasm_bindgen; + +async function clickCount() { + if (document.getElementById('bltFile').files.length === 0) { + return; + } + + // Read BLT file + let bltFile = document.getElementById('bltFile').files[0]; + let electionData = await bltFile.text(); + + // Load WASM + await wasm_bindgen('opentally_bg.wasm'); + + // Init election + let election = wasm.election_from_blt_Rational(electionData); + let state = wasm.CountStateRational.new(election); + + // Init STV options + let stv_opts = wasm.STVOptions.new( + document.getElementById('chkRoundTVs').checked ? parseInt(document.getElementById('txtRoundTVs').value) : null, + document.getElementById('chkRoundWeights').checked ? parseInt(document.getElementById('txtRoundWeights').value) : null, + document.getElementById('chkRoundVotes').checked ? parseInt(document.getElementById('txtRoundVotes').value) : null, + document.getElementById('chkRoundQuota').checked ? parseInt(document.getElementById('txtRoundQuota').value) : null, + document.getElementById('selQuota').value, + document.getElementById('selQuotaCriterion').value, + document.getElementById('selTransfers').value, + document.getElementById('selSurplus').value, + document.getElementById('selPapers').value == 'transferable', + document.getElementById('selExclusion').value, + parseInt(document.getElementById('txtPPDP').value), + ); + + // Step election + wasm.count_init_Rational(state, stv_opts); + wasm.make_and_print_result_Rational(1, state); + + for (let stage_num = 2;; stage_num++) { + let is_done = wasm.count_one_stage_Rational(state, stv_opts); + if (is_done) { + break; + } + wasm.make_and_print_result_Rational(stage_num, state); + } +} diff --git a/html/main.css b/html/main.css new file mode 100644 index 0000000..be1024f --- /dev/null +++ b/html/main.css @@ -0,0 +1,257 @@ +/* + pyRCV2: Preferential vote counting + Copyright © 2020–2021 Lee Yingtong Li (RunasSudo) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro&display=swap'); + +html, body { + font-family: 'Source Sans Pro', sans-serif; +} + +body { + padding: 0.5em; +} + +a { + color: #1d46c4; + text-decoration: none; +} +a:hover { + color: #1d3da2; + text-decoration: underline; +} + +/* Menu styling */ + +.menudiv { + border-bottom: 1px solid #ccc; + line-height: 1.8; + margin-bottom: 0.5em; + padding-bottom: 0.5em; +} + +.menudiv .subheading { + font-size: 0.8em; + font-weight: bold; +} + +.cols-12 { + display: grid; + grid-template-columns: repeat(12, 1fr); +} + +.col-3 { grid-column-end: span 3; } +.col-6 { grid-column-end: span 6; } +.col-12 { grid-column-end: span 12; } + +@media screen and (max-width: 17.5cm) { + .cols-sm-6 { + grid-template-columns: repeat(6, 1fr); + } +} + +/* Count table */ + +table { + border-collapse: collapse; +} +.result td { + padding: 0px 8px; + min-height: 1em; +} +td.count { + text-align: right; +} +td.count sup { + font-size: 0.6rem; + top: 0; +} +tr.stage-no td, tr.stage-kind td, tr.stage-comment td { + text-align: center; +} +tr.stage-no td:not(:first-child) { + border-top: 1px solid #76858c; +} +tr.stage-kind td:not(:first-child) { + font-size: 0.75em; + min-width: 5rem; + color: #1b2839; + background-color: #f0f5fb; + color-adjust: exact; + -webkit-print-color-adjust: exact; +} +td.excluded { + background-color: #fde2e2; + color-adjust: exact; + -webkit-print-color-adjust: exact; +} +td.elected { + background-color: #e0fdc5; + color-adjust: exact; + -webkit-print-color-adjust: exact; +} +tr.info td { + background-color: #f0f5fb; + color-adjust: exact; + -webkit-print-color-adjust: exact; +} +td.bt { + border-top: 1px solid #76858c; +} +td.bb { + border-bottom: 1px solid #76858c; +} + +/* BLT input tool */ + +#selBallots { + min-width: 10em; + margin-right: 1em; +} + +#bltMain { + display: flex; +} + +#tblBallot { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +#tblBallot input { + margin-right: 0.5ex; +} + +#divEditCandidates div { + margin-bottom: 0.5em; +} + +#txtCandidates { + min-width: 20em; + min-height: 10em; +} + +/* Print stylesheet */ + +#printWarning { + display: none; +} + +#printContainer > div:first-child > p:first-child { + margin-top: 0; +} + +@media print { + body.interactive > * { + display: none; + } + #divAdvancedOptions, #printPane { + /* Override inline style */ + display: none !important; + } + #printWarning { + display: block; + } +} + +/* Form styling */ +/* Adapted in part from https://github.com/nathansmith/formalize (GPL/MIT) */ + +select, input, button { + line-height: 1.15; +} + +select, input[type="text"], input[type="number"], textarea { + appearance: none; + background-color: #fff; + border: 1px solid; + border-color: #999 #bbb #ddd; + border-radius: 0; + box-sizing: border-box; + color: #000; + padding: 2px 3px; +} + +select { + /* Dropdown arrow */ + background-image: url(data:image/png;base64,R0lGODlhDQAEAIAAAAAAAP8A/yH5BAEHAAEALAAAAAANAAQAAAILhA+hG5jMDpxvhgIAOw==); + background-position: right center; + background-repeat: no-repeat; + padding-right: 20px; /* Padding for dropdown arrow */ +} + +button, input[type="file"]::-webkit-file-upload-button { + background-color: #f0f0f0; + border: 1px solid; + border-color: #ddd #bbb #999; + border-radius: 4px; + color: #000; + font-family: inherit; + padding: 2px 10px; +} +button:hover, input[type="file"]::-webkit-file-upload-button:hover { + background-color: #eaeaea; +} +button:active, input[type="file"]::-webkit-file-upload-button:active { + background-color: #dfdfdf; + border-color: #999 #bbb #ddd; +} + +/* Chrome can't parse this and ignores the entire rule */ +input[type="file"]::file-selector-button { + background-color: #f0f0f0; + border: 1px solid; + border-color: #ddd #bbb #999; + border-radius: 4px; + color: #000; + font-family: inherit; + padding: 2px 10px; +} +button:hover, input[type="file"]::file-selector-button:hover { + background-color: #f5f5f5; +} +button:active, input[type="file"]::file-selector-button:active { + background-color: #eaeaea; + border-color: #999 #bbb #ddd; +} + +input[type="checkbox"] { + appearance: none; + position: relative; + width: 0.9em; + height: 0.9em; + border: 1px solid; + border-color: #999 #bbb #ddd; + vertical-align: -2px; +} +input[type="checkbox"]:checked { + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABhGlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9Ti0VaHMwg4pChOlkQFXHUKhShQqgVWnUweekfNGlJUlwcBdeCgz+LVQcXZ10dXAVB8AfEzc1J0UVKvC8ptIjxwuN9nHfP4b37AKFZYbrVMw7ohm2mkwkpm1uVel8RQAhhRCEqzKrNyXIKvvV1T91Ud3Ge5d/3Z0W1vMWAgEQ8y2qmTbxBPL1p1zjvE4uspGjE58RjJl2Q+JHrqsdvnIsuCzxTNDPpeWKRWCp2sdrFrGTqxFPEMU03KF/Ieqxx3uKsV+qsfU/+wkjeWFnmOq1hJLGIJciQoKKOMiqwEafdIMVCms4TPv4h1y+TSyVXGYwcC6hCh+L6wf/g92ytwuSElxRJAKEXx/kYAXp3gVbDcb6PHad1AgSfgSuj4682gZlP0hsdLXYE9G8DF9cdTd0DLneAwaeaYiquFKQlFArA+xl9Uw4YuAX61ry5tc9x+gBkaFapG+DgEBgtUva6z7vD3XP7t6c9vx/h6nJtOtJaHwAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB+UBBQgnA808S3QAAABpSURBVDjLxZNLEsAgCEPRO3pKuOPrrkXRDtZFs/PzkjGMBUAOVOVQnwzM7FmwKVVFRFBVAOpucmut39xN9ukAcgJ3BuNBBr4NxmKycDDwFzNweIIHMnAo0YMZeDqFVR9pg7eJzFR+/40Xt0cwTrDq4YsAAAAASUVORK5C); + background-position: center; + background-repeat: no-repeat; + background-size: contain; +} + +button:focus, select:focus, input:focus, textarea:focus { + outline: 0; +} +select:focus, input:focus, textarea:focus { + border-color: #3daee9; +} + +label { + white-space: nowrap; +} diff --git a/pkg/test.html b/pkg/test.html deleted file mode 100644 index a0c579c..0000000 --- a/pkg/test.html +++ /dev/null @@ -1,49 +0,0 @@ - - -
- - - - - diff --git a/src/numbers/rational_num.rs b/src/numbers/rational_num.rs index 5013c00..5937109 100644 --- a/src/numbers/rational_num.rs +++ b/src/numbers/rational_num.rs @@ -247,9 +247,7 @@ impl ops::SubAssign<&Rational> for Rational { } impl ops::MulAssign<&Rational> for Rational { - fn mul_assign(&mut self, _rhs: &Rational) { - todo!() - } + fn mul_assign(&mut self, rhs: &Rational) { self.0 *= &rhs.0 } } impl ops::DivAssign<&Rational> for Rational { diff --git a/src/stv/wasm.rs b/src/stv/wasm.rs index 2a075dc..7f7b2dd 100644 --- a/src/stv/wasm.rs +++ b/src/stv/wasm.rs @@ -154,22 +154,3 @@ fn print_stage(stage_num: usize, result: &StageResult) { cprintln!(""); } - -/* -#[wasm_bindgen] -pub struct STVOptions(stv::STVOptions); -#[wasm_bindgen] -impl STVOptions { - pub fn new(round_votes: Option, exclusion: String) -> Self { - if exclusion == "one_round" { - return STVOptions(stv::STVOptions { - round_votes: round_votes, - exclusion: &"one_round", - pp_decimals: 2, - }); - } else { - panic!("Unknown --exclusion"); - } - } -} -*/