cropped_paperclip 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) 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/.DS_Store +0 -0
  8. data/app/assets/.DS_Store +0 -0
  9. data/app/assets/javascripts/.DS_Store +0 -0
  10. data/app/assets/javascripts/cropped_paperclip/filedrop.js.coffee +254 -0
  11. data/app/assets/javascripts/cropped_paperclip/upload_crop_scale.js.coffee +319 -0
  12. data/app/assets/javascripts/es5-shim.js +1105 -0
  13. data/app/assets/javascripts/uploader.js.coffee +3 -0
  14. data/app/controllers/cropped_paperclip/application_controller.rb +4 -0
  15. data/app/controllers/cropped_paperclip/uploads_controller.rb +40 -0
  16. data/app/helpers/cropped_paperclip/application_helper.rb +4 -0
  17. data/app/models/upload.rb +119 -0
  18. data/config/routes.rb +3 -0
  19. data/cropped_paperclip.gemspec +32 -0
  20. data/db/migrate/20120510103921_uploads.rb +11 -0
  21. data/init.rb +4 -0
  22. data/lib/cropped_paperclip.rb +170 -0
  23. data/lib/cropped_paperclip/engine.rb +7 -0
  24. data/lib/cropped_paperclip/glue.rb +20 -0
  25. data/lib/cropped_paperclip/schema.rb +35 -0
  26. data/lib/cropped_paperclip/version.rb +3 -0
  27. data/lib/paperclip/geometry_transformation.rb +80 -0
  28. data/lib/paperclip/validators/attachment_height_validator.rb +89 -0
  29. data/lib/paperclip/validators/attachment_width_validator.rb +89 -0
  30. data/lib/paperclip_processors/offset_thumbnail.rb +86 -0
  31. data/lib/tasks/cropped_paperclip_tasks.rake +4 -0
  32. data/script/rails +8 -0
  33. data/spec/.DS_Store +0 -0
  34. data/spec/acceptance/acceptance_helper.rb +2 -0
  35. data/spec/controllers/uploads_controller_spec.rb +5 -0
  36. data/spec/dummy/README.rdoc +261 -0
  37. data/spec/dummy/Rakefile +7 -0
  38. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  39. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  40. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  41. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  42. data/spec/dummy/app/mailers/.gitkeep +0 -0
  43. data/spec/dummy/app/models/.gitkeep +0 -0
  44. data/spec/dummy/app/models/thing.rb +15 -0
  45. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  46. data/spec/dummy/config.ru +4 -0
  47. data/spec/dummy/config/application.rb +56 -0
  48. data/spec/dummy/config/boot.rb +10 -0
  49. data/spec/dummy/config/database.yml +25 -0
  50. data/spec/dummy/config/environment.rb +5 -0
  51. data/spec/dummy/config/environments/development.rb +37 -0
  52. data/spec/dummy/config/environments/production.rb +67 -0
  53. data/spec/dummy/config/environments/test.rb +37 -0
  54. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  55. data/spec/dummy/config/initializers/inflections.rb +15 -0
  56. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  57. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  58. data/spec/dummy/config/initializers/session_store.rb +8 -0
  59. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  60. data/spec/dummy/config/locales/en.yml +5 -0
  61. data/spec/dummy/config/routes.rb +3 -0
  62. data/spec/dummy/db/migrate/20120510104910_things.rb +8 -0
  63. data/spec/dummy/db/schema.rb +51 -0
  64. data/spec/dummy/lib/assets/.gitkeep +0 -0
  65. data/spec/dummy/log/.gitkeep +0 -0
  66. data/spec/dummy/public/404.html +26 -0
  67. data/spec/dummy/public/422.html +26 -0
  68. data/spec/dummy/public/500.html +25 -0
  69. data/spec/dummy/public/favicon.ico +0 -0
  70. data/spec/dummy/script/rails +6 -0
  71. data/spec/fixtures/images/.DS_Store +0 -0
  72. data/spec/fixtures/images/icon.png +0 -0
  73. data/spec/fixtures/images/test.jpg +0 -0
  74. data/spec/models/thing_spec.rb +6 -0
  75. data/spec/models/upload_spec.rb +13 -0
  76. data/spec/spec_helper.rb +18 -0
  77. metadata +309 -0
data/.gitignore ADDED
@@ -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 cropped_paperclip.gemspec
4
+ gemspec
5
+
6
+ # jquery-rails is used by the dummy application
7
+ gem "jquery-rails"
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
+