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,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