Skip to content
Browse files

To version 1

  • Loading branch information...
0 parents commit 8d489f7fccaca9e5016cc0015e3f5c300affd725 @bbenezech committed Aug 19, 2009
74 app/controllers/papermill_controller.rb
@@ -0,0 +1,74 @@
+class PapermillController < ApplicationController
+
+ skip_before_filter :verify_authenticity_token
+
+ def show
+ begin
+ asset = PapermillAsset.find(params[:id])
+ temp_thumbnail = Paperclip::Thumbnail.make(asset_file = asset.file, params[:style])
+ new_parent_folder_path = File.dirname(new_image_path = asset_file.path(params[:style]))
+ FileUtils.mkdir_p new_parent_folder_path unless File.exists? new_parent_folder_path
+ FileUtils.cp temp_thumbnail.path, new_image_path
+ redirect_to asset.url(params[:style])
+ rescue
+ render :text => t("not-processed", :scope => "papermill"), :status => "500"
+ end
+ end
+
+ def destroy
+ begin
+ @asset = PapermillAsset.find(params[:id])
+ render :update do |page|
+ if @asset.destroy
+ page << "jQuery('#papermill_asset_#{params[:id]}').remove()"
+ else
+ page << "jQuery('#papermill_asset_#{params[:id]}').show()"
+ message = t("not-deleted", :ressource => @asset.name, :scope => "papermill")
+ page << %{ notify("#{message}", error) }
+ end
+ end
+ rescue ActiveRecord::RecordNotFound
+ render :update do |page|
+ page << "jQuery('#papermill_asset_#{params[:id]}').remove()"
+ message = t("not-found", :ressource => params[:id].to_s, :scope => "papermill")
+ page << %{ notify("#{message}", "warning") }
+ end
+ end
+ end
+
+ def update
+ @asset = PapermillAsset.find params[:id]
+ @asset.update(params)
+ render :update do |page|
+ message = t("updated", :ressource => @asset.name, :scope => "papermill")
+ page << %{ notify("#{message}", "notice") }
+ end
+ end
+
+ def edit
+ @asset = PapermillAsset.find params[:id]
+ end
+
+ def create
+ params[:assetable_type] = params[:assetable_type].camelize
+ asset_class = params[:assetable_type].constantize.papermill_associations[params[:association].to_sym][:class]
+ params[:swfupload_file] = params.delete(:Filedata)
+ @old_asset = asset_class.find(:first, :conditions => {:assetable_key => params[:assetable_key].to_s, :assetable_type => params[:assetable_type], :assetable_id => params[:assetable_id]}) unless params[:gallery]
+ @asset = asset_class.new(params.reject{|key, value| !(PapermillAsset.columns.map(&:name)+["swfupload_file"]).include?(key.to_s)})
+
+ if @asset.save
+ @old_asset.destroy if @old_asset
+ render :partial => "papermill/asset", :object => @asset, :locals => {:thumbnail => params[:thumbnail], :gallery => params[:gallery], :thumbnail_style => params[:thumbnail_style]}
+ else
+ message = t("not-created", :scope => "papermill")
+ render :text => message, :status => "500"
+ end
+ end
+
+ def sort
+ params[:papermill_asset].each_with_index do |id, index|
+ PapermillAsset.find(id).update_attribute(:position, index + 1)
+ end
+ render :nothing => true
+ end
+end
11 app/views/papermill/_asset.html.erb
@@ -0,0 +1,11 @@
+<%- dom_id = dom_id(asset) -%>
+<%- delete_link = %{<a onclick="if(confirm('#{escape_javascript I18n.t("delete-confirmation", :scope => :papermill, :resource => asset.name)}')){ $.ajax({async:true, beforeSend:function(request){$('##{dom_id}').hide();}, dataType:'script', error:function(request){$('##{dom_id}').show();}, type:'delete', url:'#{papermill_url(asset)}'})}; return false;" href="#" class="delete"><img title="#{escape_javascript t("delete", :scope => "papermill", :ressource => asset.name)}" src="/images/papermill/delete.png" alt="delete"/></a>} %>
+
+<li id="<%= dom_id %>" rel="<%= edit_papermill_url(asset) %>" title="<%= t("#{thumbnail_style ? "thumbnail-" : ""}edit-title", :scope => "papermill", :ressource => asset.name) %>">
+ <%= delete_link %>
+ <%- if thumbnail_style -%>
+ <%= render :partial => "papermill/thumbnail_asset", :object => asset, :locals => {:thumbnail_style => thumbnail_style} %>
+ <%- else -%>
+ <%= render :partial => "papermill/raw_asset", :object => asset %>
+ <%- end -%>
+</li>
1 app/views/papermill/_raw_asset.html.erb
@@ -0,0 +1 @@
+<%= link_to(raw_asset.name, raw_asset.url, :class => "name") -%>
6 app/views/papermill/_thumbnail_asset.html.erb
@@ -0,0 +1,6 @@
+<% if thumbnail_asset.image? %>
+ <span class="image"><%= image_tag(thumbnail_asset.url(thumbnail_style)) %></span>
+<% else %>
+ <span class="name" title="<%= thumbnail_asset.name %>"><%= truncate(thumbnail_asset.name, :length => 15) %></span>
+ <span class="infos"><%= thumbnail_asset.content_type[1] %></span>
+<% end %>
30 config/locale/papermill.yml
@@ -0,0 +1,30 @@
+en:
+ papermill:
+ not-processed: "Error/ressource not processed"
+ updated: "{{ressource}} updated"
+ not-deleted: "{{ressource}} could not be deleted"
+ not-created: "Ressource could not be created"
+ not-found: "Asset #{{ressource}} not found"
+ edit-title: "" # You can use {{ressource}}
+ thumbnail-edit-title: ""
+ upload-button-wording: "Upload..."
+ delete: "Remove {{ressource}}"
+ delete-confirmation: "Delete '{{resource}}'?"
+ SWFUPLOAD_PENDING: "Pending..."
+ SWFUPLOAD_LOADING: "Loading..."
+ SWFUPLOAD_ERROR: "Something happened while loading" # + " <file_name> (<swfupload error message> [<error code>])"
+fr:
+ papermill:
+ not-processed: "Erreur/ressource non trouvée"
+ updated: "{{ressource}} mise à jour"
+ not-deleted: "{{ressource}} n'a pas pu être supprimée"
+ not-created: "La ressource n'a pas pu être créée"
+ not-found: "Asset #{{ressource}} non trouvé"
+ edit-title: ""
+ thumbnail-edit-title: ""
+ upload-button-wording: "Charger.."
+ delete: "Supprimer {{ressource}}"
+ delete-confirmation: "Êtes-vous sûr de vouloir supprimer '{{resource}}' ?"
+ SWFUPLOAD_PENDING: "En attente..."
+ SWFUPLOAD_LOADING: "Chargement..."
+ SWFUPLOAD_ERROR: "Une erreur est survenue pendant le chargement de"
4 config/routes.rb
@@ -0,0 +1,4 @@
+ActionController::Routing::Routes.draw do |map|
+ map.resources :papermill, :collection => { :sort => :post }
+ map.connect "#{Papermill::PAPERMILL_DEFAULTS[:papermill_prefix]}/#{Papermill::PAPERCLIP_INTERPOLATION_STRING}", :controller => "papermill", :action => "show"
+end
3 generators/papermill/USAGE
@@ -0,0 +1,3 @@
+script/generate papermill MyTableName
+
+This will create the migration table.
16 generators/papermill/papermill_generator.rb
@@ -0,0 +1,16 @@
+class PapermillGenerator < Rails::Generator::NamedBase
+ attr_accessor :class_name, :migration_name
+
+ def initialize(args, options = {})
+ super
+ @class_name = args[0]
+ end
+
+ def manifest
+ @migration_name = file_name.camelize
+ record do |m|
+ # Migration creation
+ m.migration_template "migrate/papermill_migration.rb.erb", "db/migrate", :migration_file_name => migration_name.underscore
+ end
+ end
+end
22 generators/papermill/templates/migrate/papermill_migration.rb.erb
@@ -0,0 +1,22 @@
+class <%= migration_name %> < ActiveRecord::Migration
+ def self.up
+ create_table :papermill_assets do |t|
+ t.string :file_file_name
+ t.string :file_content_type
+ t.integer :file_file_size
+ t.integer :position
+ t.text :description
+ t.string :copyright
+ t.string :title
+ t.integer :assetable_id
+ t.string :assetable_type
+ t.string :assetable_key
+ t.string :type
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :papermill_assets
+ end
+end
25 lib/core_extensions.rb
@@ -0,0 +1,25 @@
+module HashExtensions
+ def deep_merge(hash)
+ target = dup
+ hash.keys.each do |key|
+ if hash[key].is_a? Hash and self[key].is_a? Hash
+ target[key] = target[key].deep_merge(hash[key])
+ next
+ end
+ target[key] = hash[key]
+ end
+ target
+ end
+end
+module StringExtensions
+ def simple_sql_sanitizer
+ gsub(/\\/, '\&\&').gsub(/'/, "''")
+ end
+end
+
+module ObjectExtensions
+ # Nil if empty.
+ def nie
+ self.blank? ? nil : self
+ end
+end
4 lib/papermill.rb
@@ -0,0 +1,4 @@
+require 'papermill/papermill_module'
+require 'papermill/papermill_asset'
+require 'papermill/form_builder'
+require 'papermill/papermill_helper'
91 lib/papermill/form_builder.rb
@@ -0,0 +1,91 @@
+class ActionView::Helpers::FormBuilder
+
+ def assets_upload(key = nil, method = nil, options = {})
+ papermill_upload_field key, method, { :thumbnail => false }.update(options)
+ end
+ def asset_upload(key = nil, method = nil, options = {})
+ papermill_upload_field key, method, { :gallery => false, :thumbnail => false }.update(options)
+ end
+ def images_upload(key = nil, method = nil, options = {})
+ papermill_upload_field key, method, options
+ end
+ def image_upload(key = nil, method = nil, options = {})
+ papermill_upload_field key, method, { :gallery => false }.update(options)
+ end
+
+ private
+ def papermill_upload_field(key, method, options = {})
+ assetable = @template.instance_variable_get("@#{@object_name}")
+ method ||= options[:association] || :papermill_assets
+ key ||= options[:key] || nil
+ options = assetable.class.papermill_options.deep_merge(options)
+ assetable_id = assetable.id || assetable.timestamp
+ assetable_type = assetable.class.to_s.underscore
+ id = "papermill_#{assetable_type}_#{assetable_id}_#{key}_#{method}"
+ if options[:thumbnail]
+ w = options[:thumbnail][:width] || options[:thumbnail][:height] && options[:thumbnail][:aspect_ratio] && (options[:thumbnail][:height] * options[:thumbnail][:aspect_ratio]).to_i || nil
+ h = options[:thumbnail][:height] || options[:thumbnail][:width] && options[:thumbnail][:aspect_ratio] && (options[:thumbnail][:width] / options[:thumbnail][:aspect_ratio]).to_i || nil
+ options[:thumbnail][:style] ||= (w || h) && "#{w || options[:thumbnail][:max_width]}x#{h || options[:thumbnail][:max_height]}>" || "original"
+ if options[:thumbnail][:inline_css]
+ size = []
+ size << "width:#{w}px" if w
+ size << "height:#{h}px" if h
+ size = size.join("; ")
+ @template.content_for :inline_css do
+ inline_css = ["\n"]
+ if options[:gallery]
+ vp = options[:gallery][:vpadding].to_i
+ hp = options[:gallery][:hpadding].to_i
+ vm = options[:gallery][:vmargin].to_i
+ hm = options[:gallery][:hmargin].to_i
+ b = options[:gallery][:border_thickness].to_i
+ gallery_width = (options[:gallery][:width] || w) && "width:#{options[:gallery][:width] || options[:gallery][:columns]*(w+(hp+hm+b)*2)}px;" || ""
+ gallery_height = (options[:gallery][:height] || h) && "#{options[:gallery][:autogrow] ? "" : "min-"}height:#{options[:gallery][:height] || options[:gallery][:lines]*(h+(vp+vm+b)*2)}px;" || ""
+ inline_css << %{##{id} { #{gallery_width} #{gallery_height} }}
+ inline_css << %{##{id} li { margin:#{vm}px #{hm}px; border-width:#{b}px; padding:#{vp}px #{hp}px; #{size}; }}
+ else
+ inline_css << %{##{id}, ##{id} li { #{size} }}
+ end
+ inline_css << %{##{id} .name { width:#{w || "100"}px; }}
+ inline_css.join("\n")
+ end
+ end
+ end
+ create_url = @template.url_for(:controller => "papermill", :action => "create", :escape => false, :association => method.to_s, :assetable_key => key, :assetable_id => assetable_id, :assetable_type => assetable_type, :gallery => (options[:gallery] != false), :thumbnail_style => (options[:thumbnail] && options[:thumbnail][:style]))
+ html = []
+ if assetable.new_record? && !@timestamped
+ html << self.hidden_field(:timestamp, :value => assetable.timestamp)
+ @timestamped = true
+ end
+ html << %{<div style="height: #{options[:swfupload][:button_height]}px;"><span id="browse_for_#{id}" class="swf_button"></span></div>}
+ conditions = {:assetable_type => assetable_type, :assetable_id => assetable_id}
+ conditions.merge!({:assetable_key => key.to_s}) if key
+ html << @template.content_tag(:ul, :id => id, :class => "papermill #{(options[:thumbnail] ? "papermill-thumb-container" : "papermill-asset-container")} #{(options[:gallery] ? "papermill-multiple-items" : "papermill-unique-item")}") {
+ @template.render :partial => "papermill/asset", :collection => assetable.class.papermill_associations[method][:class].find(:all, :conditions => conditions, :order => "position"), :locals => { :thumbnail_style => (options[:thumbnail] && options[:thumbnail][:style]) }
+ }
+ @template.content_for :inline_js do
+ %{
+ #{%{$("##{id}").sortable({update:function(){jQuery.ajax({async:true, data:jQuery(this).sortable('serialize'), dataType:'script', type:'post', url:'#{@template.controller.send("sort_papermill_path")}'})}})} if options[:gallery]}
+ new SWFUpload({
+ upload_id: "#{id}",
+ upload_url: "#{@template.escape_javascript create_url}",
+ file_size_limit: "#{options[:file_size_limit_mb].megabytes}",
+ file_types: "#{options[:images_only] ? '*.jpg;*.jpeg;*.png;*.gif' : ''}",
+ file_types_description: "#{options[:thumbnail] ? 'Images' : 'Files'}",
+ file_queue_limit: "#{!options[:gallery] ? '1' : '0'}",
+ file_queued_handler: Upload.file_queued,
+ file_dialog_complete_handler: Upload.file_dialog_complete,
+ upload_start_handler: Upload.upload_start,
+ upload_progress_handler: Upload.upload_progress,
+ upload_error_handler: Upload.upload_error,
+ upload_success_handler: Upload.upload_success,
+ upload_complete_handler: Upload.upload_complete,
+ button_placeholder_id : "browse_for_#{id}",
+ #{options[:swfupload].map{ |key, value| ["false", "true"].include?(value.to_s) ? "#{key.to_s}: #{value.to_s}" : "#{key.to_s}: '#{value.to_s}'" }.compact.join(", ")}
+ });
+ }
+ end
+ html.reverse! if options[:button_after_container]
+ html.join("\n")
+ end
+end
73 lib/papermill/papermill_asset.rb
@@ -0,0 +1,73 @@
+class PapermillAsset < ActiveRecord::Base
+ acts_as_list :scope => 'assetable_key=\'#{assetable_key.simple_sql_sanitizer}\' AND assetable_id=#{assetable_id} AND assetable_type=\'#{assetable_type}\''
+
+ belongs_to :assetable, :polymorphic => true
+ before_destroy :destroy_files
+
+ named_scope :key, lambda { |key| { :conditions => { :assetable_key => key } } }
+
+ Paperclip::Attachment.interpolations[:assetable_type] = proc do |attachment, style|
+ attachment.instance.assetable_type.underscore.pluralize
+ end
+
+ Paperclip::Attachment.interpolations[:assetable_id] = proc do |attachment, style|
+ attachment.instance.assetable_id
+ end
+
+ Paperclip::Attachment.interpolations[:assetable_key] = proc do |attachment, style|
+ attachment.instance.assetable_key.to_url
+ end
+
+ Paperclip::Attachment.interpolations[:escaped_basename] = proc do |attachment, style|
+ Paperclip::Attachment.interpolations[:basename].call(attachment, style).to_url
+ end
+
+ has_attached_file :file,
+ :path => "#{Papermill::PAPERMILL_DEFAULTS[:public_root]}/#{Papermill::PAPERMILL_DEFAULTS[:papermill_prefix]}/#{Papermill::PAPERCLIP_INTERPOLATION_STRING}",
+ :url => "/#{Papermill::PAPERMILL_DEFAULTS[:papermill_prefix]}/#{Papermill::PAPERCLIP_INTERPOLATION_STRING}"
+ validates_attachment_presence :file
+
+ #validates_attachment_content_type :file, :content_type => ['image/jpeg', 'image/pjpeg', 'image/jpg', 'image/png', 'image/gif']
+
+ # Fix the mime types. Make sure to require the mime-types gem
+ def swfupload_file=(data)
+ data.content_type = MIME::Types.type_for(data.original_filename).to_s
+ self.file = data
+ end
+
+ def name
+ file_file_name
+ end
+
+ def size
+ file_file_size
+ end
+
+ def url(style = nil)
+ file.url(style && CGI::escape(style.to_s))
+ end
+
+ def content_type
+ file_content_type.split("/") if file_content_type
+ end
+
+ def image?
+ content_type.first == "image" && content_type[1]
+ end
+
+ def interpolated_path(with = {}, up_to = nil)
+ Papermill::papermill_interpolated_path({":id" => self.id, ":assetable_id" => self.assetable_id, ":assetable_type" => self.assetable_type.underscore.pluralize}.merge(with), up_to)
+ end
+
+ # before_filter
+ def destroy_files
+ system "rm -rf #{self.interpolated_path({}, ':id')}/" if image?
+ true
+ end
+
+ # suppression de TOUS les thumbs. Ménage de printemps?
+ def self.clean_up
+ # suppression des dossiers de thumbs
+ system "rm -rf #{Papermill::papermill_interpolated_path({":style" => "*x*", "other" => "*"}, ":style")}/"
+ end
+end
35 lib/papermill/papermill_helper.rb
@@ -0,0 +1,35 @@
+module PapermillHelper
+
+ def papermill_javascript_tag(options = {})
+ html = []
+ root_folder = options[:path] || "javascripts"
+ if options[:with_jquery] || options[:with_jqueryui]
+ html << %{<script src="http://www.google.com/jsapi"></script>}
+ html << %{<script type="text/javascript">\n//<![CDATA[}
+ html << %{ google.load("jquery", "1");} if options[:with_jquery]
+ html << %{ google.load("jqueryui", "1");} if options[:with_jquery] || options[:with_jqueryui]
+ html << %{</script>}
+ end
+ html << %{<script src="http://swfupload.googlecode.com/svn/swfupload/tags/swfupload_v2.2.0_core/swfupload.js"></script>}
+ html << %{<script type="text/javascript">\n//<![CDATA[}
+ ["SWFUPLOAD_PENDING", "SWFUPLOAD_LOADING", "SWFUPLOAD_ERROR"].each do |js_constant|
+ html << %{var #{js_constant} = "#{I18n.t(js_constant, :scope => "papermill")}";}
+ end
+ html << %{//]]>\n</script>}
+ html << javascript_include_tag("/#{root_folder}/papermill", :cache => "swfupload-papermill")
+ html << '<script type="text/javascript">jQuery(document).ready(function() {'
+ html << @content_for_inline_js
+ html << '});</script>'
+ html.join("\n")
+ end
+
+ def papermill_stylesheet_tag(options = {})
+ html = []
+ root_folder = options[:path] || "stylesheets"
+ html << stylesheet_link_tag("/#{root_folder}/papermill")
+ html << %{<style type="text/css">}
+ html << @content_for_inline_css
+ html << %{</style>}
+ html.join("\n")
+ end
+end
176 lib/papermill/papermill_module.rb
@@ -0,0 +1,176 @@
+module Papermill
+
+ # Override these defaults :
+ # - in your application (environment.rb, ..) => in your [environment, development, production].rb file with Papermill::OPTIONS = {thumbnails => {..}, :gallery => {..}, etc. }
+ # - in your class => papermill <assoc_name>, :class_name => MySTIedPapermillAssetSubClass, thumbnails => {<my_thumbnail_parameters>}, :gallery => {<my_gallery_parameters>}, etc.
+ # - in your helper call => images_upload :my_gallery, {thumbnails => {..}, :gallery => {..}, etc. }
+ # Options will cascade as you expect them to.
+
+ # sizes (widths, paddings, etc..) are CSS pixel values.
+ PAPERMILL_DEFAULTS = {
+ :thumbnail => {
+ # you clearly want to override these two values in your templates.
+ # the rest is very optionnal and will "cascade" nicely
+ :width => 100, # Recommended if :gallery[:width] is nil
+ :height => 100, # Recommended if :gallery[:height] is nil
+ # set :width OR :height to nil to use aspect_ratio value. Remember that 4/3 == 1 => Use : 4.0/3
+ :aspect_ratio => nil,
+ :max_width => 1000, # for dynamic resize. Security against
+ :max_height => 1000,
+ # You can override ImageMagick transformation strings, defaults to "#{:width}x#{:height}>"
+ :style => nil,
+ # set to false if you don't want inline CSS
+ :inline_css => true
+ },
+ # gallery
+ :gallery => {
+ # if thumbnail.inline_css is true, css will be generated automagically with these values. Great for quick scaffolding, and complete enough for real use.
+ :width => nil, # overrides calculated width. Recommended if :thumbnail[:width] is nil.
+ :height => nil, # overrides calculated height. Recommended if :thumbnail[:height] is nil.
+ :columns => 8, # number of columns. If thumbnail.width has a value, sets a width for the gallery, calculated from thumbnails width multiplied by :columns.
+ :lines => 2, # number of default lines. (height will autogrow) If thumbnail.height has a value, sets a min-height for the gallery, calculated from thumbnails height multiplied by :lines
+ :vpadding => 0, # vertical padding around thumbnails
+ :hpadding => 0, # horizontal padding around thumbnails
+ :vmargin => 1, # vertical margin around thumbnails
+ :hmargin => 1, # horizontal margin around thumbnails
+ :border_thickness => 2, # border around thumbnails
+ :autogrow => false # sets a min-height instead of height for the gallery
+ },
+ # options passed on to SWFUpload. To remove an option when overriding, set it to nil.
+ :swfupload => {
+ # !!! Will only work if the swf file comes from the server to where the files are sent. (Flash same origin security policy)
+ :flash_url => '/flashs/swfupload.swf',
+ # You can use upload-blank.png with your own wording or upload.png with default "upload" wording (looks nicer)
+ :button_image_url => '/images/papermill/upload-blank.png',
+ :button_width => 61,
+ :button_height => 22,
+ # Wording and CSS processed through an Adobe Flash styler. Result is terrible. Feel free to put a CSS button overlayed directly on the SWF button. See swfupload website.
+ :button_text => %{<span class="button-text">#{I18n.t("upload-button-wording", :scope => :papermill)}</span>},
+ :button_text_style => %{.button-text { color: red; font-size: 12pt; font-weight: bold; }},
+ :button_disabled => "false",
+ :button_text_top_padding => 4,
+ :button_text_left_padding => 4,
+ :debug => "false",
+ :prevent_swf_caching => "false"
+ # See swfupload.js for details.
+ },
+ :images_only => false, # set to true to forbid upload of anything else than images
+ :file_size_limit_mb => 10, # file max size
+ :button_after_container => false, # set to true to move the upload button below the container
+ # DO NOT CHANGE THESE IN YOUR CLASSES. Only application wide (routes depend on it..)
+ # path to the root of your public directory
+ :public_root => ":rails_root/public",
+ # added to :public_root as the root folder for all papermill items
+ :papermill_prefix => "papermill"
+ }.deep_merge( Papermill.const_defined?("OPTIONS") ? Papermill::OPTIONS : {} )
+
+ # do not try to override this, unless you know exactly what you are doing
+ PAPERCLIP_INTERPOLATION_STRING = ":assetable_type/:assetable_id/:assetable_key/:id/:style/:escaped_basename.:extension"
+
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ def self.papermill_interpolated_path(replacements_pairs, up_to)
+ replacements_pairs = {"other" => "*", ":rails_root" => RAILS_ROOT}.merge(replacements_pairs)
+ a = "#{PAPERMILL_DEFAULTS[:public_root]}/#{PAPERMILL_DEFAULTS[:papermill_prefix]}/#{PAPERCLIP_INTERPOLATION_STRING}".split("/")
+ "#{a[0..(up_to && a.index(up_to) || -1)].map{ |s| s.starts_with?(':') ? (replacements_pairs[s] || replacements_pairs['other'] || s) : s }.join('/')}"
+ end
+
+ module ClassMethods
+ attr_reader :papermill_options
+ attr_reader :papermill_associations
+
+ # Dealing with STIed Asset table in papermill declaration is dead easy, since papermill follows ActiveRecord :has_many global syntax and the Convention over configuration concept :
+ # papermill <association_name>, :class_name => MySTIedPapermillAssetClassName
+ # class MyAsset < PapermillAsset
+ # ...
+ # end
+ # class MyAssetableClass < ActiveRecord::Base
+ # 1 -> papermill :my_association, :class_name => MyAsset
+ # 2 -> papermill :class_name => MyAsset
+ # 3 -> papermill :my_assets
+ # 4 -> papermill :my_other_assets
+ # 5 -> papermill
+ # end
+ # assetable = MyAssetableClass.new
+ # 1 -> assetable.my_association attached MyAsset objects (no magic here, association and class_name both specified)
+ # 2 -> assetable.my_assets attached MyAsset objects (association infered from :class_name as expected)
+ # 3 -> assetable.my_assets attached MyAsset objects (class_name guessed from association name)
+ # 4 -> assetable.my_other_assets attached PapermillAssets objects (couldn't find MyOtherAsset class or MyOtherAsset is not a PapermillAsset subclass, use default PapermillAsset superclass)
+ # 5 -> assetable.papermill_assets attached PapermillAssets objects (defaults)
+
+ def papermill(assoc = nil, options = {})
+ @papermill_associations ||= {}
+ asset_class = ((klass = options.delete(:class_name)) && (klass = (klass.to_s.singularize.camelize.constantize rescue nil)) && klass.superclass == PapermillAsset && klass || assoc && (klass = (assoc.to_s.singularize.camelize.constantize rescue nil)) && klass.superclass == PapermillAsset && klass || PapermillAsset)
+ assoc ||= asset_class.to_s.pluralize.underscore.to_sym
+
+ @papermill_associations.merge!({assoc => {:class => asset_class}})
+ @papermill_options = Papermill::PAPERMILL_DEFAULTS.deep_merge(options)
+ association_finder = assoc.to_s + "_finder"
+ # using finder_sql because ActiveRecord chokes with STI on polymorphic tables. (Stupidely uses class.to_s instead of class.sti_name in association, god knows when it will get fixed, but tickets seems on the way for rails 3.0)
+ has_many association_finder, :finder_sql => 'SELECT * FROM papermill_assets WHERE papermill_assets.assetable_id = #{id} AND papermill_assets.assetable_type = "#{self.class.sti_name}" AND papermill_assets.type ' + (asset_class == PapermillAsset ? "IS NULL" : %{= "#{asset_class.to_s}"}) + ' ORDER BY papermill_assets.position'
+ after_create :rebase_assets
+ # reinventing the wheel because ActiveRecord chokes on :finder_sql with associations
+ # TODO Clean the mess
+ define_method assoc do |*options|
+ klass = self.class.papermill_associations[assoc.to_sym][:class]
+ options = options.first || {}
+ conditions = {
+ :assetable_type => self.class.sti_name,
+ :assetable_id => self.id,
+ }.merge(options.delete(:conditions) || {})
+ order = (options.delete(:order) || "position ASC")
+ conditions.merge!({:type => klass.to_s}) unless klass == PapermillAsset
+ conditions.merge!({:assetable_key => options[:key].to_s}) if options[:key]
+ conditions.merge!({:type => options[:class_name]}) if options[:class_name]
+ asset_class.find(:all, :conditions => conditions, :order => order)
+ end
+
+ after_destroy :remove_papermill_folder
+ class_eval <<-EOV
+ include Papermill::InstanceMethods
+ EOV
+ end
+
+ def inherited(subclass)
+ subclass.instance_variable_set("@papermill_options", @papermill_options)
+ subclass.instance_variable_set("@papermill_associations", @papermill_associations)
+ super
+ end
+ end
+
+ module InstanceMethods
+ attr_writer :timestamp
+ def timestamp
+ @timestamp ||= "-#{(Time.now.to_f * 1000).to_i.to_s[4..-1]}"
+ end
+
+ def interpolated_path(with = {}, up_to = nil)
+ Papermill::papermill_interpolated_path({
+ ":assetable_type" => self.class.sti_name.underscore.pluralize,
+ ":assetable_id" => self.id
+ }.merge(with), up_to)
+ end
+
+ private
+
+ def rebase_assets
+ return true unless timestamp
+ PapermillAsset.find(:all, :conditions => {:assetable_id => self.timestamp}).each do |asset|
+ if asset.created_at < 2.hours.ago
+ asset.destroy
+ else
+ asset.update_attribute(:assetable_id, self.id)
+ end
+ end
+ system "mv #{interpolated_path({':assetable_id' => timestamp}, ':assetable_id')}/ #{interpolated_path({}, ':assetable_id')}/"
+ true
+ end
+
+ def remove_papermill_folder
+ system "rm -rf #{interpolated_path({}, ':assetable_id')}/"
+ true
+ end
+ end
+end
BIN public/.DS_Store
Binary file not shown.
BIN public/flashs/swfupload.swf
Binary file not shown.
BIN public/images/.DS_Store
Binary file not shown.
BIN public/images/papermill/.DS_Store
Binary file not shown.
BIN public/images/papermill/background.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/papermill/container-background.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/papermill/delete.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/papermill/upload-blank.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN public/images/papermill/upload.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 public/javascripts/papermill.js
@@ -0,0 +1,89 @@
+/*
+ Papermill SWFUpload wrapper
+ You'll need jQuery or a **very** little bit of rewriting for native/prototype
+*/
+
+notify = function(message, type) {
+ // wrap with your own javascript alert system (here is the thing for jGrowl)
+ //if(type == "notice") { jQuery.noticeAdd({ text: message, stayTime: 4000, stay: false, type: type }) }
+ //if(type == "warning") { jQuery.noticeAdd({ text: message, stayTime: 9000, stay: false, type: type }) }
+ //if(type == "error") { jQuery.noticeAdd({ text: message, stayTime: 20000, stay: false, type: type }) }
+
+ // or simply
+ alert(type + ": " + message)
+}
+
+
+var Upload = {
+ // The total number of files queued with SWFUpload
+ files_queued: 0,
+
+ set_recipient_id: function(dom_id) {
+ this.recipient_id = dom_id
+ },
+ file_dialog_complete: function(num_selected, num_queued)
+ {
+ // SwfUpload doesn't order uploads by name out of the box...
+ this.sorted_queue = [];
+ this.index = 0;
+ if (num_queued > 0) {
+ file_queue = [];
+ global_index = 0;
+ index = 0;
+ do {
+ file = this.callFlash("GetFileByIndex", [global_index]);
+ if(file != null && file.filestatus == -1) {
+ file_queue[index] = file;
+ index++;
+ }
+ global_index++;
+ } while (file != null);
+ this.sorted_queue = file_queue.sort(function(a,b){
+ if(b.name < a.name){return (1)}
+ })
+ self = this;
+ jQuery(this.sorted_queue).each( function(index, file) {
+ li = jQuery('<li></li>').attr({ 'id': file.id, 'class': 'swfupload' });
+ li.append(jQuery('<span></span>').attr('class', 'name').html(file.name.substring(0, 10) + '...'));
+ li.append(jQuery('<span></span>').attr('class', 'status').html(SWFUPLOAD_PENDING));
+ li.append(jQuery('<span></span>').attr('class', 'progress').append('<span></span>'));
+
+ if(self.settings.file_queue_limit == 1) {
+ jQuery("#" + self.settings.upload_id).html(li);
+ } else {
+ jQuery("#" + self.settings.upload_id).append(li);
+ }
+ })
+ this.startUpload(this.sorted_queue[this.index++].id);
+ }
+ },
+
+ upload_start: function(file)
+ {
+ jQuery('#' + file.id + ' .status').html(SWFUPLOAD_LOADING);
+ },
+ upload_progress: function(file, bytes, total)
+ {
+ percent = Math.ceil((bytes / total) * 100);
+ jQuery('#' + file.id + ' .progress span').width(percent + '%');
+ },
+ upload_error: function(file, code, message)
+ {
+ notify(SWFUPLOAD_ERROR + " " + file.name + " (" + message + " [" + code + "])", "error");
+ jQuery('#' + file.id).remove();
+ },
+ upload_success: function(file, data)
+ {
+ jQuery('#' + file.id).replaceWith(jQuery(data));
+ },
+ upload_complete: function(file)
+ {
+ Upload.files_queued -= 1;
+ if(this.sorted_queue[this.index]) {
+ this.startUpload(this.sorted_queue[this.index++].id)
+ }
+ },
+ file_queue_error: function(file, error_code, message) {
+ upload_error(file, error_code, message)
+ }
+}
32 public/stylesheets/papermill.css
@@ -0,0 +1,32 @@
+.papermill a:hover { background:none; color:inherit; }
+.papermill a img { border:0px; }
+.papermill li:hover { border-color: blue; }
+.papermill li a { display:block; }
+.papermill .progress { display:block; border:1px solid #C2E3EF; text-align:left; height:6px; }
+.papermill .progress span { background: #7BB963; height:6px; width:0; display:block; }
+
+.papermill-thumb-container li { border: 0px solid transparent; min-height:25px; min-width:25px; }
+.papermill-thumb-container { position:relative; border:5px solid #EEE; padding:4px; overflow-x:hidden; }
+.papermill-thumb-container li { display:block; float:left; position:relative; }
+.papermill-thumb-container .delete { position:absolute; bottom:5px; right:5px; }
+.papermill-thumb-container span { display:block; }
+.papermill-thumb-container .name { font-size: 10px; overflow: hidden; font-weight: bold; }
+.papermill-thumb-container .infos { font-size: 8px; overflow: hidden; }
+.papermill-thumb-container .status { margin-bottom: 10px; }
+
+.papermill-asset-container { border:1px solid #EEE; padding:3px; }
+.papermill-asset-container li { display:block; height: 20px; }
+.papermill-asset-container .swfupload .name {margin-left:21px;}
+.papermill-asset-container .name {float:left;}
+.papermill-asset-container .status { margin-left:5px; float:left;}
+.papermill-asset-container .progress { float:left; margin-top:6px; margin-left: 5px; width:100px; }
+.papermill-asset-container .delete { float:left; margin-top:2px; margin-right:5px; }
+
+.papermill-asset-container.papermill-multiple-items { padding-left:10px; border-left:5px solid #EEE; min-height:40px; }
+.papermill-asset-container.papermill-multiple-items li { cursor:row-resize; }
+.papermill-thumb-container.papermill-multiple-items li { cursor:move; }
+.papermill-asset-container.papermill-unique-item { padding-left:5px; min-height:20px; }
+
+/* Need some backgrounds? */
+/* .papermill li { background: transparent url(/images/papermill/background.png) repeat top left; } */
+/* .papermill-thumb-container { background: transparent url(/images/papermill/container-background.png) repeat top left; } */
1 tasks/papermill_tasks.rake
@@ -0,0 +1 @@
+
8 test/papermill_test.rb
@@ -0,0 +1,8 @@
+require 'test_helper'
+
+class PapermillTest < ActiveSupport::TestCase
+ # Replace this with your real tests.
+ test "the truth" do
+ assert true
+ end
+end
3 test/test_helper.rb
@@ -0,0 +1,3 @@
+require 'rubygems'
+require 'active_support'
+require 'active_support/test_case'

0 comments on commit 8d489f7

Please sign in to comment.
Something went wrong with that request. Please try again.