Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use turbo frame and stream to create element #3053

Merged
merged 3 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/controllers/alchemy/admin/elements_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ def create
end
end
if @element.save
render :create
render :create, status: :created
else
@element.page_version = @page_version
@elements = @page.available_element_definitions
load_clipboard_items
render :new
render :new, status: :unprocessable_entity
end
end

Expand Down
2 changes: 1 addition & 1 deletion app/helpers/alchemy/admin/form_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module FormHelper
def alchemy_form_for(object, *args, &block)
options = args.extract_options!
options[:builder] = Alchemy::Forms::Builder
options[:remote] = request.xhr?
options.key?(:remote) || options[:remote] = request.xhr?
options[:html] = {
id: options.delete(:id),
class: ["alchemy", options.delete(:class)].compact.join(" ")
Expand Down
3 changes: 2 additions & 1 deletion app/javascript/alchemy_admin/components/action.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { reloadPreview } from "alchemy_admin/components/preview_window"
import { closeCurrentDialog } from "alchemy_admin/dialog"
import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"

class Action extends HTMLElement {
Expand All @@ -10,7 +11,7 @@ class Action extends HTMLElement {
// add a intermediate closeCurrentDialog - action
// this will be gone, if all dialogs are working with a promise and
// we don't have to implicitly close the dialog
closeCurrentDialog: Alchemy.closeCurrentDialog,
closeCurrentDialog,
reloadPreview,
updateAnchorIcon: IngredientAnchorLink.updateIcon
}
Expand Down
9 changes: 9 additions & 0 deletions app/javascript/alchemy_admin/components/element_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ export class ElementEditor extends HTMLElement {
return
}

// When newly created, focus the element and refresh the preview
if (this.hasAttribute("created")) {
this.focusElement()
this.previewWindow?.refresh().then(() => {
this.focusElementPreview()
})
this.removeAttribute("created")
}

// Init GUI elements
ImageLoader.init(this)
fileEditors(
Expand Down
12 changes: 12 additions & 0 deletions app/javascript/alchemy_admin/dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,19 @@ export class Dialog {

// Initializes the Dialog body
init() {
const turbo_frame = this.dialog_body[0].querySelector("turbo-frame")

Hotkeys(this.dialog_body)

// Re-render dialog body if turbo frame render returned error
if (turbo_frame) {
turbo_frame.addEventListener("turbo:frame-render", (event) => {
if (!event.detail.fetchResponse.ok) {
this.watch_remote_forms()
}
})
}

this.watch_remote_forms()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
(nestable_element = element.nestable_elements.first) &&
Alchemy::Element.all_from_clipboard_for_parent_element(get_clipboard("elements"), element).none?
%>
<%= form_for [:admin, Alchemy::Element.new(name: nestable_element)],
remote: true, html: { class: 'add-nested-element-form', id: nil } do |f| %>
<%= f.hidden_field :name %>
<%= f.hidden_field :page_version_id, value: element.page_version_id %>
<%= f.hidden_field :parent_element_id, value: element.id %>
<button class="add-nestable-element-button" is="alchemy-button" data-turbo="false">
<%= Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element.to_sym, scope: 'element_names')) %>
</button>
<%= turbo_frame_tag("new_nested_element_#{element.id}") do %>
<%= form_for [:admin, Alchemy::Element.new(name: nestable_element)], html: { class: 'add-nested-element-form', id: nil } do |f| %>
<%= f.hidden_field :name %>
<%= f.hidden_field :page_version_id, value: element.page_version_id %>
<%= f.hidden_field :parent_element_id, value: element.id %>
<button class="add-nestable-element-button" is="alchemy-button">
<%= Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element.to_sym, scope: 'element_names')) %>
</button>
<% end %>
<% end %>
<% else %>
<%= link_to_dialog (nestable_element ? Alchemy.t(:add_nested_element, name: Alchemy.t(nestable_element.to_sym, scope: 'element_names')) : Alchemy.t("New Element")),
Expand Down
14 changes: 14 additions & 0 deletions app/views/alchemy/admin/elements/_clipboard_button.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<%= render Alchemy::Admin::ToolbarButton.new(
url: alchemy.admin_clipboard_path(remarkable_type: "elements"),
label: Alchemy.t("Show clipboard"),
icon: :clipboard,
icon_style: clipboard_empty?("elements") ? "line" : "fill",
dialog_options: {
title: Alchemy.t("Clipboard"),
size: "400x305"
},
link_options: {
id: "clipboard_button"
},
if_permitted_to: [:index, :alchemy_admin_clipboard]
) %>
2 changes: 2 additions & 0 deletions app/views/alchemy/admin/elements/_element.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
data-element-name="<%= element.name %>"
class="<%= element.css_classes.join(" ") %>"
<%= element.compact? ? "compact" : nil %>
<%= local_assigns[:created] ? "created" : nil %>
<%= element.fixed? ? "fixed" : nil %>
>
<% unless element.fixed? %>
Expand Down Expand Up @@ -68,6 +69,7 @@
<% if element.nestable_elements.any? %>
<div class="nestable-elements">
<%= content_tag :div,
id: "element_#{element.id}_nested_elements",
class: "nested-elements", data: {
'droppable-elements' => element.nestable_elements.join(' '),
'element-name' => element.name
Expand Down
28 changes: 15 additions & 13 deletions app/views/alchemy/admin/elements/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
<%= Alchemy.t(:no_more_elements_to_add) %>
<% end %>
<%- else -%>
<%= alchemy_form_for [:admin, @element] do |form| %>
<%= form.hidden_field :page_version_id %>
<%= form.input :name,
label: Alchemy.t(:element_of_type),
collection: elements_for_select(@elements),
prompt: Alchemy.t(:select_element),
selected: (@elements.first if @elements.count == 1),
input_html: {is: 'alchemy-select', autofocus: true} %>
<% if @elements.count == 1 %>
<%= form.hidden_field :name, value: @elements.first[:name] %>
<% end %>
<%= form.hidden_field :parent_element_id, value: @parent_element.try(:id) %>
<%= form.submit Alchemy.t(:add) %>
<%= turbo_frame_tag @element do %>
<%= alchemy_form_for [:admin, @element], remote: false do |form| %>
<%= form.hidden_field :page_version_id %>
<%= form.input :name,
label: Alchemy.t(:element_of_type),
collection: elements_for_select(@elements),
prompt: Alchemy.t(:select_element),
selected: (@elements.first if @elements.count == 1),
input_html: {is: 'alchemy-select', autofocus: true} %>
<% if @elements.count == 1 %>
<%= form.hidden_field :name, value: @elements.first[:name] %>
<% end %>
<%= form.hidden_field :parent_element_id, value: @parent_element.try(:id) %>
<%= form.submit Alchemy.t(:add) %>
<%- end -%>
<%- end -%>
<%- end -%>
35 changes: 0 additions & 35 deletions app/views/alchemy/admin/elements/create.js.erb

This file was deleted.

34 changes: 34 additions & 0 deletions app/views/alchemy/admin/elements/create.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<% opts = {
partial: "alchemy/admin/elements/element",
locals: {
element: Alchemy::ElementEditor.new(@element),
created: true
}
} %>

<% if @element.fixed? %>
<% target = "fixed_element_#{@element.id}" %>
<% elsif @element.parent_element %>
<% target = "element_#{@element.parent_element_id}_nested_elements" %>
<% else %>
<% target = "main-content-elements" %>
<% end %>

<%- if @cut_element_id -%>
<%= turbo_stream.remove "element_#{@cut_element_id}" %>
<% end %>

<% if @insert_at_top %>
<%= turbo_stream.prepend target, **opts %>
<% else %>
<%= turbo_stream.append target, **opts %>
<% end %>

<%= turbo_stream.replace "clipboard_button",
partial: "alchemy/admin/elements/clipboard_button" %>

<alchemy-growl>
<%= Alchemy.t(:successfully_added_element) %>
</alchemy-growl>

<alchemy-action name="closeCurrentDialog"></alchemy-action>
17 changes: 2 additions & 15 deletions app/views/alchemy/admin/elements/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,7 @@
},
if_permitted_to: [:create, Alchemy::Element]
) %>
<%= render Alchemy::Admin::ToolbarButton.new(
url: alchemy.admin_clipboard_path(remarkable_type: "elements"),
label: Alchemy.t("Show clipboard"),
icon: :clipboard,
icon_style: clipboard_empty?("elements") ? "line" : "fill",
dialog_options: {
title: Alchemy.t("Clipboard"),
size: "400x305"
},
link_options: {
id: "clipboard_button"
},
if_permitted_to: [:index, :alchemy_admin_clipboard]
) %>
<%= render "alchemy/admin/elements/clipboard_button" %>
<sl-tooltip content="<%= Alchemy.t("Collapse all elements") %>" placement="top-end" class="right">
<button id="collapse-all-elements-button" class="icon_button">
<alchemy-icon name="contract-up-down"></alchemy-icon>
Expand Down Expand Up @@ -53,7 +40,7 @@
<%= render @elements.map { |element| Alchemy::ElementEditor.new(element) } %>
</sl-tab-panel>
<% @fixed_elements.each do |element| %>
<sl-tab-panel name="fixed-element-<%= element.id %>" style="--padding: 0" class="scrollable-elements">
<sl-tab-panel id="fixed_element_<%= element.id %>" name="fixed-element-<%= element.id %>" style="--padding: 0" class="scrollable-elements">
<%= render Alchemy::ElementEditor.new(element) %>
</sl-tab-panel>
<% end %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ module Alchemy

it "should create an element from clipboard" do
post :create, params: {paste_from_clipboard: element_in_clipboard.id, element: {page_version_id: page_version.id}}, xhr: true
expect(response.status).to eq(200)
expect(response.status).to eq(201)
expect(response.body).to match(/Successfully added new element/)
end

Expand Down
46 changes: 46 additions & 0 deletions spec/helpers/alchemy/admin/form_helper_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require "rails_helper"

RSpec.describe Alchemy::Admin::FormHelper do
describe "#alchemy_form_for" do
subject { helper.alchemy_form_for(resource) {} }

let(:resource) do
[alchemy, :admin, Alchemy::Element.new(name: "article")]
end

it "returns a form with alchemy class" do
expect(subject).to have_css(".alchemy")
end

context "if options[:remote] is given" do
context "and set to true" do
subject { helper.alchemy_form_for(resource, remote: true) {} }

it "makes the form remote" do
expect(subject).to have_css("form[data-remote]")
end
end

context "and set to false" do
subject { helper.alchemy_form_for(resource, remote: false) {} }

it "makes the form non-remote" do
expect(subject).to have_css("form")
expect(subject).to_not have_css("form[data-remote]")
end
end
end

context "if options[:remote] is not given" do
context "and request is xhr" do
before do
allow(helper).to receive(:request).and_return(double(xhr?: true))
end

it "makes the form remote" do
expect(subject).to have_css("form[data-remote]")
end
end
end
end
end
11 changes: 9 additions & 2 deletions spec/javascript/alchemy_admin/components/action.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import "alchemy_admin/components/action"
import { renderComponent } from "./component.helper"
import { closeCurrentDialog } from "alchemy_admin/dialog"
import * as PreviewWindow from "alchemy_admin/components/preview_window"
import IngredientAnchorLink from "alchemy_admin/ingredient_anchor_link"

jest.mock("alchemy_admin/dialog", () => {
return {
__esModule: true,
closeCurrentDialog: jest.fn()
}
})

jest.mock("alchemy_admin/components/preview_window", () => {
return {
__esModule: true,
Expand Down Expand Up @@ -39,11 +47,10 @@ describe("alchemy-action", () => {
})

it("call closeCurrentDialog function ", () => {
window.Alchemy.closeCurrentDialog = jest.fn()
renderComponent(
"alchemy-action",
`<alchemy-action name="closeCurrentDialog"></alchemy-action>`
)
expect(window.Alchemy.closeCurrentDialog).toBeCalled()
expect(closeCurrentDialog).toBeCalled()
})
})