├── app ├── .meteor │ ├── .gitignore │ ├── release │ ├── platforms │ ├── .id │ ├── .finished-upgraders │ ├── packages │ └── versions ├── public │ ├── graph.png │ ├── graph2.jpg │ ├── graph2.png │ ├── person.jpg │ └── fonts │ │ ├── icomoon.eot │ │ ├── icomoon.ttf │ │ ├── icomoon.woff │ │ ├── icomoon.svg │ │ └── demo.html ├── client │ ├── libraries │ │ ├── subscribed.js │ │ ├── dashboard │ │ │ ├── main │ │ │ │ ├── menu │ │ │ │ │ ├── menu.js │ │ │ │ │ ├── classie.js │ │ │ │ │ ├── gnmenu.js │ │ │ │ │ └── modernizr.custom.js │ │ │ │ └── components.js │ │ │ ├── vote │ │ │ │ ├── vote.js │ │ │ │ └── poll.js │ │ │ ├── delegates │ │ │ │ ├── personal.js │ │ │ │ └── delegates.js │ │ │ ├── voted │ │ │ │ └── poll_voted.js │ │ │ └── create │ │ │ │ └── create.js │ │ └── homepage │ │ │ └── login.js │ ├── templates │ │ ├── dashboard │ │ │ ├── main │ │ │ │ ├── dashboard.html │ │ │ │ ├── menu.html │ │ │ │ └── components.html │ │ │ ├── voted │ │ │ │ └── poll_voted.html │ │ │ ├── vote │ │ │ │ ├── poll.html │ │ │ │ └── vote.html │ │ │ ├── delegates │ │ │ │ ├── personal.html │ │ │ │ └── delegates.html │ │ │ └── create │ │ │ │ └── create.html │ │ ├── head.html │ │ └── homepage │ │ │ ├── login.html │ │ │ └── home.html │ └── stylesheets │ │ ├── dashboard │ │ ├── delegates │ │ │ ├── personal.css │ │ │ └── delegates.css │ │ ├── voted │ │ │ └── poll_voted.css │ │ ├── vote │ │ │ ├── vote.css │ │ │ └── poll.css │ │ ├── main │ │ │ ├── normalize.css │ │ │ ├── dashboard.css │ │ │ └── menu.css │ │ └── create │ │ │ └── create.css │ │ └── homepage │ │ ├── login.css │ │ └── home.css ├── collections │ └── collections.js ├── README.md ├── server │ ├── accounts.js │ ├── published.js │ └── server.js └── shared │ └── routes.js ├── contracts ├── organization.sol └── poll.sol ├── README.md └── tests ├── circularDelegation └── circularcheck.js ├── README.md ├── withDelegates ├── voteCount.js └── generator.js ├── onlyVoters ├── voteCount.js └── generator.js └── treeVisualization ├── tree.html └── flare.json /app/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /app/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.2.1 2 | -------------------------------------------------------------------------------- /app/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /app/public/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domschiener/liquid-democracy/HEAD/app/public/graph.png -------------------------------------------------------------------------------- /app/public/graph2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domschiener/liquid-democracy/HEAD/app/public/graph2.jpg -------------------------------------------------------------------------------- /app/public/graph2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domschiener/liquid-democracy/HEAD/app/public/graph2.png -------------------------------------------------------------------------------- /app/public/person.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domschiener/liquid-democracy/HEAD/app/public/person.jpg -------------------------------------------------------------------------------- /app/public/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domschiener/liquid-democracy/HEAD/app/public/fonts/icomoon.eot -------------------------------------------------------------------------------- /app/public/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domschiener/liquid-democracy/HEAD/app/public/fonts/icomoon.ttf -------------------------------------------------------------------------------- /app/public/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domschiener/liquid-democracy/HEAD/app/public/fonts/icomoon.woff -------------------------------------------------------------------------------- /app/client/libraries/subscribed.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe("userData"); 2 | Meteor.subscribe("pollListings"); 3 | Meteor.subscribe("delegatesData"); 4 | -------------------------------------------------------------------------------- /app/client/libraries/dashboard/main/menu/menu.js: -------------------------------------------------------------------------------- 1 | Template.dashboard_menu.onRendered(function() { 2 | new gnMenu( document.getElementById( 'gn-menu' ) ); 3 | }); 4 | -------------------------------------------------------------------------------- /app/collections/collections.js: -------------------------------------------------------------------------------- 1 | Uservotes = new Mongo.Collection('uservotes'); 2 | Delegates = new Mongo.Collection('delegates'); 3 | poll = new Mongo.Collection('polls'); 4 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # App Structure 2 | 3 | Coming Soon 4 | 5 | ## Credits 6 | 7 | A huge shoutout to [Codrops](http://tympanus.net/codrops/), the Dashboard menu is from them. Check them out :) 8 | -------------------------------------------------------------------------------- /app/client/libraries/homepage/login.js: -------------------------------------------------------------------------------- 1 | Template.login.events({ 2 | 'click #github_login' : function() { 3 | Meteor.loginWithGithub({loginStyle: "redirect", requestPermissions: ['user']}, function(error, success) { 4 | console.log(success); 5 | }); 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /app/server/accounts.js: -------------------------------------------------------------------------------- 1 | ServiceConfiguration.configurations.remove({ 2 | service: "github" 3 | }); 4 | 5 | ServiceConfiguration.configurations.insert({ 6 | service: "github", 7 | clientId: "d0c2b45aa6bc025b2644", 8 | secret: "92dfafe10920fd509c24cc73c82fb2a9933d9fb8" 9 | }); 10 | -------------------------------------------------------------------------------- /app/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | k3rs3g1um9k6f1h7i26k 8 | -------------------------------------------------------------------------------- /app/client/libraries/dashboard/vote/vote.js: -------------------------------------------------------------------------------- 1 | Template.vote.helpers({ 2 | getDate: function(timestamp) { 3 | return new Date(timestamp); 4 | }, 5 | vote_count: function() { 6 | var current_poll = this; 7 | if (current_poll.votes) { 8 | return current_poll.votes.length; 9 | } 10 | else { 11 | return 0; 12 | } 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /app/client/templates/dashboard/main/dashboard.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /app/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | -------------------------------------------------------------------------------- /app/server/published.js: -------------------------------------------------------------------------------- 1 | Meteor.publish('pollListings', function() { 2 | if (this.userId) { 3 | return poll.find({}); 4 | } else { 5 | this.ready(); 6 | } 7 | }); 8 | 9 | Meteor.publish("userData", function () { 10 | if (this.userId) { 11 | return Meteor.users.find({_id: this.userId}, {'services': 1}); 12 | } else { 13 | this.ready(); 14 | } 15 | }); 16 | 17 | Meteor.publish("delegatesData", function () { 18 | //Make it so that only people who delegated can view the delegate 19 | if (this.userId) { 20 | return Delegates.find({}); 21 | } else { 22 | this.ready(); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /contracts/organization.sol: -------------------------------------------------------------------------------- 1 | contract Organization { 2 | // This contract will keep track of the entire organizations state 3 | // This includes voters and their delegations, polls and domains 4 | 5 | // Organization Meta Information 6 | struct Org { 7 | bytes32 name; 8 | string description; 9 | bytes32[] domains; 10 | uint numVoters; 11 | } 12 | 13 | struct Voter { 14 | bytes32 name; 15 | address ethaddress; 16 | 17 | } 18 | 19 | struct Delegation { 20 | address delegate; 21 | address delegator; 22 | 23 | } 24 | 25 | Org public org; 26 | Voter[] v; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /app/client/templates/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Liquid Democracy - Early Prototype 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/client/libraries/dashboard/main/components.js: -------------------------------------------------------------------------------- 1 | Meteor.subscribe('poll_listings'); 2 | 3 | Template.stats.helpers({ 4 | delegateInfo: function(delegate_data) { 5 | return Delegates.findOne({_id: delegate_data}); 6 | }, 7 | delegateCount: function(delegations) { 8 | if (delegations) { 9 | return Object.keys(delegations).length; 10 | } 11 | else { 12 | return 0; 13 | } 14 | } 15 | }) 16 | 17 | Template.stats.events({ 18 | 'click #quitDelegate': function() { 19 | Meteor.call('quit_delegate', Meteor.userId(), function(error, success) { 20 | if (!error) { 21 | console.log("You have successfully quit.") 22 | } 23 | }) 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /app/client/templates/homepage/login.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /app/client/libraries/dashboard/vote/poll.js: -------------------------------------------------------------------------------- 1 | Template.poll.helpers({ 2 | option_count: function(count) { 3 | return (count === 2) 4 | }, 5 | single_option: function(index, options) { 6 | return options[index]; 7 | } 8 | }); 9 | 10 | Template.poll.events({ 11 | 'click .option_click': function(event) { 12 | var option = event.target.id; 13 | var user = Meteor.userId(); 14 | var delegate = Meteor.user().delegate; 15 | var poll = this._id; 16 | if (poll === undefined) { 17 | poll = $(event.currentTarget).attr('pollid'); 18 | } 19 | 20 | Meteor.call('new_vote', option, user, delegate, poll, function(error) { 21 | if (!error) { 22 | var path = "/dashboard/vote/" + poll + "/voted"; 23 | Router.go(path); 24 | } 25 | }); 26 | } 27 | 28 | }) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Liquid Democracy 2 | 3 | This is a work in progress of a Proof of Concept for Liquid Democracy on Ethereum. The functional prototype will be based on Meteor and will consist of several smart contracts that make it easy for decentralized organizations to manage their internal decision making through Liquid Democracy 4 | 5 | If you are unfamiliar with what Liquid Democracy is, then please check out [this blog post](https://medium.com/@DomSchiener/liquid-democracy-true-democracy-for-the-21st-century-7c66f5e53b6f#.3b0eehwfc) 6 | 7 | For another prototype of voting on Ethereum, check out [PublicVotes](http://publicvotes.org) 8 | 9 | This project is sponsored by [Wanxiang Labs](http://www.blockchainlabs.org/blockgrant-x-en/) 10 | 11 | 12 | TODO 13 | Check that delegate has entereded Description + Domain 14 | Make sure that two options do not equal 15 | If vote in multiple domains, and voter has two delegations (in two different domains), only count one 16 | -------------------------------------------------------------------------------- /app/client/stylesheets/dashboard/delegates/personal.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Main Delegates Styles 3 | **/ 4 | 5 | .personal { 6 | width: 100%; 7 | background-color: #F8F3F0; 8 | top: 60px; 9 | overflow: hidden; 10 | margin: 60px 0 0; 11 | } 12 | 13 | .delegates__titles { 14 | font-weight: 500; 15 | } 16 | 17 | .revoke_delegate:hover { 18 | background-color: #b20000; 19 | } 20 | 21 | .revoke_delegate:hover:before { 22 | content:"Remove"; 23 | } 24 | 25 | 26 | /** 27 | * Delegate recent Votes and Issues 28 | **/ 29 | 30 | .delegate__votes, 31 | .delegate__issues { 32 | min-width: 250px; 33 | max-width: 550px; 34 | min-height: 100px; 35 | border: 1px solid #c6c2c0; 36 | border-radius: 5px; 37 | background-color: #fff; 38 | margin: 15px 20px; 39 | padding: 5px; 40 | text-align: center; 41 | display: flex; 42 | align-items: center; 43 | justify-content: center; 44 | flex-direction: column; 45 | } 46 | 47 | .delegate__votes-title { 48 | font-size: 22px; 49 | font-weight: 700; 50 | min-height: 50px; 51 | margin: 15px; 52 | width: 100%; 53 | } 54 | -------------------------------------------------------------------------------- /app/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-base # Packages every Meteor app needs to have 8 | mobile-experience # Packages for a great mobile UX 9 | mongo # The database Meteor supports right now 10 | blaze-html-templates # Compile .html files into Meteor Blaze views 11 | session # Client-side reactive dictionary for your app 12 | jquery # Helpful client-side library 13 | tracker # Meteor's client-side reactive programming library 14 | 15 | standard-minifiers # JS/CSS minifiers run for production mode 16 | es5-shim # ECMAScript 5 compatibility for older browsers. 17 | ecmascript # Enable ECMAScript2015+ syntax in app code 18 | 19 | iron:router 20 | accounts-twitter 21 | service-configuration 22 | maazalik:highcharts 23 | accounts-github 24 | http 25 | hilios:jquery.countdown 26 | twbs:bootstrap 27 | natestrauser:select2 28 | -------------------------------------------------------------------------------- /app/client/stylesheets/homepage/login.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Login Page 3 | **/ 4 | 5 | .login { 6 | width: 100%; 7 | height: 100%; 8 | overflow: hidden; 9 | } 10 | 11 | .login__left { 12 | height: 100%; 13 | background-color: #F25D59; 14 | padding: 0; 15 | 16 | } 17 | 18 | .login__quote { 19 | height: 100%; 20 | width: 70%; 21 | display: flex; 22 | justify-content: center; 23 | margin: 0 auto; 24 | } 25 | 26 | .login__para { 27 | font-size: 24px; 28 | font-style: italic; 29 | margin: 0.25em 0; 30 | color: #fff; 31 | align-self: center; 32 | } 33 | 34 | .login__author { 35 | color: #fff; 36 | font-size: 16px; 37 | align-self: center; 38 | margin: 5px; 39 | } 40 | 41 | .login__author:before { 42 | content: "\2014 \2009"; 43 | } 44 | 45 | /** 46 | * Login Buttons Section 47 | **/ 48 | 49 | .login__buttons { 50 | height: 100%; 51 | background-color: #292C3D; 52 | display: flex; 53 | justify-content: center; 54 | flex-direction: column; 55 | } 56 | 57 | .login__title { 58 | font-size: 40px; 59 | color: #ccc; 60 | margin: 15px 0; 61 | align-self: center; 62 | border-bottom: 2px solid #F25D59; 63 | } 64 | 65 | #github_login { 66 | margin: 10px 0; 67 | align-self: center; 68 | } 69 | -------------------------------------------------------------------------------- /app/client/libraries/dashboard/delegates/personal.js: -------------------------------------------------------------------------------- 1 | Template.personal.helpers({ 2 | personal_profile: function(delegate_id) { 3 | if (delegate_id === Meteor.userId()) { 4 | return true; 5 | } 6 | return false; 7 | }, 8 | delegateInfo: function(delegate_data) { 9 | return Delegates.findOne({_id: delegate_data}); 10 | }, 11 | delegateExpertise: function(user, delegate) { 12 | var delegations = [] 13 | user.delegates.forEach(function(delegation) { 14 | if (delegation.delegate == delegate.userID) { 15 | delegations.push(delegation.domain); 16 | } 17 | }) 18 | 19 | return delegations; 20 | }, 21 | domainRelated: function(delegateDomain, issueDomain) { 22 | issueDomain.forEach(function(domain) { 23 | if (domain === delegateDomain) { 24 | console.log(domain, delegateDomain) 25 | return true; 26 | } 27 | }) 28 | 29 | return false; 30 | } 31 | }) 32 | 33 | Template.personal.events({ 34 | 'click .revoke_delegate': function() { 35 | var delegate = this; 36 | var user = Meteor.userId(); 37 | 38 | Meteor.call('revoke_delegate', delegate, user); 39 | }, 40 | 'click .panel_button': function(event) { 41 | var poll_id = event.currentTarget.id; 42 | console.log(poll_id); 43 | Router.go('voted', {_id: poll_id}) 44 | } 45 | }) 46 | -------------------------------------------------------------------------------- /app/client/stylesheets/dashboard/voted/poll_voted.css: -------------------------------------------------------------------------------- 1 | .poll_not_finished { 2 | font-size: 22px; 3 | white-space: normal; 4 | } 5 | 6 | .quick_stats { 7 | margin-bottom: 100px; 8 | } 9 | 10 | .vote_count { 11 | width: 250px; 12 | height: 120px; 13 | background-color: #9b59b6; 14 | color: #fff; 15 | border-radius: 4px; 16 | margin: 0 auto; 17 | } 18 | 19 | .vote_time { 20 | width: 250px; 21 | height: 120px; 22 | background-color: #3498db; 23 | color: #fff; 24 | border-radius: 4px; 25 | margin: 0 auto; 26 | } 27 | 28 | .voted_title { 29 | vertical-align: middle; 30 | text-align: center; 31 | font-size: 30px; 32 | padding-top: 10px; 33 | margin-bottom: 5px; 34 | } 35 | 36 | .voted_content { 37 | margin-top: 15px; 38 | font-size: 26px; 39 | vertical-align: middle; 40 | text-align: center; 41 | } 42 | 43 | .public_votes { 44 | margin: 80px auto 50px; 45 | } 46 | 47 | .public_votes table { 48 | margin: 40px auto 0; 49 | } 50 | 51 | .public_votes th { 52 | width: 300px; 53 | height: 100px; 54 | border: 1px solid #ccc; 55 | text-align: center; 56 | background: #d8514b; 57 | font-size: 22px; 58 | } 59 | 60 | .public_votes td { 61 | border: 1px solid #ccc; 62 | width: 150px; 63 | 64 | } 65 | 66 | .timestamp { 67 | padding-left: 20px; 68 | } 69 | 70 | .centered { 71 | text-align:center; 72 | } 73 | -------------------------------------------------------------------------------- /app/client/templates/dashboard/main/menu.html: -------------------------------------------------------------------------------- 1 | 37 | -------------------------------------------------------------------------------- /tests/circularDelegation/circularcheck.js: -------------------------------------------------------------------------------- 1 | // 2 | // Function used for Circular Delegation Check 3 | // Checks if inbound delegation, matches outbound delegation 4 | // 5 | function traverseCheck(delegate, userDelegate, domain) { 6 | // Circular delegation only possible nly if delegate has outbound delegations 7 | var delegateProfile = Meteor.users.findOne({_id: delegate}); 8 | if (delegateProfile.delegates) { 9 | delegateProfile.delegates.forEach(function(delegation) { 10 | userDelegate.delegations.forEach(function(userDelegations) { 11 | if (userDelegations.voter === delegation.delegate && 12 | userDelegations.domain === delegation.domain) { 13 | throw new Meteor.Error('circularDelegation', ' Your Delegation would creat a circular Delegation and is therefore currently not possible. Please check your delegation relationships and retry again.') 14 | } 15 | else { 16 | traverseCheck(delegation.delegate, userDelegate, domain) 17 | } 18 | }) 19 | }) 20 | } 21 | } 22 | 23 | 24 | function circularDelegation(user, delegate, domain) { 25 | // If the current user is a delegate herself, check for circular delegation 26 | if (user.delegate) { 27 | var userDelegate = Delegates.findOne({_id: user._id}); 28 | 29 | // Circular delegation only possible if user already has existing inbound delegations 30 | if (userDelegate.delegations) { 31 | // Check if inbound delegation matches outbound delegation 32 | traverseCheck(delegate, userDelegate, domain); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/client/stylesheets/dashboard/delegates/delegates.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Main Delegates Styles 3 | **/ 4 | 5 | .delegates { 6 | width: 100%; 7 | min-height: 100%; 8 | background-color: #F8F3F0; 9 | overflow: hidden; 10 | margin: 0; 11 | } 12 | 13 | .delegationError { 14 | position: absolute; 15 | } 16 | 17 | /** Becoming a Delegate CSS **/ 18 | 19 | .become-delegate { 20 | width: 150px; 21 | height: 150px; 22 | border-radius: 50%; 23 | margin: 15px 0; 24 | } 25 | 26 | .become-delegate span { 27 | font-size: 36px; 28 | } 29 | 30 | .personal__title { 31 | font-size: 22px; 32 | } 33 | 34 | .personal__description { 35 | margin: 25px 0; 36 | } 37 | 38 | /** 39 | * Overview of all Delegates 40 | **/ 41 | 42 | .delegates__title { 43 | font-size: 44px; 44 | margin: 55px 0; 45 | text-align: center; 46 | } 47 | 48 | .delegate__profile { 49 | min-width: 250px; 50 | max-width: 550px; 51 | min-height: 100px; 52 | border: 1px solid #c6c2c0; 53 | border-radius: 5px; 54 | background-color: #fff; 55 | margin: 15px 20px; 56 | padding: 5px; 57 | text-align: center; 58 | display: flex; 59 | align-items: center; 60 | justify-content: center; 61 | flex-direction: column; 62 | } 63 | 64 | .delegate__profile-picture img { 65 | width: 150px; 66 | height: 150px; 67 | border: 3px solid #ccc; 68 | border-radius: 50%; 69 | margin: 15px 0; 70 | } 71 | 72 | .delegate__profile-name { 73 | font-size: 24px; 74 | } 75 | 76 | .delegate__profile-description { 77 | /** **/ 78 | } 79 | 80 | .delegate__profile-button { 81 | margin: 15px 0; 82 | } 83 | -------------------------------------------------------------------------------- /contracts/poll.sol: -------------------------------------------------------------------------------- 1 | contract NewPoll { 2 | //defines the poll 3 | struct Poll { 4 | bytes32 title; 5 | //stringified options array 6 | string options; 7 | // The type (i.e. domain) of the poll 8 | bytes32 type; 9 | uint deadline; 10 | bool status; 11 | uint numVotes; 12 | } 13 | 14 | // event tracking of all votes and poll end 15 | event NewVote(bytes32 votechoice); 16 | event PollEnded(string end, bytes32 _title); 17 | 18 | modifier live { if (p.status) _ } 19 | 20 | // declare a public poll called p 21 | Poll public p; 22 | 23 | //initiator function that stores the necessary poll information 24 | function NewPoll(bytes32 _title, string _options, bytes32 _type, uint _deadline) { 25 | p.title = _title; 26 | p.options = _options; 27 | p.type = _type; 28 | p.deadline = _deadline; 29 | p.status = true; 30 | p.numVotes = 0; 31 | } 32 | 33 | //function for user vote. input is a string choice 34 | function vote(bytes32 choice) live { 35 | //If the poll deadline is reached, we end the poll and don't record the vote 36 | if (now > p.deadline) { 37 | endPoll(); 38 | } else { 39 | p.numVotes += 1; 40 | NewVote(choice); 41 | } 42 | } 43 | 44 | // can be independently called to see if poll has ended 45 | function isEnd() live { 46 | if (now > p.deadline) { 47 | endPoll(); 48 | } 49 | } 50 | 51 | //when time limit is reached, set the poll status to false 52 | function endPoll() internal { 53 | p.status = false; 54 | PollEnded('Poll has officially ended', p.title); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/client/stylesheets/dashboard/vote/vote.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Current Issues Section 3 | **/ 4 | 5 | .vote { 6 | width: 100%; 7 | min-height: 100%; 8 | background-color: #F8F3F0; 9 | top: 60px; 10 | overflow: hidden; 11 | margin: 60px 0 0; 12 | } 13 | 14 | .issues { 15 | margin: 60px auto 0; 16 | } 17 | 18 | .issues__title { 19 | font-size: 44px; 20 | margin: 35px 0; 21 | text-align: center; 22 | } 23 | 24 | .issues__item { 25 | min-width: 350px; 26 | max-width: 650px; 27 | min-height: 100px; 28 | border: 1px solid #c6c2c0; 29 | border-radius: 5px; 30 | background-color: #fff; 31 | margin: 15px 20px; 32 | padding: 0; 33 | text-align: center; 34 | } 35 | 36 | .issues__item-title { 37 | color: #333; 38 | min-height: 50px; 39 | width: 100%; 40 | background-color: #f5f5f5; 41 | border-color: #ddd; 42 | } 43 | 44 | .panel__title { 45 | font-size: 22px; 46 | } 47 | 48 | 49 | 50 | /** 51 | * Past Votes Section 52 | **/ 53 | 54 | .past_votes { 55 | width: 100%; 56 | background-color: #c6c2c0; 57 | overflow: hidden; 58 | margin: 0; 59 | } 60 | 61 | .past__votes-item { 62 | min-width: 350px; 63 | max-width: 650px; 64 | border: 1px solid #c6c2c0; 65 | border-radius: 5px; 66 | background-color: #fff; 67 | margin: 15px auto 20px; 68 | padding: 0; 69 | text-align: center; 70 | } 71 | 72 | .panel__title a { 73 | font-size: 28px; 74 | text-decoration: none; 75 | outline: 0; 76 | } 77 | 78 | .panel-body { 79 | clear: both; 80 | } 81 | 82 | .panel__extra { 83 | text-align: center; 84 | } 85 | 86 | .panel__created, 87 | .panel__domain, 88 | .panel__votes { 89 | margin: 15px 0; 90 | } 91 | -------------------------------------------------------------------------------- /app/client/templates/dashboard/voted/poll_voted.html: -------------------------------------------------------------------------------- 1 | 50 | -------------------------------------------------------------------------------- /app/client/templates/dashboard/vote/poll.html: -------------------------------------------------------------------------------- 1 | 46 | -------------------------------------------------------------------------------- /app/client/stylesheets/dashboard/main/normalize.css: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;}body{margin:0;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;margin:0.67em 0;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:bold;}dfn{font-style:italic;}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em;}pre{white-space:pre-wrap;}q{quotes:"\201C" "\201D" "\2018" "\2019";}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{border:0;}svg:not(:root){overflow:hidden;}figure{margin:0;}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}legend{border:0;padding:0;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,select{text-transform:none;}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}button[disabled],html input[disabled]{cursor:default;}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;} -------------------------------------------------------------------------------- /app/.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.2.2 2 | accounts-github@1.0.6 3 | accounts-oauth@1.1.8 4 | accounts-twitter@1.0.6 5 | autoupdate@1.2.4 6 | babel-compiler@5.8.24_1 7 | babel-runtime@0.1.4 8 | base64@1.0.4 9 | binary-heap@1.0.4 10 | blaze@2.1.3 11 | blaze-html-templates@1.0.1 12 | blaze-tools@1.0.4 13 | boilerplate-generator@1.0.4 14 | caching-compiler@1.0.0 15 | caching-html-compiler@1.0.2 16 | callback-hook@1.0.4 17 | check@1.1.0 18 | ddp@1.2.2 19 | ddp-client@1.2.1 20 | ddp-common@1.2.2 21 | ddp-rate-limiter@1.0.0 22 | ddp-server@1.2.2 23 | deps@1.0.9 24 | diff-sequence@1.0.1 25 | ecmascript@0.1.6 26 | ecmascript-runtime@0.2.6 27 | ejson@1.0.7 28 | es5-shim@4.1.14 29 | fastclick@1.0.7 30 | geojson-utils@1.0.4 31 | github@1.1.4 32 | hilios:jquery.countdown@2.0.4 33 | hot-code-push@1.0.0 34 | html-tools@1.0.5 35 | htmljs@1.0.5 36 | http@1.1.1 37 | id-map@1.0.4 38 | iron:controller@1.0.12 39 | iron:core@1.0.11 40 | iron:dynamic-template@1.0.12 41 | iron:layout@1.0.12 42 | iron:location@1.0.11 43 | iron:middleware-stack@1.0.11 44 | iron:router@1.0.12 45 | iron:url@1.0.11 46 | jquery@1.11.4 47 | launch-screen@1.0.4 48 | livedata@1.0.15 49 | localstorage@1.0.5 50 | logging@1.0.8 51 | maazalik:highcharts@0.4.0 52 | meteor@1.1.10 53 | meteor-base@1.0.1 54 | minifiers@1.1.7 55 | minimongo@1.0.10 56 | mobile-experience@1.0.1 57 | mobile-status-bar@1.0.6 58 | mongo@1.1.3 59 | mongo-id@1.0.1 60 | natestrauser:select2@4.0.0_1 61 | npm-mongo@1.4.39_1 62 | oauth@1.1.6 63 | oauth1@1.1.5 64 | oauth2@1.1.5 65 | observe-sequence@1.0.7 66 | ordered-dict@1.0.4 67 | promise@0.5.1 68 | random@1.0.5 69 | rate-limit@1.0.0 70 | reactive-dict@1.1.3 71 | reactive-var@1.0.6 72 | reload@1.1.4 73 | retry@1.0.4 74 | routepolicy@1.0.6 75 | service-configuration@1.0.5 76 | session@1.1.1 77 | spacebars@1.0.7 78 | spacebars-compiler@1.0.7 79 | standard-minifiers@1.0.2 80 | templating@1.1.5 81 | templating-tools@1.0.0 82 | tracker@1.0.9 83 | twbs:bootstrap@3.3.6 84 | twitter@1.1.5 85 | ui@1.0.8 86 | underscore@1.0.4 87 | url@1.0.5 88 | webapp@1.2.3 89 | webapp-hashing@1.0.5 90 | -------------------------------------------------------------------------------- /app/client/libraries/dashboard/main/menu/classie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * classie - class helper functions 3 | * from bonzo https://github.com/ded/bonzo 4 | * 5 | * classie.has( elem, 'my-class' ) -> true/false 6 | * classie.add( elem, 'my-new-class' ) 7 | * classie.remove( elem, 'my-unwanted-class' ) 8 | * classie.toggle( elem, 'my-class' ) 9 | */ 10 | 11 | /*jshint browser: true, strict: true, undef: true */ 12 | /*global define: false */ 13 | 14 | ( function( window ) { 15 | 16 | 'use strict'; 17 | 18 | // class helper functions from bonzo https://github.com/ded/bonzo 19 | 20 | function classReg( className ) { 21 | return new RegExp("(^|\\s+)" + className + "(\\s+|$)"); 22 | } 23 | 24 | // classList support for class management 25 | // altho to be fair, the api sucks because it won't accept multiple classes at once 26 | var hasClass, addClass, removeClass; 27 | 28 | if ( 'classList' in document.documentElement ) { 29 | hasClass = function( elem, c ) { 30 | return elem.classList.contains( c ); 31 | }; 32 | addClass = function( elem, c ) { 33 | elem.classList.add( c ); 34 | }; 35 | removeClass = function( elem, c ) { 36 | elem.classList.remove( c ); 37 | }; 38 | } 39 | else { 40 | hasClass = function( elem, c ) { 41 | return classReg( c ).test( elem.className ); 42 | }; 43 | addClass = function( elem, c ) { 44 | if ( !hasClass( elem, c ) ) { 45 | elem.className = elem.className + ' ' + c; 46 | } 47 | }; 48 | removeClass = function( elem, c ) { 49 | elem.className = elem.className.replace( classReg( c ), ' ' ); 50 | }; 51 | } 52 | 53 | function toggleClass( elem, c ) { 54 | var fn = hasClass( elem, c ) ? removeClass : addClass; 55 | fn( elem, c ); 56 | } 57 | 58 | var classie = { 59 | // full names 60 | hasClass: hasClass, 61 | addClass: addClass, 62 | removeClass: removeClass, 63 | toggleClass: toggleClass, 64 | // short names 65 | has: hasClass, 66 | add: addClass, 67 | remove: removeClass, 68 | toggle: toggleClass 69 | }; 70 | 71 | // transport 72 | if ( typeof define === 'function' && define.amd ) { 73 | // AMD 74 | define( classie ); 75 | } else { 76 | // browser global 77 | window.classie = classie; 78 | } 79 | 80 | })( window ); 81 | -------------------------------------------------------------------------------- /app/public/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/client/libraries/dashboard/delegates/delegates.js: -------------------------------------------------------------------------------- 1 | Template.delegates.rendered = function() { 2 | $(".select2").select2({ 3 | width: 200 4 | }) 5 | } 6 | 7 | Template.delegates.helpers({ 8 | not_delegated: function(delegate_id) { 9 | if (delegate_id === Meteor.userId()) { 10 | return false; 11 | } 12 | if (Meteor.user().delegates) { 13 | var delegates = Meteor.user().delegates; 14 | for (var i = 0; i < delegates.length; i++) { 15 | if (delegate_id === delegates[i].delegate) { 16 | return false; 17 | } 18 | } 19 | } 20 | return true; 21 | } 22 | }) 23 | 24 | Template.delegates.events({ 25 | 'click .delegate__delegate': function() { 26 | $(".select2").select2({ 27 | width: 200 28 | }) 29 | }, 30 | 'click #delegate__submit': function() { 31 | var delegate = {} 32 | delegate['description'] = $("#personal__description").val(); 33 | delegate['domain'] = $("#personal_expertise").val(); 34 | 35 | //If no description or domain entered, return false 36 | if (delegate['description'] === '' || delegate['domain'] === null) { 37 | return false; 38 | } 39 | $('#becomeDelegate').modal('hide'); 40 | 41 | var user = Meteor.users.findOne({_id: Meteor.userId()}); 42 | delegate['userID'] = user._id; 43 | delegate['username'] = user.services.github.username; 44 | 45 | HTTP.get('https://api.github.com/users/' + delegate['username'], function(error, result) { 46 | delegate['profile_pic'] = result.data.avatar_url; 47 | delegate['name'] = delegate['username']; 48 | 49 | // If User has specified first and last name, get it 50 | if (result.data.name) { 51 | delegate['name'] = result.data.name; 52 | } 53 | 54 | delegate['link'] = result.data.html_url; 55 | 56 | Meteor.call('new_delegate', delegate, function(error, success) { 57 | if (!error) { 58 | console.log("You are a Delegate now! Use your powers wisely"); 59 | } 60 | }); 61 | }) 62 | }, 63 | 'click .delegatePerson': function() { 64 | var id = '#' + this._id+ "-domain"; 65 | 66 | var domain = $(id).val(); 67 | var user = Meteor.user(); 68 | var delegate = this._id; 69 | 70 | Meteor.call('delegation', domain, user, delegate, function(error, success) { 71 | if (error) { 72 | $('.delegates').append('
×ERROR!' + error.message + '
') 73 | } 74 | }); 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # About the Tests 2 | 3 | these tests were used in creating and refining the Vote Count function utilized for the application. 4 | 5 | ## Generator.js 6 | 7 | Creates a test function that basically creates 3 randomized json objects (example JSON structure below): 8 | - One for the poll (uservotes) 9 | - One for the delegates 10 | - One for the users 11 | 12 | ### Delegates: 13 | ``` 14 | { 15 | "_id":"bsJf6XjTKTPtnEFYb", 16 | "delegate":{ 17 | "description":"Very cool ey", 18 | "domain":[ 19 | "finance" 20 | ], 21 | "userID":"bsJf6XjTKTPtnEFYb", 22 | "username":"domschiener", 23 | "profile_pic":"https://avatars.githubusercontent.com/u/9785885?v=3", 24 | "name":"Dominik Messiah", 25 | "link":"https://github.com/domschiener" 26 | }, 27 | "delegations":[ 28 | { 29 | "domain":"finance", 30 | "user":"HvJf6XjTKTPtnEFYb" 31 | } 32 | ] 33 | } 34 | ``` 35 | 36 | 37 | ### Uservotes: 38 | ``` 39 | { 40 | "_id":"xsTKkv8yY29HY5cTY", 41 | "vote":[ 42 | { 43 | "voter":"kBDJXksEPgaNmi6Hk", 44 | "option":"15", 45 | "poll":"xsTKkv8yY29HY5cTY", 46 | "votedAt": ISODate("2016-01-19T09:39:01.792 Z") 47 | }, 48 | ] 49 | }{ 50 | "_id":"PBQ2GDp4bXkk8jB5S", 51 | "vote":[ 52 | { 53 | "voter":"kBDJXksEPgaNmi6Hk", 54 | "option":"Yes", 55 | "poll":"PBQ2GDp4bXkk8jB5S", 56 | "votedAt": ISODate("2016-01-19T15:35:17.103 Z") 57 | } 58 | ] 59 | } 60 | ``` 61 | 62 | ### Users Profile 63 | ``` 64 | { 65 | "_id":"HvJf6XjTKTPtnEFYb", 66 | "createdAt": ISODate("2016-01-19T16:35:48.335 Z"), 67 | "services":{ 68 | "github":{ 69 | "id":1, 70 | "accessToken":"asdf", 71 | "email":"dom@fileyy.com", 72 | "username":"domschiener", 73 | "emails":[ 74 | { 75 | "email":"dom@fileyy.com", 76 | "primary":true, 77 | "verified":true 78 | } 79 | ] 80 | }, 81 | "resume":{ 82 | "loginTokens":[ 83 | { 84 | "when": ISODate("2016-01-19T16:35:48.337 Z"), 85 | "hashedToken":"asdf" 86 | } 87 | ] 88 | } 89 | }, 90 | "profile":{ 91 | "name":"Dominik Schiener" 92 | }, 93 | "delegate":true, 94 | "description":"fdsafdsa", 95 | "profile_pic":"https://avatars.githubusercontent.com/u/9785885?v=3", 96 | "expertise":[ 97 | "finance" 98 | ], 99 | "delegates":[ 100 | { 101 | "delegate":null, 102 | "domain":[ 103 | "finance" 104 | ] 105 | }, 106 | { 107 | "delegate":"bsJf6XjTKTPtnEFYb", 108 | "domain":[ 109 | "finance" 110 | ] 111 | } 112 | ] 113 | } 114 | ``` 115 | -------------------------------------------------------------------------------- /app/client/libraries/dashboard/voted/poll_voted.js: -------------------------------------------------------------------------------- 1 | Template.voted.onRendered(function() { 2 | var timer = Session.get('timer') 3 | $('#countdown').countdown(timer, function(event) { 4 | $(this).html(event.strftime('%Dd %H:%M:%S')); 5 | }); 6 | }) 7 | 8 | Template.voted.helpers({ 9 | vote_count: function() { 10 | return this.count; 11 | }, 12 | time_limit: function() { 13 | var current_poll = this; 14 | 15 | if (current_poll.poll.isvoted) { 16 | Session.set('timer','2015/01/01'); 17 | } 18 | else { 19 | var end_date = new Date(current_poll.endDate); 20 | var seconds = end_date.getSeconds(); 21 | var minutes = end_date.getMinutes(); 22 | var hours = end_date.getHours(); 23 | var days = end_date.getUTCDate(); 24 | var months = end_date.getMonth() + 1; 25 | var year = end_date.getFullYear(); 26 | var final_date = year + '/' + months + '/' + days + ' ' + hours + ':' + minutes + ':' + seconds; 27 | 28 | Session.set('timer', '2015/01/01'); 29 | } 30 | }, 31 | topGenresChart: function() { 32 | var current_poll = poll.findOne(this._id); 33 | var poll_options = current_poll.poll.options; 34 | 35 | var votes_counted = {}; 36 | if (current_poll.votes) { 37 | for (var i = 0, j = current_poll.votes.length; i < j; i++) { 38 | votes_counted[current_poll.votes[i]] = (votes_counted[current_poll.votes[i]] || 0) + 1; 39 | } 40 | } 41 | 42 | var votes_full = []; 43 | for (var i = 0; i < poll_options.length; i++) { 44 | var option = poll_options[i]; 45 | if (votes_counted[option]) { 46 | votes_full.push(votes_counted[option]); 47 | } 48 | else { 49 | votes_full.push(0); 50 | } 51 | } 52 | 53 | return { 54 | chart: { 55 | backgroundColor: '#fff', 56 | type: 'bar', 57 | }, 58 | title: { 59 | text: '' 60 | }, 61 | xAxis: { 62 | categories: poll_options, 63 | labels: { 64 | style: { 65 | fontSize: '16px', 66 | color: '#000' 67 | } 68 | } 69 | }, 70 | yAxis: { 71 | min: 0, 72 | allowDecimals: false, 73 | labels: { 74 | overflow: 'justify', 75 | style: { 76 | fontSize: '13px', 77 | color: '#000' 78 | }, 79 | }, 80 | title: { 81 | text: '' 82 | }, 83 | gridLineColor: 'transparent', 84 | lineColor: 'transparent' 85 | }, 86 | plotOptions: { 87 | bar: { 88 | dataLabels: { 89 | enabled: true 90 | } 91 | } 92 | }, 93 | credits: { 94 | enabled: false 95 | }, 96 | series: [{ 97 | data: votes_full, 98 | color: '#2ecc71', 99 | name: 'Votes', 100 | showInLegend: false, 101 | dataLabels: { 102 | enabled: true, 103 | color: '#000', 104 | style: { 105 | fontSize: '16px' 106 | } 107 | } 108 | }] 109 | } 110 | } 111 | }) 112 | -------------------------------------------------------------------------------- /app/client/stylesheets/dashboard/vote/poll.css: -------------------------------------------------------------------------------- 1 | .poll__section { 2 | width: 100%; 3 | min-height: 100%; 4 | background-color: #F8F3F0; 5 | top: 60px; 6 | overflow: hidden; 7 | margin: 60px 0 0; 8 | } 9 | 10 | .poll { 11 | max-width: 750px; 12 | min-height: 550px; 13 | background-color: #fff; 14 | text-align: center; 15 | border: 2px solid #ccc; 16 | border-bottom-left-radius: 6px; 17 | border-bottom-right-radius: 6px; 18 | margin: -5px auto 15px; 19 | padding: 25px; 20 | } 21 | 22 | .rainbow_border { 23 | max-width: 750px; 24 | padding: 0; 25 | list-style:none; 26 | width: auto; 27 | font-size:0; 28 | border-top-left-radius: 6px; 29 | border-top-right-radius: 6px; 30 | margin: 50px auto 0; 31 | overflow: hidden; 32 | } 33 | 34 | .rainbow_border li { 35 | display: inline-block; 36 | width: 20%; 37 | height: 20px; 38 | } 39 | 40 | .rainbow_border li:nth-child(1) { 41 | background:#2ecc71; 42 | } 43 | 44 | .rainbow_border li:nth-child(2) { 45 | background:#3498db; 46 | } 47 | 48 | .rainbow_border li:nth-child(3) { 49 | background:#f1c40f; 50 | } 51 | 52 | .rainbow_border li:nth-child(4) { 53 | background:#e74c3c; 54 | } 55 | 56 | .rainbow_border li:nth-child(5) { 57 | background:#9b59b6; 58 | } 59 | 60 | .poll__title { 61 | font-size: 56px; 62 | } 63 | 64 | .poll__description { 65 | margin: 35px auto 80px; 66 | font-size: 20px; 67 | font-weight: 200; 68 | text-align: center; 69 | } 70 | 71 | .vote_small { 72 | position: relative; 73 | width: 80px; 74 | background-color: #fff; 75 | top: -12px; 76 | margin: 0 auto 50px; 77 | padding-left: 4px; 78 | } 79 | 80 | /** 81 | * Poll Options 82 | **/ 83 | 84 | .poll__options { 85 | border-top: 2px solid #000; 86 | } 87 | 88 | /** If only two options **/ 89 | 90 | .two_options { 91 | color: #fff; 92 | } 93 | 94 | .option_one { 95 | float: left; 96 | clear: none; 97 | height: 120px; 98 | width: 50%; 99 | position: relative; 100 | overflow: visible; 101 | background-color: #3498db; 102 | border-radius: 6px; 103 | 104 | display: flex; 105 | align-items: center; 106 | justify-content: center; 107 | } 108 | 109 | 110 | .option_two { 111 | float: right; 112 | clear: none; 113 | height: 120px; 114 | min-width: 50%; 115 | width: auto; 116 | position: relative; 117 | overflow: visible; 118 | background-color: #2ecc71; 119 | border-radius: 6px; 120 | 121 | display: flex; 122 | align-items: center; 123 | justify-content: center; 124 | } 125 | 126 | .option_one:hover, .option_two:hover { 127 | cursor: pointer; 128 | } 129 | 130 | .option_display { 131 | font-size: 45px; 132 | margin: 0; 133 | } 134 | 135 | .between h2 { 136 | margin: 0; 137 | } 138 | 139 | .between { 140 | height: 120px; 141 | width: 120px; 142 | background-color: rgb(58,65,86); 143 | position: absolute; 144 | overflow: hidden; 145 | margin: auto; 146 | z-index: 1; 147 | border-radius: 50%; 148 | left: 0; 149 | right: 0; 150 | 151 | display: flex; 152 | align-items: center; 153 | justify-content: center; 154 | } 155 | 156 | /** Multiple Options **/ 157 | 158 | .multi_options { 159 | display: inline-block; 160 | text-align: center; 161 | } 162 | 163 | .multi_option { 164 | margin: 20px; 165 | min-width: 200px; 166 | min-height: 120px; 167 | background-color: #f1c40f; 168 | border-radius: 4px; 169 | } 170 | -------------------------------------------------------------------------------- /app/client/templates/dashboard/vote/vote.html: -------------------------------------------------------------------------------- 1 | 89 | -------------------------------------------------------------------------------- /app/client/stylesheets/dashboard/main/dashboard.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Main Dashboard Styles 3 | **/ 4 | 5 | .dashboard { 6 | width: 100%; 7 | min-height: 100%; 8 | background-color: #F8F3F0; 9 | top: 60px; 10 | overflow: hidden; 11 | margin: 60px 0 0; 12 | } 13 | 14 | 15 | /** 16 | * Dashboard Top 17 | **/ 18 | 19 | .dashboard__top { 20 | max-width: 1080px; 21 | margin: 60px auto 0; 22 | height: 650px; 23 | } 24 | 25 | .dashboard__top_title { 26 | font-size: 34px; 27 | margin: 35px 0; 28 | font-weight: 600; 29 | } 30 | 31 | .panel-title { 32 | font-size: 20px; 33 | } 34 | 35 | .panel_button { 36 | margin: 10px 30px; 37 | } 38 | 39 | /** 40 | * Dashboard Activity 41 | **/ 42 | 43 | .dashboard__past { 44 | min-height: 500px; 45 | width: 500px; 46 | padding: 0; 47 | margin-right: 25px; 48 | overflow: hidden; 49 | } 50 | 51 | .dashboard__feed { 52 | list-style:none; 53 | overflow-y: scroll; 54 | max-height: 500px; 55 | padding: 0; 56 | margin: 0; 57 | } 58 | 59 | .dashboard__feed_item { 60 | height: 50px; 61 | margin: 6px 0; 62 | } 63 | 64 | .feed__voted_left, 65 | .feed__delegate_left, 66 | .feed__addition_left { 67 | height: 100%; 68 | z-index: 100; 69 | } 70 | 71 | .feed__voted_icon { 72 | height: 100%; 73 | width: 100%; 74 | border: 2px solid #2e611f; 75 | border-radius: 100%; 76 | background-color: #F8F3F0; 77 | } 78 | 79 | .feed__addition_icon { 80 | height: 100%; 81 | width: 100%; 82 | border: 2px solid #4b1048; 83 | border-radius: 100%; 84 | background-color: #F8F3F0; 85 | } 86 | 87 | .feed__voted_icon, 88 | .feed__voted_right, 89 | .feed__delegate_icon, 90 | .feed__delegate_right, 91 | .feed__addition_icon, 92 | .feed__addition_right { 93 | display: -ms-flexbox; 94 | display: -webkit-flex; 95 | display: flex; 96 | 97 | -ms-flex-align: center; 98 | -webkit-align-items: center; 99 | -webkit-box-align: center; 100 | 101 | align-items: center; 102 | justify-content: center; 103 | } 104 | 105 | .feed__voted_icon span { 106 | color: #4da335; 107 | font-size: 34px; 108 | } 109 | 110 | .feed__addition_icon span { 111 | color: #5d275a; 112 | font-size: 32px; 113 | } 114 | 115 | .feed__voted_right { 116 | height: 100%; 117 | margin-left: -56px; 118 | background-color: #2e611f; 119 | border-radius: 25px; 120 | color: #fff; 121 | } 122 | 123 | .feed__delegate_right { 124 | height: 100%; 125 | margin-left: -56px; 126 | background-color: #253F7B; 127 | border-radius: 25px; 128 | color: #fff; 129 | } 130 | 131 | .feed__addition_right { 132 | height: 100%; 133 | margin-left: -56px; 134 | background-color: #4B1048; 135 | border-radius: 25px; 136 | color: #fff; 137 | } 138 | 139 | .feed__message { 140 | margin: 0; 141 | padding: 0; 142 | } 143 | 144 | .feed__picture { 145 | height: 50px; 146 | width: 50px; 147 | border-radius: 100%; 148 | } 149 | 150 | .panel-collapse { 151 | text-align: center; 152 | } 153 | 154 | /** 155 | * Dashboard Stats 156 | **/ 157 | 158 | .dashboard__stats { 159 | max-width: 1080px; 160 | margin: 60px auto 0; 161 | min-height: 100%; 162 | } 163 | 164 | .dashboard__stats_title { 165 | font-size: 34px; 166 | margin: 35px 0; 167 | font-weight: 600; 168 | } 169 | 170 | .dashboard__stats_global { 171 | background-color: #fff; 172 | padding: 0; 173 | text-align: center; 174 | margin: 25px auto 50px; 175 | 176 | } 177 | 178 | .dashboard__stats_global div { 179 | height: 200px; 180 | width: 250px; 181 | margin: 0 5px 50px; 182 | border: 2px solid #ccc; 183 | border-radius: 5px; 184 | } 185 | 186 | .global__stats_title { 187 | text-align: center; 188 | margin: 35px 0; 189 | font-size: 28px; 190 | } 191 | 192 | .global__stats_item { 193 | text-align: center; 194 | font-size: 30px; 195 | } 196 | -------------------------------------------------------------------------------- /app/shared/routes.js: -------------------------------------------------------------------------------- 1 | Router.route('/', { 2 | template: 'home' 3 | }); 4 | 5 | Router.route('/join', { 6 | name: 'join', 7 | template: 'login', 8 | onBeforeAction: function() { 9 | if(Meteor.user()) { 10 | Router.go('dashboard'); 11 | } else { 12 | this.next(); 13 | } 14 | } 15 | }); 16 | 17 | Router.route('/logout', { 18 | name: 'logout', 19 | template: 'login', 20 | onRun: function() { 21 | Meteor.logout(); 22 | Router.go('join'); 23 | this.next(); 24 | } 25 | }); 26 | 27 | Router.route('/dashboard', { 28 | name: 'dashboard', 29 | layoutTemplate: 'dashboard_menu', 30 | template: 'dashboard', 31 | data: function() { 32 | var active_polls = poll.find({'poll.isvoted': false}, {sort: {createdAt: -1}}).fetch(); 33 | var past_polls = poll.find({'poll.isvoted': true}, {sort: {createdAt: -1}}).fetch(); 34 | // Only return the last 10 issues 35 | return {'polls': active_polls.slice(0, 10), 'pastPolls': past_polls.slice(0, 10)}; 36 | }, 37 | onBeforeAction: function() { 38 | var user = Meteor.userId(); 39 | if(user) { 40 | this.next(); 41 | } else { 42 | Router.go('join'); 43 | } 44 | } 45 | }); 46 | 47 | Router.route('/dashboard/vote', { 48 | name: 'vote', 49 | layoutTemplate: 'dashboard_menu', 50 | template: 'vote', 51 | data: function() { 52 | var active_polls = poll.find({'poll.isvoted': false}, {sort: {createdAt: -1}}).fetch(); 53 | var past_polls = poll.find({'poll.isvoted': true}, {sort: {createdAt: -1}}).fetch(); 54 | return {'polls': active_polls, 'pastPolls': past_polls}; 55 | }, 56 | onBeforeAction: function() { 57 | var user = Meteor.userId(); 58 | if(user) { 59 | this.next(); 60 | } else { 61 | Router.go('join'); 62 | } 63 | } 64 | }); 65 | 66 | Router.route('/dashboard/vote/:_id', { 67 | name: 'poll', 68 | layoutTemplate: 'dashboard_menu', 69 | template: 'poll', 70 | data: function() { 71 | return poll.findOne({_id: this.params._id}); 72 | }, 73 | onBeforeAction: function() { 74 | var user = Meteor.userId(); 75 | 76 | // If no user logged in, redirect to join 77 | if(user) { 78 | this.next(); 79 | } else { 80 | Router.go('join'); 81 | } 82 | 83 | var current_poll = poll.findOne({_id: this.params._id}); 84 | if (current_poll) { 85 | if (current_poll.poll.isvoted) { 86 | Router.go('voted', {_id: this.params._id}); 87 | } 88 | } 89 | } 90 | }); 91 | 92 | Router.route('/dashboard/results/:_id/', { 93 | name: 'voted', 94 | layoutTemplate: 'dashboard_menu', 95 | template: 'voted', 96 | data: function() { 97 | return poll.findOne({_id: this.params._id}); 98 | }, 99 | onBeforeAction: function() { 100 | var user = Meteor.userId(); 101 | if(user) { 102 | this.next(); 103 | } else { 104 | Router.go('join'); 105 | } 106 | 107 | var current_poll = poll.findOne({_id: this.params._id}); 108 | if (current_poll) { 109 | if (current_poll.poll.isvoted === false) { 110 | Router.go('poll', {_id: this.params._id}); 111 | } 112 | } 113 | } 114 | }); 115 | 116 | Router.route('/dashboard/create', { 117 | name: 'create', 118 | layoutTemplate: 'dashboard_menu', 119 | template: 'create', 120 | onBeforeAction: function() { 121 | var user = Meteor.userId(); 122 | if(user) { 123 | this.next(); 124 | } else { 125 | Router.go('join'); 126 | } 127 | } 128 | }); 129 | 130 | Router.route('/dashboard/delegates', { 131 | name: 'delegates', 132 | layoutTemplate: 'dashboard_menu', 133 | template: 'delegates', 134 | data: function() { 135 | var delegates = Delegates.find({}).fetch(); 136 | return {'delegates': delegates}; 137 | }, 138 | onBeforeAction: function() { 139 | var user = Meteor.userId(); 140 | if(user) { 141 | this.next(); 142 | } else { 143 | Router.go('join'); 144 | } 145 | } 146 | }) 147 | -------------------------------------------------------------------------------- /tests/withDelegates/voteCount.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | function voteCounting(poll_id, domain) { 4 | var votes = fs.readFileSync('./uservotes.json', "utf8"); 5 | votes = JSON.parse(votes); 6 | 7 | var voters = []; 8 | var delegates = []; 9 | 10 | for (var i = 0; i < votes.vote.length; i++) { 11 | if (votes.vote[i].delegate) { 12 | delegates.push(votes.vote[i]); 13 | } else { 14 | voters.push(votes.vote[i]); 15 | } 16 | } 17 | 18 | var allDelegates = fs.readFileSync('./delegates.json', "utf8"); 19 | allDelegates = JSON.parse(allDelegates); 20 | 21 | var allVoters = fs.readFileSync('./voters.json', "utf8"); 22 | allVoters = JSON.parse(allVoters); 23 | 24 | var output = voters.slice(); 25 | 26 | allDelegates.forEach(function(delegate, index) { 27 | delegates.forEach(function(current_delegate) { 28 | if (delegate._id === current_delegate.voter) { 29 | output.push(recursiveCount(delegates, voters, delegate, current_delegate, allVoters, allDelegates, domain)); 30 | } 31 | }) 32 | }) 33 | var count = 0; 34 | output.forEach(function(voter) { 35 | count += finalCount(voter); 36 | }) 37 | 38 | fs.writeFile('./votestructure.json', JSON.stringify(output), {flag: "w+"}, function(error, success) { 39 | if (!error) { 40 | console.log("Successfully generated Votestructure. Final Vote Count: ", count); 41 | } 42 | }); 43 | 44 | //recursiveCount(voters, current_delegate, false, delegates, domain, allDelegates); 45 | } 46 | 47 | /** 48 | * Recursive Count Helper Functions 49 | **/ 50 | 51 | function voterVoted(votestructure, voter) { 52 | var found = false; 53 | 54 | votestructure.forEach(function(curr_voter) { 55 | if (curr_voter.voter === voter) { 56 | found = true; 57 | } 58 | }); 59 | 60 | return found; 61 | } 62 | 63 | function delegateVoted(votestructure, voter) { 64 | var found = false; 65 | 66 | votestructure.forEach(function(curr_voter) { 67 | if (curr_voter.voter === voter) { 68 | found = true; 69 | } 70 | }); 71 | 72 | return found; 73 | } 74 | 75 | function finalCount(voter) { 76 | var count = 1; 77 | if (voter.delegates) { 78 | voter.delegates.forEach(function(delegVoter) { 79 | if (delegVoter.delegates) { 80 | count += finalCount(delegVoter); 81 | } 82 | else { 83 | count += 1; 84 | } 85 | }); 86 | } 87 | 88 | return count; 89 | } 90 | 91 | 92 | function recursiveCount(delegateVoters, votersVoters, delegate, curVoter, allVoters, allDelegates, domain) { 93 | 94 | var voterObj = { 95 | 'voter': delegate._id, 96 | 'option': curVoter.option, 97 | 'delegates': [] 98 | } 99 | 100 | delegate.delegations.forEach(function(delegVoter) { 101 | // Retrieve current voter object 102 | var voter; 103 | allVoters.forEach(function(newVoter) { 104 | if (newVoter._id === delegVoter.voter) { 105 | voter = newVoter; 106 | } 107 | }); 108 | 109 | if (voter.delegate && 110 | delegVoter.domain === domain && 111 | delegateVoted(delegateVoters, voter._id) !== true) { 112 | // Retrieve new Delegate object 113 | var newDelegate; 114 | allDelegates.forEach(function(delegate) { 115 | if (delegate._id === voter._id) { 116 | newDelegate = delegate; 117 | } 118 | }) 119 | voterObj.delegates.push(recursiveCount(delegateVoters, votersVoters, newDelegate, curVoter, allVoters, allDelegates, domain)); 120 | } 121 | else if (delegVoter.domain === domain && 122 | voterVoted(votersVoters, voter._id) !== true) { 123 | // If voter is not delegate himself, we create a new object and append to delegate voterObj 124 | var newVoter = {}; 125 | newVoter['voter'] = voter._id; 126 | newVoter['option'] = curVoter.option; 127 | voterObj.delegates.push(newVoter); 128 | } 129 | }) 130 | 131 | return voterObj; 132 | } 133 | 134 | voteCounting('xr8on9pchumcxr', 'main'); 135 | -------------------------------------------------------------------------------- /tests/onlyVoters/voteCount.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | function voteCounting(poll_id, domain) { 4 | var votes = fs.readFileSync('./uservotes.json', "utf8"); 5 | votes = JSON.parse(votes); 6 | 7 | var voters = []; 8 | var delegates = []; 9 | 10 | for (var i = 0; i < votes.vote.length; i++) { 11 | if (votes.vote[i].delegate) { 12 | delegates.push(votes.vote[i]); 13 | } else { 14 | voters.push(votes.vote[i]); 15 | } 16 | } 17 | var current_delegate; 18 | var allDelegates = fs.readFileSync('./delegates.json', "utf8"); 19 | allDelegates = JSON.parse(allDelegates); 20 | 21 | allDelegates.forEach(function(delegate) { 22 | if (delegate._id === delegates[0].voter) { 23 | current_delegate = delegate; 24 | } 25 | }) 26 | 27 | recursiveCount(voters, current_delegate, false, delegates, domain, allDelegates); 28 | } 29 | 30 | function voterVoted(votestructure, voter) { 31 | votestructure.forEach(function(curr_voter) { 32 | if (curr_voter.voter === voter) { 33 | return true; 34 | } 35 | }); 36 | 37 | return false; 38 | } 39 | 40 | function finalCount(votestructure) { 41 | var count = 0; 42 | 43 | votestructure.forEach(function(voter) { 44 | count += 1; 45 | if (voter.delegates) { 46 | voter.delegates.forEach(function(delegateVoters) { 47 | count += 1; 48 | }) 49 | } 50 | }); 51 | 52 | return count; 53 | } 54 | 55 | function recursiveCount(votestructure, current_delegate, pop_delegate, delegates, domain, allDelegates) { 56 | // Exit function, if all delegates searched, return the JSON output 57 | if (delegates.length < 1) { 58 | var count = finalCount(votestructure); 59 | fs.writeFile('./votestructure.json', JSON.stringify(votestructure), {flag: "w+"}, function(error, success) { 60 | if (!error) { 61 | console.log("Successfully generated Votestructure. Final Vote Count: ", count); 62 | } 63 | }); 64 | return votestructure; 65 | } 66 | 67 | // If we went through all voters of a delegate, pop delegate and restart with new delegate 68 | if (pop_delegate) { 69 | allDelegates.forEach(function(delegate) { 70 | if (delegate._id === delegates[0].voter) { 71 | current_delegate = delegate; 72 | } 73 | }); 74 | } 75 | 76 | var voterObj = { 77 | 'voter': current_delegate.voter === true ? current_delegate.voter : delegates[0].voter, 78 | 'option': delegates[0].option, 79 | 'delegates': [] 80 | } 81 | 82 | var allVoters = fs.readFileSync('./voters.json', "utf8"); 83 | allVoters = JSON.parse(allVoters); 84 | var containsDelegates = false; 85 | 86 | for (var j = 0; j < current_delegate.delegations.length; j++) { 87 | var current_voter; 88 | 89 | allVoters.forEach(function(voter) { 90 | if (voter._id === current_delegate.delegations[j]['voter']) { 91 | current_voter = voter; 92 | } 93 | }); 94 | 95 | // If current_voter is a Delegate herself, we call the function recursively counting voters 96 | // if (current_voter.delegate) { 97 | // containsDelegates = true; 98 | // console.log("here"); 99 | // voterObj.delegates.push(recursiveCount(votestructure, current_voter, false, delegates, domain, allDelegates)); 100 | // } 101 | if (current_delegate.delegations[j].domain === domain && 102 | voterVoted(votestructure, current_delegate.delegations[j].voter) !== true) { 103 | // If voter is not delegate himself, we create a new object and append to delegate voterObj 104 | var newVoter = {}; 105 | newVoter['voter'] = current_delegate.delegations[j].voter; 106 | newVoter['option'] = delegates[0].option; 107 | voterObj.delegates.push(newVoter); 108 | } 109 | 110 | // IF NO MORE DELEGATES, RETURN THE VOTE STRUCTURE 111 | } 112 | 113 | // If delegates, return the voter obj, else pop delegate and start anew 114 | if (containsDelegates) { 115 | return voterObj; 116 | } 117 | 118 | votestructure.push(voterObj); 119 | 120 | // If we went through all voters of a delegate, pop delegate and restart with new delegate 121 | delegates.shift(); 122 | 123 | recursiveCount(votestructure, current_delegate, true, delegates, domain, allDelegates); 124 | } 125 | 126 | voteCounting('xr8on9pchumcxr', 'main'); 127 | -------------------------------------------------------------------------------- /app/client/templates/homepage/home.html: -------------------------------------------------------------------------------- 1 | 82 | -------------------------------------------------------------------------------- /tests/onlyVoters/generator.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var domains = ['main', 'finance']; 4 | 5 | function randomID() { 6 | return Math.random().toString(36).substring(4); 7 | } 8 | 9 | /** 10 | * Generates random user objects that represent the structure of the MongoDB collection 11 | **/ 12 | 13 | function genUsers(numTimes) { 14 | var allResults = []; 15 | var delegates = []; 16 | var voters = []; 17 | var output = []; 18 | 19 | 20 | for (var i = 0; i < numTimes; i++) { 21 | var constructor = {}; 22 | constructor['_id'] = randomID(); 23 | // With 30% probability, voter == delegate 24 | constructor['delegate'] = Math.random() >= 0.7 ? true : false; 25 | if (constructor['delegate']) { 26 | constructor['expertise'] = Math.random() >= 0.5 ? 'main' : 'finance'; 27 | delegates.push(constructor['_id']); 28 | } 29 | else { 30 | voters.push(constructor['_id']); 31 | } 32 | output.push(constructor); 33 | } 34 | 35 | fs.writeFile('./voters.json', JSON.stringify(output), {flag: "w+"}, function(error, success) { 36 | if (!error) { 37 | console.log("Successfully generated Users.") 38 | } 39 | }); 40 | allResults.push(delegates, voters, output); 41 | return allResults; 42 | } 43 | 44 | /** 45 | * Generates (random) JSON Object with delegation relationships between voters. 46 | * Saves the object locally in delegates.json 47 | **/ 48 | 49 | function genDelegates(allResults) { 50 | var delegates = allResults[0]; 51 | var voters = allResults[1]; 52 | var users = allResults[2]; 53 | var output = []; 54 | 55 | function delegations(expertise) { 56 | // We basically take a random amount of voters and add them to the delegate 57 | var voter = []; 58 | var limit1 = Math.random() * voters.length; 59 | var limit2 = Math.random() * voters.length; 60 | 61 | var iterations = voters.splice(Math.min(limit1, limit2), Math.min(limit1, limit2)); 62 | iterations.forEach(function(item) { 63 | var newVoter = { 64 | 'domain': expertise, 65 | 'voter': item 66 | } 67 | voter.push(newVoter); 68 | }); 69 | 70 | return voter; 71 | } 72 | 73 | delegates.forEach(function(delegate) { 74 | users.forEach(function(user) { 75 | if (user._id === delegate) { 76 | var constructor = {}; 77 | 78 | constructor['_id'] = delegate; 79 | constructor['delegate'] = [{'domain': user.expertise}]; 80 | constructor['delegations'] = voters[0] === undefined ? [] : delegations(user.expertise); 81 | 82 | output.push(constructor); 83 | } 84 | }) 85 | }) 86 | 87 | fs.writeFile('./delegates.json', JSON.stringify(output), {flag: "w+"}, function(error, success) { 88 | if (!error) { 89 | console.log("Successfully generated Delegates.") 90 | } 91 | }); 92 | return output; 93 | } 94 | 95 | 96 | /** 97 | * Generates a new poll and lets delegates and users vote 98 | **/ 99 | 100 | function genUservotes(allVoters) { 101 | // Generates a (somewhat) random constructor of uservotes 102 | var constructor = {} 103 | constructor['_id'] = 'xr8on9pchumcxr'; //randomID(); 104 | constructor['vote'] = []; 105 | 106 | allVoters.forEach(function(voter, index) { 107 | var isDelegate = voter.delegate; 108 | 109 | if (isDelegate) { 110 | var tmpVoter = { 111 | "voter": voter._id, 112 | "option": Math.random() >= 0.5 ? 'Yes' : 'NO', 113 | "delegate": isDelegate, 114 | "poll": constructor['_id'] 115 | } 116 | 117 | constructor['vote'].push(tmpVoter); 118 | } 119 | else { 120 | // With 15% probability, the voter herself will vote (even if delegation) 121 | if (Math.random() >= 0.85) { 122 | var tmpVoter = { 123 | "voter": voter._id, 124 | "option": Math.random() >= 0.5 ? 'Yes' : 'NO', 125 | "delegate": isDelegate, 126 | "poll": constructor['_id'] 127 | } 128 | 129 | constructor['vote'].push(tmpVoter); 130 | } 131 | } 132 | }) 133 | 134 | fs.writeFile('./uservotes.json', JSON.stringify(constructor), {flag: "w+"}, function(error, success) { 135 | if (!error) { 136 | console.log("Successfully generated Uservotes.") 137 | } 138 | }); 139 | return constructor; 140 | } 141 | 142 | 143 | 144 | var voters = genUsers(50); 145 | var delegates = genDelegates(voters); 146 | var votes = genUservotes(voters[2]); 147 | -------------------------------------------------------------------------------- /app/client/templates/dashboard/main/components.html: -------------------------------------------------------------------------------- 1 | 34 | 35 | 68 | 69 | 109 | -------------------------------------------------------------------------------- /app/public/fonts/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IcoMoon Demo 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Font Name: icomoon (Glyphs: 4)

13 |
14 |
15 |

Grid Size: 16

16 |
17 |
18 | 19 | 20 | 21 | icon-home 22 |
23 |
24 | 25 | 26 |
27 |
28 | liga: 29 | 30 |
31 |
32 |
33 |
34 | 35 | 36 | 37 | icon-user-plus 38 |
39 |
40 | 41 | 42 |
43 |
44 | liga: 45 | 46 |
47 |
48 |
49 |
50 | 51 | 52 | 53 | icon-cog 54 |
55 |
56 | 57 | 58 |
59 |
60 | liga: 61 | 62 |
63 |
64 |
65 |
66 | 67 | 68 | 69 | icon-clipboard 70 |
71 |
72 | 73 | 74 |
75 |
76 | liga: 77 | 78 |
79 |
80 |
81 | 82 | 83 |
84 |

Font Test Drive

85 | 90 | 92 | 93 |
  94 |
95 |
96 | 97 |
98 |

Generated by IcoMoon

99 |
100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /app/client/libraries/dashboard/main/menu/gnmenu.js: -------------------------------------------------------------------------------- 1 | /** 2 | * gnmenu.js v1.0.0 3 | * http://www.codrops.com 4 | * 5 | * Licensed under the MIT license. 6 | * http://www.opensource.org/licenses/mit-license.php 7 | * 8 | * Copyright 2013, Codrops 9 | * http://www.codrops.com 10 | */ 11 | ;( function( window ) { 12 | 13 | 'use strict'; 14 | 15 | // http://stackoverflow.com/a/11381730/989439 16 | function mobilecheck() { 17 | var check = false; 18 | (function(a){if(/(android|ipad|playbook|silk|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera); 19 | return check; 20 | } 21 | 22 | function gnMenu( el, options ) { 23 | this.el = el; 24 | this._init(); 25 | } 26 | 27 | gnMenu.prototype = { 28 | _init : function() { 29 | this.trigger = this.el.querySelector( 'a.gn-icon-menu' ); 30 | this.menu = this.el.querySelector( 'nav.gn-menu-wrapper' ); 31 | this.isMenuOpen = false; 32 | this.eventtype = mobilecheck() ? 'touchstart' : 'click'; 33 | this._initEvents(); 34 | 35 | var self = this; 36 | this.bodyClickFn = function() { 37 | self._closeMenu(); 38 | this.removeEventListener( self.eventtype, self.bodyClickFn ); 39 | }; 40 | }, 41 | _initEvents : function() { 42 | var self = this; 43 | 44 | if( !mobilecheck() ) { 45 | this.trigger.addEventListener( 'mouseover', function(ev) { self._openIconMenu(); } ); 46 | this.trigger.addEventListener( 'mouseout', function(ev) { self._closeIconMenu(); } ); 47 | 48 | this.menu.addEventListener( 'mouseover', function(ev) { 49 | self._openMenu(); 50 | document.addEventListener( self.eventtype, self.bodyClickFn ); 51 | } ); 52 | } 53 | this.trigger.addEventListener( this.eventtype, function( ev ) { 54 | ev.stopPropagation(); 55 | ev.preventDefault(); 56 | if( self.isMenuOpen ) { 57 | self._closeMenu(); 58 | document.removeEventListener( self.eventtype, self.bodyClickFn ); 59 | } 60 | else { 61 | self._openMenu(); 62 | document.addEventListener( self.eventtype, self.bodyClickFn ); 63 | } 64 | } ); 65 | this.menu.addEventListener( this.eventtype, function(ev) { ev.stopPropagation(); } ); 66 | }, 67 | _openIconMenu : function() { 68 | classie.add( this.menu, 'gn-open-part' ); 69 | }, 70 | _closeIconMenu : function() { 71 | classie.remove( this.menu, 'gn-open-part' ); 72 | }, 73 | _openMenu : function() { 74 | if( this.isMenuOpen ) return; 75 | classie.add( this.trigger, 'gn-selected' ); 76 | this.isMenuOpen = true; 77 | classie.add( this.menu, 'gn-open-all' ); 78 | this._closeIconMenu(); 79 | }, 80 | _closeMenu : function() { 81 | if( !this.isMenuOpen ) return; 82 | classie.remove( this.trigger, 'gn-selected' ); 83 | this.isMenuOpen = false; 84 | classie.remove( this.menu, 'gn-open-all' ); 85 | this._closeIconMenu(); 86 | } 87 | } 88 | 89 | // add to global namespace 90 | window.gnMenu = gnMenu; 91 | 92 | } )( window ); -------------------------------------------------------------------------------- /app/client/libraries/dashboard/create/create.js: -------------------------------------------------------------------------------- 1 | Template.create.rendered = function() { 2 | Session.set('NumberOfOptions', 2); 3 | 4 | $("#domain_select").select2({ 5 | width: 200, 6 | allowClear: true 7 | }); 8 | 9 | $(function() { 10 | var img_cnt = $('li.activate').index() + 1; 11 | 12 | var img_amt = $('li.form-group').length; 13 | $('.img_cnt').html(img_cnt); 14 | $('.img_amt').html(img_amt); 15 | var progress = ($('.img_cnt').text() / $('.img_amt').text()) * 100; 16 | $('.progress-bar').css("width", progress + "%"); 17 | $('.make_btn_appear').keyup(function() { 18 | $('#nxt').removeClass("hide fadeOutDown").addClass("fadeInUp"); 19 | }) 20 | $('.popup_success').keyup(function() { 21 | $('#submit').removeClass("hide fadeOutDown").addClass("fadeInUp"); 22 | }) 23 | 24 | $('#nxt').click(function() { 25 | $('#nxt').removeClass("fadeInUp").addClass('hide fadeOutDown'); 26 | 27 | if ($('.progress-form li').hasClass('activate')) { 28 | 29 | $('p.alerted').removeClass('fadeInLeft').addClass('fadeOutUp'); 30 | 31 | var $activate = $('li.activate'); 32 | var $inactive = $('li.inactive'); 33 | $activate.removeClass("fadeInRightBig activate").addClass('fadeOutLeftBig hidden'); 34 | $inactive.removeClass("hide inactive").addClass("activate fadeInRightBig").next().addClass('inactive'); 35 | 36 | var img_cnt = $('li.activate').index() + 1; 37 | 38 | var img_amt = $('li.form-group').length; 39 | $('.img_cnt').html(img_cnt); 40 | $('.img_amt').html(img_amt); 41 | var progress = ($('.img_cnt').text() / $('.img_amt').text()) * 100; 42 | $('.progress-bar').css("width", progress + "%"); 43 | } 44 | }); 45 | }); 46 | } 47 | 48 | Template.create.events({ 49 | 'click #submit': function() { 50 | event.preventDefault(); 51 | num_options = Session.get('NumberOfOptions'); 52 | var poll = { 53 | 'name': '', 54 | 'description': '', 55 | 'options': '', 56 | //TODO: Change to false once integrating with Ethereum 57 | 'isactive': true, 58 | 'isvoted': false, 59 | } 60 | 61 | poll['name']= $('#name_poll').val(); 62 | poll['description'] = $('#description').val(); 63 | var option = []; 64 | for (var i = 1; i <= num_options; i++) { 65 | element_id = "#option-" + i; 66 | option.push($(element_id).val()); 67 | } 68 | 69 | poll['options'] = option; 70 | //poll['multi_option'] = $('#multi_option_switch').is(":checked"); 71 | var hours = $('#hour_limit option:selected').text(); 72 | var days = $('#day_limit option:selected').text(); 73 | 74 | poll['domain'] = $("#domain_select").select2("val"); 75 | 76 | poll['limit_hours'] = parseInt(hours.match(/\d+/)[0]); 77 | poll['limit_days'] = parseInt(days.match(/\d+/)[0]); 78 | 79 | // Calculating Deadline of Poll 80 | var cur_date = Date.now(); 81 | var days = 1000 * 60 * 2//(poll['limit_days']) * 86400000; 82 | var hours = 0//(poll['limit_hours']) * 3600000; 83 | var deadline = cur_date + days + hours; 84 | 85 | Meteor.call('new_poll', poll, deadline, Meteor.userId(), function(error, success) { 86 | if (!error) { 87 | Router.go('poll', {_id: success}); 88 | } 89 | }); 90 | } 91 | }) 92 | 93 | Template.timelimit.helpers({ 94 | hours: function(){ 95 | return [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24]; 96 | }, 97 | days: function() { 98 | return [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14]; 99 | } 100 | }); 101 | 102 | Template.more_options.events({ 103 | 'click #add_option' : function () { 104 | 105 | //Update session storage for NumberOfOptions 106 | var numOptions = Session.get('NumberOfOptions') + 1; 107 | 108 | if (numOptions <= 10) { 109 | //Create new DOM element for additional Option 110 | var new_option = document.createElement("div"); 111 | new_option.className = "form-group"; 112 | new_option.innerHTML = ''; 113 | document.getElementById('options').appendChild(new_option); 114 | Session.set('NumberOfOptions', numOptions); 115 | } 116 | }, 117 | 'click #rmv_option' : function() { 118 | var numOptions = Session.get('NumberOfOptions'); 119 | 120 | if (numOptions > 2) { 121 | var elementId = 'option-' + numOptions; 122 | var element_to_remove = document.getElementById(elementId).parentNode; 123 | document.getElementById('options').removeChild(element_to_remove); 124 | 125 | //Update Session 126 | Session.set('NumberOfOptions', numOptions - 1); 127 | } 128 | } 129 | }); 130 | -------------------------------------------------------------------------------- /app/client/templates/dashboard/delegates/personal.html: -------------------------------------------------------------------------------- 1 | 96 | -------------------------------------------------------------------------------- /tests/treeVisualization/tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 26 | 27 | 28 | 171 | -------------------------------------------------------------------------------- /tests/withDelegates/generator.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | var domains = ['main', 'finance']; 4 | 5 | function randomID() { 6 | return Math.random().toString(36).substring(4); 7 | } 8 | 9 | /** 10 | * Generates random user objects that represent the structure of the MongoDB collection 11 | **/ 12 | 13 | function genUsers(numTimes) { 14 | var allResults = []; 15 | var delegates = []; 16 | var voters = []; 17 | var output = []; 18 | 19 | 20 | for (var i = 0; i < numTimes; i++) { 21 | var constructor = {}; 22 | constructor['_id'] = randomID(); 23 | // With 30% probability, voter == delegate 24 | constructor['delegate'] = Math.random() >= 0.7 ? true : false; 25 | if (constructor['delegate']) { 26 | constructor['expertise'] = Math.random() >= 0 ? 'main' : 'finance'; 27 | delegates.push(constructor['_id']); 28 | } 29 | else { 30 | voters.push(constructor['_id']); 31 | } 32 | output.push(constructor); 33 | } 34 | 35 | fs.writeFile('./voters.json', JSON.stringify(output), {flag: "w+"}, function(error, success) { 36 | if (!error) { 37 | console.log("Successfully generated Users.") 38 | } 39 | }); 40 | allResults.push(delegates, voters, output); 41 | 42 | return allResults; 43 | } 44 | 45 | /** 46 | * Generates (random) JSON Object with delegation relationships between voters. 47 | * Saves the object locally in delegates.json 48 | **/ 49 | 50 | function genDelegates(allResults) { 51 | var delegates = allResults[0]; 52 | var voters = allResults[1]; 53 | var users = allResults[2]; 54 | var output = []; 55 | 56 | function delegations(expertise, delegate) { 57 | // We basically take a random amount of voters and add them to the delegate 58 | var voter = []; 59 | var limit1 = Math.floor(Math.random() * voters.length); 60 | var limit2 = Math.floor(Math.random() * voters.length); 61 | 62 | var iterations = voters.splice(Math.min(limit1, limit2), Math.min(limit1, limit2)); 63 | iterations.forEach(function(item) { 64 | var newVoter = { 65 | 'domain': expertise, 66 | 'voter': item 67 | } 68 | voter.push(newVoter); 69 | }); 70 | 71 | // With 15 % probability, there is a delegation between two delegates. 72 | if (Math.random() >= 0.75) { 73 | var randomChoice = Math.floor(Math.random() * delegates.length) 74 | if (delegates[randomChoice] !== delegate) { 75 | var newVoter = { 76 | 'domain': expertise, 77 | 'voter': delegates[randomChoice] 78 | } 79 | console.log("New Delegate relationship", delegate, delegates[randomChoice]) 80 | voter.push(newVoter); 81 | } 82 | else { 83 | var newVoter = { 84 | 'domain': expertise, 85 | 'voter': delegates[randomChoice - 1] 86 | } 87 | console.log("New Delegate relationship!", delegate, delegates[randomChoice - 1]) 88 | voter.push(newVoter); 89 | } 90 | } 91 | 92 | return voter; 93 | } 94 | 95 | delegates.forEach(function(delegate) { 96 | users.forEach(function(user) { 97 | if (user._id === delegate) { 98 | var constructor = {}; 99 | 100 | constructor['_id'] = delegate; 101 | constructor['delegate'] = [{'domain': user.expertise}]; 102 | constructor['delegations'] = voters[0] === undefined ? [] : delegations(user.expertise, delegate); 103 | 104 | output.push(constructor); 105 | } 106 | }) 107 | }) 108 | 109 | fs.writeFile('./delegates.json', JSON.stringify(output), {flag: "w+"}, function(error, success) { 110 | if (!error) { 111 | console.log("Successfully generated Delegates.") 112 | } 113 | }); 114 | return output; 115 | } 116 | 117 | 118 | /** 119 | * Generates a new poll and lets delegates and users vote 120 | **/ 121 | 122 | function genUservotes(allVoters) { 123 | // Generates a (somewhat) random constructor of uservotes 124 | var constructor = {} 125 | constructor['_id'] = 'xr8on9pchumcxr'; //randomID(); 126 | constructor['vote'] = []; 127 | 128 | allVoters.forEach(function(voter, index) { 129 | var isDelegate = voter.delegate; 130 | 131 | if (isDelegate) { 132 | var tmpVoter = { 133 | "voter": voter._id, 134 | "option": Math.random() >= 0.5 ? 'Yes' : 'NO', 135 | "delegate": isDelegate, 136 | "poll": constructor['_id'] 137 | } 138 | 139 | constructor['vote'].push(tmpVoter); 140 | } 141 | else { 142 | // With 15% probability, the voter herself will vote (even if delegation) 143 | if (Math.random() >= 0.85) { 144 | var tmpVoter = { 145 | "voter": voter._id, 146 | "option": Math.random() >= 0.5 ? 'Yes' : 'NO', 147 | "delegate": isDelegate, 148 | "poll": constructor['_id'] 149 | } 150 | 151 | constructor['vote'].push(tmpVoter); 152 | } 153 | } 154 | }) 155 | 156 | fs.writeFile('./uservotes.json', JSON.stringify(constructor), {flag: "w+"}, function(error, success) { 157 | if (!error) { 158 | console.log("Successfully generated Uservotes.") 159 | } 160 | }); 161 | return constructor; 162 | } 163 | 164 | 165 | 166 | var voters = genUsers(50); 167 | var delegates = genDelegates(voters); 168 | var votes = genUservotes(voters[2]); 169 | -------------------------------------------------------------------------------- /app/client/templates/dashboard/delegates/delegates.html: -------------------------------------------------------------------------------- 1 | 121 | -------------------------------------------------------------------------------- /app/client/stylesheets/dashboard/main/menu.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:after, 3 | *::before { 4 | -webkit-box-sizing: border-box; 5 | -moz-box-sizing: border-box; 6 | box-sizing: border-box; 7 | } 8 | 9 | @font-face { 10 | font-family: 'icomoon'; 11 | src: url('/fonts/icomoon.eot?sv6yly'); 12 | src: url('/fonts/icomoon.eot?sv6yly#iefix') format('embedded-opentype'), 13 | url('/fonts/icomoon.ttf?sv6yly') format('truetype'), 14 | url('/fonts/icomoon.woff?sv6yly') format('woff'), 15 | url('/fonts/icomoon.svg?sv6yly#icomoon') format('svg'); 16 | font-weight: normal; 17 | font-style: normal; 18 | } 19 | 20 | [class^="gn-icon-"], [class*=" gn-icon-"] { 21 | /* use !important to prevent issues with browser extensions that change fonts */ 22 | font-family: 'icomoon' !important; 23 | speak: none; 24 | font-style: normal; 25 | font-weight: normal; 26 | font-variant: normal; 27 | text-transform: none; 28 | 29 | /* Better Font Rendering =========== */ 30 | -webkit-font-smoothing: antialiased; 31 | -moz-osx-font-smoothing: grayscale; 32 | } 33 | 34 | .gn-menu-main, 35 | .gn-menu-main ul { 36 | margin: 0; 37 | padding: 0; 38 | background: white; 39 | color: #5f6f81; 40 | list-style: none; 41 | text-transform: none; 42 | font-weight: 300; 43 | line-height: 60px; 44 | } 45 | 46 | .gn-menu-main ul { 47 | border-right: 1px solid #c6d0da; 48 | outline: 0; 49 | } 50 | 51 | .gn-menu-main { 52 | position: fixed; 53 | top: 0; 54 | left: 0; 55 | width: 100%; 56 | height: 60px; 57 | font-size: 13px; 58 | border-bottom: 1px solid #c6d0da; 59 | z-index: 1000; 60 | } 61 | 62 | .gn-menu-main a { 63 | display: block; 64 | height: 100%; 65 | color: #5f6f81; 66 | text-decoration: none; 67 | cursor: pointer; 68 | outline: 0; 69 | } 70 | 71 | .menu_glyphicon { 72 | font-weight: 300; 73 | word-spacing: -10px; 74 | line-height: 60px; 75 | } 76 | 77 | .no-touch .gn-menu-main a:hover, 78 | .no-touch .gn-menu li.gn-search-item:hover, 79 | .no-touch .gn-menu li.gn-search-item:hover a { 80 | background: #5f6f81; 81 | color: white; 82 | } 83 | 84 | .gn-menu-main > li { 85 | display: block; 86 | float: left; 87 | height: 100%; 88 | border-right: 1px solid #c6d0da; 89 | text-align: center; 90 | } 91 | 92 | /* icon-only trigger (menu item) */ 93 | 94 | .gn-menu-main li.gn-trigger { 95 | position: relative; 96 | width: 60px; 97 | -webkit-touch-callout: none; 98 | -webkit-user-select: none; 99 | -khtml-user-select: none; 100 | -moz-user-select: none; 101 | -ms-user-select: none; 102 | user-select: none; 103 | } 104 | 105 | /*.gn-menu-main > li:last-child { 106 | float: right; 107 | border-right: none; 108 | border-left: 1px solid #c6d0da; 109 | }*/ 110 | 111 | .gn-menu-main > li > a { 112 | padding: 0 30px; 113 | text-transform: uppercase; 114 | letter-spacing: 1px; 115 | font-weight: bold; 116 | } 117 | 118 | .gn-menu-main:after { 119 | display: table; 120 | clear: both; 121 | content: ""; 122 | } 123 | 124 | .gn-menu-wrapper { 125 | position: fixed; 126 | top: 60px; 127 | bottom: 0; 128 | left: 0; 129 | overflow: hidden; 130 | width: 60px; 131 | border-top: 1px solid #c6d0da; 132 | background: white; 133 | -webkit-transform: translateX(-60px); 134 | -moz-transform: translateX(-60px); 135 | transform: translateX(-60px); 136 | -webkit-transition: -webkit-transform 0.3s, width 0.3s; 137 | -moz-transition: -moz-transform 0.3s, width 0.3s; 138 | transition: transform 0.3s, width 0.3s; 139 | } 140 | 141 | .gn-scroller { 142 | position: absolute; 143 | overflow-y: scroll; 144 | width: 370px; 145 | height: 100%; 146 | } 147 | 148 | .gn-menu { 149 | border-bottom: 1px solid #c6d0da; 150 | text-align: left; 151 | font-size: 18px; 152 | } 153 | 154 | .gn-menu li:not(:first-child), 155 | .gn-menu li li { 156 | box-shadow: inset 0 1px #c6d0da 157 | } 158 | 159 | .gn-icon::before { 160 | display: inline-block; 161 | width: 60px; 162 | text-align: center; 163 | text-transform: none; 164 | font-weight: normal; 165 | font-style: normal; 166 | font-variant: normal; 167 | line-height: 1; 168 | speak: none; 169 | -webkit-font-smoothing: antialiased; 170 | } 171 | 172 | .gn-icon-home:before { 173 | content: "\e900"; 174 | } 175 | 176 | .gn-icon-delegate:before { 177 | content: "\e901"; 178 | } 179 | 180 | .gn-icon-vote:before { 181 | content: "\e903"; 182 | } 183 | 184 | .gn-icon-cog:before { 185 | content: "\e902"; 186 | } 187 | 188 | /* if an icon anchor has a span, hide the span */ 189 | 190 | .gn-icon span { 191 | width: 0; 192 | height: 0; 193 | display: block; 194 | overflow: hidden; 195 | } 196 | 197 | .gn-icon-menu::before { 198 | margin-left: -15px; 199 | vertical-align: -2px; 200 | width: 30px; 201 | height: 3px; 202 | background: #5f6f81; 203 | box-shadow: 0 3px white, 0 -6px #5f6f81, 0 -9px white, 0 -12px #5f6f81; 204 | content: ''; 205 | } 206 | 207 | .no-touch .gn-icon-menu:hover::before, 208 | .no-touch .gn-icon-menu.gn-selected:hover::before { 209 | background: white; 210 | box-shadow: 0 3px #5f6f81, 0 -6px white, 0 -9px #5f6f81, 0 -12px white; 211 | } 212 | 213 | .gn-icon-menu.gn-selected::before { 214 | background: #5993cd; 215 | box-shadow: 0 3px white, 0 -6px #5993cd, 0 -9px white, 0 -12px #5993cd; 216 | } 217 | 218 | /* styles for opening menu */ 219 | 220 | .gn-menu-wrapper.gn-open-all, 221 | .gn-menu-wrapper.gn-open-part { 222 | -webkit-transform: translateX(0px); 223 | -moz-transform: translateX(0px); 224 | transform: translateX(0px); 225 | } 226 | 227 | .gn-menu-wrapper.gn-open-all { 228 | width: 340px 229 | } 230 | 231 | .gn-menu-wrapper.gn-open-all .gn-submenu li { 232 | height: 60px 233 | } 234 | 235 | @media screen and (max-width: 422px) { 236 | .gn-menu-wrapper.gn-open-all { 237 | -webkit-transform: translateX(0px); 238 | -moz-transform: translateX(0px); 239 | transform: translateX(0px); 240 | width: 100%; 241 | } 242 | 243 | .gn-menu-wrapper.gn-open-all .gn-scroller { 244 | width: 130% 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /app/client/templates/dashboard/create/create.html: -------------------------------------------------------------------------------- 1 | 104 | 105 | 109 | 110 | 122 | 123 | 126 | -------------------------------------------------------------------------------- /app/client/libraries/dashboard/main/menu/modernizr.custom.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.6.2 (Custom Build) | MIT & BSD 2 | * Build: http://modernizr.com/download/#-touch-shiv-cssclasses-teststyles-prefixes-load 3 | */ 4 | ;window.Modernizr=function(a,b,c){function w(a){j.cssText=a}function x(a,b){return w(m.join(a+";")+(b||""))}function y(a,b){return typeof a===b}function z(a,b){return!!~(""+a).indexOf(b)}function A(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:y(f,"function")?f.bind(d||b):f}return!1}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n={},o={},p={},q=[],r=q.slice,s,t=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["­",'"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},u={}.hasOwnProperty,v;!y(u,"undefined")&&!y(u.call,"undefined")?v=function(a,b){return u.call(a,b)}:v=function(a,b){return b in a&&y(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=r.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(r.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(r.call(arguments)))};return e}),n.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:t(["@media (",m.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c};for(var B in n)v(n,B)&&(s=B.toLowerCase(),e[s]=n[B](),q.push((e[s]?"":"no-")+s));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)v(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},w(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=m,e.testStyles=t,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+q.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f style{ 252 | display:block; 253 | font-size: .75em; 254 | color: blue; 255 | font-family: courier, sans-serif; 256 | background: #fff; 257 | line-height: 1.5; 258 | position:relative; 259 | right: -36em; 260 | top:-2em; 261 | } 262 | 263 | .btn-success { 264 | color: #fff; 265 | background-color: #00af66; 266 | border-color: transparent; 267 | } 268 | 269 | .btn-success:hover, .btn-success:focus, .btn-success:active, .btn-success.active, .open .dropdown-toggle.btn-success { 270 | color: #fff; 271 | background-color: #009e5c; 272 | border-color: transparent; 273 | } 274 | 275 | @media (max-width: 480px) { 276 | .h1-header { 277 | position:static; 278 | font-weight: 100; 279 | margin-top: 0; 280 | font-size: 18px; 281 | background: transparent; 282 | padding: 25px; 283 | margin-left: -10px; 284 | /* width: 400px; */ 285 | border-bottom:1px solid white; 286 | } 287 | 288 | .progress {margin-bottom:0; height: auto;} 289 | .form-group label {font-size:16px;} 290 | .form-group label h1 {font-size:36px; font-weight:700} 291 | 292 | .form-group textarea { 293 | font-size: 18px; 294 | padding: 10px; 295 | min-height: 150px; 296 | } 297 | 298 | .form-group input, .form-group textarea { 299 | border: none; 300 | background: rgba(255,255,255, .2); 301 | padding: 35px 15px; 302 | box-shadow: none; 303 | border-radius: 0; 304 | font-size: 20px; 305 | color: #fff; 306 | outline: 0; 307 | width: 100%; 308 | } 309 | 310 | } 311 | 312 | 313 | @media only screen 314 | and (min-device-width : 320px) 315 | and (max-device-width : 736px) 316 | and (orientation : landscape) { 317 | /* CSS here */ 318 | 319 | .h1-header { 320 | position:static; 321 | font-weight: 100; 322 | margin-top: 0; 323 | font-size: 18px; 324 | background: transparent; 325 | padding: 0 0 10px 20px; 326 | margin-left: -10px; 327 | /* width: 400px; */ 328 | border-bottom:1px solid white; 329 | } 330 | 331 | 332 | .form-group label h1 {font-size:24px;} .form-group label {font-size:14px;} .form-group input, .form-group textarea {font-size:18px;padding: 15px 15px;} .questionaire {padding:20px 0; height:auto;} .count {font-size:20px;} .cover .btn-lg {padding:10px 20px; font-size:20px;} 333 | .form-group textarea {min-height:150px;} 334 | 335 | } 336 | -------------------------------------------------------------------------------- /app/client/stylesheets/homepage/home.css: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | font-family: 'Raleway', sans-serif; 4 | height: 100%; 5 | width: 100%; 6 | margin: 0; 7 | padding: 0; 8 | box-sizing: border-box; 9 | } 10 | 11 | button:focus { 12 | outline: 0 !important; 13 | } 14 | 15 | /** 16 | * Navbar Styles 17 | **/ 18 | 19 | .navbar { 20 | position: fixed; 21 | background-color: rgba(242,242,242, 0.80); 22 | width: 100%; 23 | height: 60px; 24 | left: 0; 25 | top: 0; 26 | box-shadow: 0px 1px 1px 0px rgba(100, 100, 100, 0.2); 27 | border: 1px solid transparent; 28 | z-index: 10; 29 | } 30 | 31 | .navbar__elements { 32 | position: relative; 33 | list-style: none; 34 | margin: 0; 35 | padding: 0; 36 | width: 100%; 37 | display: -webkit-box; 38 | display: -moz-box; 39 | display: -ms-flexbox; 40 | display: -webkit-flex; 41 | display: flex; 42 | align-items: center; 43 | -webkit-flex-flow: row wrap; 44 | justify-content: flex-end; 45 | } 46 | 47 | .navbar__elements li { 48 | text-decoration: none; 49 | display: block; 50 | margin: 10px 20px; 51 | float: left; 52 | font-size: 20px; 53 | } 54 | 55 | .navbar__button { 56 | height: 40px; 57 | width: 160px; 58 | font-size: 22px; 59 | background-color: rgb(221,92,81); 60 | } 61 | 62 | .navbar__button { 63 | white-space: nowrap; 64 | border-radius: 60px; 65 | border: none; 66 | cursor: pointer; 67 | transition: 0.1s all ease; 68 | color: #fff; 69 | box-shadow: 0px 0px 0px 0px #B8B8B8; 70 | border-bottom: 1px solid #1A4164; 71 | outline: 0; 72 | } 73 | 74 | .navbar__button:hover { 75 | background-color: #c65248; 76 | top: -2px; 77 | border-bottom: 2px solid #9a4038; 78 | box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.25); 79 | } 80 | 81 | .navbar__button:active { 82 | top: 0px; 83 | border: 1px solid #9a4038; 84 | background-color: #c65248; 85 | box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.25); 86 | } 87 | 88 | .navbar__button::-moz-focus-inner { 89 | border: 0; 90 | } 91 | 92 | .navbar__button a { 93 | outline: 0; 94 | text-decoration: none; 95 | color: inherit; 96 | } 97 | 98 | 99 | /** 100 | ** Landing Page Styles 101 | **/ 102 | 103 | .landing { 104 | width: 100%; 105 | height: 100%; 106 | } 107 | 108 | .landing__background { 109 | background-color: #fff; 110 | /*background-image: url('graph2.jpg');*/ 111 | background-size: cover; 112 | background-repeat: no-repeat; 113 | background-position: center; 114 | z-index: -5; 115 | } 116 | 117 | .landing__content { 118 | position: absolute; 119 | color: #000; 120 | text-align: center; 121 | margin: 22% auto; 122 | left: 0; 123 | right: 0; 124 | width: 60%; 125 | } 126 | 127 | .landing__title { 128 | font-size: 54px; 129 | font-weight: 500; 130 | } 131 | 132 | .landing__btndiv { 133 | margin: 50px 0; 134 | text-align: center; 135 | } 136 | 137 | .landing__button { 138 | width: 280px; 139 | height: 60px; 140 | font-size: 28px; 141 | background-color: #32699b; 142 | } 143 | 144 | .buttons { 145 | white-space: nowrap; 146 | border-radius: 60px; 147 | border: none; 148 | cursor: pointer; 149 | transition: 0.1s all ease; 150 | color: #fff; 151 | box-shadow: 0px 0px 0px 0px #B8B8B8; 152 | border-bottom: 1px solid #1A4164; 153 | outline: 0; 154 | } 155 | 156 | .buttons:hover { 157 | background-color: #2d5e8b; 158 | top: -2px; 159 | border-bottom: 2px solid #1A4164; 160 | box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.25); 161 | } 162 | 163 | .buttons:active { 164 | top: 0px; 165 | border: 1px solid #1A4164; 166 | background-color: #2d5e8b; 167 | box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.25); 168 | } 169 | 170 | .buttons::-moz-focus-inner { 171 | border: 0; 172 | } 173 | 174 | .buttons a { 175 | outline: 0; 176 | text-decoration: none; 177 | color: inherit; 178 | } 179 | 180 | /** 181 | * Introduction Section 182 | **/ 183 | 184 | .intro { 185 | width: 100%; 186 | min-height: 520px; 187 | height: 40%; 188 | color: #fff; 189 | display: flex; 190 | justify-content: center; 191 | } 192 | 193 | .intro div { 194 | align-self: center; 195 | height: 100%; 196 | } 197 | 198 | .intro__title { 199 | font-size: 48px; 200 | font-weight: 300; 201 | text-align: center; 202 | width: auto; 203 | margin: 40px 0; 204 | z-index: 1000; 205 | } 206 | 207 | .intro__explanation { 208 | background-color: rgb(47,164,178); 209 | width: 65%; 210 | } 211 | 212 | .intro__summary { 213 | font-size: 22px; 214 | padding: 20px 80px; 215 | text-align: center; 216 | } 217 | 218 | .intro__summary p { 219 | text-align: justify; 220 | } 221 | 222 | .intro__summary_button { 223 | width: 180px; 224 | height: 40px; 225 | font-size: 22px; 226 | background-color: rgb(252,104,85); 227 | white-space: nowrap; 228 | border-radius: 60px; 229 | border: none; 230 | cursor: pointer; 231 | transition: 0.1s all ease; 232 | color: #fff; 233 | box-shadow: 0px 0px 0px 0px #B8B8B8; 234 | border-bottom: 1px solid #c95344; 235 | outline: 0; 236 | margin: 20px 0; 237 | } 238 | 239 | .intro__summary_button:hover { 240 | background-color: #fc6855; 241 | top: -2px; 242 | border-bottom: 2px solid #c95344; 243 | box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.25); 244 | } 245 | 246 | .intro__summary_button:active { 247 | top: 0px; 248 | border: 1px solid #c95344; 249 | background-color: #fc6855; 250 | box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.25); 251 | } 252 | 253 | .intro__summary_button::-moz-focus-inner { 254 | border: 0; 255 | } 256 | 257 | .intro__summary_button a { 258 | outline: 0; 259 | text-decoration: none; 260 | color: inherit; 261 | } 262 | 263 | .intro__summary a { 264 | color: 265 | } 266 | 267 | .intro__list { 268 | background-color: rgb(252,104,85); 269 | width: 35%; 270 | } 271 | 272 | .intro__bulletpoints { 273 | color: #fff; 274 | font-size: 22px; 275 | } 276 | 277 | .intro__bulletpoints li { 278 | list-style-type: none; 279 | margin-top: 15px; 280 | } 281 | 282 | /** 283 | * Participate Now Section 284 | **/ 285 | 286 | .participate { 287 | height: 100%; 288 | min-height: 1050px; 289 | background-color: rgb(240,246,251); 290 | overflow: hidden; 291 | text-align: center; 292 | } 293 | 294 | .participate__title { 295 | padding-top: 50px; 296 | font-size: 55px; 297 | font-weight: 600; 298 | text-align: center; 299 | color: rgb(77,157,224); 300 | } 301 | 302 | .participate__more { 303 | max-width: 1080px; 304 | margin: 80px auto 0; 305 | } 306 | 307 | .participate__more_icon { 308 | display: inline-block; 309 | margin: -40px auto 0; 310 | background-size: 60%; 311 | background-color: #fff; 312 | background-position: center; 313 | background-repeat: no-repeat; 314 | border-width: 1px; 315 | border-style: solid; 316 | width: 90px; 317 | height: 90px; 318 | border-radius: 100%; 319 | } 320 | 321 | .participate__discuss_icon, 322 | .participate__vote_icon, 323 | .participate__delegate_icon, 324 | .participate__validate_icon { 325 | vertical-align: center; 326 | text-align: center; 327 | margin-top: 22px; 328 | margin-left: 3px; 329 | font-size: 40px; 330 | } 331 | 332 | .participate__more_tile { 333 | background-color: #fff; 334 | width: 380px; 335 | height: 300px; 336 | margin: 50px; 337 | border: 1px solid #ccc; 338 | border-radius: 10px; 339 | } 340 | 341 | .participate__more_title { 342 | font-size: 32px; 343 | margin: 25px 0; 344 | } 345 | 346 | .participate__more_text { 347 | font-size: 22px; 348 | margin: 30px 0; 349 | } 350 | 351 | /** 352 | * Join Section 353 | **/ 354 | 355 | .join { 356 | width: 100%; 357 | height: 300px; 358 | overflow: hidden; 359 | } 360 | 361 | .join__overlay { 362 | width: 100%; 363 | height: inherit; 364 | background-color: #003C5F; 365 | position: absolute; 366 | opacity: 0.9; 367 | } 368 | 369 | .join__text { 370 | position: relative; 371 | width: 100%; 372 | color: #fff; 373 | z-index: 100; 374 | text-align: center; 375 | margin-top: 80px; 376 | } 377 | 378 | .join__text_title { 379 | font-size: 54px; 380 | font-weight: 300; 381 | } 382 | 383 | .join__text_button { 384 | white-space: nowrap; 385 | border-radius: 10px; 386 | height: 60px; 387 | width: 300px; 388 | margin-bottom: 10px; 389 | font-size: 24px; 390 | border: none; 391 | cursor: pointer; 392 | transition: 0.1s all ease; 393 | color: #fff; 394 | box-shadow: 0px 0px 0px 0px #B8B8B8; 395 | background-color: #4DA335; 396 | border-bottom: 1px solid #5eac49; 397 | } 398 | 399 | .join__text_button:hover { 400 | background-color: #45922f; 401 | top: -2px; 402 | outline:0; 403 | border-bottom: 2px solid #45922f; 404 | box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.25); 405 | } 406 | 407 | .join__text_button:active { 408 | top: 0px; 409 | outline:0; 410 | border: 1px solid #5eac49; 411 | background-color: #3d822a; 412 | box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.25); 413 | } 414 | 415 | .join__text_button::-moz-focus-inner { 416 | border: 0; 417 | } 418 | 419 | .join__text_button a { 420 | outline: 0; 421 | text-decoration: none; 422 | color: inherit; 423 | } 424 | 425 | .join__text_subtitle { 426 | font-size: 28px; 427 | } 428 | -------------------------------------------------------------------------------- /app/server/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main Vote Counting Function 3 | **/ 4 | 5 | function voteCounting(poll_id, domain, options) { 6 | var votes = Uservotes.findOne({_id: poll_id}); 7 | 8 | if (votes === undefined) { 9 | poll.update({_id: poll_id}, {$set: {'outcome': [], 'count': 0}}); 10 | return true; 11 | } 12 | 13 | var voters = []; 14 | var delegates = []; 15 | 16 | for (var i = 0; i < votes.vote.length; i++) { 17 | if (votes.vote[i].delegate) { 18 | delegates.push(votes.vote[i]); 19 | } else { 20 | voters.push(votes.vote[i]); 21 | } 22 | } 23 | 24 | //For Each delegate, we get all connected delegations and input into new JSON object 25 | var output = voters.slice(); 26 | 27 | delegates.forEach(function(delegateVote) { 28 | var currDelegate = Delegates.findOne({_id: delegateVote.voter}); 29 | if (currDelegate.delegations) { 30 | output.push(recursiveCount(delegates, voters, currDelegate, delegateVote, domain)); 31 | } 32 | else { 33 | output.push(delegateVote); 34 | } 35 | }) 36 | 37 | //Then we count all of the voters in the new JSON object 38 | var output = finalCount(output, options); 39 | var count = 0; 40 | // output.forEach(function(voter) { 41 | // count += finalCount(voter); 42 | // }) 43 | 44 | poll.update({_id: poll_id}, {$set: {'outcome': output, 'count': count}}); 45 | } 46 | 47 | /** 48 | * Recursive Count Helper Functions 49 | **/ 50 | 51 | function voterVoted(votestructure, voter) { 52 | var found = false; 53 | 54 | votestructure.forEach(function(curr_voter) { 55 | if (curr_voter.voter === voter) { 56 | found = true; 57 | } 58 | }); 59 | 60 | return found; 61 | } 62 | 63 | function delegateVoted(votestructure, voter) { 64 | var found = false; 65 | 66 | votestructure.forEach(function(curr_voter) { 67 | if (curr_voter.voter === voter) { 68 | found = true; 69 | } 70 | }); 71 | 72 | return found; 73 | } 74 | 75 | function hasDomain(domains, voter) { 76 | var found = false; 77 | 78 | domains.forEach(function(domain) { 79 | if (voter.domain === domain) { 80 | found = true; 81 | } 82 | }) 83 | 84 | return found; 85 | } 86 | 87 | function finalCount(allVoters, options) { 88 | var result = {}; 89 | result.name = "outcome"; 90 | result.results = []; 91 | 92 | // for each option, we add a new entry into the result object 93 | options.forEach(function(option) { 94 | var newOption = {}; 95 | newOption.name = option; 96 | newOption.count = 0; 97 | newOption.results = []; 98 | result.results.push(newOption); 99 | }) 100 | 101 | // sort voters to options and count the total 102 | allVoters.forEach(function(voter) { 103 | for (var i = 0; i < options.length; i++) { 104 | if (options[i] === voter.option) { 105 | var index = options.indexOf(options[i]); 106 | result.results[index].results.push(voter); 107 | result.results[index].count += delegateCount(voter); 108 | } 109 | } 110 | }) 111 | 112 | return result; 113 | } 114 | 115 | function delegateCount(voter) { 116 | var count = 1; 117 | if (voter.delegates) { 118 | voter.delegates.forEach(function(delegVoter) { 119 | if (delegVoter.delegates) { 120 | count += finalCount(delegVoter); 121 | } 122 | else { 123 | count += 1; 124 | } 125 | }); 126 | } 127 | 128 | return count; 129 | } 130 | 131 | 132 | /** 133 | * Recursive Count Function 134 | **/ 135 | 136 | function recursiveCount(delegateVoters, votersVoters, delegate, curVoter, domain) { 137 | 138 | var voterObj = { 139 | 'voter': delegate._id, 140 | 'option': curVoter.option, 141 | 'delegates': [] 142 | } 143 | delegate.delegations.forEach(function(delegVoter) { 144 | // If not domain-delegation, don't count Voter 145 | if (hasDomain(domain, delegVoter) === false) { 146 | return false; 147 | } 148 | 149 | // Retrieve current voter object 150 | var voter = Meteor.users.findOne({_id: delegVoter.voter});; 151 | 152 | // 153 | // If the Voter is a Delegate, retrieve his information and get all delegations 154 | // 155 | if (voter.delegate && 156 | delegateVoted(delegateVoters, voter._id) !== true) { 157 | // Retrieve new Delegate object 158 | var newDelegate = Delegates.findOne({_id: voter._id}); 159 | 160 | // Go through delegations and return them to the current voterObj 161 | voterObj.delegates.push(recursiveCount(delegateVoters, votersVoters, newDelegate, curVoter, domain)); 162 | } 163 | // 164 | // If voter is not delegate himself, we create a new object and append to delegate voterObj 165 | // 166 | else if (voter.delegate !== true && 167 | voterVoted(votersVoters, voter._id) !== true) { 168 | var newVoter = {}; 169 | newVoter['voter'] = voter._id; 170 | newVoter['option'] = curVoter.option; 171 | voterObj.delegates.push(newVoter); 172 | } 173 | }) 174 | 175 | return voterObj; 176 | } 177 | 178 | 179 | 180 | Meteor.startup(function() { 181 | //process.env.HTTP_FORWARDED_COUNT = 1; 182 | 183 | function polldeadline(poll_input, _current_date) { 184 | var current_poll = poll_input; 185 | var current_date = _current_date; 186 | 187 | var timer = Meteor.setTimeout(function() { 188 | console.log("ID:" + current_poll._id + " Name: " + current_poll.poll.name + " went offline!"); 189 | poll.update({_id:current_poll._id}, {$set: {'poll.isvoted': true, 'poll.isactive':false}}); 190 | voteCounting(current_poll._id, current_poll.poll.domain, current_poll.poll.options); 191 | }, (current_poll.endDate - current_date)); 192 | 193 | console.log("New timer set for Poll: " + current_poll._id); 194 | } 195 | 196 | // 197 | // set the deadline for each poll on server startup 198 | // 199 | var active_polls = poll.find({'poll.isvoted': false}).fetch(); 200 | console.log(active_polls); 201 | for (var i = 0; i < active_polls.length; i++) { 202 | var current_poll = active_polls[i]; 203 | var current_date = Date.now(); 204 | if ((current_poll.endDate - current_date) <= 0) { 205 | console.log("ID:" + current_poll._id + " Name: " + current_poll.poll.name + " went offline!"); 206 | poll.update({_id:current_poll._id}, {$set: {'poll.isvoted': true, 'poll.isactive':false}}); 207 | } 208 | else { 209 | polldeadline(current_poll,current_date); 210 | } 211 | } 212 | }) 213 | 214 | 215 | Meteor.methods({ 216 | new_poll: function(poll_data, deadline, creator) { 217 | var start_date = Date.now(); 218 | 219 | return poll.insert({poll: poll_data, endDate: deadline, createdAt: start_date}, function(error, success) { 220 | if (!error) { 221 | // Save the poll as a reference in delegate profile 222 | Delegates.update({_id: creator}, {$push: {'polls': {'poll': success, 'title': poll_data.name, 'domain': poll_data.domain}}}) 223 | 224 | // Set the deadline for the poll 225 | Meteor.setTimeout(function() { 226 | console.log("ID: " + success + " went offline!"); 227 | poll.update({_id: success}, {$set: {'poll.isvoted': true, 'poll.isactive':false}}); 228 | voteCounting(success, poll_data.domain, poll_data.options); 229 | },(deadline - start_date)); 230 | return success; 231 | } 232 | return error; 233 | }); 234 | }, 235 | new_vote: function(option, userID, isDelegate, pollID) { 236 | return Uservotes.update( 237 | {_id: pollID}, 238 | {$push: {vote: {voter: userID, delegate: isDelegate, option: option, votedAt: Date.now()}}}, 239 | {upsert: true}, 240 | function(error, success) 241 | { 242 | if (!error) { 243 | //TODO: temporary, for increased fairness only publish poll results after end 244 | return poll.update({_id: pollID}, {$push: {votes: option}}, function(result) { 245 | return result; 246 | }); 247 | } 248 | } 249 | ); 250 | }, 251 | new_delegate: function(data) { 252 | Meteor.users.update({_id: data.userID}, {$set: {'profile.name': data.name, 'delegate': true, 'description': data.description, 'profile_pic': data.profile_pic, 'expertise': data.domain}}); 253 | 254 | return Delegates.insert({_id: data.userID, 'delegate': data}, function(error, success) { 255 | return success; 256 | }); 257 | }, 258 | get_delegates: function() { 259 | return Delegates.find({}, {delegate: 1}).fetch(); 260 | }, 261 | delegation: function(domain, user, delegate) { 262 | // TODO: Find way to use domain as key in document 263 | if (delegate === user) { 264 | return false; 265 | } 266 | 267 | // Delegation Check: Only one Delegate per Domain possible 268 | if (user.delegates) { 269 | user.delegates.forEach(function(existingDelegate) { 270 | for (var i = 0; i < domain.length; i++) { 271 | if (existingDelegate.domain === domain[i]) { 272 | throw new Meteor.Error('existingDelegation', ' You already have a delegation for this Domain. Change your existing delegation relationships or modify the new delegation you intend to make. '); 273 | } 274 | } 275 | }) 276 | } 277 | 278 | // 279 | // Function used for Circular Delegation Check 280 | // Checks if inbound delegation, matches outbound delegation 281 | // 282 | function traverseCheck(delegate, userDelegate, domain) { 283 | // Circular delegation only possible if delegate has outbound delegations 284 | var delegateProfile = Meteor.users.findOne({_id: delegate}); 285 | if (delegateProfile.delegates) { 286 | userDelegate.delegations.forEach(function(userDelegations) { 287 | if (userDelegations.voter === delegate && 288 | userDelegations.domain === domain) { 289 | throw new Meteor.Error('circularDelegation', ' Your Delegation would create a circular Delegation and is therefore currently not possible. Please check your delegation relationships and retry again.') 290 | } 291 | else if (userDelegations.domain === domain){ 292 | traverseCheck(delegation.delegate, userDelegate, domain) 293 | } 294 | }) 295 | } 296 | } 297 | 298 | 299 | // If the current user is a delegate herself, check for circular delegation 300 | if (user.delegate) { 301 | var userDelegate = Delegates.findOne({_id: user._id}); 302 | 303 | // Circular delegation only possible if user already has existing inbound delegations 304 | if (userDelegate.delegations) { 305 | // Check if inbound delegation matches outbound delegation 306 | domain.forEach(function(delegDomain) { 307 | traverseCheck(delegate, userDelegate, delegDomain); 308 | }) 309 | } 310 | } 311 | 312 | // For every domain, we create a extra delegation entry in the User and Delegations collection 313 | for (var i = 0; i < domain.length; i++) { 314 | Meteor.users.update({_id: user._id}, {$push: {'delegates': {'delegate': delegate, 'domain': domain[i]}}}); 315 | Delegates.update({_id: delegate}, {$push: {'delegations': {'domain': domain[i], 'voter': user._id}}}) 316 | } 317 | 318 | return true; 319 | }, 320 | revoke_delegate: function(delegateobj, user) { 321 | // 322 | // Removes a single Delegation Relationship 323 | // 324 | Meteor.users.update({_id: user}, {$pull: {'delegates': {'delegate': delegateobj._id}}}); 325 | Delegates.update({_id: delegateobj._id}, {$pull: {'delegations': {'voter': user}}}); 326 | }, 327 | quit_delegate: function(delegateID) { 328 | // 329 | // Removes a Delegate as well as all delegations 330 | // 331 | var delegate = Delegates.findOne({_id: delegateID}); 332 | 333 | if (delegate.delegations) { 334 | delegate.delegations.forEach(function(delegation) { 335 | Meteor.users.update({_id: delegation.voter}, {$pull: {'delegates': {'delegate': delegateID}}}); 336 | }) 337 | } 338 | 339 | Meteor.users.update({_id: delegateID}, {$set: {'delegate': false, 'expertse': false}}); 340 | Delegates.remove({_id: delegateID}); 341 | } 342 | }) 343 | -------------------------------------------------------------------------------- /tests/treeVisualization/flare.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flare", 3 | "children": [ 4 | { 5 | "name": "analytics", 6 | "children": [ 7 | { 8 | "name": "cluster", 9 | "children": [ 10 | {"name": "AgglomerativeCluster", "size": 3938}, 11 | {"name": "CommunityStructure", "size": 3812}, 12 | {"name": "HierarchicalCluster", "size": 6714}, 13 | {"name": "MergeEdge", "size": 743} 14 | ] 15 | }, 16 | { 17 | "name": "graph", 18 | "children": [ 19 | {"name": "BetweennessCentrality", "size": 3534}, 20 | {"name": "LinkDistance", "size": 5731}, 21 | {"name": "MaxFlowMinCut", "size": 7840}, 22 | {"name": "ShortestPaths", "size": 5914}, 23 | {"name": "SpanningTree", "size": 3416} 24 | ] 25 | }, 26 | { 27 | "name": "optimization", 28 | "children": [ 29 | {"name": "AspectRatioBanker", "size": 7074} 30 | ] 31 | } 32 | ] 33 | }, 34 | { 35 | "name": "animate", 36 | "children": [ 37 | {"name": "Easing", "size": 17010}, 38 | {"name": "FunctionSequence", "size": 5842}, 39 | { 40 | "name": "interpolate", 41 | "children": [ 42 | {"name": "ArrayInterpolator", "size": 1983}, 43 | {"name": "ColorInterpolator", "size": 2047}, 44 | {"name": "DateInterpolator", "size": 1375}, 45 | {"name": "Interpolator", "size": 8746}, 46 | {"name": "MatrixInterpolator", "size": 2202}, 47 | {"name": "NumberInterpolator", "size": 1382}, 48 | {"name": "ObjectInterpolator", "size": 1629}, 49 | {"name": "PointInterpolator", "size": 1675}, 50 | {"name": "RectangleInterpolator", "size": 2042} 51 | ] 52 | }, 53 | {"name": "ISchedulable", "size": 1041}, 54 | {"name": "Parallel", "size": 5176}, 55 | {"name": "Pause", "size": 449}, 56 | {"name": "Scheduler", "size": 5593}, 57 | {"name": "Sequence", "size": 5534}, 58 | {"name": "Transition", "size": 9201}, 59 | {"name": "Transitioner", "size": 19975}, 60 | {"name": "TransitionEvent", "size": 1116}, 61 | {"name": "Tween", "size": 6006} 62 | ] 63 | }, 64 | { 65 | "name": "data", 66 | "children": [ 67 | { 68 | "name": "converters", 69 | "children": [ 70 | {"name": "Converters", "size": 721}, 71 | {"name": "DelimitedTextConverter", "size": 4294}, 72 | {"name": "GraphMLConverter", "size": 9800}, 73 | {"name": "IDataConverter", "size": 1314}, 74 | {"name": "JSONConverter", "size": 2220} 75 | ] 76 | }, 77 | {"name": "DataField", "size": 1759}, 78 | {"name": "DataSchema", "size": 2165}, 79 | {"name": "DataSet", "size": 586}, 80 | {"name": "DataSource", "size": 3331}, 81 | {"name": "DataTable", "size": 772}, 82 | {"name": "DataUtil", "size": 3322} 83 | ] 84 | }, 85 | { 86 | "name": "display", 87 | "children": [ 88 | {"name": "DirtySprite", "size": 8833}, 89 | {"name": "LineSprite", "size": 1732}, 90 | {"name": "RectSprite", "size": 3623}, 91 | {"name": "TextSprite", "size": 10066} 92 | ] 93 | }, 94 | { 95 | "name": "flex", 96 | "children": [ 97 | {"name": "FlareVis", "size": 4116} 98 | ] 99 | }, 100 | { 101 | "name": "physics", 102 | "children": [ 103 | {"name": "DragForce", "size": 1082}, 104 | {"name": "GravityForce", "size": 1336}, 105 | {"name": "IForce", "size": 319}, 106 | {"name": "NBodyForce", "size": 10498}, 107 | {"name": "Particle", "size": 2822}, 108 | {"name": "Simulation", "size": 9983}, 109 | {"name": "Spring", "size": 2213}, 110 | {"name": "SpringForce", "size": 1681} 111 | ] 112 | }, 113 | { 114 | "name": "query", 115 | "children": [ 116 | {"name": "AggregateExpression", "size": 1616}, 117 | {"name": "And", "size": 1027}, 118 | {"name": "Arithmetic", "size": 3891}, 119 | {"name": "Average", "size": 891}, 120 | {"name": "BinaryExpression", "size": 2893}, 121 | {"name": "Comparison", "size": 5103}, 122 | {"name": "CompositeExpression", "size": 3677}, 123 | {"name": "Count", "size": 781}, 124 | {"name": "DateUtil", "size": 4141}, 125 | {"name": "Distinct", "size": 933}, 126 | {"name": "Expression", "size": 5130}, 127 | {"name": "ExpressionIterator", "size": 3617}, 128 | {"name": "Fn", "size": 3240}, 129 | {"name": "If", "size": 2732}, 130 | {"name": "IsA", "size": 2039}, 131 | {"name": "Literal", "size": 1214}, 132 | {"name": "Match", "size": 3748}, 133 | {"name": "Maximum", "size": 843}, 134 | { 135 | "name": "methods", 136 | "children": [ 137 | {"name": "add", "size": 593}, 138 | {"name": "and", "size": 330}, 139 | {"name": "average", "size": 287}, 140 | {"name": "count", "size": 277}, 141 | {"name": "distinct", "size": 292}, 142 | {"name": "div", "size": 595}, 143 | {"name": "eq", "size": 594}, 144 | {"name": "fn", "size": 460}, 145 | {"name": "gt", "size": 603}, 146 | {"name": "gte", "size": 625}, 147 | {"name": "iff", "size": 748}, 148 | {"name": "isa", "size": 461}, 149 | {"name": "lt", "size": 597}, 150 | {"name": "lte", "size": 619}, 151 | {"name": "max", "size": 283}, 152 | {"name": "min", "size": 283}, 153 | {"name": "mod", "size": 591}, 154 | {"name": "mul", "size": 603}, 155 | {"name": "neq", "size": 599}, 156 | {"name": "not", "size": 386}, 157 | {"name": "or", "size": 323}, 158 | {"name": "orderby", "size": 307}, 159 | {"name": "range", "size": 772}, 160 | {"name": "select", "size": 296}, 161 | {"name": "stddev", "size": 363}, 162 | {"name": "sub", "size": 600}, 163 | {"name": "sum", "size": 280}, 164 | {"name": "update", "size": 307}, 165 | {"name": "variance", "size": 335}, 166 | {"name": "where", "size": 299}, 167 | {"name": "xor", "size": 354}, 168 | {"name": "_", "size": 264} 169 | ] 170 | }, 171 | {"name": "Minimum", "size": 843}, 172 | {"name": "Not", "size": 1554}, 173 | {"name": "Or", "size": 970}, 174 | {"name": "Query", "size": 13896}, 175 | {"name": "Range", "size": 1594}, 176 | {"name": "StringUtil", "size": 4130}, 177 | {"name": "Sum", "size": 791}, 178 | {"name": "Variable", "size": 1124}, 179 | {"name": "Variance", "size": 1876}, 180 | {"name": "Xor", "size": 1101} 181 | ] 182 | }, 183 | { 184 | "name": "scale", 185 | "children": [ 186 | {"name": "IScaleMap", "size": 2105}, 187 | {"name": "LinearScale", "size": 1316}, 188 | {"name": "LogScale", "size": 3151}, 189 | {"name": "OrdinalScale", "size": 3770}, 190 | {"name": "QuantileScale", "size": 2435}, 191 | {"name": "QuantitativeScale", "size": 4839}, 192 | {"name": "RootScale", "size": 1756}, 193 | {"name": "Scale", "size": 4268}, 194 | {"name": "ScaleType", "size": 1821}, 195 | {"name": "TimeScale", "size": 5833} 196 | ] 197 | }, 198 | { 199 | "name": "util", 200 | "children": [ 201 | {"name": "Arrays", "size": 8258}, 202 | {"name": "Colors", "size": 10001}, 203 | {"name": "Dates", "size": 8217}, 204 | {"name": "Displays", "size": 12555}, 205 | {"name": "Filter", "size": 2324}, 206 | {"name": "Geometry", "size": 10993}, 207 | { 208 | "name": "heap", 209 | "children": [ 210 | {"name": "FibonacciHeap", "size": 9354}, 211 | {"name": "HeapNode", "size": 1233} 212 | ] 213 | }, 214 | {"name": "IEvaluable", "size": 335}, 215 | {"name": "IPredicate", "size": 383}, 216 | {"name": "IValueProxy", "size": 874}, 217 | { 218 | "name": "math", 219 | "children": [ 220 | {"name": "DenseMatrix", "size": 3165}, 221 | {"name": "IMatrix", "size": 2815}, 222 | {"name": "SparseMatrix", "size": 3366} 223 | ] 224 | }, 225 | {"name": "Maths", "size": 17705}, 226 | {"name": "Orientation", "size": 1486}, 227 | { 228 | "name": "palette", 229 | "children": [ 230 | {"name": "ColorPalette", "size": 6367}, 231 | {"name": "Palette", "size": 1229}, 232 | {"name": "ShapePalette", "size": 2059}, 233 | {"name": "SizePalette", "size": 2291} 234 | ] 235 | }, 236 | {"name": "Property", "size": 5559}, 237 | {"name": "Shapes", "size": 19118}, 238 | {"name": "Sort", "size": 6887}, 239 | {"name": "Stats", "size": 6557}, 240 | {"name": "Strings", "size": 22026} 241 | ] 242 | }, 243 | { 244 | "name": "vis", 245 | "children": [ 246 | { 247 | "name": "axis", 248 | "children": [ 249 | {"name": "Axes", "size": 1302}, 250 | {"name": "Axis", "size": 24593}, 251 | {"name": "AxisGridLine", "size": 652}, 252 | {"name": "AxisLabel", "size": 636}, 253 | {"name": "CartesianAxes", "size": 6703} 254 | ] 255 | }, 256 | { 257 | "name": "controls", 258 | "children": [ 259 | {"name": "AnchorControl", "size": 2138}, 260 | {"name": "ClickControl", "size": 3824}, 261 | {"name": "Control", "size": 1353}, 262 | {"name": "ControlList", "size": 4665}, 263 | {"name": "DragControl", "size": 2649}, 264 | {"name": "ExpandControl", "size": 2832}, 265 | {"name": "HoverControl", "size": 4896}, 266 | {"name": "IControl", "size": 763}, 267 | {"name": "PanZoomControl", "size": 5222}, 268 | {"name": "SelectionControl", "size": 7862}, 269 | {"name": "TooltipControl", "size": 8435} 270 | ] 271 | }, 272 | { 273 | "name": "data", 274 | "children": [ 275 | {"name": "Data", "size": 20544}, 276 | {"name": "DataList", "size": 19788}, 277 | {"name": "DataSprite", "size": 10349}, 278 | {"name": "EdgeSprite", "size": 3301}, 279 | {"name": "NodeSprite", "size": 19382}, 280 | { 281 | "name": "render", 282 | "children": [ 283 | {"name": "ArrowType", "size": 698}, 284 | {"name": "EdgeRenderer", "size": 5569}, 285 | {"name": "IRenderer", "size": 353}, 286 | {"name": "ShapeRenderer", "size": 2247} 287 | ] 288 | }, 289 | {"name": "ScaleBinding", "size": 11275}, 290 | {"name": "Tree", "size": 7147}, 291 | {"name": "TreeBuilder", "size": 9930} 292 | ] 293 | }, 294 | { 295 | "name": "events", 296 | "children": [ 297 | {"name": "DataEvent", "size": 2313}, 298 | {"name": "SelectionEvent", "size": 1880}, 299 | {"name": "TooltipEvent", "size": 1701}, 300 | {"name": "VisualizationEvent", "size": 1117} 301 | ] 302 | }, 303 | { 304 | "name": "legend", 305 | "children": [ 306 | {"name": "Legend", "size": 20859}, 307 | {"name": "LegendItem", "size": 4614}, 308 | {"name": "LegendRange", "size": 10530} 309 | ] 310 | }, 311 | { 312 | "name": "operator", 313 | "children": [ 314 | { 315 | "name": "distortion", 316 | "children": [ 317 | {"name": "BifocalDistortion", "size": 4461}, 318 | {"name": "Distortion", "size": 6314}, 319 | {"name": "FisheyeDistortion", "size": 3444} 320 | ] 321 | }, 322 | { 323 | "name": "encoder", 324 | "children": [ 325 | {"name": "ColorEncoder", "size": 3179}, 326 | {"name": "Encoder", "size": 4060}, 327 | {"name": "PropertyEncoder", "size": 4138}, 328 | {"name": "ShapeEncoder", "size": 1690}, 329 | {"name": "SizeEncoder", "size": 1830} 330 | ] 331 | }, 332 | { 333 | "name": "filter", 334 | "children": [ 335 | {"name": "FisheyeTreeFilter", "size": 5219}, 336 | {"name": "GraphDistanceFilter", "size": 3165}, 337 | {"name": "VisibilityFilter", "size": 3509} 338 | ] 339 | }, 340 | {"name": "IOperator", "size": 1286}, 341 | { 342 | "name": "label", 343 | "children": [ 344 | {"name": "Labeler", "size": 9956}, 345 | {"name": "RadialLabeler", "size": 3899}, 346 | {"name": "StackedAreaLabeler", "size": 3202} 347 | ] 348 | }, 349 | { 350 | "name": "layout", 351 | "children": [ 352 | {"name": "AxisLayout", "size": 6725}, 353 | {"name": "BundledEdgeRouter", "size": 3727}, 354 | {"name": "CircleLayout", "size": 9317}, 355 | {"name": "CirclePackingLayout", "size": 12003}, 356 | {"name": "DendrogramLayout", "size": 4853}, 357 | {"name": "ForceDirectedLayout", "size": 8411}, 358 | {"name": "IcicleTreeLayout", "size": 4864}, 359 | {"name": "IndentedTreeLayout", "size": 3174}, 360 | {"name": "Layout", "size": 7881}, 361 | {"name": "NodeLinkTreeLayout", "size": 12870}, 362 | {"name": "PieLayout", "size": 2728}, 363 | {"name": "RadialTreeLayout", "size": 12348}, 364 | {"name": "RandomLayout", "size": 870}, 365 | {"name": "StackedAreaLayout", "size": 9121}, 366 | {"name": "TreeMapLayout", "size": 9191} 367 | ] 368 | }, 369 | {"name": "Operator", "size": 2490}, 370 | {"name": "OperatorList", "size": 5248}, 371 | {"name": "OperatorSequence", "size": 4190}, 372 | {"name": "OperatorSwitch", "size": 2581}, 373 | {"name": "SortOperator", "size": 2023} 374 | ] 375 | }, 376 | {"name": "Visualization", "size": 16540} 377 | ] 378 | } 379 | ] 380 | } 381 | --------------------------------------------------------------------------------