diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/application.js.coffee | 1 | ||||
-rw-r--r-- | app/assets/javascripts/profile.js.coffee | 49 | ||||
-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 | 8 | ||||
-rw-r--r-- | app/uploaders/avatar_uploader.rb | 11 | ||||
-rw-r--r-- | app/views/profiles/show.html.haml | 19 |
9 files changed, 129 insertions, 5 deletions
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 0651bd20d26..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() diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index bb0b66b86e1..69d590a7533 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -16,11 +16,50 @@ 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 + 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.data('label', $filename.text()).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 4767c65d9a7..de4d9fd80fa 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -78,3 +78,39 @@ max-width: 750px; margin: auto; } + +.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 02ff2456f2b..6baf2468ade 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -98,6 +98,9 @@ class User < ActiveRecord::Base # Virtual attribute for authenticating by either username or email attr_accessor :login + # Virtual attributes to define avatar cropping + attr_accessor :avatar_crop_x, :avatar_crop_y, :avatar_crop_size + # # Relations # @@ -163,6 +166,11 @@ class User < ActiveRecord::Base validate :owns_public_email, if: ->(user) { user.public_email_changed? } validates :avatar, file_size: { maximum: 200.kilobytes.to_i } + validates :avatar_crop_x, :avatar_crop_y, :avatar_crop_size, + numericality: { only_integer: true }, + presence: true, + if: ->(user) { user.avatar? } + before_validation :generate_password, on: :create before_validation :restricted_signup_domains, on: :create before_validation :sanitize_attrs diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb index 6135c3ad96f..2c72df44ff0 100644 --- a/app/uploaders/avatar_uploader.rb +++ b/app/uploaders/avatar_uploader.rb @@ -2,11 +2,22 @@ class AvatarUploader < CarrierWave::Uploader::Base include UploaderHelper + include CarrierWave::MiniMagick storage :file after :store, :reset_events_cache + process :cropper + + def cropper + return unless model.respond_to?(:avatar_crop_size) && model.valid? + + 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 5051c6bf83b..64c4bdceff9 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -90,6 +90,9 @@ %span.file_name.js-avatar-filename 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 |