defmodule BerrypodWeb.Admin.Pages.Editor do use BerrypodWeb, :live_view alias Berrypod.Pages alias Berrypod.Pages.{BlockTypes, Defaults} @impl true def mount(%{"slug" => slug}, _session, socket) do page = Pages.get_page(slug) allowed_blocks = BlockTypes.allowed_for(slug) {:ok, socket |> assign(:page_title, page.title) |> assign(:slug, slug) |> assign(:page_data, page) |> assign(:blocks, page.blocks) |> assign(:allowed_blocks, allowed_blocks) |> assign(:dirty, false) |> assign(:show_picker, false) |> assign(:picker_filter, "") |> assign(:expanded, MapSet.new()) |> assign(:live_region_message, nil)} end @impl true def handle_event("move_up", %{"id" => block_id}, socket) do blocks = socket.assigns.blocks idx = Enum.find_index(blocks, &(&1["id"] == block_id)) if idx && idx > 0 do block = Enum.at(blocks, idx) block_name = block_display_name(block) new_pos = idx new_blocks = blocks |> List.delete_at(idx) |> List.insert_at(idx - 1, block) {:noreply, socket |> assign(:blocks, new_blocks) |> assign(:dirty, true) |> assign(:live_region_message, "#{block_name} moved to position #{new_pos}")} else {:noreply, socket} end end def handle_event("move_down", %{"id" => block_id}, socket) do blocks = socket.assigns.blocks idx = Enum.find_index(blocks, &(&1["id"] == block_id)) if idx && idx < length(blocks) - 1 do block = Enum.at(blocks, idx) block_name = block_display_name(block) new_pos = idx + 2 new_blocks = blocks |> List.delete_at(idx) |> List.insert_at(idx + 1, block) {:noreply, socket |> assign(:blocks, new_blocks) |> assign(:dirty, true) |> assign(:live_region_message, "#{block_name} moved to position #{new_pos}")} else {:noreply, socket} end end def handle_event("remove_block", %{"id" => block_id}, socket) do block = Enum.find(socket.assigns.blocks, &(&1["id"] == block_id)) block_name = block_display_name(block) new_blocks = Enum.reject(socket.assigns.blocks, &(&1["id"] == block_id)) {:noreply, socket |> assign(:blocks, new_blocks) |> assign(:dirty, true) |> assign(:live_region_message, "#{block_name} removed")} end def handle_event("duplicate_block", %{"id" => block_id}, socket) do blocks = socket.assigns.blocks idx = Enum.find_index(blocks, &(&1["id"] == block_id)) if idx do original = Enum.at(blocks, idx) copy = %{ "id" => Defaults.generate_block_id(), "type" => original["type"], "settings" => original["settings"] || %{} } block_name = block_display_name(original) new_blocks = List.insert_at(blocks, idx + 1, copy) {:noreply, socket |> assign(:blocks, new_blocks) |> assign(:dirty, true) |> assign(:live_region_message, "#{block_name} duplicated")} else {:noreply, socket} end end def handle_event("toggle_expand", %{"id" => block_id}, socket) do expanded = socket.assigns.expanded block = Enum.find(socket.assigns.blocks, &(&1["id"] == block_id)) block_name = block_display_name(block) {new_expanded, action} = if MapSet.member?(expanded, block_id) do {MapSet.delete(expanded, block_id), "collapsed"} else {MapSet.put(expanded, block_id), "expanded"} end {:noreply, socket |> assign(:expanded, new_expanded) |> assign(:live_region_message, "#{block_name} settings #{action}")} end def handle_event("update_block_settings", params, socket) do block_id = params["block_id"] new_settings = params["block_settings"] || %{} # Find the block and its schema to coerce types block = Enum.find(socket.assigns.blocks, &(&1["id"] == block_id)) if block do schema = case BlockTypes.get(block["type"]) do %{settings_schema: s} -> s _ -> [] end coerced = coerce_settings(new_settings, schema) new_blocks = Enum.map(socket.assigns.blocks, fn b -> if b["id"] == block_id do Map.put(b, "settings", Map.merge(b["settings"] || %{}, coerced)) else b end end) {:noreply, socket |> assign(:blocks, new_blocks) |> assign(:dirty, true)} else {:noreply, socket} end end def handle_event("show_picker", _params, socket) do {:noreply, socket |> assign(:show_picker, true) |> assign(:picker_filter, "")} end def handle_event("hide_picker", _params, socket) do {:noreply, assign(socket, :show_picker, false)} end def handle_event("filter_picker", %{"value" => value}, socket) do {:noreply, assign(socket, :picker_filter, value)} end def handle_event("add_block", %{"type" => type}, socket) do block_def = BlockTypes.get(type) if block_def do # Build default settings from schema default_settings = block_def |> Map.get(:settings_schema, []) |> Enum.into(%{}, fn field -> {field.key, field.default} end) new_block = %{ "id" => Defaults.generate_block_id(), "type" => type, "settings" => default_settings } {:noreply, socket |> assign(:blocks, socket.assigns.blocks ++ [new_block]) |> assign(:dirty, true) |> assign(:show_picker, false) |> assign(:live_region_message, "#{block_def.name} added")} else {:noreply, socket} end end def handle_event("save", _params, socket) do %{slug: slug, blocks: blocks, page_data: page_data} = socket.assigns case Pages.save_page(slug, %{title: page_data.title, blocks: blocks}) do {:ok, _page} -> {:noreply, socket |> assign(:dirty, false) |> put_flash(:info, "Page saved")} {:error, _changeset} -> {:noreply, put_flash(socket, :error, "Failed to save page")} end end def handle_event("reset_defaults", _params, socket) do slug = socket.assigns.slug :ok = Pages.reset_page(slug) page = Pages.get_page(slug) {:noreply, socket |> assign(:blocks, page.blocks) |> assign(:dirty, false) |> put_flash(:info, "Page reset to defaults")} end @impl true def render(assigns) do ~H"""
Unsaved changes
<%!-- Block list --%>No blocks on this page yet.