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,4 @@
1
+ /* Modernizr 2.0.6 | MIT & BSD
2
+ * Contains: All core tests, html5shiv, yepnope, respond.js. Get your own custom build at www.modernizr.com/download/
3
+ */
4
+ ;window.Modernizr=function(a,b,c){function I(){e.input=function(a){for(var b=0,c=a.length;b<c;b++)t[a[b]]=a[b]in l;return t}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),e.inputtypes=function(a){for(var d=0,e,f,h,i=a.length;d<i;d++)l.setAttribute("type",f=a[d]),e=l.type!=="text",e&&(l.value=m,l.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(f)&&l.style.WebkitAppearance!==c?(g.appendChild(l),h=b.defaultView,e=h.getComputedStyle&&h.getComputedStyle(l,null).WebkitAppearance!=="textfield"&&l.offsetHeight!==0,g.removeChild(l)):/^(search|tel)$/.test(f)||(/^(url|email)$/.test(f)?e=l.checkValidity&&l.checkValidity()===!1:/^color$/.test(f)?(g.appendChild(l),g.offsetWidth,e=l.value!=m,g.removeChild(l)):e=l.value!=m)),s[a[d]]=!!e;return s}("search tel url email datetime date month week time datetime-local number range color".split(" "))}function G(a,b){var c=a.charAt(0).toUpperCase()+a.substr(1),d=(a+" "+p.join(c+" ")+c).split(" ");return F(d,b)}function F(a,b){for(var d in a)if(k[a[d]]!==c)return b=="pfx"?a[d]:!0;return!1}function E(a,b){return!!~(""+a).indexOf(b)}function D(a,b){return typeof a===b}function C(a,b){return B(o.join(a+";")+(b||""))}function B(a){k.cssText=a}var d="2.0.6",e={},f=!0,g=b.documentElement,h=b.head||b.getElementsByTagName("head")[0],i="modernizr",j=b.createElement(i),k=j.style,l=b.createElement("input"),m=":)",n=Object.prototype.toString,o=" -webkit- -moz- -o- -ms- -khtml- ".split(" "),p="Webkit Moz O ms Khtml".split(" "),q={svg:"http://www.w3.org/2000/svg"},r={},s={},t={},u=[],v=function(a,c,d,e){var f,h,j,k=b.createElement("div");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:i+(d+1),k.appendChild(j);f=["&shy;","<style>",a,"</style>"].join(""),k.id=i,k.innerHTML+=f,g.appendChild(k),h=c(k,a),k.parentNode.removeChild(k);return!!h},w=function(b){if(a.matchMedia)return matchMedia(b).matches;var c;v("@media "+b+" { #"+i+" { position: absolute; } }",function(b){c=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle).position=="absolute"});return c},x=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=D(e[d],"function"),D(e[d],c)||(e[d]=c),e.removeAttribute(d))),e=null;return f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),y,z={}.hasOwnProperty,A;!D(z,c)&&!D(z.call,c)?A=function(a,b){return z.call(a,b)}:A=function(a,b){return b in a&&D(a.constructor.prototype[b],c)};var H=function(c,d){var f=c.join(""),g=d.length;v(f,function(c,d){var f=b.styleSheets[b.styleSheets.length-1],h=f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"",i=c.childNodes,j={};while(g--)j[i[g].id]=i[g];e.touch="ontouchstart"in a||j.touch.offsetTop===9,e.csstransforms3d=j.csstransforms3d.offsetLeft===9,e.generatedcontent=j.generatedcontent.offsetHeight>=1,e.fontface=/src/i.test(h)&&h.indexOf(d.split(" ")[0])===0},g,d)}(['@font-face {font-family:"font";src:url("https://")}',["@media (",o.join("touch-enabled),("),i,")","{#touch{top:9px;position:absolute}}"].join(""),["@media (",o.join("transform-3d),("),i,")","{#csstransforms3d{left:9px;position:absolute}}"].join(""),['#generatedcontent:after{content:"',m,'";visibility:hidden}'].join("")],["fontface","touch","csstransforms3d","generatedcontent"]);r.flexbox=function(){function c(a,b,c,d){a.style.cssText=o.join(b+":"+c+";")+(d||"")}function a(a,b,c,d){b+=":",a.style.cssText=(b+o.join(c+";"+b)).slice(0,-b.length)+(d||"")}var d=b.createElement("div"),e=b.createElement("div");a(d,"display","box","width:42px;padding:0;"),c(e,"box-flex","1","width:10px;"),d.appendChild(e),g.appendChild(d);var f=e.offsetWidth===42;d.removeChild(e),g.removeChild(d);return f},r.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},r.canvastext=function(){return!!e.canvas&&!!D(b.createElement("canvas").getContext("2d").fillText,"function")},r.webgl=function(){return!!a.WebGLRenderingContext},r.touch=function(){return e.touch},r.geolocation=function(){return!!navigator.geolocation},r.postmessage=function(){return!!a.postMessage},r.websqldatabase=function(){var b=!!a.openDatabase;return b},r.indexedDB=function(){for(var b=-1,c=p.length;++b<c;)if(a[p[b].toLowerCase()+"IndexedDB"])return!0;return!!a.indexedDB},r.hashchange=function(){return x("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},r.history=function(){return!!a.history&&!!history.pushState},r.draganddrop=function(){return x("dragstart")&&x("drop")},r.websockets=function(){for(var b=-1,c=p.length;++b<c;)if(a[p[b]+"WebSocket"])return!0;return"WebSocket"in a},r.rgba=function(){B("background-color:rgba(150,255,150,.5)");return E(k.backgroundColor,"rgba")},r.hsla=function(){B("background-color:hsla(120,40%,100%,.5)");return E(k.backgroundColor,"rgba")||E(k.backgroundColor,"hsla")},r.multiplebgs=function(){B("background:url(https://),url(https://),red url(https://)");return/(url\s*\(.*?){3}/.test(k.background)},r.backgroundsize=function(){return G("backgroundSize")},r.borderimage=function(){return G("borderImage")},r.borderradius=function(){return G("borderRadius")},r.boxshadow=function(){return G("boxShadow")},r.textshadow=function(){return b.createElement("div").style.textShadow===""},r.opacity=function(){C("opacity:.55");return/^0.55$/.test(k.opacity)},r.cssanimations=function(){return G("animationName")},r.csscolumns=function(){return G("columnCount")},r.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";B((a+o.join(b+a)+o.join(c+a)).slice(0,-a.length));return E(k.backgroundImage,"gradient")},r.cssreflections=function(){return G("boxReflect")},r.csstransforms=function(){return!!F(["transformProperty","WebkitTransform","MozTransform","OTransform","msTransform"])},r.csstransforms3d=function(){var a=!!F(["perspectiveProperty","WebkitPerspective","MozPerspective","OPerspective","msPerspective"]);a&&"webkitPerspective"in g.style&&(a=e.csstransforms3d);return a},r.csstransitions=function(){return G("transitionProperty")},r.fontface=function(){return e.fontface},r.generatedcontent=function(){return e.generatedcontent},r.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType){c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"');var d='video/mp4; codecs="avc1.42E01E';c.h264=a.canPlayType(d+'"')||a.canPlayType(d+', mp4a.40.2"'),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"')}}catch(e){}return c},r.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"'),c.mp3=a.canPlayType("audio/mpeg;"),c.wav=a.canPlayType('audio/wav; codecs="1"'),c.m4a=a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")}catch(d){}return c},r.localstorage=function(){try{return!!localStorage.getItem}catch(a){return!1}},r.sessionstorage=function(){try{return!!sessionStorage.getItem}catch(a){return!1}},r.webworkers=function(){return!!a.Worker},r.applicationcache=function(){return!!a.applicationCache},r.svg=function(){return!!b.createElementNS&&!!b.createElementNS(q.svg,"svg").createSVGRect},r.inlinesvg=function(){var a=b.createElement("div");a.innerHTML="<svg/>";return(a.firstChild&&a.firstChild.namespaceURI)==q.svg},r.smil=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"animate")))},r.svgclippaths=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"clipPath")))};for(var J in r)A(r,J)&&(y=J.toLowerCase(),e[y]=r[J](),u.push((e[y]?"":"no-")+y));e.input||I(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)A(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return;b=typeof b=="boolean"?b:!!b(),g.className+=" "+(b?"":"no-")+a,e[a]=b}return e},B(""),j=l=null,a.attachEvent&&function(){var a=b.createElement("div");a.innerHTML="<elem></elem>";return a.childNodes.length!==1}()&&function(a,b){function s(a){var b=-1;while(++b<g)a.createElement(f[b])}a.iepp=a.iepp||{};var d=a.iepp,e=d.html5elements||"abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",f=e.split("|"),g=f.length,h=new RegExp("(^|\\s)("+e+")","gi"),i=new RegExp("<(/*)("+e+")","gi"),j=/^\s*[\{\}]\s*$/,k=new RegExp("(^|[^\\n]*?\\s)("+e+")([^\\n]*)({[\\n\\w\\W]*?})","gi"),l=b.createDocumentFragment(),m=b.documentElement,n=m.firstChild,o=b.createElement("body"),p=b.createElement("style"),q=/print|all/,r;d.getCSS=function(a,b){if(a+""===c)return"";var e=-1,f=a.length,g,h=[];while(++e<f){g=a[e];if(g.disabled)continue;b=g.media||b,q.test(b)&&h.push(d.getCSS(g.imports,b),g.cssText),b="all"}return h.join("")},d.parseCSS=function(a){var b=[],c;while((c=k.exec(a))!=null)b.push(((j.exec(c[1])?"\n":c[1])+c[2]+c[3]).replace(h,"$1.iepp_$2")+c[4]);return b.join("\n")},d.writeHTML=function(){var a=-1;r=r||b.body;while(++a<g){var c=b.getElementsByTagName(f[a]),d=c.length,e=-1;while(++e<d)c[e].className.indexOf("iepp_")<0&&(c[e].className+=" iepp_"+f[a])}l.appendChild(r),m.appendChild(o),o.className=r.className,o.id=r.id,o.innerHTML=r.innerHTML.replace(i,"<$1font")},d._beforePrint=function(){p.styleSheet.cssText=d.parseCSS(d.getCSS(b.styleSheets,"all")),d.writeHTML()},d.restoreHTML=function(){o.innerHTML="",m.removeChild(o),m.appendChild(r)},d._afterPrint=function(){d.restoreHTML(),p.styleSheet.cssText=""},s(b),s(l);d.disablePP||(n.insertBefore(p,n.firstChild),p.media="print",p.className="iepp-printshim",a.attachEvent("onbeforeprint",d._beforePrint),a.attachEvent("onafterprint",d._afterPrint))}(a,b),e._version=d,e._prefixes=o,e._domPrefixes=p,e.mq=w,e.hasEvent=x,e.testProp=function(a){return F([a])},e.testAllProps=G,e.testStyles=v,e.prefixed=function(a){return G(a,"pfx")},g.className=g.className.replace(/\bno-js\b/,"")+(f?" js "+u.join(" "):"");return e}(this,this.document),function(a,b){function u(){r(!0)}a.respond={},respond.update=function(){},respond.mediaQueriesSupported=b;if(!b){var c=a.document,d=c.documentElement,e=[],f=[],g=[],h={},i=30,j=c.getElementsByTagName("head")[0]||d,k=j.getElementsByTagName("link"),l=[],m=function(){var b=k,c=b.length,d=0,e,f,g,i;for(;d<c;d++)e=b[d],f=e.href,g=e.media,i=e.rel&&e.rel.toLowerCase()==="stylesheet",!!f&&i&&!h[f]&&(!/^([a-zA-Z]+?:(\/\/)?(www\.)?)/.test(f)||f.replace(RegExp.$1,"").split("/")[0]===a.location.host?l.push({href:f,media:g}):h[f]=!0);n()},n=function(){if(l.length){var a=l.shift();s(a.href,function(b){o(b,a.href,a.media),h[a.href]=!0,n()})}},o=function(a,b,c){var d=a.match(/@media[^\{]+\{([^\{\}]+\{[^\}\{]+\})+/gi),g=d&&d.length||0,b=b.substring(0,b.lastIndexOf("/")),h=function(a){return a.replace(/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,"$1"+b+"$2$3")},i=!g&&c,j=0,k,l,m,n,o;b.length&&(b+="/"),i&&(g=1);for(;j<g;j++){k=0,i?(l=c,f.push(h(a))):(l=d[j].match(/@media ([^\{]+)\{([\S\s]+?)$/)&&RegExp.$1,f.push(RegExp.$2&&h(RegExp.$2))),n=l.split(","),o=n.length;for(;k<o;k++)m=n[k],e.push({media:m.match(/(only\s+)?([a-zA-Z]+)(\sand)?/)&&RegExp.$2,rules:f.length-1,minw:m.match(/\(min\-width:[\s]*([\s]*[0-9]+)px[\s]*\)/)&&parseFloat(RegExp.$1),maxw:m.match(/\(max\-width:[\s]*([\s]*[0-9]+)px[\s]*\)/)&&parseFloat(RegExp.$1)})}r()},p,q,r=function(a){var b="clientWidth",h=d[b],l=c.compatMode==="CSS1Compat"&&h||c.body[b]||h,m={},n=c.createDocumentFragment(),o=k[k.length-1],s=(new Date).getTime();if(a&&p&&s-p<i)clearTimeout(q),q=setTimeout(r,i);else{p=s;for(var t in e){var u=e[t];if(!u.minw&&!u.maxw||(!u.minw||u.minw&&l>=u.minw)&&(!u.maxw||u.maxw&&l<=u.maxw))m[u.media]||(m[u.media]=[]),m[u.media].push(f[u.rules])}for(var t in g)g[t]&&g[t].parentNode===j&&j.removeChild(g[t]);for(var t in m){var v=c.createElement("style"),w=m[t].join("\n");v.type="text/css",v.media=t,v.styleSheet?v.styleSheet.cssText=w:v.appendChild(c.createTextNode(w)),n.appendChild(v),g.push(v)}j.insertBefore(n,o.nextSibling)}},s=function(a,b){var c=t();if(!!c){c.open("GET",a,!0),c.onreadystatechange=function(){c.readyState==4&&(c.status==200||c.status==304)&&b(c.responseText)};if(c.readyState==4)return;c.send()}},t=function(){var a=!1,b=[function(){return new ActiveXObject("Microsoft.XMLHTTP")},function(){return new XMLHttpRequest}],c=b.length;while(c--){try{a=b[c]()}catch(d){continue}break}return function(){return a}}();m(),respond.update=m,a.addEventListener?a.addEventListener("resize",u,!1):a.attachEvent&&a.attachEvent("onresize",u)}}(this,Modernizr.mq("only all")),function(a,b,c){function k(a){return!a||a=="loaded"||a=="complete"}function j(){var a=1,b=-1;while(p.length- ++b)if(p[b].s&&!(a=p[b].r))break;a&&g()}function i(a){var c=b.createElement("script"),d;c.src=a.s,c.onreadystatechange=c.onload=function(){!d&&k(c.readyState)&&(d=1,j(),c.onload=c.onreadystatechange=null)},m(function(){d||(d=1,j())},H.errorTimeout),a.e?c.onload():n.parentNode.insertBefore(c,n)}function h(a){var c=b.createElement("link"),d;c.href=a.s,c.rel="stylesheet",c.type="text/css";if(!a.e&&(w||r)){var e=function(a){m(function(){if(!d)try{a.sheet.cssRules.length?(d=1,j()):e(a)}catch(b){b.code==1e3||b.message=="security"||b.message=="denied"?(d=1,m(function(){j()},0)):e(a)}},0)};e(c)}else c.onload=function(){d||(d=1,m(function(){j()},0))},a.e&&c.onload();m(function(){d||(d=1,j())},H.errorTimeout),!a.e&&n.parentNode.insertBefore(c,n)}function g(){var a=p.shift();q=1,a?a.t?m(function(){a.t=="c"?h(a):i(a)},0):(a(),j()):q=0}function f(a,c,d,e,f,h){function i(){!o&&k(l.readyState)&&(r.r=o=1,!q&&j(),l.onload=l.onreadystatechange=null,m(function(){u.removeChild(l)},0))}var l=b.createElement(a),o=0,r={t:d,s:c,e:h};l.src=l.data=c,!s&&(l.style.display="none"),l.width=l.height="0",a!="object"&&(l.type=d),l.onload=l.onreadystatechange=i,a=="img"?l.onerror=i:a=="script"&&(l.onerror=function(){r.e=r.r=1,g()}),p.splice(e,0,r),u.insertBefore(l,s?null:n),m(function(){o||(u.removeChild(l),r.r=r.e=o=1,j())},H.errorTimeout)}function e(a,b,c){var d=b=="c"?z:y;q=0,b=b||"j",C(a)?f(d,a,b,this.i++,l,c):(p.splice(this.i++,0,a),p.length==1&&g());return this}function d(){var a=H;a.loader={load:e,i:0};return a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=r&&!s,u=s?l:n.parentNode,v=a.opera&&o.call(a.opera)=="[object Opera]",w="webkitAppearance"in l.style,x=w&&"async"in b.createElement("script"),y=r?"object":v||x?"img":"script",z=w?"img":y,A=Array.isArray||function(a){return o.call(a)=="[object Array]"},B=function(a){return Object(a)===a},C=function(a){return typeof a=="string"},D=function(a){return o.call(a)=="[object Function]"},E=[],F={},G,H;H=function(a){function f(a){var b=a.split("!"),c=E.length,d=b.pop(),e=b.length,f={url:d,origUrl:d,prefixes:b},g,h;for(h=0;h<e;h++)g=F[b[h]],g&&(f=g(f));for(h=0;h<c;h++)f=E[h](f);return f}function e(a,b,e,g,h){var i=f(a),j=i.autoCallback;if(!i.bypass){b&&(b=D(b)?b:b[a]||b[g]||b[a.split("/").pop().split("?")[0]]);if(i.instead)return i.instead(a,b,e,g,h);e.load(i.url,i.forceCSS||!i.forceJS&&/css$/.test(i.url)?"c":c,i.noexec),(D(b)||D(j))&&e.load(function(){d(),b&&b(i.origUrl,h,g),j&&j(i.origUrl,h,g)})}}function b(a,b){function c(a){if(C(a))e(a,h,b,0,d);else if(B(a))for(i in a)a.hasOwnProperty(i)&&e(a[i],h,b,i,d)}var d=!!a.test,f=d?a.yep:a.nope,g=a.load||a.both,h=a.callback,i;c(f),c(g),a.complete&&b.load(a.complete)}var g,h,i=this.yepnope.loader;if(C(a))e(a,0,i,0);else if(A(a))for(g=0;g<a.length;g++)h=a[g],C(h)?e(h,0,i,0):A(h)?H(h):B(h)&&b(h,i);else B(a)&&b(a,i)},H.addPrefix=function(a,b){F[a]=b},H.addFilter=function(a){E.push(a)},H.errorTimeout=1e4,b.readyState==null&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",G=function(){b.removeEventListener("DOMContentLoaded",G,0),b.readyState="complete"},0)),a.yepnope=d()}(this,this.document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))};
@@ -0,0 +1,203 @@
1
+ @import compass/css3
2
+
3
+ $coolgrey0: #f2f0ed
4
+ $coolgrey1: #e2e1dd
5
+ $coolgrey2: #d6d6d4
6
+ $coolgrey3: #cacac8
7
+ $coolgrey4: #bdbdbd
8
+ $coolgrey5: #b3b3b3
9
+ $coolgrey6: #afafaf
10
+ $coolgrey7: #9a9b9d
11
+ $coolgrey8: #8c8d8e
12
+ $coolgrey9: #747679
13
+ $coolgrey10: #616265
14
+ $coolgrey11: #4d4e53
15
+ $rubine: #d1005d
16
+
17
+ $text: #4d4e53
18
+ $link: #ED1C24
19
+ $hover: #9a9b9d
20
+ $active: #d1005d
21
+ $visited: #d3181f
22
+ $pale: #d6d6d4
23
+ $blue: #549dc4
24
+ $green: #74b87a
25
+ $input: #616265
26
+ $error: $rubine
27
+ $help: #e59811
28
+
29
+ $dark: #616265
30
+ $mid: #b3b3b3
31
+ $verypale: #ebeae8
32
+ $palest: #f5f5f5
33
+
34
+ .uploadbox
35
+ position: relative
36
+ width: 640px
37
+ height: 480px
38
+ overflow: hidden
39
+ background:
40
+ color: transparent
41
+ position: center center
42
+ repeat: no-repeat
43
+ &.hover
44
+ background-position: 0 - 150px
45
+ input
46
+ position: absolute
47
+ top: 300px
48
+ left: 0
49
+ .img
50
+ background:
51
+ color: $coolgrey0
52
+ repeat: no-repeat
53
+ position: top left
54
+ border: 1px solid $coolgrey1
55
+ a.detach
56
+ position: absolute
57
+ top: 10px
58
+ right: 10px
59
+ width: 32px
60
+ height: 32px
61
+ text-indent: 50px
62
+ overflow: hidden
63
+ background:
64
+ repeat: no-repeat
65
+ position: 0 -860px
66
+ image: image-url('cropper/symbols.png')
67
+ &:hover
68
+ background-color: $rubine
69
+ .prompt
70
+ text-align: center
71
+ padding: 55px 25px
72
+ a
73
+ color: $link
74
+ .note
75
+ color: $mid
76
+ &:hover, &.hover
77
+ .prompt
78
+ a
79
+ color: $hover
80
+ &.hover
81
+ .img
82
+ .prompt
83
+ a
84
+ color: $hover
85
+
86
+ .progress_holder
87
+ position: absolute
88
+ overflow: hidden
89
+ top: 10%
90
+ left: 10%
91
+ width: 75%
92
+ height: 32px
93
+ border: 1px solid $mid
94
+ background-color: white
95
+ z-index: 900
96
+ +border-radius
97
+ .progress
98
+ position: relative
99
+ width: 0%
100
+ height: 100%
101
+ background-color: $active
102
+ background: transparent image-url('cropper/progress_stripes.png') repeat-x top left scroll
103
+ .commentary
104
+ position: absolute
105
+ top: 0
106
+ left: 0
107
+ width: 100%
108
+ font-size: 1em
109
+ line-height: 1
110
+ padding: 1em
111
+ text-align: cetner
112
+ .waiter
113
+ position: absolute
114
+ overflow: hidden
115
+ top: 150px
116
+ left: 410px
117
+ width: 32px
118
+ height: 32px
119
+ background: transparent image-url('cropper/upload_spinner.gif') no-repeat center center scroll
120
+ display: none
121
+ .preview
122
+ position: absolute
123
+ top: 0
124
+ left: 0
125
+ cursor: move
126
+ img
127
+ width: 100%
128
+ height: 100%
129
+ .report
130
+ position: absolute
131
+ top: 20px
132
+ left: 20px
133
+ z-index: 500
134
+ .scaler
135
+ position: absolute
136
+ top: 100px
137
+ left: 55px
138
+ width: 240px
139
+ height: 40px
140
+ color: white
141
+ +text-shadow(1px, 2px, 3px, #333)
142
+ p.range
143
+ position: relative
144
+ margin: 0 auto
145
+ overflow: hidden
146
+ span.small, span.large
147
+ display: inline-block
148
+ width: 32px
149
+ height: 50px
150
+ background-repeat: no-repeat
151
+ span.small
152
+ background:
153
+ image: image-url('cropper/scale_small.png')
154
+ position: right center
155
+ span.large
156
+ background:
157
+ image: image-url('cropper/scale_large.png')
158
+ position: left center
159
+
160
+ .stop
161
+ display: block
162
+ position: relative
163
+ border: 2px solid white
164
+ +box-shadow
165
+ float: left
166
+ &.min
167
+ width: 9px
168
+ height: 12px
169
+ margin-top: 14px
170
+ margin-right: 4px
171
+ &.max
172
+ width: 18px
173
+ height: 24px
174
+ margin-top: 6px
175
+ margin-left: 5px
176
+ .slider
177
+ display: block
178
+ position: relative
179
+ display: inline-block
180
+ width: 150px
181
+ height: 40px
182
+ background: transparent image-url('cropper/scale.png') no-repeat 0 20px
183
+ .marker
184
+ display: block
185
+ position: absolute
186
+ background-color: white
187
+ +box-shadow
188
+ top: 9px
189
+ left: 50px
190
+ width: 12px
191
+ height: 24px
192
+ cursor: move
193
+ z-index: 1000
194
+
195
+ .overflow
196
+ position: absolute
197
+ overflow: hidden
198
+ top: 0
199
+ left: 0
200
+ +opacity(0.3)
201
+ img
202
+ width: 100%
203
+ height: 100%
@@ -0,0 +1,4 @@
1
+ module Cropper
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,74 @@
1
+ module Cropper
2
+ class UploadsController < ::ApplicationController
3
+ respond_to :js
4
+ before_filter :get_holder, :only => [:new, :create]
5
+ before_filter :find_upload, :only => [:show, :edit, :destroy]
6
+ before_filter :build_upload, :only => [:new, :create]
7
+
8
+ def index
9
+ respond_with(@uploads)
10
+ end
11
+
12
+ def show
13
+ respond_with(@upload)
14
+ end
15
+
16
+ def new
17
+ respond_with @upload do |format|
18
+ format.js { render :partial => 'pick' }
19
+ end
20
+ end
21
+
22
+ def create
23
+ @upload.holder ||= @holder
24
+ @upload.update_attributes(params[:upload])
25
+ respond_with(@upload) do |format|
26
+ format.js { render :partial => 'crop' }
27
+ end
28
+ end
29
+
30
+ def edit
31
+ respond_with(@upload) do |format|
32
+ format.js { render :partial => 'crop' }
33
+ end
34
+ end
35
+
36
+ def update
37
+ @upload.update_attributes(params[:upload])
38
+ redirect_to @holder
39
+ end
40
+
41
+ def destroy
42
+ @upload.destroy
43
+ head :ok
44
+ end
45
+
46
+ private
47
+
48
+ def get_holder
49
+ klass = params[:holder_type]
50
+ if id = params[:holder_id]
51
+ @holder = klass.classify.constantize.find(id)
52
+ else
53
+ # The difficult case is when an upload is created during the creation of a new holder
54
+ # this at least gives us access the necessary geometry methods.
55
+ @holder = klass.classify.constantize.new
56
+ end
57
+ end
58
+
59
+ def build_upload
60
+ @column = params[:holder_column] || :image
61
+ @upload = @holder.send(:"build_#{@column}_upload")
62
+ @holder.send(:"#{@column}_upload=", @upload)
63
+ end
64
+
65
+ def find_upload
66
+ if params[:uuid]
67
+ @upload = Cropper::Upload.find_by_uuid(params[:uuid])
68
+ else
69
+ @upload = Cropper::Upload.find(params[:id])
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,4 @@
1
+ module Cropper
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,231 @@
1
+ # This is a standard upload class that should be useable for most purposes.
2
+ # We assume that even when the final destination is an S3 bucket, the initial upload
3
+ # will be held locally.
4
+ #
5
+ require 'open-uri'
6
+
7
+ module Cropper
8
+ class Upload < ActiveRecord::Base
9
+ belongs_to :holder, :polymorphic => true
10
+ attr_accessible :file, :scale_width, :scale_height, :offset_top, :offset_left, :holder_type, :holder_id, :holder, :holder_column, :multiplier
11
+ attr_accessor :reprocessed, :multiplier
12
+ # Unlike previous versions, the main resizing and cropping step is now carried out within the upload object.
13
+ # Usually this happens in a second step: first we upload, then we update with crop parameters, but it is
14
+ # also possible to present the cropping in javascript and upload the file with all the necessary values.
15
+
16
+ has_attached_file :file,
17
+ :processors => [:thumbnail],
18
+ :styles => lambda { |attachment| attachment.instance.paperclip_styles }
19
+
20
+ validates :file, :attachment_presence => true
21
+ validates :holder_column, :presence => true
22
+
23
+ before_update :reprocess_if_crop_changed
24
+ before_save :apply_multiplier
25
+
26
+ scope :destined_for, lambda { |col|
27
+ where(:holder_column => col).order('updated_at DESC, created_at DESC')
28
+ }
29
+
30
+ def paperclip_styles
31
+ styles = {}
32
+ styles[:precrop] = {
33
+ :geometry => precrop_geometry,
34
+ :processors => [:thumbnail]
35
+ }
36
+ if croppable?
37
+ styles[:cropped] = {
38
+ :geometry => crop_geometry,
39
+ :processors => [:offset_thumbnail],
40
+ :scale => "#{scale_width}x",
41
+ :crop_and_offset => "%dx%d%+d%+d" % [crop_width, crop_height, -offset_left, -offset_top]
42
+ }
43
+ end
44
+ styles
45
+ end
46
+
47
+ def styled_file(style=:cropped)
48
+ settings = Rails.application.config.paperclip_defaults
49
+ if bucket = Fog::Storage.new(settings[:fog_credentials]).directories.get(settings[:fog_directory])
50
+ bucket.files.get(file.path(style))
51
+ end
52
+ end
53
+
54
+ # crop_changed? returns true if any property has changed that should cause a recrop. That shuold include the file attachment itself.
55
+ #
56
+ def crop_changed?
57
+ !self.reprocessed && !![:scale_width, :scale_height, :offset_top, :offset_left, :holder_type, :holder_id, :holder_column].detect { |col| self.send :"#{col}_changed?" }
58
+ end
59
+
60
+ def reprocess_if_crop_changed
61
+ self.file.assign(file) if crop_changed?
62
+ end
63
+
64
+ ## Image dimensions
65
+ #
66
+ # We need to know dimensions of the precrop image in order to set up the cropping interface,
67
+ # so we examine the uploaded file before it is flushed.
68
+ #
69
+ after_post_process :read_dimensions
70
+ after_save :update_holder
71
+
72
+ # ## Crop boundaries
73
+ #
74
+ # Precrop geometry is unpredictable and has to be calculated.
75
+ #
76
+ def precrop_geometry
77
+ @precrop_geometry ||= Cropper.precrop_geometry(holder_type, holder_column)
78
+ end
79
+
80
+ def precrop_width
81
+ @precrop_width ||= width(:precrop)
82
+ end
83
+
84
+ def precrop_height
85
+ @precrop_height ||= height(:precrop)
86
+ end
87
+
88
+ # Cropped geometry is always to a fixed size, so we can just return parts of the definition
89
+ # knowing that they match the eventual dimensions. Useful, because we need this information to
90
+ # build the cropping interface.
91
+ #
92
+ def crop_geometry
93
+ @crop_geometry ||= Cropper.crop_geometry(holder_type, holder_column)
94
+ end
95
+
96
+ def crop_width
97
+ @cropped_width ||= crop_geometry.split('x').first.to_i
98
+ end
99
+
100
+ def crop_height
101
+ @cropped_height ||= crop_geometry.split('x').last.to_i
102
+ end
103
+
104
+ # *original_geometry* returns the discovered dimensions of the uploaded file as a paperclip geometry object.
105
+ #
106
+ def original_geometry
107
+ @original_geometry ||= Paperclip::Geometry.new(original_width, original_height)
108
+ end
109
+
110
+ # *geometry*, given a style name, returns the dimensions of the file if that style were applied. For
111
+ # speed we calculate this rather than reading the file, which might be in S3 or some other distant place.
112
+ #
113
+ # The logic is in [lib/paperclip/geometry_tranformation.rb](/lib/paperclip/geometry_tranformation.html),
114
+ # which is a ruby library that mimics the action of imagemagick's convert command.
115
+ #
116
+ def geometry(style_name='original')
117
+ @geometry ||= {}
118
+ begin
119
+ @geometry[style_name] ||= if style_name.to_s == 'original'
120
+ # If no style name is given, or it is 'original', we return the original discovered dimensions.
121
+ original_geometry
122
+ else
123
+ # Otherwise, we apply a mock transformation to see what dimensions would result.
124
+ style = self.file.styles[style_name.to_sym]
125
+ original_geometry.transformed_by(style.geometry)
126
+ end
127
+ rescue Paperclip::TransformationError => e
128
+ # In case of explosion, we always return the original dimensions so that action can continue.
129
+ original_geometry
130
+ end
131
+ end
132
+
133
+ # *width* returns the width of this image in a given style.
134
+ #
135
+ def width(style_name='original')
136
+ geometry(style_name).width.to_i
137
+ end
138
+
139
+ # *height* returns the height of this image in a given style.
140
+ #
141
+ def height(style_name='original')
142
+ geometry(style_name).height.to_i
143
+ end
144
+
145
+ # *square?* returns true if width and height are the same.
146
+ #
147
+ def square?(style_name='original')
148
+ geometry(style_name).square?
149
+ end
150
+
151
+ # *vertical?* returns true if the image, in the given style, is taller than it is wide.
152
+ #
153
+ def vertical?(style_name='original')
154
+ geometry(style_name).vertical?
155
+ end
156
+
157
+ # *horizontal?* returns true if the image, in the given style, is wider than it is tall.
158
+ #
159
+ def horizontal?(style_name='original')
160
+ geometry(style_name).horizontal?
161
+ end
162
+
163
+ # *dimensions_known?* returns true we have managed to discover the dimensions of the original file.
164
+ #
165
+ def dimensions_known?
166
+ original_width? && original_height?
167
+ end
168
+
169
+ ##
170
+
171
+ def url(style=:cropped)
172
+ file.url(:style)
173
+ end
174
+
175
+ private
176
+
177
+ # sometimes the interface will work in miniature and send through a multiplier by which to scale everything up.
178
+ def apply_multiplier
179
+ if multiplier = self.multiplier.to_i
180
+ Rails.logger.warn "multiplying upload params by #{multiplier}."
181
+ if multiplier != 1
182
+ %w{offset_left offset_top scale_width scale_height}.each do |col|
183
+ if param = self.send(col.to_sym)
184
+ self.send(:"#{col}=", param * self.multiplier.to_i)
185
+ end
186
+ end
187
+ end
188
+ self.multiplier = 1
189
+ end
190
+ end
191
+
192
+ # *read_dimensions* is called after post processing to record in the database the original width, height
193
+ # and extension of the uploaded file. At this point the file queue will not have been flushed but the upload
194
+ # should be in place. We grab dimensions from the temp file and calculate thumbnail dimensions later, on demand.
195
+ #
196
+ def read_dimensions
197
+ if uploaded_file = self.file.queued_for_write[:original]
198
+ file = uploaded_file.send :destination
199
+ Rails.logger.warn "+++ getting geometry from queued file #{file.inspect}."
200
+ Rails.logger.warn "--- File exist? #{File.exist?(file).inspect}"
201
+ geometry = Paperclip::Geometry.from_file(file)
202
+ Rails.logger.warn "=== Geometry: #{geometry.inspect}"
203
+ self.original_width = geometry.width
204
+ self.original_height = geometry.height
205
+ self.original_extension = File.extname(file.path)
206
+ Rails.logger.warn "??? validity: #{self.valid?.inspect}. errors: #{self.errors.inspect}"
207
+ end
208
+ true
209
+ end
210
+
211
+ def croppable?
212
+ !!scale_width && !!offset_left && !!offset_top
213
+ end
214
+
215
+ def update_holder
216
+ if holder
217
+ holder.send :"#{holder_column}_upload=", self
218
+ if croppable? && holder.persisted?
219
+ if source = file.url(:cropped, false)
220
+ source = (Rails.root + "public/#{source}") unless source =~ /^http/
221
+ Rails.logger.warn "--- update_holder: #{source}"
222
+ Rails.logger.warn "--- File exist? #{File.exist?(source).inspect}"
223
+ holder.send :"#{holder_column}=", open(source)
224
+ end
225
+ end
226
+ holder.save
227
+ end
228
+ end
229
+
230
+ end
231
+ end