Skip to content

Commit

Permalink
Update game page design (#632)
Browse files Browse the repository at this point in the history
* Update game page design

Adds game box art, condenses export / race buttons, adds Twitch links,
and adds some basic information about median run lengths to the game
page.

* Clean up info bubbles
  • Loading branch information
glacials authored Oct 18, 2019
1 parent ee6dda0 commit afbeb69
Show file tree
Hide file tree
Showing 19 changed files with 162 additions and 121 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ gem 'cancancan'
gem 'doorkeeper'

# db
gem 'active_median'
gem 'active_record_union'
gem 'activerecord-import'
gem 'aws-sdk-rails'
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ GEM
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
active_median (0.2.3)
activesupport (>= 5)
active_record_union (1.3.0)
activerecord (>= 4.0)
activejob (6.0.0)
Expand Down Expand Up @@ -453,6 +455,7 @@ PLATFORMS
ruby

DEPENDENCIES
active_median
active_record_union
activerecord-import
activerecord-nulldb-adapter
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/search_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def index
@results = {}
redirect_to(games_path) && return if @query.blank?

@results[:games] = Game.search(@query).order(:name).includes(:categories)
@results[:games] = Game.search(@query).includes(:categories)
@results[:users] = User.search(@query).includes(:games)
end

Expand Down
4 changes: 2 additions & 2 deletions app/javascript/like.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const setLiked = (likeButton) => {
return
}

likeButton.classList.add('btn-light')
likeButton.classList.add('btn-dark')
likeButton.classList.remove('btn-outline-light')
likesCount.textContent = ++likesCount.dataset.value
}
Expand All @@ -48,7 +48,7 @@ const setNotLiked = (likeButton) => {
return
}

likeButton.classList.remove('btn-light')
likeButton.classList.remove('btn-dark')
likeButton.classList.add('btn-outline-light')
likesCount.textContent = --likesCount.dataset.value
}
14 changes: 14 additions & 0 deletions app/models/category.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ class Category < ApplicationRecord
has_many :rivalries, dependent: :destroy
has_many :races, dependent: :nullify, class_name: 'Race'

has_many :run_histories, through: :runs, source: :histories

has_many :users, through: :runs

has_one :srdc, class_name: 'SpeedrunDotComCategory', dependent: :destroy
Expand Down Expand Up @@ -104,4 +106,16 @@ def route

Run.find(result.id)
end

# median_duration returns the median duration of completed attempts for this category. If attempt_number is given, it
# returns the median duration of completed attempts which had that attempt number.
def median_duration(timing, attempt_number: nil)
relation = run_histories.joins(:run).where(
runs: {archived: false}
).where.not(runs: {user: nil}).where.not(Run.duration_type(timing) => nil).where.not(Run.duration_type(timing) => 0)

return Duration.new(relation.median("run_histories.#{Run.duration_type(timing)}")) if attempt_number.nil?

Duration.new(relation.where(attempt_number: attempt_number).median("run_histories.#{Run.duration_type(timing)}"))
end
end
2 changes: 1 addition & 1 deletion app/models/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class Run < ApplicationRecord

scope :by_game, ->(game_or_games) { joins(:category).where(categories: {game_id: game_or_games}) }
scope :by_category, ->(category) { where(category: category) }
scope :nonempty, -> { where('realtime_duration_ms != 0') }
scope :nonempty, -> { where.not(realtime_duration_ms: 0) }
scope :owned, -> { where.not(user: nil) }
scope :unarchived, -> { where(archived: false).where.not(user: nil) }
scope :categorized, lambda {
Expand Down
125 changes: 84 additions & 41 deletions app/views/games/categories/_title.slim
Original file line number Diff line number Diff line change
@@ -1,41 +1,84 @@
h1 = @category.game
- if @category.game.aliases.where.not(name: @category.game.name).present?
h6 Also known as:
ul
- @category.game.aliases.where.not(name: @category.game.name).each do |game_alias|
li = game_alias
h6
- if @on_game_page && @category.game.srdc.try(:url).present?
a.btn.btn-dark.mr-2.tip href[email protected] title='See on Speedrun.com'
= image_tag(asset_path('srdc.png'), style: 'height: 0.8em')
- elsif !@on_game_page && @category.srdc.try(:url).present?
a.btn.btn-dark.mr-2.tip href[email protected] title='See on Speedrun.com'
= image_tag(asset_path('srdc.png'), style: 'height: 0.8em')
- if @category.game.srl.present?
a.btn.btn-dark.mr-2.tip href[email protected] title='See on SpeedRunsLive'
= image_tag(asset_path('srl.png'))
- if can?(:edit, @category.game)
a.btn.btn-outline-light href=edit_game_path(@category.game)
=> icon('fas', 'edit')
span Edit
article data-turbolinks-temporary=true
.row
.col-sm-3.mb-3
.statcard.p-3
h3.statcard-number = @game.users.count
span.statcard-desc = 'Runner'.pluralize(@game.users.count)
.col-sm-3.mb-3
.statcard.p-3
h3.statcard-number = @game.runs.count
span.statcard-desc = 'Run'.pluralize(@game.runs.count)
.col-sm-3.mb-3
.statcard.p-3
h3.statcard-number = @game.categories.count
span.statcard-desc = 'Category'.pluralize(@game.categories.count)
- if @game.runs.any?
.col-sm-3.mb-3
.statcard.p-3
h3.statcard-number = time_ago_in_words(@game.runs.order(created_at: :desc).first.created_at)
span.statcard-desc
span> Time since
= link_to('last run', @game.runs.order(created_at: :desc).first, class: 'text-muted')
.card.mb-3
.row.no-gutters
- if @game.srdc&.cover_url
.col-md-4 style='max-width: 200px'
img.card-img src[email protected]_url
.col-md-8
.card-body
h1.card-title = @category.game
h6
.btn-group.mr-2
- if @on_game_page && @category.game.srdc.try(:url).present?
a.btn.btn-dark.tip href[email protected] title='See on Speedrun.com'
= image_tag(asset_path('srdc.png'), style: 'height: 0.8em')
- elsif !@on_game_page && @category.srdc.try(:url).present?
a.btn.btn-dark.tip href[email protected] title='See on Speedrun.com'
= image_tag(asset_path('srdc.png'), style: 'height: 0.8em')
- if @category.game.srdc&.twitch_name
a.btn.btn-dark.tip title='See on Twitch' href="https://www.twitch.tv/directory/game/#{@category.game.srdc.twitch_name}"
.text-light = icon('fab', 'twitch')
- if @category.game.srl.present?
a.btn.btn-dark.tip href[email protected] title='See on SpeedRunsLive'
= image_tag(asset_path('srl.png'))
- if @category.route
span.mr-2
= render partial: 'runs/export_button', locals: { \
run: @category.route, button_text: "#{@category} splits", force_route_only: true \
}
span#vue-race.mr-2 = render partial: 'races/create', locals: {game: @game, category: @category}
- if can?(:edit, @category.game)
a.btn.btn-dark.mr-2 href=edit_game_path(@category.game)
=> icon('fas', 'edit')
span Edit
.row
.col-sm-3.mb-3
.statcard.p-3
h3.statcard-number = @game.users.count
span.statcard-desc = 'Runner'.pluralize(@game.users.count)
.col-sm-3.mb-3
.statcard.p-3
h3.statcard-number = @game.runs.count
span.statcard-desc = 'Run'.pluralize(@game.runs.count)
.col-sm-3.mb-3
.statcard.p-3
h3.statcard-number = @game.categories.count
span.statcard-desc = 'Category'.pluralize(@game.categories.count)
- if @game.runs.any?
.col-sm-3.mb-3
.statcard.p-3
h3.statcard-number = time_ago_in_words(@game.runs.order(created_at: :desc).first.created_at)
span.statcard-desc
span> Since
= link_to('last run', @game.runs.order(created_at: :desc).first, class: 'text-muted')
.col-sm-3.mb-3
.statcard.p-3
h3.statcard-number = @category.median_duration(timing).format
span.statcard-desc
' Median run length
span.tip.text-secondary(
title='Half of all runs are slower, and half are faster, than this time.'
) = icon('fas', 'info-circle')
.col-sm-3.mb-3
.statcard.p-3
h3.statcard-number = @category.median_duration(timing, attempt_number: 1).format
span.statcard-desc
' Median blind run
span.tip.text-secondary(
title='Half of all first attempts are slower, and half are faster, than this time.'
) = icon('fas', 'info-circle')
.col-sm-3.mb-3
.statcard.p-3
h3.statcard-number = @category.median_duration(timing, attempt_number: 10).format
span.statcard-desc
' Median 10th attempt
span.tip.text-secondary(
title='Half of all 10th attempts are slower, and half are faster, than this time.'
) = icon('fas', 'info-circle')
.col-sm-3.mb-3
.statcard.p-3
h3.statcard-number = @category.median_duration(timing, attempt_number: 100).format
span.statcard-desc
' Median 100th attempt
span.tip.text-secondary(
title='Half of all 100th attempts are slower, and half are faster, than this time.'
) = icon('fas', 'info-circle')
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
li.breadcrumb-item = link_to(@category.game, game_path(@category.game))
li.breadcrumb-item = link_to(@category, game_category_path(@category.game, @category))
li.breadcrumb-item = link_to('Sum of Best Leaderboard', game_category_sum_of_bests_path(@category.game, @category))
= render partial: 'games/categories/title', locals: {category: @category}
.row.mx-2: .col-md-12 = render partial: 'games/categories/title', locals: {timing: timing, category: @category}

article data-turbolinks-temporary=true
.card
Expand All @@ -24,7 +24,7 @@ article data-turbolinks-temporary=true
tr
th
th.align-left Runner
th.align-left Sum of Best
th.align-left Sum of best
th.align-left PB
th.align-left
| Potential save
Expand Down
17 changes: 3 additions & 14 deletions app/views/games/categories/show.slim
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- timing = params[:timing] || Run::REAL
- content_for(:title, @game)
- content_for(:header) do
ol.breadcrumb.shadow
Expand All @@ -6,21 +7,9 @@
li.breadcrumb-item = link_to(@category.game, game_path(@category.game))
- unless @on_game_page
li.breadcrumb-item = link_to @category, game_category_path(@game, @category)
/ Specify the full partial path because this view is used by different controllers
= render partial: 'games/categories/title', locals: {category: @category}


#vue-race.row.mx-2
- route = @category.route
- if @category.route.present?
.col-md-4: .card.mb-4
h5.card-header #{@category.name} Route
.card-body
p.card-text Automatically determined based on popularity
= render partial: 'runs/export_button', locals: {run: route, button_text: 'Download splits', force_route_only: true}

- if current_user.present?
= render partial: 'races/create', locals: {game: @game, category: @category}
/ Specify the full partial path because this view is used by different controllers
.row.mx-2: .col-md-12 = render partial: 'games/categories/title', locals: {timing: timing, category: @category}

.row.mx-2
.col-md-6.mb-3: .card
Expand Down
2 changes: 0 additions & 2 deletions app/views/games/edit.slim
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
li.breadcrumb-item = link_to('Games', games_path)
li.breadcrumb-item = link_to(@game, game_path(@game))
li.breadcrumb-item = link_to('Edit', edit_game_path(@game))
h3 Edit game
h5 #{link_to @game, @game, class: 'stealth-link game-link'}
article
= form_for GameAlias.new, url: game_aliases_path(@game) do |f|
h3 Merge games
Expand Down
48 changes: 22 additions & 26 deletions app/views/races/_create.slim
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,25 @@

// Make sure to have #vue-race be defined before calling this partial
race-create inline-template=true game-id=game.id category-id=category.id
.col-md-4 v-cloak=true
.card.mb-4
h5.card-header Create race
.card-body
p.card-text Race #{game.to_s || ''} against others
.btn-group
button.btn.btn-primary(
type='button'
@click='createPublic'
:disabled='loading'
:title='error'
v-tippy=true
)
template v-if="loading" = render partial: 'shared/spinner'
span.text-danger v-else-if='error' => icon('fas', 'exclamation-triangle')
template v-else=true => icon('fas', 'flag-checkered')
' Create public race
button.btn.btn-primary.dropdown-toggle.dropdown-toggle-split(
type='button'
data={toggle: 'dropdown'}
aria={haspopup: true, expanded: false}
)
span.sr-only Toggle dropdown
.dropdown-menu.bg-dark
button.dropdown-item.text-secondary @click='createInviteOnly' Create invite-only race
button.dropdown-item.text-secondary @click='createSecret' Create secret race
.btn-group v-cloak=true
button.btn.btn-dark(
type='button'
@click='createPublic'
:disabled='loading'
:title='error'
v-tippy=true
)
template v-if="loading" = render partial: 'shared/spinner'
span.text-danger v-else-if='error' => icon('fas', 'exclamation-triangle')
template v-else=true => icon('fas', 'flag-checkered')
' Create race
button.btn.btn-dark.dropdown-toggle.dropdown-toggle-split(
type='button'
data={toggle: 'dropdown'}
aria={haspopup: true, expanded: false}
)
span.sr-only Toggle dropdown
.dropdown-menu.bg-dark
button.dropdown-item.text-secondary @click='createPublic' Create public race
button.dropdown-item.text-secondary @click='createInviteOnly' Create invite-only race
button.dropdown-item.text-secondary @click='createSecret' Create secret race
2 changes: 1 addition & 1 deletion app/views/runs/_compare_button.slim
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
- if run.category.present?
.btn-group
button#compare.btn.btn-outline-light.dropdown-toggle(
button#compare.btn.btn-dark.dropdown-toggle(
aria={haspopup: true, expanded: false}
data={toggle: 'dropdown'}
type='button'
Expand Down
3 changes: 2 additions & 1 deletion app/views/runs/_export_button.slim
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
.btn-group
button#export-link.btn.btn-outline-light.dropdown-toggle(
button#export-link.btn.btn-dark.dropdown-toggle.tip(
href='#'
role='button'
aria={haspopup: true, expanded: false}
data={toggle: 'dropdown'}
title=local_assigns.fetch(:tooltip_text, nil)
)
=> icon('fas', 'download')
= local_assigns.fetch(:button_text, 'Export')
Expand Down
8 changes: 4 additions & 4 deletions app/views/runs/_title.slim
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ h5
.btn-toolbar role='toolbar' aria={label: 'Run navigation'}
.btn-group.m-2 role='group' aria={label: 'Run navigation'}
.btn-group role='group'
= render partial: 'export_button', locals: {run: run}
- if can?(:edit, run)
- if request.path_info == edit_run_path(run)
a.btn.btn-outline-light href=run_path(run)
a.btn.btn-dark href=run_path(run)
=> icon('fas', 'hand-point-left')
' Back
- else
a.btn.btn-outline-light href=edit_run_path(run)
a.btn.btn-dark href=edit_run_path(run)
=> icon('fas', 'edit')
' Edit
= render partial: 'export_button', locals: {run: run}
= render partial: 'compare_button', locals: {timing: timing, run: run, compare_runs: compare_runs}
- if run.user.nil?
#claim-nav-link-container.btn-group hidden=true
Expand All @@ -47,7 +47,7 @@ h5
- if can?(:create, RunLike)
.btn-group.m-2: button#like-button.btn.tip(
data={liked: current_user.likes?(run) ? '1' : '0'}
class=(current_user.likes?(run) ? 'btn-light' : 'btn-outline-light')
class=(current_user.likes?(run) ? 'btn-dark' : 'btn-outline-light')
title=tooltip(run)
)
' 🎉
Expand Down
6 changes: 0 additions & 6 deletions app/views/runs/index.slim
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@
- if current_user.runs.any?
article
.row
.col-sm-3: .statcard.p-3
h2.statcard-number
span> = s3_bytes_used(current_user)
small.delta-indicator.delta-positive.tip-top title='Since last year'
= s3_bytes_used(current_user, since: 1.year.ago)
span.statcard-desc Space used
.col-sm-3: .statcard.p-3
h2.statcard-number
span> = current_user.runs.count
Expand Down
Loading

0 comments on commit afbeb69

Please sign in to comment.