Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Hmm

  • Loading branch information...
commit 2394787f9a39721e8c62deb9ce2c89e05a41df8a 1 parent f663fc5
@peterc authored
View
BIN  .sass-cache/a93cb009589fab34419556d3002e6431afa4aaef/style.scssc
Binary file not shown
View
3  Gemfile
@@ -8,4 +8,5 @@ gem 'json'
gem 'redis'
gem 'ohm'
gem 'ohm-contrib'
-gem 'awesome_print'
+gem 'awesome_print'
+gem 'sinatra-flash'
View
3  Gemfile.lock
@@ -43,6 +43,8 @@ GEM
rack (~> 1.3, >= 1.3.6)
rack-protection (~> 1.2)
tilt (~> 1.3, >= 1.3.3)
+ sinatra-flash (0.3.0)
+ sinatra (>= 1.0.0)
thin (1.4.1)
daemons (>= 1.0.9)
eventmachine (>= 0.12.6)
@@ -62,4 +64,5 @@ DEPENDENCIES
redis
sass
sinatra
+ sinatra-flash
thin
View
55 app/handlers/poll.rb
@@ -0,0 +1,55 @@
+class Willy
+ post '/poll' do
+ question = params[:question] || ''
+ answers = params[:answer]
+
+ unless question.to_s.length > 5 && answers.all? { |a| !a.empty? }
+ halt erb 'Invalid inputs!!'
+ end
+
+ poll = Poll.create question: question, ip: request.ip
+
+ answers.each do |answer|
+ poll.answers.push(Answer.create text: answer)
+ end
+
+ session[:tokens] ||= []
+ session[:tokens] << poll.token
+
+ redirect "#{poll.id}"
+ end
+
+ post '/poll/:id/activate' do
+ @poll = Poll[params[:id]]
+ redirect '/' unless @poll
+ redirect '/' unless params[:token] == @poll.token
+
+ @poll.start
+
+ content_type "application/json"
+ erb({status: 'success', seconds_remaining: @poll.seconds_remaining }.to_json, :layout => false)
+ end
+
+ get %r{/(\d+)} do |id|
+ @poll = Poll[id]
+ redirect '/' unless @poll
+
+ @owner = session[:tokens] && session[:tokens].include?(@poll.token)
+ erb :poll
+ end
+
+ post '/vote/:poll_id/:answer_id' do
+ @poll = Poll[params[:poll_id]]
+ redirect '/' unless @poll
+
+ Vote.create poll: @poll, answer: Answer[params[:answer_id]]
+
+ # TODO: DON'T DO THIS HERE!!
+ client = Faye::Client.new('http://localhost:9292/fayex')
+ client.publish('/messages', {'poll_id' => @poll.id, 'status' => 'counts'}.merge(@poll.counts) )
+
+ content_type "application/json"
+ erb({ status: 'success' }.to_json, :layout => false)
+ end
+
+end
View
14 app/handlers/votes.rb
@@ -1,14 +0,0 @@
-class Willy
- post '/vote' do
- question = params[:question]
- answers = params[:answer]
-
- poll = Poll.create question: question, ip: request.ip
-
- answers.each do |answer|
- poll.answers.push Answer.create text: answer
- end
-
- redirect "#{poll.id}"
- end
-end
View
1  app/models/answer.rb
@@ -1,6 +1,7 @@
class Answer < Ohm::Model
attribute :text
reference :poll, :Poll
+ collection :votes, :Vote
def validate
assert_present :text
View
56 app/models/poll.rb
@@ -2,17 +2,71 @@ class Poll < Ohm::Model
attribute :question
attribute :token
attribute :active
+ attribute :visible
attribute :ip
+ attribute :started_at
+ attribute :ending_at
+
list :answers, :Answer
- set :votes, :Vote
+ collection :votes, :Vote
include Ohm::Callbacks
def before_create
self.token = rand(10 ** 8).to_s(20)
+ self.visible = true
+ self.active = false
+ end
+
+ def active?
+ return false if self.active == false || self.active == 'false'
+ true
end
def validate
assert_present :question
end
+
+ def start
+ self.started_at = Time.now.to_i
+ self.ending_at = Time.now.to_i + 20
+ self.active = true
+ self.save
+ client = Faye::Client.new('http://localhost:9292/fayex')
+ client.publish('/messages', 'status' => 'start', 'seconds_remaining' => self.seconds_remaining, 'poll_id' => self.id)
+ end
+
+ def ready_to_start?
+ return true unless self.ending_at
+ return false if self.ending_at.to_i < Time.now.to_i
+ return false if Time.now.to_i > self.started_at.to_i
+ true
+ end
+
+ def counts
+ h = Hash[answers.to_a.map { |a| ["answer-#{a.id}", votes.count { |v| v.answer_id == a.id }] }]
+ h['max'] = h.values.max
+ h['total'] = self.votes.size
+ h
+ end
+
+ def finished?
+ return true if self.ending_at && Time.now.to_i > self.ending_at.to_i
+ false
+ end
+
+ def finish
+ self.active = false
+ self.save
+ end
+
+ def seconds_remaining
+ r = [self.ending_at.to_i - Time.now.to_i, 0].max rescue 0
+ finish if r < 1
+ r
+ end
+
+ def winner
+ votes.group_by { |v| v.answer }.max_by { |k, v| v.size }.first
+ end
end
View
9 app/parttwo.rb
@@ -1,14 +1,17 @@
Thread.new do
client = Faye::Client.new('http://localhost:9292/fayex')
- client.subscribe('/messages') do |message|
- puts message.inspect
+ client.subscribe('/votes') do |message|
+ File.open('/tmp/log.log', 'a') { |f| f.puts message.inspect }
end
+ File.open('/tmp/log.log', 'a') { |f| f.puts "balls" }
end
+
#Thread.new do
# client = Faye::Client.new('http://localhost:9292/fayex')
# 10.times do
# sleep 1
# client.publish('/messages', 'text' => 'Hellxxxo world')
# end
-#end
+#end
+
View
76 app/views/index.erb
@@ -1,39 +1,39 @@
-<!doctype html>
-<html>
-<head>
- <meta charset="utf-8">
- <title></title>
- <meta name="description" content="">
- <meta name="author" content="">
- <link rel="stylesheet" href="/style.css" />
-</head>
-<body>
- <header>
- <h1>Blah.</h1>
- </header>
- <div id="main">
- <div id="debug"></div>
- <button id="send">Send</button>
- </div>
- <footer>
-
- </footer>
- <script src="/jquery.min.js"></script>
- <script src="/ICanHaz.js"></script>
- <script src="/fayex/client.js"></script>
- <script>
- var client = new Faye.Client('/fayex');
- ich.addTemplate('message', "<div>oooh.. {{text}}</div>");
-
- $('#send').click(function() {
- client.publish('/messages', {
- text: 'Hello world'
- });
- });
- client.subscribe('/messages', function(message) {
- $('#debug').append(ich.message(message));
- });
+ <form id="questionForm" method="post" action="/poll">
+
+ <p>Question</p>
+ <input type="text" name="question" id="question" value="" required />
+
+ <p>Answers</p>
+ <input type="text" name="answer[]" value="" required />
+ <input type="text" name="answer[]" value="" required />
+ <button id="remove" style="display: none;">Remove - </button>
- </script>
-</body>
-</html>
+ <button id="add">Add + </button>
+
+ <button type="submit">Create Poll</button>
+ </form>
+
+<script>
+ $(document).ready(function() {
+
+ $('#add').click( function(e) {
+ e.preventDefault;
+ $('<input type="text" name="answer[]" value="" class="optionalAnswer" required />').insertBefore('#remove');
+ $('#remove').show();
+ });
+
+
+ $('#remove').click( function(e) {
+ e.preventDefault;
+ $('.optionalAnswer').last().remove();
+
+ // if last child DOES !NOT have a class of optional answer,
+ // we know we have our origional two left - so hold fire!!
+ if (!$('input[type=text]').last().hasClass('optionalAnswer')) {
+ $('#remove').hide();
+ }
+
+ });
+
+ });
+</script>
View
24 app/views/layout.erb
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title></title>
+ <meta name="description" content="">
+ <meta name="author" content="">
+ <link rel="stylesheet" href="/style.css" />
+ <script src="/jquery.min.js"></script>
+ <meta name="viewport" content="width=420" />
+ <!-- script src="/ICanHaz.js"></script -->
+ <!-- link href='http://fonts.googleapis.com/css?family=Homenaje' rel='stylesheet' type='text/css' -->
+</head>
+<body>
+ <div id="main">
+
+ <%= yield %>
+
+ </div>
+ <footer>
+
+ </footer>
+</body>
+</html>
View
44 app/views/poll.erb
@@ -0,0 +1,44 @@
+<% if @owner %>
+ <h2 class="url">http://no.gd/poll/<%= @poll.id %></h2>
+<% end %>
+
+<% if @owner && @poll.ready_to_start? %>
+ <div class="activator"><button id="start">Start poll!</button></div>
+<% end %>
+
+<div class="question">
+ <%= @poll.question %>
+</div>
+
+<% if !@owner %>
+ <div class="buttons" style="<%= 'display: none' if @poll.finished? %>">
+ <% @poll.answers.each do |answer| %>
+ <div class="answer">
+ <button class="vote" data-answerid="<%= answer.id %>"><%= answer.text %></button>
+ </div>
+ <% end %>
+ </div>
+<% end %>
+
+<div class="answers" style="display: none; <%= 'display: block' if @owner || @poll.finished? %>">
+ <% @poll.answers.each do |answer| %>
+ <div class="answer" id="answer-<%= answer.id %>">
+ <div class="text"><%= answer.text %></div>
+ <div class="bar"></div>
+ <div class="votes"><%= answer.votes.size %></div>
+ </div>
+ <% end %>
+</div>
+
+<div class="timer" style="<%= 'display: none' unless @poll.active? %>"><div class="value"></div> seconds left</div>
+
+<script>
+ <% if @owner %>var token = '<%= @poll.token %>';<% end %>
+ var poll_id = <%= @poll.id %>;
+ var active = <%= @poll.active %>;
+ var seconds_remaining = <%= @poll.seconds_remaining %>;
+ var owner = <%= !!@owner %>;
+</script>
+
+<script src="/fayex/client.js"></script>
+<script src="/main.js"></script>
View
30 app/views/pollvoter.erb
@@ -0,0 +1,30 @@
+<h2><%= @poll.question %></h2>
+
+<% @poll.answers.each do |answer| %>
+ <div class="answer">
+ <button class="vote" data-answerid="<%= answer.id %>"><%= answer.text %></button>
+ </div>
+<% end %>
+
+<div id="debug"></div>
+
+<script src="/fayex/client.js"></script>
+<!-- script src="/main.js"></script -->
+<script>
+ var client = new Faye.Client('/fayex');
+ ich.addTemplate('vote', "<div>{{poll_id}}-{{answer_id}}</div>");
+
+ $('button.vote').click(function() {
+ console.log($(this).data('answerid'));
+ client.publish('/votes', {
+ poll_id: '<%= @poll.id %>',
+ answer_id: $(this).data('answerid')
+ });
+ console.log('hmm');
+ });
+
+ client.subscribe('/votes', function(vote) {
+ $('#debug').append(ich.vote(vote));
+ });
+
+</script>
View
152 app/views/style.scss
@@ -1,10 +1,150 @@
-body {
- font-family: 'helvetica neue', helvetica, arial, sans-serif;
- font-size: 16px;
-}
+// body {
+// font-family: 'helvetica neue', helvetica, arial, sans-serif;
+// font-size: 16px;
+// }
+
+ body {
+ margin: 16px;
+ background-color: #fff;
+ font-size: 20px;
+ font-family: 'helvetica neue', helvetica, arial, sans-serif;
+ //text-transform: uppercase;
+ }
body > header, #main, body > footer {
margin: 8px auto;
- width: 960px;
- padding: 0 12px;
+ width: 420px;
+ padding: 0;
+}
+//
+// header, footer { display: none; }
+
+
+#questionForm {
+ width: 420px;
+ border: 0px solid #ccc;
+ padding: 20px;
+ margin: 0 auto;
+ position: relative;
+ }
+ #questionForm input {
+ width: 100%;
+ border: 1px solid #ccc;
+ padding: 5px 0 5px 5px;
+ font-size: 24px;
+ font-family: 'Homenaje', sans-serif;
+
+
+
+ }
+ input#question {
+ font-size: 32px;
+ }
+
+.url {text-align: center;}
+
+FORM P { font-size: 2.0em; }
+
+button {
+font-family: 'Homenaje', sans-serif;
+text-transform: uppercase;
+cursor: pointer;
+color: #fff;
+background-color: #DA4F49;
+padding: 5px 0 5px 0;
+font-size: 22px;
+
+width: 100%;
+background: #ff3019; /* Old browsers */
+background: -moz-linear-gradient(top, #ff3019 0%, #cf0404 100%); /* FF3.6+ */
+background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ff3019), color-stop(100%,#cf0404)); /* Chrome,Safari4+ */
+background: -webkit-linear-gradient(top, #ff3019 0%,#cf0404 100%); /* Chrome10+,Safari5.1+ */
+background: -o-linear-gradient(top, #ff3019 0%,#cf0404 100%); /* Opera 11.10+ */
+background: -ms-linear-gradient(top, #ff3019 0%,#cf0404 100%); /* IE10+ */
+background: linear-gradient(to bottom, #ff3019 0%,#cf0404 100%); /* W3C */
+filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff3019', endColorstr='#cf0404',GradientType=0 ); /* IE6-9 */
+border-color: #BD362F #BD362F #802420;
+border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+}
+
+
+#add {
+color: #000;
+background: #fcfff4; /* Old browsers */
+background: -moz-linear-gradient(top, #fcfff4 0%, #e9e9ce 100%); /* FF3.6+ */
+background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fcfff4), color-stop(100%,#e9e9ce)); /* Chrome,Safari4+ */
+background: -webkit-linear-gradient(top, #fcfff4 0%,#e9e9ce 100%); /* Chrome10+,Safari5.1+ */
+background: -o-linear-gradient(top, #fcfff4 0%,#e9e9ce 100%); /* Opera 11.10+ */
+background: -ms-linear-gradient(top, #fcfff4 0%,#e9e9ce 100%); /* IE10+ */
+background: linear-gradient(to bottom, #fcfff4 0%,#e9e9ce 100%); /* W3C */
+filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fcfff4', endColorstr='#e9e9ce',GradientType=0 ); /* IE6-9 */
+border: 1px solid #CCC;
+border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+border-color: #E6E6E6 #E6E6E6 #BFBFBF;
+border-bottom-color: #B3B3B3;
+font-size: 18px;
+margin-top: 20px;
+}
+
+.question { font-size: 2.0em; margin-bottom: 18px; margin-top: 1em; }
+
+.timer { text-align: center; font-size: 2.0em; }
+
+ p {margin: 12px 0px;}
+ hr {margin: 14px 0 15px 0; border-top: none; border-bottom: 1px solid #ccc; width: 295px; }
+
+
+.text {
+ font-size: 20px; position: absolute; top: 0; left: 0; text-align: left; width: 100%; z-index: 5000; color: #000; padding: 10px;
+}
+
+
+
+.answer {
+position: relative;
+height: 48px;
+width: 100%;
+margin-bottom: 8px;
+overflow: hidden;
+background: #e2e2e2; /* Old browsers */
+background: -moz-linear-gradient(top, #e2e2e2 0%, #dbdbdb 50%, #d1d1d1 51%, #fefefe 100%); /* FF3.6+ */
+background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#e2e2e2), color-stop(50%,#dbdbdb), color-stop(51%,#d1d1d1), color-stop(100%,#fefefe)); /* Chrome,Safari4+ */
+background: -webkit-linear-gradient(top, #e2e2e2 0%,#dbdbdb 50%,#d1d1d1 51%,#fefefe 100%); /* Chrome10+,Safari5.1+ */
+background: -o-linear-gradient(top, #e2e2e2 0%,#dbdbdb 50%,#d1d1d1 51%,#fefefe 100%); /* Opera 11.10+ */
+background: -ms-linear-gradient(top, #e2e2e2 0%,#dbdbdb 50%,#d1d1d1 51%,#fefefe 100%); /* IE10+ */
+background: linear-gradient(to bottom, #e2e2e2 0%,#dbdbdb 50%,#d1d1d1 51%,#fefefe 100%); /* W3C */
+filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e2e2e2', endColorstr='#fefefe',GradientType=0 ); /* IE6-9 */
+filter: progid:dximagetransform.microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);
+}
+
+button.vote {
+ background-color: #069;
+}
+
+button.vote[disabled=disabled] {
+ background: none;
+ background-color: #bbb;
+}
+
+.value { display: inline; color: #c00; }
+
+.bar {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 0;
+ height: 48px;
+ font-size: 40px;
+ padding-right: 1em;
+ color: #fff;
+ text-align: right;
+ background: #9dd53a; /* Old browsers */
+ background: -moz-linear-gradient(top, #9dd53a 0%, #a1d54f 50%, #80c217 51%, #7cbc0a 100%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#9dd53a), color-stop(50%,#a1d54f), color-stop(51%,#80c217),
+ color-stop(100%,#7cbc0a)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* Opera 11.10+ */
+ background: -ms-linear-gradient(top, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* IE10+ */
+ background: linear-gradient(to bottom, #9dd53a 0%,#a1d54f 50%,#80c217 51%,#7cbc0a 100%); /* W3C */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#9dd53a', endColorstr='#7cbc0a',GradientType=0 ); /* IE6-9 */
}
View
13 app/willy.rb
@@ -4,11 +4,24 @@
require 'faye'
require 'ohm'
require 'ohm/contrib'
+require 'sinatra/flash'
+require 'json'
+
+Ohm.connect thread_safe: true
$: << File.expand_path('../..', __FILE__)
class Willy < Sinatra::Base
set :public_folder, File.dirname(__FILE__) + "/../public"
+
+ SECRET_WE_DONT_CARE_ABOUT = 'fdsias89dfoifnkdasfdsay8923jbk'
+
+ use Rack::Session::Cookie, :key => 'willy',
+ :expire_after => 2592000,
+ :secret => SECRET_WE_DONT_CARE_ABOUT
+
+ set :session_secret, SECRET_WE_DONT_CARE_ABOUT
+ register Sinatra::Flash
end
Dir['app/models/*.rb'].each { |f| require f }
View
118 public/main.js
@@ -0,0 +1,118 @@
+var timer;
+
+var finishPoll = function() {
+ clearTimeout(timer);
+ $('.timer').hide();
+ setTimeout(showResults, 1000);
+};
+
+var showResults = function() {
+ $('.answers').show();
+ $('.buttons').hide();
+};
+
+var updateTimer = function() {
+ $('.timer .value').html(seconds_remaining);
+ if (seconds_remaining > 0) {
+ seconds_remaining--;
+ } else {
+ seconds_remaining = 0;
+ active = false;
+ finishPoll();
+ }
+};
+
+var startTimer = function() {
+ updateTimer();
+ $('.timer').show();
+ timer = setInterval(updateTimer, 1000);
+};
+
+$('#start').click(function() {
+
+ $.ajax({
+ type: 'POST',
+ url: '/poll/' + poll_id + '/activate',
+ data: { token: token },
+ success: function(data) {
+ console.dir(data);
+ if (data.status == 'success') {
+ $('.activator').hide();
+ active = true;
+ seconds_remaining = data.seconds_remaining ^ 0;
+ startTimer();
+ }
+ }
+ });
+});
+
+$('button.vote').click(function() {
+ $.ajax({
+ type: 'POST',
+ url: '/vote/' + poll_id + '/' + $(this).data('answerid'),
+ success: function(data) {
+
+ }
+ });
+ $('button.vote').attr("disabled", true);
+ $(this).html('Thanks for your vote!');
+ console.log('voted!');
+});
+
+var startVote = function() {
+ $('.buttons').show();
+ $('button.vote').attr("disabled", false);
+ $('.timer').show();
+};
+
+if (active) {
+ startTimer();
+ startVote();
+} else {
+ $('button.vote').attr("disabled", true);
+}
+
+var client = new Faye.Client('/fayex');
+/* ich.addTemplate('message', "<div>oooh.. {{text}}</div>");
+
+ $('#send').click(function() {
+ client.publish('/messages', {
+ text: 'Hello world'
+ });
+ }); */
+
+var updateCounts = function(data){
+ $('div.answer').each(function(i, el) {
+ if (data[el.id]) {
+ $('#' + el.id + ' .bar').html(data[el.id]);
+ $('#' + el.id + ' .bar').css('width', ((data[el.id] / data['max']) * 100) + '%');
+ }
+
+ });
+};
+
+if (owner) {
+ //client.subscribe('/messages', function(message) {
+ // $('#debug').append(ich.message(message));
+ //});
+ client.subscribe('/messages', function(data) {
+ console.log(data);
+ if (data.status == 'counts') {
+ updateCounts(data);
+ }
+ });
+} else {
+ client.subscribe('/messages', function(data) {
+ console.log(data);
+ if (data.status == 'start' && (data.poll_id ^ 0) == poll_id) {
+ active = true;
+ seconds_remaining = data.seconds_remaining ^ 0;
+ startTimer();
+ startVote();
+ }
+
+ if (data.status == 'counts') {
+ updateCounts(data);
+ }
+ });
+}
View
18 test/test_horrible.rb
@@ -22,4 +22,22 @@
@poll.answers.size.must_equal 3
end
+
+ it "can add votes to a poll" do
+ @poll = Poll.create question: 'Are you happy?'
+
+ answers = %w{yes no maybe}
+
+ answers.each do |answer|
+ @poll.answers.push(Answer.create text: answer)
+ end
+
+ [0, 1, 2, 0, 0, 1].each do |i|
+ Vote.create poll: @poll, answer: @poll.answers.to_a[i]
+ end
+
+ @poll.votes.size.must_equal 6
+ @poll.winner.must_equal @poll.answers.to_a[0]
+ @poll.answers.to_a[0].votes.size.must_equal 3
+ end
end
Please sign in to comment.
Something went wrong with that request. Please try again.