├── .gitignore ├── README.textile ├── application.rb ├── config.ru ├── environment.rb ├── public ├── images │ ├── alert-overlay.png │ ├── background-id.png │ ├── giffer.gif │ ├── icon-pause.png │ ├── icon-play.png │ ├── icon-zoom-minus.png │ └── icon-zoom-plus.png ├── js │ ├── giffer.js │ ├── jquery-1.4.1.min.js │ └── jquery-ui.js ├── robots.txt └── style.css └── views ├── index.haml └── style.sass /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. Kathy Lee Giffer 2 | 3 | HTML5 drag and drop animated GIF creation! Uses Gifsicle and Ruby to create the GIFs on the server 4 | 5 | !http://i.imgur.com/o6KbD.png! 6 | 7 | h2. Requirements 8 | * rubygems: sinatra, pow, haml and their dependencies 9 | * @convert@ (installed with the ImageMagick package) 10 | * @gifsicle@ ("http://www.lcdf.org/gifsicle/":http://www.lcdf.org/gifsicle/) 11 | * modern web browser with the FileReader API (tested against recent firefox, chrome, and safari builds) 12 | 13 | h2. Installation (brew for OS X, for Ubuntu replace brew with apt-get) 14 | 15 |
16 |   
17 |     brew install imagemagick
18 |     brew install gifsicle
19 |     gem install sinatra pow haml unicorn
20 |     mkdir tmp
21 |     mkdir log
22 |     mkdir public/images/gifs
23 |     unicorn config.ru
24 |   
25 | 
26 | 27 | h1. Contributors 28 | 29 | codez by Max Ogden 30 | perdy styles by Effalo Corp 31 | 32 | h1. License 33 | 34 | (The MIT License) 35 | 36 | Copyright (c) 2010 Max Ogden 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining 39 | a copy of this software and associated documentation files (the 40 | 'Software'), to deal in the Software without restriction, including 41 | without limitation the rights to use, copy, modify, merge, publish, 42 | distribute, sublicense, and/or sell copies of the Software, and to 43 | permit persons to whom the Software is furnished to do so, subject to 44 | the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be 47 | included in all copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 50 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 51 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 52 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 53 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 54 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 55 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 56 | -------------------------------------------------------------------------------- /application.rb: -------------------------------------------------------------------------------- 1 | get '/' do 2 | haml :index 3 | end 4 | 5 | post '/gifit' do 6 | pwd = File.join(Dir.pwd, 'tmp') 7 | params[:upload].each_with_index do |upload, i| 8 | filetype = upload[1]['content_type'].split('/')[1].downcase 9 | File.open(File.join(pwd, "#{i}.#{filetype}"), 'w') {|f| f.write(Base64.decode64(upload[1]['data'])) } 10 | end 11 | images = Dir.glob(File.join(pwd, "*.{jpg,jpeg,png}")) 12 | images.each do |image| 13 | filename = File.basename(image).split('.')[0] 14 | output = "#{filename}.gif" 15 | `convert #{File.expand_path(image)} #{File.join(pwd, output)}` 16 | `rm #{File.expand_path(image)}` 17 | end 18 | gifname = "#{Time.now.to_i}.gif" 19 | `gifsicle --delay=10 --loop #{pwd}/*.gif > #{File.join(Dir.pwd, "public", "images", "gifs", gifname)}` 20 | `rm #{pwd}/*` 21 | "/images/gifs/#{gifname}" 22 | end -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'logger' 3 | require 'rack' 4 | require 'sinatra' 5 | require 'pow' 6 | require 'haml' 7 | require 'base64' 8 | require 'sass/plugin/rack' 9 | require 'environment' 10 | require 'application' 11 | 12 | use Sass::Plugin::Rack 13 | Sass::Plugin.options[:css_location] = "./public" 14 | Sass::Plugin.options[:template_location] = "./views" 15 | 16 | log = File.new("log/sinatra.log", "a+") 17 | STDOUT.reopen(log) 18 | STDERR.reopen(log) 19 | 20 | run Sinatra::Application -------------------------------------------------------------------------------- /environment.rb: -------------------------------------------------------------------------------- 1 | configure do 2 | set :views, "#{File.dirname(__FILE__)}/views" 3 | LOGGER = Logger.new("log/sinatra.log") 4 | end 5 | 6 | helpers do 7 | def logger 8 | LOGGER 9 | end 10 | 11 | def base_dir 12 | "public/media" 13 | end 14 | 15 | def partial(page, options={}) 16 | haml page, options.merge!(:layout => false) 17 | end 18 | end -------------------------------------------------------------------------------- /public/images/alert-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/10f15adba9c82b009c6b50a088076287062ea1e5/public/images/alert-overlay.png -------------------------------------------------------------------------------- /public/images/background-id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/10f15adba9c82b009c6b50a088076287062ea1e5/public/images/background-id.png -------------------------------------------------------------------------------- /public/images/giffer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/10f15adba9c82b009c6b50a088076287062ea1e5/public/images/giffer.gif -------------------------------------------------------------------------------- /public/images/icon-pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/10f15adba9c82b009c6b50a088076287062ea1e5/public/images/icon-pause.png -------------------------------------------------------------------------------- /public/images/icon-play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/10f15adba9c82b009c6b50a088076287062ea1e5/public/images/icon-play.png -------------------------------------------------------------------------------- /public/images/icon-zoom-minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/10f15adba9c82b009c6b50a088076287062ea1e5/public/images/icon-zoom-minus.png -------------------------------------------------------------------------------- /public/images/icon-zoom-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/max-mapper/kathyleegiffer/10f15adba9c82b009c6b50a088076287062ea1e5/public/images/icon-zoom-plus.png -------------------------------------------------------------------------------- /public/js/giffer.js: -------------------------------------------------------------------------------- 1 | var attachments = []; 2 | 3 | $(document).ready(function(){ 4 | 5 | if (!FileReader) { 6 | $('#drop').html('

You need to upgrade to a browser that implements the HTML5 FileReader API in order to use this

'); 7 | } 8 | 9 | function updateListCounts() { 10 | $("#grid li").each(function() { 11 | var li = this; 12 | var count = $('li').index($(li)) + 1; 13 | $(li).find('.digit').text(count); 14 | }); 15 | }; 16 | 17 | function imagesForUpload() { 18 | var images = $("#grid ul").sortable('toArray'); 19 | return $.map(images, function(image){ 20 | var item = $("#" + image); 21 | return { 22 | "content_type" : item.data('content_type'), 23 | "data" : item.data('data') 24 | }; 25 | }); 26 | } 27 | 28 | $('.gifit').live('click', function(){ 29 | $('.sweetgif').html(''); 30 | $(this).hide(); 31 | $('.loader').show(); 32 | $.ajax({ 33 | type: 'POST', 34 | url: '/gifit', 35 | data: {upload: imagesForUpload()}, 36 | success: function(data) { 37 | $('.loader').hide(); 38 | $('.sweetgif').html(''); 39 | $('.gifit').show(); 40 | getDraggy(); 41 | } 42 | }); 43 | }) 44 | 45 | $('.zoom-minus').click(function(){ 46 | $('#grid .cell') 47 | .css('width', $('#grid .cell').width() - 50 + 'px') 48 | .css('height', $('#grid .cell').height() - 50 + 'px'); 49 | }); 50 | 51 | $('.zoom-plus').click(function(){ 52 | $('#grid .cell') 53 | .css('width', $('#grid .cell').width() + 50 + 'px') 54 | .css('height', $('#grid .cell').height() + 50 + 'px'); 55 | }); 56 | 57 | function addPhoto(file) { 58 | var image = file.result; 59 | var newImg = $('.template:first').clone(); 60 | newImg.find('.image').attr('src', image).removeClass('template'); 61 | newImg.data('data', file.result.split(",")[1]); 62 | newImg.data('content_type', file.file.type); 63 | newImg.attr('id', (((1+Math.random())*0x10000)|0).toString(16).substring(1)); 64 | $("#grid ul").append(newImg); 65 | newImg.show(); 66 | } 67 | 68 | var getBinaryDataReader, file; 69 | var SPROINGG = (function() { 70 | 71 | function hasStupidChromeBug() { 72 | return typeof(FileReader.prototype.addEventListener) !== "function"; 73 | }; 74 | 75 | function isImage(type) { 76 | return type === "image/png" || type === "image/jpeg"; 77 | }; 78 | 79 | function renderAttachments() { 80 | $("#grid ul").html(''); 81 | 82 | var i, tmp, html = "