add image uploads to on-site theme editor and fix scroll on navigation
All checks were successful
deploy / deploy (push) Successful in 1m27s
All checks were successful
deploy / deploy (push) Successful in 1m27s
Phase 4 of unified editing: image upload handling in hook context. - Configure uploads in Shop.Page mount for logo, header, icon - Add upload UI components to theme_editor compact_editor - Pass uploads through page_renderer to theme editor - Add cancel_upload handler to PageEditorHook Also fixes scroll position not resetting on patch navigation: - Push scroll-top event when path changes in handle_params - JS listener scrolls window to top instantly Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,8 @@ defmodule BerrypodWeb.Shop.Page do
|
||||
use BerrypodWeb, :live_view
|
||||
|
||||
alias BerrypodWeb.Shop.Pages
|
||||
alias Berrypod.{Media, Settings}
|
||||
alias Berrypod.Workers.FaviconGeneratorWorker
|
||||
|
||||
# Map live_action atoms to page handler modules
|
||||
@page_modules %{
|
||||
@@ -35,14 +37,159 @@ defmodule BerrypodWeb.Shop.Page do
|
||||
@impl true
|
||||
def mount(_params, session, socket) do
|
||||
# Store session for pages that need it (orders, order_detail)
|
||||
{:ok, assign(socket, :_session, session)}
|
||||
socket = assign(socket, :_session, session)
|
||||
|
||||
# Configure uploads only for admin users (theme editor image uploads)
|
||||
socket =
|
||||
if socket.assigns[:is_admin] do
|
||||
socket
|
||||
|> allow_upload(:theme_logo_upload,
|
||||
accept: ~w(.png .jpg .jpeg .webp .svg),
|
||||
max_entries: 1,
|
||||
max_file_size: 2_000_000,
|
||||
auto_upload: true,
|
||||
progress: &handle_theme_upload_progress/3
|
||||
)
|
||||
|> allow_upload(:theme_header_upload,
|
||||
accept: ~w(.png .jpg .jpeg .webp),
|
||||
max_entries: 1,
|
||||
max_file_size: 5_000_000,
|
||||
auto_upload: true,
|
||||
progress: &handle_theme_upload_progress/3
|
||||
)
|
||||
|> allow_upload(:theme_icon_upload,
|
||||
accept: ~w(.png .jpg .jpeg .webp .svg),
|
||||
max_entries: 1,
|
||||
max_file_size: 5_000_000,
|
||||
auto_upload: true,
|
||||
progress: &handle_theme_upload_progress/3
|
||||
)
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
# Handle theme image upload progress (logo, header, icon)
|
||||
defp handle_theme_upload_progress(:theme_logo_upload, entry, socket) do
|
||||
if entry.done? do
|
||||
consume_uploaded_entries(socket, :theme_logo_upload, fn %{path: path}, entry ->
|
||||
case Media.upload_from_entry(path, entry, "logo") do
|
||||
{:ok, image} ->
|
||||
Settings.update_theme_settings(%{logo_image_id: image.id})
|
||||
{:ok, image}
|
||||
|
||||
{:error, _} = error ->
|
||||
error
|
||||
end
|
||||
end)
|
||||
|> case do
|
||||
[image | _] ->
|
||||
# Trigger favicon generation if using logo as icon
|
||||
if socket.assigns[:theme_editor_settings] &&
|
||||
socket.assigns.theme_editor_settings.use_logo_as_icon do
|
||||
enqueue_favicon_generation(image.id)
|
||||
end
|
||||
|
||||
{:noreply,
|
||||
assign(socket, :theme_editor_logo_image, image) |> assign(:logo_image, image)}
|
||||
|
||||
_ ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_theme_upload_progress(:theme_header_upload, entry, socket) do
|
||||
if entry.done? do
|
||||
consume_uploaded_entries(socket, :theme_header_upload, fn %{path: path}, entry ->
|
||||
case Media.upload_from_entry(path, entry, "header") do
|
||||
{:ok, image} ->
|
||||
Settings.update_theme_settings(%{header_image_id: image.id})
|
||||
{:ok, image}
|
||||
|
||||
{:error, _} = error ->
|
||||
error
|
||||
end
|
||||
end)
|
||||
|> case do
|
||||
[image | _] ->
|
||||
socket =
|
||||
socket
|
||||
|> assign(:theme_editor_header_image, image)
|
||||
|> assign(:header_image, image)
|
||||
|> recompute_header_contrast()
|
||||
|
||||
{:noreply, socket}
|
||||
|
||||
_ ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_theme_upload_progress(:theme_icon_upload, entry, socket) do
|
||||
if entry.done? do
|
||||
consume_uploaded_entries(socket, :theme_icon_upload, fn %{path: path}, entry ->
|
||||
case Media.upload_from_entry(path, entry, "icon") do
|
||||
{:ok, image} ->
|
||||
Settings.update_theme_settings(%{icon_image_id: image.id})
|
||||
{:ok, image}
|
||||
|
||||
{:error, _} = error ->
|
||||
error
|
||||
end
|
||||
end)
|
||||
|> case do
|
||||
[image | _] ->
|
||||
enqueue_favicon_generation(image.id)
|
||||
|
||||
{:noreply,
|
||||
assign(socket, :theme_editor_icon_image, image) |> assign(:icon_image, image)}
|
||||
|
||||
_ ->
|
||||
{:noreply, socket}
|
||||
end
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
defp enqueue_favicon_generation(source_image_id) do
|
||||
%{source_image_id: source_image_id}
|
||||
|> FaviconGeneratorWorker.new()
|
||||
|> Oban.insert()
|
||||
end
|
||||
|
||||
defp recompute_header_contrast(socket) do
|
||||
header_image = socket.assigns[:theme_editor_header_image]
|
||||
theme_settings = socket.assigns[:theme_editor_settings]
|
||||
|
||||
warning =
|
||||
if theme_settings && theme_settings.header_background_enabled && header_image do
|
||||
text_color = Berrypod.Theme.Contrast.text_color_for_mood(theme_settings.mood)
|
||||
colors = Berrypod.Theme.Contrast.parse_dominant_colors(header_image.dominant_colors)
|
||||
Berrypod.Theme.Contrast.analyze_header_contrast(colors, text_color)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
|
||||
assign(socket, :theme_editor_contrast_warning, warning)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(params, uri, socket) do
|
||||
action = socket.assigns.live_action
|
||||
prev_action = socket.assigns[:_current_page_action]
|
||||
prev_path = socket.assigns[:_current_path]
|
||||
module = @page_modules[action]
|
||||
parsed_uri = URI.parse(uri)
|
||||
current_path = parsed_uri.path
|
||||
|
||||
# Clean up previous page if needed (e.g., unsubscribe from PubSub)
|
||||
socket = maybe_cleanup_previous_page(socket, prev_action)
|
||||
@@ -73,6 +220,16 @@ defmodule BerrypodWeb.Shop.Page do
|
||||
# After page init, sync editor state if editing and page changed
|
||||
socket = maybe_sync_editing_blocks(socket)
|
||||
|
||||
# Scroll to top on navigation (different path, not just query param changes)
|
||||
socket =
|
||||
if prev_path && prev_path != current_path do
|
||||
Phoenix.LiveView.push_event(socket, "scroll-top", %{})
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
socket = assign(socket, :_current_path, current_path)
|
||||
|
||||
# Always call handle_params for URL changes
|
||||
case module.handle_params(params, uri, socket) do
|
||||
{:noreply, socket} -> {:noreply, socket}
|
||||
|
||||
Reference in New Issue
Block a user