cropper 0.2.1

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