paper_cropper 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+

|
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
|