implement unified on-site editor phases 1-2
All checks were successful
deploy / deploy (push) Successful in 1m10s
All checks were successful
deploy / deploy (push) Successful in 1m10s
Add theme editing to the existing PageEditorHook, enabling on-site theme customisation alongside page editing. The editor panel now has three tabs (Page, Theme, Settings) and can be collapsed while keeping editing state intact. - Add theme editing state and event handlers to PageEditorHook - Add 3-tab UI with tab switching logic - Add transparent overlay for click-outside dismiss - Add mobile drag-to-resize with height persistence - Fix animation replay on drag release (has-dragged class) - Preserve panel height across LiveView re-renders - Default to Page tab on editable pages, Theme otherwise - Show unsaved changes indicator on FAB when panel collapsed - Fix handle_event grouping warning in admin theme Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -81,14 +81,18 @@ defmodule BerrypodWeb.PageRenderer do
|
||||
</main>
|
||||
</.shop_layout>
|
||||
|
||||
<%!-- Editor sheet for page editing --%>
|
||||
<%!-- Editor sheet for page/theme/settings editing --%>
|
||||
<.editor_sheet
|
||||
editing={@editing}
|
||||
theme_editing={Map.get(assigns, :theme_editing, false)}
|
||||
editor_dirty={@editor_dirty}
|
||||
editor_sheet_state={assigns[:editor_sheet_state] || :collapsed}
|
||||
editor_save_status={@editor_save_status}
|
||||
editor_active_tab={Map.get(assigns, :editor_active_tab, :page)}
|
||||
has_editable_page={@page != nil}
|
||||
>
|
||||
<.editor_sheet_content
|
||||
<.editor_panel_content
|
||||
editor_active_tab={Map.get(assigns, :editor_active_tab, :page)}
|
||||
page={@page}
|
||||
editing_blocks={@editing_blocks}
|
||||
editor_history={@editor_history}
|
||||
@@ -103,13 +107,247 @@ defmodule BerrypodWeb.PageRenderer do
|
||||
editor_image_picker_images={@editor_image_picker_images}
|
||||
editor_image_picker_search={@editor_image_picker_search}
|
||||
editor_at_defaults={Map.get(assigns, :editor_at_defaults, true)}
|
||||
theme_editor_settings={Map.get(assigns, :theme_editor_settings)}
|
||||
theme_editor_active_preset={Map.get(assigns, :theme_editor_active_preset)}
|
||||
theme_editor_presets={Map.get(assigns, :theme_editor_presets, [])}
|
||||
theme_editor_customise_open={Map.get(assigns, :theme_editor_customise_open, false)}
|
||||
site_name={Map.get(assigns, :site_name, "")}
|
||||
/>
|
||||
</.editor_sheet>
|
||||
"""
|
||||
end
|
||||
|
||||
# Editor panel content dispatcher - shows content based on active tab
|
||||
attr :editor_active_tab, :atom, default: :page
|
||||
attr :page, :map, default: nil
|
||||
attr :editing_blocks, :list, default: nil
|
||||
attr :editor_history, :list, default: []
|
||||
attr :editor_future, :list, default: []
|
||||
attr :editor_dirty, :boolean, default: false
|
||||
attr :editor_live_region_message, :string, default: nil
|
||||
attr :editor_expanded, :any, default: nil
|
||||
attr :editor_show_picker, :boolean, default: false
|
||||
attr :editor_picker_filter, :string, default: ""
|
||||
attr :editor_allowed_blocks, :list, default: nil
|
||||
attr :editor_image_picker_block_id, :string, default: nil
|
||||
attr :editor_image_picker_images, :list, default: []
|
||||
attr :editor_image_picker_search, :string, default: ""
|
||||
attr :editor_at_defaults, :boolean, default: true
|
||||
attr :theme_editor_settings, :map, default: nil
|
||||
attr :theme_editor_active_preset, :atom, default: nil
|
||||
attr :theme_editor_presets, :list, default: []
|
||||
attr :theme_editor_customise_open, :boolean, default: false
|
||||
attr :site_name, :string, default: ""
|
||||
|
||||
defp editor_panel_content(%{editor_active_tab: :page} = assigns) do
|
||||
~H"""
|
||||
<.editor_sheet_content
|
||||
page={@page}
|
||||
editing_blocks={@editing_blocks}
|
||||
editor_history={@editor_history}
|
||||
editor_future={@editor_future}
|
||||
editor_dirty={@editor_dirty}
|
||||
editor_live_region_message={@editor_live_region_message}
|
||||
editor_expanded={@editor_expanded}
|
||||
editor_show_picker={@editor_show_picker}
|
||||
editor_picker_filter={@editor_picker_filter}
|
||||
editor_allowed_blocks={@editor_allowed_blocks}
|
||||
editor_image_picker_block_id={@editor_image_picker_block_id}
|
||||
editor_image_picker_images={@editor_image_picker_images}
|
||||
editor_image_picker_search={@editor_image_picker_search}
|
||||
editor_at_defaults={@editor_at_defaults}
|
||||
/>
|
||||
"""
|
||||
end
|
||||
|
||||
defp editor_panel_content(%{editor_active_tab: :theme} = assigns) do
|
||||
~H"""
|
||||
<.theme_editor_content
|
||||
theme_editor_settings={@theme_editor_settings}
|
||||
theme_editor_active_preset={@theme_editor_active_preset}
|
||||
theme_editor_presets={@theme_editor_presets}
|
||||
theme_editor_customise_open={@theme_editor_customise_open}
|
||||
site_name={@site_name}
|
||||
/>
|
||||
"""
|
||||
end
|
||||
|
||||
defp editor_panel_content(%{editor_active_tab: :settings} = assigns) do
|
||||
~H"""
|
||||
<.settings_editor_content page={@page} site_name={@site_name} />
|
||||
"""
|
||||
end
|
||||
|
||||
# Theme editor content - shows theme controls
|
||||
attr :theme_editor_settings, :map, default: nil
|
||||
attr :theme_editor_active_preset, :atom, default: nil
|
||||
attr :theme_editor_presets, :list, default: []
|
||||
attr :theme_editor_customise_open, :boolean, default: false
|
||||
attr :site_name, :string, default: ""
|
||||
|
||||
defp theme_editor_content(assigns) do
|
||||
~H"""
|
||||
<div class="editor-theme-content">
|
||||
<%= if @theme_editor_settings do %>
|
||||
<%!-- Shop name --%>
|
||||
<div class="theme-section">
|
||||
<label class="theme-section-label">Shop name</label>
|
||||
<form phx-change="theme_update_setting" phx-value-field="site_name">
|
||||
<input
|
||||
type="text"
|
||||
name="site_name"
|
||||
value={@site_name}
|
||||
placeholder="Your shop name"
|
||||
class="admin-input"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<%!-- Presets --%>
|
||||
<div class="theme-section">
|
||||
<label class="theme-section-label">Preset</label>
|
||||
<div class="theme-presets">
|
||||
<%= for {preset_name, description} <- @theme_editor_presets do %>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="theme_apply_preset"
|
||||
phx-value-preset={preset_name}
|
||||
class={[
|
||||
"theme-preset",
|
||||
@theme_editor_active_preset == preset_name && "theme-preset-active"
|
||||
]}
|
||||
>
|
||||
<div class="theme-preset-name">{preset_name}</div>
|
||||
<div class="theme-preset-desc">{description}</div>
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%!-- Mood --%>
|
||||
<div class="theme-section">
|
||||
<label class="theme-section-label">Colour mood</label>
|
||||
<div class="theme-chips">
|
||||
<%= for mood <- ["warm", "neutral", "cool", "dark"] do %>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="theme_update_setting"
|
||||
phx-value-field="mood"
|
||||
phx-value-setting_value={mood}
|
||||
class={["theme-chip", @theme_editor_settings.mood == mood && "theme-chip-active"]}
|
||||
>
|
||||
{mood}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%!-- Typography --%>
|
||||
<div class="theme-section">
|
||||
<label class="theme-section-label">Font style</label>
|
||||
<div class="theme-chips">
|
||||
<%= for typo <- ["clean", "editorial", "modern", "classic", "friendly", "minimal"] do %>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="theme_update_setting"
|
||||
phx-value-field="typography"
|
||||
phx-value-setting_value={typo}
|
||||
class={[
|
||||
"theme-chip",
|
||||
@theme_editor_settings.typography == typo && "theme-chip-active"
|
||||
]}
|
||||
>
|
||||
{typo}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%!-- Shape --%>
|
||||
<div class="theme-section">
|
||||
<label class="theme-section-label">Corner style</label>
|
||||
<div class="theme-chips">
|
||||
<%= for shape <- ["sharp", "soft", "round", "pill"] do %>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="theme_update_setting"
|
||||
phx-value-field="shape"
|
||||
phx-value-setting_value={shape}
|
||||
class={["theme-chip", @theme_editor_settings.shape == shape && "theme-chip-active"]}
|
||||
>
|
||||
{shape}
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%!-- More options link --%>
|
||||
<details
|
||||
class="theme-customise"
|
||||
id="theme-customise-section"
|
||||
open={@theme_editor_customise_open}
|
||||
>
|
||||
<summary class="theme-customise-summary" phx-click="theme_toggle_customise">
|
||||
<span class="theme-customise-label">More options</span>
|
||||
<svg
|
||||
class="theme-customise-chevron"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</summary>
|
||||
<div class="theme-customise-body">
|
||||
<p class="admin-text-secondary">
|
||||
For full theme customisation including branding, colours, and layout, <a
|
||||
href="/admin/theme"
|
||||
class="admin-link"
|
||||
>visit the theme editor</a>.
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
<% else %>
|
||||
<p class="admin-text-secondary">Loading theme settings...</p>
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
# Settings editor content - shows page/shop settings
|
||||
attr :page, :map, default: nil
|
||||
attr :site_name, :string, default: ""
|
||||
|
||||
defp settings_editor_content(assigns) do
|
||||
~H"""
|
||||
<div class="editor-settings-content">
|
||||
<%= if @page do %>
|
||||
<div class="theme-section">
|
||||
<label class="theme-section-label">Page</label>
|
||||
<p class="admin-text-secondary">{@page.title}</p>
|
||||
</div>
|
||||
<div class="theme-section">
|
||||
<p class="admin-text-secondary">
|
||||
Page settings like SEO, visibility, and slug editing coming soon.
|
||||
For now, <a href="/admin/pages" class="admin-link">manage pages in admin</a>.
|
||||
</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="theme-section">
|
||||
<p class="admin-text-secondary">
|
||||
This page doesn't have editable settings.
|
||||
<a href="/admin/settings" class="admin-link">Shop settings</a>
|
||||
can be changed in admin.
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
# Editor sheet content - the block list and editing controls
|
||||
attr :page, :map, required: true
|
||||
attr :page, :map, default: nil
|
||||
attr :editing_blocks, :list, default: nil
|
||||
attr :editor_history, :list, default: []
|
||||
attr :editor_future, :list, default: []
|
||||
|
||||
Reference in New Issue
Block a user