diff --git a/Gemfile b/Gemfile index ffff6fe9..27e3a8bd 100644 --- a/Gemfile +++ b/Gemfile @@ -47,6 +47,7 @@ gem "commonmarker", require: false gem "invisible_captcha" # Unobtrusive and flexible spam protection for Rails apps [https://github.com/markets/invisible_captcha] gem "color_conversion" # A ruby gem to perform color conversions [https://github.com/devrieda/color_conversion] gem "meta-tags" # Search Engine Optimization (SEO) for Ruby on Rails applications. [https://github.com/kpumuk/meta-tags] +gem "fastimage", require: false # FastImage finds the size or type of an image given its uri by fetching as little as needed [https://github.com/sdsykes/fastimage] gem "bootsnap", require: false # Reduces boot times through caching; required in config/boot.rb [https://github.com/Shopify/bootsnap] diff --git a/Gemfile.lock b/Gemfile.lock index 4ce7bd87..9cf01823 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -223,6 +223,7 @@ GEM multipart-post (~> 2) faraday-net_http (3.4.0) net-http (>= 0.5.0) + fastimage (2.3.1) ferrum (0.15) addressable (~> 2.5) concurrent-ruby (~> 1.1) @@ -640,6 +641,7 @@ DEPENDENCIES erb_lint factory_bot_rails faker + fastimage flipper flipper-active_record flipper-ui diff --git a/app/assets/images/app-icons/apple-touch-icon-local.webp b/app/assets/images/app-icons/apple-touch-icon-local.webp new file mode 100644 index 00000000..6098a976 Binary files /dev/null and b/app/assets/images/app-icons/apple-touch-icon-local.webp differ diff --git a/app/assets/images/app-icons/apple-touch-icon.webp b/app/assets/images/app-icons/apple-touch-icon.webp new file mode 100644 index 00000000..6098a976 Binary files /dev/null and b/app/assets/images/app-icons/apple-touch-icon.webp differ diff --git a/app/assets/images/app-icons/icon-192-maskable.webp b/app/assets/images/app-icons/icon-192-maskable.webp new file mode 100644 index 00000000..2d9736a1 Binary files /dev/null and b/app/assets/images/app-icons/icon-192-maskable.webp differ diff --git a/app/assets/images/app-icons/icon-192.webp b/app/assets/images/app-icons/icon-192.webp new file mode 100644 index 00000000..d9440597 Binary files /dev/null and b/app/assets/images/app-icons/icon-192.webp differ diff --git a/app/assets/images/app-icons/icon-512-maskable.webp b/app/assets/images/app-icons/icon-512-maskable.webp new file mode 100644 index 00000000..82945fdf Binary files /dev/null and b/app/assets/images/app-icons/icon-512-maskable.webp differ diff --git a/app/assets/images/app-icons/icon-512.webp b/app/assets/images/app-icons/icon-512.webp new file mode 100644 index 00000000..3d7a5b5e Binary files /dev/null and b/app/assets/images/app-icons/icon-512.webp differ diff --git a/app/assets/images/app-icons/icon-64.webp b/app/assets/images/app-icons/icon-64.webp new file mode 100644 index 00000000..048de493 Binary files /dev/null and b/app/assets/images/app-icons/icon-64.webp differ diff --git a/app/assets/images/app-icons/noun-tears-of-joy-3555562-80ffea-7960F8.webp b/app/assets/images/app-icons/noun-tears-of-joy-3555562-80ffea-7960F8.webp new file mode 100644 index 00000000..d24477d4 Binary files /dev/null and b/app/assets/images/app-icons/noun-tears-of-joy-3555562-80ffea-7960F8.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/chrome-devtools-manifest.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/chrome-devtools-manifest.webp new file mode 100644 index 00000000..b0641877 Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/chrome-devtools-manifest.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/hot-air-balloon.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/hot-air-balloon.webp index 800eee8d..ed9adfca 100644 Binary files a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/hot-air-balloon.webp and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/hot-air-balloon.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-192-maskable.png b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-192-maskable.png new file mode 100644 index 00000000..a1a2d312 Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-192-maskable.png differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-192-maskable.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-192-maskable.webp new file mode 100644 index 00000000..2d9736a1 Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-192-maskable.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-192.png b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-192.png new file mode 100644 index 00000000..d6a559cb Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-192.png differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-192.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-192.webp new file mode 100644 index 00000000..d9440597 Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-192.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-512-maskable.png b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-512-maskable.png new file mode 100644 index 00000000..b550e18b Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-512-maskable.png differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-512-maskable.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-512-maskable.webp new file mode 100644 index 00000000..82945fdf Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-512-maskable.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-512.png b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-512.png new file mode 100644 index 00000000..a622be00 Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-512.png differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-512.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-512.webp new file mode 100644 index 00000000..3d7a5b5e Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/icon-512.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/install-prompt-edge-opt.jpg b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/install-prompt-edge-opt.jpg new file mode 100644 index 00000000..e54a7e81 Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/install-prompt-edge-opt.jpg differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/install-prompt-safari.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/install-prompt-safari.webp new file mode 100644 index 00000000..90d81be6 Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/install-prompt-safari.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/launchpad-opt.jpg b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/launchpad-opt.jpg new file mode 100644 index 00000000..4926839e Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/launchpad-opt.jpg differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/localhost-address-bar.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/localhost-address-bar.webp new file mode 100644 index 00000000..75ef9e5f Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/localhost-address-bar.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/maskable-icons.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/maskable-icons.webp new file mode 100644 index 00000000..8fd35fa9 Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/maskable-icons.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/pwa-campfire.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/pwa-campfire.webp new file mode 100644 index 00000000..08c9d644 Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/pwa-campfire.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/pwa-discourse.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/pwa-discourse.webp new file mode 100644 index 00000000..eafbff92 Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/pwa-discourse.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/pwa-feedbin.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/pwa-feedbin.webp new file mode 100644 index 00000000..4651db39 Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/pwa-feedbin.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/pwa-hatchbox.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/pwa-hatchbox.webp new file mode 100644 index 00000000..51e98565 Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/pwa-hatchbox.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/ruby-app-icon-archived.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/ruby-app-icon-archived.webp new file mode 100644 index 00000000..36853c5a Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/ruby-app-icon-archived.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/ruby-app-icon.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/ruby-app-icon.webp index efde2a4c..2f158964 100644 Binary files a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/ruby-app-icon.webp and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/ruby-app-icon.webp differ diff --git a/app/assets/images/articles/add-your-rails-app-to-the-home-screen/standalone.webp b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/standalone.webp new file mode 100644 index 00000000..e1483b4a Binary files /dev/null and b/app/assets/images/articles/add-your-rails-app-to-the-home-screen/standalone.webp differ diff --git a/app/assets/images/articles/custom-color-schemes-with-ruby-on-rails/rainbows.webp b/app/assets/images/articles/custom-color-schemes-with-ruby-on-rails/rainbows.webp index 6c51115e..8634886c 100644 Binary files a/app/assets/images/articles/custom-color-schemes-with-ruby-on-rails/rainbows.webp and b/app/assets/images/articles/custom-color-schemes-with-ruby-on-rails/rainbows.webp differ diff --git a/app/assets/images/articles/custom-color-schemes-with-ruby-on-rails/screenshot.webp b/app/assets/images/articles/custom-color-schemes-with-ruby-on-rails/screenshot.webp index 749d1c51..05e965ad 100644 Binary files a/app/assets/images/articles/custom-color-schemes-with-ruby-on-rails/screenshot.webp and b/app/assets/images/articles/custom-color-schemes-with-ruby-on-rails/screenshot.webp differ diff --git a/app/assets/images/articles/how-to-render-css-dynamically-in-rails/style-rainbow.webp b/app/assets/images/articles/how-to-render-css-dynamically-in-rails/style-rainbow.webp index c0659961..f1229eae 100644 Binary files a/app/assets/images/articles/how-to-render-css-dynamically-in-rails/style-rainbow.webp and b/app/assets/images/articles/how-to-render-css-dynamically-in-rails/style-rainbow.webp differ diff --git a/app/assets/images/articles/introducing-joy-of-rails/screenshot-opt.jpg b/app/assets/images/articles/introducing-joy-of-rails/screenshot-opt.jpg new file mode 100644 index 00000000..c66e25be Binary files /dev/null and b/app/assets/images/articles/introducing-joy-of-rails/screenshot-opt.jpg differ diff --git a/app/assets/images/articles/mastering-custom-configuration-in-rails/ice-cream.webp b/app/assets/images/articles/mastering-custom-configuration-in-rails/ice-cream.webp index 3a49d45c..afcc2208 100644 Binary files a/app/assets/images/articles/mastering-custom-configuration-in-rails/ice-cream.webp and b/app/assets/images/articles/mastering-custom-configuration-in-rails/ice-cream.webp differ diff --git a/app/assets/images/articles/web-push-notifications-from-rails/airplane.webp b/app/assets/images/articles/web-push-notifications-from-rails/airplane.webp index 1cc0496d..d607370e 100644 Binary files a/app/assets/images/articles/web-push-notifications-from-rails/airplane.webp and b/app/assets/images/articles/web-push-notifications-from-rails/airplane.webp differ diff --git a/app/assets/images/articles/web-push-notifications-from-rails/push-notifications-high-level.webp b/app/assets/images/articles/web-push-notifications-from-rails/push-notifications-high-level.webp new file mode 100644 index 00000000..5ab7ed21 Binary files /dev/null and b/app/assets/images/articles/web-push-notifications-from-rails/push-notifications-high-level.webp differ diff --git a/app/assets/images/articles/what-you-need-to-know-about-sqlite/feather.webp b/app/assets/images/articles/what-you-need-to-know-about-sqlite/feather.webp index 44d105df..485901b9 100644 Binary files a/app/assets/images/articles/what-you-need-to-know-about-sqlite/feather.webp and b/app/assets/images/articles/what-you-need-to-know-about-sqlite/feather.webp differ diff --git a/app/assets/images/articles/what-you-need-to-know-about-sqlite/mountain-range.webp b/app/assets/images/articles/what-you-need-to-know-about-sqlite/mountain-range.webp new file mode 100644 index 00000000..ab625689 Binary files /dev/null and b/app/assets/images/articles/what-you-need-to-know-about-sqlite/mountain-range.webp differ diff --git a/app/assets/images/articles/what-you-need-to-know-about-sqlite/scaling.webp b/app/assets/images/articles/what-you-need-to-know-about-sqlite/scaling.webp new file mode 100644 index 00000000..a73fa8c2 Binary files /dev/null and b/app/assets/images/articles/what-you-need-to-know-about-sqlite/scaling.webp differ diff --git a/app/assets/images/meta/deployment/hatchbox/conversation-bundler-config.webp b/app/assets/images/meta/deployment/hatchbox/conversation-bundler-config.webp new file mode 100644 index 00000000..53cab8b1 Binary files /dev/null and b/app/assets/images/meta/deployment/hatchbox/conversation-bundler-config.webp differ diff --git a/app/assets/images/meta/deployment/hatchbox/conversation-pwa.webp b/app/assets/images/meta/deployment/hatchbox/conversation-pwa.webp new file mode 100644 index 00000000..5e77d3a7 Binary files /dev/null and b/app/assets/images/meta/deployment/hatchbox/conversation-pwa.webp differ diff --git a/app/assets/images/meta/deployment/hatchbox/conversation-systemd-1.webp b/app/assets/images/meta/deployment/hatchbox/conversation-systemd-1.webp new file mode 100644 index 00000000..db36f355 Binary files /dev/null and b/app/assets/images/meta/deployment/hatchbox/conversation-systemd-1.webp differ diff --git a/app/assets/images/meta/deployment/hatchbox/conversation-systemd-2.webp b/app/assets/images/meta/deployment/hatchbox/conversation-systemd-2.webp new file mode 100644 index 00000000..e736eae9 Binary files /dev/null and b/app/assets/images/meta/deployment/hatchbox/conversation-systemd-2.webp differ diff --git a/app/assets/images/meta/deployment/hatchbox/hatchbox.webp b/app/assets/images/meta/deployment/hatchbox/hatchbox.webp index 5c80cd18..90a368e2 100644 Binary files a/app/assets/images/meta/deployment/hatchbox/hatchbox.webp and b/app/assets/images/meta/deployment/hatchbox/hatchbox.webp differ diff --git a/app/assets/images/meta/deployment/hatchbox/homepage.webp b/app/assets/images/meta/deployment/hatchbox/homepage.webp new file mode 100644 index 00000000..40c82dcd Binary files /dev/null and b/app/assets/images/meta/deployment/hatchbox/homepage.webp differ diff --git a/app/assets/images/screenshots/color-schemes-narrow-opt.jpg b/app/assets/images/screenshots/color-schemes-narrow-opt.jpg new file mode 100644 index 00000000..29f1507a Binary files /dev/null and b/app/assets/images/screenshots/color-schemes-narrow-opt.jpg differ diff --git a/app/assets/images/screenshots/color-schemes-opt.jpg b/app/assets/images/screenshots/color-schemes-opt.jpg new file mode 100644 index 00000000..b3652ede Binary files /dev/null and b/app/assets/images/screenshots/color-schemes-opt.jpg differ diff --git a/app/assets/images/screenshots/homepage-narrow-opt.jpg b/app/assets/images/screenshots/homepage-narrow-opt.jpg new file mode 100644 index 00000000..af2a0ea4 Binary files /dev/null and b/app/assets/images/screenshots/homepage-narrow-opt.jpg differ diff --git a/app/assets/images/screenshots/homepage.webp b/app/assets/images/screenshots/homepage.webp new file mode 100644 index 00000000..bb286b07 Binary files /dev/null and b/app/assets/images/screenshots/homepage.webp differ diff --git a/app/content/pages/articles/add-your-rails-app-to-the-home-screen.html.mdrb b/app/content/pages/articles/add-your-rails-app-to-the-home-screen.html.mdrb index 93a1e39a..27290e1f 100644 --- a/app/content/pages/articles/add-your-rails-app-to-the-home-screen.html.mdrb +++ b/app/content/pages/articles/add-your-rails-app-to-the-home-screen.html.mdrb @@ -68,8 +68,7 @@ You can also view installation instructions for various browsers below: <%= turbo_frame_tag "pwa_installation_instructions", src: pwa_installation_instructions_path %> -If it worked, you should be able to open Joy of Rails as a standalone application. Voilà! -![Screenshot of Joy of Rails in its standalone form](articles/add-your-rails-app-to-the-home-screen/standalone.jpg) +If it worked, you should be able to open Joy of Rails as a standalone application. Voilà! ![Screenshot of Joy of Rails in its standalone form](articles/add-your-rails-app-to-the-home-screen/standalone.jpg) ## What does it mean for a web app to be installable? @@ -246,7 +245,7 @@ end As mentioned previously, you should have, at minimum, two square images to represent your app icon on install screens: `192x192` and `512x512`. If you started from a new Rails 7.2+ app, you‘ll want to remove the generated app icon from the Rails app generator and make your own. Here are the app icons for Joy of Rails: -![Joy of Rails app icon 192x192](app-icons/icon-192.png 'Joy of Rails app icon 192x192') ![Joy of Rails app icon 512x512](app-icons/icon-512.png 'Joy of Rails app icon 512x512') +![Joy of Rails app icon 192x192](articles/add-your-rails-app-to-the-home-screen/icon-192.png 'Joy of Rails app icon 192x192') ![Joy of Rails app icon 512x512](articles/add-your-rails-app-to-the-home-screen/icon-512.png 'Joy of Rails app icon 512x512') I used ImageMagick 7 to convert these icons from the original with a command like: @@ -270,7 +269,7 @@ Some browsers will present the icon in a circular window that will crop a signif ![A masked icon](articles/add-your-rails-app-to-the-home-screen/masked-icon-bad.jpg 'A not-so-nice masked icon') My approach is to use a separate set of icons with more room to breathe. Compare the two variation: -![Primary app icon](app-icons/icon-192.png 'Primary app icon') ![Maskable app icon](app-icons/icon-192-maskable.png 'Maskable app icon') +![Primary app icon](articles/add-your-rails-app-to-the-home-screen/icon-192.png 'Primary app icon') ![Maskable app icon](articles/add-your-rails-app-to-the-home-screen/icon-192-maskable.png 'Maskable app icon') The new icon a lot better when masked: ![A masked icon](articles/add-your-rails-app-to-the-home-screen/masked-icon-good.jpg 'A nice masked icon') diff --git a/app/views/components/content/image.rb b/app/views/components/content/image.rb new file mode 100644 index 00000000..95b37184 --- /dev/null +++ b/app/views/components/content/image.rb @@ -0,0 +1,53 @@ +module Content + class Image < ApplicationComponent + attr_reader :src, :alt, :title, :attributes + + def initialize(src, alt: "", title: "", **attributes) + @src = src + @alt = alt.presence || File.basename(src, ".*").humanize + @title = title + @attributes = attributes + end + + def view_template + figure do + img(alt:, loading: "lazy", **image_attributes, **attributes) + figcaption { title } if title.present? + end + end + + private + + def asset_path(*) = helpers.asset_path(*) + + def image_attributes + if webp_src? + { + src: asset_path(webp_src) + } + elsif optimized_src? + { + src: asset_path(optimized_src) + } + else + { + src: asset_path(src) + } + end + end + + def webp_src? = File.exist? Rails.root.join("app", "assets", "images", webp_src) + + def webp_src = "#{dirname}/#{basename}.webp" + + def optimized_src? = File.exist? Rails.root.join("app", "assets", "images", optimized_src) + + def optimized_src = "#{dirname}/#{basename}-opt.#{ext}" + + def ext = File.extname(src) + + def basename = File.basename(src, ".*") + + def dirname = File.dirname(src) + end +end diff --git a/app/views/components/markdown/article.rb b/app/views/components/markdown/article.rb index c243f340..be2203d9 100644 --- a/app/views/components/markdown/article.rb +++ b/app/views/components/markdown/article.rb @@ -21,10 +21,8 @@ def code_block(source, metadata = "", **attributes) def image(src, alt: "", title: "") title, json_attributes = parse_text_and_metadata(title, separator: "|") - figure(**json_attributes) do - image_tag(src, alt: alt, title: title, loading: "lazy") - figcaption { title } - end + + render ::Content::Image.new(src, alt: alt, title: title, **json_attributes) end private diff --git a/app/views/components/pages/card.rb b/app/views/components/pages/card.rb index 28896359..bd68c341 100644 --- a/app/views/components/pages/card.rb +++ b/app/views/components/pages/card.rb @@ -13,12 +13,12 @@ def initialize(title:, description:, image:, request_path:) def view_template article(class: "") do a(href: request_path, class: "block") do - figure(class: "page-card--image w-full") do - image_tag @image || "https://placehold.co/640x360?text=#{title}", - alt: "#{File.basename(@image, ".*").humanize} illustration", - class: "w-full rounded-2xl object-cover aspect-[16/9]", - loading: "lazy" - end + render Content::Image.new \ + image || "https://placehold.co/640x360?text=#{title}", + alt: "#{File.basename(image, ".*").humanize} illustration", + title: nil, + loading: "lazy", + class: "w-full rounded-2xl object-cover aspect-[16/9]" end div(class: "max-w-xl") do h3( diff --git a/app/views/components/pages/summary.rb b/app/views/components/pages/summary.rb index fd4a5e21..e0d92b80 100644 --- a/app/views/components/pages/summary.rb +++ b/app/views/components/pages/summary.rb @@ -55,11 +55,11 @@ def content(**) def figure_image(**) div(**) do - figure(class: "page-summary--image") do - image_tag image, - alt: "#{File.basename(image, ".*").humanize} illustration", - class: "w-full object-cover aspect-[2/1] lg:aspect-[3/2]" - end + render Content::Image.new(image, + alt: "#{File.basename(image, ".*").humanize} illustration", + title: nil, + loading: nil, + class: "w-full object-cover aspect-[2/1] lg:aspect-[3/2]") rescue ActionView::Template::Error end diff --git a/script/convert-webp b/script/convert-webp index 04ac2370..847d2204 100755 --- a/script/convert-webp +++ b/script/convert-webp @@ -1,13 +1,127 @@ #!/usr/bin/env ruby +# ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +# bundle_binstub = File.expand_path("bundle", __dir__) + +# if File.file?(bundle_binstub) +# if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") +# load(bundle_binstub) +# else +# abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +# Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") +# end +# end + +# require "rubygems" +# require "bundler/setup" + +require "fastimage" + +force = false + +class ImageFile + def exists? + File.exist?(file) + end + + attr_reader :file + def initialize(file) + @file = file + end + + def to_s = file + + def to_str = file + + def as(*suffixes) + ext = suffixes.pop + ImageFile.new("#{dirname}/#{([basename] + suffixes).join("-")}.#{ext}") + end + + def as_webp = as("webp") + + def as_opt_jpg = as("opt", "jpg") + + def ext = File.extname(file) + + def basename = File.basename(file, ".*") + + def dirname = File.dirname(file) +end + +module Cmd + def self.run(cmd) + puts cmd + system cmd + end + + def self.webp(img, force: false, quality: 90, tries: 0) + dest = img.as_webp.to_s + + if force || !File.exist?(dest) + cmd = "magick #{img}" + + opts = ["-define webp:method=6", "-quality #{quality}"] + + # Resize if width is specified and image is larger + opts << "-resize 1200x" if FastImage.size(img.to_s)[0] > 1200 + + # Lossless compression for images < 96kB + if File.size?(img) && File.size(img) < 98_304 # 96kB + opts << "-define webp:lossless=true" + end + + opts << dest + + run "#{cmd} #{opts.join(" ")}" + + if File.size(dest) > 131_072 && # 128kB + tries < 8 + webp(img, force: true, quality: quality - 5, tries: tries + 1) + end + end + end + + def self.jpg(img, force: false, quality: 90, tries: 0) + dest = img.as_opt_jpg.to_s + if force || !File.exist?(dest) + run "magick #{img} -strip -interlace Plane -quality #{quality}% #{dest}" + + if File.size(dest) > 131_072 && # 128kB + tries < 8 + jpg(img, force: true, quality: quality - 5, tries: tries + 1) + end + end + end +end + ARGV.each do |file| - basename = File.basename(file, ".*") - dir = File.dirname(file) + Dir.glob(file).each do |file| + img = ImageFile.new(file) + next unless %w[.jpg .jpeg .png].include?(img.ext) + next if img.to_s.include?("-opt.jpg") + Cmd.webp img, force: force + end + + Dir.glob(file).each do |file| + img = ImageFile.new(file) + next unless %w[.jpg .jpeg].include?(img.ext) + next if img.to_s.include?("-opt.jpg") + next if File.size(img) < 32_768 # 32kB + Cmd.jpg img, force: force + end - webp = "#{dir}/#{basename}.webp" + Dir.glob(file).each do |file| + img = ImageFile.new(file) - cmd = "magick #{file} -quality 50 -define webp:lossless=true -resize 960x480 #{webp}" + files = [img.as_webp.to_s, img.as_opt_jpg.to_s].filter { |f| File.exist?(f) } - puts cmd - system cmd + smallest = files.min { |a, b| File.size(a) <=> File.size(b) } + files.each do |f| + if f != smallest || File.size(f) > File.size(img.to_s) + File.delete(f) + end + end + end end