add image picker to page editor and fix thumbnail layout
Wire up image field in block settings with a modal picker that browses the media library. Fix picker thumbnails collapsing to 14px by replacing overflow:hidden with overflow:clip on grid items (hidden sets min-height:0 in grid context). Polish media library mobile sheet with scrim overlay and tighter spacing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
847b5f3e5e
commit
2c634177c4
@ -1662,6 +1662,139 @@
|
|||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-field-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-field-remove-btn {
|
||||||
|
color: var(--t-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-field-choose-btn {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Image picker modal ─────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.image-picker-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 50;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
@media (min-width: 40em) {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker {
|
||||||
|
background: var(--t-surface-base);
|
||||||
|
border-radius: 0.75rem 0.75rem 0 0;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
@media (min-width: 40em) {
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
max-width: 32rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
|
||||||
|
& h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-search {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 0.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
@media (min-width: 40em) {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.375rem;
|
||||||
|
padding: 0.375rem;
|
||||||
|
border: 1px solid var(--t-border-default);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
color: inherit;
|
||||||
|
transition: background 100ms;
|
||||||
|
overflow: clip;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
&:hover {
|
||||||
|
background: var(--t-surface-sunken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-item-thumb,
|
||||||
|
.image-picker-item-svg {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
background: var(--t-surface-sunken);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-item-thumb {
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-item-svg {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--t-text-primary);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-item-name {
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-picker-empty {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
color: color-mix(in oklch, var(--t-text-primary) 50%, transparent);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════════════
|
||||||
Media library
|
Media library
|
||||||
═══════════════════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════════════════ */
|
||||||
@ -1913,13 +2046,27 @@
|
|||||||
padding-top: 0.75rem;
|
padding-top: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-detail-scrim {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 47.99em) {
|
@media (max-width: 47.99em) {
|
||||||
.media-grid {
|
.media-grid {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-detail-scrim {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 39;
|
||||||
|
animation: media-scrim-fade 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
.media-detail {
|
.media-detail {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
top: auto;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@ -1927,9 +2074,32 @@
|
|||||||
min-width: unset;
|
min-width: unset;
|
||||||
max-height: 70vh;
|
max-height: 70vh;
|
||||||
border-radius: 0.75rem 0.75rem 0 0;
|
border-radius: 0.75rem 0.75rem 0 0;
|
||||||
box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.25);
|
||||||
z-index: 40;
|
z-index: 40;
|
||||||
animation: media-sheet-up 0.25s ease-out;
|
animation: media-sheet-up 0.25s ease-out;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-detail-preview {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-detail-meta {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
gap: 0.125rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-detail-form {
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-detail-form .admin-fieldset {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-detail-form .admin-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1938,4 +2108,9 @@
|
|||||||
to { transform: translateY(0); }
|
to { transform: translateY(0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes media-scrim-fade {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
} /* @layer admin */
|
} /* @layer admin */
|
||||||
|
|||||||
@ -233,21 +233,37 @@ defmodule BerrypodWeb.BlockEditorComponents do
|
|||||||
<span :if={@image.alt} class="image-field-alt">{@image.alt}</span>
|
<span :if={@image.alt} class="image-field-alt">{@image.alt}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<div class="image-field-actions">
|
||||||
<div class="image-field-empty">
|
<button
|
||||||
<.icon name="hero-photo" class="size-6" />
|
type="button"
|
||||||
<span>No image selected</span>
|
phx-click={"#{@event_prefix}show_image_picker"}
|
||||||
|
phx-value-block-id={@block_id}
|
||||||
|
phx-value-field={@field.key}
|
||||||
|
class="admin-btn admin-btn-outline admin-btn-xs"
|
||||||
|
>
|
||||||
|
Change
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
phx-click={"#{@event_prefix}clear_image"}
|
||||||
|
phx-value-block-id={@block_id}
|
||||||
|
phx-value-field={@field.key}
|
||||||
|
class="admin-btn admin-btn-ghost admin-btn-xs image-field-remove-btn"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
phx-click={"#{@event_prefix}show_image_picker"}
|
||||||
|
phx-value-block-id={@block_id}
|
||||||
|
phx-value-field={@field.key}
|
||||||
|
class="admin-btn admin-btn-outline admin-btn-sm image-field-choose-btn"
|
||||||
|
>
|
||||||
|
<.icon name="hero-photo" class="size-4" /> Choose image
|
||||||
|
</button>
|
||||||
<% end %>
|
<% end %>
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name={"block_settings[#{@field.key}]"}
|
|
||||||
id={"block-#{@block_id}-#{@field.key}"}
|
|
||||||
value={if(@image, do: @image.id, else: "")}
|
|
||||||
placeholder="Paste image ID from media library"
|
|
||||||
class="admin-input admin-input-sm"
|
|
||||||
phx-debounce="blur"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
@ -449,4 +465,81 @@ defmodule BerrypodWeb.BlockEditorComponents do
|
|||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ── Image picker ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
attr :images, :list, required: true
|
||||||
|
attr :search, :string, required: true
|
||||||
|
attr :event_prefix, :string, default: ""
|
||||||
|
|
||||||
|
def image_picker(assigns) do
|
||||||
|
search = String.downcase(assigns.search)
|
||||||
|
|
||||||
|
filtered =
|
||||||
|
if search == "" do
|
||||||
|
assigns.images
|
||||||
|
else
|
||||||
|
Enum.filter(assigns.images, fn img ->
|
||||||
|
String.contains?(String.downcase(img.filename || ""), search) or
|
||||||
|
String.contains?(String.downcase(img.alt || ""), search)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
assigns = assign(assigns, :filtered_images, filtered)
|
||||||
|
|
||||||
|
~H"""
|
||||||
|
<div class="image-picker-overlay">
|
||||||
|
<div class="image-picker" phx-click-away={"#{@event_prefix}hide_image_picker"}>
|
||||||
|
<div class="image-picker-header">
|
||||||
|
<h3>Choose image</h3>
|
||||||
|
<button
|
||||||
|
phx-click={"#{@event_prefix}hide_image_picker"}
|
||||||
|
class="admin-btn admin-btn-ghost admin-btn-icon admin-btn-sm"
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
<.icon name="hero-x-mark" class="size-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search images..."
|
||||||
|
value={@search}
|
||||||
|
phx-keyup={"#{@event_prefix}image_picker_search"}
|
||||||
|
phx-key=""
|
||||||
|
class="admin-input image-picker-search"
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="image-picker-grid">
|
||||||
|
<button
|
||||||
|
:for={image <- @filtered_images}
|
||||||
|
type="button"
|
||||||
|
phx-click={"#{@event_prefix}pick_image"}
|
||||||
|
phx-value-image-id={image.id}
|
||||||
|
class="image-picker-item"
|
||||||
|
>
|
||||||
|
<%= if image.is_svg do %>
|
||||||
|
<div class="image-picker-item-svg">
|
||||||
|
<.icon name="hero-code-bracket" class="size-6" />
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<img
|
||||||
|
src={"/image_cache/#{image.id}-thumb.jpg"}
|
||||||
|
alt={image.alt || image.filename}
|
||||||
|
class="image-picker-item-thumb"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<% end %>
|
||||||
|
<span class="image-picker-item-name">{image.filename}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<p :if={@filtered_images == []} class="image-picker-empty">
|
||||||
|
No images found.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -330,6 +330,7 @@ defmodule BerrypodWeb.Admin.Media do
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%!-- detail panel --%>
|
<%!-- detail panel --%>
|
||||||
|
<div :if={@selected_image} class="media-detail-scrim" phx-click="deselect_image"></div>
|
||||||
<aside :if={@selected_image} class="media-detail">
|
<aside :if={@selected_image} class="media-detail">
|
||||||
<div class="media-detail-header">
|
<div class="media-detail-header">
|
||||||
<h3>Image details</h3>
|
<h3>Image details</h3>
|
||||||
|
|||||||
@ -34,7 +34,11 @@ defmodule BerrypodWeb.Admin.Pages.Editor do
|
|||||||
|> assign(:show_preview, false)
|
|> assign(:show_preview, false)
|
||||||
|> assign(:preview_data, preview_data)
|
|> assign(:preview_data, preview_data)
|
||||||
|> assign(:logo_image, Media.get_logo())
|
|> assign(:logo_image, Media.get_logo())
|
||||||
|> assign(:header_image, Media.get_header())}
|
|> assign(:header_image, Media.get_header())
|
||||||
|
|> assign(:image_picker_block_id, nil)
|
||||||
|
|> assign(:image_picker_field_key, nil)
|
||||||
|
|> assign(:image_picker_images, [])
|
||||||
|
|> assign(:image_picker_search, "")}
|
||||||
end
|
end
|
||||||
|
|
||||||
# ── Block manipulation events ────────────────────────────────────
|
# ── Block manipulation events ────────────────────────────────────
|
||||||
@ -150,6 +154,69 @@ defmodule BerrypodWeb.Admin.Pages.Editor do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ── Image picker events ──────────────────────────────────────────
|
||||||
|
|
||||||
|
def handle_event(
|
||||||
|
"show_image_picker",
|
||||||
|
%{"block-id" => block_id, "field" => field_key},
|
||||||
|
socket
|
||||||
|
) do
|
||||||
|
images = Media.list_images()
|
||||||
|
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:image_picker_block_id, block_id)
|
||||||
|
|> assign(:image_picker_field_key, field_key)
|
||||||
|
|> assign(:image_picker_images, images)
|
||||||
|
|> assign(:image_picker_search, "")}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("hide_image_picker", _params, socket) do
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:image_picker_block_id, nil)
|
||||||
|
|> assign(:image_picker_field_key, nil)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("image_picker_search", %{"value" => value}, socket) do
|
||||||
|
{:noreply, assign(socket, :image_picker_search, value)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("pick_image", %{"image-id" => image_id}, socket) do
|
||||||
|
block_id = socket.assigns.image_picker_block_id
|
||||||
|
field_key = socket.assigns.image_picker_field_key
|
||||||
|
|
||||||
|
case BlockEditor.update_settings(socket.assigns.blocks, block_id, %{field_key => image_id}) do
|
||||||
|
{:ok, new_blocks} ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:blocks, new_blocks)
|
||||||
|
|> assign(:dirty, true)
|
||||||
|
|> assign(:image_picker_block_id, nil)
|
||||||
|
|> assign(:image_picker_field_key, nil)}
|
||||||
|
|
||||||
|
:noop ->
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event(
|
||||||
|
"clear_image",
|
||||||
|
%{"block-id" => block_id, "field" => field_key},
|
||||||
|
socket
|
||||||
|
) do
|
||||||
|
case BlockEditor.update_settings(socket.assigns.blocks, block_id, %{field_key => ""}) do
|
||||||
|
{:ok, new_blocks} ->
|
||||||
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(:blocks, new_blocks)
|
||||||
|
|> assign(:dirty, true)}
|
||||||
|
|
||||||
|
:noop ->
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# ── UI events ────────────────────────────────────────────────────
|
# ── UI events ────────────────────────────────────────────────────
|
||||||
|
|
||||||
def handle_event("toggle_expand", %{"id" => block_id}, socket) do
|
def handle_event("toggle_expand", %{"id" => block_id}, socket) do
|
||||||
@ -323,6 +390,13 @@ defmodule BerrypodWeb.Admin.Pages.Editor do
|
|||||||
allowed_blocks={@allowed_blocks}
|
allowed_blocks={@allowed_blocks}
|
||||||
filter={@picker_filter}
|
filter={@picker_filter}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<%!-- Image picker modal --%>
|
||||||
|
<.image_picker
|
||||||
|
:if={@image_picker_block_id}
|
||||||
|
images={@image_picker_images}
|
||||||
|
search={@image_picker_search}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|||||||
@ -34,6 +34,10 @@ defmodule BerrypodWeb.PageEditorHook do
|
|||||||
|> assign(:editor_live_region_message, nil)
|
|> assign(:editor_live_region_message, nil)
|
||||||
|> assign(:editor_current_path, nil)
|
|> assign(:editor_current_path, nil)
|
||||||
|> assign(:editor_sidebar_open, true)
|
|> assign(:editor_sidebar_open, true)
|
||||||
|
|> assign(:editor_image_picker_block_id, nil)
|
||||||
|
|> assign(:editor_image_picker_field_key, nil)
|
||||||
|
|> assign(:editor_image_picker_images, [])
|
||||||
|
|> assign(:editor_image_picker_search, "")
|
||||||
|> attach_hook(:editor_params, :handle_params, &handle_editor_params/3)
|
|> attach_hook(:editor_params, :handle_params, &handle_editor_params/3)
|
||||||
|> attach_hook(:editor_events, :handle_event, &handle_editor_event/3)
|
|> attach_hook(:editor_events, :handle_event, &handle_editor_event/3)
|
||||||
|> attach_hook(:editor_info, :handle_info, &handle_editor_info/2)
|
|> attach_hook(:editor_info, :handle_info, &handle_editor_info/2)
|
||||||
@ -262,6 +266,79 @@ defmodule BerrypodWeb.PageEditorHook do
|
|||||||
{:halt, assign(socket, :editor_picker_filter, value)}
|
{:halt, assign(socket, :editor_picker_filter, value)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ── Image picker actions ───────────────────────────────────────
|
||||||
|
|
||||||
|
defp handle_editor_action(
|
||||||
|
"show_image_picker",
|
||||||
|
%{"block-id" => block_id, "field" => field_key},
|
||||||
|
socket
|
||||||
|
) do
|
||||||
|
images = Berrypod.Media.list_images()
|
||||||
|
|
||||||
|
{:halt,
|
||||||
|
socket
|
||||||
|
|> assign(:editor_image_picker_block_id, block_id)
|
||||||
|
|> assign(:editor_image_picker_field_key, field_key)
|
||||||
|
|> assign(:editor_image_picker_images, images)
|
||||||
|
|> assign(:editor_image_picker_search, "")}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_editor_action("hide_image_picker", _params, socket) do
|
||||||
|
{:halt,
|
||||||
|
socket
|
||||||
|
|> assign(:editor_image_picker_block_id, nil)
|
||||||
|
|> assign(:editor_image_picker_field_key, nil)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_editor_action("image_picker_search", %{"value" => value}, socket) do
|
||||||
|
{:halt, assign(socket, :editor_image_picker_search, value)}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_editor_action("pick_image", %{"image-id" => image_id}, socket) do
|
||||||
|
block_id = socket.assigns.editor_image_picker_block_id
|
||||||
|
field_key = socket.assigns.editor_image_picker_field_key
|
||||||
|
|
||||||
|
case BlockEditor.update_settings(socket.assigns.editing_blocks, block_id, %{
|
||||||
|
field_key => image_id
|
||||||
|
}) do
|
||||||
|
{:ok, new_blocks} ->
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(:editing_blocks, new_blocks)
|
||||||
|
|> assign(:editor_dirty, true)
|
||||||
|
|> assign(:editor_image_picker_block_id, nil)
|
||||||
|
|> assign(:editor_image_picker_field_key, nil)
|
||||||
|
|> reload_block_data(new_blocks)
|
||||||
|
|
||||||
|
{:halt, socket}
|
||||||
|
|
||||||
|
:noop ->
|
||||||
|
{:halt, socket}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_editor_action(
|
||||||
|
"clear_image",
|
||||||
|
%{"block-id" => block_id, "field" => field_key},
|
||||||
|
socket
|
||||||
|
) do
|
||||||
|
case BlockEditor.update_settings(socket.assigns.editing_blocks, block_id, %{
|
||||||
|
field_key => ""
|
||||||
|
}) do
|
||||||
|
{:ok, new_blocks} ->
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(:editing_blocks, new_blocks)
|
||||||
|
|> assign(:editor_dirty, true)
|
||||||
|
|> reload_block_data(new_blocks)
|
||||||
|
|
||||||
|
{:halt, socket}
|
||||||
|
|
||||||
|
:noop ->
|
||||||
|
{:halt, socket}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# ── Page actions ─────────────────────────────────────────────────
|
# ── Page actions ─────────────────────────────────────────────────
|
||||||
|
|
||||||
defp handle_editor_action("save", _params, socket) do
|
defp handle_editor_action("save", _params, socket) do
|
||||||
@ -325,6 +402,10 @@ defmodule BerrypodWeb.PageEditorHook do
|
|||||||
|> assign(:editor_allowed_blocks, allowed)
|
|> assign(:editor_allowed_blocks, allowed)
|
||||||
|> assign(:editor_live_region_message, nil)
|
|> assign(:editor_live_region_message, nil)
|
||||||
|> assign(:editor_sidebar_open, true)
|
|> assign(:editor_sidebar_open, true)
|
||||||
|
|> assign(:editor_image_picker_block_id, nil)
|
||||||
|
|> assign(:editor_image_picker_field_key, nil)
|
||||||
|
|> assign(:editor_image_picker_images, [])
|
||||||
|
|> assign(:editor_image_picker_search, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp exit_edit_mode(socket) do
|
defp exit_edit_mode(socket) do
|
||||||
@ -338,6 +419,10 @@ defmodule BerrypodWeb.PageEditorHook do
|
|||||||
|> assign(:editor_allowed_blocks, nil)
|
|> assign(:editor_allowed_blocks, nil)
|
||||||
|> assign(:editor_live_region_message, nil)
|
|> assign(:editor_live_region_message, nil)
|
||||||
|> assign(:editor_sidebar_open, true)
|
|> assign(:editor_sidebar_open, true)
|
||||||
|
|> assign(:editor_image_picker_block_id, nil)
|
||||||
|
|> assign(:editor_image_picker_field_key, nil)
|
||||||
|
|> assign(:editor_image_picker_images, [])
|
||||||
|
|> assign(:editor_image_picker_search, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp apply_mutation(socket, new_blocks, message, type) do
|
defp apply_mutation(socket, new_blocks, message, type) do
|
||||||
|
|||||||
@ -142,6 +142,14 @@ defmodule BerrypodWeb.PageRenderer do
|
|||||||
filter={@editor_picker_filter}
|
filter={@editor_picker_filter}
|
||||||
event_prefix="editor_"
|
event_prefix="editor_"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<%!-- Image picker modal --%>
|
||||||
|
<.image_picker
|
||||||
|
:if={@editor_image_picker_block_id}
|
||||||
|
images={@editor_image_picker_images}
|
||||||
|
search={@editor_image_picker_search}
|
||||||
|
event_prefix="editor_"
|
||||||
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<%!-- Backdrop: tapping the page dismisses the sidebar --%>
|
<%!-- Backdrop: tapping the page dismisses the sidebar --%>
|
||||||
@ -1029,7 +1037,13 @@ defmodule BerrypodWeb.PageRenderer do
|
|||||||
if image.is_svg do
|
if image.is_svg do
|
||||||
"/image_cache/#{image.id}.webp"
|
"/image_cache/#{image.id}.webp"
|
||||||
else
|
else
|
||||||
"/image_cache/#{image.id}-800.webp"
|
# Pick the largest variant that was actually generated
|
||||||
|
width =
|
||||||
|
image.source_width
|
||||||
|
|> Berrypod.Images.Optimizer.applicable_widths()
|
||||||
|
|> List.last()
|
||||||
|
|
||||||
|
"/image_cache/#{image.id}-#{width || 400}.webp"
|
||||||
end
|
end
|
||||||
|
|
||||||
{url, image.alt || image.filename}
|
{url, image.alt || image.filename}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user