avo 2.42.1 → 2.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of avo might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +118 -96
- data/app/components/avo/fields/belongs_to_field/edit_component.rb +11 -0
- data/app/components/avo/fields/tags_field/edit_component.html.erb +1 -0
- data/app/components/avo/modal_component.html.erb +17 -10
- data/app/components/avo/modal_component.rb +21 -0
- data/app/components/avo/panel_component.html.erb +2 -2
- data/app/components/avo/referrer_params_component.html.erb +1 -0
- data/app/components/avo/views/resource_edit_component.html.erb +11 -6
- data/app/components/avo/views/resource_edit_component.rb +11 -1
- data/app/controllers/avo/actions_controller.rb +1 -1
- data/app/controllers/avo/base_controller.rb +11 -2
- data/app/helpers/avo/application_helper.rb +4 -0
- data/app/javascript/js/controllers/fields/reload_belongs_to_field_controller.js +51 -0
- data/app/javascript/js/controllers/fields/tags_field_controller.js +2 -1
- data/app/javascript/js/controllers.js +2 -0
- data/app/views/avo/actions/show.html.erb +2 -0
- data/app/views/avo/base/_new_via_belongs_to.html.erb +12 -0
- data/app/views/avo/base/close_modal_and_reload_field.turbo_stream.erb +8 -0
- data/app/views/avo/base/create_fail_action.turbo_stream.erb +13 -0
- data/app/views/avo/partials/_flash_alerts.turbo_stream.erb +3 -0
- data/lib/avo/app.rb +1 -20
- data/lib/avo/configuration.rb +29 -0
- data/lib/avo/execution_context.rb +3 -3
- data/lib/avo/fields/tags_field.rb +2 -0
- data/lib/avo/licensing/h_q.rb +4 -2
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/initializer/avo.tt +7 -0
- data/public/avo-assets/avo.base.css +20 -8
- data/public/avo-assets/avo.base.js +84 -84
- data/public/avo-assets/avo.base.js.map +3 -3
- metadata +7 -3
- data/app/views/avo/actions/keep_modal_open.turbo_stream.erb +0 -5
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: e46a221f03c28ba77e3a9ab32d841176eb9ecf7125161742ad0e1c222063d1b0
         | 
| 4 | 
            +
              data.tar.gz: 53a6aced2acfe8347472915e2d1c757ab7125b4189c333b1a849b23e51e306f5
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 6c911463f13e7556ac9a9340ab45d67e18ee9130a542d5d12db3d728dbf62e4abe74f09ffc6cf9bfb4fa5b578396461715ba47ffc2a006680a6460ec25e748bd
         | 
| 7 | 
            +
              data.tar.gz: 7399602dd68b5442f19ed38a483373c95cde447a43fba2108e6e3e76905888f547841fd81675700c7467d0f035aab5a5563cc2a870ced29faf74581818266396
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
| @@ -1,115 +1,137 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
              %>
         | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
                <%= field_wrapper **field_wrapper_args, help: @field.polymorphic_help || '' do %>
         | 
| 16 | 
            -
                  <%= @form.select @field.type_input_foreign_key, @field.types.map { |type| [::Avo::App.get_resource_by_model_name(type.to_s).name, type.to_s] },
         | 
| 17 | 
            -
                  {
         | 
| 18 | 
            -
                    value: @field.value,
         | 
| 19 | 
            -
                    include_blank: @field.placeholder,
         | 
| 20 | 
            -
                  },
         | 
| 21 | 
            -
                  {
         | 
| 22 | 
            -
                    class: classes("w-full"),
         | 
| 23 | 
            -
                    data: {
         | 
| 24 | 
            -
                      **@field.get_html(:data, view: view, element: :input),
         | 
| 25 | 
            -
                      action: "change->belongs-to-field#changeType #{field_html_action}",
         | 
| 26 | 
            -
                      'belongs-to-field-target': "select",
         | 
| 27 | 
            -
                    },
         | 
| 28 | 
            -
                    disabled: disabled
         | 
| 29 | 
            -
                  }
         | 
| 1 | 
            +
            <div data-controller="reload-belongs-to-field"
         | 
| 2 | 
            +
                 data-action="turbo:before-stream-render@document->reload-belongs-to-field#beforeStreamRender"
         | 
| 3 | 
            +
                 data-reload-belongs-to-field-polymorphic-value="<%= is_polymorphic? %>"
         | 
| 4 | 
            +
                 data-reload-belongs-to-field-searchable-value="<%= @field.searchable %>"
         | 
| 5 | 
            +
                 data-reload-belongs-to-field-relation-name-value="<%= @field.id %>"
         | 
| 6 | 
            +
                 data-reload-belongs-to-field-target-name-value="<%= form.object_name %>[<%= @field.id_input_foreign_key %>]"
         | 
| 7 | 
            +
            >
         | 
| 8 | 
            +
              <% if is_polymorphic? %>
         | 
| 9 | 
            +
                <%
         | 
| 10 | 
            +
                  # Set the model keys so we can pass them over
         | 
| 11 | 
            +
                  model_keys = @field.types.map do |type|
         | 
| 12 | 
            +
                    resource = Avo::App.get_resource_by_model_name(type.to_s)
         | 
| 13 | 
            +
                    [type.to_s, resource.model_key]
         | 
| 14 | 
            +
                  end.to_h
         | 
| 30 15 | 
             
                %>
         | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 16 | 
            +
                <div class="divide-y"
         | 
| 17 | 
            +
                  data-controller="belongs-to-field"
         | 
| 18 | 
            +
                  data-searchable="<%= @field.searchable %>"
         | 
| 19 | 
            +
                  data-association="<%= @field.id %>"
         | 
| 20 | 
            +
                  data-association-class="<%= @field&.target_resource&.model_class || nil %>"
         | 
| 21 | 
            +
                >
         | 
| 22 | 
            +
                  <%= field_wrapper **field_wrapper_args, help: @field.polymorphic_help || '' do %>
         | 
| 23 | 
            +
                    <%= @form.select @field.type_input_foreign_key, @field.types.map { |type| [::Avo::App.get_resource_by_model_name(type.to_s).name, type.to_s] },
         | 
| 24 | 
            +
                    {
         | 
| 25 | 
            +
                      value: @field.value,
         | 
| 26 | 
            +
                      include_blank: @field.placeholder,
         | 
| 27 | 
            +
                    },
         | 
| 28 | 
            +
                    {
         | 
| 29 | 
            +
                      class: classes("w-full"),
         | 
| 30 | 
            +
                      data: {
         | 
| 31 | 
            +
                        **@field.get_html(:data, view: view, element: :input),
         | 
| 32 | 
            +
                        action: "change->belongs-to-field#changeType #{field_html_action}",
         | 
| 33 | 
            +
                        'belongs-to-field-target': "select",
         | 
| 34 | 
            +
                      },
         | 
| 35 | 
            +
                      disabled: disabled
         | 
| 36 | 
            +
                    }
         | 
| 37 | 
            +
                  %>
         | 
| 38 | 
            +
                    <%
         | 
| 39 | 
            +
                      # If the select field is disabled, no value will be sent. It's how HTML works.
         | 
| 40 | 
            +
                      # Thus the extra hidden field to actually send the related id to the server.
         | 
| 41 | 
            +
                      if disabled %>
         | 
| 42 | 
            +
                      <%= @form.hidden_field @field.type_input_foreign_key %>
         | 
| 43 | 
            +
                    <% end %>
         | 
| 36 44 | 
             
                  <% end %>
         | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
                     | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 45 | 
            +
                  <% @field.types.each do |type| %>
         | 
| 46 | 
            +
                    <div class="hidden"
         | 
| 47 | 
            +
                      data-belongs-to-field-target="type"
         | 
| 48 | 
            +
                      data-type="<%= type %>"
         | 
| 49 | 
            +
                    >
         | 
| 50 | 
            +
                      <%= field_wrapper **field_wrapper_args, label: ::Avo::App.get_resource_by_model_name(type.to_s).name do %>
         | 
| 51 | 
            +
                        <div class="flex flex-col gap-1">
         | 
| 52 | 
            +
                          <% if @field.searchable %>
         | 
| 53 | 
            +
                            <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
         | 
| 54 | 
            +
                              disabled: disabled,
         | 
| 55 | 
            +
                              field: @field,
         | 
| 56 | 
            +
                              foreign_key: @field.id_input_foreign_key,
         | 
| 57 | 
            +
                              model_key: model_keys[type.to_s],
         | 
| 58 | 
            +
                              polymorphic_record: polymorphic_record,
         | 
| 59 | 
            +
                              resource: @resource,
         | 
| 60 | 
            +
                              style: @field.get_html(:style, view: view, element: :input),
         | 
| 61 | 
            +
                              type: type,
         | 
| 62 | 
            +
                              classes: classes("w-full"),
         | 
| 63 | 
            +
                              view: view
         | 
| 64 | 
            +
                          %>
         | 
| 65 | 
            +
                          <% else %>
         | 
| 66 | 
            +
                            <%= @form.select @field.id_input_foreign_key,
         | 
| 67 | 
            +
                            options_for_select(@field.values_for_type(type), @resource.present? && @resource.model.present? ? @resource.model[@field.id_input_foreign_key] : nil),
         | 
| 68 | 
            +
                            {
         | 
| 69 | 
            +
                              value: @resource.model[@field.id_input_foreign_key].to_s,
         | 
| 70 | 
            +
                              include_blank: @field.placeholder,
         | 
| 71 | 
            +
                            },
         | 
| 72 | 
            +
                            {
         | 
| 73 | 
            +
                              class: classes("w-full"),
         | 
| 74 | 
            +
                              data: @field.get_html(:data, view: view, element: :input),
         | 
| 75 | 
            +
                              disabled: disabled
         | 
| 76 | 
            +
                            }
         | 
| 77 | 
            +
                          %>
         | 
| 78 | 
            +
                            <%
         | 
| 79 | 
            +
                            # If the select field is disabled, no value will be sent. It's how HTML works.
         | 
| 80 | 
            +
                            # Thus the extra hidden field to actually send the related id to the server.
         | 
| 81 | 
            +
                            if disabled %>
         | 
| 82 | 
            +
                              <%= @form.hidden_field @field.id_input_foreign_key %>
         | 
| 83 | 
            +
                            <% end %>
         | 
| 84 | 
            +
                          <% end %>
         | 
| 85 | 
            +
                          <% create_href = create_path(::Avo::App.get_resource_by_model_name(type.to_s)) %>
         | 
| 86 | 
            +
                          <% if !disabled && create_href.present? %>
         | 
| 87 | 
            +
                            <%= link_to t("avo.create_new_item", item: type.to_s.downcase),
         | 
| 88 | 
            +
                                        create_href,
         | 
| 89 | 
            +
                                        class: "text-sm"
         | 
| 90 | 
            +
                            %>
         | 
| 91 | 
            +
                          <% end %>
         | 
| 92 | 
            +
                        </div>
         | 
| 93 | 
            +
                      <% end %>
         | 
| 94 | 
            +
                    </div>
         | 
| 95 | 
            +
                  <% end %>
         | 
| 96 | 
            +
                </div>
         | 
| 97 | 
            +
              <% else %>
         | 
| 98 | 
            +
                <%= field_wrapper **field_wrapper_args do %>
         | 
| 99 | 
            +
                  <div class="flex flex-col gap-1">
         | 
| 100 | 
            +
                    <% if @field.searchable %>
         | 
| 101 | 
            +
                      <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
         | 
| 102 | 
            +
                        field: @field,
         | 
| 103 | 
            +
                        model_key: @field.target_resource&.model_key,
         | 
| 104 | 
            +
                        foreign_key: @field.id_input_foreign_key,
         | 
| 105 | 
            +
                        resource: @resource,
         | 
| 106 | 
            +
                        disabled: disabled,
         | 
| 107 | 
            +
                        classes: classes("w-full"),
         | 
| 108 | 
            +
                        view: view,
         | 
| 109 | 
            +
                        style: @field.get_html(:style, view: view, element: :input)
         | 
| 56 110 | 
             
                      %>
         | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
                        options_for_select(@field.values_for_type(type), @resource.present? && @resource.model.present? ? @resource.model[@field.id_input_foreign_key] : nil),
         | 
| 111 | 
            +
                    <% else %>
         | 
| 112 | 
            +
                      <%= @form.select @field.id_input_foreign_key, @field.options,
         | 
| 60 113 | 
             
                        {
         | 
| 61 | 
            -
                          value: @resource.model[@field.id_input_foreign_key].to_s,
         | 
| 62 114 | 
             
                          include_blank: @field.placeholder,
         | 
| 115 | 
            +
                          value: @field.value
         | 
| 63 116 | 
             
                        },
         | 
| 64 117 | 
             
                        {
         | 
| 65 118 | 
             
                          class: classes("w-full"),
         | 
| 66 119 | 
             
                          data: @field.get_html(:data, view: view, element: :input),
         | 
| 67 | 
            -
                          disabled: disabled
         | 
| 120 | 
            +
                          disabled: disabled,
         | 
| 121 | 
            +
                          style: @field.get_html(:style, view: view, element: :input)
         | 
| 68 122 | 
             
                        }
         | 
| 69 123 | 
             
                      %>
         | 
| 70 | 
            -
             | 
| 124 | 
            +
                      <%
         | 
| 71 125 | 
             
                        # If the select field is disabled, no value will be sent. It's how HTML works.
         | 
| 72 126 | 
             
                        # Thus the extra hidden field to actually send the related id to the server.
         | 
| 73 127 | 
             
                        if disabled %>
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                        <% end %>
         | 
| 128 | 
            +
                        <%= @form.hidden_field @field.id_input_foreign_key %>
         | 
| 76 129 | 
             
                      <% end %>
         | 
| 77 130 | 
             
                    <% end %>
         | 
| 131 | 
            +
                    <% if !disabled && create_path.present? %>
         | 
| 132 | 
            +
                      <%= link_to t("avo.create_new_item", item: @field.name.downcase), create_path, class: "text-sm" %>
         | 
| 133 | 
            +
                    <% end %>
         | 
| 78 134 | 
             
                  </div>
         | 
| 79 135 | 
             
                <% end %>
         | 
| 80 | 
            -
              </div>
         | 
| 81 | 
            -
            <% else %>
         | 
| 82 | 
            -
              <%= field_wrapper **field_wrapper_args do %>
         | 
| 83 | 
            -
                <% if @field.searchable %>
         | 
| 84 | 
            -
                  <%= render Avo::Fields::BelongsToField::AutocompleteComponent.new form: @form,
         | 
| 85 | 
            -
                    field: @field,
         | 
| 86 | 
            -
                    model_key: @field.target_resource&.model_key,
         | 
| 87 | 
            -
                    foreign_key: @field.id_input_foreign_key,
         | 
| 88 | 
            -
                    resource: @resource,
         | 
| 89 | 
            -
                    disabled: disabled,
         | 
| 90 | 
            -
                    classes: classes("w-full"),
         | 
| 91 | 
            -
                    view: view,
         | 
| 92 | 
            -
                    style: @field.get_html(:style, view: view, element: :input)
         | 
| 93 | 
            -
                  %>
         | 
| 94 | 
            -
                <% else %>
         | 
| 95 | 
            -
                  <%= @form.select @field.id_input_foreign_key, @field.options,
         | 
| 96 | 
            -
                    {
         | 
| 97 | 
            -
                      include_blank: @field.placeholder,
         | 
| 98 | 
            -
                      value: @field.value
         | 
| 99 | 
            -
                    },
         | 
| 100 | 
            -
                    {
         | 
| 101 | 
            -
                      class: classes("w-full"),
         | 
| 102 | 
            -
                      data: @field.get_html(:data, view: view, element: :input),
         | 
| 103 | 
            -
                      disabled: disabled,
         | 
| 104 | 
            -
                      style: @field.get_html(:style, view: view, element: :input)
         | 
| 105 | 
            -
                    }
         | 
| 106 | 
            -
                  %>
         | 
| 107 | 
            -
                  <%
         | 
| 108 | 
            -
                    # If the select field is disabled, no value will be sent. It's how HTML works.
         | 
| 109 | 
            -
                    # Thus the extra hidden field to actually send the related id to the server.
         | 
| 110 | 
            -
                    if disabled %>
         | 
| 111 | 
            -
                    <%= @form.hidden_field @field.id_input_foreign_key %>
         | 
| 112 | 
            -
                  <% end %>
         | 
| 113 | 
            -
                <% end %>
         | 
| 114 136 | 
             
              <% end %>
         | 
| 115 | 
            -
             | 
| 137 | 
            +
            </div>
         | 
| @@ -57,6 +57,17 @@ class Avo::Fields::BelongsToField::EditComponent < Avo::Fields::EditComponent | |
| 57 57 | 
             
                @field.get_html(:data, view: view, element: :input).fetch(:action, nil)
         | 
| 58 58 | 
             
              end
         | 
| 59 59 |  | 
| 60 | 
            +
              def create_path(target_resource = nil)
         | 
| 61 | 
            +
                return nil if @resource.blank?
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                helpers.new_resource_path(**{
         | 
| 64 | 
            +
                  via_relation: @field.id.to_s,
         | 
| 65 | 
            +
                  resource: target_resource || @field.target_resource,
         | 
| 66 | 
            +
                  via_resource_id: resource.model.to_param,
         | 
| 67 | 
            +
                  via_belongs_to_resource_class: resource.class.name
         | 
| 68 | 
            +
                }.compact)
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 60 71 | 
             
              private
         | 
| 61 72 |  | 
| 62 73 | 
             
              def visit_through_association?
         | 
| @@ -4,6 +4,7 @@ | |
| 4 4 | 
             
                  tags_field_whitelist_items_value: @field.suggestions.to_json,
         | 
| 5 5 | 
             
                  tags_field_disallowed_items_value: @field.disallowed.to_json,
         | 
| 6 6 | 
             
                  tags_field_enforce_suggestions_value: @field.enforce_suggestions,
         | 
| 7 | 
            +
                  tags_field_suggestions_max_items_value: @field.suggestions_max_items,
         | 
| 7 8 | 
             
                  tags_field_close_on_select_value: @field.close_on_select,
         | 
| 8 9 | 
             
                  tags_field_delimiters_value: @field.delimiters,
         | 
| 9 10 | 
             
                  tags_field_fetch_values_from_value: @field.fetch_values_from,
         | 
| @@ -3,19 +3,26 @@ | |
| 3 3 | 
             
              data-modal-target="modal"
         | 
| 4 4 | 
             
            >
         | 
| 5 5 | 
             
              <div aria-expanded="true" class="modal-overlay absolute w-full h-full bg-opacity-25 bg-gray-800 flex justify-center items-center" data-action="click->modal#close"></div>
         | 
| 6 | 
            -
              <div aria-expanded="true" role="dialog" aria-modal="true" class="modal-body rounded-lg inset-auto  | 
| 6 | 
            +
              <div aria-expanded="true" role="dialog" aria-modal="true" class="modal-body rounded-lg inset-auto bg-white flex z-50 relative shadow-modal overflow-auto  <%= width_classes %> <%= height_classes %>">
         | 
| 7 7 | 
             
                <div class="flex-1 flex flex-col justify-between">
         | 
| 8 8 | 
             
                  <div>
         | 
| 9 | 
            -
                     | 
| 10 | 
            -
                       | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 9 | 
            +
                    <% if heading? %>
         | 
| 10 | 
            +
                      <div class="p-6 text-2xl tracking-normal font-semibold text-black">
         | 
| 11 | 
            +
                        <%= heading %>
         | 
| 12 | 
            +
                      </div>
         | 
| 13 | 
            +
                    <% end %>
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    <% if content? %>
         | 
| 16 | 
            +
                      <div class="px-6 text-base text-gray-500 <%= body_class %>">
         | 
| 17 | 
            +
                        <%= content %>
         | 
| 18 | 
            +
                      </div>
         | 
| 19 | 
            +
                    <% end %>
         | 
| 18 20 | 
             
                  </div>
         | 
| 21 | 
            +
                  <% if controls? %>
         | 
| 22 | 
            +
                    <div class="flex justify-end items-baseline space-x-4 p-4 bg-gray-100">
         | 
| 23 | 
            +
                      <%= controls %>
         | 
| 24 | 
            +
                    </div>
         | 
| 25 | 
            +
                  <% end %>
         | 
| 19 26 | 
             
                </div>
         | 
| 20 27 | 
             
              </div>
         | 
| 21 28 | 
             
            </div>
         | 
| @@ -3,4 +3,25 @@ | |
| 3 3 | 
             
            class Avo::ModalComponent < ViewComponent::Base
         | 
| 4 4 | 
             
              renders_one :heading
         | 
| 5 5 | 
             
              renders_one :controls
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              attr_reader :width
         | 
| 8 | 
            +
              attr_reader :body_class
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              def initialize(width: :md, body_class: nil)
         | 
| 11 | 
            +
                @width = width
         | 
| 12 | 
            +
                @body_class = body_class
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def width_classes
         | 
| 16 | 
            +
                case width.to_sym
         | 
| 17 | 
            +
                when :md
         | 
| 18 | 
            +
                  "w-11/12 lg:w-1/2 sm:max-w-168"
         | 
| 19 | 
            +
                when :xl
         | 
| 20 | 
            +
                  "w-11/12 lg:w-3/4"
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
              def height_classes
         | 
| 25 | 
            +
                "max-h-full min-h-1/4 max-h-11/12"
         | 
| 26 | 
            +
              end
         | 
| 6 27 | 
             
            end
         | 
| @@ -32,12 +32,12 @@ | |
| 32 32 | 
             
                    </div>
         | 
| 33 33 | 
             
                  </div>
         | 
| 34 34 | 
             
                  <% if sidebar? %>
         | 
| 35 | 
            -
                    <div class="w-full sm:w-1/3 flex-shrink-0 h-full <%= white_panel_classes %>">
         | 
| 35 | 
            +
                    <div class="max-w-full sm:w-1/3 flex-shrink-0 h-full <%= white_panel_classes %>">
         | 
| 36 36 | 
             
                      <%= sidebar %>
         | 
| 37 37 | 
             
                    </div>
         | 
| 38 38 | 
             
                  <% end %>
         | 
| 39 39 | 
             
                  <% if bare_sidebar? %>
         | 
| 40 | 
            -
                    <div class="w-full sm:w-1/3 flex-shrink-0 h-full">
         | 
| 40 | 
            +
                    <div class="max-w-full sm:w-1/3 flex-shrink-0 h-full">
         | 
| 41 41 | 
             
                      <%= bare_sidebar %>
         | 
| 42 42 | 
             
                    </div>
         | 
| 43 43 | 
             
                  <% end %>
         | 
| @@ -3,4 +3,5 @@ | |
| 3 3 | 
             
            <%= hidden_field_tag :via_resource_class, params[:via_resource_class] if params[:via_resource_class] %>
         | 
| 4 4 | 
             
            <%= hidden_field_tag :via_resource_id, params[:via_resource_id] if params[:via_resource_id] %>
         | 
| 5 5 | 
             
            <%= hidden_field_tag :via_relation, params[:via_relation] if params[:via_relation] %>
         | 
| 6 | 
            +
            <%= hidden_field_tag :via_belongs_to_resource_class, params[:via_belongs_to_resource_class] if params[:via_belongs_to_resource_class] %>
         | 
| 6 7 | 
             
            <%= hidden_field_tag :referrer, back_path if params[:via_resource_class] %>
         | 
| @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            <%= content_tag :div,
         | 
| 2 | 
            +
              id: helpers.frame_id(@resource),
         | 
| 2 3 | 
             
              data: {
         | 
| 3 4 | 
             
                model_name: @resource.model_name.to_s,
         | 
| 4 5 | 
             
                resource_name: @resource.class.to_s,
         | 
| @@ -18,12 +19,16 @@ | |
| 18 19 | 
             
                multipart: true do |form| %>
         | 
| 19 20 | 
             
                <%= render Avo::ReferrerParamsComponent.new back_path: back_path %>
         | 
| 20 21 | 
             
                <%= content_tag :div, class: 'space-y-12' do %>
         | 
| 21 | 
            -
                  <%= render Avo::PanelComponent.new(name: title, description: @resource.resource_description, | 
| 22 | 
            +
                  <%= render Avo::PanelComponent.new(name: title, description: @resource.resource_description,
         | 
| 23 | 
            +
                                                     display_breadcrumbs: display_breadcrumbs?,
         | 
| 24 | 
            +
                                                     index: 0, data: { panel_id: "main" }) do |c| %>
         | 
| 22 25 | 
             
                    <% c.with_tools do %>
         | 
| 23 | 
            -
                       | 
| 24 | 
            -
                         | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 26 | 
            +
                      <% if back_path.present? %>
         | 
| 27 | 
            +
                        <%= a_link back_path,
         | 
| 28 | 
            +
                          style: :text,
         | 
| 29 | 
            +
                          icon: 'arrow-left' do %>
         | 
| 30 | 
            +
                          <%= t('avo.cancel').capitalize %>
         | 
| 31 | 
            +
                        <% end %>
         | 
| 27 32 | 
             
                      <% end %>
         | 
| 28 33 | 
             
                      <% if can_see_the_destroy_button? %>
         | 
| 29 34 | 
             
                        <%= a_link destroy_path,
         | 
| @@ -68,7 +73,7 @@ | |
| 68 73 | 
             
                        </div>
         | 
| 69 74 | 
             
                      <% end %>
         | 
| 70 75 | 
             
                    <% end %>
         | 
| 71 | 
            -
                    <% if sidebar.present? %>
         | 
| 76 | 
            +
                    <% if sidebar.present? && sidebar.visible_items.present? %>
         | 
| 72 77 | 
             
                      <% c.with_sidebar do %>
         | 
| 73 78 | 
             
                        <%= render sidebar_component form: form %>
         | 
| 74 79 | 
             
                      <% end %>
         | 
| @@ -4,11 +4,12 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent | |
| 4 4 | 
             
              include Avo::ResourcesHelper
         | 
| 5 5 | 
             
              include Avo::ApplicationHelper
         | 
| 6 6 |  | 
| 7 | 
            -
              def initialize(resource: nil, model: nil, actions: [], view: :edit)
         | 
| 7 | 
            +
              def initialize(resource: nil, model: nil, actions: [], view: :edit, display_breadcrumbs: true)
         | 
| 8 8 | 
             
                @resource = resource
         | 
| 9 9 | 
             
                @model = model
         | 
| 10 10 | 
             
                @actions = actions
         | 
| 11 11 | 
             
                @view = view
         | 
| 12 | 
            +
                @display_breadcrumbs = display_breadcrumbs
         | 
| 12 13 | 
             
              end
         | 
| 13 14 |  | 
| 14 15 | 
             
              def title
         | 
| @@ -16,6 +17,7 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent | |
| 16 17 | 
             
              end
         | 
| 17 18 |  | 
| 18 19 | 
             
              def back_path
         | 
| 20 | 
            +
                return if via_belongs_to?
         | 
| 19 21 | 
             
                return resource_view_path if via_resource?
         | 
| 20 22 | 
             
                return resources_path if via_index?
         | 
| 21 23 |  | 
| @@ -46,12 +48,20 @@ class Avo::Views::ResourceEditComponent < Avo::ResourceComponent | |
| 46 48 | 
             
                @resource.authorization.authorize_action @view, raise_exception: false
         | 
| 47 49 | 
             
              end
         | 
| 48 50 |  | 
| 51 | 
            +
              def display_breadcrumbs?
         | 
| 52 | 
            +
                @reflection.blank? && @display_breadcrumbs
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 49 55 | 
             
              private
         | 
| 50 56 |  | 
| 51 57 | 
             
              def via_index?
         | 
| 52 58 | 
             
                params[:via_view] == "index"
         | 
| 53 59 | 
             
              end
         | 
| 54 60 |  | 
| 61 | 
            +
              def via_belongs_to?
         | 
| 62 | 
            +
                params[:via_belongs_to_resource_class].present?
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 55 65 | 
             
              def is_edit?
         | 
| 56 66 | 
             
                view.in?([:edit, :update])
         | 
| 57 67 | 
             
              end
         | 
| @@ -107,6 +107,11 @@ module Avo | |
| 107 107 | 
             
                  @model = @resource.model_class.new
         | 
| 108 108 | 
             
                  @resource = @resource.hydrate(model: @model, view: :new, user: _current_user)
         | 
| 109 109 |  | 
| 110 | 
            +
                  # Handle special cases when creating a new record via a belongs_to relationship
         | 
| 111 | 
            +
                  if params[:via_belongs_to_resource_class].present?
         | 
| 112 | 
            +
                    return render turbo_stream: turbo_stream.append('attach_modal', partial: 'avo/base/new_via_belongs_to')
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
             | 
| 110 115 | 
             
                  set_actions
         | 
| 111 116 |  | 
| 112 117 | 
             
                  @page_title = @resource.default_panel_name.to_s
         | 
| @@ -130,7 +135,7 @@ module Avo | |
| 130 135 | 
             
                  @resource.hydrate(model: @model, view: :new, user: _current_user)
         | 
| 131 136 |  | 
| 132 137 | 
             
                  # This means that the record has been created through another parent record and we need to attach it somehow.
         | 
| 133 | 
            -
                  if params[:via_resource_id].present?
         | 
| 138 | 
            +
                  if params[:via_resource_id].present? && params[:via_belongs_to_resource_class].nil?
         | 
| 134 139 | 
             
                    @reflection = @model._reflections[params[:via_relation]]
         | 
| 135 140 | 
             
                    # Figure out what kind of association does the record have with the parent record
         | 
| 136 141 |  | 
| @@ -420,15 +425,19 @@ module Avo | |
| 420 425 | 
             
                end
         | 
| 421 426 |  | 
| 422 427 | 
             
                def create_success_action
         | 
| 428 | 
            +
                  return render "close_modal_and_reload_field" if params[:via_belongs_to_resource_class].present?
         | 
| 429 | 
            +
             | 
| 423 430 | 
             
                  respond_to do |format|
         | 
| 424 431 | 
             
                    format.html { redirect_to after_create_path, notice: create_success_message}
         | 
| 425 432 | 
             
                  end
         | 
| 426 433 | 
             
                end
         | 
| 427 434 |  | 
| 428 435 | 
             
                def create_fail_action
         | 
| 436 | 
            +
                  flash.now[:error] = create_fail_message
         | 
| 437 | 
            +
             | 
| 429 438 | 
             
                  respond_to do |format|
         | 
| 430 | 
            -
                    flash.now[:error] = create_fail_message
         | 
| 431 439 | 
             
                    format.html { render :new, status: :unprocessable_entity }
         | 
| 440 | 
            +
                    format.turbo_stream { render "create_fail_action" }
         | 
| 432 441 | 
             
                  end
         | 
| 433 442 | 
             
                end
         | 
| 434 443 |  | 
| @@ -119,6 +119,10 @@ module Avo | |
| 119 119 | 
             
                  Avo::Filters::BaseFilter.encode_filters(filter_params)
         | 
| 120 120 | 
             
                end
         | 
| 121 121 |  | 
| 122 | 
            +
                def frame_id(resource)
         | 
| 123 | 
            +
                  ["frame", resource.model_name.singular, resource.model.id].compact.join("-")
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 122 126 | 
             
                private
         | 
| 123 127 |  | 
| 124 128 | 
             
                # Taken from the original library
         | 
| @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            import { Controller } from '@hotwired/stimulus'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            // Used as a custom Stream Action <turbo-stream action="update-belongs-to" />
         | 
| 4 | 
            +
            export default class extends Controller {
         | 
| 5 | 
            +
              static values = {
         | 
| 6 | 
            +
                polymorphic: Boolean,
         | 
| 7 | 
            +
                searchable: Boolean,
         | 
| 8 | 
            +
                targetName: String,
         | 
| 9 | 
            +
                relationName: String
         | 
| 10 | 
            +
              }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              beforeStreamRender(event) {
         | 
| 13 | 
            +
                const { relationName } = event.target.dataset
         | 
| 14 | 
            +
                if (event.target.action !== "update-belongs-to" || this.relationNameValue !== relationName) {
         | 
| 15 | 
            +
                  return false;
         | 
| 16 | 
            +
                }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                event.detail.render = (stream) => {
         | 
| 19 | 
            +
                  if (this.searchableValue) {
         | 
| 20 | 
            +
                    this.updateSearchable(stream)
         | 
| 21 | 
            +
                  } else{
         | 
| 22 | 
            +
                    this.updateNonSearchable(stream)
         | 
| 23 | 
            +
                  }
         | 
| 24 | 
            +
                };
         | 
| 25 | 
            +
              }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              updateSearchable(stream) {
         | 
| 28 | 
            +
                // Update the id component
         | 
| 29 | 
            +
                document.querySelector(`input[name="${this.targetNameValue}"][type="hidden"]`).value = stream.dataset.targetResourceId
         | 
| 30 | 
            +
                // Update the label
         | 
| 31 | 
            +
                document.querySelector(`input[name="${this.targetNameValue}"][type="text"]`).value = stream.dataset.targetResourceLabel
         | 
| 32 | 
            +
              }
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              updateNonSearchable(stream) {
         | 
| 35 | 
            +
                const select = this.selectorContext(stream).querySelector(`select[name="${this.targetNameValue}"]`)
         | 
| 36 | 
            +
                const option = document.createElement('option')
         | 
| 37 | 
            +
                option.value = stream.dataset.targetResourceId
         | 
| 38 | 
            +
                option.text = stream.dataset.targetResourceLabel
         | 
| 39 | 
            +
                option.selected = true
         | 
| 40 | 
            +
                select.appendChild(option)
         | 
| 41 | 
            +
              }
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              selectorContext(stream) {
         | 
| 44 | 
            +
                // if polymorphic, search for the select in the correct sub-container
         | 
| 45 | 
            +
                if (this.polymorphicValue) {
         | 
| 46 | 
            +
                  return document.querySelector(`[data-type="${stream.dataset.targetResourceClass}"]`)
         | 
| 47 | 
            +
                }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                return document
         | 
| 50 | 
            +
              }
         | 
| 51 | 
            +
            }
         | 
| @@ -15,6 +15,7 @@ export default class extends BaseController { | |
| 15 15 | 
             
                enforceSuggestions: { type: Boolean, default: false },
         | 
| 16 16 | 
             
                closeOnSelect: { type: Boolean, default: false },
         | 
| 17 17 | 
             
                delimiters: { type: Array, default: [] },
         | 
| 18 | 
            +
                suggestionsMaxItems: { type: Number, default: 20 },
         | 
| 18 19 | 
             
                mode: String,
         | 
| 19 20 | 
             
                fetchValuesFrom: String,
         | 
| 20 21 | 
             
              }
         | 
| @@ -36,7 +37,7 @@ export default class extends BaseController { | |
| 36 37 | 
             
                  enforceWhitelist: this.enforceSuggestionsValue || this.fetchValuesFromValue,
         | 
| 37 38 | 
             
                  delimiters: this.delimitersValue.join('|'),
         | 
| 38 39 | 
             
                  dropdown: {
         | 
| 39 | 
            -
                    maxItems:  | 
| 40 | 
            +
                    maxItems: this.suggestionsMaxItemsValue,
         | 
| 40 41 | 
             
                    enabled: 0,
         | 
| 41 42 | 
             
                    searchKeys: [this.labelAttributeValue],
         | 
| 42 43 | 
             
                    closeOnSelect: this.closeOnSelectValue,
         | 
| @@ -21,6 +21,7 @@ import ModalController from './controllers/modal_controller' | |
| 21 21 | 
             
            import MultipleSelectFilterController from './controllers/multiple_select_filter_controller'
         | 
| 22 22 | 
             
            import PerPageController from './controllers/per_page_controller'
         | 
| 23 23 | 
             
            import ProgressBarFieldController from './controllers/fields/progress_bar_field_controller'
         | 
| 24 | 
            +
            import ReloadBelongsToFieldController from './controllers/fields/reload_belongs_to_field_controller'
         | 
| 24 25 | 
             
            import ResourceEditController from './controllers/resource_edit_controller'
         | 
| 25 26 | 
             
            import ResourceIndexController from './controllers/resource_index_controller'
         | 
| 26 27 | 
             
            import ResourceShowController from './controllers/resource_show_controller'
         | 
| @@ -70,6 +71,7 @@ application.register('date-field', DateFieldController) | |
| 70 71 | 
             
            application.register('easy-mde', EasyMdeController)
         | 
| 71 72 | 
             
            application.register('key-value', KeyValueController)
         | 
| 72 73 | 
             
            application.register('progress-bar-field', ProgressBarFieldController)
         | 
| 74 | 
            +
            application.register("reload-belongs-to-field", ReloadBelongsToFieldController)
         | 
| 73 75 | 
             
            application.register('trix-field', TrixFieldController)
         | 
| 74 76 |  | 
| 75 77 | 
             
            // Custom controllers
         | 
| @@ -17,6 +17,7 @@ | |
| 17 17 | 
             
                  <% c.with_heading do %>
         | 
| 18 18 | 
             
                    <%= @action.action_name %>
         | 
| 19 19 | 
             
                  <% end %>
         | 
| 20 | 
            +
             | 
| 20 21 | 
             
                  <div class="flex-1 flex">
         | 
| 21 22 | 
             
                    <%= @action.get_message %>
         | 
| 22 23 | 
             
                  </div>
         | 
| @@ -34,6 +35,7 @@ | |
| 34 35 | 
             
                      <% end %>
         | 
| 35 36 | 
             
                    </div>
         | 
| 36 37 | 
             
                  <% end %>
         | 
| 38 | 
            +
             | 
| 37 39 | 
             
                  <% c.with_controls do %>
         | 
| 38 40 | 
             
                    <%= a_button type: :button,
         | 
| 39 41 | 
             
                      data: { action: 'click->modal#close' },
         | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            <%= turbo_frame_tag "new_via_belongs_to" do %>
         | 
| 2 | 
            +
              <%= render(Avo::ModalComponent.new(width: :xl, body_class: "bg-application")) do |c| %>
         | 
| 3 | 
            +
                <div class="pt-4 pb-8">
         | 
| 4 | 
            +
                  <%= render Avo::Views::ResourceEditComponent.new(
         | 
| 5 | 
            +
                    resource: @resource,
         | 
| 6 | 
            +
                    model: @model,
         | 
| 7 | 
            +
                    view: @view,
         | 
| 8 | 
            +
                    display_breadcrumbs: false
         | 
| 9 | 
            +
                  ) %>
         | 
| 10 | 
            +
                </div>
         | 
| 11 | 
            +
              <% end %>
         | 
| 12 | 
            +
            <% end %>
         | 
| @@ -0,0 +1,8 @@ | |
| 1 | 
            +
            <%= turbo_stream.remove("new_via_belongs_to") %>
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            <turbo-stream action="update-belongs-to"
         | 
| 4 | 
            +
                          data-relation-name="<%= params[:via_relation] %>"
         | 
| 5 | 
            +
                          data-target-resource-id="<%= @model.id %>"
         | 
| 6 | 
            +
                          data-target-resource-label="<%= @resource.label %>"
         | 
| 7 | 
            +
                          data-target-resource-class="<%= @model.class.name %>">
         | 
| 8 | 
            +
            </turbo-stream>
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            <%= turbo_stream.replace(frame_id(@resource), template: "avo/base/new")  %>
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            <%= turbo_stream.append "alerts" do %>
         | 
| 4 | 
            +
              <%= render Avo::FlashAlertsComponent.new flashes: flash %>
         | 
| 5 | 
            +
            <% end %>
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            <% if @model.errors.any? %>
         | 
| 8 | 
            +
              <%= turbo_stream.append("alerts") do %>
         | 
| 9 | 
            +
                <% @model.errors.full_messages.each do |message| %>
         | 
| 10 | 
            +
                  <%= render Avo::AlertComponent.new :error, message %>
         | 
| 11 | 
            +
                <% end %>
         | 
| 12 | 
            +
              <% end %>
         | 
| 13 | 
            +
            <% end %>
         |