From b109d990543e5c61fa93b9486f8192e2ddfab927 Mon Sep 17 00:00:00 2001 From: RunasSudo Date: Mon, 25 Jan 2021 20:25:32 +1100 Subject: [PATCH] BLT editor: Basic BLT import functionality --- html/blt/index.html | 5 +-- html/blt/index.js | 58 +++++++++++++++++++++++++++++------ pyRCV2/numbers/__init__.py | 1 + pyRCV2/numbers/fixed_js.py | 6 +++- pyRCV2/numbers/gfixed_js.py | 10 ++++-- pyRCV2/numbers/string_js.py | 28 +++++++++++++++++ pyRCV2/random/sharandom_js.py | 8 ++++- 7 files changed, 100 insertions(+), 16 deletions(-) create mode 100644 pyRCV2/numbers/string_js.py diff --git a/html/blt/index.html b/html/blt/index.html index 3768c76..3d36fc7 100644 --- a/html/blt/index.html +++ b/html/blt/index.html @@ -28,8 +28,8 @@ - + + Information and instructions @@ -55,6 +55,7 @@
Warning: Adding, removing or reordering candidates once ballots have been input may result in unexpected behaviour.
+ diff --git a/html/blt/index.js b/html/blt/index.js index 44f90ca..68d9493 100644 --- a/html/blt/index.js +++ b/html/blt/index.js @@ -106,24 +106,63 @@ function changeBallot() { tblBallot.querySelector('input').select(); } +let openMode = 'json'; + function clickOpenJSON() { + openMode = 'json'; + inpFile.value = ''; + inpFile.click(); +} + +function clickImportBLT() { + openMode = 'blt'; inpFile.value = ''; inpFile.click(); } async function changeInpFile() { if (inpFile.value !== '') { - // Open JSON file - let text = await inpFile.files[0].text(); - let obj = JSON.parse(text); - - // Load data - candidates = obj['candidates']; - ballots = obj['ballots']; + if (openMode === 'json') { + // Open JSON file + let text = await inpFile.files[0].text(); + let obj = JSON.parse(text); + + // Load data + candidates = obj['candidates']; + ballots = obj['ballots']; + } else if (openMode === 'blt') { + // Open BLT file + let text = await inpFile.files[0].text(); + + // Import data + py.pyRCV2.numbers.set_numclass(py.pyRCV2.numbers.StringNum); + let election = py.pyRCV2.blt.readBLT(text); + + candidates = []; + for (let candidate of election.candidates) { + candidates.push(candidate.py_name); + } + + ballots = []; + for (let ballot of election.ballots) { + let js_ballot = {'value': ballot.value.impl, 'preferences': []}; + for (let i = 0; i < candidates.length; i++) { + js_ballot['preferences'].push(''); + } + + // Add preference numbers + for (let i = 0; i < ballot.preferences.length; i++) { + candidate = ballot.preferences[i]; + js_ballot['preferences'][election.candidates.indexOf(candidate)] = '' + (i + 1); + } + + ballots.push(js_ballot); + } + } // Update ballot entry initBallot(); - + // Update ballot list selBallots.innerHTML = ''; selBallots.value = 'new'; @@ -133,8 +172,9 @@ async function changeInpFile() { elOption.innerText = 'Ballot ' + (i + 1); selBallots.insertBefore(elOption, selBallots.selectedOptions[0]); } + + inpFile.value = ''; } - inpFile.value = ''; } function clickSaveJSON() { diff --git a/pyRCV2/numbers/__init__.py b/pyRCV2/numbers/__init__.py index 7ed999b..a4de19d 100644 --- a/pyRCV2/numbers/__init__.py +++ b/pyRCV2/numbers/__init__.py @@ -34,6 +34,7 @@ else: # pragma: no cover from pyRCV2.numbers.gfixed_js import FixedGuarded, _gfixed_set_dps from pyRCV2.numbers.native_js import Native from pyRCV2.numbers.rational_js import Rational + from pyRCV2.numbers.string_js import StringNum # GLOBALS diff --git a/pyRCV2/numbers/fixed_js.py b/pyRCV2/numbers/fixed_js.py index 5e1c7c0..1db33a3 100644 --- a/pyRCV2/numbers/fixed_js.py +++ b/pyRCV2/numbers/fixed_js.py @@ -16,7 +16,11 @@ from pyRCV2.numbers.base import BaseNum, compatible_types -Big.DP = 6 +if __pragma__('js', '{}', 'typeof(bigInt)') != 'undefined': + Big.DP = 6 +else: + # Fail gracefully if dependencies not present + pass def _fixed_set_dps(dps): Big.DP = dps diff --git a/pyRCV2/numbers/gfixed_js.py b/pyRCV2/numbers/gfixed_js.py index 1e3be35..166fdd8 100644 --- a/pyRCV2/numbers/gfixed_js.py +++ b/pyRCV2/numbers/gfixed_js.py @@ -16,9 +16,13 @@ from pyRCV2.numbers.base import BaseNum, compatible_types -Big.DP = 12 -_pos_epsilon = Big('10').pow(-6).div('2') -_neg_epsilon = _pos_epsilon.times('-1') +if __pragma__('js', '{}', 'typeof(bigInt)') != 'undefined': + Big.DP = 12 + _pos_epsilon = Big('10').pow(-6).div('2') + _neg_epsilon = _pos_epsilon.times('-1') +else: + # Fail gracefully if dependencies not present + pass def _gfixed_set_dps(dps): global _pos_epsilon, _neg_epsilon diff --git a/pyRCV2/numbers/string_js.py b/pyRCV2/numbers/string_js.py new file mode 100644 index 0000000..7fd871d --- /dev/null +++ b/pyRCV2/numbers/string_js.py @@ -0,0 +1,28 @@ +# 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 . + +from pyRCV2.numbers.base import BaseNum + +class StringNum(BaseNum): + """ + Dummy class wrapping simple strings + Used when reading/writing BLTs with no arithmetic required - avoids loading dependencies + """ + + @classmethod + def _to_impl(cls, value): + """Implements BaseNum._to_impl""" + return value diff --git a/pyRCV2/random/sharandom_js.py b/pyRCV2/random/sharandom_js.py index b15f25d..49e9509 100644 --- a/pyRCV2/random/sharandom_js.py +++ b/pyRCV2/random/sharandom_js.py @@ -14,8 +14,14 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +if __pragma__('js', '{}', 'typeof(bigInt)') != 'undefined': + _MAX_VAL = bigInt(2).pow(256).subtract(1) +else: + # Fail gracefully if dependencies not present + _MAX_VAL = None + class SHARandom: - MAX_VAL = bigInt(2).pow(256).subtract(1) + MAX_VAL = _MAX_VAL def __init__(self, seed): self.seed = seed