diff --git a/applications/voting/vote.js b/applications/voting/vote.js index 24d7941..ad0ec62 100644 --- a/applications/voting/vote.js +++ b/applications/voting/vote.js @@ -17,6 +17,7 @@ // TODO: Voting results page // TODO: Joining poll sometimes causes to double stack on other clients poll_list? +// TODO: Do active polls persist across domain leave? If so close them on session leave (() => { "use strict"; @@ -25,7 +26,7 @@ let active = false; const debug = false; - let poll = {id: '', title: '', description: '', host: '', question: '', options: []}; // The current poll + let poll = {id: '', title: '', description: '', host: '', question: '', options: [], host_can_vote: false}; // The current poll let responses = {}; // All ballots received and to be used by the election function. let electionIterations = 0; // How many times the election function has been called to narrow down a candidate. @@ -117,13 +118,6 @@ // Update the UI screen _emitEvent({type: "create_poll"}); - - // Debug: Create a lot of fake ballots - if (!debug) return; - - for (let i = 0; i < 25; ++i) { - _debugDummyBallot(); - } } // Closes the poll and return to the main menu @@ -200,9 +194,6 @@ function emitPrompt(){ if (poll.host != myUuid) return; // We are not the host of this poll - console.log(`Clearing responses`) - responses = {} - console.log(`Emitting prompt`); Messages.sendMessage(poll.id, JSON.stringify({type: "poll_prompt", prompt: {question: poll.question, options: poll.options}})); } @@ -252,6 +243,7 @@ // NOTE: Has to be *over* 50%. if (sortedObject[Object.keys(sortedObject)[0]] > majority) { // Show dialog of election statistics + Messages.sendMessage(poll.id, JSON.stringify({type: "poll_winner", winner: Object.keys(sortedObject)[0], rounds: electionIterations, votesCounted: totalVotes})); console.log(`\nWinner: ${Object.keys(sortedObject)[0]}\nElection rounds: ${electionIterations}\nVotes counted: ${totalVotes}`); return; // Winner was selected. We are done! }; @@ -287,9 +279,9 @@ function _debugDummyBallot() { if (!debug) return; // Just incase... - let ballot = getRandomOrder('C1', 'C2', 'C3', 'C4', 'C5', 'C6'); - - responses[Object.keys(responses).length.toString()] = ballot; + let ballot = getRandomOrder(...poll.options); + const responsesKeyName = Object.keys(responses).length.toString(); + responses[responsesKeyName] = ballot; function getRandomOrder(...words) { for (let i = words.length - 1; i > 0; i--) { @@ -320,9 +312,17 @@ case "prompt": poll.question = event.prompt.question; poll.options = event.prompt.options; + poll.host_can_vote = event.host_can_vote emitPrompt(); break; case "run_election": + // Debug: Create a lot of fake ballots + if (debug) { + for (let i = 0; i < 25; ++i) { + _debugDummyBallot(); + } + } + preformElection(); break; } @@ -383,14 +383,16 @@ // Received poll information if (message.type == "poll_prompt") { - if (poll.host == myUuid) return; // We are the host of this poll console.log(`Prompt:\n ${JSON.stringify(message.prompt)}`); // TODO: This is still silly. Try using UUIDs per prompt and check if we are answering the same question by id? // Don't recreate the prompt if we already have the matching question - if (message.prompt.question == poll.question) return; + if (message.prompt.question == poll.question && !poll.host_can_vote) return; + + // Play sound for new poll const newPollSound = SoundCache.getSound(Script.resolvePath("./sound/new_vote.mp3")) Audio.playSystemSound(newPollSound, {volume: 0.5}); + _emitEvent({type: "poll_prompt", prompt: message.prompt}); poll.question = message.prompt.question; @@ -410,6 +412,11 @@ // console.log(JSON.stringify(responses)); } + // Winner was broadcasted + if (message.type == "poll_winner") { + _emitEvent({type: "poll_winner", winner: message.winner, rounds: message.rounds, votesCounted: message.votes}); + } + } } diff --git a/applications/voting/vote.qml b/applications/voting/vote.qml index bb77114..17be45a 100644 --- a/applications/voting/vote.qml +++ b/applications/voting/vote.qml @@ -1,5 +1,5 @@ import QtQuick 2.7 -import QtQuick.Controls 2.0 +import QtQuick.Controls 2.15 import QtQuick.Layouts 1.3 import controlsUit 1.0 as HifiControlsUit @@ -10,9 +10,11 @@ Rectangle { height: 700 id: root - // property string current_page: "poll_host_view" + // property string current_page: "poll_results" property string current_page: "poll_list" + property bool host_can_vote: false + property bool is_host: false // Poll List view ColumnLayout { @@ -121,6 +123,26 @@ Rectangle { } + RowLayout { + width: parent.width + + Text { + text: "Allow host voting" + color:"white" + font.pointSize: 12 + Layout.fillWidth: true + } + + CheckBox { + width: 30 + height: 25 + checked: false + onToggled: { + host_can_vote = checked + } + } + } + // Submit button RowLayout { @@ -239,7 +261,7 @@ Rectangle { } } - // Add Option Button + // Host actions ColumnLayout { anchors.horizontalCenter: parent.horizontalCenter width: parent.width @@ -249,6 +271,7 @@ Rectangle { width: parent.width anchors.horizontalCenter: parent.horizontalCenter + // Close poll Rectangle { width: 150 height: 40 @@ -269,6 +292,7 @@ Rectangle { } } + // Add poll option Rectangle { width: 40 height: 40 @@ -289,6 +313,7 @@ Rectangle { } } + // Submit the poll to the users Rectangle { width: 150 height: 40 @@ -312,32 +337,12 @@ Rectangle { options.push(element.option) } - toScript({type: "prompt", prompt: {question: poll_to_respond_title.text, options: options}}) - } - } - } - } + // Send the prompt to the server + toScript({type: "prompt", prompt: {question: poll_to_respond_title.text, options: options}, host_can_vote: host_can_vote}); - RowLayout { - width: parent.width - anchors.horizontalCenter: parent.horizontalCenter - - Rectangle { - width: 150 - height: 40 - color: "#1c71d8" - - Text { - anchors.centerIn: parent - text:"Run Election" - color: "white" - font.pointSize:18 - } - - MouseArea { - anchors.fill: parent - onClicked: { - toScript({type: "run_election"}) + // If the host can vote, change the screen to the client view to allow the vote + if (host_can_vote) current_page = "poll_client_view"; + else current_page = "poll_results" } } } @@ -441,6 +446,7 @@ Rectangle { // TODO: Turn into function and move to root + // TODO: Validate responses onClicked: { var votes = {}; var orderedArray = []; @@ -470,6 +476,9 @@ Rectangle { // Send our ballot to the host (by sending it to everyone in the poll lol) toScript({type: "cast_vote", ballot: onlyNames}); + + // Change screen to results screen + current_page = "poll_results" } } } @@ -477,6 +486,218 @@ Rectangle { } } + // Poll results + ColumnLayout { + width: parent.width + height: parent.height - 40 + visible: current_page == "poll_results" + + // Header + Item { + height: 100 + Layout.fillWidth: true + + Rectangle { + color: "black" + anchors.fill: parent + } + + Text { + width: parent.width + text: "Winner" + color: "gray" + font.pointSize: 12 + wrapMode: Text.NoWrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + y: 20 + } + Text { + id: poll_winner + width: parent.width + text: "Me" + color: "white" + font.pointSize: 20 + wrapMode: Text.NoWrap + anchors.top: parent.children[1].bottom + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + + ColumnLayout { + Layout.fillHeight: true + width: parent.width - 40 + anchors.horizontalCenter: parent.horizontalCenter + + RowLayout { + width: parent.width + + Text { + text: "Votes recived:" + color: "gray" + Layout.fillWidth: true + font.pointSize: 12 + } + Text { + text: "0" + color: "white" + font.pointSize: 14 + } + } + + RowLayout { + width: parent.width + + Text { + text: "Iterations:" + color: "gray" + Layout.fillWidth: true + font.pointSize: 12 + } + Text { + text: "-" + color: "white" + font.pointSize: 14 + } + } + } + } + + // TODO: Style: Not centered. Remake. + // Host actions + RowLayout { + visible: is_host + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + + // Recast vote + Rectangle { + width: 150 + height: 40 + color: "#c0bfbc" + visible: (is_host && host_can_vote) || !is_host + + Text { + anchors.centerIn: parent + text:"Recast Vote" + color: "black" + font.pointSize:18 + } + + MouseArea { + anchors.fill: parent + onClicked: { + current_page = "poll_client_view" + } + } + } + } + + // Host actions + RowLayout { + visible: is_host + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + + // Preform Election + Rectangle { + width: 150 + height: 40 + color: "#c0bfbc" + + Text { + anchors.centerIn: parent + text:"Tally Votes" + color: "black" + font.pointSize:18 + } + + MouseArea { + anchors.fill: parent + onClicked: { + toScript({type: "run_election"}) + } + } + } + + // Preform Election + Rectangle { + width: 150 + height: 40 + color: "#c0bfbc" + + Text { + anchors.centerIn: parent + text:"Poll Settings" + color: "black" + font.pointSize:18 + } + + MouseArea { + anchors.fill: parent + onClicked: { + } + } + } + } + + // TODO: View a list of the ballots and the results of the election rounds + // Item { + // Layout.fillHeight: true + // Layout.fillWidth: true + + // // TODO: Allow scrolling + // ScrollView { + // // ScrollBar.horizontal.policy: ScrollBar.AlwaysOn + // // ScrollBar.vertical.policy: ScrollBar.AlwaysOff + // clip: true + // width: parent.width + // height: parent.height + + // ColumnLayout { + // Repeater { + // model: 3 + // ColumnLayout { + // Text { + // text: "Round "+ index +": Eleminated XXX" + // font.pointSize: 18 + // color: "white" + // } + + // Repeater { + // model: 20 + + // RowLayout { + // height: 30 + + // Repeater { + // model: 4 + // Item { + // width: 100 + // height: 30 + + // Text { + // text: 'One ' + index + // color: "white" + // font.pointSize: 12 + // width: parent.width - 10 + // } + // } + // } + // } + // } + // } + // } + // } + // } + // } + } + // Templates // Active poll listing @@ -681,11 +902,15 @@ Rectangle { // Switch view to the create poll view case "create_poll": // Reset poll host page - poll_to_respond_title.text = "Prompt" + poll_to_respond_title.text = "" poll_option_model_host.clear(); // Show host page current_page = "poll_host_view"; + + // Set variables + is_host = true + break; // Add poll info to the list of active polls @@ -720,6 +945,10 @@ Rectangle { active_polls.remove(i); } } + + // Set variables + is_host = false + break; // Open the host view @@ -735,6 +964,9 @@ Rectangle { poll_option_model_host.append({option: option}) } break; + case "poll_winner": + poll_winner.text = message.winner + break; } }