cropped_paperclip 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/.rvmrc +48 -0
- data/Gemfile +7 -0
- data/README.md +17 -0
- data/Rakefile +31 -0
- data/app/.DS_Store +0 -0
- data/app/assets/.DS_Store +0 -0
- data/app/assets/javascripts/.DS_Store +0 -0
- data/app/assets/javascripts/cropped_paperclip/filedrop.js.coffee +254 -0
- data/app/assets/javascripts/cropped_paperclip/upload_crop_scale.js.coffee +319 -0
- data/app/assets/javascripts/es5-shim.js +1105 -0
- data/app/assets/javascripts/uploader.js.coffee +3 -0
- data/app/controllers/cropped_paperclip/application_controller.rb +4 -0
- data/app/controllers/cropped_paperclip/uploads_controller.rb +40 -0
- data/app/helpers/cropped_paperclip/application_helper.rb +4 -0
- data/app/models/upload.rb +119 -0
- data/config/routes.rb +3 -0
- data/cropped_paperclip.gemspec +32 -0
- data/db/migrate/20120510103921_uploads.rb +11 -0
- data/init.rb +4 -0
- data/lib/cropped_paperclip.rb +170 -0
- data/lib/cropped_paperclip/engine.rb +7 -0
- data/lib/cropped_paperclip/glue.rb +20 -0
- data/lib/cropped_paperclip/schema.rb +35 -0
- data/lib/cropped_paperclip/version.rb +3 -0
- data/lib/paperclip/geometry_transformation.rb +80 -0
- data/lib/paperclip/validators/attachment_height_validator.rb +89 -0
- data/lib/paperclip/validators/attachment_width_validator.rb +89 -0
- data/lib/paperclip_processors/offset_thumbnail.rb +86 -0
- data/lib/tasks/cropped_paperclip_tasks.rake +4 -0
- data/script/rails +8 -0
- data/spec/.DS_Store +0 -0
- data/spec/acceptance/acceptance_helper.rb +2 -0
- data/spec/controllers/uploads_controller_spec.rb +5 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/thing.rb +15 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +56 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/db/migrate/20120510104910_things.rb +8 -0
- data/spec/dummy/db/schema.rb +51 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/fixtures/images/.DS_Store +0 -0
- data/spec/fixtures/images/icon.png +0 -0
- data/spec/fixtures/images/test.jpg +0 -0
- data/spec/models/thing_spec.rb +6 -0
- data/spec/models/upload_spec.rb +13 -0
- data/spec/spec_helper.rb +18 -0
- metadata +309 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,
|
7
|
+
# Only full ruby name is supported here, for short names use:
|
8
|
+
# echo "rvm use 1.9.3" > .rvmrc
|
9
|
+
environment_id="ruby-1.9.3-p194"
|
10
|
+
|
11
|
+
# Uncomment the following lines if you want to verify rvm version per project
|
12
|
+
# rvmrc_rvm_version="1.13.8 (master)" # 1.10.1 seams as a safe start
|
13
|
+
# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
|
14
|
+
# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
|
15
|
+
# return 1
|
16
|
+
# }
|
17
|
+
|
18
|
+
# First we attempt to load the desired environment directly from the environment
|
19
|
+
# file. This is very fast and efficient compared to running through the entire
|
20
|
+
# CLI and selector. If you want feedback on which environment was used then
|
21
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
22
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
|
23
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
24
|
+
then
|
25
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
26
|
+
[[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
|
27
|
+
\. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
|
28
|
+
else
|
29
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
30
|
+
rvm --create "$environment_id" || {
|
31
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
32
|
+
return 1
|
33
|
+
}
|
34
|
+
fi
|
35
|
+
|
36
|
+
# If you use bundler, this might be useful to you:
|
37
|
+
# if [[ -s Gemfile ]] && {
|
38
|
+
# ! builtin command -v bundle >/dev/null ||
|
39
|
+
# builtin command -v bundle | GREP_OPTIONS= \grep $rvm_path/bin/bundle >/dev/null
|
40
|
+
# }
|
41
|
+
# then
|
42
|
+
# printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
|
43
|
+
# gem install bundler
|
44
|
+
# fi
|
45
|
+
# if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null
|
46
|
+
# then
|
47
|
+
# bundle install | GREP_OPTIONS= \grep -vE '^Using|Your bundle is complete'
|
48
|
+
# fi
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# CroppedPaperclip
|
2
|
+
|
3
|
+
This is a rails-specific gem. It uses paperclip to provide a general-purpose upload-and-crop mechanism
|
4
|
+
in which a single upload object (with attached file) can be shared between any number of models, and
|
5
|
+
each model can have any number of upload relations. Each associate has its own cropping paramaters.
|
6
|
+
|
7
|
+
## Status
|
8
|
+
|
9
|
+
New and in progress. Extracted from working code but likely to have suffered in the process. Please file issues.
|
10
|
+
|
11
|
+
## Copyright
|
12
|
+
|
13
|
+
Developed by spanner for Socionical and released under the MIT license.
|
14
|
+
|
15
|
+
## Bugs and issues
|
16
|
+
|
17
|
+
Very likely. Please file github issues or write to will at spanner dot org.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'CroppedPaperclip'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
24
|
+
load 'rails/tasks/engine.rake'
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
28
|
+
require 'rspec/core/rake_task'
|
29
|
+
|
30
|
+
RSpec::Core::RakeTask.new(:spec)
|
31
|
+
task :default => :spec
|
data/app/.DS_Store
ADDED
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,254 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
# This is a somewhat hacked up version of filedrop, by Weixi Yen:
|
4
|
+
#
|
5
|
+
# Email: [Firstname][Lastname]@gmail.com
|
6
|
+
#
|
7
|
+
# Copyright (c) 2010 Resopollution
|
8
|
+
#
|
9
|
+
# Licensed under the MIT license:
|
10
|
+
# http://www.opensource.org/licenses/mit-license.php
|
11
|
+
#
|
12
|
+
# Project home:
|
13
|
+
# http://www.github.com/weixiyen/jquery-filedrop
|
14
|
+
#
|
15
|
+
# Version: 0.1.0
|
16
|
+
#
|
17
|
+
# Features:
|
18
|
+
# Allows sending of extra parameters with file.
|
19
|
+
# Works with Firefox 3.6+
|
20
|
+
# Future-compliant with HTML5 spec (will work with Webkit browsers and IE9)
|
21
|
+
# Usage:
|
22
|
+
# See README at project homepage
|
23
|
+
|
24
|
+
Modernizr.addTest 'filereader', ->
|
25
|
+
!!(window.File && window.FileList && window.FileReader)
|
26
|
+
|
27
|
+
jQuery ($) ->
|
28
|
+
jQuery.event.props.push "dataTransfer"
|
29
|
+
|
30
|
+
opts = {}
|
31
|
+
errors = [ "BrowserNotSupported", "TooManyFiles", "FileTooLarge" ]
|
32
|
+
doc_leave_timer = undefined
|
33
|
+
stop_loop = false
|
34
|
+
files_count = 0
|
35
|
+
files = undefined
|
36
|
+
|
37
|
+
drop = (e) ->
|
38
|
+
e.preventDefault()
|
39
|
+
opts.drop e
|
40
|
+
files = e.dataTransfer.files
|
41
|
+
unless Modernizr.filereader && files?
|
42
|
+
opts.error(errors[0])
|
43
|
+
return false
|
44
|
+
files_count = files.length
|
45
|
+
upload()
|
46
|
+
false
|
47
|
+
|
48
|
+
pick = (e, filefield) ->
|
49
|
+
e.preventDefault()
|
50
|
+
files = filefield.files
|
51
|
+
unless Modernizr.filereader && files?
|
52
|
+
opts.error(errors[0])
|
53
|
+
return false
|
54
|
+
files_count = files.length
|
55
|
+
upload()
|
56
|
+
false
|
57
|
+
|
58
|
+
getBuilder = (filename, filedata, boundary) ->
|
59
|
+
dashdash = "--"
|
60
|
+
crlf = "\r\n"
|
61
|
+
builder = ""
|
62
|
+
$.each opts.data, (i, val) ->
|
63
|
+
val = val() if typeof val is "function"
|
64
|
+
builder += dashdash
|
65
|
+
builder += boundary
|
66
|
+
builder += crlf
|
67
|
+
builder += "Content-Disposition: form-data; name=\"" + i + "\""
|
68
|
+
builder += crlf
|
69
|
+
builder += crlf
|
70
|
+
builder += val
|
71
|
+
builder += crlf
|
72
|
+
|
73
|
+
builder += dashdash
|
74
|
+
builder += boundary
|
75
|
+
builder += crlf
|
76
|
+
builder += "Content-Disposition: form-data; name=\"" + opts.paramname + "\""
|
77
|
+
builder += "; filename=\"" + filename + "\""
|
78
|
+
builder += crlf
|
79
|
+
builder += "Content-Type: image/jpeg"
|
80
|
+
builder += crlf
|
81
|
+
builder += crlf
|
82
|
+
builder += filedata
|
83
|
+
builder += crlf
|
84
|
+
builder += dashdash
|
85
|
+
builder += boundary
|
86
|
+
builder += dashdash
|
87
|
+
builder += crlf
|
88
|
+
builder
|
89
|
+
|
90
|
+
progress = (e) ->
|
91
|
+
if e.lengthComputable
|
92
|
+
percentage = Math.round((e.loaded * 100) / e.total)
|
93
|
+
unless @currentProgress is percentage
|
94
|
+
@currentProgress = percentage
|
95
|
+
opts.progressUpdated @index, @file, @currentProgress
|
96
|
+
elapsed = new Date().getTime()
|
97
|
+
diffTime = elapsed - @currentStart
|
98
|
+
if diffTime >= opts.refresh
|
99
|
+
diffData = e.loaded - @startData
|
100
|
+
speed = diffData / diffTime
|
101
|
+
opts.speedUpdated @index, @file, speed
|
102
|
+
@startData = e.loaded
|
103
|
+
@currentStart = elapsed
|
104
|
+
|
105
|
+
upload = ->
|
106
|
+
send = (e) ->
|
107
|
+
e.target.index = getIndexBySize(e.total) if e.target.index is `undefined`
|
108
|
+
xhr = new XMLHttpRequest()
|
109
|
+
ul = xhr.upload
|
110
|
+
file = files[e.target.index]
|
111
|
+
index = e.target.index
|
112
|
+
start_time = new Date().getTime()
|
113
|
+
boundary = "------multipartformboundary" + (new Date).getTime()
|
114
|
+
builder = undefined
|
115
|
+
newName = rename(file.name)
|
116
|
+
if typeof newName is "string"
|
117
|
+
builder = getBuilder(newName, e.target.result, boundary)
|
118
|
+
else
|
119
|
+
builder = getBuilder(file.name, e.target.result, boundary)
|
120
|
+
ul.index = index
|
121
|
+
ul.file = file
|
122
|
+
ul.downloadStartTime = start_time
|
123
|
+
ul.currentStart = start_time
|
124
|
+
ul.currentProgress = 0
|
125
|
+
ul.startData = 0
|
126
|
+
ul.addEventListener "progress", progress, false
|
127
|
+
xhr.open "POST", opts.url, true
|
128
|
+
xhr.setRequestHeader "content-type", "multipart/form-data; boundary=" + boundary
|
129
|
+
xhr.sendAsBinary builder
|
130
|
+
opts.uploadStarted index, file, files_count
|
131
|
+
xhr.onload = ->
|
132
|
+
if xhr.responseText
|
133
|
+
now = new Date().getTime()
|
134
|
+
timeDiff = now - start_time
|
135
|
+
result = opts.uploadFinished(index, file, xhr.responseText, timeDiff)
|
136
|
+
filesDone++
|
137
|
+
afterAll() if filesDone is files_count - filesRejected
|
138
|
+
stop_loop = true if result is false
|
139
|
+
|
140
|
+
stop_loop = false
|
141
|
+
unless files
|
142
|
+
opts.error errors[0]
|
143
|
+
return false
|
144
|
+
filesDone = 0
|
145
|
+
filesRejected = 0
|
146
|
+
if files_count > opts.maxfiles
|
147
|
+
opts.error errors[1]
|
148
|
+
return false
|
149
|
+
i = 0
|
150
|
+
|
151
|
+
while i < files_count
|
152
|
+
return false if stop_loop
|
153
|
+
try
|
154
|
+
unless beforeEach(files[i]) is false
|
155
|
+
return if i is files_count
|
156
|
+
reader = new FileReader()
|
157
|
+
max_file_size = 1048576 * opts.maxfilesize
|
158
|
+
reader.index = i
|
159
|
+
if files[i].size > max_file_size
|
160
|
+
opts.error errors[2], files[i], i
|
161
|
+
filesRejected++
|
162
|
+
continue
|
163
|
+
reader.onloadend = send
|
164
|
+
reader.readAsBinaryString files[i]
|
165
|
+
else
|
166
|
+
filesRejected++
|
167
|
+
catch err
|
168
|
+
opts.error errors[0]
|
169
|
+
return false
|
170
|
+
i++
|
171
|
+
getIndexBySize = (size) ->
|
172
|
+
i = 0
|
173
|
+
|
174
|
+
while i < files_count
|
175
|
+
return i if files[i].size is size
|
176
|
+
i++
|
177
|
+
`undefined`
|
178
|
+
rename = (name) ->
|
179
|
+
opts.rename name
|
180
|
+
beforeEach = (file) ->
|
181
|
+
opts.beforeEach file
|
182
|
+
afterAll = ->
|
183
|
+
opts.afterAll()
|
184
|
+
dragEnter = (e) ->
|
185
|
+
clearTimeout doc_leave_timer
|
186
|
+
e.preventDefault()
|
187
|
+
opts.dragEnter e
|
188
|
+
dragOver = (e) ->
|
189
|
+
clearTimeout doc_leave_timer
|
190
|
+
e.preventDefault()
|
191
|
+
opts.docOver e
|
192
|
+
opts.dragOver e
|
193
|
+
dragLeave = (e) ->
|
194
|
+
clearTimeout doc_leave_timer
|
195
|
+
opts.dragLeave e
|
196
|
+
e.stopPropagation()
|
197
|
+
docDrop = (e) ->
|
198
|
+
e.preventDefault()
|
199
|
+
opts.docLeave e
|
200
|
+
false
|
201
|
+
docEnter = (e) ->
|
202
|
+
clearTimeout doc_leave_timer
|
203
|
+
e.preventDefault()
|
204
|
+
opts.docEnter e
|
205
|
+
false
|
206
|
+
docOver = (e) ->
|
207
|
+
clearTimeout doc_leave_timer
|
208
|
+
e.preventDefault()
|
209
|
+
opts.docOver e
|
210
|
+
false
|
211
|
+
docLeave = (e) ->
|
212
|
+
doc_leave_timer = setTimeout(->
|
213
|
+
opts.docLeave e
|
214
|
+
, 200)
|
215
|
+
empty = ->
|
216
|
+
|
217
|
+
default_opts =
|
218
|
+
url: ""
|
219
|
+
refresh: 1000
|
220
|
+
paramname: "userfile"
|
221
|
+
maxfiles: 25
|
222
|
+
maxfilesize: 1
|
223
|
+
data: {}
|
224
|
+
drop: empty
|
225
|
+
dragEnter: empty
|
226
|
+
dragOver: empty
|
227
|
+
dragLeave: empty
|
228
|
+
docEnter: empty
|
229
|
+
docOver: empty
|
230
|
+
docLeave: empty
|
231
|
+
beforeEach: empty
|
232
|
+
afterAll: empty
|
233
|
+
rename: empty
|
234
|
+
error: (err, file, i) ->
|
235
|
+
alert err
|
236
|
+
|
237
|
+
uploadStarted: empty
|
238
|
+
uploadFinished: empty
|
239
|
+
progressUpdated: empty
|
240
|
+
speedUpdated: empty
|
241
|
+
|
242
|
+
$.fn.filedrop = (options) ->
|
243
|
+
opts = $.extend({}, default_opts, options)
|
244
|
+
@bind("drop", drop).bind("pick", pick).bind("dragenter", dragEnter).bind("dragover", dragOver).bind "dragleave", dragLeave
|
245
|
+
$(document).bind("drop", docDrop).bind("dragenter", docEnter).bind("dragover", docOver).bind "dragleave", docLeave
|
246
|
+
|
247
|
+
try
|
248
|
+
return if XMLHttpRequest::sendAsBinary
|
249
|
+
XMLHttpRequest::sendAsBinary = (datastr) ->
|
250
|
+
byteValue = (x) ->
|
251
|
+
x.charCodeAt(0) & 0xff
|
252
|
+
ords = Array::map.call(datastr, byteValue)
|
253
|
+
ui8a = new Uint8Array(ords)
|
254
|
+
@send ui8a.buffer
|
@@ -0,0 +1,319 @@
|
|
1
|
+
jQuery ($) ->
|
2
|
+
$.fn.uploader = (opts) ->
|
3
|
+
@each ->
|
4
|
+
options = $.extend {}, opts
|
5
|
+
dropbox = $(@)
|
6
|
+
csrf_token = dropbox.parents("form").find('input[name="authenticity_token"]').val()
|
7
|
+
filefield_selector = options.filefield ? 'input[type="file"]'
|
8
|
+
filefield = dropbox.find(filefield_selector)
|
9
|
+
url = options.url ? dropbox.attr("data-upload-path") ? dropbox.attr("rel")
|
10
|
+
paramname = options.paramname ? "upload[file]"
|
11
|
+
|
12
|
+
finisher = (i, file, response, time) ->
|
13
|
+
dropbox.find(".progress_holder").remove()
|
14
|
+
dropbox.find(".waiter").remove()
|
15
|
+
new Cropper(response, dropbox)
|
16
|
+
|
17
|
+
dropbox.filedrop
|
18
|
+
maxfiles: 1
|
19
|
+
maxfilesize: 10
|
20
|
+
url: url
|
21
|
+
paramname: paramname
|
22
|
+
data:
|
23
|
+
authenticity_token: csrf_token
|
24
|
+
|
25
|
+
error: (err, file) ->
|
26
|
+
switch err
|
27
|
+
when "BrowserNotSupported"
|
28
|
+
auth = $('input[name="authenticity_token"]').clone()
|
29
|
+
form = $('<form id="uform" method="post" enctype="multipart/form-data" />').append(auth)
|
30
|
+
iframe = $('<iframe id="uframe" name="uframe" />').appendTo($('body'))
|
31
|
+
newff = filefield.clone()
|
32
|
+
filefield.before(newff).attr("name", paramname)
|
33
|
+
form.append(filefield).appendTo("body").attr("action", url).attr("target", "uframe")
|
34
|
+
newff.change((e) ->
|
35
|
+
dropbox.trigger "pick", filefield[0]
|
36
|
+
)
|
37
|
+
filefield = newff
|
38
|
+
iframe.bind "load", () ->
|
39
|
+
response = iframe[0].contentWindow.document.body.innerHTML
|
40
|
+
if response and response isnt ""
|
41
|
+
finisher.call this, null, null, response, null
|
42
|
+
iframe.remove()
|
43
|
+
form.remove()
|
44
|
+
|
45
|
+
dropbox.find(".instructions").hide()
|
46
|
+
dropbox.find(".img").fadeTo('slow', 0.1)
|
47
|
+
dropbox.find(".waiter").show()
|
48
|
+
form.submit()
|
49
|
+
|
50
|
+
when "TooManyFiles"
|
51
|
+
alert "You can only upload 1 file."
|
52
|
+
|
53
|
+
when "FileTooLarge"
|
54
|
+
alert "#{file.name} is too large! Files up to 10MB are allowed"
|
55
|
+
|
56
|
+
else
|
57
|
+
alert "#{file.name} caused an unknown error: #{err}"
|
58
|
+
|
59
|
+
dragOver: ->
|
60
|
+
dropbox.addClass "hover"
|
61
|
+
|
62
|
+
dragLeave: ->
|
63
|
+
dropbox.removeClass "hover"
|
64
|
+
|
65
|
+
beforeEach: (file) ->
|
66
|
+
dropbox.removeClass "hover"
|
67
|
+
unless file.type.match(/^image\//)
|
68
|
+
alert "Sorry: only image files are allowed!"
|
69
|
+
false
|
70
|
+
|
71
|
+
afterAll: ->
|
72
|
+
filefield.val ""
|
73
|
+
|
74
|
+
uploadStarted: (i, file, len) ->
|
75
|
+
dropbox.find("img").fadeTo "fast", 0.5
|
76
|
+
dropbox.find("p.instructions").hide()
|
77
|
+
dropbox.append "<div class=\"progress_holder\"><div class=\"progress\"></div><div class=\"commentary\">0% uploaded</div></div>"
|
78
|
+
|
79
|
+
progressUpdated: (i, file, progress) ->
|
80
|
+
dropbox.find("div.progress").width progress + "%"
|
81
|
+
dropbox.find("div.commentary").text progress + "% uploaded"
|
82
|
+
|
83
|
+
uploadFinished: finisher
|
84
|
+
|
85
|
+
dropbox.find("a.picker").picker()
|
86
|
+
filefield.change (e) ->
|
87
|
+
dropbox.trigger "pick", filefield[0]
|
88
|
+
@
|
89
|
+
|
90
|
+
$.fn.recropper = ->
|
91
|
+
dropbox = $("div.dropbox")
|
92
|
+
@click (e) ->
|
93
|
+
e.preventDefault()
|
94
|
+
dropbox.find("div.waiter").show()
|
95
|
+
$.get $(this).attr("href"), ((response) ->
|
96
|
+
new Cropper(response, dropbox)
|
97
|
+
), "html"
|
98
|
+
@
|
99
|
+
|
100
|
+
$.fn.picker = ->
|
101
|
+
@click (e) ->
|
102
|
+
e.preventDefault()
|
103
|
+
e.stopPropagation()
|
104
|
+
$("#file_upload").trigger('click')
|
105
|
+
@
|
106
|
+
|
107
|
+
$.fn.click_proxy = (target_selector) ->
|
108
|
+
this.bind "click", (e) ->
|
109
|
+
e.preventDefault()
|
110
|
+
$(target_selector).click()
|
111
|
+
|
112
|
+
class Cropper
|
113
|
+
constructor: (response, container) ->
|
114
|
+
@element = $(response)
|
115
|
+
@container = container
|
116
|
+
@preview = @element.find("div.preview")
|
117
|
+
@fields = @element.find("fieldset.crop")
|
118
|
+
@overflow = $("<div class=\"overflow\">").append(@preview.find("img").clone())
|
119
|
+
@controls = @container.find(".controls")
|
120
|
+
@container.find("div.preview").remove()
|
121
|
+
@container.find("div.img").after(@preview)
|
122
|
+
@container.find("div.waiter").hide()
|
123
|
+
@container.append @fields
|
124
|
+
@container.before @overflow
|
125
|
+
|
126
|
+
@top = @preview.position().top
|
127
|
+
@left = @preview.position().left
|
128
|
+
@lastX = 0
|
129
|
+
@lastY = 0
|
130
|
+
|
131
|
+
range = @fields.find("input[type=\"range\"]")
|
132
|
+
@scaler = new Scaler range,
|
133
|
+
drag: @showOverflow
|
134
|
+
move: @resize
|
135
|
+
drop: @hideOverflow
|
136
|
+
|
137
|
+
@controls.find(".cancel a").bind "click", @cancel
|
138
|
+
@preview.bind "mousedown", @drag
|
139
|
+
|
140
|
+
@recalculateLimits()
|
141
|
+
@setOverflow()
|
142
|
+
@setControls()
|
143
|
+
|
144
|
+
drag: (e) =>
|
145
|
+
e.preventDefault()
|
146
|
+
$(document).bind "mousemove", @move
|
147
|
+
$(document).bind "mouseup", @drop
|
148
|
+
@lastY = e.pageY
|
149
|
+
@lastX = e.pageX
|
150
|
+
@showOverflow()
|
151
|
+
|
152
|
+
move: (e) =>
|
153
|
+
e.preventDefault()
|
154
|
+
@moveTop e.pageY - @lastY
|
155
|
+
@moveLeft e.pageX - @lastX
|
156
|
+
@lastY = e.pageY
|
157
|
+
@lastX = e.pageX
|
158
|
+
@setOverflow()
|
159
|
+
|
160
|
+
resize: (w) =>
|
161
|
+
h = Math.round(w * @aspect)
|
162
|
+
deltaT = Math.round((w - @preview.width()) / 2)
|
163
|
+
deltaL = Math.round((h - @preview.height()) / 2)
|
164
|
+
@preview.css
|
165
|
+
width: w
|
166
|
+
height: h
|
167
|
+
@fields.find("input.sh").val h
|
168
|
+
@recalculateLimits()
|
169
|
+
@moveTop(-deltaT)
|
170
|
+
@moveLeft(-deltaL)
|
171
|
+
@setOverflow()
|
172
|
+
|
173
|
+
recalculateLimits: (argument) =>
|
174
|
+
@toplimit = @container.height() - @preview.height()
|
175
|
+
@leftlimit = @container.width() - @preview.width()
|
176
|
+
@aspect = @preview.height() / @preview.width()
|
177
|
+
|
178
|
+
moveTop: (y) =>
|
179
|
+
@top = @top + y
|
180
|
+
@top = 0 if @top > 0
|
181
|
+
@top = @toplimit if @top < @toplimit
|
182
|
+
@preview.css "top", @top
|
183
|
+
@fields.find("input.ot").val @top
|
184
|
+
|
185
|
+
moveLeft: (x) =>
|
186
|
+
@left = @left + x
|
187
|
+
@left = 0 if @left > 0
|
188
|
+
@left = @leftlimit if @left < @leftlimit
|
189
|
+
@preview.css "left", @left
|
190
|
+
@fields.find("input.ol").val @left
|
191
|
+
|
192
|
+
drop: (e) =>
|
193
|
+
$(document).unbind "mousemove", @move
|
194
|
+
$(document).unbind "mouseup", @drop
|
195
|
+
@move e
|
196
|
+
@hideOverflow()
|
197
|
+
|
198
|
+
showOverflow: =>
|
199
|
+
@overflow.fadeTo('normal', 0.3)
|
200
|
+
|
201
|
+
hideOverflow: =>
|
202
|
+
@overflow.fadeOut('normal')
|
203
|
+
|
204
|
+
setOverflow: (argument) =>
|
205
|
+
@overflow.css
|
206
|
+
width: @preview.width()
|
207
|
+
height: @preview.height()
|
208
|
+
@overflow.offset @preview.offset()
|
209
|
+
|
210
|
+
cancel: (e) =>
|
211
|
+
e.preventDefault()
|
212
|
+
@preview.remove()
|
213
|
+
@overflow.remove()
|
214
|
+
@scaler.remove()
|
215
|
+
@fields.remove()
|
216
|
+
@resetControls()
|
217
|
+
@container.find("img").fadeIn "slow"
|
218
|
+
@container.find("p.instructions").show()
|
219
|
+
|
220
|
+
complete: (e) =>
|
221
|
+
e.preventDefault()
|
222
|
+
@scaler.hide()
|
223
|
+
@hideOverflow()
|
224
|
+
@preview.unbind "mousedown", @drag
|
225
|
+
@preview.css "cursor", 'auto'
|
226
|
+
@container.find(".range_marker").hide()
|
227
|
+
@resetControls()
|
228
|
+
@controls.find(".recrop").removeClass('unavailable').unbind('click').bind "click", @resume
|
229
|
+
|
230
|
+
resume: (e) =>
|
231
|
+
e.preventDefault()
|
232
|
+
@scaler.show()
|
233
|
+
@showOverflow()
|
234
|
+
@preview.bind "mousedown", @drag
|
235
|
+
@preview.css "cursor", 'move'
|
236
|
+
@container.find(".range_marker").show()
|
237
|
+
@setControls()
|
238
|
+
|
239
|
+
setControls: =>
|
240
|
+
@controls.show()
|
241
|
+
@controls.find(".edit").hide()
|
242
|
+
@controls.find(".cancel").show()
|
243
|
+
@controls.find("a.picker").addClass("unavailable").unbind "click"
|
244
|
+
@controls.find(".save a").removeClass("unavailable").bind "click", @complete
|
245
|
+
|
246
|
+
resetControls: =>
|
247
|
+
@controls.find(".cancel").hide()
|
248
|
+
@controls.find(".edit").show()
|
249
|
+
@controls.find("a.picker").removeClass("unavailable").picker()
|
250
|
+
@controls.find(".save a").addClass("unavailable").unbind("click")
|
251
|
+
|
252
|
+
|
253
|
+
class Scaler
|
254
|
+
constructor: (range, callbacks) ->
|
255
|
+
@callbacks = $.extend {}, callbacks
|
256
|
+
@input = $(range)
|
257
|
+
@pos = 0
|
258
|
+
@value = @input.val()
|
259
|
+
@max = parseInt(@input.attr("max"), 10)
|
260
|
+
@min = parseInt(@input.attr("min"), 10)
|
261
|
+
@slider = $("<span class=\"slider\"><span class=\"scale\"><span class=\"marker\"></span></span></span>")
|
262
|
+
@scale = @slider.find(".scale")
|
263
|
+
@scale_width = 150
|
264
|
+
|
265
|
+
@marker = @slider.find(".marker")
|
266
|
+
@lastX = 0
|
267
|
+
|
268
|
+
@reposition()
|
269
|
+
@marker.bind("mousedown", @drag)
|
270
|
+
@input.before(@slider).hide()
|
271
|
+
|
272
|
+
drag: (e) =>
|
273
|
+
e.preventDefault()
|
274
|
+
@lastX = e.pageX
|
275
|
+
$(document).bind "mousemove", @move
|
276
|
+
$(document).bind "mouseup", @drop
|
277
|
+
@callbacks.drag?.call @, @value
|
278
|
+
|
279
|
+
move: (e) =>
|
280
|
+
deltaX = e.pageX - @lastX
|
281
|
+
@.pos = @pos + e.pageX - @lastX
|
282
|
+
@.pos = 0 if @pos < 0
|
283
|
+
@.pos = 400 if @pos > 400
|
284
|
+
@.placeMarker(@pos)
|
285
|
+
@.recalculate()
|
286
|
+
@.lastX = e.pageX
|
287
|
+
@callbacks.move?.call @, @value
|
288
|
+
|
289
|
+
drop: (e) =>
|
290
|
+
@move e
|
291
|
+
$(document).unbind "mousemove", @move
|
292
|
+
$(document).unbind "mouseup", @drop
|
293
|
+
@callbacks.drop?.call @, @value
|
294
|
+
|
295
|
+
recalculate: =>
|
296
|
+
origin = @min
|
297
|
+
pixel_proportion = (@pos / @scale_width)
|
298
|
+
value_width = @max - @min
|
299
|
+
@value = Math.round(origin + (value_width * pixel_proportion))
|
300
|
+
@input.val(@value)
|
301
|
+
|
302
|
+
reposition: =>
|
303
|
+
origin = @min
|
304
|
+
value_proportion = (@value - origin) / (@max - origin)
|
305
|
+
@pos = Math.round(@scale_width * value_proportion)
|
306
|
+
@placeMarker @pos
|
307
|
+
|
308
|
+
placeMarker: (x) =>
|
309
|
+
@marker.css "left", x - 3
|
310
|
+
|
311
|
+
remove: =>
|
312
|
+
@slider.remove()
|
313
|
+
|
314
|
+
hide: =>
|
315
|
+
@slider.hide()
|
316
|
+
|
317
|
+
show: =>
|
318
|
+
@slider.show()
|
319
|
+
|