alchemy-dragonfly-s3 4.0.3 → 4.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5282390f9bee229c6deb6d78740307924c3ad12b6ed1ff964cb351b38896e1c9
4
- data.tar.gz: 559baa3bee41168030536cb7d19c925399b422b1ecb72c2886dc455369b71998
3
+ metadata.gz: 3b874666e4ea078bbbd88849204a3817c5460d3578a30b9a5e6ecbef8a411912
4
+ data.tar.gz: b755cef56da8ba88634bf18b07571c21c4d7d43e37d331697893fceb15d1bd80
5
5
  SHA512:
6
- metadata.gz: b1f5f13dedeea05be74e6889c7dcb6b021e02c48adcb0f0f1a991f4a709e24cdf9825cfbd1ec207100cd08aec8993a352a1ac1edb47dd1034106a4c07bb8caa2
7
- data.tar.gz: 5a7371ad6b0e4d04ce7d1463c931a3dd350d89d5547c7cb2b83bb90c6bbd85af517cfea57f47e6606dd1004df0919c034966caa4ee6455ef5ec89bd44f6a83fc
6
+ metadata.gz: 65e050d8241328e3d1daac76e8eeb0cb83022216be1f1534ec98e7a93d25b4e04e9fceccd9db0b6a005cf528281256288d32d573fefcaec4c8209661ee66aff2
7
+ data.tar.gz: 4b10af37543c65d84ae518a2e14fbeacb60436a9f59cf486c49ce8c6ad49679b08636300f4ade1d273288acf2c7785e241678dfd4bfe564886ae2b4950906a23
@@ -0,0 +1 @@
1
+ //= link alchemy/missing-image.svg
@@ -0,0 +1 @@
1
+ <svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" width="16" height="16"><path fill="#f7f7f7" d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z" class=""></path></svg>
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class Attachment < BaseRecord
5
+ class S3Url
6
+ def self.call(attachment)
7
+ attachment.file.remote_url
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class Picture < BaseRecord
5
+ class S3Url
6
+ attr_reader :variant
7
+
8
+ # @param [Alchemy::PictureVariant]
9
+ #
10
+ def initialize(variant)
11
+ raise ArgumentError, "Variant missing!" if variant.nil?
12
+
13
+ @variant = variant
14
+ end
15
+
16
+ def call(*)
17
+ return variant.image.remote_url unless processible_image?
18
+
19
+ ::Dragonfly.app(:alchemy_pictures).remote_url_for(uid)
20
+ end
21
+
22
+ private
23
+
24
+ def processible_image?
25
+ variant.image.is_a?(::Dragonfly::Job)
26
+ end
27
+
28
+ def uid
29
+ signature = PictureThumb::Signature.call(variant)
30
+ thumb = variant.picture.thumbs.detect { |t| t.signature == signature }
31
+ if thumb
32
+ uid = thumb.uid
33
+ else
34
+ uid = PictureThumb::Uid.call(signature, variant)
35
+ PictureThumb::Create.call(variant, signature, uid)
36
+ end
37
+ uid
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ # The persisted version of a rendered picture variant
5
+ #
6
+ class PictureThumb < BaseRecord
7
+ belongs_to :picture, class_name: "Alchemy::Picture"
8
+
9
+ validates :signature, presence: true
10
+ validates :uid, presence: true
11
+
12
+ class << self
13
+ # Upfront generation of picture thumbnails
14
+ #
15
+ # Called after a Alchemy::Picture has been created (after an image has been uploaded)
16
+ #
17
+ # Generates three types of thumbnails that are used by Alchemys picture archive and
18
+ # persists them in the configures file store (Default Dragonfly::FileDataStore).
19
+ #
20
+ # @see Picture::THUMBNAIL_SIZES
21
+ def generate_thumbs!(picture)
22
+ Alchemy::Picture::THUMBNAIL_SIZES.values.map do |size|
23
+ variant = Alchemy::PictureVariant.new(picture, {
24
+ size: size,
25
+ flatten: true,
26
+ })
27
+ signature = Alchemy::PictureThumb::Signature.call(variant)
28
+ thumb = find_by(signature: signature)
29
+ next if thumb
30
+
31
+ uid = Alchemy::PictureThumb::Uid.call(signature, variant)
32
+ Alchemy::PictureThumb::Create.call(variant, signature, uid)
33
+ uid
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ module Alchemy
2
+ class PictureThumb < BaseRecord
3
+ # Stores the render result of a Alchemy::PictureVariant
4
+ # in the Dragonfly S3 datastore
5
+ #
6
+ class Create
7
+ def self.call(variant, signature, uid)
8
+ image = variant.image
9
+ image.store(path: uid)
10
+ variant.picture.thumbs.create!(
11
+ picture: variant.picture,
12
+ signature: signature,
13
+ uid: uid,
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class PictureThumb < BaseRecord
5
+ class Signature
6
+ # Returns a unique image process signature
7
+ #
8
+ # @param [Alchemy::PictureVariant]
9
+ #
10
+ # @return [String]
11
+ def self.call(variant)
12
+ steps_without_fetch = variant.image.steps.reject do |step|
13
+ step.is_a?(::Dragonfly::Job::Fetch)
14
+ end
15
+
16
+ steps_with_id = [[variant.picture.id]] + steps_without_fetch
17
+ job_string = steps_with_id.map(&:to_a).to_dragonfly_unique_s
18
+
19
+ Digest::SHA1.hexdigest(job_string)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alchemy
4
+ class PictureThumb < BaseRecord
5
+ class Uid
6
+ # Returns a image variant uid for storage
7
+ #
8
+ # @param [String]
9
+ # @param [Alchemy::PictureVariant]
10
+ #
11
+ # @return [String]
12
+ def self.call(signature, variant)
13
+ picture = variant.picture
14
+ filename = variant.image_file_name || "image"
15
+ name = File.basename(filename, ".*").gsub(/[^\w.]+/, "_")
16
+ ext = variant.render_format
17
+
18
+ "pictures/#{picture.id}/#{signature}/#{name}.#{ext}"
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Alchemy
6
+ # Represents a rendered picture
7
+ #
8
+ # Resizes, crops and encodes the image with imagemagick
9
+ #
10
+ class PictureVariant
11
+ extend Forwardable
12
+
13
+ include Alchemy::Logger
14
+ include Alchemy::Picture::Transformations
15
+
16
+ attr_reader :picture, :render_format
17
+
18
+ def_delegators :@picture,
19
+ :image_file,
20
+ :image_file_width,
21
+ :image_file_height,
22
+ :image_file_name,
23
+ :image_file_size
24
+
25
+ # @param [Alchemy::Picture]
26
+ #
27
+ # @param [Hash] options passed to the image processor
28
+ # @option options [Boolean] :crop Pass true to enable cropping
29
+ # @option options [String] :crop_from Coordinates to start cropping from
30
+ # @option options [String] :crop_size Size of the cropping area
31
+ # @option options [Boolean] :flatten Pass true to flatten GIFs
32
+ # @option options [String|Symbol] :format Image format to encode the image in
33
+ # @option options [Integer] :quality JPEG compress quality
34
+ # @option options [String] :size Size of resulting image in WxH
35
+ # @option options [Boolean] :upsample Pass true to upsample (grow) an image if the original size is lower than the resulting size
36
+ #
37
+ def initialize(picture, options = {})
38
+ raise ArgumentError, "Picture missing!" if picture.nil?
39
+
40
+ @picture = picture
41
+ @options = options
42
+ @render_format = options[:format] || picture.default_render_format
43
+ end
44
+
45
+ # Process a variant of picture
46
+ #
47
+ # @return [Dragonfly::Attachment|Dragonfly::Job] The processed image variant
48
+ #
49
+ def image
50
+ image = image_file
51
+
52
+ raise MissingImageFileError, "Missing image file for #{picture.inspect}" if image.nil?
53
+
54
+ image = processed_image(image, @options)
55
+ image = encoded_image(image, @options)
56
+ image
57
+ rescue MissingImageFileError, WrongImageFormatError => e
58
+ log_warning(e.message)
59
+ nil
60
+ end
61
+
62
+ private
63
+
64
+ # Returns the processed image dependent of size and cropping parameters
65
+ def processed_image(image, options = {})
66
+ size = options[:size]
67
+ upsample = !!options[:upsample]
68
+
69
+ return image unless size.present? && picture.has_convertible_format?
70
+
71
+ if options[:crop]
72
+ crop(size, options[:crop_from], options[:crop_size], upsample)
73
+ else
74
+ resize(size, upsample)
75
+ end
76
+ end
77
+
78
+ # Returns the encoded image
79
+ #
80
+ # Flatten animated gifs, only if converting to a different format.
81
+ # Can be overwritten via +options[:flatten]+.
82
+ #
83
+ def encoded_image(image, options = {})
84
+ unless render_format.in?(Alchemy::Picture.allowed_filetypes)
85
+ raise WrongImageFormatError.new(picture, @render_format)
86
+ end
87
+
88
+ options = {
89
+ flatten: render_format != "gif" && picture.image_file_format == "gif",
90
+ }.with_indifferent_access.merge(options)
91
+
92
+ encoding_options = []
93
+
94
+ convert_format = render_format != picture.image_file_format.sub("jpeg", "jpg")
95
+
96
+ if render_format =~ /jpe?g/ && convert_format
97
+ quality = options[:quality] || Config.get(:output_image_jpg_quality)
98
+ encoding_options << "-quality #{quality}"
99
+ end
100
+
101
+ if options[:flatten]
102
+ encoding_options << "-flatten"
103
+ end
104
+
105
+ convertion_needed = convert_format || encoding_options.present?
106
+
107
+ if picture.has_convertible_format? && convertion_needed
108
+ image = image.encode(render_format, encoding_options.join(" "))
109
+ end
110
+
111
+ image
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,54 @@
1
+ <div class="resource_info">
2
+ <div class="value">
3
+ <label>
4
+ <%= render_icon @attachment.icon_css_class, style: 'regular', size: 'lg' %>
5
+ </label>
6
+ <p><%= @attachment.file_name %></p>
7
+ </div>
8
+ <div class="value with-icon">
9
+ <label><%= Alchemy::Attachment.human_attribute_name(:url) %></label>
10
+ <p><%= @attachment.url %></p>
11
+ <a data-clipboard-text="<%= @attachment.url %>" class="icon_button--right">
12
+ <%= render_icon(:clipboard, style: 'regular') %>
13
+ </a>
14
+ </div>
15
+ <div class="value with-icon">
16
+ <label><%= Alchemy::Attachment.human_attribute_name(:download_url) %></label>
17
+ <p><%= @attachment.url %></p>
18
+ <a data-clipboard-text="<%= @attachment.url %>" class="icon_button--right">
19
+ <%= render_icon(:clipboard, style: 'regular') %>
20
+ </a>
21
+ </div>
22
+ </div>
23
+
24
+ <% case @attachment.icon_css_class %>
25
+ <% when "file-image" %>
26
+ <div class="attachment_preview_container image-preview">
27
+ <%= image_tag(@attachment.url, class: "full_width") %>
28
+ </div>
29
+ <% when "file-audio" %>
30
+ <div class="attachment_preview_container player-preview">
31
+ <%= audio_tag(@attachment.url, preload: "none", controls: true, class: "full_width") %>
32
+ </div>
33
+ <% when "file-video" %>
34
+ <div class="attachment_preview_container player-preview">
35
+ <%= video_tag(@attachment.url, preload: "metadata", controls: true, class: "full_width") %>
36
+ </div>
37
+ <% when "file-pdf" %>
38
+ <iframe src="<%= @attachment.url %>" frameborder=0 class="full-iframe">
39
+ Your browser does not support frames.
40
+ </iframe>
41
+ <% end %>
42
+
43
+ <script type="text/javascript">
44
+ $(function() {
45
+ var clipboard = new Clipboard('.icon_button--right');
46
+ clipboard.on('success', function(e) {
47
+ Alchemy.growl('<%= Alchemy.t("Copied to clipboard") %>');
48
+ e.clearSelection();
49
+ });
50
+ Alchemy.currentDialog().dialog.on('DialogClose.Alchemy', function() {
51
+ clipboard.destroy();
52
+ });
53
+ });
54
+ </script>
@@ -0,0 +1,53 @@
1
+ <div class="picture_thumbnail <%= @size %>" id="picture_<%= picture.id %>" name="<%= picture.name %>">
2
+ <span class="picture_tool select">
3
+ <%= check_box_tag "picture_ids[]", picture.id %>
4
+ </span>
5
+ <% if picture.deletable? && can?(:destroy, picture) %>
6
+ <span class="picture_tool delete">
7
+ <%= link_to_confirm_dialog(
8
+ render_icon(:minus),
9
+ Alchemy.t(:confirm_to_delete_image_from_server),
10
+ alchemy.admin_picture_path(
11
+ id: picture,
12
+ q: search_filter_params[:q],
13
+ page: params[:page],
14
+ tagged_with: search_filter_params[:tagged_with],
15
+ size: params[:size],
16
+ filter: search_filter_params[:filter]
17
+ ),
18
+ {
19
+ title: Alchemy.t('Delete image')
20
+ }
21
+ ) -%>
22
+ </span>
23
+ <% end %>
24
+ <% image = image_tag(
25
+ picture.url(size: preview_size(@size), flatten: true) || "alchemy/missing-image.svg",
26
+ alt: picture.name,
27
+ title: Alchemy.t(:zoom_image)
28
+ ) %>
29
+ <% if can?(:edit, picture) %>
30
+ <%= link_to(
31
+ image,
32
+ alchemy.admin_picture_path(
33
+ id: picture,
34
+ q: search_filter_params[:q],
35
+ page: params[:page],
36
+ tagged_with: search_filter_params[:tagged_with],
37
+ size: params[:size],
38
+ filter: search_filter_params[:filter]
39
+ ),
40
+ class: 'thumbnail_background'
41
+ ) %>
42
+ <% else %>
43
+ <%= image %>
44
+ <% end %>
45
+ <span class="picture_name" title="<%= picture.name %>">
46
+ <%= picture.name %>
47
+ </span>
48
+ <div class="picture_tags">
49
+ <% picture.tag_list.each do |tag| %>
50
+ <span class="tag"><%= tag %></span>
51
+ <% end %>
52
+ </div>
53
+ </div>
@@ -0,0 +1,21 @@
1
+ <div class="picture_thumbnail assign_image_list_detail <%= size.blank? ? 'medium' : size %>" name="<%= picture_to_assign.name %>" id="picture_to_assign_<%= picture_to_assign.id %>">
2
+ <%= link_to(
3
+ image_tag(
4
+ picture_to_assign.url(size: preview_size(size), flatten: true) || "alchemy/missing-image.svg",
5
+ alt: picture_to_assign.name
6
+ ),
7
+ alchemy.assign_admin_essence_pictures_path(
8
+ picture_id: picture_to_assign.id,
9
+ content_id: @content,
10
+ options: options
11
+ ),
12
+ remote: true,
13
+ onclick: '$(self).attr("href", "#").off("click"); return false',
14
+ method: 'put',
15
+ title: Alchemy.t(:assign_image),
16
+ class: 'thumbnail_background'
17
+ ) %>
18
+ <div class="picture_name" title="<%= picture_to_assign.name %>">
19
+ <%= picture_to_assign.name %>
20
+ </div>
21
+ </div>
@@ -0,0 +1,43 @@
1
+ <div class="zoomed-picture-background">
2
+ <%= image_tag @picture.url || "alchemy/missing-image.svg" %>
3
+ </div>
4
+
5
+ <div class="picture-overlay-navigation">
6
+ <% if @previous %>
7
+ <%= link_to alchemy.admin_picture_path(
8
+ id: @previous,
9
+ q: search_filter_params[:q],
10
+ page: params[:page],
11
+ tagged_with: search_filter_params[:tagged_with],
12
+ size: params[:size],
13
+ filter: search_filter_params[:filter]
14
+ ),
15
+ class: "previous-picture",
16
+ remote: true do %>
17
+ <i class="icon fas fa-angle-left fa-fw"></i>
18
+ <% end %>
19
+ <% end %>
20
+ <% if @next %>
21
+ <%= link_to alchemy.admin_picture_path(
22
+ id: @next,
23
+ q: search_filter_params[:q],
24
+ page: params[:page],
25
+ tagged_with: search_filter_params[:tagged_with],
26
+ size: params[:size],
27
+ filter: search_filter_params[:filter]
28
+ ),
29
+ class: "next-picture",
30
+ remote: true do %>
31
+ <i class="icon fas fa-angle-right fa-fw"></i>
32
+ <% end %>
33
+ <% end %>
34
+ </div>
35
+
36
+ <div class="picture-details-overlay">
37
+ <%= render 'form' %>
38
+ <%= render 'infos' %>
39
+ </div>
40
+
41
+ <div class="picture-overlay-handle">
42
+ <i class="icon fas fa-angle-double-right fa-fw"></i>
43
+ </div>
@@ -0,0 +1,14 @@
1
+ <% content = local_assigns[:content] || local_assigns[:essence_file_view] %>
2
+ <%- if attachment = content.ingredient -%>
3
+ <%- html_options = local_assigns.fetch(:html_options, {}) -%>
4
+ <%= link_to(
5
+ content.essence.link_text.presence ||
6
+ content.settings_value(:link_text, local_assigns.fetch(:options, {})) ||
7
+ attachment.name,
8
+ attachment.url,
9
+ {
10
+ class: content.essence.css_class.presence,
11
+ title: content.essence.title.presence
12
+ }.merge(html_options)
13
+ ) -%>
14
+ <%- end -%>
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateAlchemyPictureThumbs < ActiveRecord::Migration[5.0]
4
+ def change
5
+ create_table :alchemy_picture_thumbs do |t|
6
+ t.references :picture, null: false, foreign_key: { to_table: :alchemy_pictures }
7
+ t.string :signature, null: false
8
+ t.text :uid, null: false
9
+ end
10
+ add_index :alchemy_picture_thumbs, :signature, unique: true
11
+ end
12
+ end
@@ -3,7 +3,7 @@
3
3
  module Alchemy
4
4
  module Dragonfly
5
5
  module S3
6
- VERSION = "4.0.3"
6
+ VERSION = "4.0.4"
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alchemy-dragonfly-s3
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.3
4
+ version: 4.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas von Deyen
@@ -117,6 +117,21 @@ extra_rdoc_files: []
117
117
  files:
118
118
  - MIT-LICENSE
119
119
  - README.md
120
+ - app/assets/config/alchemy_dragonfly_s3_manifest.js
121
+ - app/assets/images/alchemy/missing-image.svg
122
+ - app/models/alchemy/attachment/s3_url.rb
123
+ - app/models/alchemy/picture/s3_url.rb
124
+ - app/models/alchemy/picture_thumb.rb
125
+ - app/models/alchemy/picture_thumb/create.rb
126
+ - app/models/alchemy/picture_thumb/signature.rb
127
+ - app/models/alchemy/picture_thumb/uid.rb
128
+ - app/models/alchemy/picture_variant.rb
129
+ - app/views/alchemy/admin/attachments/show.html.erb
130
+ - app/views/alchemy/admin/pictures/_picture.html.erb
131
+ - app/views/alchemy/admin/pictures/_picture_to_assign.html.erb
132
+ - app/views/alchemy/admin/pictures/show.html.erb
133
+ - app/views/alchemy/essences/_essence_file_view.html.erb
134
+ - db/migrate/1_create_alchemy_picture_thumbs.rb
120
135
  - lib/alchemy-dragonfly-s3.rb
121
136
  - lib/alchemy/attachment_monkey_patch.rb
122
137
  - lib/alchemy/dragonfly/s3/engine.rb
@@ -143,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
158
  - !ruby/object:Gem::Version
144
159
  version: '0'
145
160
  requirements: []
146
- rubygems_version: 3.1.4
161
+ rubygems_version: 3.0.3
147
162
  signing_key:
148
163
  specification_version: 4
149
164
  summary: AlchemyCMS Dragonfly S3.