switch mockup generator to UK print providers and add --replace flag
- Prefer Print Clever (72) for canvas, Monster Digital (29) for apparel and mugs where available, fall back to default providers - Rename Art Print products to Canvas (new Satin Canvas blueprint) - Add Canvas Prints category in Printify tag extraction - Add --replace/-r flag to purge existing Printify products before generating (with interactive confirmation) - Add purge_all_products/1 to generator module - Remove old art print mockups, add new canvas + extra provider mockups Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ -16,6 +16,9 @@ defmodule Mix.Tasks.GenerateMockups do
|
||||
# Generate mockups (keeps products in Printify)
|
||||
mix generate_mockups
|
||||
|
||||
# Delete existing products first, then generate fresh ones
|
||||
mix generate_mockups --replace
|
||||
|
||||
# Generate mockups and delete products afterwards
|
||||
mix generate_mockups --cleanup
|
||||
|
||||
@ -45,12 +48,14 @@ defmodule Mix.Tasks.GenerateMockups do
|
||||
OptionParser.parse(args,
|
||||
switches: [
|
||||
cleanup: :boolean,
|
||||
replace: :boolean,
|
||||
search: :string,
|
||||
list_blueprints: :boolean,
|
||||
help: :boolean
|
||||
],
|
||||
aliases: [
|
||||
c: :cleanup,
|
||||
r: :replace,
|
||||
s: :search,
|
||||
l: :list_blueprints,
|
||||
h: :help
|
||||
@ -84,6 +89,7 @@ defmodule Mix.Tasks.GenerateMockups do
|
||||
mix generate_mockups [options]
|
||||
|
||||
Options:
|
||||
--replace, -r Delete ALL existing products from Printify before generating (with confirmation)
|
||||
--cleanup, -c Delete created products from Printify after downloading mockups
|
||||
--search, -s TERM Search for blueprints by name
|
||||
--list-blueprints List all available blueprint IDs and names
|
||||
@ -149,12 +155,14 @@ defmodule Mix.Tasks.GenerateMockups do
|
||||
|
||||
defp generate_mockups(opts) do
|
||||
cleanup = Keyword.get(opts, :cleanup, false)
|
||||
replace = Keyword.get(opts, :replace, false)
|
||||
|
||||
Mix.shell().info("""
|
||||
|
||||
╔═══════════════════════════════════════════╗
|
||||
║ Printify Mockup Generator ║
|
||||
╠═══════════════════════════════════════════╣
|
||||
║ Replace mode: #{if replace, do: "ON ", else: "OFF"} ║
|
||||
║ Cleanup mode: #{if cleanup, do: "ON ", else: "OFF"} ║
|
||||
╚═══════════════════════════════════════════╝
|
||||
|
||||
@ -177,6 +185,8 @@ defmodule Mix.Tasks.GenerateMockups do
|
||||
""")
|
||||
|
||||
_token ->
|
||||
if replace, do: purge_existing_products()
|
||||
|
||||
results = MockupGenerator.generate_all(cleanup: cleanup)
|
||||
|
||||
# Summary
|
||||
@ -200,4 +210,27 @@ defmodule Mix.Tasks.GenerateMockups do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp purge_existing_products do
|
||||
alias SimpleshopTheme.Clients.Printify, as: Client
|
||||
|
||||
Mix.shell().info("Fetching existing products...")
|
||||
|
||||
{:ok, shop_id} = Client.get_shop_id()
|
||||
{:ok, %{"data" => products}} = Client.list_products(shop_id)
|
||||
count = length(products)
|
||||
|
||||
if count == 0 do
|
||||
Mix.shell().info("No existing products to delete.\n")
|
||||
else
|
||||
Mix.shell().info("Found #{count} existing products in Printify.")
|
||||
|
||||
if Mix.shell().yes?("Delete all #{count} products before generating new ones?") do
|
||||
deleted = MockupGenerator.purge_all_products(shop_id)
|
||||
Mix.shell().info("Deleted #{deleted} products.\n")
|
||||
else
|
||||
Mix.shell().info("Skipping purge.\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -21,43 +21,43 @@ defmodule SimpleshopTheme.Mockups.Generator do
|
||||
def product_definitions do
|
||||
[
|
||||
%{
|
||||
name: "Mountain Sunrise Art Print",
|
||||
slug: "mountain-sunrise-print",
|
||||
category: "Art Prints",
|
||||
name: "Mountain Sunrise Canvas",
|
||||
slug: "mountain-sunrise-canvas",
|
||||
category: "Canvas Prints",
|
||||
artwork_url: unsplash_download_url("UweNcthlmDc"),
|
||||
product_type: :poster,
|
||||
product_type: :canvas,
|
||||
price: 2400
|
||||
},
|
||||
%{
|
||||
name: "Ocean Waves Art Print",
|
||||
slug: "ocean-waves-print",
|
||||
category: "Art Prints",
|
||||
name: "Ocean Waves Canvas",
|
||||
slug: "ocean-waves-canvas",
|
||||
category: "Canvas Prints",
|
||||
artwork_url: unsplash_download_url("XRhUTUVuXAE"),
|
||||
product_type: :poster,
|
||||
product_type: :canvas,
|
||||
price: 2400
|
||||
},
|
||||
%{
|
||||
name: "Wildflower Meadow Art Print",
|
||||
slug: "wildflower-meadow-print",
|
||||
category: "Art Prints",
|
||||
name: "Wildflower Meadow Canvas",
|
||||
slug: "wildflower-meadow-canvas",
|
||||
category: "Canvas Prints",
|
||||
artwork_url: unsplash_download_url("QvjL4y7SF9k"),
|
||||
product_type: :poster,
|
||||
product_type: :canvas,
|
||||
price: 2400
|
||||
},
|
||||
%{
|
||||
name: "Geometric Abstract Art Print",
|
||||
slug: "geometric-abstract-print",
|
||||
category: "Art Prints",
|
||||
name: "Geometric Abstract Canvas",
|
||||
slug: "geometric-abstract-canvas",
|
||||
category: "Canvas Prints",
|
||||
artwork_url: unsplash_download_url("-6GvTDpkkPU"),
|
||||
product_type: :poster,
|
||||
product_type: :canvas,
|
||||
price: 2800
|
||||
},
|
||||
%{
|
||||
name: "Botanical Illustration Print",
|
||||
slug: "botanical-illustration-print",
|
||||
category: "Art Prints",
|
||||
name: "Botanical Illustration Canvas",
|
||||
slug: "botanical-illustration-canvas",
|
||||
category: "Canvas Prints",
|
||||
artwork_url: unsplash_download_url("FNtNIDQWUZY"),
|
||||
product_type: :poster,
|
||||
product_type: :canvas,
|
||||
price: 2400
|
||||
},
|
||||
%{
|
||||
@ -157,25 +157,20 @@ defmodule SimpleshopTheme.Mockups.Generator do
|
||||
"""
|
||||
def blueprint_config do
|
||||
%{
|
||||
# Search terms matched to Printify's actual blueprint titles (partial match)
|
||||
poster: %{blueprint_id: nil, print_provider_id: nil, search_term: "Matte Posters"},
|
||||
tshirt: %{blueprint_id: nil, print_provider_id: nil, search_term: "Softstyle T-Shirt"},
|
||||
hoodie: %{blueprint_id: nil, print_provider_id: nil, search_term: "Pullover Hoodie"},
|
||||
tote: %{blueprint_id: nil, print_provider_id: nil, search_term: "Cotton Tote Bag"},
|
||||
mug: %{blueprint_id: nil, print_provider_id: nil, search_term: "Mug 11oz"},
|
||||
cushion: %{
|
||||
blueprint_id: nil,
|
||||
print_provider_id: nil,
|
||||
search_term: "Spun Polyester Square Pillow"
|
||||
},
|
||||
blanket: %{blueprint_id: nil, print_provider_id: nil, search_term: "Sherpa Fleece Blanket"},
|
||||
notebook: %{
|
||||
blueprint_id: nil,
|
||||
print_provider_id: nil,
|
||||
search_term: "Hardcover Journal Matte"
|
||||
},
|
||||
phone_case: %{blueprint_id: nil, print_provider_id: nil, search_term: "Tough Phone Cases"},
|
||||
laptop_sleeve: %{blueprint_id: nil, print_provider_id: nil, search_term: "Laptop Sleeve"}
|
||||
# Search terms matched to Printify's actual blueprint titles (partial match).
|
||||
# preferred_provider_id selects a UK-based provider where available:
|
||||
# 29 = Monster Digital (UK) — apparel, mugs
|
||||
# 72 = Print Clever (UK) — canvas prints
|
||||
canvas: %{search_term: "Satin Canvas, Stretched", preferred_provider_id: 72},
|
||||
tshirt: %{search_term: "Softstyle T-Shirt", preferred_provider_id: 29},
|
||||
hoodie: %{search_term: "Heavy Blend™ Hooded Sweatshirt", preferred_provider_id: 29},
|
||||
tote: %{search_term: "Cotton Tote Bag", preferred_provider_id: nil},
|
||||
mug: %{search_term: "Ceramic Mug", preferred_provider_id: 29},
|
||||
cushion: %{search_term: "Spun Polyester Square Pillow", preferred_provider_id: nil},
|
||||
blanket: %{search_term: "Sherpa Fleece Blanket", preferred_provider_id: nil},
|
||||
notebook: %{search_term: "Hardcover Journal Matte", preferred_provider_id: nil},
|
||||
phone_case: %{search_term: "Tough Phone Cases", preferred_provider_id: nil},
|
||||
laptop_sleeve: %{search_term: "Laptop Sleeve", preferred_provider_id: nil}
|
||||
}
|
||||
end
|
||||
|
||||
@ -210,13 +205,19 @@ defmodule SimpleshopTheme.Mockups.Generator do
|
||||
|
||||
@doc """
|
||||
Find a suitable print provider for a blueprint.
|
||||
Prefers providers with good ratings and reasonable pricing.
|
||||
|
||||
When `preferred_provider_id` is given, uses that provider if available
|
||||
for the blueprint. Falls back to the first provider otherwise.
|
||||
"""
|
||||
def find_print_provider(blueprint_id) do
|
||||
def find_print_provider(blueprint_id, preferred_provider_id \\ nil) do
|
||||
case Client.get_print_providers(blueprint_id) do
|
||||
{:ok, providers} when is_list(providers) and providers != [] ->
|
||||
# Just pick the first provider for simplicity
|
||||
{:ok, hd(providers)}
|
||||
provider =
|
||||
if preferred_provider_id do
|
||||
Enum.find(providers, fn p -> p["id"] == preferred_provider_id end)
|
||||
end
|
||||
|
||||
{:ok, provider || hd(providers)}
|
||||
|
||||
{:ok, []} ->
|
||||
{:error, :no_providers}
|
||||
@ -384,6 +385,26 @@ defmodule SimpleshopTheme.Mockups.Generator do
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes all products from the Printify shop.
|
||||
Returns the number of products deleted.
|
||||
"""
|
||||
def purge_all_products(shop_id) do
|
||||
case Client.list_products(shop_id) do
|
||||
{:ok, %{"data" => products}} when is_list(products) ->
|
||||
Enum.each(products, fn p ->
|
||||
IO.puts(" Deleting: #{p["title"]} (#{p["id"]})")
|
||||
Client.delete_product(shop_id, p["id"])
|
||||
Process.sleep(200)
|
||||
end)
|
||||
|
||||
length(products)
|
||||
|
||||
_ ->
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generate mockups for all products.
|
||||
"""
|
||||
@ -450,7 +471,7 @@ defmodule SimpleshopTheme.Mockups.Generator do
|
||||
with {:ok, blueprint} <- find_blueprint(config.search_term),
|
||||
blueprint_id = blueprint["id"],
|
||||
_ = IO.puts(" Found blueprint: #{blueprint["title"]} (#{blueprint_id})"),
|
||||
{:ok, provider} <- find_print_provider(blueprint_id),
|
||||
{:ok, provider} <- find_print_provider(blueprint_id, config[:preferred_provider_id]),
|
||||
provider_id = provider["id"],
|
||||
_ = IO.puts(" Using provider: #{provider["title"]} (#{provider_id})"),
|
||||
{:ok, variants} <- get_variant_info(blueprint_id, provider_id),
|
||||
|
||||
@ -418,10 +418,11 @@ defmodule SimpleshopTheme.Providers.Printify do
|
||||
|
||||
cond do
|
||||
has_tag?(tags, ~w[t-shirt tshirt shirt hoodie sweatshirt apparel clothing]) -> "Apparel"
|
||||
has_tag?(tags, ~w[canvas]) -> "Canvas Prints"
|
||||
has_tag?(tags, ~w[mug cup blanket pillow cushion homeware homewares home]) -> "Homewares"
|
||||
has_tag?(tags, ~w[notebook journal stationery]) -> "Stationery"
|
||||
has_tag?(tags, ~w[phone case bag tote accessories]) -> "Accessories"
|
||||
has_tag?(tags, ~w[art print poster canvas wall]) -> "Art Prints"
|
||||
has_tag?(tags, ~w[art print poster wall]) -> "Art Prints"
|
||||
true -> List.first(raw["tags"])
|
||||
end
|
||||
end
|
||||
|
||||
BIN
priv/static/mockups/autumn-leaves-notebook-5.webp
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
priv/static/mockups/autumn-leaves-notebook-6.webp
Normal file
|
After Width: | Height: | Size: 398 KiB |
BIN
priv/static/mockups/botanical-illustration-canvas-1.webp
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
priv/static/mockups/botanical-illustration-canvas-2.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
priv/static/mockups/botanical-illustration-canvas-3.webp
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
priv/static/mockups/botanical-illustration-canvas-4.webp
Normal file
|
After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 118 KiB |
BIN
priv/static/mockups/forest-light-hoodie-10.webp
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
priv/static/mockups/forest-light-hoodie-11.webp
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
priv/static/mockups/forest-light-hoodie-12.webp
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
priv/static/mockups/forest-light-hoodie-13.webp
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
priv/static/mockups/forest-light-hoodie-14.webp
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
priv/static/mockups/forest-light-hoodie-15.webp
Normal file
|
After Width: | Height: | Size: 171 KiB |
BIN
priv/static/mockups/forest-light-hoodie-16.webp
Normal file
|
After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 94 KiB |
BIN
priv/static/mockups/forest-light-hoodie-3.webp
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
priv/static/mockups/forest-light-hoodie-4.webp
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
priv/static/mockups/forest-light-hoodie-5.webp
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
priv/static/mockups/forest-light-hoodie-6.webp
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
priv/static/mockups/forest-light-hoodie-7.webp
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
priv/static/mockups/forest-light-hoodie-8.webp
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
priv/static/mockups/forest-light-hoodie-9.webp
Normal file
|
After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-10.webp
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-11.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-12.webp
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-13.webp
Normal file
|
After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-5.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-6.webp
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-7.webp
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-8.webp
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
priv/static/mockups/forest-silhouette-tshirt-9.webp
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
priv/static/mockups/geometric-abstract-canvas-1.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
priv/static/mockups/geometric-abstract-canvas-2.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
priv/static/mockups/geometric-abstract-canvas-3.webp
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
priv/static/mockups/geometric-abstract-canvas-4.webp
Normal file
|
After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 209 KiB |
BIN
priv/static/mockups/monstera-leaf-notebook-5.webp
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
priv/static/mockups/monstera-leaf-notebook-6.webp
Normal file
|
After Width: | Height: | Size: 422 KiB |
BIN
priv/static/mockups/monstera-leaf-phone-case-5.webp
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
priv/static/mockups/monstera-leaf-phone-case-6.webp
Normal file
|
After Width: | Height: | Size: 353 KiB |
BIN
priv/static/mockups/mountain-sunrise-canvas-1.webp
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
priv/static/mockups/mountain-sunrise-canvas-2.webp
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
priv/static/mockups/mountain-sunrise-canvas-3.webp
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
priv/static/mockups/mountain-sunrise-canvas-4.webp
Normal file
|
After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 210 KiB |
BIN
priv/static/mockups/ocean-waves-canvas-1.webp
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
priv/static/mockups/ocean-waves-canvas-2.webp
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
priv/static/mockups/ocean-waves-canvas-3.webp
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
priv/static/mockups/ocean-waves-canvas-4.webp
Normal file
|
After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 212 KiB |
BIN
priv/static/mockups/sunset-gradient-tote-5.webp
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
priv/static/mockups/wildflower-meadow-canvas-1.webp
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
priv/static/mockups/wildflower-meadow-canvas-2.webp
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
priv/static/mockups/wildflower-meadow-canvas-3.webp
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
priv/static/mockups/wildflower-meadow-canvas-4.webp
Normal file
|
After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 218 KiB |
BIN
priv/static/mockups/wildflower-meadow-tote-5.webp
Normal file
|
After Width: | Height: | Size: 239 KiB |