diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3409fdb --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Emacs +*~ +.#* +\#*\# +TAGS + +# Mac +.DS_Store + +# pytest +__pycache__ +.pytest_cache + +# Ignore python local install build symlinks et al +.venv +poetry.lock diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fedf9ef --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +# Ancient Makefile implicit rule disabler +(%): % +%:: %,v +%:: RCS/%,v +%:: s.% +%:: SCCS/s.% +%.out: % +%.c: %.w %.ch +%.tex: %.w %.ch +%.mk: + +# Variables +DOC_DIR := docs +SRC_DIR := . +TEST_DIR := tests + +# Use colors for errors and warnings when in an interactive terminal +INTERACTIVE := $(shell test -t 0 && echo 1) +ifdef INTERACTIVE + RED := \033[0;31m + END := \033[0m +else + RED := + END := +endif + +# Let there be no default target +.PHONY: default +default: + @echo "${RED}There is no default make target.${END} Specify one of:" + @echo "etags - constructs an emacs tags table" + @echo "" + @echo "See ${BUILD_DIR}/README.md for more details and info" + +# emacs tags (for javascript need GNU's universal-ctag package) +.PHONY: etags +etags: + /opt/homebrew/bin/ctags -e -R --language-force=javascript diff --git a/docs/DesignNotes.md b/docs/DesignNotes.md new file mode 100644 index 0000000..cf52a03 --- /dev/null +++ b/docs/DesignNotes.md @@ -0,0 +1,75 @@ +# VoteTracker+ Client Side UI/UX Design Notes + +## User Story 1 - landing page + +#### What happens when a client (smart device browser of choice) initially connects to the VTP demo? + +Displays a short welcome and help message that briefly explains the standard contest buttons. There is only one active button on the page, __CONTINUE__. Activating that continues to step 2. + +## User Story 2 - voting + +#### What happens when the voter starts to mark the ballot (votes)? + +Displays the first contest. At the moment two types of contests are supported, plurality and IRV (RCV). + +If plurality: +- can select up to the number of open positions by tapping the choice of interest in the vertical list +- individual choices can be de-selected by tapping again +- if more than the allowable number of choices are selected, a dialog abort window appears + +If IRV/RCV: +- the vertical list of candidates is displayed below an (initially empty) vertical list of ranked/selected candidates above +- clicking an candidate from the below list adds the candidate to the bottom of the upper list and removes the candidate from the below list +- swiping a candidate left or right in the upper list will remove the candidate from the upper list and re-appear the candidate in the original order in the below list +- the upper list is draggable - the upper list can be reordered at will + +Common buttons/actions/UI: + +- there is a __DE-SELECT ALL__ button somewhere in the lower left portion of the page +- there is a __CONTINUE__ button somewhere in the lower right portion of the page + - if the continue button is activated and there is an undervote situation, a warning window appears that allows the user to go to the next contest or stay with the current contest +- across the top is a horizontal progress bar divided into equal width sections by contest + - each contest is clickable and takes the user to that contest + - if there is an undervote in the current contest, a proceed-able warning window appears + - in the completion bar, completed contests are solid bright green, undervote contests are solid somewhat dimm yellow, and no-vote or undervote contests that are to the left of any green or yellow contest are red outlined boxes. +- immediately below the progress bar in the left corner is a __PREVIOUS CONTEST__ button +- immediately below the progress bar in the right corner is a __NEXT CONTEST__ button + - when the last contest in focus, the upper right button says __CHECKOUT__ +- when the __CHECKOUT__ box is clicked, all the contests are displayed with only two buttons: __GO BACK__ and __THIS IS MY VOTE__ + - __GO BACK__ goes to the previous page with the checkout box in the upper right + - __THIS IS MY VOTE__ submits the ballot + +Note that the backend python server side will re-validate the incoming ballot CVR. The __THIS IS MY VOTE__ entrypoint can return various errors to the user: + +- a non compliant contest selection was found +- there was a problem on the server side + +If there is no error, the endpoint returns: + +- the voter's ballot check as if it were paper (no links) but with the QR code being a valid link (into the voter's backend server's still active git workspace) +- the voter's row offset into the ballot check is temporarily displayed +- in addition there is, for demo purposes only, two additional buttons on this page + - a __VERIFY BALLOT RECEIPT__ button + - a __TALLY CONTESTS__ button + +## User Story 3 - inspecting the ballot check/QR code + +#### What happens when the user clicks the QR code (or takes a photo of the QR code)? + +Displays the git repo copy of the contents of the ballot check. All the digests are active links to the actual git digests for a specific vote for a specific contest. + +## User Story 4 - verifying the ballot check + +#### What happens when the user wants to verify their ballot check? + +The user clicks the __VERIFY BALLOT RECEIPT__ (end of User Story 1) and a new page displays the 'console log' output of that function with digests hyperlinked. The output page contains a few by default blank fields that the user can optionally fill in. One of them is a row offset. + +The page contains a __RE-VERIFY__ button that re-verifies the receipt honoring the new values in the various fields. + +## User Story 5 - tallying the election + +#### What happens when the user wants to tally the election? + +The user clicks the __TALLY CONTESTS__ button (end of User Story 1) and a new page displays the 'console log' output of that function with digests hyperlinked. The output page contains a few by default blank fields that the user can optionally fill in. One of them is a row offset. + +The page contains a __RE-TALLY CONTESTS__ button that re-tallies the election honoring the new values in the various fields. diff --git a/docs/ProjectPlan.md b/docs/ProjectPlan.md new file mode 100644 index 0000000..4380510 --- /dev/null +++ b/docs/ProjectPlan.md @@ -0,0 +1,74 @@ +# VoteTracker+ Client Side UI/UX Project Plan + +#### 1. Background + +The current design target is to create a usable client/voter frontend in a few weeks for a June 8th live demo. The date and available skill level are driving the client UI side design and implementation. + +The current tech choice to achieve this is a plain html, css, and javascript implementation. The current development environment is Google's devtools, Microsoft's Visual Studio Code, and emacs :-). + +See the VoteTracker+ [project page](https://github.com/orgs/TrustTheVote-Project/projects/2/views/1) for status. + +#### 2. Overall VTP Git Repo Context + +Summary of the VTP git repo's: + +The [VTP-dev-end](https://github.com/TrustTheVote-Project/VTP-dev-env) holds/defines the development/run-time environment. Contained within VTP-dev-end as submodules are three repos: +- [VTP-mock-election.US.15](https://github.com/TrustTheVote-Project/VTP-mock-election.US.15) - contains the election configuration and data +- [VTP-web-api](https://github.com/TrustTheVote-Project/VTP-web-api) - contains the FASTapi rest interface +- [VoteTrackerPlus](https://github.com/TrustTheVote-Project/VoteTrackerPlus) - contains the python backend code + +This repo, [VTP-web-client](https://github.com/TrustTheVote-Project/VTP-web-client) contains the client frontend a.k.a. the voter's /UI/UX + +See [DesignNotes.md](DesignNotes.md) in this repo for details on the end user (voter) user stories + +#### 3. Basic Project Plan + +The basic project plan is to stub out static versions of the various pages and write the javascript to support that. Then incrementally add support for deeper integrations with the restful web-api. The web-api hopefully will not require major work at this point. + +The four major milestones are: + +1. Create just the controls and UI without dealing with any JSON data - just get the basic html/css/JS UI controls for the five web pages (see below) working. + +2. Add JSON decoding support and integrate with static JSON data. This includes reading and sending (static) JSON to a stubbed out backend. + +3. Plug in the web-api and use actual live JSON data from the web-api + +4. Full end-to-end testing of the demo + +#### 4. Timeline + +To achieve the demo all four of the above major milestones need to be completed by end of may. Project completion means that the live demo is running without significant issues on a standalong router disconnected from any WAN. Thus: + +- Completion of milestone 1: 02/21 (1 week) +- Completion of milestone 2: 03/21 (4 weeks) +- Completion of milestone 3: 04/17 (4 weeks) +- Completion of milestone 4: 05/01 (2 weeks) + +which leaves 3 weeks of buffer + +#### 5. File layout (this git repo <-> end user web server) + +Given the decomposition into [5 user stories](./DesingNotes.md), out-of-the-gate associate different html pages with each user story. + +1. index.html + - the landing page + - one js button that jumps to the (next) voting.html page +2. voting.html + - handles a single contest + - provides navigation to previous, next, other contests (same page), and to the checkout.html page + - provides progress status +3. checkout.html + - allows the voter to double check their ballot prior to casting + - does not support editing the ballot + - provides three options: spoil ballot (exit), edit ballot (back to voting.html), and submit/cast ballot +4. ballot check displaying + - either create the JS to retrieve the data from the web-api (new endpoint), creating an explicit ballot-check.html + - or standup a http server running somewhere that is pointing to the repo of interest for the demo. This would probably require changing the upstream remote git repo URI to the demo LAN local git server, which would be ok as it would completely isolate the demo. +5. verify-ballot-check.html + - handles the output of the ballot check function (basically console log output) + - supplies various end user functions associated with verify their ballot check - a UX TBD +6. tally-election.html + - handles the output of tallying the election (basically console log output) +- initial UX target is to supply a few buttons for enabling switches and converting digests to links (back to the local LAN git server or the upstream remote - see 3 above) + +Regarding css and javascript, created as needed in standard locations. diff --git a/index.html b/index.html new file mode 100644 index 0000000..21f81d6 --- /dev/null +++ b/index.html @@ -0,0 +1,33 @@ + + + + + + Landing Page - User Story 1 + + + +

Welcome to a VoteTrackr+ Demo

+

This is a simulated conceptual demo of what voting in an election that is + integrated with the VoteTrackr+ technology

+

You may vote as many times as you would like

+

Please click the continue button to start voting

+

+

+

+ + + + + diff --git a/javascript/blank-ballot.js b/javascript/blank-ballot.js new file mode 100644 index 0000000..3e53dea --- /dev/null +++ b/javascript/blank-ballot.js @@ -0,0 +1,127 @@ +const blankBallotJSON = `{ + "active_ggos": [ + ".", + "GGOs/states/Massachusetts", + "GGOs/states/Massachusetts/GGOs/counties/Middlesex", + "GGOs/states/Massachusetts/GGOs/towns/Concord" + ], + "ballot_filename": "000,001,002,003,ballot.json", + "ballot_node": "GGOs/states/Massachusetts/GGOs/towns/Concord", + "ballot_subdir": "GGOs/states/Massachusetts/GGOs/towns/Concord", + "contests": { + "GGOs/states/Massachusetts": [ + { + "U.S. President": { + "choices": [ + { + "name": "Circle Party Ticket", + "ticket_names": [ + "Rey Skywalker", + "Obi-Wan Kenobi" + ] + }, + { + "name": "Square Party Ticket", + "ticket_names": [ + "Atticus Finch", + "Hermione Granger" + ] + }, + { + "name": "Triangle Party Ticket", + "ticket_names": [ + "Evelyn Quan Wang", + "Waymond Wang" + ] + } + ], + "contest_type": "ticket", + "max": 1, + "tally": "plurality", + "ticket_offices": [ + "President", + "Vice President" + ], + "uid": "0000" + } + }, + { + "U.S. Senate": { + "choices": [ + { + "name": "Anthony Alpha", + "party": "Circle Party" + }, + { + "name": "Betty Beta", + "party": "Dyad Party" + }, + { + "name": "Gloria Gamma", + "party": "Triangle Party" + }, + { + "name": "David Delta", + "party": "Square Party" + }, + { + "name": "Emily Echo", + "party": "Pentagon Party" + }, + { + "name": "Francis Foxtrot", + "party": "Hexagon Party" + } + ], + "tally": "rcv", + "uid": "0001" + } + }, + { + "Governor": { + "choices": [ + { + "name": "Spencer Cogswell", + "party": "Circle Party" + }, + { + "name": "Cosmo Spacely", + "party": "Triangle Party" + } + ], + "max": 1, + "tally": "plurality", + "uid": "0002" + } + } + ], + "GGOs/states/Massachusetts/GGOs/counties/Middlesex": [ + { + "County Clerk": { + "choices": [ + "Jean-Luc Picard", + "Katniss Everdeen", + "James T. Kirk" + ], + "max": 1, + "tally": "plurality", + "uid": "0003" + } + } + ], + "GGOs/states/Massachusetts/GGOs/towns/Concord": [ + { + "Question 1 - should the starting time of the annual town meeting be moved to 6:30 PM?": { + "choices": [ + "yes", + "no" + ], + "description": "Should the Town of Concord start the annual Town Meeting at 6:30 PM instead of 7:00 PM?", + "max": 1, + "tally": "plurality", + "uid": "0004" + } + } + ] + } +}`; diff --git a/stylesheets/voting.css b/stylesheets/voting.css new file mode 100644 index 0000000..e0d9971 --- /dev/null +++ b/stylesheets/voting.css @@ -0,0 +1,110 @@ +/* Set the overall styles for the progress bar */ +#progressBar { + display: flex; + height: 20px; + width: 100%; + text-align: center; + background-color: #E5E4E2; +} +#youAreHereBar { + display: flex; + height: 20px; + width: 100%; + text-align: center; +} + +/* colors for completely voted and undervoted and novoted */ +.novotedText { + color: #FF6B6B; +} +.undervotedText { + color: #FFD166; +} +.votedText { + color: #6EE7B7; +} +.novotedBG { + background-color: #FF6B6B; +} +.undervotedBG { + background-color: #FFD166; +} +.votedBG { + background-color: #6EE7B7; +} +.activeContest { + background-color: white; +} + +/* Each section (class) gets an individual black border */ +.progSection { + flex: 1; /* Equal distribution of space */ + border-right: 1px solid black; +} +.yrhSection { + flex: 1; /* Equal distribution of space */ +} + +/* when a plurality candidate is selected or not */ +.selected { + background-color: #D5F5E3; +} +.unselected { + background-color: #f5f5f5; +} + +/* the incoming choices */ +#choiceList li { + margin: 10px; + padding: 15px; + border: 1px solid #dfdfdf; + list-style-type: none; +} + +/* in RCV the selection is a sortable list - the defaults */ +#sortableList { + list-style: none; + padding: 0; + margin: 0; +} + +#sortableList li { + margin: 10px; + padding: 15px; + border: 1px solid #dfdfdf; + background: #f5f5f5; +} + +#sortableList li.hint { + border: 1px solid #ffc49a; + background: #feffb4; +} + +#sortableList li.active { + border: 1px solid #ffa5a5; + background: #ffe7e7; +} + +/* bottom section */ +.noBullets { + list-style-type: none; +} + +/* flexbox for the checkout page selection box */ +.selectionFlexContainer { + display: flex; +} +.tableStyle { + padding: 0; + margin: 0; +} + +/* For JSON pretty printing */ +pre {outline: 1px solid #ccc; padding: 5px; margin: 5px; } +.JSONstring { color: green; } +.JSONnumber { color: darkorange; } +.JSONboolean { color: blue; } +.JSONnull { color: magenta; } +.JSONkey { color: red; } + + diff --git a/voting.html b/voting.html new file mode 100644 index 0000000..e776235 --- /dev/null +++ b/voting.html @@ -0,0 +1,851 @@ + + + + + + + + Voting - User Story 2 + + + + +
+
+ + +
+ + +
+ + +
+ + + + + + +