paper_cropper 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +127 -0
- data/lib/assets/javascripts/paper_cropper.js +149 -0
- data/lib/assets/stylesheets/paper_cropper.css +7 -0
- data/lib/paper_cropper/engine.rb +4 -0
- data/lib/paper_cropper/helpers.rb +66 -0
- data/lib/paper_cropper/logger.rb +6 -0
- data/lib/paper_cropper/model_extension.rb +129 -0
- data/lib/paper_cropper/reg_exp.rb +6 -0
- data/lib/paper_cropper/schema.rb +76 -0
- data/lib/paper_cropper/version.rb +12 -0
- data/lib/paper_cropper.rb +7 -0
- data/lib/paperclip_processors/paper_cropper.rb +31 -0
- data/vendor/assets/images/Jcrop.gif +0 -0
- data/vendor/assets/javascripts/cropper.js +3556 -0
- data/vendor/assets/stylesheets/cropper.css +357 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 765ec5c7bbb8a9db019e7c04a976c98a61d0b5ce
|
4
|
+
data.tar.gz: db472297bc0ae0fe57f463d113141514198fefaa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8005e706ae466fb0029f9c34aee3fe5929fbee660b7bd9486347267c1ad657a84717e3204fc2caf83338f709a57b2784659a3acdab82f9938f516a61ebe7f68e
|
7
|
+
data.tar.gz: 81906bd23691a56d4617c5ebe2981efc16b9995c37315eca45adae02ced0d7a3137e8bea97dc2e0f03b669e789da56db4df93c5fcf6e62aabffa9d7a2eaebdb0
|
data/README.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# PaperCropper gem
|
2
|
+
|
3
|
+
PaperCropper gem is a gem for cropping images using [cropper js](https://github.com/fengyuanchen/cropperjs) and [Paperclip](https://github.com/thoughtbot/paperclip)
|
4
|
+
|
5
|
+
All examples are based on a `cover` attachment
|
6
|
+
|
7
|
+
## migrations
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
create_table :gallery_albums do |t|
|
11
|
+
t.attachment :cover # paperclip required
|
12
|
+
t.crop_attachment :cover # add extra columns for crop
|
13
|
+
end
|
14
|
+
```
|
15
|
+
|
16
|
+
## model
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
# aspect 1:1
|
20
|
+
crop_attached_file :cover
|
21
|
+
|
22
|
+
# aspect 16:4
|
23
|
+
crop_attached_file :cover, aspect: '16:4'
|
24
|
+
|
25
|
+
# free (without aspect restriction)
|
26
|
+
crop_attached_file :cover, aspect: false
|
27
|
+
```
|
28
|
+
|
29
|
+
## controler
|
30
|
+
Take care of permitted params. Add the following fields to ```permitted``` strong attributes:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
:cover_crop_x, :cover_crop_y, :cover_crop_width, :cover_crop_height
|
34
|
+
```
|
35
|
+
|
36
|
+
## javascripts
|
37
|
+
|
38
|
+
~~~
|
39
|
+
//= require cropper
|
40
|
+
//= require paper_cropper
|
41
|
+
|
42
|
+
// Crop
|
43
|
+
$('.crop-image-container').each(function(){
|
44
|
+
papercropper = new PaperCropper(this, {})
|
45
|
+
});
|
46
|
+
~~~
|
47
|
+
|
48
|
+
## scss
|
49
|
+
|
50
|
+
~~~
|
51
|
+
@import "cropper";
|
52
|
+
@import "paper_cropper";
|
53
|
+
~~~
|
54
|
+
|
55
|
+
The paper_cropper is not required, is only for a small changes. The only thing required from 'paper_cropper' is this rule:
|
56
|
+
|
57
|
+
~~~
|
58
|
+
/* Limit image width to avoid overflow the container */
|
59
|
+
.crop-image-container img.crop-image {
|
60
|
+
max-width: 100%; /* This rule is very important, please do not ignore this! */
|
61
|
+
}
|
62
|
+
~~~
|
63
|
+
|
64
|
+
---------------------------------------------
|
65
|
+
|
66
|
+
## helpers
|
67
|
+
|
68
|
+
helper, with twitter bootstrap classes, custom formatted, and other things
|
69
|
+
|
70
|
+
~~~
|
71
|
+
<%= form_for ..... %>
|
72
|
+
<%= f.tws_cropper_box :cover %>
|
73
|
+
<% end %>
|
74
|
+
~~~
|
75
|
+
|
76
|
+
**custom (without helper)**
|
77
|
+
|
78
|
+
if you do not want to use helper, you can do what you want, for example you could make the file selector ".crop-image-file-input" below the picture "crop-image-wrapper"
|
79
|
+
|
80
|
+
the most important thing is to keep the "crop-image ..." classes and "crop-image-container" and "crop-image-wrapper" containers
|
81
|
+
|
82
|
+
for example:
|
83
|
+
|
84
|
+
~~~
|
85
|
+
<!-- [required] Main container (you need to supply this in JS constructor) -->
|
86
|
+
<div class="crop-image-container">
|
87
|
+
<!-- [required] Hidden input for save the crop values -->
|
88
|
+
<input class="crop-image-value-x" type="hidden" name="cover_crop_x" value="">
|
89
|
+
<input class="crop-image-value-y" type="hidden" name="cover_crop_y" value="">
|
90
|
+
<input class="crop-image-value-width" type="hidden" name="cover_crop_width" value="">
|
91
|
+
<input class="crop-image-value-height" type="hidden" name="cover_crop_height" value="">
|
92
|
+
|
93
|
+
<!-- [required] Wrap the image or canvas element with a block element (container) -->
|
94
|
+
<div class="crop-image-wrapper">
|
95
|
+
<!-- [required] Image to crop -->
|
96
|
+
<img class="crop-image" src="">
|
97
|
+
</div>
|
98
|
+
|
99
|
+
<!-- [required] File input for change the image -->
|
100
|
+
<input class="crop-image-file-input" type="file">
|
101
|
+
|
102
|
+
<!-- [optional] Buttons -->
|
103
|
+
<button class="crop-image-btn-zoom-in" type="button">Zoom in</button>
|
104
|
+
<button class="crop-image-btn-zoom-out" type="button">Zoom out</button>
|
105
|
+
<button class="crop-image-btn-move-left" type="button">Move left</button>
|
106
|
+
<button class="crop-image-btn-move-right" type="button">Move right</button>
|
107
|
+
<button class="crop-image-btn-move-up" type="button">Move up</button>
|
108
|
+
<button class="crop-image-btn-move-down" type="button">Move down</button>
|
109
|
+
</div>
|
110
|
+
~~~
|
111
|
+
|
112
|
+
# License
|
113
|
+
|
114
|
+
PaperCropper is Copyright © 2016 CodiTramuntana SL. It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
|
115
|
+
|
116
|
+
# About CodiTramuntana
|
117
|
+
|
118
|
+
![coditramuntana](http://www.coditramuntana.com/assets/ic_logo_medium.png)
|
119
|
+
|
120
|
+
PaperCropper is maintained by [CodiTramuntana](http://www.coditramuntana.com).
|
121
|
+
PaperCropper is the mix of [cropper js](https://github.com/fengyuanchen/cropperjs) and
|
122
|
+
[Paperclip](https://github.com/thoughtbot/paperclip) and some glue code to make things
|
123
|
+
easier.
|
124
|
+
|
125
|
+
The names and logos for CodiTramuntana are trademarks of CodiTramuntana SL.
|
126
|
+
|
127
|
+
We love open source software!
|
@@ -0,0 +1,149 @@
|
|
1
|
+
/**
|
2
|
+
* PaperCropper
|
3
|
+
*
|
4
|
+
* @param container
|
5
|
+
* Main container of objects
|
6
|
+
* @param options
|
7
|
+
* Extra options
|
8
|
+
*
|
9
|
+
* Example, important all classes used for crop starts with "crop-image*"
|
10
|
+
* The buttons, classes 'crop-image-btn-*' are optional
|
11
|
+
*
|
12
|
+
<!-- Main container (you need to supply this in constructor) -->
|
13
|
+
<div class="crop-image-container">
|
14
|
+
<!-- Hidden input for save the crop values -->
|
15
|
+
<input class="crop-image-value-x" type="hidden" name="cover_crop_x" value="">
|
16
|
+
<input class="crop-image-value-y" type="hidden" name="cover_crop_y" value="">
|
17
|
+
<input class="crop-image-value-width" type="hidden" name="cover_crop_width" value="">
|
18
|
+
<input class="crop-image-value-height" type="hidden" name="cover_crop_height" value="">
|
19
|
+
|
20
|
+
<!-- File input for change the image -->
|
21
|
+
<input class="crop-image-file-input" type="file">
|
22
|
+
|
23
|
+
<hr>
|
24
|
+
|
25
|
+
<!-- Wrap the image or canvas element with a block element (container) -->
|
26
|
+
<div class="crop-image-wrapper">
|
27
|
+
<img class="crop-image" src="">
|
28
|
+
</div>
|
29
|
+
|
30
|
+
<hr>
|
31
|
+
|
32
|
+
<button class="crop-image-btn-zoom-in" type="button">Zoom in</button>
|
33
|
+
<button class="crop-image-btn-zoom-out" type="button">Zoom out</button>
|
34
|
+
<button class="crop-image-btn-move-left" type="button">Move left</button>
|
35
|
+
<button class="crop-image-btn-move-right" type="button">Move right</button>
|
36
|
+
<button class="crop-image-btn-move-up" type="button">Move up</button>
|
37
|
+
<button class="crop-image-btn-move-down" type="button">Move down</button>
|
38
|
+
</div>
|
39
|
+
|
40
|
+
*/
|
41
|
+
function PaperCropper(container, opt) {
|
42
|
+
var that = this;
|
43
|
+
this.container = $(container);
|
44
|
+
this.image = $(container).find('.crop-image');
|
45
|
+
this.inputX = $(container).find('.crop-image-value-x');
|
46
|
+
this.inputY = $(container).find('.crop-image-value-y');
|
47
|
+
this.inputWidth = $(container).find('.crop-image-value-width');
|
48
|
+
this.inputHeight = $(container).find('.crop-image-value-height');
|
49
|
+
|
50
|
+
var aspect = $(container).attr('data-crop-image-range');
|
51
|
+
if( isNaN(aspect) ) {
|
52
|
+
this.aspect = null;
|
53
|
+
} else {
|
54
|
+
this.aspect = parseFloat(aspect);
|
55
|
+
}
|
56
|
+
|
57
|
+
this.init();
|
58
|
+
}
|
59
|
+
|
60
|
+
/** Check if browswer support it */
|
61
|
+
PaperCropper.isSupported = function() {
|
62
|
+
return (typeof FileReader !== "undefined");
|
63
|
+
}
|
64
|
+
|
65
|
+
/** Initializes all */
|
66
|
+
PaperCropper.prototype.init = function() {
|
67
|
+
var fileInput = this.container.find('input.crop-image-file-input');
|
68
|
+
var that = this;
|
69
|
+
|
70
|
+
// file input change
|
71
|
+
fileInput.change(function(event){
|
72
|
+
that.fileInputChange(event);
|
73
|
+
});
|
74
|
+
|
75
|
+
// cropper
|
76
|
+
this.cropper = new Cropper(this.image[0], {
|
77
|
+
aspectRatio: this.aspect,
|
78
|
+
crop: function(event){
|
79
|
+
that.cropperCrop(event);
|
80
|
+
},
|
81
|
+
built: function() {
|
82
|
+
console.log('cropper build')
|
83
|
+
that.cropperBuild();
|
84
|
+
}
|
85
|
+
});
|
86
|
+
|
87
|
+
|
88
|
+
// buttons
|
89
|
+
this.container.find('.crop-image-btn-zoom-in').click(function(){
|
90
|
+
that.cropper.zoom(0.1);
|
91
|
+
});
|
92
|
+
this.container.find('.crop-image-btn-zoom-out').click(function(){
|
93
|
+
that.cropper.zoom(-0.1);
|
94
|
+
});
|
95
|
+
this.container.find('.crop-image-btn-move-left').click(function(){
|
96
|
+
that.cropper.move(-10,0);
|
97
|
+
});
|
98
|
+
this.container.find('.crop-image-btn-move-right').click(function(){
|
99
|
+
that.cropper.move(10,0);
|
100
|
+
});
|
101
|
+
this.container.find('.crop-image-btn-move-up').click(function(){
|
102
|
+
that.cropper.move(0,-10);
|
103
|
+
});
|
104
|
+
this.container.find('.crop-image-btn-move-down').click(function(){
|
105
|
+
that.cropper.move(0,10);
|
106
|
+
});
|
107
|
+
}
|
108
|
+
|
109
|
+
/** Callback method used when cropper are full loaded */
|
110
|
+
PaperCropper.prototype.cropperBuild = function() {
|
111
|
+
// set initial values
|
112
|
+
this.setCropperData(this.inputX.val(), this.inputY.val(), this.inputWidth.val(), this.inputHeight.val());
|
113
|
+
}
|
114
|
+
|
115
|
+
/** Callback method used when the user changes the crop */
|
116
|
+
PaperCropper.prototype.cropperCrop = function (event) {
|
117
|
+
console.log('crop:',event.detail)
|
118
|
+
console.log('crop crop data:',this.cropper.getData())
|
119
|
+
this.inputX.val(parseInt(event.detail.x));
|
120
|
+
this.inputY.val(parseInt(event.detail.y));
|
121
|
+
this.inputWidth.val(parseInt(event.detail.width));
|
122
|
+
this.inputHeight.val(parseInt(event.detail.height));
|
123
|
+
}
|
124
|
+
|
125
|
+
/** Callback method used when the user changes the file */
|
126
|
+
PaperCropper.prototype.fileInputChange = function (event) {
|
127
|
+
var input = event.target;
|
128
|
+
var that = this;
|
129
|
+
if (input.files && input.files[0]) {
|
130
|
+
var reader = new FileReader();
|
131
|
+
reader.onload = function (e) {
|
132
|
+
that.cropper.replace(e.target.result);
|
133
|
+
}
|
134
|
+
reader.readAsDataURL(input.files[0]);
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
/** Set the cropper data */
|
139
|
+
PaperCropper.prototype.setCropperData = function (x, y, width, height) {
|
140
|
+
this.cropper.setData({
|
141
|
+
'x': parseInt(x),
|
142
|
+
'y': parseInt(y),
|
143
|
+
'width': parseInt(width),
|
144
|
+
'height': parseInt(height),
|
145
|
+
'rotate': 0,
|
146
|
+
'scaleX': 1,
|
147
|
+
'scaleY': 1,
|
148
|
+
});
|
149
|
+
}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module PaperCropper
|
2
|
+
module FormHelpers
|
3
|
+
|
4
|
+
# Create a new cropper box with all buttons and classe. This will be use the
|
5
|
+
# twitter bootstrap classes
|
6
|
+
# @param attachment_name [Symbol] Name of the attachment to crop
|
7
|
+
def tws_cropper_box attachment_name
|
8
|
+
obj = self.object
|
9
|
+
form_helper = self
|
10
|
+
aspect_ratio = obj.send "#{attachment_name}_aspect"
|
11
|
+
|
12
|
+
out = "<div class='crop-image-container' data-crop-image-range='#{aspect_ratio}'>"
|
13
|
+
|
14
|
+
out << form_helper.hidden_field(:"#{attachment_name}_crop_x", class: 'crop-image-value-x')
|
15
|
+
out << form_helper.hidden_field(:"#{attachment_name}_crop_y", class: 'crop-image-value-y')
|
16
|
+
out << form_helper.hidden_field(:"#{attachment_name}_crop_width", class: 'crop-image-value-width')
|
17
|
+
out << form_helper.hidden_field(:"#{attachment_name}_crop_height", class: 'crop-image-value-height')
|
18
|
+
|
19
|
+
out << form_helper.file_field(attachment_name, class: 'crop-image-file-input', label_col: "col-sm-3", control_col: "col-sm-9")
|
20
|
+
|
21
|
+
out << '<hr>'
|
22
|
+
|
23
|
+
image_src = obj.send("#{attachment_name}?") ? obj.send(attachment_name).url : ''
|
24
|
+
out << '<div class="crop-image-wrapper">'
|
25
|
+
out << "<img class='crop-image' class='crop-image' src='#{image_src}'>"
|
26
|
+
out << '</div>'
|
27
|
+
|
28
|
+
out << '<hr>'
|
29
|
+
|
30
|
+
out << '<div class="btn-group pull-left" role="group">'
|
31
|
+
out << "<button type='button' class='crop-image-btn-zoom-in btn btn-default'>+</button>"
|
32
|
+
out << "<button type='button' class='crop-image-btn-zoom-out btn btn-default'>-</button>"
|
33
|
+
out << '</div>'
|
34
|
+
|
35
|
+
out << '<div class="btn-group pull-right" role="group">'
|
36
|
+
out << "<button type='button' class='crop-image-btn-move-left btn btn-default'>left</button>"
|
37
|
+
out << "<button type='button' class='crop-image-btn-move-right btn btn-default'>right</button>"
|
38
|
+
out << "<button type='button' class='crop-image-btn-move-up btn btn-default'>up</button>"
|
39
|
+
out << "<button type='button' class='crop-image-btn-move-down btn btn-default'>down</button>"
|
40
|
+
out << '</div>'
|
41
|
+
|
42
|
+
out << '</div>'
|
43
|
+
|
44
|
+
out.html_safe
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# ActionView::Helpers::FormBuilder support
|
52
|
+
# #########################################################################
|
53
|
+
if defined? ActionView::Helpers::FormBuilder
|
54
|
+
ActionView::Helpers::FormBuilder.class_eval do
|
55
|
+
include PaperCropper::FormHelpers
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# BootstrapForm::FormBuilder support
|
61
|
+
# #########################################################################
|
62
|
+
# if defined? BootstrapForm::FormBuilder
|
63
|
+
# BootstrapForm::FormBuilder.class_eval do
|
64
|
+
# include PaperCropper::FormHelpers
|
65
|
+
# end
|
66
|
+
# end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module PaperCropper
|
2
|
+
module ModelExtension
|
3
|
+
# Class methods
|
4
|
+
# #########################################################################
|
5
|
+
# Initializes attachment cropping in your model
|
6
|
+
#
|
7
|
+
# crop_attached_file :avatar
|
8
|
+
#
|
9
|
+
# You can also define an initial aspect ratio for the crop and preview box through opts[:aspect]
|
10
|
+
#
|
11
|
+
# crop_attached_file :avatar, :aspect => "4:3"
|
12
|
+
#
|
13
|
+
# Or unlock it
|
14
|
+
#
|
15
|
+
# crop_attached_file :avatar, :aspect => false
|
16
|
+
#
|
17
|
+
# @param attachment_name [Symbol] Name of the desired attachment to crop
|
18
|
+
# @param opts [Hash]
|
19
|
+
# @option opts [Range, String, FalseClass] :aspect
|
20
|
+
module ClassMethods
|
21
|
+
def crop_attached_file(attachment_name, opts = {})
|
22
|
+
opts = opts.dup
|
23
|
+
|
24
|
+
include PaperCropper::ModelExtension::InstanceMethods
|
25
|
+
|
26
|
+
aspect = normalize_aspect opts[:aspect]
|
27
|
+
send :define_method, :"#{attachment_name}_aspect" do
|
28
|
+
aspect.first.to_f / aspect.last.to_f if aspect
|
29
|
+
end
|
30
|
+
|
31
|
+
if respond_to? :attachment_definitions
|
32
|
+
# for Paperclip <= 3.4
|
33
|
+
definitions = attachment_definitions
|
34
|
+
else
|
35
|
+
# for Paperclip >= 3.5
|
36
|
+
definitions = Paperclip::Tasks::Attachments.instance.definitions_for(self)
|
37
|
+
end
|
38
|
+
processors = definitions[attachment_name][:processors] ||= []
|
39
|
+
unless processors.include? :paper_cropper
|
40
|
+
processors << :paper_cropper
|
41
|
+
end
|
42
|
+
|
43
|
+
after_save :"reprocess_to_crop_#{attachment_name}_attachment"
|
44
|
+
after_update {
|
45
|
+
if send(:attachment_changed?, attachment_name)
|
46
|
+
self.update_column(:"#{attachment_name}_updated_at", Time.now)
|
47
|
+
end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a valid and normalized value for aspect ratio
|
52
|
+
# It will return 1.. if aspect is nil or a invalid string
|
53
|
+
# @param aspect [Range, String, FalseClass]
|
54
|
+
#
|
55
|
+
# @return [Range]
|
56
|
+
def normalize_aspect(aspect)
|
57
|
+
if aspect.kind_of?(String) && aspect =~ PaperCropper::RegExp::ASPECT
|
58
|
+
Range.new *aspect.split(':').map(&:to_i)
|
59
|
+
elsif aspect.kind_of?(Range)
|
60
|
+
return aspect.first.to_i..aspect.last.to_i
|
61
|
+
elsif aspect == false
|
62
|
+
return aspect
|
63
|
+
else
|
64
|
+
1..1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Instance methods
|
70
|
+
# #########################################################################
|
71
|
+
module InstanceMethods
|
72
|
+
# Asks if the attachment received a crop process
|
73
|
+
# @param attachment_name [Symbol]
|
74
|
+
#
|
75
|
+
# @return [Boolean]
|
76
|
+
def cropping?(attachment_name)
|
77
|
+
!self.send(:"#{attachment_name}_crop_x").blank? &&
|
78
|
+
!self.send(:"#{attachment_name}_crop_y").blank? &&
|
79
|
+
!self.send(:"#{attachment_name}_crop_width").blank? &&
|
80
|
+
!self.send(:"#{attachment_name}_crop_height").blank?
|
81
|
+
end
|
82
|
+
|
83
|
+
def attachment_changed?(attachment_name)
|
84
|
+
["#{attachment_name}_name", "#{attachment_name}_content_type",
|
85
|
+
"#{attachment_name}_file_size", "#{attachment_name}_updated_at",
|
86
|
+
"#{attachment_name}_crop_x", "#{attachment_name}_crop_y",
|
87
|
+
"#{attachment_name}_crop_width",
|
88
|
+
"#{attachment_name}_crop_height"
|
89
|
+
].any? { |attr|
|
90
|
+
method= "#{attr}_changed?".to_sym
|
91
|
+
if respond_to?(method)
|
92
|
+
self.send(method)
|
93
|
+
end
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
# Uses method missing to responding the model callback for reprocess the image
|
98
|
+
def method_missing(method, *args)
|
99
|
+
if method.to_s =~ PaperCropper::RegExp::CALLBACK
|
100
|
+
reprocess_cropped_attachment(
|
101
|
+
method.to_s.scan(PaperCropper::RegExp::CALLBACK).flatten.first.to_sym
|
102
|
+
)
|
103
|
+
else
|
104
|
+
super
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
# Saves the attachment if the crop attributes are present
|
110
|
+
# @param attachment_name [Symbol]
|
111
|
+
def reprocess_cropped_attachment(attachment_name)
|
112
|
+
if cropping?(attachment_name)
|
113
|
+
attachment_instance = send(attachment_name)
|
114
|
+
attachment_instance.assign(attachment_instance)
|
115
|
+
attachment_instance.save
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
# ActiveRercord support
|
124
|
+
# #########################################################################
|
125
|
+
if defined? ActiveRecord::Base
|
126
|
+
ActiveRecord::Base.class_eval do
|
127
|
+
extend PaperCropper::ModelExtension::ClassMethods
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Custom migration data type
|
2
|
+
# Based on papercli schema.rb from: https://github.com/thoughtbot/paperclip => /lib/paperclip/schema.rb
|
3
|
+
module PaperCropper
|
4
|
+
module Schema
|
5
|
+
COLUMNS = {
|
6
|
+
crop_x: :integer,
|
7
|
+
crop_y: :integer,
|
8
|
+
crop_width: :integer,
|
9
|
+
crop_height: :integer
|
10
|
+
}
|
11
|
+
|
12
|
+
#
|
13
|
+
# #########################################################################
|
14
|
+
module Statements
|
15
|
+
def add_crop_attachment(table_name, *attachment_names)
|
16
|
+
raise ArgumentError, "Please specify attachment name in your add_attachment call in your migration." if attachment_names.empty?
|
17
|
+
|
18
|
+
options = attachment_names.extract_options!
|
19
|
+
|
20
|
+
attachment_names.each do |attachment_name|
|
21
|
+
COLUMNS.each_pair do |column_name, column_type|
|
22
|
+
column_options = options.merge(options[column_name.to_sym] || {})
|
23
|
+
add_column(table_name, "#{attachment_name}_#{column_name}", column_type, column_options)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def remove_crop_attachment(table_name, *attachment_names)
|
29
|
+
raise ArgumentError, "Please specify attachment name in your remove_attachment call in your migration." if attachment_names.empty?
|
30
|
+
|
31
|
+
attachment_names.each do |attachment_name|
|
32
|
+
COLUMNS.keys.each do |column_name|
|
33
|
+
remove_column(table_name, "#{attachment_name}_#{column_name}")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# #########################################################################
|
41
|
+
module TableDefinition
|
42
|
+
def crop_attachment(*attachment_names)
|
43
|
+
options = attachment_names.extract_options!
|
44
|
+
attachment_names.each do |attachment_name|
|
45
|
+
COLUMNS.each_pair do |column_name, column_type|
|
46
|
+
column_options = options.merge(options[column_name.to_sym] || {})
|
47
|
+
column("#{attachment_name}_#{column_name}", column_type, column_options)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# #########################################################################
|
55
|
+
module CommandRecorder
|
56
|
+
def add_attachment(*args)
|
57
|
+
record(:add_attachment, args)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def invert_add_attachment(args)
|
63
|
+
[:remove_attachment, args]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
# ActiveRecord support
|
72
|
+
# #########################################################################
|
73
|
+
ActiveRecord::ConnectionAdapters::Table.send :include, PaperCropper::Schema::TableDefinition
|
74
|
+
ActiveRecord::ConnectionAdapters::TableDefinition.send :include, PaperCropper::Schema::TableDefinition
|
75
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, PaperCropper::Schema::Statements
|
76
|
+
ActiveRecord::Migration::CommandRecorder.send :include, PaperCropper::Schema::CommandRecorder
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module PaperCropper
|
2
|
+
# Changelog based on Semantic Versioning: http://semver.org
|
3
|
+
# REMEMBER: Major version zero (0.y.z) is for initial development. Anything may change at any time. The public API should not be considered stable.
|
4
|
+
|
5
|
+
# 0.0.2 PATCH
|
6
|
+
#- Added ActiveRecord extensions to force attachment updated_at field to be setted
|
7
|
+
# on every attachment change.
|
8
|
+
|
9
|
+
unless defined?(PaperCropper::VERSION)
|
10
|
+
VERSION = "0.0.2".freeze
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "paperclip"
|
2
|
+
|
3
|
+
module Paperclip
|
4
|
+
class PaperCropper < Thumbnail
|
5
|
+
def transformation_command
|
6
|
+
if crop_command
|
7
|
+
crop_command + super.join(' ').sub(/ -crop \S+/, '').split(' ')
|
8
|
+
else
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def crop_command
|
14
|
+
target = @attachment.instance
|
15
|
+
|
16
|
+
if target.cropping?(@attachment.name)
|
17
|
+
begin
|
18
|
+
x = Integer(target.send :"#{@attachment.name}_crop_x")
|
19
|
+
y = Integer(target.send :"#{@attachment.name}_crop_y")
|
20
|
+
w = Integer(target.send :"#{@attachment.name}_crop_width")
|
21
|
+
h = Integer(target.send :"#{@attachment.name}_crop_height")
|
22
|
+
["-crop", "#{w}x#{h}+#{x}+#{y}"]
|
23
|
+
rescue Exception => e
|
24
|
+
::PaperCropper.log("[paper_cropper] #{@attachment.name} crop w/h/x/y were non-integer. Error: #{e.to_s}")
|
25
|
+
return
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
Binary file
|