cropper 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.gitignore +8 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +48 -0
  4. data/Gemfile +7 -0
  5. data/README.md +17 -0
  6. data/Rakefile +31 -0
  7. data/app/assets/images/cropper/progress_stripes.png +0 -0
  8. data/app/assets/images/cropper/scale.png +0 -0
  9. data/app/assets/images/cropper/scale_large.png +0 -0
  10. data/app/assets/images/cropper/scale_small.png +0 -0
  11. data/app/assets/images/cropper/upload_spinner.gif +0 -0
  12. data/app/assets/javascripts/cropper.js.coffee +9 -0
  13. data/app/assets/javascripts/cropper/filedrop.js.coffee +255 -0
  14. data/app/assets/javascripts/cropper/upload_crop_scale.js.coffee +360 -0
  15. data/app/assets/javascripts/lib/es5-shim.js +1105 -0
  16. data/app/assets/javascripts/lib/modernizr.js +4 -0
  17. data/app/assets/stylesheets/cropper/uploads.css.sass +203 -0
  18. data/app/controllers/cropper/application_controller.rb +4 -0
  19. data/app/controllers/cropper/uploads_controller.rb +74 -0
  20. data/app/helpers/cropper/application_helper.rb +4 -0
  21. data/app/models/cropper/upload.rb +231 -0
  22. data/app/views/cropper/uploads/_crop.html.haml +34 -0
  23. data/app/views/cropper/uploads/_error.html.haml +1 -0
  24. data/app/views/cropper/uploads/_pick.html.haml +42 -0
  25. data/app/views/cropper/uploads/edit.html.haml +6 -0
  26. data/app/views/cropper/uploads/new.html.haml +5 -0
  27. data/app/views/cropper/uploads/show.html.haml +1 -0
  28. data/config/locales/en.yml +5 -0
  29. data/config/routes.rb +3 -0
  30. data/cropper.gemspec +32 -0
  31. data/db/migrate/20120510103921_uploads.rb +11 -0
  32. data/db/migrate/20130226190434_uploads_hold_crop_data.rb +8 -0
  33. data/db/migrate/20130402074802_upload_holder.rb +20 -0
  34. data/init.rb +4 -0
  35. data/lib/cropper.rb +159 -0
  36. data/lib/cropper/engine.rb +8 -0
  37. data/lib/cropper/glue.rb +20 -0
  38. data/lib/cropper/routing.rb +23 -0
  39. data/lib/cropper/schema.rb +63 -0
  40. data/lib/cropper/version.rb +3 -0
  41. data/lib/paperclip/geometry_transformation.rb +80 -0
  42. data/lib/paperclip/validators/attachment_height_validator.rb +89 -0
  43. data/lib/paperclip/validators/attachment_width_validator.rb +89 -0
  44. data/lib/paperclip_processors/offset_thumbnail.rb +93 -0
  45. data/lib/tasks/cropper_tasks.rake +4 -0
  46. data/script/rails +8 -0
  47. data/spec/acceptance/acceptance_helper.rb +2 -0
  48. data/spec/controllers/uploads_controller_spec.rb +5 -0
  49. data/spec/dummy/README.rdoc +261 -0
  50. data/spec/dummy/Rakefile +7 -0
  51. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  52. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  53. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  54. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  55. data/spec/dummy/app/mailers/.gitkeep +0 -0
  56. data/spec/dummy/app/models/.gitkeep +0 -0
  57. data/spec/dummy/app/models/thing.rb +4 -0
  58. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  59. data/spec/dummy/config.ru +4 -0
  60. data/spec/dummy/config/application.rb +56 -0
  61. data/spec/dummy/config/boot.rb +10 -0
  62. data/spec/dummy/config/database.yml +25 -0
  63. data/spec/dummy/config/environment.rb +5 -0
  64. data/spec/dummy/config/environments/development.rb +37 -0
  65. data/spec/dummy/config/environments/production.rb +67 -0
  66. data/spec/dummy/config/environments/test.rb +37 -0
  67. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  68. data/spec/dummy/config/initializers/inflections.rb +15 -0
  69. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  70. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  71. data/spec/dummy/config/initializers/session_store.rb +8 -0
  72. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  73. data/spec/dummy/config/locales/en.yml +5 -0
  74. data/spec/dummy/config/routes.rb +6 -0
  75. data/spec/dummy/db/migrate/20120510104910_things.rb +8 -0
  76. data/spec/dummy/db/schema.rb +51 -0
  77. data/spec/dummy/lib/assets/.gitkeep +0 -0
  78. data/spec/dummy/log/.gitkeep +0 -0
  79. data/spec/dummy/public/404.html +26 -0
  80. data/spec/dummy/public/422.html +26 -0
  81. data/spec/dummy/public/500.html +25 -0
  82. data/spec/dummy/public/favicon.ico +0 -0
  83. data/spec/dummy/script/rails +6 -0
  84. data/spec/fixtures/images/icon.png +0 -0
  85. data/spec/fixtures/images/test.jpg +0 -0
  86. data/spec/models/thing_spec.rb +6 -0
  87. data/spec/models/upload_spec.rb +7 -0
  88. data/spec/spec_helper.rb +18 -0
  89. metadata +326 -0
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ spec/dummy/db/*.sqlite3
6
+ spec/dummy/log/*.log
7
+ spec/dummy/tmp/
8
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
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
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cropper.gemspec
4
+ gemspec
5
+
6
+ # jquery-rails is used by the dummy application
7
+ gem "jquery-rails"
@@ -0,0 +1,17 @@
1
+ # Cropper
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.
@@ -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 = 'cropper'
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
@@ -0,0 +1,9 @@
1
+ #= require lib/modernizr
2
+ #= require lib/es5-shim
3
+ #= require cropper/filedrop
4
+ #= require cropper/upload_crop_scale
5
+
6
+ jQuery ($) ->
7
+ $.activate_with () ->
8
+ @find_including_self('[data-action="upload"]').uploader()
9
+ @find_including_self('[data-action="recrop"]').recropper()
@@ -0,0 +1,255 @@
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
+ console.log "pick", files
56
+ upload()
57
+ false
58
+
59
+ getBuilder = (filename, filedata, boundary) ->
60
+ dashdash = "--"
61
+ crlf = "\r\n"
62
+ builder = ""
63
+ $.each opts.data, (i, val) ->
64
+ val = val() if typeof val is "function"
65
+ builder += dashdash
66
+ builder += boundary
67
+ builder += crlf
68
+ builder += "Content-Disposition: form-data; name=\"" + i + "\""
69
+ builder += crlf
70
+ builder += crlf
71
+ builder += val
72
+ builder += crlf
73
+
74
+ builder += dashdash
75
+ builder += boundary
76
+ builder += crlf
77
+ builder += "Content-Disposition: form-data; name=\"" + opts.paramname + "\""
78
+ builder += "; filename=\"" + filename + "\""
79
+ builder += crlf
80
+ builder += "Content-Type: image/jpeg"
81
+ builder += crlf
82
+ builder += crlf
83
+ builder += filedata
84
+ builder += crlf
85
+ builder += dashdash
86
+ builder += boundary
87
+ builder += dashdash
88
+ builder += crlf
89
+ builder
90
+
91
+ progress = (e) ->
92
+ if e.lengthComputable
93
+ percentage = Math.round((e.loaded * 100) / e.total)
94
+ unless @currentProgress is percentage
95
+ @currentProgress = percentage
96
+ opts.progressUpdated @index, @file, @currentProgress
97
+ elapsed = new Date().getTime()
98
+ diffTime = elapsed - @currentStart
99
+ if diffTime >= opts.refresh
100
+ diffData = e.loaded - @startData
101
+ speed = diffData / diffTime
102
+ opts.speedUpdated @index, @file, speed
103
+ @startData = e.loaded
104
+ @currentStart = elapsed
105
+
106
+ upload = ->
107
+ send = (e) ->
108
+ e.target.index = getIndexBySize(e.total) if e.target.index is `undefined`
109
+ xhr = new XMLHttpRequest()
110
+ ul = xhr.upload
111
+ file = files[e.target.index]
112
+ index = e.target.index
113
+ start_time = new Date().getTime()
114
+ boundary = "------multipartformboundary" + (new Date).getTime()
115
+ builder = undefined
116
+ newName = rename(file.name)
117
+ if typeof newName is "string"
118
+ builder = getBuilder(newName, e.target.result, boundary)
119
+ else
120
+ builder = getBuilder(file.name, e.target.result, boundary)
121
+ ul.index = index
122
+ ul.file = file
123
+ ul.downloadStartTime = start_time
124
+ ul.currentStart = start_time
125
+ ul.currentProgress = 0
126
+ ul.startData = 0
127
+ ul.addEventListener "progress", progress, false
128
+ xhr.open "POST", opts.url, true
129
+ xhr.setRequestHeader "content-type", "multipart/form-data; boundary=" + boundary
130
+ xhr.sendAsBinary builder
131
+ opts.uploadStarted index, file, files_count
132
+ xhr.onload = ->
133
+ if xhr.responseText
134
+ now = new Date().getTime()
135
+ timeDiff = now - start_time
136
+ result = opts.uploadFinished(index, file, xhr.responseText, timeDiff)
137
+ filesDone++
138
+ afterAll() if filesDone is files_count - filesRejected
139
+ stop_loop = true if result is false
140
+
141
+ stop_loop = false
142
+ unless files
143
+ opts.error errors[0]
144
+ return false
145
+ filesDone = 0
146
+ filesRejected = 0
147
+ if files_count > opts.maxfiles
148
+ opts.error errors[1]
149
+ return false
150
+ i = 0
151
+
152
+ while i < files_count
153
+ return false if stop_loop
154
+ try
155
+ unless beforeEach(files[i]) is false
156
+ return if i is files_count
157
+ reader = new FileReader()
158
+ max_file_size = 1048576 * opts.maxfilesize
159
+ reader.index = i
160
+ if files[i].size > max_file_size
161
+ opts.error errors[2], files[i], i
162
+ filesRejected++
163
+ continue
164
+ reader.onloadend = send
165
+ reader.readAsBinaryString files[i]
166
+ else
167
+ filesRejected++
168
+ catch err
169
+ opts.error errors[0]
170
+ return false
171
+ i++
172
+ getIndexBySize = (size) ->
173
+ i = 0
174
+
175
+ while i < files_count
176
+ return i if files[i].size is size
177
+ i++
178
+ `undefined`
179
+ rename = (name) ->
180
+ opts.rename name
181
+ beforeEach = (file) ->
182
+ opts.beforeEach file
183
+ afterAll = ->
184
+ opts.afterAll()
185
+ dragEnter = (e) ->
186
+ clearTimeout doc_leave_timer
187
+ e.preventDefault()
188
+ opts.dragEnter e
189
+ dragOver = (e) ->
190
+ clearTimeout doc_leave_timer
191
+ e.preventDefault()
192
+ opts.docOver e
193
+ opts.dragOver e
194
+ dragLeave = (e) ->
195
+ clearTimeout doc_leave_timer
196
+ opts.dragLeave e
197
+ e.stopPropagation()
198
+ docDrop = (e) ->
199
+ e.preventDefault()
200
+ opts.docLeave e
201
+ false
202
+ docEnter = (e) ->
203
+ clearTimeout doc_leave_timer
204
+ e.preventDefault()
205
+ opts.docEnter e
206
+ false
207
+ docOver = (e) ->
208
+ clearTimeout doc_leave_timer
209
+ e.preventDefault()
210
+ opts.docOver e
211
+ false
212
+ docLeave = (e) ->
213
+ doc_leave_timer = setTimeout(->
214
+ opts.docLeave e
215
+ , 200)
216
+ empty = ->
217
+
218
+ default_opts =
219
+ url: ""
220
+ refresh: 1000
221
+ paramname: "userfile"
222
+ maxfiles: 25
223
+ maxfilesize: 1
224
+ data: {}
225
+ drop: empty
226
+ dragEnter: empty
227
+ dragOver: empty
228
+ dragLeave: empty
229
+ docEnter: empty
230
+ docOver: empty
231
+ docLeave: empty
232
+ beforeEach: empty
233
+ afterAll: empty
234
+ rename: empty
235
+ error: (err, file, i) ->
236
+ alert err
237
+
238
+ uploadStarted: empty
239
+ uploadFinished: empty
240
+ progressUpdated: empty
241
+ speedUpdated: empty
242
+
243
+ $.fn.filedrop = (options) ->
244
+ opts = $.extend({}, default_opts, options)
245
+ @bind("drop", drop).bind("pick", pick).bind("dragenter", dragEnter).bind("dragover", dragOver).bind "dragleave", dragLeave
246
+ $(document).bind("drop", docDrop).bind("dragenter", docEnter).bind("dragover", docOver).bind "dragleave", docLeave
247
+
248
+ try
249
+ return if XMLHttpRequest::sendAsBinary
250
+ XMLHttpRequest::sendAsBinary = (datastr) ->
251
+ byteValue = (x) ->
252
+ x.charCodeAt(0) & 0xff
253
+ ords = Array::map.call(datastr, byteValue)
254
+ ui8a = new Uint8Array(ords)
255
+ @send ui8a.buffer
@@ -0,0 +1,360 @@
1
+ jQuery ($) ->
2
+
3
+ $.fn.detach_upload = ->
4
+ @click (e) ->
5
+ e.preventDefault() if e
6
+ $(@).parents('div.uploadbox').find('input[data-clear-on-detach]').val(1)
7
+ $(@).parents('.img').empty()
8
+
9
+ $.fn.recropper = ->
10
+ uploadbox = $("div.uploadbox")
11
+ @click (e) ->
12
+ e.preventDefault()
13
+ uploadbox.find("div.waiter").show()
14
+ $.get $(this).attr("href"), ((response) ->
15
+ new Cropper(response, uploadbox)
16
+ ), "html"
17
+ @
18
+
19
+ $.fn.picker = (filefield) ->
20
+ @click (e) ->
21
+ e.preventDefault()
22
+ e.stopPropagation()
23
+ filefield ?= $(".uploadbox").find('input[type="file"]')
24
+ filefield.trigger('click')
25
+ @
26
+
27
+ $.fn.click_proxy = (target_selector) ->
28
+ this.bind "click", (e) ->
29
+ e.preventDefault()
30
+ $(target_selector).click()
31
+
32
+ $.fn.uploader = (opts) ->
33
+ @each ->
34
+ options = $.extend {}, opts
35
+ dropbox = $(@)
36
+ csrf_token = dropbox.parents("form").find('input[name="authenticity_token"]').val()
37
+ filefield = dropbox.find('input[type="file"]')
38
+ url = options.url ? dropbox.attr("data-url")
39
+ paramname = options.paramname ? "upload[file]"
40
+
41
+ finisher = (i, file, response, time) ->
42
+ dropbox.find(".progress_holder").remove()
43
+ dropbox.find(".waiter").remove()
44
+ new Cropper(response, dropbox)
45
+
46
+ dropbox.filedrop
47
+ maxfiles: 1
48
+ maxfilesize: 10
49
+ url: url
50
+ paramname: paramname
51
+ data:
52
+ authenticity_token: csrf_token
53
+
54
+ error: (err, file) ->
55
+ switch err
56
+ when "BrowserNotSupported"
57
+ auth = $('input[name="authenticity_token"]').clone()
58
+ form = $('<form id="uform" method="post" enctype="multipart/form-data" />').append(auth)
59
+ iframe = $('<iframe id="uframe" name="uframe" />').appendTo($('body'))
60
+ newff = filefield.clone()
61
+ filefield.before(newff).attr("name", paramname)
62
+ form.append(filefield).appendTo("body").attr("action", url).attr("target", "uframe")
63
+ newff.change((e) ->
64
+ dropbox.trigger "pick", filefield[0]
65
+ )
66
+ filefield = newff
67
+ iframe.bind "load", () ->
68
+ response = iframe[0].contentWindow.document.body.innerHTML
69
+ if response and response isnt ""
70
+ finisher.call this, null, null, response, null
71
+ iframe.remove()
72
+ form.remove()
73
+
74
+ dropbox.find(".instructions").hide()
75
+ dropbox.find(".img").fadeTo('slow', 0.1)
76
+ dropbox.find(".waiter").show()
77
+ form.submit()
78
+
79
+ when "TooManyFiles"
80
+ alert "You can only upload 1 file."
81
+
82
+ when "FileTooLarge"
83
+ alert "#{file.name} is too large! Files up to 10MB are allowed"
84
+
85
+ else
86
+ alert "#{file.name} caused an unknown error: #{err}"
87
+
88
+ dragOver: ->
89
+ dropbox.addClass "hover"
90
+
91
+ dragLeave: ->
92
+ dropbox.removeClass "hover"
93
+
94
+ beforeEach: (file) ->
95
+ dropbox.removeClass "hover"
96
+ unless file.type.match(/^image\//)
97
+ alert "Sorry: only image files are allowed!"
98
+ false
99
+
100
+ afterAll: ->
101
+ filefield.val ""
102
+
103
+ uploadStarted: (i, file, len) ->
104
+ dropbox.find("img").fadeTo "fast", 0.5
105
+ dropbox.find("p.instructions").hide()
106
+ dropbox.append "<div class=\"progress_holder\"><div class=\"progress\"></div><div class=\"commentary\">0% uploaded</div></div>"
107
+
108
+ progressUpdated: (i, file, progress) ->
109
+ dropbox.find("div.progress").width progress + "%"
110
+ dropbox.find("div.commentary").text progress + "% uploaded"
111
+
112
+ uploadFinished: finisher
113
+
114
+ dropbox.find('a[data-action="pick"]').picker(filefield)
115
+ dropbox.find('a[data-action="detach"]').detach_upload()
116
+ filefield.change (e) ->
117
+ dropbox.trigger "pick", filefield[0]
118
+ @
119
+
120
+
121
+
122
+
123
+ class Cropper
124
+ constructor: (response, container) ->
125
+ @element = $(response)
126
+ @container = $(container)
127
+ @filefield = @container.find('input[type="file"]')
128
+ @fields = @element.find("fieldset.crop")
129
+ @preview = @element.find("div.preview")
130
+ @instructions = @element.find("p.drag_instructions")
131
+ @overflow = $("<div class=\"overflow\">").append(@preview.find("img").clone())
132
+ @controls = @container.find(".controls")
133
+ @container.find("div.preview").remove()
134
+ @container.find("div.img").after(@preview)
135
+ @container.find("div.waiter").hide()
136
+ @container.append @instructions
137
+ @container.append @fields
138
+ @container.before @overflow
139
+
140
+ @filefield?.prop('disabled', true)
141
+
142
+ @detacher = $('<a href="#" class="detach" />').appendTo(@container)
143
+ @detacher.click @cancel
144
+
145
+ @top = @preview.position().top
146
+ @left = @preview.position().left
147
+ @lastX = 0
148
+ @lastY = 0
149
+
150
+ range = @fields.find("input[type=\"range\"]")
151
+ @scaler = new Scaler range,
152
+ drag: @showOverflow
153
+ move: @resize
154
+ drop: @hideOverflow
155
+
156
+ @controls.find(".cancel a").bind "click", @cancel
157
+ @preview.bind "mousedown", @drag
158
+
159
+ @recalculateLimits()
160
+ @setOverflow()
161
+ @setControls()
162
+ @container.bind "mouseover", @showClutter
163
+ @container.bind "mouseout", @hideClutter
164
+ # @hide()
165
+
166
+ drag: (e) =>
167
+ e.preventDefault()
168
+ $(document).bind "mousemove", @move
169
+ $(document).bind "mouseup", @drop
170
+ @lastY = e.pageY
171
+ @lastX = e.pageX
172
+ @hideClutter()
173
+ @showOverflow()
174
+
175
+ move: (e) =>
176
+ e.preventDefault()
177
+ @moveTop e.pageY - @lastY
178
+ @moveLeft e.pageX - @lastX
179
+ @lastY = e.pageY
180
+ @lastX = e.pageX
181
+ @setOverflow()
182
+
183
+ resize: (w) =>
184
+ h = Math.round(w * @aspect)
185
+ deltaT = Math.round((w - @preview.width()) / 2)
186
+ deltaL = Math.round((h - @preview.height()) / 2)
187
+ @preview.css
188
+ width: w
189
+ height: h
190
+ @fields.find("input.sh").val h
191
+ @recalculateLimits()
192
+ @moveTop(-deltaT)
193
+ @moveLeft(-deltaL)
194
+ @setOverflow()
195
+
196
+ recalculateLimits: (argument) =>
197
+ @toplimit = @container.height() - @preview.height()
198
+ @leftlimit = @container.width() - @preview.width()
199
+ @aspect = @preview.height() / @preview.width()
200
+
201
+ moveTop: (y) =>
202
+ @top = @top + y
203
+ @top = 0 if @top > 0
204
+ @top = @toplimit if @top < @toplimit
205
+ @preview.css "top", @top
206
+ @fields.find("input.ot").val @top
207
+
208
+ moveLeft: (x) =>
209
+ @left = @left + x
210
+ @left = 0 if @left > 0
211
+ @left = @leftlimit if @left < @leftlimit
212
+ @preview.css "left", @left
213
+ @fields.find("input.ol").val @left
214
+
215
+ drop: (e) =>
216
+ $(document).unbind "mousemove", @move
217
+ $(document).unbind "mouseup", @drop
218
+ @move e
219
+ @showClutter()
220
+ @hideOverflow()
221
+
222
+ setOverflow: () =>
223
+ @overflow.css
224
+ width: @preview.width()
225
+ height: @preview.height()
226
+ @overflow.offset @preview.offset()
227
+
228
+ cancel: (e) =>
229
+ e.preventDefault()
230
+ @preview.remove()
231
+ @overflow.remove()
232
+ @scaler.remove()
233
+ @fields.remove()
234
+ @detacher.remove()
235
+ @instructions.remove()
236
+ @resetControls()
237
+ @container.find("img").fadeIn "slow"
238
+ @container.find("p.instructions").show()
239
+ @filefield?.prop('disabled', false)
240
+
241
+ resume: (e) =>
242
+ e.preventDefault()
243
+ @scaler.show()
244
+ @detacher.show()
245
+ @showOverflow()
246
+ @preview.bind "mousedown", @drag
247
+ @preview.css "cursor", 'move'
248
+ @container.find(".range_marker").show()
249
+ @setControls()
250
+
251
+ setControls: =>
252
+ @controls.show()
253
+ @controls.find(".edit").hide()
254
+ @controls.find(".cancel").show()
255
+ @controls.find('a[data-action="pick"]').addClass("unavailable").unbind("click").bind("click", @blocker)
256
+ @detacher.bind "click", @cancel
257
+
258
+ resetControls: =>
259
+ @controls.find(".cancel").hide()
260
+ @controls.find(".edit").show()
261
+ @controls.find('a[data-action="pick"]').removeClass("unavailable").picker()
262
+
263
+ blocker: (e) =>
264
+ if e
265
+ e.preventDefault()
266
+ e.stopPropagation()
267
+
268
+ show: =>
269
+ @showClutter()
270
+ @showOverflow()
271
+
272
+ showClutter: =>
273
+ @scaler?.show()
274
+ @controls?.show()
275
+ @detacher?.show()
276
+ @instructions?.show()
277
+
278
+ showOverflow: =>
279
+ @overflow.show()#fadeTo('normal', 0.3)
280
+
281
+ hide: =>
282
+ @hideClutter()
283
+ @hideOverflow()
284
+
285
+ hideClutter: =>
286
+ @scaler?.hide()
287
+ @controls?.hide()
288
+ @detacher?.hide()
289
+ @instructions?.hide()
290
+
291
+ hideOverflow: =>
292
+ @overflow.hide()#fadeOut('normal')
293
+
294
+ class Scaler
295
+ constructor: (range, callbacks) ->
296
+ @callbacks = $.extend {}, callbacks
297
+ @input = $(range)
298
+ @pos = 0
299
+ @value = @input.val()
300
+ @max = parseInt(@input.attr("max"), 10)
301
+ @min = parseInt(@input.attr("min"), 10)
302
+ @slider = $("<span class=\"slider\"><span class=\"scale\"><span class=\"marker\"></span></span></span>")
303
+ @scale = @slider.find(".scale")
304
+ @marker = @slider.find(".marker")
305
+ @lastX = 0
306
+
307
+ @marker.bind("mousedown", @drag)
308
+ @input.before(@slider).hide()
309
+ @scale_width = @scale.width() || 480
310
+ @reposition()
311
+
312
+ drag: (e) =>
313
+ e.preventDefault()
314
+ @lastX = e.pageX
315
+ $(document).bind "mousemove", @move
316
+ $(document).bind "mouseup", @drop
317
+ @callbacks.drag?.call @, @value
318
+
319
+ move: (e) =>
320
+ deltaX = e.pageX - @lastX
321
+ @pos = @pos + deltaX
322
+ @pos = 0 if @pos < 0
323
+ @pos = @scale_width-5 if @pos > @scale_width-5
324
+ @placeMarker(@pos)
325
+ @recalculate()
326
+ @lastX = e.pageX
327
+ @callbacks.move?.call @, @value
328
+
329
+ drop: (e) =>
330
+ @move e
331
+ $(document).unbind "mousemove", @move
332
+ $(document).unbind "mouseup", @drop
333
+ @callbacks.drop?.call @, @value
334
+
335
+ recalculate: =>
336
+ origin = @min
337
+ pixel_proportion = (@pos / @scale_width)
338
+ value_width = @max - @min
339
+ @value = Math.round(origin + (value_width * pixel_proportion))
340
+ @input.val(@value)
341
+
342
+ reposition: =>
343
+ origin = @min
344
+ value_proportion = (@value - origin) / (@max - origin)
345
+ @pos = Math.round(@scale_width * value_proportion)
346
+ @placeMarker @pos
347
+
348
+ placeMarker: (x) =>
349
+ @marker.css "left", x
350
+
351
+ remove: =>
352
+ @slider.remove()
353
+
354
+ hide: =>
355
+ @slider.hide()
356
+
357
+ show: =>
358
+ @slider.show()
359
+
360
+