add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
defmodule BerrypodWeb.Admin.Media do
|
|
|
|
|
|
use BerrypodWeb, :live_view
|
|
|
|
|
|
|
|
|
|
|
|
alias Berrypod.Media
|
|
|
|
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
|
|
def mount(_params, _session, socket) do
|
|
|
|
|
|
socket =
|
|
|
|
|
|
socket
|
|
|
|
|
|
|> assign(:page_title, "Media")
|
|
|
|
|
|
|> assign(:filter_type, nil)
|
|
|
|
|
|
|> assign(:filter_search, "")
|
|
|
|
|
|
|> assign(:filter_orphans, false)
|
2026-03-01 09:42:34 +00:00
|
|
|
|
|> assign(:pagination, nil)
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
|> assign(:selected_image, nil)
|
|
|
|
|
|
|> assign(:selected_usages, [])
|
|
|
|
|
|
|> assign(:edit_form, nil)
|
|
|
|
|
|
|> assign(:upload_alt, "")
|
|
|
|
|
|
|> assign(:confirm_delete, false)
|
2026-03-08 07:34:17 +00:00
|
|
|
|
|> assign(:metadata_status, :idle)
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
|> allow_upload(:media_upload,
|
|
|
|
|
|
accept: ~w(.png .jpg .jpeg .webp .svg .gif),
|
|
|
|
|
|
max_entries: 1,
|
|
|
|
|
|
max_file_size: 5_000_000,
|
|
|
|
|
|
auto_upload: true,
|
|
|
|
|
|
progress: &handle_progress/3
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
{:ok, socket}
|
|
|
|
|
|
end
|
|
|
|
|
|
|
2026-03-01 09:42:34 +00:00
|
|
|
|
@impl true
|
|
|
|
|
|
def handle_params(params, _uri, socket) do
|
|
|
|
|
|
page_num = Berrypod.Pagination.parse_page(params)
|
|
|
|
|
|
opts = image_filter_opts(socket)
|
|
|
|
|
|
page = Media.list_images_paginated([page: page_num] ++ opts)
|
|
|
|
|
|
|
|
|
|
|
|
socket =
|
|
|
|
|
|
socket
|
|
|
|
|
|
|> assign(:pagination, page)
|
|
|
|
|
|
|> stream(:images, page.items, reset: true)
|
|
|
|
|
|
|
|
|
|
|
|
{:noreply, socket}
|
|
|
|
|
|
end
|
|
|
|
|
|
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
defp handle_progress(:media_upload, entry, socket) do
|
|
|
|
|
|
if entry.done? do
|
|
|
|
|
|
alt = socket.assigns.upload_alt
|
|
|
|
|
|
|
|
|
|
|
|
consume_uploaded_entries(socket, :media_upload, fn %{path: path}, entry ->
|
|
|
|
|
|
extra = if alt != "", do: %{alt: alt}, else: %{}
|
|
|
|
|
|
Media.upload_from_entry(path, entry, "media", extra)
|
|
|
|
|
|
end)
|
|
|
|
|
|
|> case do
|
|
|
|
|
|
[image | _] ->
|
|
|
|
|
|
# Reload without BLOB to insert into stream
|
|
|
|
|
|
image_without_blob = Media.get_image(image.id) |> Map.put(:data, nil)
|
|
|
|
|
|
|
|
|
|
|
|
{:noreply,
|
|
|
|
|
|
socket
|
|
|
|
|
|
|> stream_insert(:images, image_without_blob, at: 0)
|
|
|
|
|
|
|> assign(:upload_alt, "")
|
|
|
|
|
|
|> put_flash(:info, "Image uploaded")}
|
|
|
|
|
|
|
|
|
|
|
|
_ ->
|
|
|
|
|
|
{:noreply, put_flash(socket, :error, "Upload failed")}
|
|
|
|
|
|
end
|
|
|
|
|
|
else
|
|
|
|
|
|
{:noreply, socket}
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
|
|
def handle_event("filter_type", %{"type" => type}, socket) do
|
|
|
|
|
|
type = if type == "", do: nil, else: type
|
2026-03-01 09:42:34 +00:00
|
|
|
|
|
|
|
|
|
|
{:noreply,
|
|
|
|
|
|
socket
|
|
|
|
|
|
|> assign(:filter_type, type)
|
|
|
|
|
|
|> reload_images()}
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def handle_event("filter_search", %{"value" => value}, socket) do
|
2026-03-01 09:42:34 +00:00
|
|
|
|
{:noreply,
|
|
|
|
|
|
socket
|
|
|
|
|
|
|> assign(:filter_search, value)
|
|
|
|
|
|
|> reload_images()}
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def handle_event("toggle_orphans", _params, socket) do
|
2026-03-01 09:42:34 +00:00
|
|
|
|
{:noreply,
|
|
|
|
|
|
socket
|
|
|
|
|
|
|> assign(:filter_orphans, !socket.assigns.filter_orphans)
|
|
|
|
|
|
|> reload_images()}
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def handle_event("select_image", %{"id" => id}, socket) do
|
|
|
|
|
|
image = Media.get_image(id)
|
|
|
|
|
|
|
|
|
|
|
|
if image do
|
|
|
|
|
|
usages = Media.find_usages(id)
|
|
|
|
|
|
|
|
|
|
|
|
form =
|
|
|
|
|
|
to_form(
|
|
|
|
|
|
%{
|
|
|
|
|
|
"alt" => image.alt || "",
|
|
|
|
|
|
"caption" => image.caption || "",
|
|
|
|
|
|
"tags" => image.tags || ""
|
|
|
|
|
|
},
|
|
|
|
|
|
as: :metadata
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
{:noreply,
|
|
|
|
|
|
socket
|
|
|
|
|
|
|> assign(:selected_image, Map.put(image, :data, nil))
|
|
|
|
|
|
|> assign(:selected_usages, usages)
|
|
|
|
|
|
|> assign(:edit_form, form)
|
2026-03-08 07:34:17 +00:00
|
|
|
|
|> assign(:confirm_delete, false)
|
|
|
|
|
|
|> assign(:metadata_status, :idle)}
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
else
|
|
|
|
|
|
{:noreply, socket}
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def handle_event("deselect_image", _params, socket) do
|
|
|
|
|
|
{:noreply,
|
|
|
|
|
|
assign(socket,
|
|
|
|
|
|
selected_image: nil,
|
|
|
|
|
|
selected_usages: [],
|
|
|
|
|
|
edit_form: nil,
|
|
|
|
|
|
confirm_delete: false
|
|
|
|
|
|
)}
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def handle_event("update_metadata", %{"metadata" => params}, socket) do
|
|
|
|
|
|
image = socket.assigns.selected_image
|
|
|
|
|
|
|
|
|
|
|
|
case Media.update_image_metadata(image, params) do
|
|
|
|
|
|
{:ok, updated} ->
|
|
|
|
|
|
updated_no_blob = Map.put(updated, :data, nil)
|
|
|
|
|
|
|
|
|
|
|
|
{:noreply,
|
|
|
|
|
|
socket
|
|
|
|
|
|
|> stream_insert(:images, updated_no_blob)
|
|
|
|
|
|
|> assign(:selected_image, updated_no_blob)
|
2026-03-08 07:34:17 +00:00
|
|
|
|
|> assign(:metadata_status, :saved)}
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
|
|
|
|
|
|
{:error, _changeset} ->
|
2026-03-08 07:34:17 +00:00
|
|
|
|
{:noreply, assign(socket, :metadata_status, :error)}
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def handle_event("confirm_delete", _params, socket) do
|
|
|
|
|
|
{:noreply, assign(socket, :confirm_delete, true)}
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def handle_event("cancel_delete", _params, socket) do
|
|
|
|
|
|
{:noreply, assign(socket, :confirm_delete, false)}
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def handle_event("delete_image", _params, socket) do
|
|
|
|
|
|
image = socket.assigns.selected_image
|
|
|
|
|
|
|
|
|
|
|
|
case Media.delete_with_cleanup(image) do
|
|
|
|
|
|
{:ok, _} ->
|
|
|
|
|
|
{:noreply,
|
|
|
|
|
|
socket
|
|
|
|
|
|
|> stream_delete(:images, image)
|
|
|
|
|
|
|> assign(:selected_image, nil)
|
|
|
|
|
|
|> assign(:selected_usages, [])
|
|
|
|
|
|
|> assign(:edit_form, nil)
|
|
|
|
|
|
|> assign(:confirm_delete, false)
|
|
|
|
|
|
|> put_flash(:info, "Image deleted")}
|
|
|
|
|
|
|
|
|
|
|
|
{:error, :in_use, _usages} ->
|
|
|
|
|
|
{:noreply, put_flash(socket, :error, "Cannot delete — image is still in use")}
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def handle_event("set_upload_alt", %{"value" => value}, socket) do
|
|
|
|
|
|
{:noreply, assign(socket, :upload_alt, value)}
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# ── Private helpers ──────────────────────────────────────────────
|
|
|
|
|
|
|
2026-03-01 09:42:34 +00:00
|
|
|
|
defp image_filter_opts(socket) do
|
|
|
|
|
|
[
|
|
|
|
|
|
type: socket.assigns.filter_type,
|
|
|
|
|
|
search: if(socket.assigns.filter_search != "", do: socket.assigns.filter_search),
|
|
|
|
|
|
tag: nil
|
|
|
|
|
|
]
|
|
|
|
|
|
|> Enum.reject(fn {_, v} -> is_nil(v) end)
|
|
|
|
|
|
end
|
|
|
|
|
|
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
defp reload_images(socket) do
|
2026-03-01 09:42:34 +00:00
|
|
|
|
if socket.assigns.filter_orphans do
|
|
|
|
|
|
# Orphan mode: load all, filter in Elixir (no pagination)
|
|
|
|
|
|
opts = image_filter_opts(socket)
|
|
|
|
|
|
images = Media.list_images(opts)
|
|
|
|
|
|
used = Media.used_image_ids()
|
|
|
|
|
|
orphans = Enum.reject(images, &MapSet.member?(used, &1.id))
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
|
2026-03-01 09:42:34 +00:00
|
|
|
|
socket
|
|
|
|
|
|
|> assign(:pagination, nil)
|
|
|
|
|
|
|> stream(:images, orphans, reset: true)
|
|
|
|
|
|
else
|
|
|
|
|
|
push_patch(socket, to: ~p"/admin/media")
|
|
|
|
|
|
end
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
defp format_file_size(nil), do: "—"
|
|
|
|
|
|
|
|
|
|
|
|
defp format_file_size(bytes) when bytes < 1024, do: "#{bytes} B"
|
|
|
|
|
|
|
|
|
|
|
|
defp format_file_size(bytes) when bytes < 1_048_576 do
|
|
|
|
|
|
kb = Float.round(bytes / 1024, 1)
|
|
|
|
|
|
"#{kb} KB"
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
defp format_file_size(bytes) do
|
|
|
|
|
|
mb = Float.round(bytes / 1_048_576, 1)
|
|
|
|
|
|
"#{mb} MB"
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
defp format_dimensions(nil, _), do: "—"
|
|
|
|
|
|
defp format_dimensions(_, nil), do: "—"
|
|
|
|
|
|
defp format_dimensions(w, h), do: "#{w} × #{h}"
|
|
|
|
|
|
|
|
|
|
|
|
defp type_badge_class("product"), do: "admin-badge admin-badge-sm admin-badge-info"
|
|
|
|
|
|
defp type_badge_class("media"), do: "admin-badge admin-badge-sm admin-badge-accent"
|
|
|
|
|
|
defp type_badge_class("logo"), do: "admin-badge admin-badge-sm admin-badge-warning"
|
|
|
|
|
|
defp type_badge_class("header"), do: "admin-badge admin-badge-sm admin-badge-warning"
|
|
|
|
|
|
defp type_badge_class("icon"), do: "admin-badge admin-badge-sm admin-badge-warning"
|
|
|
|
|
|
defp type_badge_class(_), do: "admin-badge admin-badge-sm admin-badge-neutral"
|
|
|
|
|
|
|
|
|
|
|
|
defp image_thumbnail_url(image) do
|
|
|
|
|
|
cond do
|
|
|
|
|
|
image.is_svg -> nil
|
|
|
|
|
|
image.variants_status == "complete" -> "/image_cache/#{image.id}-thumb.jpg"
|
|
|
|
|
|
true -> nil
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
@impl true
|
|
|
|
|
|
def render(assigns) do
|
|
|
|
|
|
~H"""
|
|
|
|
|
|
<.header>
|
|
|
|
|
|
Media
|
|
|
|
|
|
</.header>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="media-layout">
|
|
|
|
|
|
<%!-- upload zone --%>
|
|
|
|
|
|
<div class="media-upload-zone" phx-drop-target={@uploads.media_upload.ref}>
|
|
|
|
|
|
<form phx-change="set_upload_alt" class="media-upload-form">
|
|
|
|
|
|
<div class="media-upload-row">
|
|
|
|
|
|
<label class="admin-btn admin-btn-primary">
|
|
|
|
|
|
<.icon name="hero-arrow-up-tray" class="size-4" /> Upload image
|
|
|
|
|
|
<.live_file_input upload={@uploads.media_upload} class="sr-only" />
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
name="value"
|
|
|
|
|
|
value={@upload_alt}
|
|
|
|
|
|
placeholder="Alt text (recommended)"
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
|
class="admin-input admin-input-fill"
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
phx-debounce="200"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
<%= for entry <- @uploads.media_upload.entries do %>
|
|
|
|
|
|
<div class="media-upload-progress">
|
|
|
|
|
|
<span>{entry.client_name}</span>
|
|
|
|
|
|
<progress value={entry.progress} max="100">{entry.progress}%</progress>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<% end %>
|
|
|
|
|
|
<%= for err <- upload_errors(@uploads.media_upload) do %>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
|
<p class="admin-error">{Phoenix.Naming.humanize(err)}</p>
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
<% end %>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<%!-- filter bar --%>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
|
<div class="admin-filter-row">
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
<form phx-change="filter_type" class="contents">
|
|
|
|
|
|
<select name="type" class="admin-select">
|
|
|
|
|
|
<option value="" selected={is_nil(@filter_type)}>All types</option>
|
|
|
|
|
|
<option value="media" selected={@filter_type == "media"}>Media</option>
|
|
|
|
|
|
<option value="product" selected={@filter_type == "product"}>Product</option>
|
|
|
|
|
|
<option value="logo" selected={@filter_type == "logo"}>Logo</option>
|
|
|
|
|
|
<option value="header" selected={@filter_type == "header"}>Header</option>
|
|
|
|
|
|
<option value="icon" selected={@filter_type == "icon"}>Icon</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="search"
|
|
|
|
|
|
placeholder="Search filename or alt text..."
|
|
|
|
|
|
value={@filter_search}
|
|
|
|
|
|
phx-keyup="filter_search"
|
|
|
|
|
|
phx-debounce="300"
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
|
class="admin-input admin-input-fill"
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
/>
|
|
|
|
|
|
<button
|
|
|
|
|
|
phx-click="toggle_orphans"
|
|
|
|
|
|
class={[
|
|
|
|
|
|
"admin-btn admin-btn-sm",
|
|
|
|
|
|
@filter_orphans && "admin-btn-primary",
|
|
|
|
|
|
!@filter_orphans && "admin-btn-ghost"
|
|
|
|
|
|
]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<.icon name="hero-trash" class="size-4" /> Orphans
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="media-main">
|
2026-03-01 09:42:34 +00:00
|
|
|
|
<%!-- image grid + pagination --%>
|
|
|
|
|
|
<div class="media-grid-wrapper">
|
|
|
|
|
|
<div id="media-grid" phx-update="stream" class="media-grid">
|
|
|
|
|
|
<div
|
|
|
|
|
|
:for={{dom_id, image} <- @streams.images}
|
|
|
|
|
|
id={dom_id}
|
|
|
|
|
|
phx-click="select_image"
|
|
|
|
|
|
phx-value-id={image.id}
|
|
|
|
|
|
class={[
|
|
|
|
|
|
"media-card",
|
|
|
|
|
|
@selected_image && @selected_image.id == image.id && "media-card-selected"
|
|
|
|
|
|
]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="media-card-thumb">
|
|
|
|
|
|
<%= if image.is_svg do %>
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
<div class="media-card-svg-placeholder">
|
2026-03-01 09:42:34 +00:00
|
|
|
|
<.icon name="hero-code-bracket" class="size-8" />
|
|
|
|
|
|
<span>SVG</span>
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
</div>
|
2026-03-01 09:42:34 +00:00
|
|
|
|
<% else %>
|
|
|
|
|
|
<%= if thumb = image_thumbnail_url(image) do %>
|
|
|
|
|
|
<img src={thumb} alt={image.alt || image.filename} loading="lazy" />
|
|
|
|
|
|
<% else %>
|
|
|
|
|
|
<div class="media-card-svg-placeholder">
|
|
|
|
|
|
<.icon name="hero-photo" class="size-8" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<% end %>
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
<% end %>
|
|
|
|
|
|
</div>
|
2026-03-01 09:42:34 +00:00
|
|
|
|
<div class="media-card-info">
|
|
|
|
|
|
<span class="media-card-filename" title={image.filename}>{image.filename}</span>
|
|
|
|
|
|
<div class="media-card-meta">
|
|
|
|
|
|
<span class={type_badge_class(image.image_type)}>{image.image_type}</span>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
|
<span class="media-card-size">{format_file_size(image.file_size)}</span>
|
2026-03-01 09:42:34 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<span
|
|
|
|
|
|
:if={!image.alt || image.alt == ""}
|
|
|
|
|
|
class="media-card-no-alt"
|
|
|
|
|
|
title="Missing alt text"
|
|
|
|
|
|
>
|
|
|
|
|
|
<.icon name="hero-exclamation-triangle" class="size-3" /> No alt text
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-03-01 09:42:34 +00:00
|
|
|
|
|
|
|
|
|
|
<.admin_pagination :if={@pagination} page={@pagination} patch={~p"/admin/media"} />
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<%!-- detail panel --%>
|
2026-02-28 01:00:48 +00:00
|
|
|
|
<div :if={@selected_image} class="media-detail-scrim" phx-click="deselect_image"></div>
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
<aside :if={@selected_image} class="media-detail">
|
|
|
|
|
|
<div class="media-detail-header">
|
|
|
|
|
|
<h3>Image details</h3>
|
|
|
|
|
|
<button phx-click="deselect_image" class="admin-btn admin-btn-sm admin-btn-ghost">
|
|
|
|
|
|
<.icon name="hero-x-mark" class="size-4" />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="media-detail-preview">
|
|
|
|
|
|
<%= if @selected_image.is_svg do %>
|
|
|
|
|
|
<div class="media-detail-svg">
|
|
|
|
|
|
<.icon name="hero-code-bracket" class="size-12" />
|
|
|
|
|
|
<span>SVG image</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<% else %>
|
|
|
|
|
|
<%= if thumb = image_thumbnail_url(@selected_image) do %>
|
|
|
|
|
|
<img src={thumb} alt={@selected_image.alt || @selected_image.filename} />
|
|
|
|
|
|
<% end %>
|
|
|
|
|
|
<% end %>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<dl class="media-detail-meta">
|
|
|
|
|
|
<dt>Filename</dt>
|
|
|
|
|
|
<dd>{@selected_image.filename}</dd>
|
|
|
|
|
|
<dt>Type</dt>
|
|
|
|
|
|
<dd>{@selected_image.image_type}</dd>
|
|
|
|
|
|
<dt>Size</dt>
|
|
|
|
|
|
<dd>{format_file_size(@selected_image.file_size)}</dd>
|
|
|
|
|
|
<dt>Dimensions</dt>
|
|
|
|
|
|
<dd>{format_dimensions(@selected_image.source_width, @selected_image.source_height)}</dd>
|
|
|
|
|
|
<dt>Uploaded</dt>
|
|
|
|
|
|
<dd>{Calendar.strftime(@selected_image.inserted_at, "%d %b %Y %H:%M")}</dd>
|
|
|
|
|
|
</dl>
|
|
|
|
|
|
|
|
|
|
|
|
<.form for={@edit_form} phx-submit="update_metadata" class="media-detail-form">
|
|
|
|
|
|
<.input field={@edit_form[:alt]} label="Alt text" placeholder="Describe this image..." />
|
|
|
|
|
|
<.input field={@edit_form[:caption]} label="Caption" placeholder="Optional caption..." />
|
|
|
|
|
|
<.input field={@edit_form[:tags]} label="Tags" placeholder="hero, homepage, banner..." />
|
2026-03-08 07:34:17 +00:00
|
|
|
|
<div class="admin-form-actions-sm">
|
|
|
|
|
|
<button type="submit" class="admin-btn admin-btn-primary admin-btn-sm">
|
|
|
|
|
|
Save metadata
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<.inline_feedback status={@metadata_status} />
|
|
|
|
|
|
</div>
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
</.form>
|
|
|
|
|
|
|
|
|
|
|
|
<%= if @selected_usages != [] do %>
|
|
|
|
|
|
<div class="media-detail-usages">
|
|
|
|
|
|
<h4>Used in</h4>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<%= for usage <- @selected_usages do %>
|
|
|
|
|
|
<li>
|
|
|
|
|
|
<span class={type_badge_class(to_string(usage.type))}>{usage.type}</span>
|
|
|
|
|
|
{usage.label}
|
|
|
|
|
|
</li>
|
|
|
|
|
|
<% end %>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<% end %>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="media-detail-actions">
|
|
|
|
|
|
<%= if @confirm_delete do %>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
|
<p class="admin-error">
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
<%= if @selected_usages != [] do %>
|
|
|
|
|
|
This image is in use. Deleting it may break pages.
|
|
|
|
|
|
<% else %>
|
|
|
|
|
|
Are you sure?
|
|
|
|
|
|
<% end %>
|
|
|
|
|
|
</p>
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
|
<div class="admin-row">
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
<button phx-click="delete_image" class="admin-btn admin-btn-sm admin-btn-danger">
|
|
|
|
|
|
Yes, delete
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button phx-click="cancel_delete" class="admin-btn admin-btn-sm admin-btn-ghost">
|
|
|
|
|
|
Cancel
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<% else %>
|
|
|
|
|
|
<button
|
|
|
|
|
|
phx-click="confirm_delete"
|
complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones)
- Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid
primitives and gap variants (sm, md, lg, xl)
- Add transitions.css import and layout.css import to admin.css entry point
- Replace all Tailwind utility classes across 26 admin templates with
semantic admin-*/theme-*/page-specific CSS classes
- Replace all non-dynamic inline styles with semantic classes
- Add ~100 new semantic classes to components.css (analytics, dashboard,
order detail, settings, theme editor, generic utilities)
- Fix stray text-error → admin-text-error in media.ex
- Add missing .truncate definition to admin CSS
- Only remaining inline styles are dynamic data values (progress bars,
chart dimensions) and one JS.toggle target
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:40:21 +00:00
|
|
|
|
class="admin-btn admin-btn-sm admin-btn-ghost admin-text-error"
|
add admin media library with image management and block picker integration
- Schema: alt, caption, tags fields on images table with metadata changeset
- Context: list_images with filters, find_usages, used_image_ids, delete_with_cleanup
- Admin UI: /admin/media with grid view, upload, filters, detail panel, metadata editing
- Block editor: :image field type for image_text and content_body blocks
- Page renderer: image_id resolution with legacy URL fallback
- Mobile: bottom sheet detail panel with slide-up animation
- CSS: uses correct --t-* admin theme tokens, admin-badge colour variants
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:20:51 +00:00
|
|
|
|
>
|
|
|
|
|
|
<.icon name="hero-trash" class="size-4" /> Delete image
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<% end %>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</aside>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
"""
|
|
|
|
|
|
end
|
|
|
|
|
|
end
|