summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorJohann Pardanaud <pardanaud.j@gmail.com>2016-02-09 00:35:42 +0100
committerJohann Pardanaud <pardanaud.j@gmail.com>2016-02-09 22:26:50 +0100
commit6d5808801f460ed2c7bb99a316687202f2f70282 (patch)
tree379598f0aeae10b2dce417e4f166e35cff349abb /app
parent16abacb1f07c866e7a1f5deb6a6bc20a4eaee71c (diff)
downloadgitlab-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.coffee5
-rw-r--r--app/assets/javascripts/profile.js.coffee50
-rw-r--r--app/assets/stylesheets/application.scss1
-rw-r--r--app/assets/stylesheets/framework/mixins.scss6
-rw-r--r--app/assets/stylesheets/pages/profile.scss36
-rw-r--r--app/controllers/profiles_controller.rb3
-rw-r--r--app/models/user.rb10
-rw-r--r--app/uploaders/avatar_uploader.rb9
-rw-r--r--app/views/profiles/show.html.haml21
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 ...
&nbsp;
- %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
+ &times;
+ %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