feat: add product image download pipeline for PageSpeed 100%
Downloads Printify CDN images via ImageDownloadWorker, processes through Media pipeline (WebP conversion, AVIF/WebP variant generation), and links to ProductImage via new image_id FK. - Add image_id to product_images table - ImageDownloadWorker downloads and processes external images - sync_product_images preserves image_id when URL unchanged - PreviewData uses local images for responsive <picture> elements - VariantCache enqueues pending downloads on startup - mix simpleshop.download_images backfill task Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,16 +19,24 @@
|
||||
|
||||
<main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<.breadcrumb
|
||||
items={[
|
||||
%{label: "Home", page: "home", href: "/"},
|
||||
%{
|
||||
label: @product.category,
|
||||
page: "collection",
|
||||
href:
|
||||
"/collections/#{@product.category |> String.downcase() |> String.replace(" ", "-")}"
|
||||
},
|
||||
%{label: @product.name, current: true}
|
||||
]}
|
||||
items={
|
||||
[
|
||||
%{label: "Home", page: "home", href: "/"}
|
||||
] ++
|
||||
if @product.category do
|
||||
[
|
||||
%{
|
||||
label: @product.category,
|
||||
page: "collection",
|
||||
href:
|
||||
"/collections/#{@product.category |> String.downcase() |> String.replace(" ", "-")}"
|
||||
}
|
||||
]
|
||||
else
|
||||
[]
|
||||
end ++
|
||||
[%{label: @product.name, current: true}]
|
||||
}
|
||||
mode={@mode}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1404,7 +1404,8 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
||||
|
||||
src =
|
||||
if image_id do
|
||||
"/images/#{image_id}/variant"
|
||||
# Trailing slash so build_srcset produces /images/{id}/variant/800.webp
|
||||
"/images/#{image_id}/variant/"
|
||||
else
|
||||
image_url
|
||||
end
|
||||
@@ -4012,10 +4013,15 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
||||
available = Optimizer.applicable_widths(assigns.source_width)
|
||||
default_width = Enum.max(available)
|
||||
|
||||
# Database images end with / (e.g., /images/{id}/variant/)
|
||||
# Mockups use - separator (e.g., /mockups/product-1)
|
||||
separator = if String.ends_with?(assigns.src, "/"), do: "", else: "-"
|
||||
|
||||
assigns =
|
||||
assigns
|
||||
|> assign(:available_widths, available)
|
||||
|> assign(:default_width, default_width)
|
||||
|> assign(:separator, separator)
|
||||
|
||||
~H"""
|
||||
<picture>
|
||||
@@ -4030,7 +4036,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
||||
sizes={@sizes}
|
||||
/>
|
||||
<img
|
||||
src={"#{@src}-#{@default_width}.jpg"}
|
||||
src={"#{@src}#{@separator}#{@default_width}.jpg"}
|
||||
srcset={build_srcset(@src, @available_widths, "jpg")}
|
||||
sizes={@sizes}
|
||||
alt={@alt}
|
||||
@@ -4046,9 +4052,13 @@ defmodule SimpleshopThemeWeb.ShopComponents do
|
||||
end
|
||||
|
||||
defp build_srcset(base, widths, format) do
|
||||
# Database images end with / (e.g., /images/{id}/variant/)
|
||||
# Mockups use - separator (e.g., /mockups/product-1)
|
||||
separator = if String.ends_with?(base, "/"), do: "", else: "-"
|
||||
|
||||
widths
|
||||
|> Enum.sort()
|
||||
|> Enum.map(&"#{base}-#{&1}.#{format} #{&1}w")
|
||||
|> Enum.map(&"#{base}#{separator}#{&1}.#{format} #{&1}w")
|
||||
|> Enum.join(", ")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,13 +34,11 @@ defmodule SimpleshopThemeWeb.ShopLive.ProductShow do
|
||||
|> Enum.reject(fn p -> p.id == product.id end)
|
||||
|> Enum.take(4)
|
||||
|
||||
# Build gallery images (filter out nils)
|
||||
# Build gallery images from local image_id or external URL
|
||||
gallery_images =
|
||||
[
|
||||
product.image_url,
|
||||
product.hover_image_url,
|
||||
product.image_url,
|
||||
product.hover_image_url
|
||||
image_src(product[:image_id], product[:image_url]),
|
||||
image_src(product[:hover_image_id], product[:hover_image_url])
|
||||
]
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|
||||
@@ -70,6 +68,14 @@ defmodule SimpleshopThemeWeb.ShopLive.ProductShow do
|
||||
List.first(products)
|
||||
end
|
||||
|
||||
# Build image source URL - prefer local image_id, fall back to external URL
|
||||
defp image_src(image_id, _url) when is_binary(image_id) do
|
||||
"/images/#{image_id}/variant/1200.webp"
|
||||
end
|
||||
|
||||
defp image_src(_, url) when is_binary(url), do: url
|
||||
defp image_src(_, _), do: nil
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
|
||||
Reference in New Issue
Block a user