add block previews, picker thumbnails and newsletter settings
All checks were successful
deploy / deploy (push) Successful in 1m30s

Block cards now show a one-line content summary below the name.
Block picker items include SVG wireframe thumbnails. Newsletter
block marked as decorative with configurable title/description
and form submission prevented on the shop side.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-28 20:34:52 +00:00
parent 0a7982dfe8
commit 8f989d892d
6 changed files with 636 additions and 12 deletions

View File

@@ -15,6 +15,7 @@ defmodule BerrypodWeb.BlockEditorComponents do
import BerrypodWeb.CoreComponents, only: [icon: 1]
alias Berrypod.Pages.{BlockEditor, BlockTypes}
alias BerrypodWeb.BlockThumbnails
# ── Block card ─────────────────────────────────────────────────
@@ -28,12 +29,14 @@ defmodule BerrypodWeb.BlockEditorComponents do
block_type = BlockTypes.get(assigns.block["type"])
has_settings = BlockEditor.has_settings?(assigns.block)
expanded = MapSet.member?(assigns.expanded, assigns.block["id"])
preview = block_preview(assigns.block, block_type)
assigns =
assigns
|> assign(:block_type, block_type)
|> assign(:has_settings, has_settings)
|> assign(:is_expanded, expanded)
|> assign(:preview, preview)
~H"""
<div
@@ -49,9 +52,12 @@ defmodule BerrypodWeb.BlockEditorComponents do
<.icon name={(@block_type && @block_type.icon) || "hero-puzzle-piece"} class="size-5" />
</span>
<span class="block-card-name">
{(@block_type && @block_type.name) || @block["type"]}
</span>
<div class="block-card-info">
<span class="block-card-name">
{(@block_type && @block_type.name) || @block["type"]}
</span>
<span :if={@preview} class="block-card-preview">{@preview}</span>
</div>
<span class="block-card-controls">
<button
@@ -111,6 +117,7 @@ defmodule BerrypodWeb.BlockEditorComponents do
:if={@is_expanded}
block={@block}
schema={@block_type.settings_schema}
hint={@block_type[:hint]}
event_prefix={@event_prefix}
/>
</div>
@@ -121,6 +128,7 @@ defmodule BerrypodWeb.BlockEditorComponents do
attr :block, :map, required: true
attr :schema, :list, required: true
attr :hint, :string, default: nil
attr :event_prefix, :string, default: ""
def block_settings_form(assigns) do
@@ -129,6 +137,9 @@ defmodule BerrypodWeb.BlockEditorComponents do
~H"""
<div class="block-card-settings" id={"block-settings-#{@block["id"]}"}>
<p :if={@hint} class="block-settings-hint">
<.icon name="hero-information-circle-mini" class="size-4" /> {@hint}
</p>
<form phx-change={"#{@event_prefix}update_block_settings"}>
<input type="hidden" name="block_id" value={@block["id"]} />
@@ -454,6 +465,7 @@ defmodule BerrypodWeb.BlockEditorComponents do
phx-value-type={type}
class="block-picker-item"
>
<BlockThumbnails.block_thumbnail type={type} />
<.icon name={def.icon} class="size-5" />
<span class="block-picker-item-name">{def.name}</span>
<span :if={def[:description]} class="block-picker-item-desc">
@@ -564,4 +576,69 @@ defmodule BerrypodWeb.BlockEditorComponents do
</div>
"""
end
# ── Block preview ──────────────────────────────────────────────
# Extracts a short one-line preview from block settings for display in the card
defp block_preview(_block, nil), do: nil
defp block_preview(block, block_type) do
settings = block["settings"] || %{}
schema = block_type.settings_schema
# Try text/textarea fields first, then selects, then repeaters
find_text_preview(settings, schema) ||
find_select_preview(settings, schema) ||
find_repeater_preview(settings, schema)
end
defp find_text_preview(settings, schema) do
schema
|> Enum.filter(&(&1.type in [:text, :textarea]))
|> Enum.find_value(fn field ->
case settings[field.key] do
val when is_binary(val) and val != "" ->
truncate(val, 60)
_ ->
nil
end
end)
end
defp find_select_preview(settings, schema) do
schema
|> Enum.filter(&(&1.type == :select))
|> Enum.find_value(fn field ->
case settings[field.key] do
val when is_binary(val) and val != "" -> "#{field.label}: #{val}"
_ -> nil
end
end)
end
defp find_repeater_preview(settings, schema) do
schema
|> Enum.filter(&(&1.type == :repeater))
|> Enum.find_value(fn field ->
case settings[field.key] do
items when is_list(items) and items != [] ->
count = length(items)
"#{count} #{if count == 1, do: "item", else: "items"}"
_ ->
nil
end
end)
end
defp truncate(str, max) do
str = String.replace(str, ~r/\s+/, " ") |> String.trim()
if String.length(str) > max do
String.slice(str, 0, max) <> "..."
else
str
end
end
end