active_storage-async_variants-ui 0.1.0

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.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +24 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Appraisals +11 -0
  6. data/CHANGELOG.md +8 -0
  7. data/CLAUDE.md +35 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +89 -0
  10. data/Rakefile +27 -0
  11. data/app/assets/javascripts/progress-bar.js +347 -0
  12. data/app/assets/javascripts/retry.js +11 -0
  13. data/app/assets/stylesheets/progress.css +35 -0
  14. data/app/assets/stylesheets/retry.css +98 -0
  15. data/app/controllers/active_storage/async_variants/states_controller.rb +71 -0
  16. data/app/views/active_storage/async_variants/states/_failed.html.erb +44 -0
  17. data/app/views/active_storage/async_variants/states/_pending.html.erb +1 -0
  18. data/app/views/active_storage/async_variants/states/_processed.html.erb +5 -0
  19. data/app/views/active_storage/async_variants/states/_processing.html.erb +15 -0
  20. data/app/views/active_storage/async_variants/states/show.html.erb +10 -0
  21. data/config/routes.rb +20 -0
  22. data/features/retry_flow.feature +20 -0
  23. data/features/retry_visibility.feature +23 -0
  24. data/features/state_rendering.feature +40 -0
  25. data/features/step_definitions/dummy_steps.rb +144 -0
  26. data/features/support/env.rb +28 -0
  27. data/gemfiles/rails_7.2.gemfile +18 -0
  28. data/gemfiles/rails_8.0.gemfile +18 -0
  29. data/gemfiles/rails_8.1.gemfile +18 -0
  30. data/lib/active_storage/async_variants/asset_tag_helper_extension.rb +59 -0
  31. data/lib/active_storage/async_variants/helper.rb +80 -0
  32. data/lib/active_storage/async_variants/ui/version.rb +9 -0
  33. data/lib/active_storage/async_variants/ui.rb +45 -0
  34. data/log/.gitkeep +0 -0
  35. metadata +118 -0
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ module AsyncVariants
5
+ # View/controller helper methods. Included in the AssetTagHelperExtension
6
+ # (so image_tag/video_tag can use them) and added to StatesController via
7
+ # `helper ActiveStorage::AsyncVariants::Helper` (so the state partials can
8
+ # use them).
9
+ module Helper
10
+ # Invisible box reserving layout for the progress bar. The image src is a
11
+ # tiny gem GIF whose URL ends in the media filename (no original fetched);
12
+ # the video placeholder carries no <source>.
13
+ def async_variant_placeholder_tag(variant, html_options = {}, kind: :image)
14
+ opts = async_variant_box_dimensions(variant)
15
+ .merge(html_options.symbolize_keys.except(:src, :controls, :autoplay, :preload, :poster))
16
+ if kind == :video
17
+ content_tag(:video, "", opts)
18
+ else
19
+ src = Rails.application.routes.url_helpers.async_variant_placeholder_path(variant.blob.filename.to_s)
20
+ image_tag(src, opts)
21
+ end
22
+ end
23
+
24
+ def async_variant_box_dimensions(variant)
25
+ resize = variant.variation.transformations
26
+ .values_at(:resize_to_limit, :resize_to_fit, :resize_to_fill)
27
+ .compact.first
28
+ resize.is_a?(Array) ? { width: resize[0], height: resize[1] } : {}
29
+ end
30
+
31
+ # In test with non-bucket-backed services, the gem defers to vanilla
32
+ # ActiveStorage (synchronous vips transform) -- inline rendering keeps
33
+ # those environments simple. Otherwise, only inline a normal <img> when
34
+ # the variant has reached the processed terminal state.
35
+ def async_variant_processed_inline?(variant)
36
+ !variant.blob.bucket_backed? || variant.async_state == "processed"
37
+ end
38
+
39
+ def async_variant_resolved_src(variant, direct:)
40
+ if direct && variant.async_state == "processed"
41
+ async_variant_direct_url(variant)
42
+ else
43
+ variant.processed if variant.blob.bucket_backed?
44
+ async_variant_representation_path(variant)
45
+ end
46
+ end
47
+
48
+ def async_variant_frame_id(variant)
49
+ digest = variant.variation.digest.gsub(/[^a-zA-Z0-9_-]/, "")
50
+ "async-variant-#{variant.blob.id}-#{digest}"
51
+ end
52
+
53
+ def async_variant_frame_src(variant, kind:, direct:, html_options: {})
54
+ async_variant_state_path(
55
+ signed_blob_id: variant.blob.signed_id,
56
+ variation_key: variant.variation.key,
57
+ kind:,
58
+ direct:,
59
+ opts: html_options.slice(*PASS_THROUGH_HTML_OPTIONS),
60
+ )
61
+ end
62
+
63
+ def async_variant_direct_url(variant)
64
+ if cdn = ActiveStorage::AsyncVariants.cdn_host
65
+ "#{cdn}/#{variant.key}"
66
+ else
67
+ variant.image.url
68
+ end
69
+ end
70
+
71
+ def async_variant_representation_path(variant)
72
+ Rails.application.routes.url_helpers.rails_blob_representation_path(
73
+ variant.blob.signed_id,
74
+ variant.variation.key,
75
+ variant.blob.filename.to_s,
76
+ )
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorage
4
+ module AsyncVariants
5
+ module UI
6
+ VERSION = "0.1.0"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_storage/async_variants" # the plumbing gem
4
+ require "turbo-rails"
5
+ require "isolate_assets"
6
+ require_relative "ui/version"
7
+ require_relative "helper"
8
+ require_relative "asset_tag_helper_extension"
9
+
10
+ module ActiveStorage
11
+ module AsyncVariants
12
+ # HTML attributes round-tripped through the state-endpoint URL so the
13
+ # eventual processed-state render can apply them to the inner <img>/<video>.
14
+ PASS_THROUGH_HTML_OPTIONS = %i[alt width height controls autoplay preload].freeze
15
+
16
+ mattr_accessor :cdn_host
17
+ mattr_accessor :retry_visible_proc, default: ->(_view) { false }
18
+
19
+ # Lets the host app plug its auth chain (and thus `current_user`) into the
20
+ # gem's StatesController. Set to a string so resolution is deferred until
21
+ # the host's class is autoloadable. Defaults to ActionController::Base.
22
+ mattr_accessor :parent_controller, default: "ActionController::Base"
23
+
24
+ # Gates the failed-state retry affordance; the block runs in the view context.
25
+ def self.retry_visible_if(&block)
26
+ self.retry_visible_proc = block
27
+ end
28
+
29
+ def self.retry_visible?(view)
30
+ !!view.instance_exec(&retry_visible_proc)
31
+ rescue StandardError
32
+ false
33
+ end
34
+
35
+ class UIEngine < ::Rails::Engine
36
+ config.after_initialize do
37
+ ActionView::Helpers::AssetTagHelper.prepend(
38
+ ActiveStorage::AsyncVariants::AssetTagHelperExtension
39
+ )
40
+ end
41
+ end
42
+
43
+ Assets = IsolateAssets.register(namespace: self, engine: UIEngine, route_name: :async_variant_asset)
44
+ end
45
+ end
data/log/.gitkeep ADDED
File without changes
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_storage-async_variants-ui
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Micah Geisel
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2026-06-10 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: active_storage-async_variants
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0.9'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0.9'
26
+ - !ruby/object:Gem::Dependency
27
+ name: turbo-rails
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: isolate_assets
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0.4'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0.4'
54
+ email:
55
+ - micah@botandrose.com
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - ".github/workflows/ci.yml"
61
+ - ".ruby-gemset"
62
+ - ".ruby-version"
63
+ - Appraisals
64
+ - CHANGELOG.md
65
+ - CLAUDE.md
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - app/assets/javascripts/progress-bar.js
70
+ - app/assets/javascripts/retry.js
71
+ - app/assets/stylesheets/progress.css
72
+ - app/assets/stylesheets/retry.css
73
+ - app/controllers/active_storage/async_variants/states_controller.rb
74
+ - app/views/active_storage/async_variants/states/_failed.html.erb
75
+ - app/views/active_storage/async_variants/states/_pending.html.erb
76
+ - app/views/active_storage/async_variants/states/_processed.html.erb
77
+ - app/views/active_storage/async_variants/states/_processing.html.erb
78
+ - app/views/active_storage/async_variants/states/show.html.erb
79
+ - config/routes.rb
80
+ - features/retry_flow.feature
81
+ - features/retry_visibility.feature
82
+ - features/state_rendering.feature
83
+ - features/step_definitions/dummy_steps.rb
84
+ - features/support/env.rb
85
+ - gemfiles/rails_7.2.gemfile
86
+ - gemfiles/rails_8.0.gemfile
87
+ - gemfiles/rails_8.1.gemfile
88
+ - lib/active_storage/async_variants/asset_tag_helper_extension.rb
89
+ - lib/active_storage/async_variants/helper.rb
90
+ - lib/active_storage/async_variants/ui.rb
91
+ - lib/active_storage/async_variants/ui/version.rb
92
+ - log/.gitkeep
93
+ homepage: https://github.com/botandrose/active_storage-async_variants-ui
94
+ licenses:
95
+ - MIT
96
+ metadata:
97
+ homepage_uri: https://github.com/botandrose/active_storage-async_variants-ui
98
+ source_code_uri: https://github.com/botandrose/active_storage-async_variants-ui
99
+ changelog_uri: https://github.com/botandrose/active_storage-async_variants-ui/blob/master/CHANGELOG.md
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: 3.3.0
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubygems_version: 3.6.2
115
+ specification_version: 4
116
+ summary: 'Turbo-frame UI for active_storage-async_variants: image_tag/video_tag rendering,
117
+ progress bars, and retry.'
118
+ test_files: []