From 044824947b2a17f32c435f874afb476b0b87a7b4 Mon Sep 17 00:00:00 2001 From: andrew Date: Fri, 1 Feb 2008 04:46:06 +0000 Subject: [PATCH] adding taggable images git-svn-id: http://svn.barleysodas.com/barleysodas/trunk@103 0f7b21a7-9e3a-4941-bbeb-ce5c7c368fa7 --- app/controllers/galleries_controller.rb | 78 ++++++++++ app/controllers/tag_images_controller.rb | 88 +++++++++++ app/helpers/application_helper.rb | 44 ++++++ app/helpers/galleries_helper.rb | 5 + app/helpers/tag_images_helper.rb | 2 + app/models/beer.rb | 1 + app/models/image.rb | 96 ++++++++++++ app/models/people.rb | 2 + app/models/tag_image.rb | 10 ++ app/views/beers/show.rhtml | 3 + app/views/breweries/show.rhtml | 1 + app/views/galleries/_image.rhtml | 6 + app/views/galleries/_image_form.rhtml | 9 ++ app/views/galleries/edit.rhtml | 12 ++ app/views/galleries/index.rhtml | 7 + app/views/galleries/new.rhtml | 20 +++ app/views/galleries/show.rhtml | 9 ++ app/views/layouts/application.rhtml | 2 + app/views/shared/_lightbox.rhtml | 30 ++++ app/views/shared/_tagged_image_browser.rhtml | 46 ++++++ app/views/tag_images/_tag_image_errors.rhtml | 1 + app/views/tag_images/_tag_images.rhtml | 8 + app/views/tag_images/_taggable_results.rhtml | 10 ++ app/views/tag_images/_tagged_images.rhtml | 11 ++ app/views/tag_images/show.rhtml | 110 ++++++++++++++ config/environment.rb | 1 + config/routes.rb | 4 +- db/migrate/011_create_images.rb | 26 ++++ db/migrate/012_create_tag_images.rb | 19 +++ generate_permissions | 7 +- lib/has_many_tagged_images.rb | 25 ++++ public/images/edit-clear.png | Bin 0 -> 773 bytes public/images/go-first.png | Bin 0 -> 666 bytes public/images/go-last.png | Bin 0 -> 685 bytes public/images/go-next.png | Bin 0 -> 676 bytes public/images/go-previous.png | Bin 0 -> 655 bytes public/javascripts/application.js | 12 ++ public/stylesheets/application.css | 9 +- public/stylesheets/content.css | 141 ++++++++---------- public/stylesheets/layout.css | 2 +- public/stylesheets/lightboxes.css | 69 +++++++++ test/fixtures/images.yml | 5 + test/fixtures/tag_images.yml | 5 + test/functional/galleries_controller_test.rb | 57 +++++++ test/functional/tag_images_controller_test.rb | 57 +++++++ test/unit/image_test.rb | 10 ++ test/unit/tag_image_test.rb | 10 ++ 47 files changed, 986 insertions(+), 84 deletions(-) create mode 100644 app/controllers/galleries_controller.rb create mode 100644 app/controllers/tag_images_controller.rb create mode 100644 app/helpers/galleries_helper.rb create mode 100644 app/helpers/tag_images_helper.rb create mode 100644 app/models/image.rb create mode 100644 app/models/tag_image.rb create mode 100644 app/views/galleries/_image.rhtml create mode 100644 app/views/galleries/_image_form.rhtml create mode 100644 app/views/galleries/edit.rhtml create mode 100644 app/views/galleries/index.rhtml create mode 100644 app/views/galleries/new.rhtml create mode 100644 app/views/galleries/show.rhtml create mode 100644 app/views/shared/_lightbox.rhtml create mode 100644 app/views/shared/_tagged_image_browser.rhtml create mode 100644 app/views/tag_images/_tag_image_errors.rhtml create mode 100644 app/views/tag_images/_tag_images.rhtml create mode 100644 app/views/tag_images/_taggable_results.rhtml create mode 100644 app/views/tag_images/_tagged_images.rhtml create mode 100644 app/views/tag_images/show.rhtml create mode 100644 db/migrate/011_create_images.rb create mode 100644 db/migrate/012_create_tag_images.rb create mode 100644 lib/has_many_tagged_images.rb create mode 100644 public/images/edit-clear.png create mode 100644 public/images/go-first.png create mode 100644 public/images/go-last.png create mode 100644 public/images/go-next.png create mode 100644 public/images/go-previous.png create mode 100644 public/stylesheets/lightboxes.css create mode 100644 test/fixtures/images.yml create mode 100644 test/fixtures/tag_images.yml create mode 100644 test/functional/galleries_controller_test.rb create mode 100644 test/functional/tag_images_controller_test.rb create mode 100644 test/unit/image_test.rb create mode 100644 test/unit/tag_image_test.rb diff --git a/app/controllers/galleries_controller.rb b/app/controllers/galleries_controller.rb new file mode 100644 index 0000000..9d7a574 --- /dev/null +++ b/app/controllers/galleries_controller.rb @@ -0,0 +1,78 @@ +class GalleriesController < ApplicationController + append_before_filter :fetch_image, :only => [ :show, :destroy, + :download_original ] + + # GET /images + # GET /images.xml + def index + @content_title = 'Image Gallery' + cond_ary = [] + cond_var = { + :people_id => params[:id] + } + @secondary_title = "Everybody's Images" + if params[:id] + cond_ary << 'images.people_id = :people_id' + @people = People.find(params[:id]) + @secondary_title = "Images from #{@people.title}" + end + cond_ary << '1 = 1' if cond_ary.empty? + @pages, @images = paginate :images, :per_page => per_page, + :order => 'images.created_at DESC', :include => [ 'people' ], + :conditions => [ cond_ary.join(' AND '), cond_var ] + flash.now[:notice] = 'There are no images yet.' if @images.empty? + respond_to do |format| + format.html # index.rhtml + format.xml { render :xml => @images.to_xml } + end + end + + # GET /galleries/1 + # GET /galleries/1.xml + def show + respond_to do |format| + format.html # show.rhtml + format.xml { render :xml => @image.to_xml } + end + end + + # GET /galleries/new + def new + @image = Image.new + end + + # POST /images + # POST /images.xml + def create + @image = Image.new(params[:image]) + if @image.save + flash[:notice] = 'Great success!' + redirect_to gallery_url(@image) + else + render :action => :new + end + end + + # DELETE /galleries/1 + # DELETE /galleries/1.xml + def destroy + @image.destroy + flash[:notice] = 'Destroyed the image.' + redirect_to galleries_url(:id => @image.people_id) + end + + ## + # Sends a copy of the original Image to the People. + # + def download_original + send_file("#{RAILS_ROOT}/public/images/" + + @image.filename_for_version(:original), + :disposition => 'inline', :type => @image.content_type) + end + + protected + + def fetch_image + @image = Image.find(params[:id]) + end +end diff --git a/app/controllers/tag_images_controller.rb b/app/controllers/tag_images_controller.rb new file mode 100644 index 0000000..3e145fc --- /dev/null +++ b/app/controllers/tag_images_controller.rb @@ -0,0 +1,88 @@ +class TagImagesController < ApplicationController + # GET /tag_images + # GET /tag_images.xml + def index + redirect_to images_url + end + + # GET /tag_images/1 + # GET /tag_images/1.xml + def show + @content_title = 'Tag your friends and beers!' + @image = Image.find(params[:id], :include => [ :tag_images ]) + @tag_images = @image.tag_images + respond_to do |format| + format.html # show.rhtml + format.xml { render :xml => @tag_images.to_xml } + end + end + + # POST /tag_images + # POST /tag_images.xml + def create + @tag_image = TagImage.new(params[:tag_image]) + @image = @tag_image.image + if @tag_image.save + @tag_images = @image.tag_images + render :partial => 'tag_images' + else + render :partial => 'tag_image_errors', :status => 500 + end + end + + # DELETE /tag_images/1 + # DELETE /tag_images/1.xml + def destroy + @tag_image = TagImage.find params[:id], :include => [ :image ] + @image = @tag_image.image + @tag_image.destroy + @image.tag_images.reload + @tag_images = @image.tag_images + render :partial => 'tag_images' + end + + ## + # Searches for all known models that support image tagging. Sticks all of + # the matching results into a hash that is indexed by the type. + # + def taggable_search + @results = {} + cond_ary = [ 'title ILIKE :title' ] + cond_var = { :title => "%#{params[:name]}%" } + TagImage.types_for_select.flatten.each do |ctype| + klass = Class.class_eval(ctype) + @results[ctype] = klass.find :all, :order => 'title ASC', + :conditions => [ cond_ary.join(' AND '), cond_var ] + end + render :partial => 'taggable_results' + end + + ## + # Renders an Ajax browser of all tagged Image models for any +:taggable_type+ + # + def tagged_images + images_per_page = 4 + @page_count, @current_page, @tagged_type, @tagged_images = nil, nil, nil, nil + @tagged_type = params[:tagged_type] + if TagImage.types_for_select.flatten.include?(@tagged_type) + cond_ary = [ + 'tagged_type = :tt', + 'tagged_id = :tid' + ] + cond_var = { :tt => @tagged_type, :tid => params[:id] } + conditions = [ cond_ary.join(' AND '), cond_var ] + @current_page = params[:page].to_i + @current_page = 1 if @current_page == 0 + image_count = TagImage.count(conditions) + @page_count = (image_count.to_f / per_page.to_f + 0.5).to_i + @page_count = 1 if @page_count == 0 and image_count >= 0 + @tagged_images = TagImage.find :all, :limit => images_per_page, + :conditions => conditions, :order => 'created_at ASC', + :offset => ((@current_page - 1) * images_per_page), + :include => [ 'image' ] + render :partial => 'tag_images/tagged_images' + else + render :nothing => true, :status => 500 + end + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5482a33..4a2a213 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -91,4 +91,48 @@ module ApplicationHelper res end end + + ## + # Captures a block output and renders it in a partial as body + # + def block_to_partial(partial_name, options = {}, &block) + options.merge!(:body => capture(&block)) + concat(render(:partial => partial_name, :locals => options), block.binding) + end + + ## + # Helper to build a prototype dialog. + # + def lightbox(options = {}, &block) + options = { + :title => 'DialogTitle', + :window_id => 'DialogId', + :modal => false + }.merge(options) + block_to_partial('shared/lightbox', options, &block) + end + + ## + # Pagination link image browser thingey for the tagged image lightbox. + # + def image_browser_navigation_link(image_name, page_number, total_pages, + tagged_class, tagged_id) + if page_number == 0 or + (page_number == 1 and total_pages == 1) or + (page_number > total_pages) + image_tag(image_name) + else + link_to_remote image_tag(image_name), :update => 'browser_box', + :url => { :controller => 'tag_images', :action => 'tagged_images', + :id => :tagged_id, :tagged_class => tagged_class } + end + end + + ## + # Link to open the dialog box for the tagged image browser. + # + def tagged_image_browser_link + link_to_function 'Tagged Images', + "lightboxes['tagged_image_browser'].open()" + end end diff --git a/app/helpers/galleries_helper.rb b/app/helpers/galleries_helper.rb new file mode 100644 index 0000000..23b68a6 --- /dev/null +++ b/app/helpers/galleries_helper.rb @@ -0,0 +1,5 @@ +module GalleriesHelper + def new_image_link + link_to 'Upload Image', new_gallery_url + end +end diff --git a/app/helpers/tag_images_helper.rb b/app/helpers/tag_images_helper.rb new file mode 100644 index 0000000..5c023de --- /dev/null +++ b/app/helpers/tag_images_helper.rb @@ -0,0 +1,2 @@ +module TagImagesHelper +end diff --git a/app/models/beer.rb b/app/models/beer.rb index 8a9dab5..8ae9f04 100644 --- a/app/models/beer.rb +++ b/app/models/beer.rb @@ -6,6 +6,7 @@ class Beer < ActiveRecord::Base has_one_tuxwiki_page :owner_class => 'Beer' belongs_to :style validates_presence_of :style_id + has_many_tagged_images ## # Returns a list of attributes for the Page partial. diff --git a/app/models/image.rb b/app/models/image.rb new file mode 100644 index 0000000..1d0fed2 --- /dev/null +++ b/app/models/image.rb @@ -0,0 +1,96 @@ +require 'mini_magick' + +class Image < ActiveRecord::Base + attr_accessor :file + belongs_to :people + validates_presence_of :people_id + before_validation_on_create :set_people_id + before_create :validate_image_sanity + after_create :setup_directories + before_destroy :destroy_directories + has_many :tag_images, :dependent => :destroy + has_many :tagged_items, :through => :tag_images + + ## + # Builds the filename for this model for a particular version of the file. + # + def filename_for_version(ver = :screen) + if respond_to?(ver) + "community/#{id}/#{self.send(ver)}" + else + "/images/image-missing.png" + end + end + + protected + + ## + # Determines the base directory for all files in this model. + # + def base_directory + "#{RAILS_ROOT}/public/images/community/#{id}" + end + + ## + # Sets the People marker for ownership on creation. + # + def set_people_id + self[:people_id] = ApplicationController.current_people_id rescue nil + self[:people_id] ||= People.penguincoder.id rescue nil + end + + ## + # Checks to make sure that the file exists and is an image. + # + def validate_image_sanity + if @file.nil? or @file.to_s.empty? + errors.add(:file, 'is not a file') + return false + end + errors.add(:file, 'is too big (3MB max)') if @file.size > 3 * 1048576 + begin + @magick_image = MiniMagick::Image.from_blob(@file.read, + File.extname(@file.original_filename)) + rescue + logger.debug("Caught an exception saving an image:") + logger.debug("* #{$!}") + errors.add(:file, 'is not an image') + end + return false if self.errors.size > 0 + self.content_type = @file.content_type.chomp + true + end + + ## + # Makes the directories and writes the different versions for the uploaded + # files if applicable. + # + def setup_directories + Dir.mkdir(base_directory) unless File.exist?(base_directory) + self.original = File.basename(@file.original_filename).gsub(/[^\w._-]/, '') + @magick_image.write("#{base_directory}/#{self.original}") + @magick_image.thumbnail("600x600>") + self.screen = "screen_#{self.original}" + @magick_image.write("#{base_directory}/#{self.screen}") + if @magick_image.output =~ / (\d+)x(\d+) / + self.screen_width = $1 + self.screen_height = $2 + end + @magick_image.thumbnail("50x50>") + self.thumbnail = "thumbnail_#{self.original}" + @magick_image.write("#{base_directory}/#{self.thumbnail}") + self.save + end + + ## + # Removes the directories and files associated with this model on destroy. + # + def destroy_directories + return unless File.exists?(base_directory) + Dir.foreach(base_directory) do |file| + next if file =~ /^\.\.?$/ + File.delete(base_directory + '/' + file) + end + Dir.delete(base_directory) + end +end diff --git a/app/models/people.rb b/app/models/people.rb index 6840ab2..4069f8f 100644 --- a/app/models/people.rb +++ b/app/models/people.rb @@ -7,6 +7,8 @@ class People < ActiveRecord::Base attr_protected :role_id has_many :created_pages, :class_name => 'Page', :foreign_key => 'created_by' has_many :updated_pages, :class_name => 'Page', :foreign_key => 'updated_by' + has_many :images, :dependent => :destroy + has_many_tagged_images validates_uniqueness_of :title make_authenticatable diff --git a/app/models/tag_image.rb b/app/models/tag_image.rb new file mode 100644 index 0000000..18a39fe --- /dev/null +++ b/app/models/tag_image.rb @@ -0,0 +1,10 @@ +class TagImage < ActiveRecord::Base + belongs_to :image + belongs_to :tagged, :polymorphic => true + validates_presence_of :image_id, :tagged_id, :tagged_type + validates_uniqueness_of :tagged_id, :scope => :tagged_type + + def self.types_for_select + [ 'Beer', 'People', 'Brewery' ].collect { |x| [x] } + end +end diff --git a/app/views/beers/show.rhtml b/app/views/beers/show.rhtml index ecd793d..669aabb 100644 --- a/app/views/beers/show.rhtml +++ b/app/views/beers/show.rhtml @@ -1,7 +1,10 @@ +<%= render :partial => 'shared/tagged_image_browser', :locals => { :obj => @beer } %> + <%= render :partial => 'pages/page' %> <% content_for :sidebar do -%> <%= new_beer_link -%>
<%= edit_beer_link(@beer) -%>
<%= link_to 'Destroy', beer_path(@beer.page.title_for_url), :confirm => 'Are you sure?', :method => :delete %>
+ <% unless @beer.tagged_images.empty? -%><%= tagged_image_browser_link -%>
<% end -%> <% end -%> diff --git a/app/views/breweries/show.rhtml b/app/views/breweries/show.rhtml index 872515a..e40d782 100644 --- a/app/views/breweries/show.rhtml +++ b/app/views/breweries/show.rhtml @@ -4,4 +4,5 @@ <%= new_brewery_link -%>
<%= edit_brewery_link(@brewery) -%>
<%= link_to 'Destroy', brewery_path(@brewery.page.title_for_url), :confirm => 'Are you sure?', :method => :delete %>
+ <%= tagged_image_browser_link -%>
<% end -%> diff --git a/app/views/galleries/_image.rhtml b/app/views/galleries/_image.rhtml new file mode 100644 index 0000000..66afb9f --- /dev/null +++ b/app/views/galleries/_image.rhtml @@ -0,0 +1,6 @@ +
+<% version ||= :screen -%> +
+ <%= link_to_unless_current(image_tag(image.filename_for_version(version), :alt => image.original), gallery_url(image)) %> +

Uploaded by <%= link_to(image.people.title, galleries_url(:id => image.people_id)) -%>

+
\ No newline at end of file diff --git a/app/views/galleries/_image_form.rhtml b/app/views/galleries/_image_form.rhtml new file mode 100644 index 0000000..f19e0cd --- /dev/null +++ b/app/views/galleries/_image_form.rhtml @@ -0,0 +1,9 @@ +
+

Upload an image

+

+ +

+
diff --git a/app/views/galleries/edit.rhtml b/app/views/galleries/edit.rhtml new file mode 100644 index 0000000..5867c8c --- /dev/null +++ b/app/views/galleries/edit.rhtml @@ -0,0 +1,12 @@ +

Editing images

+ +<%= error_messages_for :images %> + +<% form_for(:images, :url => images_path(@images), :html => { :method => :put }) do |f| %> +

+ <%= submit_tag "Update" %> +

+<% end %> + +<%= link_to 'Show', images_path(@images) %> | +<%= link_to 'Back', images_path %> \ No newline at end of file diff --git a/app/views/galleries/index.rhtml b/app/views/galleries/index.rhtml new file mode 100644 index 0000000..0117257 --- /dev/null +++ b/app/views/galleries/index.rhtml @@ -0,0 +1,7 @@ +<%= render :partial => 'image', :collection => @images, :locals => { :version => :thumbnail } %> + +<%= render :partial => 'shared/pagination_links' %> + +<% content_for :sidebar do -%> + <%= new_image_link -%>
+<% end -%> \ No newline at end of file diff --git a/app/views/galleries/new.rhtml b/app/views/galleries/new.rhtml new file mode 100644 index 0000000..cdbc490 --- /dev/null +++ b/app/views/galleries/new.rhtml @@ -0,0 +1,20 @@ +<%= error_messages_for :image %> + +<% form_for(:images, :url => galleries_path, :html => { :multipart => true, :onsubmit => "$('spinner').style.display = 'inline';" }) do |f| %> +
+

Upload an image

+

+ +

+
+

+ <%= submit_tag "Create" %> <%= image_tag '/images/spinner.gif', :id => 'spinner', :style => 'display:none' %> +

+<% end %> + +<% content_for :sidebar do -%> + <%= new_image_link -%>
+<% end -%> \ No newline at end of file diff --git a/app/views/galleries/show.rhtml b/app/views/galleries/show.rhtml new file mode 100644 index 0000000..3057b82 --- /dev/null +++ b/app/views/galleries/show.rhtml @@ -0,0 +1,9 @@ +<%= render :partial => 'image', :locals => { :image => @image } %> +
+ +<% content_for :sidebar do -%> + <%= link_to "#{@image.people.title}'s images (#{@image.people.images.size})", galleries_path(:id => @image.people_id) -%>
+ <%= link_to "Download original", :action => 'download_original', :id => @image.id -%>
+ <%= link_to 'Destroy', gallery_path(@image), :confirm => 'Are you sure?', :method => :delete %>
+ <%= link_to 'Tag Image', :controller => :tag_images, :action => :show, :id => @image.id -%>
+<% end -%> \ No newline at end of file diff --git a/app/views/layouts/application.rhtml b/app/views/layouts/application.rhtml index 1c326cf..33e5281 100644 --- a/app/views/layouts/application.rhtml +++ b/app/views/layouts/application.rhtml @@ -5,6 +5,7 @@ <%= stylesheet_link_tag 'application', :media => 'all' %> <%= javascript_include_tag :defaults %> + <%= javascript_include_tag 'control.modal.js' %> @@ -53,4 +54,5 @@ + diff --git a/app/views/shared/_lightbox.rhtml b/app/views/shared/_lightbox.rhtml new file mode 100644 index 0000000..d2dbf55 --- /dev/null +++ b/app/views/shared/_lightbox.rhtml @@ -0,0 +1,30 @@ +
+
+
<%= title -%>
+
+
<%= body -%>
+
+
+<%= link_to(title, "##{window_id}_dialog", { :class => "#{modal ? '' : 'non'}modal_controls", :onclick => "return false;", :id => "#{window_id}_id_key", :style => 'display: none;' }) %> +<% content_for :script do -%> + addLoadEvent(function(){ + if(!window.lightboxes) + lightboxes = {}; + if(!window.after_opens) + after_opens = {}; + if(!window.before_closes) + before_closes = {} + var link = $("<%= window_id -%>_id_key"); + var key = '<%= window_id -%>'; + var ao = after_opens[key]; + var bc = before_closes[key]; + if(ao == undefined) + ao = function(){}; + if(bc == undefined) + bc = function(){}; + lightboxes[key] = new Control.Modal(link, { + afterOpen: ao, beforeClose: bc, + overlayCloseOnClick: <%= modal ? 'false' : 'true' -%> + }); + }); +<% end -%> \ No newline at end of file diff --git a/app/views/shared/_tagged_image_browser.rhtml b/app/views/shared/_tagged_image_browser.rhtml new file mode 100644 index 0000000..115c397 --- /dev/null +++ b/app/views/shared/_tagged_image_browser.rhtml @@ -0,0 +1,46 @@ +<% content_for :stylesheet do -%> +#browser_box { +} + +#browser_box img { + vertical-align: middle; +} + +#browser_controls { + padding: 3px 10px 3px 10px; + text-align: center; +} +<% end -%> + +<% content_for :script do -%> +if(!window.after_opens) + after_opens = {}; +if(!window.before_closes) + before_closes = {} + +after_opens['tagged_image_browser'] = function(){ + $('browser_box').hide(); + new Ajax.Updater('browser_box', + '<%= url_for(:controller => :tag_images, :action => :tagged_images, :id => ((obj ||= nil).nil? ? nil : obj.id), :tagged_type => obj.class) -%>', + { + onFailure: function() { + lightboxes['tagged_image_browser'].close(); + }, + onSuccess: function() { + $('browser_spinner').hide(); + new Effect.Appear('browser_box', { duration: 1.5 }); + } + } + ); +} + +before_closes['tagged_image_browser'] = function(){ + $('browser_box').hide(); + $('browser_spinner').show(); +} +<% end -%> + +<% lightbox :title => 'Tagged Images', :window_id => 'tagged_image_browser' do -%> +
<%= image_tag('spinner.gif') -%>
+
+<% end -%> \ No newline at end of file diff --git a/app/views/tag_images/_tag_image_errors.rhtml b/app/views/tag_images/_tag_image_errors.rhtml new file mode 100644 index 0000000..b7a52c0 --- /dev/null +++ b/app/views/tag_images/_tag_image_errors.rhtml @@ -0,0 +1 @@ +<%= error_messages_for 'tag_image' %> diff --git a/app/views/tag_images/_tag_images.rhtml b/app/views/tag_images/_tag_images.rhtml new file mode 100644 index 0000000..fcb48ab --- /dev/null +++ b/app/views/tag_images/_tag_images.rhtml @@ -0,0 +1,8 @@ +

+ Tagged items: + <% if @tag_images.empty? -%> + None. + <% else -%> + <%= @tag_images.collect { |t| "#{t.tagged.title} (#{t.tagged_type} | #{link_to_remote('Remove', :url => tag_image_path(t), :update => 'tag_images', :method => :delete)})" }.join(', ') -%> + <% end -%> +

\ No newline at end of file diff --git a/app/views/tag_images/_taggable_results.rhtml b/app/views/tag_images/_taggable_results.rhtml new file mode 100644 index 0000000..3c96a98 --- /dev/null +++ b/app/views/tag_images/_taggable_results.rhtml @@ -0,0 +1,10 @@ +<% @results.keys.each do |ctype| -%> +<% next if @results[ctype].empty? -%> +

<%= ctype.pluralize -%>

+

+ <%= @results[ctype].collect { |r| link_to_function(r.title, "set_taggable_item(#{r.id}, '#{r.title}', '#{ctype}');") }.join(', ') %> +

+<% end -%> +<% unless @results.detect { |key, val| !val.empty? } -%> + No results found... +<% end -%> diff --git a/app/views/tag_images/_tagged_images.rhtml b/app/views/tag_images/_tagged_images.rhtml new file mode 100644 index 0000000..6cf85fd --- /dev/null +++ b/app/views/tag_images/_tagged_images.rhtml @@ -0,0 +1,11 @@ +
+<% @tagged_images.each do |tag_image| -%> + <%= link_to(image_tag(tag_image.image.filename_for_version(:thumbnail)), gallery_path(tag_image.image), :popup => true) %> +<% end -%> +
+ +
+ +
+ <%= image_browser_navigation_link('go-first.png', 1, @page_count, @tagged_type, params[:id]) -%> <%= image_browser_navigation_link('go-previous.png', @current_page - 1, @page_count, @tagged_type, params[:id]) -%> <%= @current_page -%> / <%= @page_count -%> <%= image_browser_navigation_link('go-next.png', @current_page + 1, @page_count, @tagged_type, params[:id]) -%> <%= image_browser_navigation_link('go-last.png', @page_count, @page_count, @tagged_type, params[:id]) -%> +
diff --git a/app/views/tag_images/show.rhtml b/app/views/tag_images/show.rhtml new file mode 100644 index 0000000..7ff1c4e --- /dev/null +++ b/app/views/tag_images/show.rhtml @@ -0,0 +1,110 @@ +<% content_for :stylesheet do -%> +#image_block { + z-index: 0; + border: 1px solid black; + padding: 0px; + width: <%= @image.screen_width -%>px; + height: <%= @image.screen_height -%>px; + background-image: url('/images/<%= @image.filename_for_version -%>'); + background-repeat: no-repeat; +} +#image_block_container { + margin: 10px <%= (605 - @image.screen_width) / 2 -%>px 20px <%= (605 - @image.screen_width) / 2 -%>px; +} +#image_tag_box { + position: relative; + z-index: 2; + width: 100px; + height: 100px; + border: 5px solid #db3333; + left: 0; + top: 0; + display: none; +} +<% end -%> + +<% content_for :script do -%> +function show_tag_at(xcoord, ycoord) +{ + $('image_tag_box').style.top = (ycoord - 50) + 'px'; + $('image_tag_box').style.left = (xcoord - 50) + 'px'; + $('image_tag_box').style.display = 'block'; +} + +function hide_tag_box() +{ + $('image_tag_box').style.display = 'none'; +} + +function set_coordinates(event) +{ + xcoord = (event.offsetX ? event.offsetX : (event.pageX - $('image_block').offsetLeft)); + ycoord = (event.offsetY ? event.offsetY : (event.pageY - $('image_block').offsetTop)); + show_tag_at(xcoord, ycoord); + lightboxes['taggedContentDialog'].open(); +} + +function set_taggable_item(id, title, type) +{ + $('tag_image_tagged_id').value = id; + $('tag_image_title').innerHTML = title; + $('tag_image_tagged_type').value = type; +} + +if(!window.after_opens) + after_opens = {}; +if(!window.before_closes) + before_closes = {}; +after_opens['taggedContentDialog'] = function(){ + $('tag_image_x').value = xcoord; + $('tag_image_y').value = ycoord; + $('tag_image_image_id').value = <%= params[:id] -%>; + $('search').focus(); +} +before_closes['taggedContentDialog'] = function(){ + hide_tag_box(); +} +<% end -%> + +
+
+ +
+
+
+ <%= render :partial => 'tag_images' %> +
+
+ +<% lightbox :title => 'Search for a taggable item', :window_id => 'taggedContentDialog' do -%> +
+
+ <%= hidden_field 'tag_image', 'x' %> + <%= hidden_field 'tag_image', 'y' %> + <%= hidden_field 'tag_image', 'image_id' %> + <%= hidden_field 'tag_image', 'tagged_id' %> + <%= hidden_field 'tag_image', 'tagged_type' %> +
+ +
+ <%= link_to_function(image_tag('edit-clear.png'), "$('search').value = '';") -%> <%= text_field_tag 'search', '', :size => 30 -%>
+ Selected: None +
+ +
+ + <%= observe_field 'search', + :url => { :action => 'taggable_search' }, + :frequency => 2, + :update => 'taggable_results', + :with => "'name='+escape(value)" %> + +
+ <%= link_to_remote('Save', { :url => tag_images_path, :with => "Form.serialize($('tag_image_fields'))", :success => 'Control.Modal.close()', :update => { :success => 'tag_images', :failure => 'tag_image_errors' } }, { :method => :post }) -%> + <%= link_to_function 'Cancel', "Control.Modal.close()" -%> +
+<% end -%> + +<% content_for :sidebar do -%> + <%= link_to 'Image Details', gallery_path(@image) -%>
+<% end -%> \ No newline at end of file diff --git a/config/environment.rb b/config/environment.rb index 8d3f1c9..4aae99d 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -9,3 +9,4 @@ end require 'redcloth' require 'has_one_page' +require 'has_many_tagged_images' diff --git a/config/routes.rb b/config/routes.rb index f0b9569..fe9a874 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,8 @@ ActionController::Routing::Routes.draw do |map| + map.resources :tag_images + map.resources :beers, :breweries, :pages, :discussions, :peoples, :roles, - :sessions, :styles, :galleries + :sessions, :styles, :galleries, :tag_images map.connect ':controller/:action/:id.:format' map.connect ':controller/:action/:id' diff --git a/db/migrate/011_create_images.rb b/db/migrate/011_create_images.rb new file mode 100644 index 0000000..f365957 --- /dev/null +++ b/db/migrate/011_create_images.rb @@ -0,0 +1,26 @@ +class CreateImages < ActiveRecord::Migration + def self.up + create_table :images do |t| + t.column :people_id, :integer + t.column :created_at, :datetime + t.column :original, :string + t.column :thumbnail, :string + t.column :screen, :string + t.column :screen_width, :integer + t.column :screen_height, :integer + t.column :content_type, :string + end + add_index :images, :people_id + create_table :images_pages, :id => false do |t| + t.column :image_id, :integer + t.column :page_id, :integer + end + add_index :images_pages, :image_id + add_index :images_pages, :page_id + end + + def self.down + drop_table :images + drop_table :images_pages + end +end diff --git a/db/migrate/012_create_tag_images.rb b/db/migrate/012_create_tag_images.rb new file mode 100644 index 0000000..7a6e71d --- /dev/null +++ b/db/migrate/012_create_tag_images.rb @@ -0,0 +1,19 @@ +class CreateTagImages < ActiveRecord::Migration + def self.up + create_table :tag_images do |t| + t.column :image_id, :integer + t.column :tagged_id, :integer + t.column :tagged_type, :string, :limit => 32 + t.column :primary, :boolean + t.column :x, :integer + t.column :y, :integer + end + add_index :tag_images, :image_id + add_index :tag_images, :tagged_id + add_index :tag_images, :tagged_type + end + + def self.down + drop_table :tag_images + end +end diff --git a/generate_permissions b/generate_permissions index af64f71..d2ccdd8 100644 --- a/generate_permissions +++ b/generate_permissions @@ -5,7 +5,7 @@ base_actions = ApplicationController.action_methods # rather than defining them here. controllers = [ PagesController, DiscussionsController, StylesController, PeoplesController, BeersController, BreweriesController, RolesController, - GalleriesController ] + GalleriesController, TagImagesController ] controllers.each do |c| actions = c.action_methods - base_actions cname = c.controller_name @@ -28,6 +28,11 @@ Permission.find(:all, next if [ 'new', 'create', 'edit', 'update', 'destroy' ].include?(p.action) r.permissions << p end +Permission.find(:all, + :conditions => [ 'controller = ?', 'tag_images' ]).each do |p| + next if [ 'show', 'create', 'destroy', 'taggable_search' ].include?(p.action) + r.permissions << p +end r2 = Role.admin_role Permission.find(:all).each do |p| diff --git a/lib/has_many_tagged_images.rb b/lib/has_many_tagged_images.rb new file mode 100644 index 0000000..66f9d71 --- /dev/null +++ b/lib/has_many_tagged_images.rb @@ -0,0 +1,25 @@ +module ActiveRecord # :nodoc: + class Base # :nodoc: + class << self + ## + # This method will add a has_one :page association and a few useful + # callbacks to the requested model. It expects to have a + # :owner_class parameter given so that it knows what the owner class + # name should be. The associated model will automatically be deleted + # when this model is deleted. + # + # The Page will automatically have the title updated from the owner's + # title field and be saved after a successful save. When a Page errors + # on validation, the errors are automatically copied into the owner so + # that the user doesn't even have to know what is going on. + # + def has_many_tagged_images(options = {}) + class_eval do + has_many :tagged_images, :source_type => self.base_class.to_s, + :source => :tagged, :through => :tag_images + has_many :tag_images, :dependent => :destroy, :as => :tagged + end + end + end + end +end diff --git a/public/images/edit-clear.png b/public/images/edit-clear.png new file mode 100644 index 0000000000000000000000000000000000000000..e6c8e8b9f341cbf3a1795631ccaafd14b0e0c911 GIT binary patch literal 773 zcmV+g1N!`lP)5 zl1pfmXB38?@0*$EOcI;hNJ5iHyw#`_D<+Ey(t?XhOLl^ng^SURf+GA21zi{y1{{!5 z5QL7#g;8=>?7hOBWP~@Qv%4i?;^z?o7$2sh!G| zts6JQ<4rGsx`hNvL;&d8r%uFIa{QCF&sB3vJ3Xhnz3G3(a_m%-#>gcR&Ll0E*wJ36qI~C2ft2!

!GOpKDtrJ2lSaNQ|Hgjn@93PsJ(gZ`2+IBy96 z0wag|dVeYUtj2Xcip4zE2Iz1NmGK?q-I(0Ec_nX5@MXf0%df=eg{JcD1;K)00000NkvXXu0mjf Dmc(vD literal 0 HcmV?d00001 diff --git a/public/images/go-first.png b/public/images/go-first.png new file mode 100644 index 0000000000000000000000000000000000000000..9c15c09e95c3430b5bd1bf24e7a01e8203a2e9a8 GIT binary patch literal 666 zcmV;L0%iS)P)hqp0JED290h! zVU0NBR?cr`p~-QQ_EsWfBH@EQ`Nc0GKMZR^J`BSAxf26@WBs>$$pHKvI7xf7HPn_K z?i~+>+atsnZ|Jg4VBK;@=p(qz`$(+u}xd3d6n3+*N?MDJ7VE8(JO(EzhB^&{0eRn zE*?mNaRLBn_0NHlrd}YI5_3+{j?R9X$}BA9ySqF4x2)2Bd@j8KF~Xg@H(lYH;N~^f z^&poLB>)3X(jI*{c`tpTvp3}I9wGn`t_kK+f;Jb)x5~Mcm=MA{nSJ@R@P7JTO$g5q z)>vRYlF^9H&d+2YJejC8!ZrPk8zPM`j$uE{Us}u$`^<2oGynhq07*qoM6N<$f}(68 A0{{R3 literal 0 HcmV?d00001 diff --git a/public/images/go-last.png b/public/images/go-last.png new file mode 100644 index 0000000000000000000000000000000000000000..6e904efd06236206721c6a662ec8ecfe74e971c5 GIT binary patch literal 685 zcmV;e0#f~nP)5 zlTApJQ51%s``vp#$I+r-vIvbVCs0U1TC~hLW^i)Pz0@_MiE-%s!hV+qFhuE z1Zm_VBKtFl7NgQ1VznuPg3$S4O4HHl`{uj1g=uPI)C+fW&f$UkzUK&QElXA3suR|5 zJ<;J``J?$u(U`UXPO+vn)Kyk&?u@qR+tHXVUkqlzip{BLi%w!$yRaxEd1FmzWb`T3ks8r@G#>8hztmaZq7PV0 zbE3nanxCyZt880y97jl`qmV)&9f_2Z=U?uj9L4I=^=w>K!{FN|e&4{&F>6dCFan?e zj0t%B{u)}App{FB8^U#6l+*|z$>u-tJ)Ndz>rP!+QC-#j=G5`zmT}-uUontb9=r!a)e|uV|)6#W_-^(UF2DL6`zGMn!p-|9fRGGizh?e=&U5++$Mxhv T)(+xx00000NkvXXu0mjfgrPr^ literal 0 HcmV?d00001 diff --git a/public/images/go-next.png b/public/images/go-next.png new file mode 100644 index 0000000000000000000000000000000000000000..6ef8de76e0f5bf01c09da24a07c61cfe558d7a4b GIT binary patch literal 676 zcmV;V0$crwP)5 zl1oTbVI0MO-{a21M#B_R#9l-oXcPp6(?)2~B7y{4Mu9@O$#RS~LMu0okc%jC6BNCq zMPfd15iMrWDWnuP(IPA}ow0!)<9P4f`?UyPGZWN-v-$pq!}*{8hcSkHhP!mEu~WAd zo8?nd1jeIrclCk3aF;a@j#!~$nl%(P0Jzf98$5aR>}dqE;fU4n-v&x*nhu}wwKrd{ z4f;b9;fU2%OeY#6`YVQ=TOJkJo9%;vsn26nmF>ePxAA!V*2;(ZnPFo%AB#FaHw-$p z>A83xDHKX5gpddX0EtgSc(|1Lcd)Cxp7`{*Gd4M}fH9|HQD+7~1Grv}*vDrmsZm0K z5C{Q60m5V1o+G_9&%wGQR!!BO+NWc8C&Ce{BNlb~H9*c=C7oHocC*-S7Ns<$C1sQZ zLijB|M!48sj(1C=)P(9pYjao0(5pv%$FEw)G{Dl2Io>Bnm(2P=eF~WywGEfv2*dEE+1BNH1p0gO_(!va6Ym6#!ZeU0XL$ zmOyFA$XqYlC#eqYr*8WRCf~&E#M}5+`DMhod1o}n6nu_w#4?v#yZPlMNv2Zf#pvLQ?bsc$8%}?|wxEMGI60fdRAO~{ zc52bv6uF?Yza^+BugrY=o*FhT7dA)!rvyS0Urwj)#iE6g^YI%&-U+mBcICDJ0000< KMNUMnLSTZVNH5j^ literal 0 HcmV?d00001 diff --git a/public/images/go-previous.png b/public/images/go-previous.png new file mode 100644 index 0000000000000000000000000000000000000000..659cd90d7f80488a8a6a2c12f6f9e5ad98720461 GIT binary patch literal 655 zcmV;A0&x9_P)_fWZPlDqB9?#%D^>oA$FjP8SXdVl!5?|TnYO4RbBH?=GA zTUy2|reO<?=@r%h} zW6#cJQfPC~s>mwxzQ}>D!BbZalds_8_h}r7_JCAa@f4F))r6lUrCf(hQ;9=;jU5L& z+1FnmP_XjQREAhnABYqX00}@!0U-tMq%XJx@e~^h8rvWApSa|&uWz9Di?6_?1E%N@ zl4EB68Hfl3f`qGnd$iXb;n;2VsHt)P`R?O`My|Agci-NCF&}hI2Ui3vQGjU{P_$N1 zwo6b*g`Wk{x;wF^0p~U>`wvX#H%c-OZ~=##s0b5Wa!4-09SO%4;Ep?Tu9$V#I5TQr z&Mtkq{`h6ugzb~9dlQg)8A30C1pt3Qg&t7B3bbY+H`>YWThbX9oF#2!=nK7F-=6arTX3U&+XWy2~<%(BYwFX|c pI#R>7P%^)q3wROKeC1m2{1 { } + assert_equal old_count+1, Images.count + + assert_redirected_to images_path(assigns(:images)) + end + + def test_should_show_images + get :show, :id => 1 + assert_response :success + end + + def test_should_get_edit + get :edit, :id => 1 + assert_response :success + end + + def test_should_update_images + put :update, :id => 1, :images => { } + assert_redirected_to images_path(assigns(:images)) + end + + def test_should_destroy_images + old_count = Images.count + delete :destroy, :id => 1 + assert_equal old_count-1, Images.count + + assert_redirected_to images_path + end +end diff --git a/test/functional/tag_images_controller_test.rb b/test/functional/tag_images_controller_test.rb new file mode 100644 index 0000000..6eda0a9 --- /dev/null +++ b/test/functional/tag_images_controller_test.rb @@ -0,0 +1,57 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'tag_images_controller' + +# Re-raise errors caught by the controller. +class TagImagesController; def rescue_action(e) raise e end; end + +class TagImagesControllerTest < Test::Unit::TestCase + fixtures :tag_images + + def setup + @controller = TagImagesController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_should_get_index + get :index + assert_response :success + assert assigns(:tag_images) + end + + def test_should_get_new + get :new + assert_response :success + end + + def test_should_create_tag_image + old_count = TagImage.count + post :create, :tag_image => { } + assert_equal old_count+1, TagImage.count + + assert_redirected_to tag_image_path(assigns(:tag_image)) + end + + def test_should_show_tag_image + get :show, :id => 1 + assert_response :success + end + + def test_should_get_edit + get :edit, :id => 1 + assert_response :success + end + + def test_should_update_tag_image + put :update, :id => 1, :tag_image => { } + assert_redirected_to tag_image_path(assigns(:tag_image)) + end + + def test_should_destroy_tag_image + old_count = TagImage.count + delete :destroy, :id => 1 + assert_equal old_count-1, TagImage.count + + assert_redirected_to tag_images_path + end +end diff --git a/test/unit/image_test.rb b/test/unit/image_test.rb new file mode 100644 index 0000000..499d844 --- /dev/null +++ b/test/unit/image_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ImageTest < Test::Unit::TestCase + fixtures :images + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/test/unit/tag_image_test.rb b/test/unit/tag_image_test.rb new file mode 100644 index 0000000..8f1f3d1 --- /dev/null +++ b/test/unit/tag_image_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class TagImageTest < Test::Unit::TestCase + fixtures :tag_images + + # Replace this with your real tests. + def test_truth + assert true + end +end