diff options
author | Johann Pardanaud <pardanaud.j@gmail.com> | 2016-02-09 00:35:42 +0100 |
---|---|---|
committer | Johann Pardanaud <pardanaud.j@gmail.com> | 2016-02-09 22:26:50 +0100 |
commit | 6d5808801f460ed2c7bb99a316687202f2f70282 (patch) | |
tree | 379598f0aeae10b2dce417e4f166e35cff349abb /app | |
parent | 16abacb1f07c866e7a1f5deb6a6bc20a4eaee71c (diff) | |
download | gitlab-ce-6d5808801f460ed2c7bb99a316687202f2f70282.tar.gz |
Fix #7959: Fix avatar stretching by providing a cropping feature
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/application.js.coffee | 5 | ||||
-rw-r--r-- | app/assets/javascripts/profile.js.coffee | 50 | ||||
-rw-r--r-- | app/assets/stylesheets/application.scss | 1 | ||||
-rw-r--r-- | app/assets/stylesheets/framework/mixins.scss | 6 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/profile.scss | 36 | ||||
-rw-r--r-- | app/controllers/profiles_controller.rb | 3 | ||||
-rw-r--r-- | app/models/user.rb | 10 | ||||
-rw-r--r-- | app/uploaders/avatar_uploader.rb | 9 | ||||
-rw-r--r-- | app/views/profiles/show.html.haml | 21 |
9 files changed, 133 insertions, 8 deletions
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 367bd098bfd..5463397f475 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -44,6 +44,7 @@ #= require jquery.nicescroll #= require_tree . #= require fuzzaldrin-plus +#= require cropper.js window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() @@ -210,7 +211,7 @@ $ -> $this = $(this) $this.attr 'value', $this.val() return - + $(document) .off 'keyup', 'input[type="search"]' .on 'keyup', 'input[type="search"]' , (e) -> @@ -253,7 +254,7 @@ $ -> $('.page-with-sidebar') .removeClass('right-sidebar-collapsed') .addClass('right-sidebar-expanded') - $.cookie("collapsed_gutter", + $.cookie("collapsed_gutter", $('.right-sidebar') .hasClass('right-sidebar-collapsed'), { path: '/' }) diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index bb0b66b86e1..01def4fe63e 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -16,11 +16,51 @@ class @Profile $('.update-notifications').on 'ajax:complete', -> $(this).find('.btn-save').enable() - $('.js-choose-user-avatar-button').bind "click", -> - form = $(this).closest("form") - form.find(".js-user-avatar-input").click() + # Avatar management + + $avatarInput = $('.js-user-avatar-input') + $filename = $('.js-avatar-filename') + $modalCrop = $('.modal-profile-crop') + $modalCropImg = $('.modal-profile-crop-image') + + $('.js-choose-user-avatar-button').on "click", -> + $form = $(this).closest("form") + $form.find(".js-user-avatar-input").click() + + $modalCrop.on 'shown.bs.modal', -> + setTimeout ( -> # The cropper must be asynchronously initialized + $modalCropImg.cropper + aspectRatio: 1 + autoCropArea: 1 + modal: false + scalable: false + rotatable: false + zoomable: false + + crop: (event) -> + ['x', 'y'].forEach (key) -> + $("#user_avatar_crop_#{key}").val(Math.floor(event[key])) + $("#user_avatar_crop_size").val(Math.floor(event.width)) + ), 0 + + $modalCrop.on 'hidden.bs.modal', -> + $modalCropImg.attr('src', '').cropper('destroy') + $avatarInput.val('') + $filename.text($filename.data('label')) - $('.js-user-avatar-input').bind "change", -> + $('.js-upload-user-avatar').on 'click', -> + $('.edit_user').submit() + + $avatarInput.on "change", -> form = $(this).closest("form") filename = $(this).val().replace(/^.*[\\\/]/, '') - form.find(".js-avatar-filename").text(filename) + $filename.text(filename) + + reader = new FileReader + + reader.onload = (event) -> + $modalCrop.modal('show') + $modalCropImg.attr('src', event.target.result) + + fileData = reader.readAsDataURL(this.files[0]) + diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 0c0451fe4dd..f51054f13dc 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -9,6 +9,7 @@ *= require_self *= require dropzone/basic *= require cal-heatmap + *= require cropper.css */ /* diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 1d5000fe388..368bbfe5355 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -41,6 +41,12 @@ transition: $transition; } +@mixin transform($transform) { + -webkit-transform: $transform; + -ms-transform: $transform; + transform: $transform; +} + /** * Prefilled mixins * Mixins with fixed values diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 95fc26a608a..67fbbef73ae 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -69,3 +69,39 @@ text-decoration: none; } } + +.modal-profile-crop { + .modal-dialog { + width: 500px; + } + + .modal-body { + p { + display: table; + margin: auto; + overflow: hidden; + } + + img { + display: block; + max-width: 400px; + max-height: 400px; + } + + .cropper-bg { + background: none; + } + + .cropper-crop-box { + box-sizing: content-box; + border: 999px solid transparentize(#ccc, 0.5); + @include transform(translate(-999px, -999px)); + } + } +} + +@media (max-width: 520px) { + .modal-profile-crop .modal-dialog { + width: auto; + } +} diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 28803164fcf..fa7a1148961 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -65,6 +65,9 @@ class ProfilesController < Profiles::ApplicationController def user_params params.require(:user).permit( + :avatar_crop_x, + :avatar_crop_y, + :avatar_crop_size, :avatar, :bio, :email, diff --git a/app/models/user.rb b/app/models/user.rb index 234c1cd89f9..a8e602f925a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -98,6 +98,11 @@ class User < ActiveRecord::Base # Virtual attribute for authenticating by either username or email attr_accessor :login + # Virtual attributes to define avatar cropping + [:avatar_crop_x, :avatar_crop_y, :avatar_crop_size].each do |field| + attr_accessor field + end + # # Relations # @@ -146,6 +151,11 @@ class User < ActiveRecord::Base # Validations # validates :name, presence: true + + [:avatar_crop_x, :avatar_crop_y, :avatar_crop_size].each do |field| + validates field, numericality: { only_integer: true }, allow_blank: true + end + # Note that a 'uniqueness' and presence check is provided by devise :validatable for email. We do not need to # duplicate that here as the validation framework will have duplicate errors in the event of a failure. validates :email, presence: true, email: { strict_mode: true } diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb index 6135c3ad96f..ec9e285b266 100644 --- a/app/uploaders/avatar_uploader.rb +++ b/app/uploaders/avatar_uploader.rb @@ -2,11 +2,20 @@ class AvatarUploader < CarrierWave::Uploader::Base include UploaderHelper + include CarrierWave::MiniMagick storage :file after :store, :reset_events_cache + process :cropper + + def cropper + manipulate! do |img| + img.crop "#{model.avatar_crop_size}x#{model.avatar_crop_size}+#{model.avatar_crop_x}+#{model.avatar_crop_y}" + end + end + def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index add9a00138b..6686c037b60 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -88,8 +88,11 @@ %i.fa.fa-paperclip %span Choose File ... - %span.file_name.js-avatar-filename File name... + %span.file_name.js-avatar-filename{:'data-label' => 'File name...'} File name... = f.file_field :avatar, class: "js-user-avatar-input hidden" + = f.hidden_field :avatar_crop_x + = f.hidden_field :avatar_crop_y + = f.hidden_field :avatar_crop_size .light The maximum file size allowed is 200KB. - if @user.avatar? %hr @@ -99,3 +102,19 @@ .form-actions = f.submit 'Save changes', class: "btn btn-success" = link_to "Cancel", user_path(current_user), class: "btn btn-cancel" + +.modal.modal-profile-crop + .modal-dialog + .modal-content + .modal-header + %button.close{:type => "button", :'data-dismiss' => "modal"} + %span + × + %h4.modal-title + Crop your new profile picture + .modal-body + %p + %img.modal-profile-crop-image + .modal-footer + %button.btn.btn-primary.js-upload-user-avatar{:type => "button"} + Set new profile picture |