add per-colour product images and gallery colour filtering

Tag product images with their colour during sync (both Printful and
Printify providers). Printify images are cherry-picked: hero colour
keeps all angles, other colours keep front + back only. Printful
MockupEnricher now generates mockups per colour from the
color_variant_map.

PDP gallery filters by the selected colour, falling back to all
images when the selected colour has none. Fix option name mismatch
(Printify "Colors" vs variant "Color") by singularizing in
Product.option_types.

Generator creates multi-colour apparel products so mock data matches
real sync behaviour.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-15 23:21:22 +00:00
parent 29d8839ac2
commit daa6d3de71
13 changed files with 375 additions and 146 deletions

View File

@@ -66,7 +66,8 @@ defmodule SimpleshopTheme.Mockups.Generator do
category: "Apparel",
artwork_url: unsplash_download_url("EhvMzMRO4_o"),
product_type: :tshirt,
price: 2999
price: 2999,
colors: ["Black", "White", "Sport Grey", "Forest Green"]
},
%{
name: "Forest Light Hoodie",
@@ -74,7 +75,8 @@ defmodule SimpleshopTheme.Mockups.Generator do
category: "Apparel",
artwork_url: unsplash_download_url("FwVkxITt8Bg"),
product_type: :hoodie,
price: 4499
price: 4499,
colors: ["Dark Heather", "Navy", "Forest Green", "Sand"]
},
%{
name: "Wildflower Meadow Tote Bag",
@@ -275,6 +277,10 @@ defmodule SimpleshopTheme.Mockups.Generator do
@doc """
Create a product with the uploaded artwork.
When the product definition includes a `colors` list, enables one variant
per colour (picking a middle size for each). Printify generates mockup
images for every enabled colour automatically.
"""
def create_product(
shop_id,
@@ -286,17 +292,17 @@ defmodule SimpleshopTheme.Mockups.Generator do
print_provider_id,
variants
) do
# Get the first variant for simplicity (typically a standard size/color)
variant = hd(variants)
variant_id = variant["id"]
selected_variants = select_variants(variants, product_def)
# Get placeholder info
IO.puts(" Enabling #{length(selected_variants)} variant(s)")
# Use the first selected variant for placeholder/scale calculations
variant = hd(selected_variants)
placeholders = variant["placeholders"] || []
front_placeholder =
Enum.find(placeholders, fn p -> p["position"] == "front" end) || hd(placeholders)
# Extract placeholder dimensions and calculate cover scale
placeholder_width = front_placeholder["width"]
placeholder_height = front_placeholder["height"]
@@ -307,32 +313,25 @@ defmodule SimpleshopTheme.Mockups.Generator do
" Scale calculation: artwork #{image_width}x#{image_height}, placeholder #{placeholder_width}x#{placeholder_height} -> scale #{Float.round(scale, 3)}"
)
variant_ids = Enum.map(selected_variants, & &1["id"])
product_data = %{
title: product_def.name,
description: "#{product_def.name} - Nature-inspired design from Wildprint Studio",
blueprint_id: blueprint_id,
print_provider_id: print_provider_id,
variants: [
%{
id: variant_id,
price: product_def.price,
is_enabled: true
}
],
variants:
Enum.map(selected_variants, fn v ->
%{id: v["id"], price: product_def.price, is_enabled: true}
end),
print_areas: [
%{
variant_ids: [variant_id],
variant_ids: variant_ids,
placeholders: [
%{
position: front_placeholder["position"] || "front",
images: [
%{
id: image_id,
x: 0.5,
y: 0.5,
scale: scale,
angle: 0
}
%{id: image_id, x: 0.5, y: 0.5, scale: scale, angle: 0}
]
}
]
@@ -343,6 +342,33 @@ defmodule SimpleshopTheme.Mockups.Generator do
Client.create_product(shop_id, product_data)
end
# Pick one variant per requested colour (middle size), or fall back to hd.
defp select_variants(variants, %{colors: colors}) when is_list(colors) and colors != [] do
# Group variants by the colour portion of their title ("Dark Heather / L" → "Dark Heather")
by_color =
Enum.group_by(variants, fn v ->
v["title"] |> to_string() |> String.split(" / ") |> hd() |> String.trim()
end)
selected =
Enum.flat_map(colors, fn color ->
case Map.get(by_color, color) do
nil ->
IO.puts(" Warning: colour #{inspect(color)} not found in blueprint variants")
[]
color_variants ->
# Pick the middle variant (typically a medium size)
mid = div(length(color_variants), 2)
[Enum.at(color_variants, mid)]
end
end)
if selected == [], do: [hd(variants)], else: selected
end
defp select_variants(variants, _product_def), do: [hd(variants)]
@doc """
Extract mockup image URLs from a created product.
"""