Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract Thumbnails and CropAction concerns #2141

Merged
merged 2 commits into from
Jun 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions app/controllers/alchemy/admin/essence_pictures_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
module Alchemy
module Admin
class EssencePicturesController < Alchemy::Admin::BaseController
include CropAction

authorize_resource class: Alchemy::EssencePicture

before_action :load_essence_picture, only: [:edit, :crop, :update]
before_action :load_essence_picture, only: [:edit, :update]
before_action :load_content, only: [:edit, :update]

helper "alchemy/admin/contents"
Expand All @@ -15,16 +17,6 @@ class EssencePicturesController < Alchemy::Admin::BaseController
def edit
end

def crop
@picture = Picture.find_by(id: params[:picture_id])
if @picture
@essence_picture.picture = @picture
@settings = @essence_picture.image_cropper_settings
else
@no_image_notice = Alchemy.t(:no_image_for_cropper_found)
end
end

def update
@essence_picture.update(essence_picture_params)
end
Expand All @@ -35,6 +27,10 @@ def load_essence_picture
@essence_picture = EssencePicture.find(params[:id])
end

def load_croppable_resource
@croppable_resource = EssencePicture.find(params[:id])
end

def load_content
@content = Content.find(params[:content_id])
end
Expand Down
26 changes: 26 additions & 0 deletions app/controllers/concerns/alchemy/admin/crop_action.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module Alchemy
module Admin
module CropAction
extend ActiveSupport::Concern

included do
before_action :load_croppable_resource, only: [:crop]
end

def crop
@picture = Alchemy::Picture.find_by(id: params[:picture_id])
if @picture
@croppable_resource.picture = @picture
@settings = @croppable_resource.image_cropper_settings
@element = @croppable_resource.element
else
@no_image_notice = Alchemy.t(:no_image_for_cropper_found)
end

render template: "alchemy/admin/crop"
end
end
end
end
176 changes: 4 additions & 172 deletions app/models/alchemy/essence_picture.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,93 +23,18 @@

module Alchemy
class EssencePicture < BaseRecord
include Alchemy::PictureThumbnails

acts_as_essence ingredient_column: :picture, belongs_to: {
class_name: "Alchemy::Picture",
foreign_key: :picture_id,
inverse_of: :essence_pictures,
optional: true,
}

delegate :image_file_width, :image_file_height, :image_file, to: :picture, allow_nil: true
before_save :fix_crop_values
before_save :replace_newlines

# The url to show the picture.
#
# Takes all values like +name+ and crop sizes (+crop_from+, +crop_size+ from the build in graphical image cropper)
# and also adds the security token.
#
# You typically want to set the size the picture should be resized to.
#
# === Example:
#
# essence_picture.picture_url(size: '200x300', crop: true, format: 'gif')
# # '/pictures/1/show/200x300/crop/cats.gif?sh=765rfghj'
#
# @option options size [String]
# The size the picture should be resized to.
#
# @option options format [String]
# The format the picture should be rendered in.
# Defaults to the +image_output_format+ from the +Alchemy::Config+.
#
# @option options crop [Boolean]
# If set to true the picture will be cropped to fit the size value.
#
# @return [String]
def picture_url(options = {})
return if picture.nil?

picture.url(picture_url_options.merge(options)) || "missing-image.png"
end
delegate :settings, to: :content

# Picture rendering options
#
# Returns the +default_render_format+ of the associated +Alchemy::Picture+
# together with the +crop_from+ and +crop_size+ values
#
# @return [HashWithIndifferentAccess]
def picture_url_options
return {} if picture.nil?

crop = crop_values_present? || content.settings[:crop]

{
format: picture.default_render_format,
crop: !!crop,
crop_from: crop_from.presence,
crop_size: crop_size.presence,
size: content.settings[:size],
}.with_indifferent_access
end

# Returns an url for the thumbnail representation of the assigned picture
#
# It takes cropping values into account, so it always represents the current
# image displayed in the frontend.
#
# @return [String]
def thumbnail_url
return if picture.nil?

picture.url(thumbnail_url_options) || "alchemy/missing-image.svg"
end

# Thumbnail rendering options
#
# @return [HashWithIndifferentAccess]
def thumbnail_url_options
crop = crop_values_present? || content.settings[:crop]

{
size: "160x120",
crop: !!crop,
crop_from: crop_from.presence || default_crop_from&.join("x"),
crop_size: crop_size.presence || default_crop_size&.join("x"),
flatten: true,
format: picture&.image_file_format || "jpg",
}
end
before_save :replace_newlines

# The name of the picture used as preview text in element editor views.
#
Expand All @@ -130,101 +55,8 @@ def serialized_ingredient
picture_url(content.settings)
end

# Show image cropping link for content
def allow_image_cropping?
content && content.settings[:crop] && picture &&
picture.can_be_cropped_to?(
content.settings[:size],
content.settings[:upsample],
) && !!picture.image_file
end

def crop_values_present?
crop_from.present? && crop_size.present?
end

# Settings for the graphical JS image cropper

def image_cropper_settings
Alchemy::ImageCropperSettings.new(
render_size: dimensions_from_string(render_size.presence || content.settings[:size]),
default_crop_from: default_crop_from,
default_crop_size: default_crop_size,
fixed_ratio: content.settings[:fixed_ratio],
image_width: picture&.image_file_width,
image_height: picture&.image_file_height,
).to_h
end

private

def fix_crop_values
%i(crop_from crop_size).each do |crop_value|
if self[crop_value].is_a?(String)
write_attribute crop_value, normalize_crop_value(crop_value)
end
end
end

def default_crop_size
return nil unless content.settings[:crop] && content.settings[:size]

mask = inferred_dimensions_from_string(content.settings[:size])
zoom = thumbnail_zoom_factor(mask)
return nil if zoom.zero?

[(mask[0] / zoom), (mask[1] / zoom)].map(&:round)
end

def thumbnail_zoom_factor(mask)
[
mask[0].to_f / (image_file_width || 1),
mask[1].to_f / (image_file_height || 1),
].max
end

def default_crop_from
return nil unless content.settings[:crop]
return nil if default_crop_size.nil?

[
((image_file_width || 0) - default_crop_size[0]) / 2,
((image_file_height || 0) - default_crop_size[1]) / 2,
].map(&:round)
end

def dimensions_from_string(string)
return if string.nil?

string.split("x", 2).map(&:to_i)
end

def inferred_dimensions_from_string(string)
return if string.nil?

width, height = dimensions_from_string(string)
ratio = image_file_width.to_f / image_file_height.to_i

if width.zero? && ratio.is_a?(Float)
width = height * ratio
end

if height.zero? && ratio.is_a?(Float)
height = width / ratio
end

[width.to_i, height.to_i]
end

def normalize_crop_value(crop_value)
self[crop_value].split("x").map { |n| normalize_number(n) }.join("x")
end

def normalize_number(number)
number = number.to_f.round
number.negative? ? 0 : number
end

def replace_newlines
return nil if caption.nil?

Expand Down
Loading