alchemy-dragonfly-s3 4.0.3 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.