- Update Theme Studio sidebar to use DaisyUI theme-aware classes for dark mode - Convert Customise accordion to native details/summary elements for proper interaction - Add "Current combination" card showing active theme settings - Add SVG recolorer for logo color customization - Add image controller for serving uploaded images - Implement header background image controls (zoom, position) - Add toggle_customise event handler to preserve accordion state across re-renders 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
153 lines
3.2 KiB
Elixir
153 lines
3.2 KiB
Elixir
defmodule SimpleshopTheme.Media do
|
|
@moduledoc """
|
|
The Media context for managing images and file uploads.
|
|
"""
|
|
|
|
import Ecto.Query, warn: false
|
|
alias SimpleshopTheme.Repo
|
|
alias SimpleshopTheme.Media.Image, as: ImageSchema
|
|
|
|
@thumbnail_size 200
|
|
|
|
@doc """
|
|
Uploads an image and stores it in the database.
|
|
|
|
Automatically generates a thumbnail for non-SVG images if the Image library
|
|
is available and working.
|
|
|
|
## Examples
|
|
|
|
iex> upload_image(%{image_type: "logo", filename: "logo.png", ...})
|
|
{:ok, %Image{}}
|
|
|
|
"""
|
|
def upload_image(attrs) do
|
|
attrs = maybe_generate_thumbnail(attrs)
|
|
|
|
%ImageSchema{}
|
|
|> ImageSchema.changeset(attrs)
|
|
|> Repo.insert()
|
|
end
|
|
|
|
@doc """
|
|
Uploads an image from a LiveView upload entry.
|
|
|
|
This handles consuming the upload and extracting metadata from the entry.
|
|
|
|
## Examples
|
|
|
|
iex> upload_from_entry(socket, :logo_upload, fn path, entry -> ... end)
|
|
{:ok, %Image{}}
|
|
|
|
"""
|
|
def upload_from_entry(path, entry, image_type) do
|
|
file_binary = File.read!(path)
|
|
|
|
upload_image(%{
|
|
image_type: image_type,
|
|
filename: entry.client_name,
|
|
content_type: entry.client_type,
|
|
file_size: entry.client_size,
|
|
data: file_binary
|
|
})
|
|
end
|
|
|
|
defp maybe_generate_thumbnail(%{data: data, content_type: content_type} = attrs)
|
|
when is_binary(data) do
|
|
if String.starts_with?(content_type || "", "image/svg") do
|
|
attrs
|
|
else
|
|
case generate_thumbnail(data) do
|
|
{:ok, thumbnail_data} ->
|
|
Map.put(attrs, :thumbnail_data, thumbnail_data)
|
|
|
|
{:error, _reason} ->
|
|
attrs
|
|
end
|
|
end
|
|
end
|
|
|
|
defp maybe_generate_thumbnail(attrs), do: attrs
|
|
|
|
defp generate_thumbnail(image_data) do
|
|
try do
|
|
with {:ok, image} <- Image.from_binary(image_data),
|
|
{:ok, thumbnail} <- Image.thumbnail(image, @thumbnail_size),
|
|
{:ok, binary} <- Image.write(thumbnail, :memory, suffix: ".jpg") do
|
|
{:ok, binary}
|
|
end
|
|
rescue
|
|
_e ->
|
|
{:error, :thumbnail_generation_failed}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets a single image by ID.
|
|
|
|
## Examples
|
|
|
|
iex> get_image(id)
|
|
%Image{}
|
|
|
|
iex> get_image("nonexistent")
|
|
nil
|
|
|
|
"""
|
|
def get_image(id) do
|
|
Repo.get(ImageSchema, id)
|
|
end
|
|
|
|
@doc """
|
|
Gets the current logo image.
|
|
|
|
## Examples
|
|
|
|
iex> get_logo()
|
|
%Image{}
|
|
|
|
"""
|
|
def get_logo do
|
|
Repo.one(from i in ImageSchema, where: i.image_type == "logo", order_by: [desc: i.inserted_at], limit: 1)
|
|
end
|
|
|
|
@doc """
|
|
Gets the current header image.
|
|
|
|
## Examples
|
|
|
|
iex> get_header()
|
|
%Image{}
|
|
|
|
"""
|
|
def get_header do
|
|
Repo.one(from i in ImageSchema, where: i.image_type == "header", order_by: [desc: i.inserted_at], limit: 1)
|
|
end
|
|
|
|
@doc """
|
|
Deletes an image.
|
|
|
|
## Examples
|
|
|
|
iex> delete_image(image)
|
|
{:ok, %Image{}}
|
|
|
|
"""
|
|
def delete_image(%ImageSchema{} = image) do
|
|
Repo.delete(image)
|
|
end
|
|
|
|
@doc """
|
|
Lists all images of a specific type.
|
|
|
|
## Examples
|
|
|
|
iex> list_images_by_type("logo")
|
|
[%Image{}, ...]
|
|
|
|
"""
|
|
def list_images_by_type(type) do
|
|
Repo.all(from i in ImageSchema, where: i.image_type == ^type, order_by: [desc: i.inserted_at])
|
|
end
|
|
end
|