diff options
139 files changed, 4190 insertions, 510 deletions
@@ -24,3 +24,4 @@ nohup.out *# .#* .nfs* +.*.swp diff --git a/app/assets/images/Plus.png b/app/assets/images/Plus.png Binary files differnew file mode 100644 index 0000000..fa6a7a5 --- /dev/null +++ b/app/assets/images/Plus.png diff --git a/app/assets/images/bg.png b/app/assets/images/bg.png Binary files differnew file mode 100644 index 0000000..91c77c8 --- /dev/null +++ b/app/assets/images/bg.png diff --git a/app/assets/images/chess_icon.png b/app/assets/images/chess_icon.png Binary files differnew file mode 100644 index 0000000..6bcffe6 --- /dev/null +++ b/app/assets/images/chess_icon.png diff --git a/app/assets/images/league_icon.gif b/app/assets/images/league_icon.gif Binary files differnew file mode 100644 index 0000000..1f0fa43 --- /dev/null +++ b/app/assets/images/league_icon.gif diff --git a/app/assets/javascripts/alerts/edit.js.coffee b/app/assets/javascripts/alerts/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/alerts/edit.js.coffee diff --git a/app/assets/javascripts/alerts/index.js.coffee b/app/assets/javascripts/alerts/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/alerts/index.js.coffee diff --git a/app/assets/javascripts/alerts/new.js.coffee b/app/assets/javascripts/alerts/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/alerts/new.js.coffee diff --git a/app/assets/javascripts/alerts/show.js.coffee b/app/assets/javascripts/alerts/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/alerts/show.js.coffee diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index d6925fa..1130838 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,4 +13,8 @@ //= require jquery //= require jquery_ujs //= require turbolinks -//= require_tree . +//= require 'drag.js' +//= require 'dragsort.js' +//= require 'coordinates.js' +// +//= require_tree ./application diff --git a/app/assets/javascripts/application/.keep b/app/assets/javascripts/application/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/application/.keep diff --git a/app/assets/javascripts/brackets/edit.js.coffee b/app/assets/javascripts/brackets/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/brackets/edit.js.coffee diff --git a/app/assets/javascripts/brackets/index.js.coffee b/app/assets/javascripts/brackets/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/brackets/index.js.coffee diff --git a/app/assets/javascripts/brackets/new.js.coffee b/app/assets/javascripts/brackets/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/brackets/new.js.coffee diff --git a/app/assets/javascripts/brackets/show.js.coffee b/app/assets/javascripts/brackets/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/brackets/show.js.coffee diff --git a/app/assets/javascripts/games/edit.js.coffee b/app/assets/javascripts/games/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/games/edit.js.coffee diff --git a/app/assets/javascripts/games/index.js.coffee b/app/assets/javascripts/games/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/games/index.js.coffee diff --git a/app/assets/javascripts/games/new.js.coffee b/app/assets/javascripts/games/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/games/new.js.coffee diff --git a/app/assets/javascripts/games/show.js.coffee b/app/assets/javascripts/games/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/games/show.js.coffee diff --git a/app/assets/javascripts/main/homepage.js.coffee b/app/assets/javascripts/main/homepage.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/main/homepage.js.coffee diff --git a/app/assets/javascripts/matches/show.js.coffee b/app/assets/javascripts/matches/show.js.coffee new file mode 100644 index 0000000..2609a69 --- /dev/null +++ b/app/assets/javascripts/matches/show.js.coffee @@ -0,0 +1,2 @@ +window.onload = -> + BetterDragSort.makeListSortable(document.getElementById("boxes")); diff --git a/app/assets/javascripts/pms/edit.js.coffee b/app/assets/javascripts/pms/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/pms/edit.js.coffee diff --git a/app/assets/javascripts/pms/index.js.coffee b/app/assets/javascripts/pms/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/pms/index.js.coffee diff --git a/app/assets/javascripts/pms/new.js.coffee b/app/assets/javascripts/pms/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/pms/new.js.coffee diff --git a/app/assets/javascripts/pms/show.js.coffee b/app/assets/javascripts/pms/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/pms/show.js.coffee diff --git a/app/assets/javascripts/search/go.js.coffee b/app/assets/javascripts/search/go.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/search/go.js.coffee diff --git a/app/assets/javascripts/server/edit.js.coffee b/app/assets/javascripts/server/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/server/edit.js.coffee diff --git a/app/assets/javascripts/server/show.js.coffee b/app/assets/javascripts/server/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/server/show.js.coffee diff --git a/app/assets/javascripts/sessions/new.js.coffee b/app/assets/javascripts/sessions/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/sessions/new.js.coffee diff --git a/app/assets/javascripts/teams/edit.js.coffee b/app/assets/javascripts/teams/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/teams/edit.js.coffee diff --git a/app/assets/javascripts/teams/index.js.coffee b/app/assets/javascripts/teams/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/teams/index.js.coffee diff --git a/app/assets/javascripts/teams/new.js.coffee b/app/assets/javascripts/teams/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/teams/new.js.coffee diff --git a/app/assets/javascripts/teams/show.js.coffee b/app/assets/javascripts/teams/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/teams/show.js.coffee diff --git a/app/assets/javascripts/tournaments/edit.js.coffee b/app/assets/javascripts/tournaments/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/tournaments/edit.js.coffee diff --git a/app/assets/javascripts/tournaments/index.js.coffee b/app/assets/javascripts/tournaments/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/tournaments/index.js.coffee diff --git a/app/assets/javascripts/tournaments/new.js.coffee b/app/assets/javascripts/tournaments/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/tournaments/new.js.coffee diff --git a/app/assets/javascripts/tournaments/show.js.coffee b/app/assets/javascripts/tournaments/show.js.coffee new file mode 100644 index 0000000..8620210 --- /dev/null +++ b/app/assets/javascripts/tournaments/show.js.coffee @@ -0,0 +1,32 @@ +json_url = window.location.href.replace(/\.[^/]*$/,'')+".json" + +update = (tournament) -> + here = tournament["players"].length + needed = (tournament["min_teams_per_match"] * tournament["min_players_per_team"]) + pct_complete = here / needed + + $("#prog-bar").width (pct_complete * 100) + "%" + $("#players-needed").text here + " " + ((if here is 1 then "player has" else "players have")) + " signed up. " + needed + " players needed. " + + # Update the user list + players = "" + for player in tournament["players"] + players = players + "<li><span class=\"black\">" + player["user_name"] + "</span></li>" + $("#tournament-users").html players + + # Enable/disable the "start" button depending on the number of players + $("input[value=\"Start Tournament\"]").prop "disabled", (if (pct_complete >= 1) then false else true) + + # Switch views if the tournament has been started + if tournament["status"] is 1 + window.location.reload true + + # Do it all again + setTimeout (-> + $.ajax(url: json_url).done update + return + ), 2000 + +# Now kick off the whole process +window.onload = -> + $.ajax(url: json_url).done update diff --git a/app/assets/javascripts/users/edit.js.coffee b/app/assets/javascripts/users/edit.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/users/edit.js.coffee diff --git a/app/assets/javascripts/users/index.js.coffee b/app/assets/javascripts/users/index.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/users/index.js.coffee diff --git a/app/assets/javascripts/users/new.js.coffee b/app/assets/javascripts/users/new.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/users/new.js.coffee diff --git a/app/assets/javascripts/users/show.js.coffee b/app/assets/javascripts/users/show.js.coffee new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/assets/javascripts/users/show.js.coffee diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 3192ec8..677791c 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -11,3 +11,14 @@ *= require_self *= require_tree . */ + +#query{ + background-color: #303030; + border: 2px solid #ED9C28; + border-radius: 5px; + color: #FFF; + font-weight: bold; + height: 30px; + padding: 0px 5px; +} + diff --git a/app/assets/stylesheets/custom.css.scss b/app/assets/stylesheets/custom.css.scss new file mode 100644 index 0000000..4104235 --- /dev/null +++ b/app/assets/stylesheets/custom.css.scss @@ -0,0 +1,95 @@ +@import "bootstrap"; + +$page-color: #444; +$toolbar-color: black; +$orange: #DD9125; +$darker-orange: #9D4102; +$link-yellow: #FFC50D; + +header > nav { + @extend .navbar; + @extend .navbar-inverse; + color: white; + + #log-buttons { + margin-top: 8px; + form { display: inline; } + } + form.search { + @extend .navbar-form; + @extend .navbar-right; + input[type="submit"] { + @extend .btn-warning; margin-top: -3px; margin-right: 8px; + } + } +} + + +a, input[type="submit"] { + @extend .btn; + &.user { @extend .btn-info; } + &.signup { @extend .btn-success; } + &.signin { @extend .btn-warning; } + &.signout { @extend .btn-danger; } + &.server { @extend .btn-danger; } + font-weight: bold !important; +} + + +input[type="text"], input[type="password"]{ + -webkit-box-shadow: inset 0 0 8px rgba(0,0,0,0.1), 0 0 16px rgba(0,0,0,0.1); + -moz-box-shadow: inset 0 0 8px rgba(0,0,0,0.1), 0 0 16px rgba(0,0,0,0.1); + box-shadow: inset 0 0 8px rgba(0,0,0,0.1), 0 0 16px rgba(0,0,0,0.1); + + border: 3px inset #A5A5A5; + padding: 8px; + background: rgba(0,0,0,0.5); + margin: 0 0 5px 0; +} + +select { + background-color: #333; + padding: 5px; + border: none; + color: #DD9125 !important; +} + +p.errors { + background-color: rgba(0,0,0,0.5);; + color: red; + border-radius: 7px; + padding: 10px; +} + +#error_explanation { + width: 450px; + border: 2px solid red; + padding: 7px; + padding-bottom: 0; + margin-bottom: 20px; + background-color: #f0f0f0; + h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + margin-bottom: 0px; + background-color: #c00; + color: #fff; + } + ul li { + font-size: 12px; + list-style: square; + } +} + +// Limitation: Only one box can be expanded at a time +.collapsible { + .collapsed { display: block; } + .expanded { display: none; } + &:target { + .collapsed { display: none; } + .expanded { display: block; } + } +} diff --git a/app/assets/stylesheets/main.css.scss b/app/assets/stylesheets/main.css.scss index a0d94c1..3859f53 100644 --- a/app/assets/stylesheets/main.css.scss +++ b/app/assets/stylesheets/main.css.scss @@ -1,3 +1,14 @@ // Place all the styles related to the main controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ + +.jumbotron { + background-color: #FFF; + box-shadow: 1px 1px 20px black; + width: 93%; + + p { + line-height: 1.5em; + } + +} diff --git a/app/assets/stylesheets/matches.css.scss b/app/assets/stylesheets/matches.css.scss index 4c396e3..66da68e 100644 --- a/app/assets/stylesheets/matches.css.scss +++ b/app/assets/stylesheets/matches.css.scss @@ -1,3 +1,54 @@ // Place all the styles related to the matches controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ +#boxes li { + cursor: move; + position: relative; + float: left; + margin: 5px; + width: 180px; + height: 240px; + border: 1px solid rgb(0, 0, 0); + text-align: center; + padding-top: 10px; + background-color: rgb(238, 238, 255); +} +#numeric li { + cursor: move; + position: relative; + float: left; + margin: 5px; + width: 180px; + height: 240px; + border: 1px solid rgb(0, 0, 0); + text-align: center; + padding-top: 10px; + background-color: rgb(238, 238, 255); +} + +#match-stats { + padding-bottom: 10px; + border-bottom: thick dashed #F0AD4E; +} + +#match-winner { + margin: 0 auto; + text-align: center; + font-size: 2em; +} + +#current-id { + display: none; +} + + + +/**** INDEX PAGE - TABLE AND GRAPH ****/ +#matches-table { + @extend .table; + color: #FFF; + + form { + color: #333; + } +}
\ No newline at end of file diff --git a/app/assets/stylesheets/pms.css.scss b/app/assets/stylesheets/pms.css.scss index 5106093..a14299e 100644 --- a/app/assets/stylesheets/pms.css.scss +++ b/app/assets/stylesheets/pms.css.scss @@ -1,3 +1,11 @@ // Place all the styles related to the pms controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ + +p, li { + color: #DD9125; +} +td, th { + padding: 0px; + color: #DD9125; +}
\ No newline at end of file diff --git a/app/assets/stylesheets/scaffolds.css.scss b/app/assets/stylesheets/scaffolds.css.scss index 6ec6a8f..eb1f751 100644 --- a/app/assets/stylesheets/scaffolds.css.scss +++ b/app/assets/stylesheets/scaffolds.css.scss @@ -1,9 +1,30 @@ +@import "bootstrap"; + +$page-color: #444; +$toolbar-color: black; +$orange: #DD9125; +$darker-orange: #9D4102; +$link-yellow: #FFC50D; + +html{ + height: 100%; +} + body { - background-color: #fff; - color: #333; + background: asset-url("bg.png", image) repeat scroll 0 0 $page-color; + color: $page-color; font-family: verdana, arial, helvetica, sans-serif; font-size: 13px; line-height: 18px; + height: 100%; +} + +h1, h2, h3, h4, h5, p, li, label{ + color: $orange; +} + +h1, h2, h3, h4, h5 { + text-shadow: 0px 0px 2px $darker-orange; } p, ol, ul, td { @@ -12,6 +33,26 @@ p, ol, ul, td { line-height: 18px; } +/* NAVBAR */ + +.navbar-brand { + @extend .no-dec; + a{ + color: white; + &:hover, &:active, &:focus { + color: white; + font-weight: normal; + text-decoration: none; + } + } +} + +.navbar-inverse, header > nav { + background-color: $toolbar-color; + border-radius: 0px 0px 5px 5px; + box-shadow: 0px 0px 5px black; +} + pre { background-color: #eee; padding: 10px; @@ -19,13 +60,10 @@ pre { } a { - color: #000; - &:visited { - color: #666; - } - &:hover { - color: #fff; - background-color: #000; + color: $link-yellow; + &:hover { + color: #FFEF00; + text-decoration: none; } } @@ -36,34 +74,43 @@ div { } #notice { - color: green; + background-color: rgba(0,0,0,0.5); + border-radius: 7px; + padding: 10px; + width: 80%; + margin: 0px auto; + text-align: center; + + p{ + color: lightgreen !important; + font-weight: bold; + } } .field_with_errors { - padding: 2px; - background-color: red; + padding: 1px; + background-color: #FF4C4C; + box-shadow: 0px 0px 5px red; display: table; } -#error_explanation { - width: 450px; - border: 2px solid red; - padding: 7px; - padding-bottom: 0; - margin-bottom: 20px; - background-color: #f0f0f0; - h2 { - text-align: left; - font-weight: bold; - padding: 5px 5px 5px 15px; - font-size: 12px; - margin: -7px; - margin-bottom: 0px; - background-color: #c00; - color: #fff; - } - ul li { - font-size: 12px; - list-style: square; - } +.wrapper { + width: 80%; + margin-top: 10px; + min-height: 80%; + height: auto !important; + height: 99%; + margin: 0 auto; } + +button, input[type="submit"] { + @extend .btn; +} + +footer { + clear: both; + border-top: solid 1px $orange; + text-align: center; + margin: 0 auto; + width: 90%; +}
\ No newline at end of file diff --git a/app/assets/stylesheets/servers.css.scss b/app/assets/stylesheets/servers.css.scss index 4710386..1ff4536 100644 --- a/app/assets/stylesheets/servers.css.scss +++ b/app/assets/stylesheets/servers.css.scss @@ -1,3 +1,26 @@ // Place all the styles related to the servers controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ + +$page-color: #444; +$toolbar-color: black; +$orange: #DD9125; +$darker-orange: #9D4102; +$link-yellow: #FFC50D; + +.edit_server { + + legend { + color: #EEE; + } + color: #FFF; + + input[type="submit"] { + color: $page-color; + } + + li { + list-style: none; + } + +}
\ No newline at end of file diff --git a/app/assets/stylesheets/tournaments.css.scss b/app/assets/stylesheets/tournaments.css.scss index e372b90..cc55253 100644 --- a/app/assets/stylesheets/tournaments.css.scss +++ b/app/assets/stylesheets/tournaments.css.scss @@ -1,3 +1,95 @@ // Place all the styles related to the tournaments controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ + +p.default-field { + display: inline; +} + +span.default-explanation { + color: gray; + font-style: italic; +} + +#players-needed { + text-align: center; + font-style: italic; +} + +#tournament-side-params { + background: none repeat scroll 0 0 rgba(0,0,0,0.5); + border-radius: 5px; + float: right; + font-size: 7px; + padding: 10px; + + p { + font-size: 10px; + margin-bottom: 5px; + } + +} + +#tournament-users{ + + li { + color: #10A010; + } + + .black { + color: white; + } +} + + +/* Style of a tournament listing div */ +div.tournament-listing { + margin: 10px 0px; + border-radius: 5px; + box-shadow: 0px 0px 3px #B8B8B8; + background-color: rgba(0, 0, 0, 0.6); + border: 1px solid #AAAAAA; + min-height: 100px; + padding: 8px 4px; + + /* AKA the listing title */ + h3 { + margin-top: 0px; + color: #F0AD4E; + font-weight: bold; + } + + h3:hover { + color: #D09D3E; + } + + /* host of the tournament */ + .host { + font-weight: bold; + color: #FFF; + } + + .col-md-8 { + padding: 0; + a { + padding: 5px 0 0 0; + } + } + + .t-game{ + font-weight: bold; + text-align: center; + } + + .t-image{ + display: block; + margin:auto; + } +} + +div.leave-buttons { + margin-top: 50px; + form { + display: inline; + } +}
\ No newline at end of file diff --git a/app/assets/stylesheets/users.css.scss b/app/assets/stylesheets/users.css.scss index 1efc835..5df7aee 100644 --- a/app/assets/stylesheets/users.css.scss +++ b/app/assets/stylesheets/users.css.scss @@ -1,3 +1,41 @@ // Place all the styles related to the users controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ + + +/*** FOR NEW USER - AKA SIGN UP ***/ +.simple_captcha { + background-color: rgba(255, 255, 255, 0.7); + margin: 10px 0px; +} + + +div.user-listing { + margin: 10px 0px; + border-radius: 5px; + box-shadow: 0px 0px 3px #B8B8B8; + background-color: rgba(0, 0, 0, 0.6); + border: 1px solid #AAAAAA; + min-height: 100px; + padding: 8px 4px; + display: inline-table; + + /* AKA the listing title */ + h3 { + margin-top: 0px; + color: #F0AD4E; + font-weight: bold; + } + + h3:hover { + color: #D09D3E; + } + + .things { + padding: 0px 10px; + } + + p { + margin: 0; + } +}
\ No newline at end of file diff --git a/app/controllers/alerts_controller.rb b/app/controllers/alerts_controller.rb index a3cb8f9..6ab3663 100644 --- a/app/controllers/alerts_controller.rb +++ b/app/controllers/alerts_controller.rb @@ -1,6 +1,4 @@ class AlertsController < ApplicationController - before_action :set_alert, only: [:show, :edit, :update, :destroy] - # GET /alerts # GET /alerts.json def index @@ -25,7 +23,7 @@ class AlertsController < ApplicationController # POST /alerts.json def create @alert = Alert.new(alert_params) - + @alert.author = current_user respond_to do |format| if @alert.save format.html { redirect_to @alert, notice: 'Alert was successfully created.' } @@ -62,11 +60,16 @@ class AlertsController < ApplicationController end private + # Use callbacks to share common setup or constraints between actions. def set_alert @alert = Alert.find(params[:id]) end + def is_owner?(object) + object.author == current_user + end + # Never trust parameters from the scary internet, only allow the white list through. def alert_params params.require(:alert).permit(:author_id, :message) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 27ef6a7..d5752aa 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,55 @@ class ApplicationController < ActionController::Base + before_action :set_object, only: [:show] + before_action :check_create, only: [:new, :create] + before_action :check_edit, only: [:edit, :update] + before_action :check_delete, only: [:destroy] + # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception + + #include sessionhelper for the session controller and view + include SessionsHelper + + include SimpleCaptcha::ControllerHelpers + + def check_permission(verb, object=nil) + unless current_user.can?((verb.to_s+"_"+noun).to_sym) or (!object.nil? and is_owner?(object)) + respond_to do |format| + format.html do + if object.nil? + redirect_to send(noun.pluralize+"_url"), notice: "You don't have permission to #{verb} #{noun.pluralize}." + else + redirect_to object, notice: "You don't have permission to #{verb} this #{noun}." + end + end + format.json { render json: "Permission denied", status: :forbidden } + end + end + end + + def noun + @noun ||= self.class.name.underscore.sub(/_controller$/, '').singularize + end + + def set_object + object = send("set_"+noun) + end + + def check_create + check_permission(:create) + end + def check_edit + object = send("set_"+noun) + check_permission(:edit, object) + end + def check_delete + object = send("set_"+noun) + check_permission(:edit, object) + end + + # Override this + def is_owner?(object) + return false + end end diff --git a/app/controllers/games_controller.rb b/app/controllers/games_controller.rb index 8546efb..aec5294 100644 --- a/app/controllers/games_controller.rb +++ b/app/controllers/games_controller.rb @@ -1,6 +1,4 @@ class GamesController < ApplicationController - before_action :set_game, only: [:show, :edit, :update, :destroy] - # GET /games # GET /games.json def index diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 6519d7b..0ba4d94 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -1,2 +1,4 @@ class MainController < ApplicationController + def homepage + end end diff --git a/app/controllers/matches_controller.rb b/app/controllers/matches_controller.rb index b1162ef..1ad86d0 100644 --- a/app/controllers/matches_controller.rb +++ b/app/controllers/matches_controller.rb @@ -1,74 +1,298 @@ class MatchesController < ApplicationController - before_action :set_match, only: [:show, :edit, :update, :destroy] + require 'httparty' + require 'json' + require 'delayed_job' - # GET /matches - # GET /matches.json + before_action :set_tournament, only: [:index] + + # GET /tournaments/1/matches + # GET /tournaments/1/matches.json def index - @matches = Match.all end - # GET /matches/1 - # GET /matches/1.json - def show + # For compatability with the router assumptions made by ApplicationController#check_permission + def matches_url + set_tournament + tournament_matches_path(@tournament) end - # GET /matches/new - def new - @match = Match.new - end + def get_riot + + players_id = Array.new + players = Array.new - # GET /matches/1/edit - def edit - end + @match.teams.each do |team| + team.users.each do |user| + players_id.push(user.remote_usernames[0]["json_value"]["id"]) + players.push(user.remote_usernames[0]["json_value"]["id"]) + end + end + + recent = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/game/by-summoner/#{players_id[0]}/recent?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + + blue = Hash.new + purple = Hash.new - # POST /matches - # POST /matches.json - def create - @match = Match.new(match_params) + for i in 0..8 + current_player = players_id[i] + place = players[i] + info = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/game/by-summoner/#{current_player}/recent?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") - respond_to do |format| - if @match.save - format.html { redirect_to @match, notice: 'Match was successfully created.' } - format.json { render action: 'show', status: :created, location: @match } + if 100 == info["games"][0]["stats"]["team"] + blue.merge!("#{place}" => info["games"][0]["stats"]) else - format.html { render action: 'new' } - format.json { render json: @match.errors, status: :unprocessable_entity } + purple.merge!("#{place}" => info["games"][0]["stats"]) end + sleep(1) end + + #look into this glitch + if 100 == recent["games"][0]["stats"]["team"] + blue.merge!("#{players[9]}" => recent["games"][0]["stats"]) + else + purple.merge!("#{players[9]}" => recent["games"][0]["stats"]) + end + + @purp = purple + @blue = blue + end - # PATCH/PUT /matches/1 - # PATCH/PUT /matches/1.json - def update - respond_to do |format| - if @match.update(match_params) - format.html { redirect_to @match, notice: 'Match was successfully updated.' } - format.json { head :no_content } + def get_riot_info_fake + pull = "Kaceytron" + #current user information + response = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/by-name/#{pull.downcase}?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + + id = response["#{pull.downcase}"]['id'] + + #recent game information + recent = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/game/by-summoner/#{response["#{pull.downcase}"]['id']}/recent?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + + game_id = recent["games"][0]["gameId"] + + #members of most recent game id's + player1 = recent["games"][0]["fellowPlayers"][0]["summonerId"] + player2 = recent["games"][0]["fellowPlayers"][1]["summonerId"] + player3 = recent["games"][0]["fellowPlayers"][2]["summonerId"] + player4 = recent["games"][0]["fellowPlayers"][3]["summonerId"] + player5 = recent["games"][0]["fellowPlayers"][4]["summonerId"] + player6 = recent["games"][0]["fellowPlayers"][5]["summonerId"] + player7 = recent["games"][0]["fellowPlayers"][6]["summonerId"] + player8 = recent["games"][0]["fellowPlayers"][7]["summonerId"] + player9 = recent["games"][0]["fellowPlayers"][8]["summonerId"] + + players_by_id = [player1, player2, player3, player4, player5, player6, player7, player8, player9] + + #collect summoner names + memb1 = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/#{player1}/name?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + memb1 = memb1["#{player1}"] + sleep(1); + + memb2 = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/#{player2}/name?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + memb2 = memb2["#{player2}"] + sleep(1); + + memb3 = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/#{player3}/name?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + memb3 = memb3["#{player3}"] + sleep(1); + + memb4 = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/#{player4}/name?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + memb4 = memb4["#{player4}"] + sleep(1); + + memb5 = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/#{player5}/name?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + memb5 = memb5["#{player5}"] + sleep(1); + + memb6 = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/#{player6}/name?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + memb6 = memb6["#{player6}"] + sleep(1); + + memb7 = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/#{player7}/name?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + memb7 = memb7["#{player7}"] + sleep(1); + + memb8 = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/#{player8}/name?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + memb8 = memb8["#{player8}"] + sleep(1); + + memb9 = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/#{player9}/name?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + memb9 = memb9["#{player9}"] + sleep(1); + + memb10 = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/#{id}/name?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + memb10 = memb10["#{id}"] + + players = ["#{memb1}", "#{memb2}", "#{memb3}", "#{memb4}", "#{memb5}", "#{memb6}", "#{memb7}", "#{memb8}", "#{memb9}", "#{memb10}"] + + sleep(5); + + blue = Hash.new + purple = Hash.new + + for i in 0..8 + current_player = players_by_id[i] + place = players[i] + info = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/game/by-summoner/#{current_player}/recent?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + + if 100 == info["games"][0]["stats"]["team"] + blue.merge!("#{place}" => info["games"][0]["stats"]) else - format.html { render action: 'edit' } - format.json { render json: @match.errors, status: :unprocessable_entity } + purple.merge!("#{place}" => info["games"][0]["stats"]) end + sleep(1) + end + + if 100 == recent["games"][0]["stats"]["team"] + blue.merge!("#{players[9]}" => recent["games"][0]["stats"]) + else + purple.merge!("#{players[9]}" => recent["games"][0]["stats"]) + end + + @purp = purple + @blue = blue + + end #end def + + # GET /tournaments/1/matches/1 + # GET /tournaments/1/matches/1.json + def show + if @match.tournament_stage.tournament.game_id == 1 + file_blue = "blue.yaml" + file_purple = "purple.yaml" + @blue2 = YAML.load_file(file_blue) + @purp2 = YAML.load_file(file_purple) end end - # DELETE /matches/1 - # DELETE /matches/1.json - def destroy - @match.destroy - respond_to do |format| - format.html { redirect_to matches_url } - format.json { head :no_content } + # PATCH/PUT /tournaments/1/matches/1 + # PATCH/PUT /tournaments/1/matches/1.json + def update + case params[:update_action] + when "start" + @match.status = 1 + respond_to do |format| + if @match.save + format.html { redirect_to tournament_match_path(@tournament, @match), notice: 'Match has started.' } + format.json { head :no_content } + else + format.html { redirect_to @tournament, notice: "You don't have permission to start this match." } + format.json { render json: "Permission denied", status: :forbidden } + end + end + when "finish" + + #make this use the statistics interface for scoring and ScoringAlgorithms + + + + # Individual scores + scores = params["scores"] + scores.each do |user_name, score| + Statistic.create(user: User.find_by_user_name(user_name), match: @match, name: "score", value: score.to_i) + end + + # Team scores (processing for manual) + team_scores = {} + @match.teams.each do |team| + team_scores[team] = 0 + team.users.each do |user| + team_scores[team] += scores[user.user_name].to_i + end + end + teams = team_scores.invert + @match.winner = teams[teams.keys.sort.last] + + # Schedule next match + #cur_match_num = @tournament.matches_ordered.invert[@match] + #unless cur_match_num == 1 + # @match.winner.matches.push(@tournament.matches_ordered[cur_match_num/2]) + #end + + # Skip peer evaluation if there aren't enough players per team + peer = false + @match.teams.each do |team| + if team.users.count > 2 + peer = true + end + end + @match.status = peer ? 2 : 3 + + respond_to do |format| + if @match.save + format.html { redirect_to tournament_match_path(@tournament, @match), notice: 'Peer evaluation started.' } + format.json { head :no_content } + else + format.html { redirect_to @tournament, notice: "You don't have permission to start this match." } + format.json { render json: "Permission denied", status: :forbidden } + end + end + when "peer" + order = params[:review_action] + base_score = 2 + next_score = 3 + order.split(",").reverse.each do |elem| + player_score = base_score + if @match.winner.user.include?(@current_user) + player_score += 10 + else + player_score += 7 + end + Score.create(user: elem, match: @match, value: player_score ) + base_score = next_score + next_score += base_score + end + @match.submitted_peer_evaluations += 1 + players = []; @match.teams.each{|t| players.concat(t.users.all)} + if (@match.submitted_peer_evaluations == players.count) + @match.status = 3 + end + respond_to do |format| + if @match.save + format.html { redirect_to tournament_match_path(@tournament, @match), notice: 'Scores Submitted' } + format.json { head :no_content } + else + format.html { redirect_to @tournament, notice: "You don't have permission to start this match." } + format.json { render json: "Permission denied", status: :forbidden } + end + end + when "reset" + @match.status = 0 + respond_to do |format| + if @match.save + format.html { redirect_to tournament_match_path(@tournament, @match), notice: 'Match Status Reset to 0' } + format.json { head :no_content } + else + format.html { redirect_to @tournament, notice: "You don't have permission to start this match." } + format.json { render json: "Permission denied", status: :forbidden } + end + end + else + respond_to do |format| + format.html { redirect_to @tournament, notice: "Invalid action", status: :unprocessable_entity } + format.json { render json: @tournament.errors, status: :unprocessable_entity } + end end + end private # Use callbacks to share common setup or constraints between actions. def set_match @match = Match.find(params[:id]) + @tournament = @match.tournament_stage.tournament + end + def set_tournament + @tournament = Tournament.find(params[:tournament_id]) end # Never trust parameters from the scary internet, only allow the white list through. def match_params - params.require(:match).permit(:status, :tournament_stage_id, :winner_id, :remote_id, :submitted_peer_evaluations) + params.require(:match).permit(:status, :tournament_stage_id, :winner_id, :remote_id, :submitted_peer_evaluations, :update_action) + end + + # Turn of check_edit, since our #update is flexible + def check_edit + set_match end end diff --git a/app/controllers/pms_controller.rb b/app/controllers/pms_controller.rb index b62a6ef..1d6540d 100644 --- a/app/controllers/pms_controller.rb +++ b/app/controllers/pms_controller.rb @@ -1,6 +1,4 @@ class PmsController < ApplicationController - before_action :set_pm, only: [:show, :edit, :update, :destroy] - # GET /pms # GET /pms.json def index @@ -25,6 +23,12 @@ class PmsController < ApplicationController # POST /pms.json def create @pm = Pm.new(pm_params) + @pm.author = current_user + require 'pp' + pp @pm.message + @pm.recipient = User.find_by_user_name(pm_params['recipient_id']) + + @pm.author.send_message(@pm.recipient, @pm.message, 'Default') respond_to do |format| if @pm.save diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index ee61487..d312623 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,2 +1,30 @@ class SearchController < ApplicationController + + def go + stringMade = false; + @games = Game.all + @query = params[:query] + @gametype = params[:game_type] + + if ( @gametype.nil? and (@query.nil? or @query.empty?)) then + return + end + + qstring = "" + if (!@query.empty?) + qstring += "name LIKE '%#{@query}%'" + stringMade = true + end + if (!@gametype.nil? and !@gametype.empty?) + if (stringMade) + qstring += " AND " + end + qstring += "game_id=#{@gametype}" + end + + @tournaments = Tournament.where(qstring) + @players = User.where("name LIKE '%#{@query}%'") + + end + end diff --git a/app/controllers/servers_controller.rb b/app/controllers/servers_controller.rb index 4c12c7e..83a9f31 100644 --- a/app/controllers/servers_controller.rb +++ b/app/controllers/servers_controller.rb @@ -1,44 +1,15 @@ class ServersController < ApplicationController - before_action :set_server, only: [:show, :edit, :update, :destroy] - - # GET /servers - # GET /servers.json - def index - @servers = Server.all - end - - # GET /servers/1 - # GET /servers/1.json + # GET /server + # GET /server.json def show end - # GET /servers/new - def new - @server = Server.new - end - - # GET /servers/1/edit + # GET /server/edit def edit end - # POST /servers - # POST /servers.json - def create - @server = Server.new(server_params) - - respond_to do |format| - if @server.save - format.html { redirect_to @server, notice: 'Server was successfully created.' } - format.json { render action: 'show', status: :created, location: @server } - else - format.html { render action: 'new' } - format.json { render json: @server.errors, status: :unprocessable_entity } - end - end - end - - # PATCH/PUT /servers/1 - # PATCH/PUT /servers/1.json + # PATCH/PUT /server + # PATCH/PUT /server.json def update respond_to do |format| if @server.update(server_params) @@ -51,24 +22,15 @@ class ServersController < ApplicationController end end - # DELETE /servers/1 - # DELETE /servers/1.json - def destroy - @server.destroy - respond_to do |format| - format.html { redirect_to servers_url } - format.json { head :no_content } - end - end - private + # Use callbacks to share common setup or constraints between actions. def set_server - @server = Server.find(params[:id]) + @server = Server.first end # Never trust parameters from the scary internet, only allow the white list through. def server_params - params.require(:server).permit(:default_user_permissions) + params.require(:server).permit(:default_user_permissions, :default_user_abilities => User.permission_bits.keys) end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index b035ea0..a0390ad 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,52 +1,27 @@ class SessionsController < ApplicationController - before_action :set_session, only: [:show, :edit, :update, :destroy] - - # GET /sessions - # GET /sessions.json - def index - @sessions = Session.all - end - - # GET /sessions/1 - # GET /sessions/1.json - def show - end # GET /sessions/new def new - @session = Session.new - end - - # GET /sessions/1/edit - def edit + @user = User.new + #@session = Session.new end # POST /sessions # POST /sessions.json def create - @session = Session.new(session_params) + # find the user... + @user = User.find_by_email(params[:session][:username_or_email]) || User.find_by_user_name(params[:session][:username_or_email]) + #@session = Session.new(@user) + # ... and create a new session respond_to do |format| - if @session.save - format.html { redirect_to @session, notice: 'Session was successfully created.' } - format.json { render action: 'show', status: :created, location: @session } + if @user && @user.authenticate(params[:session][:password]) + sign_in @user + format.html { redirect_to root_path } + #format.json { #TODO } else format.html { render action: 'new' } - format.json { render json: @session.errors, status: :unprocessable_entity } - end - end - end - - # PATCH/PUT /sessions/1 - # PATCH/PUT /sessions/1.json - def update - respond_to do |format| - if @session.update(session_params) - format.html { redirect_to @session, notice: 'Session was successfully updated.' } - format.json { head :no_content } - else - format.html { render action: 'edit' } - format.json { render json: @session.errors, status: :unprocessable_entity } + format.json { render json: @user.errors, status: :unprocessable_entity } end end end @@ -54,9 +29,10 @@ class SessionsController < ApplicationController # DELETE /sessions/1 # DELETE /sessions/1.json def destroy - @session.destroy + #@session.destroy + sign_out respond_to do |format| - format.html { redirect_to sessions_url } + format.html { redirect_to root_path } format.json { head :no_content } end end @@ -64,11 +40,16 @@ class SessionsController < ApplicationController private # Use callbacks to share common setup or constraints between actions. def set_session - @session = Session.find(params[:id]) + @token = Session.hash_token(cookies[:remember_token]) + @session = Session.find_by(token: @token) end # Never trust parameters from the scary internet, only allow the white list through. def session_params - params.require(:session).permit(:user_id, :token) + params.require(:session).permit(:session_email, :session_user_name, :session_password) + end + + def is_owner?(object) + object.user == current_user end end diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb index 57b3d91..6abc74c 100644 --- a/app/controllers/teams_controller.rb +++ b/app/controllers/teams_controller.rb @@ -1,5 +1,4 @@ class TeamsController < ApplicationController - before_action :set_team, only: [:show, :edit, :update, :destroy] # GET /teams # GET /teams.json @@ -71,4 +70,8 @@ class TeamsController < ApplicationController def team_params params[:team] end + + def is_owner?(object) + object.users.include?(current_user) + end end diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index 6bf79fd..ebc54c1 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -1,5 +1,4 @@ class TournamentsController < ApplicationController - before_action :set_tournament, only: [:show, :edit, :update, :destroy] # GET /tournaments # GET /tournaments.json @@ -10,24 +9,55 @@ class TournamentsController < ApplicationController # GET /tournaments/1 # GET /tournaments/1.json def show + respond_to do |format| + format.html { + case @tournament.status + when 0 + render action: 'show' + when 1 + #redirect_to tournament_matches_page(@tournament) + redirect_to "/tournaments/" + @tournament.id.to_s + "/matches" + when 2 + redirect_to tournaments_page + end + } + format.json { + data = JSON.parse(@tournament.to_json) + data["players"] = @tournament.players; + render :json => data.to_json + } + end end # GET /tournaments/new def new - @tournament = Tournament.new + @games = Game.all + @tournament = Tournament.new(tournament_attribute_params) end # GET /tournaments/1/edit def edit + check_permission(:edit, @tournament) end # POST /tournaments # POST /tournaments.json def create - @tournament = Tournament.new(tournament_params) - + require 'pp' + puts "----attributes:" + pp tournament_attribute_params + @tournament = Tournament.new(tournament_attribute_params) + @tournament.status = 0 + ok = true + ActiveRecord::Base.transaction do + ok &= @tournament.save + puts "----settings:" + pp tournament_setting_params + ok &= @tournament.update(tournament_setting_params) + ok &= @tournament.hosts.push(current_user) + end respond_to do |format| - if @tournament.save + if ok format.html { redirect_to @tournament, notice: 'Tournament was successfully created.' } format.json { render action: 'show', status: :created, location: @tournament } else @@ -40,12 +70,70 @@ class TournamentsController < ApplicationController # PATCH/PUT /tournaments/1 # PATCH/PUT /tournaments/1.json def update - respond_to do |format| - if @tournament.update(tournament_params) - format.html { redirect_to @tournament, notice: 'Tournament was successfully updated.' } - format.json { head :no_content } - else - format.html { render action: 'edit' } + case params[:update_action] + when nil + check_permission(:edit, @tournament) + ok = true + ActiveRecord::Base.transaction do + ok &= @tournament.update(tournament_attribute_params) + ok &= @tournament.update(tournament_setting_params) + end + respond_to do |format| + if ok + format.html { redirect_to @tournament, notice: 'Tournament was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: 'edit' } + format.json { render json: @tournament.errors, status: :unprocessable_entity } + end + end + when "join" + # permission checking for join is done in the Tournament model + respond_to do |format| + if @tournament.join(current_user) + format.html { redirect_to @tournament, notice: 'You have joined this tournament.' } + format.json { head :no_content } + else + format.html { redirect_to @tournament, notice: "You can't join this tournament." } + format.json { render json: "Permission denied", status: :forbidden } + end + end + when "leave" + respond_to do |format| + if @tournament.leave(current_user) + format.html { redirect_to tournaments_url, notice: 'You have left the tournament.' } + format.json { head :no_content } + else + format.html { redirect_to @tournament, notice: 'You were\'t a part of this tournament.' } + format.json { render json: "Permission denied", status: :forbidden } + end + end + when "start" + check_permission(:edit, @tournament) + respond_to do |format| + if @tournament.status == 0 + @tournament.status = 1 + success = true + ActiveRecord::Base.transaction do + success &= @tournament.save && + success &= @tournament.tournament_stages.create(scheduling: "elimination") + success &= @tournament.tournament_stages.first.create_matches + end + if success + format.html { redirect_to @tournament, notice: 'You have started this tournament.' } + format.json { head :no_content } + else + format.html { redirect_to @tournament, notice: "You don't have permission to start this tournament." } + format.json { render json: "Permission denied", status: :forbidden } + end + else + format.html { redirect_to @tournament, notice: "This tournament is not in a state that it can be started." } + format.json { render json: "Permission denied", status: :forbidden } + end + end + else + respond_to do |format| + format.html { redirect_to @tournament, notice: "Invalid action", status: :unprocessable_entity } format.json { render json: @tournament.errors, status: :unprocessable_entity } end end @@ -64,11 +152,38 @@ class TournamentsController < ApplicationController private # Use callbacks to share common setup or constraints between actions. def set_tournament - @tournament = Tournament.find(params[:id]) + begin + @tournament = Tournament.find(params[:id]) + rescue + redirect_to tournaments_url, notice: 'That tournament no longer exists.' + end end # Never trust parameters from the scary internet, only allow the white list through. - def tournament_params - params.require(:tournament).permit(:game_id, :status, :name, :min_players_per_team, :max_players_per_team, :min_teams_per_match, :max_teams_per_match, :set_rounds, :randomized_teams, :sampling_method) + def tournament_attribute_params + if params[:tournament] + params.require(:tournament).permit(:game_id, :status, :name, :min_players_per_team, :max_players_per_team, :min_teams_per_match, :max_teams_per_match, :set_rounds, :randomized_teams, :sampling_method) + else + return {} + end + end + + def tournament_setting_params + if tournament_attribute_params[:game_id] + game = Game.find(params[:tournament][:game_id]) + params.require(:tournament).permit({:settings => game.settings.collect{|s| s.name}}) + else + return {} + end end + + def is_owner?(object) + object.hosts.include?(current_user) + end + + # Turn of check_edit, since our #update is flexible + def check_edit + set_tournament + end + end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 58bf4c6..27b3c61 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,7 +1,10 @@ class UsersController < ApplicationController - before_action :set_user, only: [:show, :edit, :update, :destroy] + + require 'httparty' + require 'json' # GET /users + # GET /users.json def index @users = User.all @@ -25,13 +28,26 @@ class UsersController < ApplicationController # POST /users.json def create @user = User.new(user_params) + unless (simple_captcha_valid?) + respond_to do |format| + format.html { render action: 'new', status: :unprocessable_entity } + format.json { render json: @user.errors, status: :unprocessable_entity } + end + return + end respond_to do |format| if @user.save - format.html { redirect_to @user, notice: 'User was successfully created.' } + sign_in @user + if @user.id == 1 + # This is the first user, so give them all the power + @user.permissions = 0xFFFFFFFF + @user.save + end + format.html { redirect_to root_path, notice: 'User was successfully created.' } format.json { render action: 'show', status: :created, location: @user } else - format.html { render action: 'new' } + format.html { render action: 'new', status: :unprocessable_entity } format.json { render json: @user.errors, status: :unprocessable_entity } end end @@ -40,8 +56,30 @@ class UsersController < ApplicationController # PATCH/PUT /users/1 # PATCH/PUT /users/1.json def update + ok = true + if params[:user][:remote_usernames].nil? + ok &= @user.update(user_params) + else + params[:user][:remote_usernames].each do |game_name,user_name| + game = Game.find_by_name(game_name) + remote_username = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/by-name/#{user_name.downcase}?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + + id = "#{remote_username["#{user_name.downcase}"]["id"]}".to_i + username = "#{remote_username["#{user_name.downcase}"]["name"]}" + + hash = {:username => username, :id => id} + + remote = @user.remote_usernames.where(:game => game).first + if remote.nil? + ok &= @user.remote_usernames.create(game: game, value: hash) + else + remote.value = hash + ok &= remote.save + end + end + end respond_to do |format| - if @user.update(user_params) + if ok format.html { redirect_to @user, notice: 'User was successfully updated.' } format.json { head :no_content } else @@ -61,14 +99,23 @@ class UsersController < ApplicationController end end + private # Use callbacks to share common setup or constraints between actions. def set_user @user = User.find(params[:id]) end + def is_owner?(object) + object == current_user + end + # Never trust parameters from the scary internet, only allow the white list through. def user_params - params.require(:user).permit(:name, :email, :user_name) + permitted = [ :name, :email, :user_name, :password, :password_confirmation ] + if current_user.can? :edit_permissions + permitted.push(:abilities => User.permission_bits.keys) + end + params.require(:user).permit(permitted) end end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index 309f8b2..499e988 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -1,2 +1,67 @@ +require 'user' + module SessionsHelper + def sign_in(user) + @session = Session.new(user: user) + raw_token = @session.create_token + @session.save # FIXME: error handling + + @token = Session.hash_token(raw_token) + cookies.permanent[:remember_token] = { value: raw_token, expires: 20.minutes.from_now.utc } + + #set the current user to be the given user + @current_user = user + end + + # sets the @current_user instance virable to the user corresponding + # to the remember token, but only if @current_user is undefined + # since the remember token is hashed, we need to hash the cookie + # to find match the remember token + def current_user + @token ||= Session.hash_token(cookies[:remember_token]) + @session ||= Session.find_by(token: @token) + @current_user ||= (@session.nil? ? User::NilUser.new : @session.user) + end + + # checks if someone is currently signed in + def signed_in? + !current_user.nil? + end + + def sign_out + if signed_in? + @session.destroy + end + @current_user = User::NilUser.new + cookies.delete(:remember_token) + end + + # This is for anyone that cares about how long a user is signed + # in: + # + # Currently I have a user to be signed in forever unless they + # log out (cookies.permanent....). + # + # If you want to change that, change line 7 to this: + # + # cookies[:remember_token] = { value: remember_token, + # expires: 20.years.from_now.utc } + # + # which will expire the cookie in 20 years from its date of + # creation. + # + # Oddly enough, this line above is equivalent to the: + # + # cookies.permanent + # + # This is just a short cut for this line since most people + # create permanent cookies these days. + # + # Other times are: + # + # 10.weeks.from_now + # + # 5.days.ago + # + # etc... end diff --git a/app/models/alert.rb b/app/models/alert.rb index 0516355..9876711 100644 --- a/app/models/alert.rb +++ b/app/models/alert.rb @@ -1,3 +1,3 @@ class Alert < ActiveRecord::Base - belongs_to :author + belongs_to :author, class_name: "User" end diff --git a/app/models/game.rb b/app/models/game.rb index 13520ac..5f4c46d 100644 --- a/app/models/game.rb +++ b/app/models/game.rb @@ -1,3 +1,5 @@ class Game < ActiveRecord::Base - belongs_to :parent + belongs_to :parent, class_name: "Game" + has_many :children, class_name: "Game" + has_many :settings, class_name: "GameSetting" end diff --git a/app/models/match.rb b/app/models/match.rb index b5f539b..20a36a5 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -1,4 +1,45 @@ class Match < ActiveRecord::Base belongs_to :tournament_stage - belongs_to :winner + has_many :scores + has_and_belongs_to_many :teams + + belongs_to :winner, class_name: "Team" + + def setup() + + end + + def is_match_over(match, firstPlayer) + #response = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/summoner/by-name/#{firstPlayer}?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + #riot_id = response["#{firstPlayer}"]['id'] + #recent game information + #game_info = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/game/by-summoner/#{riot_id}/recent?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + #first_id = game_info["games"][0]["gameId"] + + count = 0 + while true do + #sleep(5) #wait four minutes + + puts("Every 4 minutes.") + puts("Every 4 minutes.") + count += 1 + #game_info = HTTParty.get("https://prod.api.pvp.net/api/lol/na/v1.3/game/by-summoner/#{riot_id}/recent?api_key=ad539f86-22fd-474d-9279-79a7a296ac38") + #current_id = game_info["games"][0]["gameId"] + + #if current_id != first_id + if count > 2 + puts(count) + #sleep(10) + match.status = 2 + match.save + return true + end + end #while + end + #handle_asynchronously :is_match_over + + def win?(player) + winner.players.include? player + end + end diff --git a/app/models/pm.rb b/app/models/pm.rb index 9fce2b3..3ebb69d 100644 --- a/app/models/pm.rb +++ b/app/models/pm.rb @@ -1,4 +1,14 @@ class Pm < ActiveRecord::Base - belongs_to :author - belongs_to :recipient + belongs_to :author, class_name: "User" + belongs_to :recipient, class_name: "User" + + def name + return current_user.name + end + +=begin + def mailboxer_email(email) + return current_user.email + end +=end end diff --git a/app/models/remote_username.rb b/app/models/remote_username.rb index c477f8a..8c1ce26 100644 --- a/app/models/remote_username.rb +++ b/app/models/remote_username.rb @@ -1,4 +1,16 @@ class RemoteUsername < ActiveRecord::Base belongs_to :game belongs_to :user -end + + def value + begin + return JSON.parse(self.json_value) + rescue + return {} + end + end + + def value=(v) + self.json_value = v.to_json + end +end diff --git a/app/models/server.rb b/app/models/server.rb index 120f0fa..5ba7524 100644 --- a/app/models/server.rb +++ b/app/models/server.rb @@ -1,2 +1,38 @@ class Server < ActiveRecord::Base + def default_user_abilities + @abilities ||= User::Abilities.new(DefaultUser.new(self)) + end + def default_user_abilities=(new) + new.each do |k,v| + if v == "0" + v = false + end + default_user_abilities[k] = v + end + end + class DefaultUser + def initialize(server) + @server = server + end + def can?(action) + bit = User.permission_bits[action] + if bit.nil? + return false + else + return (@server.default_user_permissions & bit != 0) + end + end + def add_ability(action) + bit = User.permission_bits[action.to_sym] + unless bit.nil? + @server.default_user_permissions |= bit + end + end + def remove_ability(action) + bit = User.permission_bits[action.to_sym] + unless bit.nil? + @server.default_user_permissions &= ~ bit + end + end + end end diff --git a/app/models/session.rb b/app/models/session.rb index a5fd26e..f5e642b 100644 --- a/app/models/session.rb +++ b/app/models/session.rb @@ -1,3 +1,42 @@ class Session < ActiveRecord::Base belongs_to :user + + ## + # Create a random remember token for the user. This will be + # changed every time the user creates a new session. + # + # If you want this value, hang on to it; the raw value is + # discarded afterward. + # + # By changing the cookie every new session, any hijacked sessions + # (where the attacker steals a cookie to sign in as a certain + # user) will expire the next time the user signs back in. + # + # The random string is of length 16 composed of A-Z, a-z, 0-9 + # This is the browser's cookie value. + def create_token() + t = SecureRandom.urlsafe_base64 + self.token = Session.hash_token(t) + t + end + + ## + # Encrypt the remember token. + # This is the encrypted version of the cookie stored on + # the database. + # + # The reasoning for storing a hashed token is so that even if + # the database is compromised, the attacker won't be able to use + # the remember tokens to sign in. + def Session.hash_token(token) + # SHA-1 (Secure Hash Algorithm) is a US engineered hash + # function that produces a 20 byte hash value which typically + # forms a hexadecimal number 40 digits long. + # The reason I am not using the Bcrypt algorithm is because + # SHA-1 is much faster and I will be calling this on + # every page a user accesses. + # + # https://en.wikipedia.org/wiki/SHA-1 + Digest::SHA1.hexdigest(token.to_s) + end end diff --git a/app/models/team.rb b/app/models/team.rb index fa7ba9e..77136e7 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -1,2 +1,4 @@ class Team < ActiveRecord::Base + has_and_belongs_to_many :matches + has_and_belongs_to_many :users end diff --git a/app/models/tournament.rb b/app/models/tournament.rb index dcdb8d5..e21ccb1 100644 --- a/app/models/tournament.rb +++ b/app/models/tournament.rb @@ -1,3 +1,89 @@ class Tournament < ActiveRecord::Base belongs_to :game + has_many :tournament_stages + has_many :settings_raw, class_name: "TournamentSetting" + has_and_belongs_to_many :players, class_name: "User", association_foreign_key: "player_id", join_table: "players_tournaments" + has_and_belongs_to_many :hosts, class_name: "User", association_foreign_key: "host_id", join_table: "hosts_tournaments" + + def stages_ordered + h = {} + i = 1 + self.tournament_stages.order(:id).each do |s| + h[i] = s + i += 1 + end + return h + end + + def settings + @settings ||= Settings.new(self) + end + def settings=(setting) + setting.each do |key, value| + value = false if value == "0" + settings[key] = value + end + end + + class Settings + def initialize(tournament) + @tournament = tournament + end + + def [](setting_name) + tournament_setting = @tournament.settings_raw.find_by_name(setting_name) + if tournament_setting.nil? + return nil + else + return tournament_setting.value + end + end + + def []=(setting_name, val) + tournament_setting = @tournament.settings_raw.find_by_name(setting_name) + if tournament_setting.nil? + game_setting = @tournament.game.settings.find_by_name(setting_name) + @tournament.settings_raw.create(name: setting, value: val, + vartype: game_setting.vartype, + type_opt: game_setting.type_opt, + description: game_setting.description, + display_order: game_setting.display_order) + else + tournament_setting.value = val + end + end + + def keys + @tournament.settings_raw.all.collect { |x| x.name } + end + + def method_missing(name, *args) + if name.to_s.ends_with?('=') + self[name.to_s.sub(/=$/, '').to_sym] = args.first + else + return self[name.to_sym] + end + end + end + + def open? + return true + end + + def joinable_by?(user) + return (open? and user.can?(:join_tournament) and !players.include?(user)) + end + + def join(user) + unless joinable_by?(user) + return false + end + players.push(user) + end + + def leave(user) + if players.include?(user) && status == 0 + players.delete(user) + end + end end diff --git a/app/models/tournament_stage.rb b/app/models/tournament_stage.rb index 205c8cc..0775305 100644 --- a/app/models/tournament_stage.rb +++ b/app/models/tournament_stage.rb @@ -1,3 +1,69 @@ class TournamentStage < ActiveRecord::Base belongs_to :tournament + has_many :matches + + def matches_ordered + h = {} + i = 1 + self.matches.order(:id).each do |m| + h[i] = m + i += 1 + end + return h + end + + def create_matches + set_scheduling + @scheduling.create_matches + end + + def to_svg(highlight_user) + set_scheduling + return @scheduling.graph(highlight_user) + end + + def pair + set_pairing + return @pairing.pair(matches, players) + end + + def score + set_scoring + #populating the user scores in the database form what you get from @scoring.score(match, interface) + end + + #populate the statistics interface (with populating method) + def populate + set_populating + #? + end + + private + def set_scheduling + if @scheduling.nil? + @scheduling = "Scheduling::#{self.scheduling.capitalize}".constantize.new(self) + end + return @scheduling + end + + private + def set_pairing + if @pairing.nil? + if(@tournament.randomized_teams) + @pairing = "Pairing::RandomPairing" + #elsif(setTeams) + #@pairing = Pre built + #return @pairing + end + end + return @pairing + end + + private + def set_scoring + end + + private + def set_populating + end end diff --git a/app/models/user.rb b/app/models/user.rb index 4a57cf0..e5ae7ea 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,2 +1,196 @@ class User < ActiveRecord::Base + before_save :default_values + + has_and_belongs_to_many :tournaments_played, class_name: "Tournament", foreign_key: "player_id", join_table: "players_tournaments" + has_and_belongs_to_many :tournaments_hosted, class_name: "Tournament", foreign_key: "host_id", join_table: "hosts_tournaments" + has_and_belongs_to_many :teams + has_many :sessions + has_many :statistics + has_many :remote_usernames + + apply_simple_captcha + + acts_as_messageable + + def mailboxer_email(object) + return nil + end + + before_save { self.email = email.downcase } + before_save { self.user_name = user_name } + + def default_values + self.permissions ||= Server.first.default_user_permissions + end + + def scores + self.statistics.find_by_name(:score) + end + + def find_remote_username(game) + obj = self.remote_usernames.where(:game => game).first + if obj.nil? + if game.parent.nil? + return nil + else + return find_remote_username(game.parent) + end + else + return obj.value + end + end + + def self.permission_bits + return { + :create_tournament => (2**1), + :edit_tournament => (2**2), + :join_tournament => (2**3), + :delete_tournament => (2**4), + + :create_game => (2**5), + :edit_game => (2**6), + :delete_game => (2**7), + + :create_user => (2**8), + :edit_user => (2**9), + :delete_user => (2**10), + + :create_alert => (2**11), + :edit_alert => (2**12), + :delete_alert => (2**13), + + :create_pm => (2**14), + :edit_pm => (2**15), + :delete_pm => (2**16), + + :create_session => (2**17), + :delete_session => (2**18), + + :edit_permissions => (2**19), + :edit_server => (2**20), + } + end + + def can?(action) + bit = User.permission_bits[action] + if bit.nil? + return false + else + return (self.permissions & bit != 0) + end + end + + def add_ability(action) + bit = User.permission_bits[action.to_sym] + unless bit.nil? + self.permissions |= bit + end + end + + def remove_ability(action) + bit = User.permission_bits[action.to_sym] + unless bit.nil? + self.permissions &= ~ bit + end + end + + + # A representation of the permission bits as a mock-array. + def abilities + @abilities ||= Abilities.new(self) + end + def abilities=(new) + new.each do |k,v| + if v == "0" + v = false + end + abilities[k] = v + end + end + + # A thin array-like wrapper around the permission bits to make it + # easy to modify them using a form. + class Abilities + def initialize(user) + @user = user + end + def [](ability) + return @user.can?(ability) + end + def []=(ability, val) + if val + @user.add_ability(ability) + else + @user.remove_ability(ability) + end + end + def keys + User.permission_bits.keys + end + def method_missing(name, *args) + if name.to_s.ends_with?('=') + self[name.to_s.sub(/=$/, '').to_sym] = args.first + else + return self[name.to_sym] + end + end + end + + # VAILD_EMAIL is the regex used to validate a user given email. + VALID_EMAIL_REG = /\A\S+@\S+\.\S+\z/i + + # VALID_USER_NAME checks to make sure a user's user_name + # is in the proper format. + VALID_USER_NAME_REG = /\A[a-zA-Z0-9 _\-]+\z/ + + # The following lines put a user account through a series of + # validations in order to make sure all of their information + # is in the proper format. + # + # validates :symbol_to_be_validated + # + # - presence: determines whether or not a symbol is filled or not + # - length: ensures there is a length limit on the symbol + # - format: checks the format of given information to ensure + # validity + validates(:name, presence: true, length: { maximum: 50 }) + validates(:email, presence: true, format: {with: + VALID_EMAIL_REG}, + uniqueness: { case_sensitive: false }) + validates(:user_name, presence: true, length:{maximum: 50}, + format: {with: VALID_USER_NAME_REG }, + uniqueness: {case_sensitive: false }) + + # Instead of adding password and password_confirmation + # attributes, requiring the presence of a password, + # requiring that pw and pw_com match, and add an authenticate + # method to compare an encrypted password to the + # password_digest to authenticate users, I can just add + # has_secure_password which does all of this for me. + has_secure_password + + validates :password, length: { minimum: 6 } + + class NilUser + def nil? + return true + end + def can?(action) + case action + when :create_user + return true + when :create_session + return true + else + return false + end + end + def method_missing(name, *args) + # Throw an error if User doesn't have this method + super unless User.new.respond_to?(name) + # User has this method -- return a blank value + # 'false' if the method ends with '?'; 'nil' otherwise. + name.to_s.ends_with?('?') ? false : nil + end + end end diff --git a/app/views/alerts/index.html.erb b/app/views/alerts/index.html.erb index 458b951..1d441a2 100644 --- a/app/views/alerts/index.html.erb +++ b/app/views/alerts/index.html.erb @@ -12,14 +12,18 @@ </thead> <tbody> - <% @alerts.each do |alert| %> - <tr> - <td><%= alert.author %></td> - <td><%= alert.message %></td> - <td><%= link_to 'Show', alert %></td> - <td><%= link_to 'Edit', edit_alert_path(alert) %></td> - <td><%= link_to 'Destroy', alert, method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> + <% if !@alerts.nil? %> + <% @alerts.each do |alert| %> + <tr> + <td><%= alert.author %></td> + <td><%= alert.message %></td> + <td><%= link_to 'Show', alert %></td> + <td><%= link_to 'Edit', edit_alert_path(alert) %></td> + <td><%= link_to 'Destroy', alert, method: :delete, data: { confirm: 'Are you sure?' } %></td> + </tr> + <% end %> + <% else %> + <td><p>There are no alerts!</p></td> <% end %> </tbody> </table> diff --git a/app/views/alerts/show.html.erb b/app/views/alerts/show.html.erb index eeab7f7..59d8094 100644 --- a/app/views/alerts/show.html.erb +++ b/app/views/alerts/show.html.erb @@ -1,8 +1,6 @@ -<p id="notice"><%= notice %></p> - <p> <strong>Author:</strong> - <%= @alert.author %> + <%# @alert.author %> </p> <p> diff --git a/app/views/application/.keep b/app/views/application/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/views/application/.keep diff --git a/app/views/common/_error_messages.html.erb b/app/views/common/_error_messages.html.erb new file mode 100644 index 0000000..731f62c --- /dev/null +++ b/app/views/common/_error_messages.html.erb @@ -0,0 +1,11 @@ +<%# http://railscasts.com/episodes/211-validations-in-rails-3 %> +<% if target.errors.any? %> +<div id="errorExplanation"> + <h2><%= pluralize(target.errors.count, "error") %> prohibited this form from being submitted:</h2> + <ul> + <% target.errors.full_messages.each do |msg| %> + <li><%= msg %></li> + <% end %> + </ul> +</div> +<% end %> diff --git a/app/views/common/_show_tournament.html.erb b/app/views/common/_show_tournament.html.erb new file mode 100644 index 0000000..0f60fad --- /dev/null +++ b/app/views/common/_show_tournament.html.erb @@ -0,0 +1,34 @@ +<div class="row tournament-listing"> + <div class="col-md-2 col-sm-3 col-xs-6"><%= image_tag('http://www.gravatar.com/avatar/' + Digest::MD5.hexdigest(target.hosts.first.email) + '?s=100&d=mm', class: "t-image") %> + <p class="t-game"> <%= Game.find(target.game_id).name %></p> + </div> + <div class="col-md-8 col-sm-7 col-xs-6"> + <%# "header" %> + <%= link_to(target) do %><h3><%= target.name %></h3><% end %> + <div class="row" style="margin-left:2%;"> + <div class="col-md-4 host"> + Hosted by: <%= target.hosts.first.name %> + </div> + <div class="col-md-4 things"> + <p> Players per team: <%= target.min_players_per_team %></p> + <p> Players signed up: <%= target.players.count %> </p> + </div> + <div class="col-md-4 things"> + <p> <%= (target.randomized_teams)? "Teams are Random" : "Teams are Chosen" %></p> + <p> Players signed up: <%= target.players.count %> </p> + </div> + </div> + </div> + <div class="col-md-2 col-sm-2 col-xs-2"> + <% if signed_in? %> + <% if !target.players.include?(current_user) %> + <%= form_tag(tournament_path(target), method: "put") do %> + <input type="hidden" name="update_action" value="join"> + <%= submit_tag("Join")%> + <% end %> + <% else %> + <p style="margin-top:10px;"> You've signed up for this tournament! </p> + <% end %> + <% end %> + </div> +</div>
\ No newline at end of file diff --git a/app/views/common/_show_user.html.erb b/app/views/common/_show_user.html.erb new file mode 100644 index 0000000..dd136a0 --- /dev/null +++ b/app/views/common/_show_user.html.erb @@ -0,0 +1,24 @@ +<div class="row user-listing"> + <div class="col-md-3 col-sm-4 col-xs-4"><%= image_tag ('http://www.gravatar.com/avatar/' + Digest::MD5.hexdigest(target.email) + '?s=100&d=mm') %></div> + + + <div class="col-md-9 col-sm-8 col-xs-8"> + <%# "header" %> + <%= link_to(target) do %><h3><%= target.user_name %></h3><% end %> + + <div class="row" style="margin-left:2%;"> + <div class="col-md-6 things"> + <p> Preferred Name: </p> + <p> <%= target.name %></p> + </div> + <div class="col-md-6 things"> + <p>Latest Tournament: + <% if !target.tournaments_played.first.nil? %> + <%= target.tournaments_played.first.name %> + <% end %> + </p> + </div> + </div> + + </div> +</div>
\ No newline at end of file diff --git a/app/views/games/index.html.erb b/app/views/games/index.html.erb index b4c3950..bcd10dd 100644 --- a/app/views/games/index.html.erb +++ b/app/views/games/index.html.erb @@ -1,6 +1,6 @@ <h1>Listing games</h1> -<table> +<table class="table table-hover"> <thead> <tr> <th>Parent</th> @@ -40,4 +40,6 @@ <br> -<%= link_to 'New Game', new_game_path %> + +<%= link_to 'New Game', new_game_path, {:class => "btn btn-warning"} %> + diff --git a/app/views/games/show.html.erb b/app/views/games/show.html.erb index 38c8ccb..1250cbd 100644 --- a/app/views/games/show.html.erb +++ b/app/views/games/show.html.erb @@ -1,5 +1,3 @@ -<p id="notice"><%= notice %></p> - <p> <strong>Parent:</strong> <%= @game.parent %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index cefd1be..dd0ea91 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -4,11 +4,55 @@ <title>Leaguer</title> <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> <%= javascript_include_tag "application", "data-turbolinks-track" => true %> + <%= javascript_include_tag "#{params[:controller]}", "data-turbolinks-track" => true %> + <%= javascript_include_tag "#{params[:controller]}/#{params[:action]}", "data-turbolinks-track" => true %> <%= csrf_meta_tags %> + <%= yield :head %> </head> <body> +<header> + <nav> + <%# This is the logo %> + <div class="navbar-brand"><%= link_to('Leaguer', root_path) %></div> -<%= yield %> + <%# This is the search bar #%> + <div> + <%= form_tag("/search", method: "get", :class => "search") do %> + <%= text_field_tag(:query, params[:query]) %> + <%= submit_tag("Search", :name=>nil) %> + <% end %> + </div> + + <%# these are the log in buttons #%> + <div id="log-buttons"> + <% if signed_in? %> + <%= link_to current_user.user_name, current_user, :class => "user" %> + <%= link_to "Messages", pms_path, :class => "signup" %> + <%= link_to "Sign out", session_path("current"), method: "delete", :class => "signout" %> + <% if current_user.can? :edit_server %> + <%= link_to "Server settings", edit_server_path, :class => "server" %> + <% end %> + <% if current_user.can? :create_alert %> + <%= link_to "Create Alert", new_alert_path, :class => "signin" %> + <% end %> + <% else %> + <%= link_to "Log in", new_session_path, :class => "signin" %> + <%= link_to "Sign up", new_user_path, :class => "signup" %> + <% end %> + </div> + </nav> +</header> + +<% if notice %><div id="notice"><p><%= notice %></p></div><% end %> + +<div class="wrapper"> + <%= yield %> +</div> + +<footer> + <p>Leaguer © 2014, Tomer Kimia, Andrew Murrell, Luke Shumaker, Nathaniel Foy, Davis Webb, and Guntas Grewal</p> + <%= debug(params) if Rails.env.development? %> +</footer> </body> </html> diff --git a/app/views/main/homepage.html.erb b/app/views/main/homepage.html.erb new file mode 100644 index 0000000..3ca8176 --- /dev/null +++ b/app/views/main/homepage.html.erb @@ -0,0 +1,18 @@ +<div role="main" class="container theme-showcase"> + + <div class="jumbotron"> + <h1>Welcome to Leaguer</h1> + <p>This is a tournment management system designed to be used for any team sport. Our peer review system ensures that the best players move on to the next round! Try creating a new tournament and having people sign up for it. </p> + <p id="jumbo-buttons"> + <% if !signed_in? %> + <%= link_to 'Log In', new_session_path, :class => "btn btn-warning btn-lg", :role => "button" %> + <%= link_to 'Sign Up', new_user_path, :class => "btn btn-warning btn-lg", :role => "button" %> + <% else %> + <%= link_to 'Start a Tournament', new_tournament_path, :class => "btn btn-warning btn-lg", :role => "button" %> + <% end %> + <%= link_to 'See Ongoing Tournaments', tournaments_path, :class => "btn btn-warning btn-lg", :role => "button" %> + + </p> + </div> + + </div> diff --git a/app/views/matches/_form.html.erb b/app/views/matches/_form.html.erb index 9e09fd8..9d402b3 100644 --- a/app/views/matches/_form.html.erb +++ b/app/views/matches/_form.html.erb @@ -1,16 +1,5 @@ -<%= form_for(@match) do |f| %> - <% if @match.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@match.errors.count, "error") %> prohibited this match from being saved:</h2> - - <ul> - <% @match.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> - <% end %> - +<%= form_for([@tournament, @tournament.matches.build]) do |f| %> + <div class="field"> <%= f.label :status %><br> <%= f.number_field :status %> diff --git a/app/views/matches/index.html.erb b/app/views/matches/index.html.erb index 766c3e8..8a50fee 100644 --- a/app/views/matches/index.html.erb +++ b/app/views/matches/index.html.erb @@ -1,35 +1,41 @@ -<h1>Listing matches</h1> +<h1><%= @tournament.name %> - Matches</h1> -<table> - <thead> - <tr> - <th>Status</th> - <th>Tournament stage</th> - <th>Winner</th> - <th>Remote</th> - <th>Submitted peer evaluations</th> - <th></th> - <th></th> - <th></th> - </tr> - </thead> - <tbody> - <% @matches.each do |match| %> - <tr> - <td><%= match.status %></td> - <td><%= match.tournament_stage %></td> - <td><%= match.winner %></td> - <td><%= match.remote_id %></td> - <td><%= match.submitted_peer_evaluations %></td> - <td><%= link_to 'Show', match %></td> - <td><%= link_to 'Edit', edit_match_path(match) %></td> - <td><%= link_to 'Destroy', match, method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> - <% end %> - </tbody> +<table id="matches-table" class="table"> + <thead> + <tr> + <th>Name</th> + <th>Status</th> + <th>Winner</th> + <th><!-- link to --></th> + <th><!-- start button --></th> + </tr> + </thead> + <tbody> + <% @tournament.stages_ordered.keys.sort.each do |stage_key| %> + <% stage = @tournament.stages_ordered[stage_key] %> + <% stage.matches_ordered.keys.sort.reverse.each do |match_key| %><tr> + <% match = stage.matches_ordered[match_key] %> + <td><%= "Match #{match.id}" %></td> + <td><%= match.status %></td> + <td><%= (match.winner.nil? ? "-" : "Team #{match.winner.id}") %></td> + <td><%= link_to "Show", tournament_match_path(@tournament, match) %> + <td> <%# If user is the host, let them start the tournment %> + <% if @tournament.hosts.include?(current_user) %> + <%= form_tag(tournament_match_path(@tournament, match), method: "put") do %> + <input type="hidden" name="update_action" value="start"> + <% @startable = (match.status == 0) and (match.teams.count >= @tournament.min_teams_per_match) %> + <%= submit_tag("Start Match", :disabled => ! @startable) %> + <% end %> + <% end %> + </td> + </tr><% end %> + <% end %> + </tbody> </table> <br> -<%= link_to 'New Match', new_match_path %> +<% @tournament.stages_ordered.keys.sort.each do |stage_key| %> + <div class="graph"><%= raw @tournament.stages_ordered[stage_key].to_svg(current_user) %></div> +<% end %> diff --git a/app/views/matches/new.html.erb b/app/views/matches/new.html.erb index bd4c78c..74e7e3a 100644 --- a/app/views/matches/new.html.erb +++ b/app/views/matches/new.html.erb @@ -1,5 +1,3 @@ <h1>New match</h1> <%= render 'form' %> - -<%= link_to 'Back', matches_path %> diff --git a/app/views/matches/show.html.erb b/app/views/matches/show.html.erb index 8384ff2..e1fe29e 100644 --- a/app/views/matches/show.html.erb +++ b/app/views/matches/show.html.erb @@ -1,29 +1,130 @@ -<p id="notice"><%= notice %></p> +<script type="text/javascript"> +function score_peers() { + //get each player in order and assign score here! + var $lisp = $('ol#boxes'); + var comma = "," + for(var i=0; i < $lisp.length; i++) { + if ( i == lisp.length-1) { + comma = ""; + } + $('review_action').value += $('ol#boxes:eq(' + i + ')').text() + comma; + } +} +</script> <p> - <strong>Status:</strong> - <%= @match.status %> + <strong>Status:</strong> + <%= @match.status %> </p> - <p> - <strong>Tournament stage:</strong> - <%= @match.tournament_stage %> + <strong>Tournament stage:</strong> + <%= @match.tournament_stage %> </p> -<p> - <strong>Winner:</strong> - <%= @match.winner %> -</p> +<!-- + Match Status 0 => Created, waiting to start + Match Status 1 => Match ready + Match Status 2 => Match is running, waiting to finish + Match Status 3 => Match finished, waiting for statistics to populate + Match Status 4 => Match Totally done. Archived. -<p> - <strong>Remote:</strong> - <%= @match.remote_id %> -</p> + Four views:- (status is Match status) + A. Pairings, when status is 1 for either Host or Player Or when status is 2 for player + B. A page the host will see if status is 2 OR 3 + C. The Peer review page that the players will see if status is 3. + D. The page everyone will see when status is 4. -<p> - <strong>Submitted peer evaluations:</strong> - <%= @match.submitted_peer_evaluations %> -</p> + Note:- The change of status from 2 to 3 for League of Legends is coming from League Data Pull (RIOT API) + +--> + +<div> + <h2>Teams/users</h2> + <ul> + <% @match.teams.each do |team| %> + <li>Team <%= team.id %><ul> + <% team.users.each do |user| %> + <% if @match.status <= 1 %> + <li><%= user.user_name %></li> + <% else %> + <li><%= user.user_name %> - SCORE: <%= Statistic.where(:name => "score", :user => user, :match => @match).first.value %></li> + <% end %> + <% end %> + </ul></li> + <% end %> + </ul> +</div> + +<% unless @match.winner.nil? %> + <p> + <strong>Winner:</strong> + <%= @match.winner.users.collect{|u| u.user_name}.join(", ") %> + </p> +<% end %> -<%= link_to 'Edit', edit_match_path(@match) %> | -<%= link_to 'Back', matches_path %> +<div id="action"> + <%= form_tag(tournament_match_path(@tournament, @match), method: "put") do %> + <% case @match.status %> + <% when 0 %> + <!-- Created, waiting to start --> + <% if @tournament.hosts.include? current_user %> + <input type="hidden" name="update_action" value="start"> + <%= submit_tag("Start Match", :disabled => @match.teams.count < @tournament.min_teams_per_match) %> + <% else %> + <p>Match is waiting to start.</p> + <% end %> + <% when 1 %> + <!-- Started, waiting to finish --> + <!-- This will depend on the Sampling Method Eventually instead of always being Manual --> + <% if @tournament.hosts.include? current_user %> + <input type="hidden" name="update_action" value="finish"> + <% @match.teams.each do |team| %> + <fieldset><legend>Team <%= team.id.to_s %></legend> + <% team.users.collect{|u| u.user_name}.each do |k| %><label> + Score for <%= k %><br> + <% @player_score = 0 %> + <% current_user.statistics.where(:match => @match, :user => current_user).each{ |s| @player_score+=s.value } %> + <%= text_field_tag("scores[#{k}]", @player_score, size: 3) %> + </label><% end %> + </fieldset> + <% end %> + <%= submit_tag("Finish match") %> + <% else %> + <p>The match is running; the host has yet to post the scores of the match.</p> + <% end %> + <% when 2 %> + <!-- Finished, waiting for peer reviews --> + <input type="hidden" name="update_action" value="peer"> + <input type="hidden" name="review_action" value=""> + <% users = []; @match.teams.each{|t| users.concat(t.users)}; %> + <% if users.include? current_user %> + <% @match.teams.each do |team| %> + <% if team.users.include?(current_user) %> + <ol id="boxes" class="sortable"> + <% team.users.reject{ |u| (u.user_name == @current_user.user_name) }.collect {|u| u.user_name }.each do |k| %> + <li><%= k%> + <br> + <% if (@tournament.game_id == 1) %> + <%= if @blue2["#{k}"] == nil + "Level: #{@purp2["#{k}"]["level"]} K/D/A: #{@purp2["#{k}"]["championsKilled"]}/#{@purp2["#{k}"]["numDeaths"]}/#{@purp2["#{k}"]["assists"]} Gold:#{@purp2["#{k}"]["goldEarned"]}" + else + "Level: #{@blue2["#{k}"]["level"]} K/D/A: #{@blue2["#{k}"]["championsKilled"]}/#{@blue2["#{k}"]["numDeaths"]}/#{@blue2["#{k}"]["assists"]} Gold:#{@blue2["#{k}"]["goldEarned"]}" + end %> + <% end %> + </li> + <% end %> + </ol> + <% end %> + <% end %> + <%= submit_tag("Submit peer evaluation", :onsubmit => "score_peers()") %> + <% else %> + Waiting for peer evaluations to be submitted. + <% end %> + <% when 3 %> + <!-- Totally done --> + This match is done. + <input type="hidden" name="update_action" value="reset"> + <%= submit_tag("Reset Status") %> + <% end # case %> + <% end # form %> +</div> diff --git a/app/views/pms/_form.html.erb b/app/views/pms/_form.html.erb index 480e308..e9a7c08 100644 --- a/app/views/pms/_form.html.erb +++ b/app/views/pms/_form.html.erb @@ -12,10 +12,6 @@ <% end %> <div class="field"> - <%= f.label :author_id %><br> - <%= f.text_field :author_id %> - </div> - <div class="field"> <%= f.label :recipient_id %><br> <%= f.text_field :recipient_id %> </div> diff --git a/app/views/pms/index.html.erb b/app/views/pms/index.html.erb index cb7fe4b..1175cb2 100644 --- a/app/views/pms/index.html.erb +++ b/app/views/pms/index.html.erb @@ -13,16 +13,106 @@ </thead> <tbody> - <% @pms.each do |pm| %> + + <tr> + <td><h2>Inbox<h2></td> + </tr> + + <% message = @pms.where(recipient: current_user) %> + <% unless message.empty? then message.each do |pm| %> + <tr> + <td><%= pm.author.user_name %></td> + <td><%= pm.recipient.user_name %></td> + <td><%= pm.message %></td> + <td><%= link_to 'Show', pm %></td> + <td><%# link_to 'Edit', edit_pm_path(pm) %></td> + <td><%= link_to 'Delete', pm, method: :delete, data: { confirm: 'Are you sure (also deletes the author\'s copy)?' } %></td> + </tr> + <% end %> + <% else %> + <td><h3>No New Messages</h3></td> + <% end %> + + <tr> + <td><h2>Outbox<h2></td> + </tr> + + <% message = @pms.where(author: current_user) %> + <% unless message.empty? then message.each do |pm| %> <tr> - <td><%= pm.author %></td> - <td><%= pm.recipient %></td> + <td><%= pm.author.user_name %></td> + <td><%= pm.recipient.user_name %></td> <td><%= pm.message %></td> <td><%= link_to 'Show', pm %></td> - <td><%= link_to 'Edit', edit_pm_path(pm) %></td> - <td><%= link_to 'Destroy', pm, method: :delete, data: { confirm: 'Are you sure?' } %></td> + <td><%# link_to 'Edit', edit_pm_path(pm) %></td> + <td><%= link_to 'Delete', pm, method: :delete, data: { confirm: 'Are you sure (also deletes the recipient\'s copy)?'} %></td> </tr> + <% end %> + <% else %> + <td><h3>No New Messages</h3></td> <% end %> + + <tr> + <td><h2>Conversations<h2></td> + </tr> + <tr> + <td><h3>Inbox<h3></td> + </tr> + <tr> + <% conversations = current_user.mailbox.inbox %> + <% if !conversations.nil? %> + <tr> + <td><b>From</b></td> + <td><b>Subject</b></td> + <td><b>Body</b></td> + </tr> + <% conversations.each do |conversation| %> + <% receipts = conversation.receipts_for current_user %> + <% receipts.each do |receipt| %> + <% message = receipt.message %> + <tr> + <td><%= conversation.last_sender.user_name %></td> + <td><%= message.subject %></td> + <td><%= message.body %></td> + <td><%# link_to 'Show', pm %></td> + </tr> + <% end %> + <% end %> + + <% else %> + <td><p> No Messages </p></td> + <% end %> + </tr> + + <tr> + <td><h3>Outbox<h3></td> + </tr> + <tr> + <% conversations = current_user.mailbox.sentbox %> + <% if !conversations.nil? %> + <tr> + <td><b>To</b></td> + <td><b>Subject</b></td> + <td><b>Body</b></td> + </tr> + <% conversations.each do |conversation| %> + <% receipts = conversation.receipts_for current_user %> + <% receipts.each do |receipt| %> + <% message = receipt.message %> + <tr> + <td>Doesn't work</td> + <td><%= message.subject %></td> + <td><%= message.body %></td> + <td><%# link_to 'Show', conversation %></td> + </tr> + <% end %> + <% end %> + + <% else %> + <td><p> No Messages </p></td> + <% end %> + </tr> + </tbody> </table> diff --git a/app/views/pms/show.html.erb b/app/views/pms/show.html.erb index 5ee483f..cfaf00d 100644 --- a/app/views/pms/show.html.erb +++ b/app/views/pms/show.html.erb @@ -1,19 +1,17 @@ -<p id="notice"><%= notice %></p> - <p> <strong>Author:</strong> - <%= @pm.author %> + <%= @conversation.last_sender.user_name %> </p> <p> <strong>Recipient:</strong> - <%= @pm.recipient %> + <%#@pm.recipient.user_name %> </p> <p> <strong>Message:</strong> - <%= @pm.message %> + <%# @pm.message %> </p> -<%= link_to 'Edit', edit_pm_path(@pm) %> | +<%# link_to 'Edit', edit_pm_path(@pm) %> | <%= link_to 'Back', pms_path %> diff --git a/app/views/search/go.html.erb b/app/views/search/go.html.erb new file mode 100644 index 0000000..ea2dabf --- /dev/null +++ b/app/views/search/go.html.erb @@ -0,0 +1,45 @@ +<div id="advanced_search" class="collapsible"> + <div class="collapsed"> + <h5><a href="#advanced_search">Advanced Search [show]</a></h5> + </div> + <div class="expanded"> + <h5><a href="#collapse">Advanced Search [hide]</a></h5> + <%= form_tag("/search", method: "get") do %> + <div class="form-group"> + <%= label_tag :query, 'Find:' %> + <%= text_field_tag(:query, params[:query]) %> + </div> + <div class="form-group"> + <%= label_tag :game_type, 'Game Type:' %> + <%= select_tag(:game_type, options_from_collection_for_select(@games, 'id', 'name'), :prompt => 'All Games') %> + </div> + <div> + <%= submit_tag("Search", :name=>nil) %> + </div> + <% end %> + </div> +</div> + +<%# Show search results if a query was not nill %> +<% if !@query.nil? and !@query.empty? %> + + <% if @tournaments.empty? and @players.empty? %> + <h3> No results found for "<%= @query %>" </h3> + <% else %> + <h3> Showing results for: <span><%= @query %></span></h3> + <% if @tournaments.length > 0 %> + <h4> Tournaments </h4> + <% end %> + <% @tournaments.each do |t| %> + <%= render "common/show_tournament", :target => t %> + <% end %> + + <% if @players.length > 0 %> + <h4> Players </h4> + <% end %> + <% @players.each do |p| %> + <%= render "common/show_user", :target => p %> + <% end %> + + <% end %> +<% end %> diff --git a/app/views/servers/_form.html.erb b/app/views/servers/_form.html.erb index 6211f9a..1afde11 100644 --- a/app/views/servers/_form.html.erb +++ b/app/views/servers/_form.html.erb @@ -1,20 +1,17 @@ <%= form_for(@server) do |f| %> - <% if @server.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@server.errors.count, "error") %> prohibited this server from being saved:</h2> + <%= render "common/error_messages", :target => @server %> - <ul> - <% @server.errors.full_messages.each do |msg| %> - <li><%= msg %></li> + <fieldset> + <legend>Default permissions for new users</legend> + <ul> + <%= fields_for "server[default_user_abilities]", @server.default_user_abilities do |a| %> + <% @server.default_user_abilities.keys.each do |ability| %> + <li><label><%= a.check_box(ability) %> <%= ability.to_s.humanize %></label></li> <% end %> - </ul> - </div> - <% end %> + <% end %> + </ul> + </fieldset> - <div class="field"> - <%= f.label :default_user_permissions %><br> - <%= f.number_field :default_user_permissions %> - </div> <div class="actions"> <%= f.submit %> </div> diff --git a/app/views/servers/edit.html.erb b/app/views/servers/edit.html.erb index a92cdb5..f29a65c 100644 --- a/app/views/servers/edit.html.erb +++ b/app/views/servers/edit.html.erb @@ -1,6 +1,3 @@ <h1>Editing server</h1> <%= render 'form' %> - -<%= link_to 'Show', @server %> | -<%= link_to 'Back', servers_path %> diff --git a/app/views/servers/index.html.erb b/app/views/servers/index.html.erb deleted file mode 100644 index b3064f4..0000000 --- a/app/views/servers/index.html.erb +++ /dev/null @@ -1,27 +0,0 @@ -<h1>Listing servers</h1> - -<table> - <thead> - <tr> - <th>Default user permissions</th> - <th></th> - <th></th> - <th></th> - </tr> - </thead> - - <tbody> - <% @servers.each do |server| %> - <tr> - <td><%= server.default_user_permissions %></td> - <td><%= link_to 'Show', server %></td> - <td><%= link_to 'Edit', edit_server_path(server) %></td> - <td><%= link_to 'Destroy', server, method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> - <% end %> - </tbody> -</table> - -<br> - -<%= link_to 'New Server', new_server_path %> diff --git a/app/views/servers/index.json.jbuilder b/app/views/servers/index.json.jbuilder deleted file mode 100644 index 3c9df60..0000000 --- a/app/views/servers/index.json.jbuilder +++ /dev/null @@ -1,4 +0,0 @@ -json.array!(@servers) do |server| - json.extract! server, :id, :default_user_permissions - json.url server_url(server, format: :json) -end diff --git a/app/views/servers/new.html.erb b/app/views/servers/new.html.erb deleted file mode 100644 index 0422009..0000000 --- a/app/views/servers/new.html.erb +++ /dev/null @@ -1,5 +0,0 @@ -<h1>New server</h1> - -<%= render 'form' %> - -<%= link_to 'Back', servers_path %> diff --git a/app/views/servers/show.html.erb b/app/views/servers/show.html.erb index b18f09f..54aaf66 100644 --- a/app/views/servers/show.html.erb +++ b/app/views/servers/show.html.erb @@ -1,9 +1,6 @@ -<p id="notice"><%= notice %></p> - <p> <strong>Default user permissions:</strong> <%= @server.default_user_permissions %> </p> -<%= link_to 'Edit', edit_server_path(@server) %> | -<%= link_to 'Back', servers_path %> +<%= link_to 'Edit', edit_server_path %> diff --git a/app/views/sessions/_form.html.erb b/app/views/sessions/_form.html.erb deleted file mode 100644 index 90ad0ad..0000000 --- a/app/views/sessions/_form.html.erb +++ /dev/null @@ -1,25 +0,0 @@ -<%= form_for(@session) do |f| %> - <% if @session.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@session.errors.count, "error") %> prohibited this session from being saved:</h2> - - <ul> - <% @session.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> - <% end %> - - <div class="field"> - <%= f.label :user_id %><br> - <%= f.text_field :user_id %> - </div> - <div class="field"> - <%= f.label :token %><br> - <%= f.text_field :token %> - </div> - <div class="actions"> - <%= f.submit %> - </div> -<% end %> diff --git a/app/views/sessions/edit.html.erb b/app/views/sessions/edit.html.erb deleted file mode 100644 index bbd8407..0000000 --- a/app/views/sessions/edit.html.erb +++ /dev/null @@ -1,6 +0,0 @@ -<h1>Editing session</h1> - -<%= render 'form' %> - -<%= link_to 'Show', @session %> | -<%= link_to 'Back', sessions_path %> diff --git a/app/views/sessions/index.html.erb b/app/views/sessions/index.html.erb deleted file mode 100644 index 43a7e1f..0000000 --- a/app/views/sessions/index.html.erb +++ /dev/null @@ -1,29 +0,0 @@ -<h1>Listing sessions</h1> - -<table> - <thead> - <tr> - <th>User</th> - <th>Token</th> - <th></th> - <th></th> - <th></th> - </tr> - </thead> - - <tbody> - <% @sessions.each do |session| %> - <tr> - <td><%= session.user %></td> - <td><%= session.token %></td> - <td><%= link_to 'Show', session %></td> - <td><%= link_to 'Edit', edit_session_path(session) %></td> - <td><%= link_to 'Destroy', session, method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> - <% end %> - </tbody> -</table> - -<br> - -<%= link_to 'New Session', new_session_path %> diff --git a/app/views/sessions/index.json.jbuilder b/app/views/sessions/index.json.jbuilder deleted file mode 100644 index 5205ede..0000000 --- a/app/views/sessions/index.json.jbuilder +++ /dev/null @@ -1,4 +0,0 @@ -json.array!(@sessions) do |session| - json.extract! session, :id, :user_id, :token - json.url session_url(session, format: :json) -end diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb index 55c9eca..ff27762 100644 --- a/app/views/sessions/new.html.erb +++ b/app/views/sessions/new.html.erb @@ -1,5 +1,23 @@ -<h1>New session</h1> +<h1>Sign in</h1> +<% if @user.nil? %> + <p class="errors"> The email/username or password is incorrect. Verify that CAPS LOCK is not on, and then retype the current email/username and password. </p> +<% end %> -<%= render 'form' %> + <div class="span6 offset3"> + <%= form_for(:session, url: sessions_path) do |f| %> + <p> + <%= f.label(:username_or_email, "Username/Email") %><br/> + <%= f.text_field :username_or_email%> + </p> + <p> + <%= f.label :password %><br/> + <%= f.password_field :password %> + </p> + <p> + <%= f.submit "Log in", class: "signin" %> + </p> + <% end %> + + <p>New user? <%= link_to("Sign up now!", new_user_path) %></p> + </div> -<%= link_to 'Back', sessions_path %> diff --git a/app/views/sessions/show.html.erb b/app/views/sessions/show.html.erb deleted file mode 100644 index 230e6bd..0000000 --- a/app/views/sessions/show.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -<p id="notice"><%= notice %></p> - -<p> - <strong>User:</strong> - <%= @session.user %> -</p> - -<p> - <strong>Token:</strong> - <%= @session.token %> -</p> - -<%= link_to 'Edit', edit_session_path(@session) %> | -<%= link_to 'Back', sessions_path %> diff --git a/app/views/sessions/show.json.jbuilder b/app/views/sessions/show.json.jbuilder deleted file mode 100644 index c9efd3b..0000000 --- a/app/views/sessions/show.json.jbuilder +++ /dev/null @@ -1 +0,0 @@ -json.extract! @session, :id, :user_id, :token, :created_at, :updated_at diff --git a/app/views/tournaments/_selected.html.erb b/app/views/tournaments/_selected.html.erb new file mode 100644 index 0000000..9240c49 --- /dev/null +++ b/app/views/tournaments/_selected.html.erb @@ -0,0 +1,53 @@ +<%= form_for(@tournament) do |f| %> + <%= render "common/error_messages", :target => @tournament %> + <%= f.hidden_field(:game_id) %> + + <% @tournament.attributes.each do |name, value| %> + <% if (name == "id") or (name =~ /.*_at$/) or (name == "game_id") or (name == "status") or (name == "set_rounds") %> + <% next %> + <% end %> + <p> + <%= f.label name %><br> + <% unless @tournament.game.attributes[name].nil? %> + <% if name == "sampling_method" %> + <%= f.select( name, @tournament.game.sampling_method.split(',') ) %> + <% else %> + <%= f.text_field(name, :value => @tournament.game.attributes[name] ) %> + <% end %> + <% else %> + <%= f.text_field name %> + <% end %> + </p> + <% end %> + + <%= fields_for "tournament[settings]", @tournament.settings do |setting_fields| %> + <% @tournament.game.settings.each do |setting| %> + <p> + <%= setting_fields.label setting.name %> + <br> + <% case setting.vartype %> + <% when 0 %> + <%= setting_fields.text_field( setting.name ) %> + <% when 1 %> + <%= setting_fields.text_area( setting.name ) %> + <% when 2 %> + <% setting.type_opt.split(',').each do |option|%> + <%= setting_fields.radio_button( setting.name, option ) %> <%= option %> <br> + <% end %> + <% when 3 %> + <% setting.type_opt.split(',').each do |option|%> + <%= check_box_tag(setting.name, value = option, checked = false, options = {}) %> <%= option %> <br> + <% end %> + <% when 4 %> + <%# setting_fields.label "true" %> + <%= setting_fields.radio_button( setting.name, "true" ) %> True + <%# setting_fields.label "false" %> + <%= setting_fields.radio_button( setting.name, "false" ) %> False + <% when 5 %> + <%= setting_fields.select( setting.name, setting.type_opt.split(',') ) %> + <% end %> + <% end %> + </p> + <% end %> + <%= f.submit %> +<% end %> diff --git a/app/views/tournaments/index.html.erb b/app/views/tournaments/index.html.erb index 49a83ec..06e1b25 100644 --- a/app/views/tournaments/index.html.erb +++ b/app/views/tournaments/index.html.erb @@ -1,45 +1,24 @@ -<h1>Listing tournaments</h1> +<h1>Listing Tournaments</h1> -<table> - <thead> - <tr> - <th>Game</th> - <th>Status</th> - <th>Name</th> - <th>Min players per team</th> - <th>Max players per team</th> - <th>Min teams per match</th> - <th>Max teams per match</th> - <th>Set rounds</th> - <th>Randomized teams</th> - <th>Sampling method</th> - <th></th> - <th></th> - <th></th> - </tr> - </thead> +<div id="tournament-list"> + <% if @tournaments.length > 0 %> - <tbody> - <% @tournaments.each do |tournament| %> - <tr> - <td><%= tournament.game %></td> - <td><%= tournament.status %></td> - <td><%= tournament.name %></td> - <td><%= tournament.min_players_per_team %></td> - <td><%= tournament.max_players_per_team %></td> - <td><%= tournament.min_teams_per_match %></td> - <td><%= tournament.max_teams_per_match %></td> - <td><%= tournament.set_rounds %></td> - <td><%= tournament.randomized_teams %></td> - <td><%= tournament.sampling_method %></td> - <td><%= link_to 'Show', tournament %></td> - <td><%= link_to 'Edit', edit_tournament_path(tournament) %></td> - <td><%= link_to 'Destroy', tournament, method: :delete, data: { confirm: 'Are you sure?' } %></td> - </tr> - <% end %> - </tbody> -</table> + <%# Each tournament has a div for its listing %> + <% @tournaments.each do |t| %> + <%= render "common/show_tournament", :target => t %> + <% end %> -<br> + <% else %> -<%= link_to 'New Tournament', new_tournament_path %> + <p class="no-entries"> No tournaments going on right now... + <% if current_user.can?(:create_tournament) %> + Why not start your own? + <% end %> + </p> + + <% end %> +</div> + +<% if current_user.can?(:create_tournament) %> + <div><%= link_to 'New Tournament', new_tournament_path, :class => "btn btn-warning btn-lg" %></div> +<% end %> diff --git a/app/views/tournaments/join.html.erb b/app/views/tournaments/join.html.erb new file mode 100644 index 0000000..1d38d68 --- /dev/null +++ b/app/views/tournaments/join.html.erb @@ -0,0 +1,2 @@ + <%= @user.name %> + diff --git a/app/views/tournaments/new.html.erb b/app/views/tournaments/new.html.erb index 2a60539..2837708 100644 --- a/app/views/tournaments/new.html.erb +++ b/app/views/tournaments/new.html.erb @@ -1,5 +1,16 @@ -<h1>New tournament</h1> +<h1>New Tournament</h1> -<%= render 'form' %> +<%= form_tag(new_tournament_path, method: "get") do %> + <%= select_tag('tournament[game_id]', + options_from_collection_for_select(@games, 'id', 'name', @tournament.game.nil? || @tournament.game.id), + :prompt => "Select a Game Type") %> + <%= submit_tag("Select", :class => "btn") %> +<% end %> + +<div id='ajax-form'> + <% if not @tournament.game.nil? %> + <%= render 'selected' %> + <% end %> +</div> <%= link_to 'Back', tournaments_path %> diff --git a/app/views/tournaments/show.html.erb b/app/views/tournaments/show.html.erb index d97e504..a5eb46c 100644 --- a/app/views/tournaments/show.html.erb +++ b/app/views/tournaments/show.html.erb @@ -1,13 +1,23 @@ -<p id="notice"><%= notice %></p> +<h2 id="tournament-name"> + <%= @tournament.name %> +</h2> -<p> - <strong>Game:</strong> - <%= @tournament.game %> -</p> +<div class="progress"> + <%= tag("div", {:id => "prog-bar", :class => "progress-bar progress-bar-warning", :style => "width: " +(@tournament.players.count * 100 / (@tournament.min_players_per_team * @tournament.min_teams_per_match)).to_s + "%", "aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => (@tournament.players.count * 100 / (@tournament.min_players_per_team * @tournament.min_teams_per_match)).to_s, "role" => "progressbar"}) %> + <span class="sr-only">60% Complete (warning)</span> + </div> +</div> +<p id="players-needed"><%= pluralize(@tournament.players.count, "player has", "players have") %> signed up. <%= @tournament.min_players_per_team * @tournament.min_teams_per_match %> needed. </p> + +<span id="tournament-side-params"> <p> <strong>Status:</strong> - <%= @tournament.status %> + <% if @tournament.status == 0 %> + Waiting for players... + <% else %> + Started + <% end %> </p> <p> @@ -35,10 +45,6 @@ <%= @tournament.max_teams_per_match %> </p> -<p> - <strong>Set rounds:</strong> - <%= @tournament.set_rounds %> -</p> <p> <strong>Randomized teams:</strong> @@ -47,8 +53,63 @@ <p> <strong>Sampling method:</strong> + <!-- TODO --> <%= @tournament.sampling_method %> </p> -<%= link_to 'Edit', edit_tournament_path(@tournament) %> | -<%= link_to 'Back', tournaments_path %> +<% @tournament.settings.each do |setting| %> +<p> + <strong><%= setting.name %></strong> + <%= setting.value %> +</p> +<% end %> + +</span> + +<div > +<%# Show all players in the tournament %> +<% if @tournament.players.length > 0 %> +<h3> Players Here: </h3> + <ul id="tournament-users"> + <% @tournament.players.each do |p| %> + <li><span class="black"> <%= p.user_name %> </span> </li> + <% end %> + </ul> + <% else %> + <h3 div="players-needed">Hmmm.... nobody's here yet! You and your friends should join the tournament.</h3> +<% end %> + +<div class="leave-buttons"> +<%# If user can join, and user hasn't joined already, show the join tournment tag %> +<% if @tournament.joinable_by?(current_user) && !@tournament.players.include?(current_user) %> + <%= form_tag(tournament_path(@tournament), method: "put") do %> + <input type="hidden" name="update_action" value="join"> + <%= submit_tag("Join Tournament") %> + <% end %> + +<% elsif @tournament.players.include?(current_user) %> + <%= form_tag(tournament_path(@tournament), method: "put") do %> + <input type="hidden" name="update_action" value="leave"> + <%= submit_tag("Leave Tournament") %> + <% end %> +<% end %> + +<%# If user is the host, let them start the tournment %> +<% if @tournament.hosts.include?(current_user) %> + + <%= form_tag(tournament_path(@tournament), method: "put") do %> + <input type="hidden" name="update_action" value="start"> + <% if @tournament.players.count >= @tournament.min_players_per_team * @tournament.min_teams_per_match %> + <%= submit_tag("Start Tournament") %> + <% else %> + <%= submit_tag("Start Tournament", disabled: true) %> + <% end %> + <br /> + <%= link_to 'Edit', edit_tournament_path(@tournament) %> | + <%= link_to 'Back', tournaments_path %> | + <%= link_to 'Cancel Tournament', @tournament, method: :delete, data: { confirm: 'Are you sure?' } %> + <% end %> +</div> + +<%end %> +</div> diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb index 4d28738..40f8f09 100644 --- a/app/views/users/_form.html.erb +++ b/app/views/users/_form.html.erb @@ -1,29 +1,45 @@ <%= form_for(@user) do |f| %> - <% if @user.errors.any? %> - <div id="error_explanation"> - <h2><%= pluralize(@user.errors.count, "error") %> prohibited this user from being saved:</h2> - - <ul> - <% @user.errors.full_messages.each do |msg| %> - <li><%= msg %></li> - <% end %> - </ul> - </div> - <% end %> + <%= render "common/error_messages", :target => @user %> <div class="field"> <%= f.label :name %><br> <%= f.text_field :name %> </div> + <div class="field"> <%= f.label :email %><br> <%= f.text_field :email %> </div> + <div class="field"> <%= f.label :user_name %><br> <%= f.text_field :user_name %> </div> + + <div> + <%= f.label(:password, "New Password (or use old)") %><br> + <%= f.password_field :password %> + </div> + <div> + <%= f.label(:password_confirmation, "Confirm Password") %><br> + <%= f.password_field :password_confirmation %> + </div> + + <% if current_user.can? :edit_permissions %> + <fieldset> + <legend>User permissions</legend> + <ul> + <%= fields_for "user[abilities]", @user.abilities do |abilities_fields| %> + <% @user.abilities.keys.each do |ability| %> + <li><label><%= abilities_fields.check_box(ability) %> <%= ability.to_s.humanize %></label></li> + <% end %> + <% end %> + </ul> + </fieldset> + <% end %> + <div class="actions"> <%= f.submit %> </div> + <% end %> diff --git a/app/views/users/already_signed_in.html.erb b/app/views/users/already_signed_in.html.erb new file mode 100644 index 0000000..04b4248 --- /dev/null +++ b/app/views/users/already_signed_in.html.erb @@ -0,0 +1 @@ +<h1>You are currently signed in</h1> diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index 99bd4cc..52f32a2 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -3,4 +3,4 @@ <%= render 'form' %> <%= link_to 'Show', @user %> | -<%= link_to 'Back', users_path %> +<%= link_to 'Users', users_path %> diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 3692112..89e369a 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -1,8 +1,9 @@ <h1>Listing users</h1> -<table> +<table class="table table-hover"> <thead> <tr> + <th>Username</th> <th>Name</th> <th>Email</th> <th>User name</th> @@ -15,8 +16,9 @@ <tbody> <% @users.each do |user| %> <tr> + <td><%= link_to("#{user.user_name}", user, nil) %></td> <td><%= user.name %></td> - <td><%= user.email %></td> + <td> ******* </td> <td><%= user.user_name %></td> <td><%= link_to 'Show', user %></td> <td><%= link_to 'Edit', edit_user_path(user) %></td> diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index efc0404..5e369ac 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -1,5 +1,34 @@ -<h1>New user</h1> +<h1> Sign Up </h1> -<%= render 'form' %> +<%= form_for @user do |f| %> + <%= render "common/error_messages", :target => @user %> + <p> + <%= f.label :name %><br> + <%= f.text_field :name %> + </p> + <p> + <%= f.label :email %><br> + <%= f.text_field :email %> + </p> + <p> + <%= f.label :user_name %><br> + <%= f.text_field :user_name %> + </p> + <p> + <%= f.label :password %><br> + <%= f.password_field :password %> + </p> + <p> + <%= f.label(:password_confirmation, "Confirm Password") %><br> + <%= f.password_field :password_confirmation %> + </p> + <p> + <%= show_simple_captcha %> + + <%= f.submit("Be a Leaguer", :class => "signup") %> + </p> +<% end %> + + +<%= link_to 'Already Have an Account? Log in', new_session_path, :class => "signin" %> -<%= link_to 'Back', users_path %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 9455a3c..a67aed9 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,8 +1,8 @@ -<p id="notice"><%= notice %></p> + +<h1> <%= @user.user_name %>'s Profile </h1> <p> - <strong>Name:</strong> - <%= @user.name %> + <%= image_tag 'http://www.gravatar.com/avatar/' + Digest::MD5.hexdigest(@user.email) + '?s=100&d=mm' %> </p> <p> @@ -11,9 +11,47 @@ </p> <p> - <strong>User name:</strong> + <strong>Preferred name:</strong> <%= @user.user_name %> </p> +<p> + <strong>Relationship Status:</strong> + too single +</p> + + +<p> +<% if @user.remote_usernames[0].nil? %> + <%= form_for @user do |f| %> + <label>Have a League of Legends Account? + <input type=text name="user[remote_usernames][League of Legends]"> + </label> + <%= f.submit "Add Username", :class => 'signup' %> + <% end %> +<% end %> +</p> + + +<div class="row"> + <div class="col-md-6"> + <h3> Recent Tournaments Played </h3> + <ul> + <% @user.tournaments_played.each do |t| %> + <li><%= t.name %></li> + <% end %> + </ul> + </div> + <div class="col-md-6"> + <h3> Recent Tournaments Hosted </h3> + <% if @user.tournaments_hosted.count == 0 %> <p> <%= @user.user_name %> has never hosted a tournament </p> <%end %> + <ul> + <% @user.tournaments_hosted.each do |t| %> + <li><%= t.name %></li> + <% end %> + </ul> + </div> +</div> + <%= link_to 'Edit', edit_user_path(@user) %> | -<%= link_to 'Back', users_path %> +<%= link_to 'Users', users_path %> diff --git a/blue.yaml b/blue.yaml new file mode 100644 index 0000000..720fa14 --- /dev/null +++ b/blue.yaml @@ -0,0 +1,200 @@ +--- +M9Fumjaa: + level: 16 + goldEarned: 11160 + numDeaths: 6 + minionsKilled: 80 + championsKilled: 4 + goldSpent: 10405 + totalDamageDealt: 86661 + totalDamageTaken: 18253 + team: 100 + win: true + neutralMinionsKilled: 8 + largestMultiKill: 1 + physicalDamageDealtPlayer: 52332 + magicDamageDealtPlayer: 34328 + physicalDamageTaken: 9855 + magicDamageTaken: 8323 + largestCriticalStrike: 611 + timePlayed: 2437 + totalHeal: 467 + totalUnitsHealed: 1 + assists: 8 + item0: 3087 + item1: 3254 + item2: 3031 + item3: 3086 + item4: 1018 + item6: 3341 + sightWardsBought: 3 + visionWardsBought: 2 + magicDamageDealtToChampions: 9814 + physicalDamageDealtToChampions: 6069 + totalDamageDealtToChampions: 15883 + trueDamageTaken: 75 + wardKilled: 5 + wardPlaced: 5 + neutralMinionsKilledEnemyJungle: 1 + neutralMinionsKilledYourJungle: 7 + totalTimeCrowdControlDealt: 593 +spikevsnaruto: + level: 17 + goldEarned: 11608 + numDeaths: 7 + turretsKilled: 3 + minionsKilled: 133 + championsKilled: 3 + goldSpent: 11275 + totalDamageDealt: 97547 + totalDamageTaken: 34598 + doubleKills: 1 + killingSprees: 1 + largestKillingSpree: 3 + team: 100 + win: true + neutralMinionsKilled: 11 + largestMultiKill: 2 + physicalDamageDealtPlayer: 58212 + magicDamageDealtPlayer: 38603 + physicalDamageTaken: 24930 + magicDamageTaken: 8900 + timePlayed: 2437 + totalHeal: 198 + totalUnitsHealed: 1 + assists: 6 + item0: 3083 + item1: 3074 + item2: 3143 + item4: 1001 + item6: 3340 + magicDamageDealtToChampions: 6017 + physicalDamageDealtToChampions: 5486 + totalDamageDealtToChampions: 12235 + trueDamageDealtPlayer: 732 + trueDamageDealtToChampions: 732 + trueDamageTaken: 768 + wardPlaced: 8 + neutralMinionsKilledEnemyJungle: 5 + neutralMinionsKilledYourJungle: 6 + totalTimeCrowdControlDealt: 1128 +GoogleMaSkills: + level: 16 + goldEarned: 12384 + numDeaths: 7 + barracksKilled: 1 + turretsKilled: 2 + minionsKilled: 107 + championsKilled: 4 + goldSpent: 12065 + totalDamageDealt: 134161 + totalDamageTaken: 34963 + killingSprees: 2 + largestKillingSpree: 2 + team: 100 + win: true + neutralMinionsKilled: 40 + largestMultiKill: 1 + physicalDamageDealtPlayer: 122998 + magicDamageDealtPlayer: 726 + physicalDamageTaken: 28243 + magicDamageTaken: 6299 + largestCriticalStrike: 582 + timePlayed: 2437 + totalHeal: 6236 + totalUnitsHealed: 1 + assists: 8 + item0: 3035 + item1: 3046 + item2: 1037 + item3: 3072 + item4: 3006 + item5: 1018 + item6: 3340 + physicalDamageDealtToChampions: 7310 + totalDamageDealtToChampions: 7420 + trueDamageDealtPlayer: 10436 + trueDamageDealtToChampions: 110 + trueDamageTaken: 420 + wardPlaced: 9 + neutralMinionsKilledEnemyJungle: 15 + neutralMinionsKilledYourJungle: 25 + totalTimeCrowdControlDealt: 236 +james chamberlan: + level: 18 + goldEarned: 17239 + numDeaths: 9 + barracksKilled: 1 + turretsKilled: 2 + minionsKilled: 179 + championsKilled: 14 + goldSpent: 14495 + totalDamageDealt: 212794 + totalDamageTaken: 31349 + doubleKills: 3 + tripleKills: 1 + killingSprees: 3 + largestKillingSpree: 5 + team: 100 + win: true + neutralMinionsKilled: 24 + largestMultiKill: 3 + physicalDamageDealtPlayer: 179480 + magicDamageDealtPlayer: 31260 + physicalDamageTaken: 17890 + magicDamageTaken: 12640 + largestCriticalStrike: 898 + timePlayed: 2437 + totalHeal: 286 + totalUnitsHealed: 1 + assists: 8 + item0: 3072 + item1: 3046 + item2: 3031 + item3: 3250 + item4: 3071 + item6: 3340 + sightWardsBought: 1 + magicDamageDealtToChampions: 8376 + physicalDamageDealtToChampions: 28456 + totalDamageDealtToChampions: 37504 + trueDamageDealtPlayer: 2054 + trueDamageDealtToChampions: 672 + trueDamageTaken: 819 + wardKilled: 1 + wardPlaced: 5 + neutralMinionsKilledEnemyJungle: 4 + neutralMinionsKilledYourJungle: 20 + totalTimeCrowdControlDealt: 777 +Kaceytron: + level: 15 + goldEarned: 9776 + numDeaths: 16 + barracksKilled: 1 + minionsKilled: 118 + goldSpent: 8245 + totalDamageDealt: 104719 + totalDamageTaken: 25219 + team: 100 + win: true + physicalDamageDealtPlayer: 10231 + magicDamageDealtPlayer: 94127 + physicalDamageTaken: 16694 + magicDamageTaken: 8368 + timePlayed: 2437 + totalHeal: 163 + totalUnitsHealed: 1 + assists: 5 + item0: 3174 + item1: 3135 + item2: 1026 + item3: 3108 + item4: 1058 + item6: 3340 + magicDamageDealtToChampions: 13741 + physicalDamageDealtToChampions: 1933 + totalDamageDealtToChampions: 16034 + trueDamageDealtPlayer: 360 + trueDamageDealtToChampions: 360 + trueDamageTaken: 156 + totalTimeCrowdControlDealt: 372 diff --git a/config/application.rb b/config/application.rb index 658e0aa..13bab0b 100644 --- a/config/application.rb +++ b/config/application.rb @@ -12,6 +12,9 @@ module Leaguer # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. + config.autoload_paths += ["#{Rails.root}/lib"] + config.watchable_dirs["#{Rails.root}/lib"] = [:rb] + I18n.enforce_available_locales = true # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. diff --git a/config/initializers/mailboxer.rb b/config/initializers/mailboxer.rb index 8876f80..b529481 100644 --- a/config/initializers/mailboxer.rb +++ b/config/initializers/mailboxer.rb @@ -1,7 +1,7 @@ Mailboxer.setup do |config| #Configures if you applications uses or no the email sending for Notifications and Messages - config.uses_emails = true + config.uses_emails = false #Configures the default from for the email sent for Messages and Notifications of Mailboxer config.default_from = "no-reply@mailboxer.com" diff --git a/config/locales/en.yml b/config/locales/en.yml index 0653957..9b7f013 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -21,3 +21,11 @@ en: hello: "Hello world" + + simple_captcha: + placeholder: "Enter the image value" + label: "Enter the code in the box:" + + message: + default: "Secret Code did not match with the Image" + user: "The secret Image and code were different" diff --git a/config/routes.rb b/config/routes.rb index 262a156..d7807f0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,24 +1,41 @@ Leaguer::Application.routes.draw do resources :brackets - resources :sessions + resources :sessions, only: [:new, :create, :destroy] resources :users resources :games - resources :tournaments - resources :pms resources :alerts resources :teams - resources :matches + resources :tournaments do + resources :matches, only: [:index, :show, :update] + end + + resource :server, only: [:show, :edit, :update] + + root to: 'main#homepage' + + get '/search', to: 'search#go' - resources :servers +end + + +Leaguer::Application.routes.named_routes.module.module_eval do + def match_path(match, options={}) + tournament_match_path(match.tournament_stage.tournament, match, options) + end + def match_url(match, options={}) + tournament_match_url(match.tournament_stage.tournament, match, options) + end +end +if false # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/db/seeds.rb b/db/seeds.rb index 4edb1e8..7b8709c 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,48 @@ # # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) # Mayor.create(name: 'Emanuel', city: cities.first) +# +p = User.permission_bits +Server.create(default_user_permissions: p[:join_tournament] | p[:create_pm]) + +Game.create(name: "League of Legends",min_players_per_team: 5, max_players_per_team: 5, min_teams_per_match: 2, max_teams_per_match: 2, set_rounds: nil, randomized_teams: true, sampling_method: "Manual,Double Blind,RiotAPI") +Game.create(name: "Chess", min_players_per_team: 1, max_players_per_team: 1, min_teams_per_match: 2, max_teams_per_match: 2, set_rounds: nil, randomized_teams: true, sampling_method: "Manual,Double Blind") +Game.create(name: "Hearthstone", min_players_per_team: 1, max_players_per_team: 1, min_teams_per_match: 2, max_teams_per_match: 2, set_rounds: 1, randomized_teams: false, sampling_method: "Manual,Double Blind") +Game.create(name: "Rock, Paper, Scissors", min_players_per_team: 1, max_players_per_team: 3, min_teams_per_match: 2, max_teams_per_match: 2, set_rounds: nil, randomized_teams: false, sampling_method: "Manual,Double Blind") + +Game.find_by_name("League of Legends").settings.create(name: "Map", default: "Summoners Rift", type_opt: "Summoners Rift,Twisted Treeline,Crystal Scar,Haunted Abyss", description: "Select a map to play on.", vartype: 5, display_order: 1) +Game.find_by_name("League of Legends").settings.create(name: "Pick Type", type_opt: "Blind Pick,Draft", description: "Select a pick type.", vartype: 5, display_order: 2) + +Game.find_by_name("Chess").settings.create(name: "Time Control", description: "Enter a value for Time Control (ie. 5-5, 30, 6hr, or None)", vartype: 0, display_order: 1) + +Game.find_by_name("Hearthstone").settings.create(name: "Deck Name", description: "Enter a name for your deck, be descriptive.", vartype: 1, display_order: 1) + +Game.find_by_name("Rock, Paper, Scissors").settings.create(name: "Favorite Object", description: "What is your favorite object in RPS?", type_opt: "Rock,Paper,Scissors", vartype: 2, display_order: 2) +Game.find_by_name("Rock, Paper, Scissors").settings.create(name: "Lizard, Spock allowed?", description: "Will you allow Lizard and Spock?", vartype: 4, display_order: 1) +Game.find_by_name("Rock, Paper, Scissors").settings.create(name: "Why are those up there even called radio buttons?", description: "Check boxes make sense at least", type_opt: "I do not know.,There is now spoon.,Wow.,Because electricity.,Wat?", vartype: 3, display_order: 3) + +if Rails.env.development? + User.create(name: "Administrator", user_name: "admin", email: "root@localhost.lan", password: "password", password_confirmation: "password", permissions: 0xFFFFFFFF) + + User.create(name: "John 0", password: "password", email: "john0@gmail.com", user_name: "johndoe0", password_confirmation: "password") + User.create(name: "John 1", password: "password", email: "john1@gmail.com", user_name: "johndoe1", password_confirmation: "password") + User.create(name: "John 2", password: "password", email: "john2@gmail.com", user_name: "johndoe2", password_confirmation: "password") + User.create(name: "John 3", password: "password", email: "john3@gmail.com", user_name: "johndoe3", password_confirmation: "password") + User.create(name: "John 4", password: "password", email: "john4@gmail.com", user_name: "johndoe4", password_confirmation: "password") + User.create(name: "John 5", password: "password", email: "john5@gmail.com", user_name: "johndoe5", password_confirmation: "password") + User.create(name: "John 6", password: "password", email: "john6@gmail.com", user_name: "johndoe6", password_confirmation: "password") + User.create(name: "John 7", password: "password", email: "john7@gmail.com", user_name: "johndoe7", password_confirmation: "password") + User.create(name: "John 8", password: "password", email: "john8@gmail.com", user_name: "johndoe8", password_confirmation: "password") + User.create(name: "John 9", password: "password", email: "john9@gmail.com", user_name: "johndoe9", password_confirmation: "password") + + User.create(name: "Sytrie", password: "password", email: "Sytrie@gmail.com", user_name: "Sytrie", password_confirmation: "password") + User.create(name: "Derpanator115", password: "password", email: "Derpanator115@gmail.com", user_name: "Derpanator115", password_confirmation: "password") + User.create(name: "Wlknexe56", password: "password", email: "Wlknexe56@gmail.com", user_name: "Wlknexe56", password_confirmation: "password") + User.create(name: "DVisionzz", password: "password", email: "DVisionzz@gmail.com", user_name: "DVisionzz", password_confirmation: "password") + User.create(name: "HYP3RTONIC", password: "password", email: "HYP3RTONIC@gmail.com", user_name: "HYP3RTONIC", password_confirmation: "password") + User.create(name: "M9Fumjaa", password: "password", email: "M9Fumjaa@gmail.com", user_name: "M9Fumjaa", password_confirmation: "password") + User.create(name: "spikevsnaruto", password: "password", email: "spikevsnaruto@gmail.com", user_name: "spikevsnaruto", password_confirmation: "password") + User.create(name: "GoogleMaSkills", password: "password", email: "GoogleMaSkills@gmail.com", user_name: "GoogleMaSkills", password_confirmation: "password") + User.create(name: "james chamberlan", password: "password", email: "james chamberlan@gmail.com", user_name: "james chamberlan", password_confirmation: "password") + User.create(name: "Kaceytron", password: "password", email: "Kaceytron@gmail.com", user_name: "Kaceytron", password_confirmation: "password") +end diff --git a/doc/Sprint1-Retrospective.md b/doc/Sprint1-Retrospective.md new file mode 100644 index 0000000..7bffde7 --- /dev/null +++ b/doc/Sprint1-Retrospective.md @@ -0,0 +1,220 @@ +--- +title: "Team 6 - Project Leaguer: Sprint 1 Retrospective" +author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ] +--- + +# User Stories + +1) As an administrator, I would like to install and boot my own server. + - Alternately: As a developer, I would like a demo/testing server, + with a basic Rails setup. +2) As a host/player, I would like to register and have an account. + - For this task, we will be creating the user registration and log + in capabilities for Leaguer. +3) As a host, I would like to start a tournament. + - For this task, we will be creating a base tournament system for a + host to run. +4) As a host/player, I would like to enter scores for players. + - For sprint own, the scores will be entered by hand. +5) As an administrator, I want to specify how users become hosts. +6) As a user I would like to see the progress of the tournament in my + browser. +7) As a user, I would like a presentable homepage. + - For this task, we will be creating a Leaguer homepage and ensure that it + is pleasing to the eye and easy to navigate. + +# Tasks + +The "size" is using the modified Fibonacci scale. A '1' is expected +to take less than an hour. A '3' is expected to take 3-6 hours. A +'5' should take the better part of a day or two. An 8 should take +several days. + ++---------------------------------------------------------+------+--------+----+ +| Tasks Implemented and Working | Size | Person | US | ++=========================================================+======+========+====+ +| [Learn Rails, set up Scaffolding for all Models, Views, | 8 | All | 1 | +| Controllers](#learn-rails) | | | | ++---------------------------------------------------------+------+--------+----+ +| [Deploy rails on Luke's server](#deploy-rails) | 3 | Luke | 1 | ++---------------------------------------------------------+------+--------+----+ +| [Create log-in system back-end (verification, cookies, | 5 | Davis | 2 | +| and redirection)](#login-backend) | | | | ++---------------------------------------------------------+------+--------+----+ +| [Create log-in system UI](#login-ui) | 2 | Tomer | 2 | ++---------------------------------------------------------+------+--------+----+ +| [Create Tournament Settings Page](#tourney-settings) | 3 | Guntas | 3 | ++---------------------------------------------------------+------+--------+----+ +| [Implement Tournament Registration and Tournament | 2 | Andrew | 3 | +| Controller](#tourney-registration) | | | | ++---------------------------------------------------------+------+--------+----+ +| [Implement match controller](#match-controller) | 3 | Dav+And| 4 | ++---------------------------------------------------------+------+--------+----+ +| [Implement permissions system over the users | 3 | Luke | 5 | +| system](#permissions) | | | | ++---------------------------------------------------------+------+--------+----+ +| [Create View Tournament Page](#tourney-view) | 5 | All | 6 | ++---------------------------------------------------------+------+--------+----+ +| [Create Presentable Homepage](#homepage) | 5 | Guntas | 7 | ++---------------------------------------------------------+------+--------+----+ + + ++---------------------------------------------------------+------+--------+----+ +| Tasks Implemented and Not Working Well | Size | Person | US | ++=========================================================+======+========+====+ +| [Design and implement match score models](#match-score) | 3 | Foy | 4 | ++---------------------------------------------------------+------+--------+----+ +| [Create Admin-level Server Management Page](#srv-man) | 2 | Luke | 5 | ++---------------------------------------------------------+------+--------+----+ + + ++---------------------------------------------------------+------+--------+----+ +| Tasks Not Implemented | Size | Person | US | ++=========================================================+======+========+====+ +| [Design/Code Scoring/Pairing Algorithms and | 5 | Foy | 3 | +| Procedures](#score-algo) | | | | ++---------------------------------------------------------+------+--------+----+ +| [Observe Foy Design/Code Scoring/Pairing | 2 | Dav+Foy| 3 | +| Algorithms](#score-algo) | | | | ++---------------------------------------------------------+------+--------+----+ +| [Create a Player-level Data Entry Page/Method for | 3 | Tomer | 4 | +| Results](#data-entry) | | | | ++---------------------------------------------------------+------+--------+----+ + +# Implemented and working + +## Learn Rails {#learn-rails} + +Learning Rails has been a growing experience for the majority of the +team. Some of us coming from no significant experience to being able +to put together a relatively functional product in only three weeks +has been an impressive journey. + +## Deploy Rails {#deploy-rails} + +The entire team became familiar with deploying Rails in our rather +diverse working environments and successfully deployed a server +instance located at demo.projectleaguer.net as well as on our local boxes. + +## Login (back-end) {#login-backend} + +Our login back-end successfully logs users in and our and can handle +user registrations and first-come-first-serve uniqueness validation. + +## Login (UI) {#login-ui} + +Our login user interface successfully differentiates between logged in +and logged out users as well as between users of different +designations (although for the demo some of the hooks were not in +place, this has been fixed). + +## Tournament settings {#tourney-settings} + +Tournament settings were implemented at a basic level, instituting those +items which are similar to all tournaments, regardless of type, orginating +from the game model. + +## Tournament registration {#tourney-registration} + +Tournament registration and the tournament contoller were completed which +allowed users to join and participate in basic tournaments of several types. +The tournament controller handled a variety of tournament related tasks, +including creating and updating tournaments and validating tournament related +operations. + +## Match controller {#match-controller} + +The Match Controller creates the separate matches for a specific tournament. +When a tournament is started, it begins with an initial match that contains +no players. Currently, a player must join a match by entering the specific +tournament (by clicking the 'show' button on the tournament), +then they must enter the match (again by clicking the 'show' button but this +time on the match they desire to participate in) and then finally clicking +the 'join' button. This updates the match with the user as a participant in +the matc and then finally clicking the 'join' button. This updates the match +with the user as a participant in the match. A match can also be destroyed +by clicking the 'delete' button on the no longer desired match on the page. + +## Permissions system {#permissions} + +The permissions system is implemented, easy to use, and works well. +In some places, it appears to be broken (overly-permisive), but this +is because the relevant page doesn't hook into the permission system. +This needs to be fixed with unit tests. + +## Tournament view {#tourney-view} +The view page for tournaments contains a table that lists all on going +tournaments for all types of games. It also lists other game attribute like +Players per team, Teams per match, whether or not teams were randomized +and also has links to Show, Edit, Destroy, Join a particular tournament. +A link to create a tournament is also provided. Each of the links correspond +to view pages which are html.erb pages that provide styles and functionality +of each of the tournament setting features. Show, Edit, Destroy, all have +views of their own to perform each of the above actions. + +## Homepage {#homepage} +The homepage is mainly controlled by the views that are associated with each +model and controller. The main view for the homepage is the one found in the +layouts called application.html.erb, this view is responsible for display of +all the headings, navigation bars, forms and containers. This view page +contains mostly links to other view pages and yields whatever the other view +pages have to offer. The Homepage redirects to Login, Signup, See Ongoing +Tournaments and shows the view for those models. + + +# Implemented but not working well + +## Match score models {#match-score} + +This only functioned properly for noting which team would win a match. We want +more information to be included, such as individual player scores. We also +only had it working where the tournament host would decide who won. + +## Server management {#srv-man} + +The server management software interface is implemented, and working +fine. The other modules use it. However, what we didn't implement is +an actual *page* to edit these settings. We had this task in the +iteration because other items depended on it. Though we did not +implement the full story, we implemented the core reason that we +wanted it. + +# Not implemented + +## Scoring Algorithms {#score-algo} + +Scoring algorithms was not implemented because we did not have time for +implementing player statistics in the first sprint. There were some +preliminary approaches, but the task lost priority and was abandoned. + +## Data entry {#data-entry} + +It was decided to not be a priority for sprint one due to time constraints. +Also, we want to implement data entry for League of Legends through +Riot Games (TM)'s API for grabbing match data. + +# How to improve + +Peer reviews and testing were our biggest pitfalls. + + +1. All testing was just manual, in-browser testing, rather than unit + tests. We really need write unit tests this iteration, as we had + breakages where we said "this is exactly why we need unit + testing." However, that happened late enough in the iteration that + we didn't have time to do anything about it. + +2. That leads us into time management. Our commit activity plotted + against time has humps each weak, each growing a little. That is, + we started slow, and ended with a lot of work. This wasn't exactly + poor planing, but we had a poor idea of how much time things would + take. We plan to fix this by front-loading this iteration instead + of back-loading it. + +3. We had the approach of "show everyone everything" with peer + reviews, as we anticipated that this would be nescessary for + everyone learning Rails. However, in effect it meant that + sometimes information was spread very thin, or because things were + being done "in the open", we didn't ever explicitly review them. + We plan on fixing this next iteration by committing to do very + specific peer reviews with just a couple members of the team. diff --git a/doc/Sprint2-Retrospective.md b/doc/Sprint2-Retrospective.md new file mode 100644 index 0000000..9a98ec5 --- /dev/null +++ b/doc/Sprint2-Retrospective.md @@ -0,0 +1,215 @@ +--- +title: "Team 6 - Project Leaguer: Sprint 2 Retrospective" +author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ] +--- + +# Tasks + +The "size" is using the modified Fibonacci scale. A '1' is expected +to take less than an hour. A '3' is expected to take 3-6 hours. A +'5' should take the better part of a day or two. An 8 should take +several days. + ++---------------------------------------------------------+------+------------+----+ +| Tasks Implemented and Working | Size | Person\* | US | ++=========================================================+======+============+====+ +| [Implement Anti-spam measures](#anti-spam) | 2 | Davis | 2 | ++---------------------------------------------------------+------+------------+----+ +| [Implement Teammate Rating System (peer review view)] | 5 | Guntas | 3 | +| (#peer-review) | | | | ++---------------------------------------------------------+------+------------+----+ +| [Design/Code Scoring/Pairing Algorithms and Procedures] | 5 | D+F+A | 3 | +| (#pair-alg) | | | | ++---------------------------------------------------------+------+------------+----+ +| [Implement game-type specific and tournament | 8 | L+A+G | 4 | +| specific settings and preferences] (#setting-and-pref) | | | | ++---------------------------------------------------------+------+------------+----+ +| [Retrieve data from Riot Games (TM) API ](#riot-api) | 3 | Foy | 5 | ++---------------------------------------------------------+------+------------+----+ +| [Parse Riot data and attach to scoring subsystem] | 5 | Davis | 5 | +| (#parse-riot) | | | | ++---------------------------------------------------------+------+------------+----+ +| [Teach Andrew and Tomer AJAX ](#teach-ajax) | 2 | Luke | 6 | ++---------------------------------------------------------+------+------------+----+ +| [Make pages auto-update with AJAX](#ajax) | 5 | T+A | 6 | ++---------------------------------------------------------+------+------------+----+ +| [Setting up a Tournament View for matches and tree] | 5 | Tomer | 7 | +| (#match-gui) | | | | ++---------------------------------------------------------+------+------------+----+ +| [Increase Usability](#usability) | 3 | All-L | 8 | ++---------------------------------------------------------+------+------------+----+ +| [Develop comprehensive data storage for s&p&other] | 5 | L+A | 9 | +| (#data-storage) | | | | ++---------------------------------------------------------+------+------------+----+ +| [Create Player Profile Pages](#profile) | 2 | Tomer | 10 | ++---------------------------------------------------------+------+------------+----+ +| [Gravatar Integration](#gravatar) | 2 | Foy | 10 | ++---------------------------------------------------------+------+------------+----+ + + + + ++---------------------------------------------------------+------+------------+----+ +| Tasks Implemented and Not Working Well | Size | Person\* | US | ++=========================================================+======+============+====+ +| [Not Applicable](#all-or-nothing) | 0 | --- | 0 | ++---------------------------------------------------------+------+------------+----+ + + + + ++---------------------------------------------------------+------+------------+----+ +| Tasks Not Implemented | Size | Person\* | US | ++=========================================================+======+============+====+ +| [Email Verification Option](#email-varify) | 5 | Luke | 2 | ++---------------------------------------------------------+------+------------+----+ +| [Project Leaguer Logo](#logo) | spike| D+G | 8 | ++---------------------------------------------------------+------+------------+----+ +| [Define Specific Unit Tests for Security] | 3 | All | 1 | +| (#security-test) | | | | ++---------------------------------------------------------+------+------------+----+ + + + + +# Implemented and working + +## Implement Anti-spam measures {#anti-spam} + +To handle potential spam problems, Project Leaguer has implemented Simple Captcha +on the user sign up page. Users must enter the correct code corresponding with +Simple Captcha's generated image when registering. Usernames must also be unique. +E-mail verification has been pushed to Sprint 3. + +## Implement Teammate Rating System (peer review view) {#peer-review} + +This sprint covered both the database framework and actual implementation of the +peer review rating system. Peer review was accomplished with both server-side +processing and client-side manipulation of the DOM via a floating tactile dragable +info-box interface. + +## Design/Code Scoring/Pairing Algorithms and Procedures {#pair-alg} + +Several scoring algorithms were considered for demonstration purposes for this +sprint and eventually a modified fibonachi peer review system was chosen as the +most fair system. This was the only scoring algorithm implemented in this sprint. +A single-elimination pairing algorithm was chosen for similar reasons (as well +as for simplicity in SVG generation). + +## Implement game-type specific and tournament specific settings and preferences + {#setting-and-pref} + +The input for settings and preferences for creating tournaments are displayed +dynamically in both the substance of the content and form in which it is displayed. + +## Retrieve data from Riot Games (TM) API {#riot-api} + +Grabbing League of Legends user and match data from Riot's servers has been +implemented using their newly available API. A developer key is necessary in +order to retrieve data from their servers. We currently are using Davis's to do +so. Information is grabbed with HTTParty.get and the correct url. A hash of +information is stored this way. Grabbing information for a user requires the +user's League of Legends summoner's name or summoner id. Our current developer +key is limited to utilizing 10 pulls per 10 seconds. + +## Parse Riot data and attach to scoring subsystem {#parse-riot} + +We successfully parse the data we recieve from the Riot servers. The information +is stored in a JSON hash which we separate based on the information we want (like +kills, deaths, etc). One issue with our current pull method is that it can exceed +the pull limit that is on our current development key. To fix this, we are planning +on implementing a remote user id to link users Leaguer information to their Riot +information. + +## Teach Andrew and Tomer AJAX {#teach-ajax} + +Luke instructed Tomer on his AJAX tasks, but most of Andrew's were deferred to +sprint 3 and he focused his efforts elsewhere. + +## Make pages auto-update with AJAX {#ajax} + +AJAX was used in tournament and match views to update the tournament progress bar +and manage input options for tournament flow but still needs to be implemented +across the website in other areas. + +## Setting up a Tournament View for matches and tree {#match-gui} + +A new system was set-up so that matches are created from the trunk (final match) to +the most out matches, and teams are inserted into matches starting at the leaves and +and filling up the trunk. Any number of teams is now supported. A lot of log-based +math was used to write the rails-generated SVG, and a lot of arithmetic was done to +calculate the relative proportions. + +## Increase Usability {#usability} + +Project Leaguer has many new features that have increased usability. AJAX +integration, tournament visuals (ready bar, match trees), Gravatar images, +and Riot API integration all contribute towards an easier and more automatic +web interface available for our users to utilize. + +## Develop comprehensive data storage for s&p&other{#data-storage} + +Settings and Preferences (those options specific to tournaments of a game type +or a specific tournament, respectively) are handled through a single +TournamentPreference SQL (ActiveRecord) interface. + +## Create Player Profile Pages {#profile} + +Player Profile Pages successfully list important and useful user information. +Player username, e-mail, relationship status, and recent tournament information +are all listed on a user's profile page. Gravatar images are also shown here. +Users can also edit their pages. + +## Gravatar Integration {#gravatar} + +Gravatar images are fetched from the gravatar website. A user's e-mail is used to +generate a hash key and that key is used to grab their gravatar image from a url. +If their e-mail is not recognized by Gravatar, then we have a wide number of +optionable default images to use. We currently use a mystery man default. It's +also possible to utilize a number of other image options, such as sizing. + + + +# Implemented but not working well + +## Not Applicable {#all-or-nothing} + +Everything we implemented was implemented well, or else we didn't implement it. + + + +# Not implemented + +## Email Verification Option {#email-verify} + +This was not implemented for lack of time. Luke probably would have been able to +implement it with his time constraints if he wasn't busy frequently assisting +other members with various problems. In the end, the email verification was also +simply a low priority. + +## Project Leaguer Logo {#logo} + +The Project Leaguer Logo was discussed before the sprint started. We decided we +would follow up on any opportunities to explore creating a Leauger Logo, but this +simply did not happen. We greatly want one, but it's still just a low priority +extra feature. + +## Define Specific Unit Tests for Security {#security-test} + +Because of heavy "dog-food" style testing during the development process and +fairly heavy rapid redesign, specific unit tests were not given a high priority +for this sprint. The interdependency of components for tournament logic provided +near-instant feedback when something was wrong. + +# How to improve + +1. We can better document our code with proper commentation and indentation. The +team has run into issues where we've become confused with our own code and wasted +time reviewing code. + +2. Our commits slowed to a halt the week before spring break. In this upcoming +sprint we plan to take a stronger initiative and take a running start rather than +a last lap dash. + +3. We can more carefully push and merge. We ran into a couple issues where team +members broke each others work. These mistakes cost a lot of time to fix. diff --git a/doc/Sprint2.md b/doc/Sprint2.md new file mode 100644 index 0000000..a823600 --- /dev/null +++ b/doc/Sprint2.md @@ -0,0 +1,103 @@ +--- +title: "Team 6 - Project Leaguer: Sprint 2" +author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ] +--- + +# User Stories + +1) As an admin, I would like hosts/players, to have only the options + their group entitles them to. + +2) As an admin, I would like anti-spam measures for registration. + +3) As a player I would like to review my peers and have our + scores reflect these reviews. + +4) As a host I would like to have both game-type specific settings and + tournament specific preferences available when creating a new + tournament. + - These settings and preferences were moved to sprint 2 from sprint 1 + because we did not have the API functionality for any specific game yet. + +5) As a host/player/spectator I would like to have Riot Games League + of Legends API integration for match and player statistics and results for + League of Legends tournaments. + +6) As a host/player, I would like my pages to actively update without + refreshing my current page. + - For this task, we will implement an Active Status Update system with AJAX. + +7) As a host/player, I would like to see an interactive tournament lobby page + that displays tournament and match information. + - This will be accomplished with dynamic graphs, trees, and status bars. + +8) As a host/player, I would like the Leaguer application to be more intuitive + and easy to use. + +9) As a user, I would like past tournament and player information to be + persistent and search-able. + - A working search utility should be implemented that will find specific + players or tournaments and return their pages. + +10) As a user, I would like to see Player Profile pages. + - For this task, we will be creating profile pages for registered users that + have player-specific information such as tournament history and activity. + +# Tasks + +The "size" is using the modified Fibonacci scale. A '1' is expected +to take less than an hour. A '3' is expected to take 3-6 hours. A +'5' should take the better part of a day or two. An 8 should take +several days. + ++---------------------------------------------------------+------+------------+----+ +| Task Description | Size | Person\* | US | ++=========================================================+======+============+====+ +| Define Specific Unit Tests for Security | 3 | All | 1 | ++---------------------------------------------------------+------+------------+----+ +| Implement Anti-spam measures | 2 | Davis | 2 | ++---------------------------------------------------------+------+------------+----+ +| Email Verification Option | 5 | Luke | 2 | ++---------------------------------------------------------+------+------------+----+ +| Implement Teammate Rating System (peer review view) | 5 | Guntas | 3 | ++---------------------------------------------------------+------+------------+----+ +| Design/Code Scoring/Pairing Algorithms and Procedures | 5 | D+F+A | 3 | ++---------------------------------------------------------+------+------------+----+ +| Implement game-type specific and tournament | 8 | L+A+G | 4 | +| specific settings and preferences | | | | ++---------------------------------------------------------+------+------------+----+ +| Retrieve data from Riot Games (TM) API | 3 | Foy | 5 | ++---------------------------------------------------------+------+------------+----+ +| Parse Riot data and attach to scoring subsystem | 5 | Davis | 5 | ++---------------------------------------------------------+------+------------+----+ +| Teach Andrew and Tomer AJAX | 2 | Luke | 6 | ++---------------------------------------------------------+------+------------+----+ +| Make pages auto-update with AJAX | 5 | T+A | 6 | ++---------------------------------------------------------+------+------------+----+ +| Setting up a Tournament View for matches and tree | 5 | Tomer | 7 | ++---------------------------------------------------------+------+------------+----+ +| Increase Usability | 3 | All-L | 8 | ++---------------------------------------------------------+------+------------+----+ +| Project Leaguer Logo | spike| D+G | 8 | ++---------------------------------------------------------+------+------------+----+ +| Develop comprehensive data storage for s&p&other | 5 | L+A | 9 | ++---------------------------------------------------------+------+------------+----+ +| Create Player Profile Pages | 2 | Tomer | 10 | ++---------------------------------------------------------+------+------------+----+ +| Gravitar Integration | 2 | Foy | 10 | ++---------------------------------------------------------+------+------------+----+ +| Test it | 1 | All-L | all| ++---------------------------------------------------------+------+------------+----+ +| Peer review | 1 | All | all| ++---------------------------------------------------------+------+------------+----+ + +Total Size of Iteration: 55 + + D = Davis = 10 + A = Andrew = 10 + F = Nathaniel = 10 + G = Guntas = 10 + L = Luke = 11 + T = Tomer = 10 + +\* `+` means those members work together, `-` means exclude following members diff --git a/doc/Sprint3.md b/doc/Sprint3.md new file mode 100644 index 0000000..31f505e --- /dev/null +++ b/doc/Sprint3.md @@ -0,0 +1,80 @@ +--- +title: "Team 6 - Project Leaguer: Sprint 3" +author: [ Nathaniel Foy, Guntas Grewal, Tomer Kimia, Andrew Murrell, Luke Shumaker, Davis Webb ] +--- + +# User Stories + +1) As a user, I would like the web interface to look more professional and complete. + +2) As an admin, I would like to have an email verification system for a more secure + and spam free enviroment. + +3) As a user, I would like intelligent error handling (e.g. 404 and 403 redirection). + +4) As an admin, I would like to send alerts to users. + +5) As a user, I would like to be able to send private messages. + +6) As a user, I would like a working search utility. + +7) As a host or player, I would like customizable settings for peer evaluation and + scoring. + +8) As a host, I would like to have multiple tournament structures and types for + pairing and running tournaments. (e.g. Round Robin team pairings). + +9) As a host, I would like to have an interface for adding tournament-specific + preferences (e.g. Capture the Teemo). + +10) As a user, I would like to view and create brackets. + +11) As a user, I would like the Riot API to be asynchronously polled in the + background so League of Legends tournaments proceed automatically. + +12) As a player, I would like a way to enter my usernames for several different + remote games. + + +#Tasks + +The "size" is using the modified Fibonacci scale. A '1' is expected +to take less than an hour. A '3' is expected to take 3-6 hours. A +'5' should take the better part of a day or two. An 8 should take +several days. + ++---------------------------------------------------------+------+------------+----+ +| Task Description | Size | Person | US | ++=========================================================+======+============+====+ +| Intelligent Error Handling (404 redirection) | 3 | Andrew | 3 | ++---------------------------------------------------------+------+------------+----+ +| Search | 5 | Tomer | 6 | ++---------------------------------------------------------+------+------------+----+ +| Remote Game UserNames | 3 | Davis | 12 | ++---------------------------------------------------------+------+------------+----+ +| Email verification | 8 | Luke | 2 | ++---------------------------------------------------------+------+------------+----+ +| Alternate Scoring and pairing methods | 5 | G, A, D | 7,8| ++---------------------------------------------------------+------+------------+----+ +| Tournament preference interace | 3 | Andrew | 9 | ++---------------------------------------------------------+------+------------+----+ +| More types of seeded settings | 2 | Andrew | 9 | ++---------------------------------------------------------+------+------------+----+ +| Asynchronous Riot Pulls | 5 | Nathaniel | 11 | ++---------------------------------------------------------+------+------------+----+ +| Map out brackets scaffolding | 5 | Tomer | 10 | ++---------------------------------------------------------+------+------------+----+ +| Create braket creation and submission gui | 3 | Tomer | 10 | ++---------------------------------------------------------+------+------------+----+ +| General Interface Cleanups | 2 | Tomer | 1 | ++---------------------------------------------------------+------+------------+----+ +| Make it look professional | 3 | All | 1 | ++---------------------------------------------------------+------+------------+----+ +| Expand Peer Evaluation | 3 | G, A, D | 7 | ++---------------------------------------------------------+------+------------+----+ +| Private Messages | 5 | N, L | 5 | ++---------------------------------------------------------+------+------------+----+ +| Alerts | 3 | Guntas | 4 | ++---------------------------------------------------------+------+------------+----+ +| Project Leaguer Logo | spike| G, D | 1 | ++---------------------------------------------------------+------+------------+----+ diff --git a/doc/sprint2Retro.md b/doc/sprint2Retro.md new file mode 100644 index 0000000..1a40bac --- /dev/null +++ b/doc/sprint2Retro.md @@ -0,0 +1,12 @@ +Riot API
+
+The Riot API allowed us to pull information from the Riot servers for League of Legends
+Tournaments. For sprint 2, we were able to pull the information, but not very efficiently.
+We ran into an issue where we were making too many pull requests for our Riot developer key
+which would gave us some issues.
+
+Anti-Spam
+
+For now, the only anti-spam protection that we have is a captcha at the user creation page.
+Our hopes is to implement an email verification system in order to help further protect
+Project Leaguer from spam.
diff --git a/lib/pairing/PairingAlgorithm.rb b/lib/pairing/PairingAlgorithm.rb new file mode 100644 index 0000000..81e4df6 --- /dev/null +++ b/lib/pairing/PairingAlgorithm.rb @@ -0,0 +1,6 @@ +module Pairing + class PairingAlgorithm + def self.pair(matches, players) + end + end +end diff --git a/lib/playing/.keep b/lib/playing/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/lib/playing/.keep diff --git a/lib/scheduling/elimination.rb b/lib/scheduling/elimination.rb new file mode 100644 index 0000000..1ac696d --- /dev/null +++ b/lib/scheduling/elimination.rb @@ -0,0 +1,130 @@ + +module Scheduling + class Elimination + include Rails.application.routes.url_helpers + + def initialize(tournament_stage) + @tournament_stage = tournament_stage + end + + def tournament_stage + @tournament_stage + end + + def tournament + self.tournament_stage.tournament + end + + def create_matches + num_teams = (self.tournament.players.count/self.tournament.min_players_per_team).floor + num_matches = num_teams - 1 + for i in 1..num_matches + self.tournament_stage.matches.create(status: 0, submitted_peer_evaluations: 0) + end + + match_num = num_matches-1 + team_num = 0 + + self.tournament.players.shuffle + + # for each grouping of min_players_per_team + self.tournament.players.each_slice(self.tournament.min_players_per_team) do |team_members| + # if the match is full, move to the next match, otherwise move to the next team + if (team_num == self.tournament.min_teams_per_match) + match_num -= 1 + team_num = 0 + else + team_num += 1 + end + # create a new team in the current match + self.tournament_stage.matches[match_num].teams.push(Team.create(users: team_members)) + end + end + + def match_finished(match) + matches = match.tournament_stage.matches_ordered + cur_match_num = matches.invert[match] + unless cur_match_num == 1 + match.winner.matches.push(matches[cur_match_num/2]) + end + end + + def graph(current_user) + matches = @tournament_stage.matches_ordered + # depth of SVG tree + depth = Math.log2(matches.count).floor+1; + # height of SVG + height = [200 * 2**Math.log2(matches.count).floor + 100, 500].max; + lastrx = 0 + lastry = 0 + lastrh = 0 + lastrw = 0 + + str = <<-STRING +<svg version="1.1" baseProfile="full" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + width="100%" height="#{height}"> + <defs> + <radialGradient id="gradMatch" cx="50%" cy="50%" r="80%" fx="80%" fy="80%"> + <stop offset="0%" style="stop-color:#ffd281; stop-opacity:0" /> + <stop offset="100%" style="stop-color:#ccc;stop-opacity:1" /> + </radialGradient> + </defs> +STRING + (1..matches.count).each do |i| + rh = 100/(2**(depth-1)+1) - 5 + rw = 100/(depth+1) - 5 + rx = 50/(depth+1) + 100/(depth+1)*(depth-(Math.log2(i).floor+1)) + ry = ( 100/(2**(Math.log2(i).floor)+1) + rh * 1.1 * (2**Math.log2(i).ceil-i)) + + str += "\t<a id=\"svg-match-#{i}\" xlink:href=\"#{match_path(matches[i])}\"><g>\n" + str += "\t\t<rect height=\"#{rh}%\" width=\"#{rw}%\" x=\"#{rx}%\" y=\"#{ry}%\" fill=\"url(#gradMatch)\" rx=\"5px\" stroke-width=\"2\"" + case matches[i].status + when 0 + if matches[i].teams.count < @tournament_stage.tournament.min_teams_per_match + str += ' stroke="red"' + str += ' fill-opacity="0.6"' + else + str += ' stroke="green"' + end + when 1 + str += ' stroke="orange"' + when 2 + str += ' stroke="yellow"' + when 3 + str += ' stroke="grey"' + end + str += "/>\n" + + color = (matches[i].teams[0] and matches[i].teams[0].users.include?(current_user)) ? "#BCED91" : "white" + str += "\t\t<rect width=\"#{rw-5}%\" height=\"#{rh/4}%\" x=\"#{rx + 2.5}%\" y=\"#{ry + rh/6}%\" fill=\"#{color}\" />\n" + if matches[i].teams.first + str += "\t\t<text x=\"#{rx + rw/4}%\" y=\"#{ry + rh/3}%\" font-size=\"#{rh}\">Team #{matches[i].teams.first.id}</text>\n" + end + + str += "\t\t<text x=\"#{rx + 1.3*rw/3}%\" y=\"#{ry + 5.2*rh/9}%\" font-size=\"#{rh}\"> VS </text>\n" + + color = (matches[i].teams[1] and matches[i].teams[1].users.include?(current_user)) ? "#BCED91" : "white" + str += "\t\t<rect width=\"#{rw-5}%\" height=\"#{rh/4}%\" x=\"#{rx + 2.5}%\" y=\"#{ry + 3*rh/5}%\" fill=\"#{color}\" />\n" + if matches[i].teams[1] + str += "\t\t<text x=\"#{rx + rw/4}%\" y=\"#{ry + 4*rh/5}%\" font-size=\"#{rh}\">Team #{matches[i].teams[1].id}</text>\n" + end + + if i > 1 + str += "\t\t<line x1=\"#{rx+rw}%\" y1=\"#{ry+rh/2}%\" x2=\"#{lastrx}%\" y2=\"#{lastry+lastrh/2}%\" stroke=\"black\" stroke-width=\"2\" >\n" + end + if Math.log2(i+1) == Math.log2(i+1).ceil + lastrx = rx + lastry = ry + lastrh = rh + lastrw = rw + end + str += "</g></a>\n" + end + str += '</svg>' + + return str + end + end +end diff --git a/lib/scoring/fibonacci_peer_with_blowout.rb b/lib/scoring/fibonacci_peer_with_blowout.rb new file mode 100644 index 0000000..8043fb7 --- /dev/null +++ b/lib/scoring/fibonacci_peer_with_blowout.rb @@ -0,0 +1,21 @@ +module Scoring + module FibonacciPeerWithBlowout + def stats_needed + return [:votes] + end + + def score(match, interface) + scores = {} + match.players.each do |player| + scores[player.user_name] = score_user(interface.get_statistic(match, player, :votes), match.win?(player), match.blowout) + end + scores + end + + private + def score_user(votes, win, blowout) + fibonacci = Hash.new { |h,k| h[k] = k < 2 ? k : h[k-1] + h[k-2] } + fibonacci[votes+3] + (win ? blowout ? 12 : 10 : blowout ? 5 : 7) + end + end +end diff --git a/lib/scoring/marginal_peer.rb b/lib/scoring/marginal_peer.rb new file mode 100644 index 0000000..13e1796 --- /dev/null +++ b/lib/scoring/marginal_peer.rb @@ -0,0 +1,15 @@ +module Scoring + module MarginalPeer + def stats_needed + return [:rating] + end + + def score(match, interface) + scores = {} + match.players.each do |player| + scores[player.user_name] = interface.get_statistic(match, player, :rating) + end + scores + end + end +end diff --git a/lib/scoring/winner_takes_all.rb b/lib/scoring/winner_takes_all.rb new file mode 100644 index 0000000..517dfd6 --- /dev/null +++ b/lib/scoring/winner_takes_all.rb @@ -0,0 +1,20 @@ +module Scoring + module WinnerTakesAll + def stats_needed + return [] + end + + def score(match, interface) + scores = {} + match.players.each do |player| + scores[player.user_name] = score_user(match.win?(player)) + end + scores + end + + private + def score_user(win) + win.nil? ? 0.5 : win ? 1 : 0 + end + end +end diff --git a/purple.yaml b/purple.yaml new file mode 100644 index 0000000..0413cde --- /dev/null +++ b/purple.yaml @@ -0,0 +1,194 @@ +--- +Sytrie: + level: 16 + goldEarned: 9257 + numDeaths: 7 + turretsKilled: 1 + minionsKilled: 57 + championsKilled: 4 + goldSpent: 8025 + totalDamageDealt: 71437 + totalDamageTaken: 20413 + killingSprees: 1 + largestKillingSpree: 2 + team: 200 + win: false + largestMultiKill: 1 + physicalDamageDealtPlayer: 15947 + magicDamageDealtPlayer: 55489 + physicalDamageTaken: 12706 + magicDamageTaken: 7567 + timePlayed: 2437 + totalHeal: 566 + totalUnitsHealed: 1 + assists: 11 + item0: 3027 + item1: 3020 + item2: 1056 + item3: 3135 + item4: 1026 + item6: 3340 + sightWardsBought: 3 + visionWardsBought: 1 + magicDamageDealtToChampions: 19109 + physicalDamageDealtToChampions: 1682 + totalDamageDealtToChampions: 20791 + trueDamageTaken: 140 + wardPlaced: 12 + totalTimeCrowdControlDealt: 77 +Derpanator115: + level: 17 + goldEarned: 13648 + numDeaths: 7 + turretsKilled: 2 + minionsKilled: 218 + championsKilled: 10 + goldSpent: 12885 + totalDamageDealt: 161790 + totalDamageTaken: 27265 + killingSprees: 3 + largestKillingSpree: 4 + team: 200 + win: false + largestMultiKill: 1 + physicalDamageDealtPlayer: 158065 + magicDamageDealtPlayer: 3724 + physicalDamageTaken: 16740 + magicDamageTaken: 10176 + largestCriticalStrike: 834 + timePlayed: 2437 + totalHeal: 2442 + totalUnitsHealed: 1 + assists: 12 + item0: 3035 + item1: 3072 + item2: 3250 + item3: 3031 + item4: 1028 + item6: 3340 + magicDamageDealtToChampions: 2286 + physicalDamageDealtToChampions: 21244 + totalDamageDealtToChampions: 23531 + trueDamageTaken: 348 + wardKilled: 2 + wardPlaced: 9 + totalTimeCrowdControlDealt: 90 +Wlknexe56: + level: 16 + goldEarned: 11547 + numDeaths: 4 + minionsKilled: 144 + championsKilled: 10 + goldSpent: 12285 + totalDamageDealt: 112047 + totalDamageTaken: 19254 + killingSprees: 3 + largestKillingSpree: 5 + team: 200 + win: false + largestMultiKill: 1 + physicalDamageDealtPlayer: 101821 + magicDamageDealtPlayer: 8983 + physicalDamageTaken: 10827 + magicDamageTaken: 7883 + largestCriticalStrike: 805 + timePlayed: 2437 + totalHeal: 1526 + totalUnitsHealed: 1 + assists: 10 + item0: 3270 + item1: 3035 + item2: 3074 + item3: 3071 + item4: 1031 + item6: 3340 + magicDamageDealtToChampions: 3039 + physicalDamageDealtToChampions: 21523 + totalDamageDealtToChampions: 25804 + trueDamageDealtPlayer: 1242 + trueDamageDealtToChampions: 1242 + trueDamageTaken: 544 + wardPlaced: 10 + totalTimeCrowdControlDealt: 49 +DVisionzz: + level: 18 + goldEarned: 12520 + numDeaths: 5 + barracksKilled: 2 + turretsKilled: 1 + minionsKilled: 53 + championsKilled: 12 + goldSpent: 11353 + totalDamageDealt: 156993 + totalDamageTaken: 25675 + doubleKills: 1 + killingSprees: 2 + largestKillingSpree: 7 + team: 200 + win: false + neutralMinionsKilled: 69 + largestMultiKill: 2 + physicalDamageDealtPlayer: 113041 + magicDamageDealtPlayer: 34822 + physicalDamageTaken: 21062 + magicDamageTaken: 4546 + largestCriticalStrike: 595 + timePlayed: 2437 + totalHeal: 6278 + totalUnitsHealed: 1 + assists: 4 + item0: 3160 + item1: 3260 + item2: 3078 + item3: 1036 + item4: 3172 + item5: 1011 + item6: 3340 + magicDamageDealtToChampions: 2229 + physicalDamageDealtToChampions: 17146 + totalDamageDealtToChampions: 19606 + trueDamageDealtPlayer: 9129 + trueDamageDealtToChampions: 231 + trueDamageTaken: 66 + wardPlaced: 13 + neutralMinionsKilledEnemyJungle: 26 + neutralMinionsKilledYourJungle: 43 + totalTimeCrowdControlDealt: 180 +HYP3RTONIC: + level: 17 + goldEarned: 10739 + numDeaths: 3 + minionsKilled: 101 + championsKilled: 9 + goldSpent: 10275 + totalDamageDealt: 80887 + totalDamageTaken: 22178 + killingSprees: 1 + largestKillingSpree: 8 + team: 200 + win: false + neutralMinionsKilled: 9 + largestMultiKill: 1 + physicalDamageDealtPlayer: 31149 + magicDamageDealtPlayer: 49738 + physicalDamageTaken: 13555 + magicDamageTaken: 7847 + timePlayed: 2437 + totalHeal: 1514 + totalUnitsHealed: 1 + assists: 11 + item0: 1056 + item1: 3255 + item2: 3135 + item3: 3027 + item4: 3003 + item5: 1028 + item6: 3340 + magicDamageDealtToChampions: 17867 + physicalDamageDealtToChampions: 3182 + totalDamageDealtToChampions: 21050 + trueDamageTaken: 776 + wardPlaced: 8 + neutralMinionsKilledEnemyJungle: 6 + neutralMinionsKilledYourJungle: 3 + totalTimeCrowdControlDealt: 62 diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..2b58327 --- /dev/null +++ b/start.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +bundle exec rails server --daemon + @@ -0,0 +1,3 @@ +#!/bin/bash + +kill $(<tmp/pids/server.pid) diff --git a/vendor/assets/javascripts/coordinates.js b/vendor/assets/javascripts/coordinates.js new file mode 100644 index 0000000..e2f5bf2 --- /dev/null +++ b/vendor/assets/javascripts/coordinates.js @@ -0,0 +1,94 @@ +var Coordinates = { + ORIGIN : new Coordinate(0, 0), + + northwestPosition : function(element) { + var x = parseInt(element.style.left); + var y = parseInt(element.style.top); + + return new Coordinate(isNaN(x) ? 0 : x, isNaN(y) ? 0 : y); + }, + + southeastPosition : function(element) { + return Coordinates.northwestPosition(element).plus( + new Coordinate(element.offsetWidth, element.offsetHeight)); + }, + + northwestOffset : function(element, isRecursive) { + var offset = new Coordinate(element.offsetLeft, element.offsetTop); + + if (!isRecursive) return offset; + + var parent = element.offsetParent; + while (parent) { + offset = offset.plus( + new Coordinate(parent.offsetLeft, parent.offsetTop)); + parent = parent.offsetParent; + } + return offset; + }, + + southeastOffset : function(element, isRecursive) { + return Coordinates.northwestOffset(element, isRecursive).plus( + new Coordinate(element.offsetWidth, element.offsetHeight)); + }, + + fixEvent : function(event) { + event.windowCoordinate = new Coordinate(event.clientX, event.clientY); + } +}; + +function Coordinate(x, y) { + this.x = x; + this.y = y; +} + +Coordinate.prototype.toString = function() { + return "(" + this.x + "," + this.y + ")"; +} + +Coordinate.prototype.plus = function(that) { + return new Coordinate(this.x + that.x, this.y + that.y); +} + +Coordinate.prototype.minus = function(that) { + return new Coordinate(this.x - that.x, this.y - that.y); +} + +Coordinate.prototype.distance = function(that) { + var deltaX = this.x - that.x; + var deltaY = this.y - that.y; + + return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); +} + +Coordinate.prototype.max = function(that) { + var x = Math.max(this.x, that.x); + var y = Math.max(this.y, that.y); + return new Coordinate(x, y); +} + +Coordinate.prototype.constrain = function(min, max) { + if (min.x > max.x || min.y > max.y) return this; + + var x = this.x; + var y = this.y; + + if (min.x != null) x = Math.max(x, min.x); + if (max.x != null) x = Math.min(x, max.x); + if (min.y != null) y = Math.max(y, min.y); + if (max.y != null) y = Math.min(y, max.y); + + return new Coordinate(x, y); +} + +Coordinate.prototype.reposition = function(element) { + element.style["top"] = this.y + "px"; + element.style["left"] = this.x + "px"; +} + +Coordinate.prototype.equals = function(that) { + if (this == that) return true; + if (!that || that == null) return false; + + return this.x == that.x && this.y == that.y; +} diff --git a/vendor/assets/javascripts/drag.js b/vendor/assets/javascripts/drag.js new file mode 100644 index 0000000..9ba6746 --- /dev/null +++ b/vendor/assets/javascripts/drag.js @@ -0,0 +1,229 @@ +/* + * drag.js - click & drag DOM elements + * + * originally based on Youngpup's dom-drag.js, www.youngpup.net + */ + +var Drag = { + BIG_Z_INDEX : 10000, + group : null, + isDragging : false, + + makeDraggable : function(group) { + group.handle = group; + group.handle.group = group; + + group.minX = null; + group.minY = null; + group.maxX = null; + group.maxY = null; + group.threshold = 0; + group.thresholdY = 0; + group.thresholdX = 0; + + group.onDragStart = new Function(); + group.onDragEnd = new Function(); + group.onDrag = new Function(); + + // TODO: use element.prototype.myFunc + group.setDragHandle = Drag.setDragHandle; + group.setDragThreshold = Drag.setDragThreshold; + group.setDragThresholdX = Drag.setDragThresholdX; + group.setDragThresholdY = Drag.setDragThresholdY; + group.constrain = Drag.constrain; + group.constrainVertical = Drag.constrainVertical; + group.constrainHorizontal = Drag.constrainHorizontal; + + group.onmousedown = Drag.onMouseDown; + }, + + constrainVertical : function() { + var nwOffset = Coordinates.northwestOffset(this, true); + this.minX = nwOffset.x; + this.maxX = nwOffset.x; + }, + + constrainHorizontal : function() { + var nwOffset = Coordinates.northwestOffset(this, true); + this.minY = nwOffset.y; + this.maxY = nwOffset.y; + }, + + constrain : function(nwPosition, sePosition) { + this.minX = nwPosition.x; + this.minY = nwPosition.y; + this.maxX = sePosition.x; + this.maxY = sePosition.y; + }, + + setDragHandle : function(handle) { + if (handle && handle != null) + this.handle = handle; + else + this.handle = this; + + this.handle.group = this; + this.onmousedown = null; + this.handle.onmousedown = Drag.onMouseDown; + }, + + setDragThreshold : function(threshold) { + if (isNaN(parseInt(threshold))) return; + + this.threshold = threshold; + }, + + setDragThresholdX : function(threshold) { + if (isNaN(parseInt(threshold))) return; + + this.thresholdX = threshold; + }, + + setDragThresholdY : function(threshold) { + if (isNaN(parseInt(threshold))) return; + + this.thresholdY = threshold; + }, + + onMouseDown : function(event) { + event = Drag.fixEvent(event); + Drag.group = this.group; + + var group = this.group; + var mouse = event.windowCoordinate; + var nwOffset = Coordinates.northwestOffset(group, true); + var nwPosition = Coordinates.northwestPosition(group); + var sePosition = Coordinates.southeastPosition(group); + var seOffset = Coordinates.southeastOffset(group, true); + + group.originalOpacity = group.style.opacity; + group.originalZIndex = group.style.zIndex; + group.initialWindowCoordinate = mouse; + // TODO: need a better name, but don't yet understand how it + // participates in the magic while dragging + group.dragCoordinate = mouse; + + Drag.showStatus(mouse, nwPosition, sePosition, nwOffset, seOffset); + + group.onDragStart(nwPosition, sePosition, nwOffset, seOffset); + + // TODO: need better constraint API + if (group.minX != null) + group.minMouseX = mouse.x - nwPosition.x + + group.minX - nwOffset.x; + if (group.maxX != null) + group.maxMouseX = group.minMouseX + group.maxX - group.minX; + + if (group.minY != null) + group.minMouseY = mouse.y - nwPosition.y + + group.minY - nwOffset.y; + if (group.maxY != null) + group.maxMouseY = group.minMouseY + group.maxY - group.minY; + + group.mouseMin = new Coordinate(group.minMouseX, group.minMouseY); + group.mouseMax = new Coordinate(group.maxMouseX, group.maxMouseY); + + document.onmousemove = Drag.onMouseMove; + document.onmouseup = Drag.onMouseUp; + + return false; + }, + + showStatus : function(mouse, nwPosition, sePosition, nwOffset, seOffset) { + window.status = + "mouse: " + mouse.toString() + " " + + "NW pos: " + nwPosition.toString() + " " + + "SE pos: " + sePosition.toString() + " " + + "NW offset: " + nwOffset.toString() + " " + + "SE offset: " + seOffset.toString(); + }, + + onMouseMove : function(event) { + event = Drag.fixEvent(event); + var group = Drag.group; + var mouse = event.windowCoordinate; + var nwOffset = Coordinates.northwestOffset(group, true); + var nwPosition = Coordinates.northwestPosition(group); + var sePosition = Coordinates.southeastPosition(group); + var seOffset = Coordinates.southeastOffset(group, true); + + Drag.showStatus(mouse, nwPosition, sePosition, nwOffset, seOffset); + + if (!Drag.isDragging) { + if (group.threshold > 0) { + var distance = group.initialWindowCoordinate.distance( + mouse); + if (distance < group.threshold) return true; + } else if (group.thresholdY > 0) { + var deltaY = Math.abs(group.initialWindowCoordinate.y - mouse.y); + if (deltaY < group.thresholdY) return true; + } else if (group.thresholdX > 0) { + var deltaX = Math.abs(group.initialWindowCoordinate.x - mouse.x); + if (deltaX < group.thresholdX) return true; + } + + Drag.isDragging = true; + group.style["zIndex"] = Drag.BIG_Z_INDEX; + group.style["opacity"] = 0.75; + } + + // TODO: need better constraint API + var adjusted = mouse.constrain(group.mouseMin, group.mouseMax); + nwPosition = nwPosition.plus(adjusted.minus(group.dragCoordinate)); + nwPosition.reposition(group); + group.dragCoordinate = adjusted; + + // once dragging has started, the position of the group + // relative to the mouse should stay fixed. They can get out + // of sync if the DOM is manipulated while dragging, so we + // correct the error here + // + // TODO: what we really want to do is find the offset from + // our corner to the mouse coordinate and adjust to keep it + // the same + var offsetBefore = Coordinates.northwestOffset(group); + group.onDrag(nwPosition, sePosition, nwOffset, seOffset); + var offsetAfter = Coordinates.northwestOffset(group); + + if (!offsetBefore.equals(offsetAfter)) { + var errorDelta = offsetBefore.minus(offsetAfter); + nwPosition = Coordinates.northwestPosition(group).plus(errorDelta); + nwPosition.reposition(group); + } + + return false; + }, + + onMouseUp : function(event) { + event = Drag.fixEvent(event); + var group = Drag.group; + + var mouse = event.windowCoordinate; + var nwOffset = Coordinates.northwestOffset(group, true); + var nwPosition = Coordinates.northwestPosition(group); + var sePosition = Coordinates.southeastPosition(group); + var seOffset = Coordinates.southeastOffset(group, true); + + document.onmousemove = null; + document.onmouseup = null; + group.onDragEnd(nwPosition, sePosition, nwOffset, seOffset); + + if (Drag.isDragging) { + // restoring zIndex before opacity avoids visual flicker in Firefox + group.style["zIndex"] = group.originalZIndex; + group.style["opacity"] = group.originalOpacity; + } + + Drag.group = null; + Drag.isDragging = false; + + return false; + }, + + fixEvent : function(event) { + if (typeof event == 'undefined') event = window.event; + Coordinates.fixEvent(event); + + return event; + } +}; diff --git a/vendor/assets/javascripts/dragsort.js b/vendor/assets/javascripts/dragsort.js new file mode 100644 index 0000000..6356663 --- /dev/null +++ b/vendor/assets/javascripts/dragsort.js @@ -0,0 +1,234 @@ +// TODO: refactor away duplicationg in DragSort and DragSortX + +var BetterDragSort = { + + makeListSortable : function(list) { + var items = list.getElementsByTagName("li"); + + for (var i = 0; i < items.length; i++) { + BetterDragSort.makeItemSortable(items[i]); + } + }, + + makeItemSortable : function(item) { + Drag.makeDraggable(item); + item.setDragThresholdY(5); + + item.onDragStart = BetterDragSort.onDragStart; + item.onDrag = BetterDragSort.onDrag; + item.onDragEnd = BetterDragSort.onDragEnd; + }, + + onDragStart : function(nwPosition, sePosition, nwOffset, seOffset) { + var items = this.parentNode.getElementsByTagName("li"); + var minOffset = Coordinates.northwestOffset(items[0], true); + var maxOffset = minOffset; + for (var i = 0; i < items.length; i++) { + maxOffset = maxOffset.max(Coordinates.northwestOffset(items[i], true)); + } + this.constrain(minOffset, maxOffset); + }, + + onDrag : function(nwPosition, sePosition, nwOffset, seOffset) { + var swapper = null; + + var next = DragUtils.nextItem(this); + while (next != null) { + var nextNWOffset = Coordinates.northwestOffset(next, true); + var nextSEOffset = Coordinates.southeastOffset(next, true); + + if (nwOffset.y >= (nextNWOffset.y - 2) && + nwOffset.y <= (nextSEOffset.y + 2) && + nwOffset.x >= (nextNWOffset.x - 2) && + nwOffset.x <= (nextSEOffset.x + 2)) { + var swapper = next; + break; + } + var next = DragUtils.nextItem(next); + } + if (swapper != null) { + BetterDragSort.moveAfter(this, swapper); + return; + } + + var previous = DragUtils.previousItem(this); + while (previous != null) { + var previousNWOffset = Coordinates.northwestOffset(previous, true); + var previousSEOffset = Coordinates.southeastOffset(previous, true); + + var fudgeFactor = 2; + if (nwOffset.y >= (previousNWOffset.y - fudgeFactor) && + nwOffset.y <= (previousSEOffset.y + fudgeFactor) && + nwOffset.x >= (previousNWOffset.x - fudgeFactor) && + nwOffset.x <= (previousSEOffset.x + fudgeFactor)) { + var swapper = previous; + break; + } + var previous = DragUtils.previousItem(previous); + } + if (swapper != null) { + BetterDragSort.moveBefore(this, swapper); + return; + } + }, + + moveAfter : function(item1, item2) { + var parent = item1.parentNode; + parent.removeChild(item1); + parent.insertBefore(item1, item2.nextSibling); + + item1.style["top"] = "0px"; + item1.style["left"] = "0px"; + }, + + moveBefore : function(item1, item2) { + var parent = item1.parentNode; + parent.removeChild(item1); + parent.insertBefore(item1, item2); + + item1.style["top"] = "0px"; + item1.style["left"] = "0px"; + }, + + onDragEnd : function(nwPosition, sePosition, nwOffset, seOffset) { + this.style["top"] = "0px"; + this.style["left"] = "0px"; + } +}; + + +var DragSort = { + + makeListSortable : function(list) { + var items = list.getElementsByTagName("li"); + + for (var i = 0; i < items.length; i++) { + DragSort.makeItemSortable(items[i]); + } + }, + + makeItemSortable : function(item) { + Drag.makeDraggable(item); + item.setDragThresholdY(5); + + item.onDragStart = DragSort.onDragStart; + item.onDrag = DragSort.onDrag; + item.onDragEnd = DragSort.onDragEnd; + }, + + onDragStart : function(nwPosition, sePosition, nwOffset, seOffset) { + var items = this.parentNode.getElementsByTagName("li"); + var minOffset = Coordinates.northwestOffset(items[0], true); + var maxOffset = Coordinates.northwestOffset(items[items.length - 1], true); + this.constrain(minOffset, maxOffset); + }, + + onDrag : function(nwPosition, sePosition, nwOffset, seOffset) { + var parent = this.parentNode; + + var item = this; + var next = DragUtils.nextItem(item); + while (next != null && this.offsetTop >= next.offsetTop - 2) { + var item = next; + var next = DragUtils.nextItem(item); + } + if (this != item) { + DragUtils.swap(this, next); + return; + } + + var item = this; + var previous = DragUtils.previousItem(item); + while (previous != null && this.offsetTop <= previous.offsetTop + 2) { + var item = previous; + var previous = DragUtils.previousItem(item); + } + if (this != item) { + DragUtils.swap(this, item); + return; + } + }, + + onDragEnd : function(nwPosition, sePosition, nwOffset, seOffset) { + this.style["top"] = "0px"; + this.style["left"] = "0px"; + } +}; + +var DragSortX = { + + makeListSortable : function(list) { + var items = list.getElementsByTagName("li"); + + var minOffset = Coordinates.northwestOffset(items[0], true); + var maxOffset = Coordinates.northwestOffset(items[items.length - 1], true); + + for (var i = 0; i < items.length; i++) { + Drag.makeDraggable(items[i]); + items[i].constrain(minOffset, maxOffset); + items[i].setDragThresholdX(5); + + items[i].onDrag = DragSortX.onDrag; + + items[i].onDragEnd = function(nwPosition, sePosition, nwOffset, seOffset) { + this.style["top"] = "0px"; + this.style["left"] = "0px"; + }; + } + }, + + onDrag : function(nwPosition, sePosition, nwOffset, seOffset) { + var parent = this.parentNode; + + var item = this; + var next = DragUtils.nextItem(item); + while (next != null && this.offsetLeft >= next.offsetLeft - 2) { + var item = next; + var next = DragUtils.nextItem(item); + } + if (this != item) { + DragUtils.swap(this, next); + return; + } + + var item = this; + var previous = DragUtils.previousItem(item); + while (previous != null && this.offsetLeft <= previous.offsetLeft + 2) { + var item = previous; + var previous = DragUtils.previousItem(item); + } + if (this != item) { + DragUtils.swap(this, item); + return; + } + } +}; + +var DragUtils = { + swap : function(item1, item2) { + var parent = item1.parentNode; + parent.removeChild(item1); + parent.insertBefore(item1, item2); + + item1.style["top"] = "0px"; + item1.style["left"] = "0px"; + }, + + nextItem : function(item) { + var sibling = item.nextSibling; + while (sibling != null) { + if (sibling.nodeName == item.nodeName) return sibling; + sibling = sibling.nextSibling; + } + return null; + }, + + previousItem : function(item) { + var sibling = item.previousSibling; + while (sibling != null) { + if (sibling.nodeName == item.nodeName) return sibling; + sibling = sibling.previousSibling; + } + return null; + } +}; |