Merge remote-tracking branch 'origin/fileuploads'
This commit is contained in:
commit
ddae6a8e46
|
@ -18,6 +18,7 @@ coverage/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
/public/assets
|
/public/assets
|
||||||
|
/public/system
|
||||||
|
|
||||||
# damn vim backup files
|
# damn vim backup files
|
||||||
*~
|
*~
|
||||||
|
|
2
Gemfile
2
Gemfile
|
@ -29,6 +29,8 @@ gem 'font-kit-rails'
|
||||||
gem 'nprogress-rails'
|
gem 'nprogress-rails'
|
||||||
gem 'font-awesome-rails', '~> 4.2.0.0'
|
gem 'font-awesome-rails', '~> 4.2.0.0'
|
||||||
gem 'rails-assets-growl'
|
gem 'rails-assets-growl'
|
||||||
|
gem "paperclip", "~> 4.2"
|
||||||
|
gem 'delayed_paperclip'
|
||||||
|
|
||||||
gem 'ruby-progressbar'
|
gem 'ruby-progressbar'
|
||||||
|
|
||||||
|
|
13
Gemfile.lock
13
Gemfile.lock
|
@ -67,7 +67,11 @@ GEM
|
||||||
xpath (~> 2.0)
|
xpath (~> 2.0)
|
||||||
celluloid (0.16.0)
|
celluloid (0.16.0)
|
||||||
timers (~> 4.0.0)
|
timers (~> 4.0.0)
|
||||||
|
climate_control (0.0.3)
|
||||||
|
activesupport (>= 3.0)
|
||||||
cliver (0.3.2)
|
cliver (0.3.2)
|
||||||
|
cocaine (0.5.5)
|
||||||
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
coffee-rails (4.1.0)
|
coffee-rails (4.1.0)
|
||||||
coffee-script (>= 2.2.0)
|
coffee-script (>= 2.2.0)
|
||||||
railties (>= 4.0.0, < 5.0)
|
railties (>= 4.0.0, < 5.0)
|
||||||
|
@ -80,6 +84,8 @@ GEM
|
||||||
crass (1.0.1)
|
crass (1.0.1)
|
||||||
daemons (1.1.9)
|
daemons (1.1.9)
|
||||||
database_cleaner (1.3.0)
|
database_cleaner (1.3.0)
|
||||||
|
delayed_paperclip (2.9.0)
|
||||||
|
paperclip (>= 3.3)
|
||||||
devise (3.4.1)
|
devise (3.4.1)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
orm_adapter (~> 0.1)
|
orm_adapter (~> 0.1)
|
||||||
|
@ -167,6 +173,11 @@ GEM
|
||||||
multi_json (~> 1.3)
|
multi_json (~> 1.3)
|
||||||
omniauth-oauth (~> 1.0)
|
omniauth-oauth (~> 1.0)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
|
paperclip (4.2.1)
|
||||||
|
activemodel (>= 3.0.0)
|
||||||
|
activesupport (>= 3.0.0)
|
||||||
|
cocaine (~> 0.5.3)
|
||||||
|
mime-types
|
||||||
pg (0.17.1)
|
pg (0.17.1)
|
||||||
poltergeist (1.5.1)
|
poltergeist (1.5.1)
|
||||||
capybara (~> 2.1)
|
capybara (~> 2.1)
|
||||||
|
@ -339,6 +350,7 @@ DEPENDENCIES
|
||||||
capybara
|
capybara
|
||||||
coffee-rails (~> 4.1.0)
|
coffee-rails (~> 4.1.0)
|
||||||
database_cleaner
|
database_cleaner
|
||||||
|
delayed_paperclip
|
||||||
devise
|
devise
|
||||||
factory_girl_rails
|
factory_girl_rails
|
||||||
faker
|
faker
|
||||||
|
@ -354,6 +366,7 @@ DEPENDENCIES
|
||||||
nprogress-rails
|
nprogress-rails
|
||||||
omniauth
|
omniauth
|
||||||
omniauth-twitter
|
omniauth-twitter
|
||||||
|
paperclip (~> 4.2)
|
||||||
pg
|
pg
|
||||||
poltergeist
|
poltergeist
|
||||||
questiongenerator!
|
questiongenerator!
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#= require nprogress-turbolinks
|
#= require nprogress-turbolinks
|
||||||
#= require growl
|
#= require growl
|
||||||
#= require cheet
|
#= require cheet
|
||||||
|
#= require jquery.guillotine
|
||||||
#= require_tree .
|
#= require_tree .
|
||||||
|
|
||||||
NProgress.configure
|
NProgress.configure
|
||||||
|
|
|
@ -3,3 +3,49 @@
|
||||||
if ($ "input#user_current_password").val().length == 0
|
if ($ "input#user_current_password").val().length == 0
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
$("button[data-target=#modal-passwd]").trigger 'click'
|
$("button[data-target=#modal-passwd]").trigger 'click'
|
||||||
|
|
||||||
|
|
||||||
|
# Profile pic
|
||||||
|
($ document).on 'change', 'input#user_profile_picture[type=file]', ->
|
||||||
|
input = ($ this)[0]
|
||||||
|
|
||||||
|
($ '#profile-picture-crop-controls').slideUp 400, ->
|
||||||
|
if input.files and input.files[0]
|
||||||
|
fr = new FileReader()
|
||||||
|
($ fr).on 'load', (e) ->
|
||||||
|
cropper = ($ '#profile-picture-cropper')
|
||||||
|
preview = ($ '#profile-picture-preview')
|
||||||
|
|
||||||
|
updateVars = (data, action) ->
|
||||||
|
($ '#crop_x').val Math.floor(data.x / data.scale)
|
||||||
|
($ '#crop_y').val Math.floor(data.y / data.scale)
|
||||||
|
($ '#crop_w').val Math.floor(data.w / data.scale)
|
||||||
|
($ '#crop_h').val Math.floor(data.h / data.scale)
|
||||||
|
# rx = 100 / data.w
|
||||||
|
# ry = 100 / data.h
|
||||||
|
# ($ '#profile-picture-preview').css
|
||||||
|
# width: Math.round(rx * preview[0].naturalWidth) + 'px'
|
||||||
|
# height: Math.round(ry * preview[0].naturalHeight) + 'px'
|
||||||
|
# marginLeft: '-' + Math.round(rx * data.x) + 'px'
|
||||||
|
# marginTop: '-' + Math.round(ry * data.y) + 'px'
|
||||||
|
|
||||||
|
cropper.on 'load', ->
|
||||||
|
side = if cropper[0].naturalWidth > cropper[0].naturalHeight
|
||||||
|
cropper[0].naturalHeight
|
||||||
|
else
|
||||||
|
cropper[0].naturalWidth
|
||||||
|
|
||||||
|
cropper.guillotine
|
||||||
|
width: side
|
||||||
|
height: side
|
||||||
|
onChange: updateVars
|
||||||
|
|
||||||
|
updateVars cropper.guillotine('getData'), 'drag' # just because
|
||||||
|
|
||||||
|
($ '#cropper-zoom-out').click -> cropper.guillotine 'zoomOut'
|
||||||
|
($ '#cropper-zoom-in').click -> cropper.guillotine 'zoomIn'
|
||||||
|
($ '#profile-picture-crop-controls').slideDown()
|
||||||
|
|
||||||
|
cropper.attr 'src', e.target.result
|
||||||
|
|
||||||
|
fr.readAsDataURL(input.files[0])
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
*= require rails_bootstrap_forms
|
*= require rails_bootstrap_forms
|
||||||
*= require growl
|
*= require growl
|
||||||
|
*= require jquery.guillotine
|
||||||
*= require_self
|
*= require_self
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
class UserController < ApplicationController
|
class UserController < ApplicationController
|
||||||
|
before_filter :authenticate_user!, only: %w(edit update)
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
|
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
|
||||||
@answers = @user.answers.reverse_order.paginate(page: params[:page])
|
@answers = @user.answers.reverse_order.paginate(page: params[:page])
|
||||||
|
@ -9,14 +11,17 @@ class UserController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
authenticate_user!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
authenticate_user!
|
user_attributes = params.require(:user).permit(:display_name, :profile_picture, :motivation_header, :website,
|
||||||
user_attributes = params.require(:user).permit(:display_name, :motivation_header, :website, :location, :bio)
|
:location, :bio, :crop_x, :crop_y, :crop_w, :crop_h)
|
||||||
unless current_user.update_attributes(user_attributes)
|
if current_user.update_attributes(user_attributes)
|
||||||
flash[:error] = 'fork it'
|
text = 'Your profile has been updated!'
|
||||||
|
text += ' It might take a few minutes until your new profile picture is shown everywhere.' if user_attributes[:profile_picture]
|
||||||
|
flash[:success] = text
|
||||||
|
else
|
||||||
|
flash[:error] = 'An error occurred. ;_;'
|
||||||
end
|
end
|
||||||
redirect_to edit_user_profile_path
|
redirect_to edit_user_profile_path
|
||||||
end
|
end
|
||||||
|
|
|
@ -59,11 +59,13 @@ module ApplicationHelper
|
||||||
((!current_user.nil?) && ((current_user == user) || current_user.mod?)) ? true : false
|
((!current_user.nil?) && ((current_user == user) || current_user.mod?)) ? true : false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# @deprecated Use {User#profile_picture.url} instead.
|
||||||
def gravatar_url(user)
|
def gravatar_url(user)
|
||||||
|
return user.profile_picture.url :medium
|
||||||
# return '/cage.png'
|
# return '/cage.png'
|
||||||
return '//www.gravatar.com/avatar' if user.nil?
|
#return '//www.gravatar.com/avatar' if user.nil?
|
||||||
return "//www.gravatar.com/avatar/#{Digest::MD5.hexdigest(user)}" if user.is_a? String
|
#return "//www.gravatar.com/avatar/#{Digest::MD5.hexdigest(user)}" if user.is_a? String
|
||||||
"//www.gravatar.com/avatar/#{Digest::MD5.hexdigest(user.email)}"
|
#"//www.gravatar.com/avatar/#{Digest::MD5.hexdigest(user.email)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def ios_web_app?
|
def ios_web_app?
|
||||||
|
|
|
@ -40,6 +40,12 @@ class User < ActiveRecord::Base
|
||||||
|
|
||||||
# validates :website, format: { with: WEBSITE_REGEX }
|
# validates :website, format: { with: WEBSITE_REGEX }
|
||||||
|
|
||||||
|
has_attached_file :profile_picture, styles: { large: "500x500#", medium: "256x256#", small: "80x80#" },
|
||||||
|
default_url: "/images/:style/no_avatar.png", use_timestamp: false,
|
||||||
|
processors: [:cropper]
|
||||||
|
validates_attachment_content_type :profile_picture, :content_type => /\Aimage\/.*\Z/
|
||||||
|
process_in_background :profile_picture
|
||||||
|
|
||||||
before_save do
|
before_save do
|
||||||
self.display_name = 'WRYYYYYYYY' if display_name == 'Dio Brando'
|
self.display_name = 'WRYYYYYYYY' if display_name == 'Dio Brando'
|
||||||
self.website = if website.match %r{\Ahttps?://}
|
self.website = if website.match %r{\Ahttps?://}
|
||||||
|
@ -150,4 +156,8 @@ class User < ActiveRecord::Base
|
||||||
def report_comment(report, content)
|
def report_comment(report, content)
|
||||||
ModerationComment.create!(user: self, report: report, content: content)
|
ModerationComment.create!(user: self, report: report, content: content)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cropping?
|
||||||
|
!crop_x.blank? && !crop_y.blank? && !crop_w.blank? && !crop_h.blank?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
.container.j2-page
|
.container.j2-page
|
||||||
= render 'user/settings_tabs'
|
= render 'user/settings_tabs'
|
||||||
.col-md-9.col-xs-12.col-sm-9
|
.col-md-9.col-xs-12.col-sm-9
|
||||||
.alert.alert-info
|
|
||||||
We currently only support avatars using
|
|
||||||
= succeed ',' do
|
|
||||||
%a{href: "https://en.gravatar.com"} Gravatar
|
|
||||||
after you set yours up, use the E-Mail you are using for it on here as well, we will directly use this image then!
|
|
||||||
= render 'layouts/messages'
|
= render 'layouts/messages'
|
||||||
.panel.panel-default
|
.panel.panel-default
|
||||||
.panel-body
|
.panel-body
|
||||||
|
|
|
@ -4,10 +4,27 @@
|
||||||
= render 'layouts/messages'
|
= render 'layouts/messages'
|
||||||
.panel.panel-default
|
.panel.panel-default
|
||||||
.panel-body
|
.panel-body
|
||||||
= bootstrap_form_for(current_user, url: {action: "edit"}, method: "patch") do |f|
|
= bootstrap_form_for(current_user, url: {action: "edit"}, :html => { :multipart => true }, method: "patch") do |f|
|
||||||
|
|
||||||
= f.text_field :display_name, label: "Your name"
|
= f.text_field :display_name, label: "Your name"
|
||||||
|
|
||||||
|
.media
|
||||||
|
.pull-left
|
||||||
|
%img.img-rounded.profile--img{src: current_user.profile_picture.url(:medium)}
|
||||||
|
.media-body
|
||||||
|
= f.file_field :profile_picture
|
||||||
|
|
||||||
|
.row#profile-picture-crop-controls{style: 'display: none;'}
|
||||||
|
.col-sm-10.col-md-8
|
||||||
|
%strong Adjust your new image
|
||||||
|
%img#profile-picture-cropper{src: current_user.profile_picture.url(:medium)}
|
||||||
|
.col-sm-2.col-md-4
|
||||||
|
.btn-group
|
||||||
|
%button#cropper-zoom-out.btn.btn-inverse{type: :button}
|
||||||
|
%i.fa.fa-search-minus
|
||||||
|
%button#cropper-zoom-in.btn.btn-inverse{type: :button}
|
||||||
|
%i.fa.fa-search-plus
|
||||||
|
|
||||||
= f.text_field :motivation_header, label: "Motivation header", placeholder: 'Ask me anything!'
|
= f.text_field :motivation_header, label: "Motivation header", placeholder: 'Ask me anything!'
|
||||||
|
|
||||||
= f.text_field :website, label: "Website", placeholder: 'http://bad-dragon.com'
|
= f.text_field :website, label: "Website", placeholder: 'http://bad-dragon.com'
|
||||||
|
@ -16,4 +33,7 @@
|
||||||
|
|
||||||
= f.text_area :bio, label: "Bio", placeholder: 'In Bio war ich nie gut x--DD'
|
= f.text_area :bio, label: "Bio", placeholder: 'In Bio war ich nie gut x--DD'
|
||||||
|
|
||||||
|
- for attrib in %i(crop_x crop_y crop_w crop_h)
|
||||||
|
= f.hidden_field attrib, id: attrib
|
||||||
|
|
||||||
= f.submit "Save settings", class: 'btn btn-primary'
|
= f.submit "Save settings", class: 'btn btn-primary'
|
|
@ -7,3 +7,4 @@ production:
|
||||||
:concurrency: 25
|
:concurrency: 25
|
||||||
:queues:
|
:queues:
|
||||||
- share
|
- share
|
||||||
|
- paperclip
|
|
@ -0,0 +1,11 @@
|
||||||
|
class AddAttachmentProfilePictureToUsers < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
change_table :users do |t|
|
||||||
|
t.attachment :profile_picture
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
remove_attachment :users, :profile_picture
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddProfilePictureProcessingToUsers < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :users, :profile_picture_processing, :boolean
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,10 @@
|
||||||
|
class AddCropValuesToUsers < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
# this is a ugly hack and will stay until I find a way to pass parameters
|
||||||
|
# to the paperclip Sidekiq worker. oh well.
|
||||||
|
add_column :users, :crop_x, :integer
|
||||||
|
add_column :users, :crop_y, :integer
|
||||||
|
add_column :users, :crop_w, :integer
|
||||||
|
add_column :users, :crop_h, :integer
|
||||||
|
end
|
||||||
|
end
|
41
db/schema.rb
41
db/schema.rb
|
@ -11,7 +11,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20141228202825) do
|
ActiveRecord::Schema.define(version: 20141229133149) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -133,12 +133,12 @@ ActiveRecord::Schema.define(version: 20141228202825) do
|
||||||
add_index "smiles", ["user_id"], name: "index_smiles_on_user_id", using: :btree
|
add_index "smiles", ["user_id"], name: "index_smiles_on_user_id", using: :btree
|
||||||
|
|
||||||
create_table "users", force: true do |t|
|
create_table "users", force: true do |t|
|
||||||
t.string "email", default: "", null: false
|
t.string "email", default: "", null: false
|
||||||
t.string "encrypted_password", default: "", null: false
|
t.string "encrypted_password", default: "", null: false
|
||||||
t.string "reset_password_token"
|
t.string "reset_password_token"
|
||||||
t.datetime "reset_password_sent_at"
|
t.datetime "reset_password_sent_at"
|
||||||
t.datetime "remember_created_at"
|
t.datetime "remember_created_at"
|
||||||
t.integer "sign_in_count", default: 0, null: false
|
t.integer "sign_in_count", default: 0, null: false
|
||||||
t.datetime "current_sign_in_at"
|
t.datetime "current_sign_in_at"
|
||||||
t.datetime "last_sign_in_at"
|
t.datetime "last_sign_in_at"
|
||||||
t.string "current_sign_in_ip"
|
t.string "current_sign_in_ip"
|
||||||
|
@ -146,19 +146,28 @@ ActiveRecord::Schema.define(version: 20141228202825) do
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.datetime "updated_at"
|
t.datetime "updated_at"
|
||||||
t.string "screen_name"
|
t.string "screen_name"
|
||||||
t.integer "friend_count", default: 0, null: false
|
t.integer "friend_count", default: 0, null: false
|
||||||
t.integer "follower_count", default: 0, null: false
|
t.integer "follower_count", default: 0, null: false
|
||||||
t.integer "asked_count", default: 0, null: false
|
t.integer "asked_count", default: 0, null: false
|
||||||
t.integer "answered_count", default: 0, null: false
|
t.integer "answered_count", default: 0, null: false
|
||||||
t.integer "commented_count", default: 0, null: false
|
t.integer "commented_count", default: 0, null: false
|
||||||
t.string "display_name"
|
t.string "display_name"
|
||||||
t.integer "smiled_count", default: 0, null: false
|
t.integer "smiled_count", default: 0, null: false
|
||||||
t.boolean "admin", default: false, null: false
|
t.boolean "admin", default: false, null: false
|
||||||
t.string "motivation_header", default: "", null: false
|
t.string "motivation_header", default: "", null: false
|
||||||
t.string "website", default: "", null: false
|
t.string "website", default: "", null: false
|
||||||
t.string "location", default: "", null: false
|
t.string "location", default: "", null: false
|
||||||
t.text "bio", default: "", null: false
|
t.text "bio", default: "", null: false
|
||||||
t.boolean "moderator", default: false, null: false
|
t.boolean "moderator", default: false, null: false
|
||||||
|
t.string "profile_picture_file_name"
|
||||||
|
t.string "profile_picture_content_type"
|
||||||
|
t.integer "profile_picture_file_size"
|
||||||
|
t.datetime "profile_picture_updated_at"
|
||||||
|
t.boolean "profile_picture_processing"
|
||||||
|
t.integer "crop_x"
|
||||||
|
t.integer "crop_y"
|
||||||
|
t.integer "crop_w"
|
||||||
|
t.integer "crop_h"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
|
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
|
||||||
|
|
|
@ -0,0 +1,417 @@
|
||||||
|
// Generated by CoffeeScript 1.8.0
|
||||||
|
|
||||||
|
/*
|
||||||
|
* jQuery Guillotine Plugin v1.3.0
|
||||||
|
* http://matiasgagliano.github.com/guillotine/
|
||||||
|
*
|
||||||
|
* Copyright 2014, Matías Gagliano.
|
||||||
|
* Dual licensed under the MIT or GPLv3 licenses.
|
||||||
|
* http://opensource.org/licenses/MIT
|
||||||
|
* http://opensource.org/licenses/GPL-3.0
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
var $, Guillotine, canTransform, defaults, events, getPointerPosition, hardwareAccelerate, isTouch, pluginName, scope, touchRegExp, validEvent, whitelist,
|
||||||
|
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
||||||
|
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||||
|
|
||||||
|
$ = jQuery;
|
||||||
|
|
||||||
|
pluginName = 'guillotine';
|
||||||
|
|
||||||
|
scope = 'guillotine';
|
||||||
|
|
||||||
|
events = {
|
||||||
|
start: "touchstart." + scope + " mousedown." + scope + " pointerdown." + scope,
|
||||||
|
move: "touchmove." + scope + " mousemove." + scope + " pointermove." + scope,
|
||||||
|
stop: "touchend." + scope + " mouseup." + scope + " pointerup." + scope
|
||||||
|
};
|
||||||
|
|
||||||
|
defaults = {
|
||||||
|
width: 400,
|
||||||
|
height: 300,
|
||||||
|
zoomStep: 0.1,
|
||||||
|
init: null,
|
||||||
|
eventOnChange: null,
|
||||||
|
onChange: null
|
||||||
|
};
|
||||||
|
|
||||||
|
touchRegExp = /touch/i;
|
||||||
|
|
||||||
|
isTouch = function(e) {
|
||||||
|
return touchRegExp.test(e.type);
|
||||||
|
};
|
||||||
|
|
||||||
|
validEvent = function(e) {
|
||||||
|
if (isTouch(e)) {
|
||||||
|
return e.originalEvent.changedTouches.length === 1;
|
||||||
|
} else {
|
||||||
|
return e.which === 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getPointerPosition = function(e) {
|
||||||
|
if (isTouch(e)) {
|
||||||
|
e = e.originalEvent.touches[0];
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x: e.pageX,
|
||||||
|
y: e.pageY
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
canTransform = function() {
|
||||||
|
var hasTransform, helper, prefix, prefixes, prop, test, tests, value, _i, _len;
|
||||||
|
hasTransform = false;
|
||||||
|
prefixes = 'webkit,Moz,O,ms,Khtml'.split(',');
|
||||||
|
tests = {
|
||||||
|
transform: 'transform'
|
||||||
|
};
|
||||||
|
for (_i = 0, _len = prefixes.length; _i < _len; _i++) {
|
||||||
|
prefix = prefixes[_i];
|
||||||
|
tests[prefix + 'Transform'] = "-" + (prefix.toLowerCase()) + "-transform";
|
||||||
|
}
|
||||||
|
helper = document.createElement('img');
|
||||||
|
document.body.insertBefore(helper, null);
|
||||||
|
for (test in tests) {
|
||||||
|
prop = tests[test];
|
||||||
|
if (helper.style[test] === void 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
helper.style[test] = 'rotate(90deg)';
|
||||||
|
value = window.getComputedStyle(helper).getPropertyValue(prop);
|
||||||
|
if ((value != null) && value.length && value !== 'none') {
|
||||||
|
hasTransform = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.body.removeChild(helper);
|
||||||
|
canTransform = hasTransform ? (function() {
|
||||||
|
return true;
|
||||||
|
}) : (function() {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
return canTransform();
|
||||||
|
};
|
||||||
|
|
||||||
|
hardwareAccelerate = function(el) {
|
||||||
|
return $(el).css({
|
||||||
|
'-webkit-perspective': 1000,
|
||||||
|
'perspective': 1000,
|
||||||
|
'-webkit-backface-visibility': 'hidden',
|
||||||
|
'backface-visibility': 'hidden'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine = (function() {
|
||||||
|
function Guillotine(element, options) {
|
||||||
|
this._drag = __bind(this._drag, this);
|
||||||
|
this._unbind = __bind(this._unbind, this);
|
||||||
|
this._start = __bind(this._start, this);
|
||||||
|
var _ref;
|
||||||
|
this.op = $.extend(true, {}, defaults, options, $(element).data(pluginName));
|
||||||
|
this.enabled = true;
|
||||||
|
this.zoomInFactor = 1 + this.op.zoomStep;
|
||||||
|
this.zoomOutFactor = 1 / this.zoomInFactor;
|
||||||
|
_ref = [0, 0, 0, 0, 0], this.width = _ref[0], this.height = _ref[1], this.left = _ref[2], this.top = _ref[3], this.angle = _ref[4];
|
||||||
|
this.data = {
|
||||||
|
scale: 1,
|
||||||
|
angle: 0,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: this.op.width,
|
||||||
|
h: this.op.height
|
||||||
|
};
|
||||||
|
this._wrap(element);
|
||||||
|
if (this.op.init != null) {
|
||||||
|
this._init();
|
||||||
|
}
|
||||||
|
if (this.width < 1 || this.height < 1) {
|
||||||
|
this._fit() && this._center();
|
||||||
|
}
|
||||||
|
hardwareAccelerate(this.$el);
|
||||||
|
this.$el.on(events.start, this._start);
|
||||||
|
}
|
||||||
|
|
||||||
|
Guillotine.prototype._wrap = function(element) {
|
||||||
|
var canvas, el, guillotine, height, img, paddingTop, width, _ref, _ref1, _ref2;
|
||||||
|
el = $(element);
|
||||||
|
if (el.prop('tagName') === 'IMG') {
|
||||||
|
img = document.createElement('img');
|
||||||
|
img.src = el.attr('src');
|
||||||
|
_ref = [img.width, img.height], width = _ref[0], height = _ref[1];
|
||||||
|
} else {
|
||||||
|
_ref1 = [el.width(), el.height()], width = _ref1[0], height = _ref1[1];
|
||||||
|
}
|
||||||
|
_ref2 = [width / this.op.width, height / this.op.height], this.width = _ref2[0], this.height = _ref2[1];
|
||||||
|
canvas = $('<div>').addClass('guillotine-canvas');
|
||||||
|
canvas.css({
|
||||||
|
width: this.width * 100 + '%',
|
||||||
|
height: this.height * 100 + '%',
|
||||||
|
top: 0,
|
||||||
|
left: 0
|
||||||
|
});
|
||||||
|
canvas = el.wrap(canvas).parent();
|
||||||
|
paddingTop = this.op.height / this.op.width * 100 + '%';
|
||||||
|
guillotine = $('<div>').addClass('guillotine-window');
|
||||||
|
guillotine.css({
|
||||||
|
width: '100%',
|
||||||
|
height: 'auto',
|
||||||
|
'padding-top': paddingTop
|
||||||
|
});
|
||||||
|
guillotine = canvas.wrap(guillotine).parent();
|
||||||
|
this.$el = el;
|
||||||
|
this.el = el[0];
|
||||||
|
this.$canvas = canvas;
|
||||||
|
this.canvas = canvas[0];
|
||||||
|
this.$gllt = guillotine;
|
||||||
|
this.gllt = guillotine[0];
|
||||||
|
return this.$document = $(element.ownerDocument);
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype._unwrap = function() {
|
||||||
|
this.$el.removeAttr('style');
|
||||||
|
this.$el.insertBefore(this.gllt);
|
||||||
|
return this.$gllt.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype._init = function() {
|
||||||
|
var angle, o, scale;
|
||||||
|
o = this.op.init;
|
||||||
|
if ((scale = parseFloat(o.scale))) {
|
||||||
|
this._zoom(scale);
|
||||||
|
}
|
||||||
|
if ((angle = parseInt(o.angle))) {
|
||||||
|
this._rotate(angle);
|
||||||
|
}
|
||||||
|
return this._offset(parseInt(o.x) / this.op.width || 0, parseInt(o.y) / this.op.height || 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype._start = function(e) {
|
||||||
|
if (!(this.enabled && validEvent(e))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
this.p = getPointerPosition(e);
|
||||||
|
return this._bind();
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype._bind = function() {
|
||||||
|
this.$document.on(events.move, this._drag);
|
||||||
|
return this.$document.on(events.stop, this._unbind);
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype._unbind = function(e) {
|
||||||
|
this.$document.off(events.move, this._drag);
|
||||||
|
this.$document.off(events.stop, this._unbind);
|
||||||
|
if (e != null) {
|
||||||
|
return this._trigger('drag');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype._trigger = function(action) {
|
||||||
|
if (this.op.eventOnChange != null) {
|
||||||
|
this.$el.trigger(this.op.eventOnChange, [this.data, action]);
|
||||||
|
}
|
||||||
|
if (typeof this.op.onChange === 'function') {
|
||||||
|
return this.op.onChange.call(this.el, this.data, action);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype._drag = function(e) {
|
||||||
|
var dx, dy, left, p, top;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
p = getPointerPosition(e);
|
||||||
|
dx = p.x - this.p.x;
|
||||||
|
dy = p.y - this.p.y;
|
||||||
|
this.p = p;
|
||||||
|
left = dx === 0 ? null : this.left - dx / this.gllt.clientWidth;
|
||||||
|
top = dy === 0 ? null : this.top - dy / this.gllt.clientHeight;
|
||||||
|
return this._offset(left, top);
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype._offset = function(left, top) {
|
||||||
|
if (left || left === 0) {
|
||||||
|
if (left < 0) {
|
||||||
|
left = 0;
|
||||||
|
}
|
||||||
|
if (left > this.width - 1) {
|
||||||
|
left = this.width - 1;
|
||||||
|
}
|
||||||
|
this.canvas.style.left = (-left * 100).toFixed(2) + '%';
|
||||||
|
this.left = left;
|
||||||
|
this.data.x = Math.round(left * this.op.width);
|
||||||
|
}
|
||||||
|
if (top || top === 0) {
|
||||||
|
if (top < 0) {
|
||||||
|
top = 0;
|
||||||
|
}
|
||||||
|
if (top > this.height - 1) {
|
||||||
|
top = this.height - 1;
|
||||||
|
}
|
||||||
|
this.canvas.style.top = (-top * 100).toFixed(2) + '%';
|
||||||
|
this.top = top;
|
||||||
|
return this.data.y = Math.round(top * this.op.height);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype._zoom = function(factor) {
|
||||||
|
var h, left, top, w, _ref;
|
||||||
|
if (factor <= 0 || factor === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ref = [this.width, this.height], w = _ref[0], h = _ref[1];
|
||||||
|
if (w * factor > 1 && h * factor > 1) {
|
||||||
|
this.width *= factor;
|
||||||
|
this.height *= factor;
|
||||||
|
this.canvas.style.width = (this.width * 100).toFixed(2) + '%';
|
||||||
|
this.canvas.style.height = (this.height * 100).toFixed(2) + '%';
|
||||||
|
this.data.scale *= factor;
|
||||||
|
} else {
|
||||||
|
this._fit();
|
||||||
|
factor = this.width / w;
|
||||||
|
}
|
||||||
|
left = (this.left + 0.5) * factor - 0.5;
|
||||||
|
top = (this.top + 0.5) * factor - 0.5;
|
||||||
|
return this._offset(left, top);
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype._fit = function() {
|
||||||
|
var prevWidth, relativeRatio;
|
||||||
|
prevWidth = this.width;
|
||||||
|
relativeRatio = this.height / this.width;
|
||||||
|
if (relativeRatio > 1) {
|
||||||
|
this.width = 1;
|
||||||
|
this.height = relativeRatio;
|
||||||
|
} else {
|
||||||
|
this.width = 1 / relativeRatio;
|
||||||
|
this.height = 1;
|
||||||
|
}
|
||||||
|
this.canvas.style.width = (this.width * 100).toFixed(2) + '%';
|
||||||
|
this.canvas.style.height = (this.height * 100).toFixed(2) + '%';
|
||||||
|
return this.data.scale *= this.width / prevWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype._center = function() {
|
||||||
|
return this._offset((this.width - 1) / 2, (this.height - 1) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype._rotate = function(angle) {
|
||||||
|
var canvasRatio, glltRatio, h, w, _ref, _ref1, _ref2;
|
||||||
|
if (!canTransform()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!(angle !== 0 && angle % 90 === 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.angle = (this.angle + angle) % 360;
|
||||||
|
if (this.angle < 0) {
|
||||||
|
this.angle = 360 + this.angle;
|
||||||
|
}
|
||||||
|
if (angle % 180 !== 0) {
|
||||||
|
glltRatio = this.op.height / this.op.width;
|
||||||
|
_ref = [this.height * glltRatio, this.width / glltRatio], this.width = _ref[0], this.height = _ref[1];
|
||||||
|
if (this.width >= 1 && this.height >= 1) {
|
||||||
|
this.canvas.style.width = this.width * 100 + '%';
|
||||||
|
this.canvas.style.height = this.height * 100 + '%';
|
||||||
|
} else {
|
||||||
|
this._fit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ref1 = [1, 1], w = _ref1[0], h = _ref1[1];
|
||||||
|
if (this.angle % 180 !== 0) {
|
||||||
|
canvasRatio = this.height / this.width * glltRatio;
|
||||||
|
_ref2 = [canvasRatio, 1 / canvasRatio], w = _ref2[0], h = _ref2[1];
|
||||||
|
}
|
||||||
|
this.el.style.width = w * 100 + '%';
|
||||||
|
this.el.style.height = h * 100 + '%';
|
||||||
|
this.el.style.left = (1 - w) / 2 * 100 + '%';
|
||||||
|
this.el.style.top = (1 - h) / 2 * 100 + '%';
|
||||||
|
this.$el.css({
|
||||||
|
transform: "rotate(" + this.angle + "deg)"
|
||||||
|
});
|
||||||
|
this._center();
|
||||||
|
return this.data.angle = this.angle;
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype.rotateLeft = function() {
|
||||||
|
return this.enabled && (this._rotate(-90), this._trigger('rotateLeft'));
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype.rotateRight = function() {
|
||||||
|
return this.enabled && (this._rotate(90), this._trigger('rotateRight'));
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype.center = function() {
|
||||||
|
return this.enabled && (this._center(), this._trigger('center'));
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype.fit = function() {
|
||||||
|
return this.enabled && (this._fit(), this._center(), this._trigger('fit'));
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype.zoomIn = function() {
|
||||||
|
return this.enabled && (this._zoom(this.zoomInFactor), this._trigger('zoomIn'));
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype.zoomOut = function() {
|
||||||
|
return this.enabled && (this._zoom(this.zoomOutFactor), this._trigger('zoomOut'));
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype.getData = function() {
|
||||||
|
return this.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype.enable = function() {
|
||||||
|
return this.enabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype.disable = function() {
|
||||||
|
return this.enabled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Guillotine.prototype.remove = function() {
|
||||||
|
this._unbind();
|
||||||
|
this._unwrap();
|
||||||
|
this.disable();
|
||||||
|
this.$el.off(events.start, this._start);
|
||||||
|
return this.$el.removeData(pluginName + 'Instance');
|
||||||
|
};
|
||||||
|
|
||||||
|
return Guillotine;
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
|
whitelist = ['rotateLeft', 'rotateRight', 'center', 'fit', 'zoomIn', 'zoomOut', 'instance', 'getData', 'enable', 'disable', 'remove'];
|
||||||
|
|
||||||
|
$.fn[pluginName] = function(options) {
|
||||||
|
if (typeof options !== 'string') {
|
||||||
|
return this.each(function() {
|
||||||
|
var guillotine;
|
||||||
|
if (!$.data(this, pluginName + 'Instance')) {
|
||||||
|
guillotine = new Guillotine(this, options);
|
||||||
|
return $.data(this, pluginName + 'Instance', guillotine);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (__indexOf.call(whitelist, options) >= 0) {
|
||||||
|
if (options === 'instance') {
|
||||||
|
return $.data(this[0], pluginName + 'Instance');
|
||||||
|
}
|
||||||
|
if (options === 'getData') {
|
||||||
|
return $.data(this[0], pluginName + 'Instance')[options]();
|
||||||
|
}
|
||||||
|
return this.each(function() {
|
||||||
|
var guillotine;
|
||||||
|
guillotine = $.data(this, pluginName + 'Instance');
|
||||||
|
if (guillotine) {
|
||||||
|
return guillotine[options]();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}).call(this);
|
|
@ -0,0 +1,28 @@
|
||||||
|
.guillotine-window {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guillotine-canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guillotine-canvas > * {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
max-width: none;
|
||||||
|
max-height: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
module Paperclip
|
||||||
|
class Cropper < Thumbnail
|
||||||
|
def transformation_command
|
||||||
|
if crop_command
|
||||||
|
x = super
|
||||||
|
i = x.index '-crop'
|
||||||
|
2.times { x.delete_at i } if i
|
||||||
|
crop_command + x
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def crop_command
|
||||||
|
target = @attachment.instance
|
||||||
|
if target.cropping?
|
||||||
|
['-crop', "'#{target.crop_w.to_i}x#{target.crop_h.to_i}+#{target.crop_x.to_i}+#{target.crop_y.to_i}'"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
Loading…
Reference in New Issue